diff --git a/doc/qtcreator/images/icons/jumpto.png b/doc/qtcreator/images/icons/jumpto.png index a913b47d8c1..f56df24647b 100644 Binary files a/doc/qtcreator/images/icons/jumpto.png and b/doc/qtcreator/images/icons/jumpto.png differ diff --git a/doc/qtcreator/images/icons/objectsmap.png b/doc/qtcreator/images/icons/objectsmap.png index ca01cdc9113..61b4e253cac 100644 Binary files a/doc/qtcreator/images/icons/objectsmap.png and b/doc/qtcreator/images/icons/objectsmap.png differ diff --git a/doc/qtcreator/images/qtcreator-boot2qt-device-configurations.png b/doc/qtcreator/images/qtcreator-boot2qt-device-configurations.png index cddc2ea4d2f..584c595dcb3 100644 Binary files a/doc/qtcreator/images/qtcreator-boot2qt-device-configurations.png and b/doc/qtcreator/images/qtcreator-boot2qt-device-configurations.png differ diff --git a/doc/qtcreator/images/qtcreator-build-run-options-cmake.png b/doc/qtcreator/images/qtcreator-build-run-options-cmake.png deleted file mode 100644 index f95f905883a..00000000000 Binary files a/doc/qtcreator/images/qtcreator-build-run-options-cmake.png and /dev/null differ diff --git a/doc/qtcreator/images/qtcreator-build-settings-default.png b/doc/qtcreator/images/qtcreator-build-settings-default.png index 2f5fb01edff..82b48e93a7c 100644 Binary files a/doc/qtcreator/images/qtcreator-build-settings-default.png and b/doc/qtcreator/images/qtcreator-build-settings-default.png differ diff --git a/doc/qtcreator/images/qtcreator-build-settings-qmake.png b/doc/qtcreator/images/qtcreator-build-settings-qmake.png deleted file mode 100644 index 6371c617250..00000000000 Binary files a/doc/qtcreator/images/qtcreator-build-settings-qmake.png and /dev/null differ diff --git a/doc/qtcreator/images/qtcreator-cmake-build-settings-initial.png b/doc/qtcreator/images/qtcreator-cmake-build-settings-initial.png index ca0dbd741b3..3392c8ee671 100644 Binary files a/doc/qtcreator/images/qtcreator-cmake-build-settings-initial.png and b/doc/qtcreator/images/qtcreator-cmake-build-settings-initial.png differ diff --git a/doc/qtcreator/images/qtcreator-cmake-build-settings.png b/doc/qtcreator/images/qtcreator-cmake-build-settings.png index 30491794188..9c88b064d67 100644 Binary files a/doc/qtcreator/images/qtcreator-cmake-build-settings.png and b/doc/qtcreator/images/qtcreator-cmake-build-settings.png differ diff --git a/doc/qtcreator/images/qtcreator-cmakeexecutable.png b/doc/qtcreator/images/qtcreator-cmakeexecutable.png deleted file mode 100755 index 7f49186f271..00000000000 Binary files a/doc/qtcreator/images/qtcreator-cmakeexecutable.png and /dev/null differ diff --git a/doc/qtcreator/images/qtcreator-custom-parser-list.png b/doc/qtcreator/images/qtcreator-custom-parser-list.png index 5446372e66f..1758b1626d1 100644 Binary files a/doc/qtcreator/images/qtcreator-custom-parser-list.png and b/doc/qtcreator/images/qtcreator-custom-parser-list.png differ diff --git a/doc/qtcreator/images/qtcreator-devices-boot2qt.png b/doc/qtcreator/images/qtcreator-devices-boot2qt.png index a737732fa46..7adc10f4dc7 100644 Binary files a/doc/qtcreator/images/qtcreator-devices-boot2qt.png and b/doc/qtcreator/images/qtcreator-devices-boot2qt.png differ diff --git a/doc/qtcreator/images/qtcreator-kits-cmake.png b/doc/qtcreator/images/qtcreator-kits-cmake.png index 9ccecb61db1..404e90ce271 100644 Binary files a/doc/qtcreator/images/qtcreator-kits-cmake.png and b/doc/qtcreator/images/qtcreator-kits-cmake.png differ diff --git a/doc/qtcreator/images/qtcreator-preferences-build-run-qmake.png b/doc/qtcreator/images/qtcreator-preferences-build-run-qmake.png new file mode 100644 index 00000000000..b5129ada13e Binary files /dev/null and b/doc/qtcreator/images/qtcreator-preferences-build-run-qmake.png differ diff --git a/doc/qtcreator/images/qtcreator-preferences-cmake-general.png b/doc/qtcreator/images/qtcreator-preferences-cmake-general.png new file mode 100644 index 00000000000..fc096178c71 Binary files /dev/null and b/doc/qtcreator/images/qtcreator-preferences-cmake-general.png differ diff --git a/doc/qtcreator/images/qtcreator-preferences-cmake-tools.png b/doc/qtcreator/images/qtcreator-preferences-cmake-tools.png new file mode 100644 index 00000000000..c7abce19779 Binary files /dev/null and b/doc/qtcreator/images/qtcreator-preferences-cmake-tools.png differ diff --git a/doc/qtcreator/images/qtcreator-session-manager.png b/doc/qtcreator/images/qtcreator-session-manager.png index 22feaaf4117..18e337d2ba1 100644 Binary files a/doc/qtcreator/images/qtcreator-session-manager.png and b/doc/qtcreator/images/qtcreator-session-manager.png differ diff --git a/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdoc b/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdoc index 17f920bc0ab..2e8d893c197 100644 --- a/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdoc +++ b/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdoc @@ -47,6 +47,19 @@ Select \uicontrol {Kit Configuration} to edit the CMake settings for the build and run kit selected for the project. + \section CMake Presets + + You can use CMake presets files to specify common configure, build, and test + options and share them with others. \c CMakePresets.json contains options for + project-wide builds, whereas \c CMakeUserPresets.json contains options for + your local builds. + + Create the presets files in the format described in + \l{https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html} + {cmake-presets(7)} and store the in project's root directory. + + To use the preset files, specify the \c {--preset} option for your project. + \section1 Multi-Config Support \QC supports @@ -126,10 +139,10 @@ does not delete the initial configuration. To be asked before \QC resets the changes, select \uicontrol Edit > - \uicontrol Preferences > \uicontrol {Build & Run} > \uicontrol CMake > + \uicontrol Preferences > \uicontrol CMake > \uicontrol General > \uicontrol {Ask before re-configuring with initial parameters}. - \image qtcreator-build-run-options-cmake.png "CMake Build & Run options" + \image qtcreator-preferences-cmake-general.png "General tab in CMake Preferences" \section1 Viewing CMake Output @@ -205,8 +218,8 @@ \QC can automatically set up the \l {Setting Up Conan} {Conan package manager} for use with CMake. - Select \uicontrol Edit > \uicontrol Preferences > \uicontrol {Build & Run} > - \uicontrol CMake > \uicontrol {Package manager auto setup} to set the + Select \uicontrol Edit > \uicontrol Preferences > \uicontrol CMake + \uicontrol General > \uicontrol {Package manager auto setup} to set the value of the \c CMAKE_PROJECT_INCLUDE_BEFORE variable to the path to a CMake script that installs dependencies from a \c conanfile.txt, \c conanfile.py, or \c vcpkg.json file in the project source directory. diff --git a/doc/qtcreator/src/cmake/creator-projects-cmake.qdoc b/doc/qtcreator/src/cmake/creator-projects-cmake.qdoc index 76f16cc5ce2..441cfe3a179 100644 --- a/doc/qtcreator/src/cmake/creator-projects-cmake.qdoc +++ b/doc/qtcreator/src/cmake/creator-projects-cmake.qdoc @@ -74,10 +74,10 @@ \list 1 - \li Select \uicontrol Edit > \uicontrol Preferences > \uicontrol Kits > - \uicontrol CMake. + \li Select \uicontrol Edit > \uicontrol Preferences > \uicontrol CMake > + \uicontrol Tools. - \image qtcreator-cmakeexecutable.png + \image qtcreator-preferences-cmake-tools.png "Tools tab in CMake Preferences" \li The \uicontrol Name field displays a name for the CMake installation. @@ -107,7 +107,8 @@ To remove the selected CMake executable from the list, select \uicontrol Remove. - Select the \uicontrol Kits tab to add the CMake tool to a build and run kit. + To add the CMake tool to a build and run kit, select \uicontrol Edit > + \uicontrol Preferences > \uicontrol Kits. The kit also specifies the CMake generator that is used for producing project files for \QC and the initial configuration parameters: diff --git a/doc/qtcreator/src/howto/creator-only/creator-squish.qdoc b/doc/qtcreator/src/howto/creator-only/creator-squish.qdoc index 85d7e273e78..d6aafd4469e 100644 --- a/doc/qtcreator/src/howto/creator-only/creator-squish.qdoc +++ b/doc/qtcreator/src/howto/creator-only/creator-squish.qdoc @@ -242,7 +242,8 @@ To edit the object map of a test suite, select the \inlineimage icons/objectsmap.png - button next to the test suite in \uicontrol {Test Suites}. + (\uicontrol {Object Map}) button next to the test + suite in \uicontrol {Test Suites}. \image qtcreator-squish-symbolic-names.png "Symbolic Names view" diff --git a/doc/qtcreator/src/linux-mobile/b2qtdev.qdoc b/doc/qtcreator/src/linux-mobile/b2qtdev.qdoc index f7f3712547e..11167b35fef 100644 --- a/doc/qtcreator/src/linux-mobile/b2qtdev.qdoc +++ b/doc/qtcreator/src/linux-mobile/b2qtdev.qdoc @@ -99,14 +99,15 @@ \list 1 \li Select \uicontrol Edit > \uicontrol Preferences > \uicontrol Devices > \uicontrol Devices > \uicontrol Add > - \uicontrol Boot2Qt > \uicontrol {Finish}. + \uicontrol Boot2Qt. \image qtcreator-devices-boot2qt.png "Boot2Qt Network Device Setup wizard" \li In the \uicontrol {Device name} field, enter a name for the connection. \li In the \uicontrol {Device address} field, enter the host name or IP address of the device. This value will be available in the \c %{Device:HostAddress} variable. - \li Click \uicontrol {Next} to create the connection. + \li Click > \uicontrol {Finish} to test the connection and + add the device. You can edit the connection parameters in the \uicontrol Devices tab. The wizard does not show diff --git a/doc/qtcreator/src/overview/creator-only/creator-glossary.qdoc b/doc/qtcreator/src/overview/creator-only/creator-glossary.qdoc index bf8bdcad611..43959315b4e 100644 --- a/doc/qtcreator/src/overview/creator-only/creator-glossary.qdoc +++ b/doc/qtcreator/src/overview/creator-only/creator-glossary.qdoc @@ -125,6 +125,10 @@ if you need many build configurations for a single set of source files. + Shadow builds are also widely known as \e {out-of-source builds} + to separate them from \e {in-source builds} or + \e {in-tree builds}. + \endtable */ diff --git a/doc/qtcreator/src/projects/creator-only/creator-build-settings-qmake.qdoc b/doc/qtcreator/src/projects/creator-only/creator-build-settings-qmake.qdoc index d37d8615483..f70ba9a0ea4 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-build-settings-qmake.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-build-settings-qmake.qdoc @@ -50,13 +50,13 @@ > \uicontrol Preferences > \uicontrol {Build & Run} > \uicontrol Qmake. - \image qtcreator-build-settings-qmake.png "qmake build and run options" + \image qtcreator-preferences-build-run-qmake.png "Qmake tab in Build & Run Preferences" To set the default build properties, select \uicontrol Edit > \uicontrol Preferences > \uicontrol {Build & Run} > \uicontrol {Default Build Properties}. - \image qtcreator-build-settings-default.png "default build options" + \image qtcreator-build-settings-default.png "Default Build Properties tab in Build & Run Preferences" \section1 Compiling QML diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-build-systems.qdocinc b/doc/qtcreator/src/projects/creator-only/creator-projects-build-systems.qdocinc index 171e259c772..6604121d606 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-build-systems.qdocinc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-build-systems.qdocinc @@ -41,11 +41,7 @@ To change the location of the project directory, and to specify settings for building and running projects, select \uicontrol Edit > - \uicontrol Preferences > \uicontrol {Build & Run} > \uicontrol General. The - \uicontrol CMake tab contains additional settings for CMake. You can find - more settings for CMake in \uicontrol Edit > \uicontrol Preferences > - \uicontrol Kits > \uicontrol CMake and for Qbs in \uicontrol Edit > - \uicontrol Preferences > \uicontrol Qbs. + \uicontrol Preferences > \uicontrol {Build & Run} > \uicontrol General. To specify build and run settings for different target platforms, select \uicontrol Projects. For more information on the options you have, diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-building.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-building.qdoc index bfdca7d8d56..77805ea2aa1 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-building.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-building.qdoc @@ -99,9 +99,11 @@ \QC automatically runs CMake when you make changes to \c {CMakeLists.txt} files. To disable this feature, select \uicontrol Edit > - \uicontrol Preferences > \uicontrol Kits > \uicontrol CMake. Select the CMake - executable to edit, and then deselect the \uicontrol {Autorun CMake} check - box. + \uicontrol Preferences > \uicontrol CMake > \uicontrol Tools. Select the + CMake executable to edit, and then deselect the \uicontrol {Autorun CMake} + check box. + + \image qtcreator-preferences-cmake-tools.png "Tools tab in CMake Preferences" For more information, see \l {Setting Up CMake}. @@ -110,6 +112,10 @@ To prevent failures on incremental builds, it might make sense to always run qmake before building, even though it means that building will take more time. To enable this option, select \uicontrol Edit - > \uicontrol Preferences > \uicontrol {Build & Run} > \uicontrol qmake > + > \uicontrol Preferences > \uicontrol {Build & Run} > \uicontrol Qmake > \uicontrol {Run qmake on every build}. + + \image qtcreator-preferences-build-run-qmake.png "qmake tab in Build & Run Preferences" + + For more information, see \l {qmake Build Configuration}. */ diff --git a/doc/qtcreator/src/projects/creator-only/creator-projects-creating.qdoc b/doc/qtcreator/src/projects/creator-only/creator-projects-creating.qdoc index 8b2f050adc8..418b4083b6f 100644 --- a/doc/qtcreator/src/projects/creator-only/creator-projects-creating.qdoc +++ b/doc/qtcreator/src/projects/creator-only/creator-projects-creating.qdoc @@ -167,8 +167,8 @@ not use any default classes. \row \li Code Snippet - \li Creates a qmake project from a code snippet. When fixing bug - reports that contain a code snippet, you can place the code + \li Creates a qmake project from a code snippet. When working on + bug reports that contain a code snippet, you can place the code snippet into a project to compile and check it. \row \li {1,4} Non-Qt Project diff --git a/doc/qtcreatordev/src/qtcreator-documentation.qdoc b/doc/qtcreatordev/src/qtcreator-documentation.qdoc index 08ab05b3fc9..58ccbb3c88e 100644 --- a/doc/qtcreatordev/src/qtcreator-documentation.qdoc +++ b/doc/qtcreatordev/src/qtcreator-documentation.qdoc @@ -299,22 +299,26 @@ users, but always place example values also in the text. \list - \li Use the screen resolution of 1366x768 (available on the most + \li Use the screen resolution of 1920x1080 (available on the most commonly used screens, as of this writing). - - \li Use the aspect ratio of 16:9. - - \li Open the application in the maximum size on full screen. + \note Use display scaling 100%. \li Use your favorite tool to take the screen shot. \li Include only the part of the screen that you need (you can crop the - image also in the screen capture tool). + image also in the screen capture tool). In \QDS, close all + unnecessary views to avoid clutter. + + \li Do not scale or resize the images in the tool because it causes + reduced visual quality and bigger file size. Also, the CSS we use + online scales down images if needed (their width is larger than 800 + pixels). \li To highlight parts of the screen shot, use the images of numbers - that are stored in \c{doc/images/numbers} in the \QC repository. + that are stored in \c{qtcreator/doc/qtcreator/images/numbers} in + the \QC repository. - \li Before you submit the images to the repository, optimize them to + \li Before you submit PNG images to the repository, optimize them to save space. \endlist @@ -322,9 +326,9 @@ You can use number icons in screenshots to highlight parts of the screenshot (instead of using red arrows or borders, or something similar). You can then - refer to the numbers in text. For and example, see the - \l{https://doc.qt.io/qt/topics-app-development.html}{Development Tools} - topic in the Qt reference documentation. + refer to the numbers in text. For an example, see the + \l{https://doc.qt.io/qtcreator/creator-quick-tour.html}{User Interface} + topic in the \QC Manual. This improves the consistency of the look and feel of Qt documentation, and eliminates the need to describe parts of the UI in the text because @@ -332,8 +336,8 @@ brackets. You can find a set of images that show the numbers from 1 to 10 in the - \c doc/images/numbers directory (or in the \c qtdoc module sources in - \c doc/images/numbers). + \c qtcreator/doc/qtcreator/images/numbers directory (or in the \c qtdoc + module sources in \c doc/images/numbers). To use the numbers, copy-paste the number images on the screenshot to the places that you want to refer to from text. @@ -358,6 +362,13 @@ recolors icons in \c qtcreator/doc/qtcreator/images/icons. Use the \c -docspath option to specify the path to another icon source directory. + For example, if you saved the new icons in \c {C:\iconconversions}, switch to + the \c {qtcreator\src\tools\icons} folder and enter: + + \badcode + recolordocsicons.py -docspath C:\iconconversions + \endcode + To run the script, you will need to install the following tools and add them to the PATH: @@ -367,20 +378,26 @@ \li optipng \endlist + \section2 Saving Images + + Save images in PNG or WebP format in the \QC project folder in the + \c doc/qtcreator/images or \c doc/qtdesignstudio/images folder. Binary + images can easily add megabytes to the Git + history. To keep the history as small as possible, the Git post-commit hooks + remind you to try to keep image file size below 50 kilobytes. To achieve this + goal, crop images so that only relevant information is visible in them. + + If your screenshot contains lots of colorful content or a photo, for example, + consider saving it in WebP format for a smaller image file size. + \section2 Optimizing Images - Save images in the PNG format in the \QC project folder in the - \c {doc/images} folder. Binary images can easily add megabytes to the Git - history. To keep the history as small as possible, the Git post-commit hooks - remind you to try to keep image size below 50 kilobytes. To achieve this - goal, crop images so that only relevant information is visible in them. - Before committing images, optimize them by using an image optimization tool. - - Optimization should not visibly reduce image quality. If it does, do not do - it. + Before committing PNG images, optimize them by using an image optimization + tool. Optimization should not visibly reduce image quality. If it does, try + saving the image as WebP instead. You can use a web service, such as \l{https://tinypng.com}, or an image - optimization tool to shrink the images. For example, you can use the Radical + optimization tool to shrink PNG images. For example, you can use the Radical Image Optimization Tool (RIOT) or OptiPNG on Windows, ImageOptim on \macos, or some other tool available on Linux. @@ -425,6 +442,16 @@ optipng -o 7 -strip all doc/images/ \endcode + \section2 Creating GIF Videos + + Sometimes it is easier to explain how something works by recording + a short GIF video. You can use any tool you like, for example + \l {https://www.screentogif.com/}{ScreenToGif}. GIF videos are typically + bigger than screenshots, so try to make them as short and to the point as + you can. + + Use the \c {\image} command to add GIF files to the documentation. + \section2 Linking to Youtube Videos You can use the \c {\youtube} macro to link to a video on Youtube. The HTML @@ -477,11 +504,7 @@ \section2 Setting Up Documentation Builds - For more information about setting up the build environment with a - self-built Qt if you do not want to build the whole Qt, see - \l{https://wiki.qt.io/Building_Qt_Documentation}{Building Qt Documentation} - on the Qt wiki. - + You can use an installed Qt version to build the documentation. The content and formatting of documentation are separated in QDoc. The documentation configuration, style sheets, and templates have changed over time, so they differ between Qt and \QC versions. @@ -532,7 +555,7 @@ For example (all on one line): \badcode C:\dev\qtc-doc-build>cmake -DWITH_DOCS=ON - "-DCMAKE_PREFIX_PATH=C:\Qt\5.15.1\msvc2019_64" + "-DCMAKE_PREFIX_PATH=C:\Qt\6.4.0\msvc2019_64" C:\dev\qtc-super\qtcreator \endcode \li To also build Extending \QC Manual, add the following option: @@ -544,7 +567,7 @@ \badcode C:\dev\qtc-doc-build>cmake -DWITH_DOCS=ON -DBUILD_DEVELOPER_DOCS=ON "-DCMAKE_MODULE_PATH=C:\dev\tqtc-plugin-qtquickdesigner\studiodata\branding" - "-DCMAKE_PREFIX_PATH=C:\Qt\5.15.1\msvc2019_64" + "-DCMAKE_PREFIX_PATH=C:\Qt\6.4.0\msvc2019_64" C:\dev\qtc-super\qtcreator \endcode \li To build the docs using the online style, use the following option @@ -556,7 +579,7 @@ C:\dev\qtc-doc-build>cmake -DWITH_ONLINE_DOCS=ON -DBUILD_DEVELOPER_DOCS=ON "-DCMAKE_MODULE_PATH=C:\dev\tqtc-plugin-qtquickdesigner\studiodata\branding" - "-DCMAKE_PREFIX_PATH=C:\Qt\5.15.1\msvc2019_64" + "-DCMAKE_PREFIX_PATH=C:\Qt\6.4.0\msvc2019_64" C:\dev\qtc-super\qtcreator \endcode \note If you already ran CMake \c {-DWITH_DOCS=ON} in a folder and diff --git a/doc/qtdesignstudio/examples/doc/StateTransitions.qdoc b/doc/qtdesignstudio/examples/doc/StateTransitions.qdoc index 4352e0c5f73..2c2729d77ad 100644 --- a/doc/qtdesignstudio/examples/doc/StateTransitions.qdoc +++ b/doc/qtdesignstudio/examples/doc/StateTransitions.qdoc @@ -4,7 +4,7 @@ /*! \page state-transition-animations.html \ingroup gstutorials - \sa States, {Transitions}, {Adding States} + \sa States, {Transitions}, {Working with States} \title Animated State Transitions \brief Illustrates how to create animated state transitions. @@ -12,7 +12,7 @@ \image animated-state-transitions.jpg The \e{Animated State Transitions} tutorial illustrates how you can animate - the transition between \l{Adding States}{states}. + the transition between \l{Working with States}{states}. The starting point of this tutorial is the Car Demo project, you can download it from diff --git a/doc/qtdesignstudio/examples/doc/images/loginui1-library-assets.jpg b/doc/qtdesignstudio/examples/doc/images/loginui1-library-assets.jpg index 999959b20bf..4dc8e4e910c 100644 Binary files a/doc/qtdesignstudio/examples/doc/images/loginui1-library-assets.jpg and b/doc/qtdesignstudio/examples/doc/images/loginui1-library-assets.jpg differ diff --git a/doc/qtdesignstudio/examples/doc/images/loginui1-project.png b/doc/qtdesignstudio/examples/doc/images/loginui1-project.png index 93ca7c6c8c9..28984175b94 100644 Binary files a/doc/qtdesignstudio/examples/doc/images/loginui1-project.png and b/doc/qtdesignstudio/examples/doc/images/loginui1-project.png differ diff --git a/doc/qtdesignstudio/examples/doc/loginui1.qdoc b/doc/qtdesignstudio/examples/doc/loginui1.qdoc index c75dea8b70d..c10c118fdd6 100644 --- a/doc/qtdesignstudio/examples/doc/loginui1.qdoc +++ b/doc/qtdesignstudio/examples/doc/loginui1.qdoc @@ -8,23 +8,18 @@ \title Log In UI - Components \brief Illustrates how to use wizard templates to create a simple UI - wireframe that contains a text label, images, and push buttons. + that contains a text label, images, and push buttons. \image loginui1.jpg - \e{Log In UI - Components} is the first in a series of tutorials that build - on each other to illustrate how to use \QDS to create a simple UI with some - basic UI components, such as pages, buttons, and fields. The first tutorial - in the series describes how to use the \QDS wizard templates to create a - project and a button UI control, and how to modify the files generated by - the wizard templates to wireframe the UI. + \e{Log In UI - Components} is the first tutorial in a series of tutorials + that describes how to use the \QDS wizard templates to create a project and + a button UI control, and how to modify the files generated by the wizard + templates to design the UI. You can donwnload the completed project from \l{https://git.qt.io/public-demos/qtdesign-studio/-/tree/master/tutorial%20projects/Loginui1}{here}. - The \e {Learn More} sections provide additional information about the - tasks performed by the wizards and about other basic tasks and concepts. - \section1 Creating the UI Project For the purposes of this tutorial, you will use the empty wizard template. @@ -47,6 +42,7 @@ \li Set \uicontrol Width to 720 and \uicontrol Height to 1280. You can change the screen size later in \l Properties. \endlist + \li Select \uicontrol Create to create the project. \endlist @@ -57,7 +53,7 @@ The wizard constructs the \e Screen01 \l{glossary-component}{component} using instances of a \l{basic-rectangle}{Rectangle} component that forms - the background and a \l Text component that displays some text. + the background, a \l Button, and a \l Text component that displays some text. \note The visibility of views depends on the selected workspace, so your \QDS might look somewhat different from the above image. @@ -65,53 +61,14 @@ in the \uicontrol Design mode. For more information about moving views around, see \l {Managing Workspaces}. - \section2 Learn More - Projects and Files + You should remove this \l Button for now from the UI + to have a clean workspace. You'll add this later in the course of the tutorial. + Then you shall know how to modify and adjust it as you need. - \QDS creates a set of boilerplate files and folders that you need to create - a UI. The files are listed in the \l{File System} view. - - \image loginui1-project-files.png - - \list - \li The \e {loginui1.qmlproject} project file defines that all - component, JavaScript, and image files in the project folder belong - to the project. Therefore, you do not need to individually list new - files when you add them to the project. - \li The \e {loginui1.qml} file defines the functionality of - the UI. For the time being, it does not do anything. - \li The \e {Screen01.ui.qml} file is a custom component created by - the wizard template. For more information, see \l {UI Files}. - - By default, this is the main file in the project, but you can - change that in the .qmlproject file. While the custom component - is a good starting point for new users, you don't have to use it. - Specifically, if you export and import designs using \QB, your main - file is most likely called something else. For more information, - see \l {Exporting from Design Tools}. - \li The \e CMakeLists.txt project configuration file allowing you to - share your project as a fully working C++ application with - developers. - \li The \e {qtquickcontrols2.conf} file specifies the selected - \l {Styling Qt Quick Controls}{UI style} and some style-specific - arguments. - \li The \e imports folder contains \e {Constants.qml} and - \e {DirectoryFontLoader.qml} files that specify a font loader - and a \e qmldir module definition file that declares the Constant - component. For more information, see - \l {Module Definition qmldir Files}. The \e EventListModel.qml and - \e EventListSimulator.qml files are not used in this example, so - you can ignore them for now. - \endlist - - \l{UI Files}{UI files} define a hierarchy of components with a - highly-readable, structured layout. Every UI file consists of two parts: - an imports section and an component declaration section. The components and - functionality most common to UIs are provided in the \c QtQuick import. You - can view the code of a \e .ui.qml file in the \l{Code} view. + To remove this Button, just select it and press \key {Backspace}. Next, you will edit the values of the properties of the component instances to create the main page of the UI. - \section1 Creating the Main Page You will now change the values of the properties of the \l Text component @@ -123,9 +80,9 @@ You can download the logo and the background image from here: \list - \li \l{https://doc.qt.io/qtdesignstudio/images/used-in-examples/loginui1/images/qt_logo_green_128x128px.png} + \li \l{https://git.qt.io/public-demos/qtdesign-studio/-/blob/master/tutorial%20projects/Loginui1/content/images/qt_logo_green_128x128px.png} {qt_logo_green_128x128px.png} - \li \l{https://doc.qt.io/qtdesignstudio/images/used-in-examples/loginui1/images/adventurePage.jpg} + \li \l{https://git.qt.io/public-demos/qtdesign-studio/-/blob/master/tutorial%20projects/Loginui1/content/images/adventurePage.jpg} {Background image} (\e adventurePage.jpg) Photo by \l{https://unsplash.com/photos/a2MgJdG6UvE} @@ -139,7 +96,8 @@ To add the assets: \list 1 \li Select \uicontrol Assets > \inlineimage icons/plus.png - . + (Select \uicontrol View > \uicontrol Views > \uicontrol Assets to enable it, + if you can't find it). \li Select the asset files, and then select \uicontrol Open. \li Select the location where the files will be saved in the \uicontrol {Add Resources} dialog. @@ -158,8 +116,8 @@ To modify the \e Screen01 component in the \uicontrol {2D} view: \list 1 - \li Drag-and-drop the background image from \uicontrol Assets to the - rectangle in \l Navigator. + \li Drag-and-drop the background image (1) from \uicontrol Assets to the + \l{basic-rectangle}{Rectangle} (2) in \l Navigator. \image loginui1-library-assets.jpg "Assets view" \li \QDS automatically creates an instance of the \l{Images}{Image} component for you with the path to the image file set as the @@ -167,7 +125,7 @@ \image loginui1-image-properties.png "Image properties" \li Drag-and-drop the Qt logo from \uicontrol Assets to the rectangle in \uicontrol Navigator and move it to the top-center of the - background image in \uicontrol the {2D} view. + background image in the \uicontrol {2D} view. \li Select \e Text in \uicontrol Navigator and drag it below the logo in the \uicontrol {2D} view. If the text is hidden behind the background, select \inlineimage icons/navigator-arrowdown.png @@ -182,8 +140,8 @@ line: \e {Are you ready to explore?}. \image loginui1-text-properties.png "Text properties" \li In \uicontrol Font, select \e {Titillium Web ExtraLight}. - \li In \uicontrol Size, set the font size of the tag line to - \e 50 pixels (\uicontrol px). + \li In \uicontrol Size, first select the scale to pixels (\uicontrol px), + then set font size of the tag line to \e 50 (\uicontrol px). \li In \uicontrol {Text color}, set the text color to white (\e #ffffff). \endlist @@ -197,74 +155,6 @@ \image loginui1-main-page.jpg "Modified UI in the Design mode" You can resize the preview dialog to display the whole screen. - - \section2 Learn More - Components - - \QDS provides preset \l{glossary-component}{components} for creating - UIs, including components for creating and animating visual components, - receiving user input, and creating data models and views. - - To be able to use the functionality of preset components, the wizard template - adds the following \e import statements to the UI files (.ui.qml) that it - creates: - - \quotefromfile Loginui1/content/Screen01.ui.qml - \skipto import - \printuntil Controls - - You can view the import statements in the \uicontrol {Code} view. - - The \l Components view lists the components in each module that are - supported by \QDS. You can use the basic components to create your own - components, and they will be listed in \uicontrol {My Components}. - This section is only visible if you have created custom components. - - The \l {basic-rectangle}{Rectangle}, \l Text, and \l {Images}{Image} - components used in this tutorial are based on the \l Item component. - It is the base component for all visual elements, with implementation - of basic functions and properties, such as component type, ID, position, - size, and visibility. - - For more information, see \l{Use Case - Visual Elements In QML}. For - descriptions of all components, see \l{All QML Types} in the Qt reference - documentation. - - \section3 Regtangle Properties - - The default \l {basic-rectangle}{Rectangle} component is used for drawing - shapes with four sides and four corners. You can fill rectangles either with - a solid fill color or a gradient. You can specify the border color separately. - By setting the value of the radius property, you can create shapes with - rounded corners. - - If you want to specify the radius of each corner separately, you can use the - \l{studio-rectangle}{Rectangle} component from the - \uicontrol {Qt Quick Studio Components} module instead of the basic rectangle - component. It is available in \uicontrol Components - > \uicontrol {Qt Quick Studio Components}. - - \section3 Text Properties - - The \l Text component is used for adding static text to the UI, such as - titles and labels. You can select the font to use and specify extensive - properties for each text component, such as size in points or pixels, - weight, style, and spacing. - - If you want to create a label with a background, use the \l Label component - from the \uicontrol {Qt Quick Controls} module instead of the Text component. - - \section3 Image Properties - - The \l {Images}{Image} component is used for adding images to the UI in several - supported formats, including bitmap formats such as PNG and JPEG and vector - graphics formats such as SVG. To add an image to \uicontrol Assets, select - \inlineimage icons/plus.png - , and then select the image file. - - If you need to display animated images, use the \l {Animated Image} - component, also available in \uicontrol Components > - \uicontrol {Default Components} > \uicontrol Basic. - \section1 Creating a Push Button You can use another wizard template to create a push button and to add it to @@ -298,21 +188,9 @@ \image loginui1-button.png "Button in the Design mode." - \section2 Learn More - UI Controls - - The \e {Custom Button} wizard template creates a button component - based on the \l {Button} control in the \l {Qt Quick Controls} module. It - is a push-button control that can be pushed or clicked by the user. Buttons - are normally used to perform an action or to answer a question. The - properties and functionality inherited from the Button component enable - you to set text, display an icon, react to mouse clicks, and so on. - - To be able to use the functionality of the Button control, the wizard template - adds the following \e import statements to the \e EntryField.ui.qml file: - - \quotefromfile Loginui1/content/EntryField.ui.qml - \skipto import - \printuntil Controls + \note To open the \uicontrol States view, select it from + \uicontrol View > \uicontrol Views > \uicontrol States, if + it is not available by default. Next, you will change the appearance of the EntryField component by modifying its properties. @@ -353,8 +231,8 @@ properties in \uicontrol Properties. \li In \uicontrol Character > \uicontrol Font, select \e {Titillium Web ExtraLight}. - \li In \uicontrol Size, set the font size to \e 34 pixels - (\uicontrol px). + \li In \uicontrol Size, first select the scale to pixels (\uicontrol px), + then set font size to \e 34 (\uicontrol px). \li In \uicontrol {Text color}, set the text color to white (\e #ffffff). \li In \uicontrol {Alignment H}, select the \uicontrol Left button to @@ -374,6 +252,10 @@ \image loginui1-entry-field-styled.jpg "Modified button in the 2D view" + \note Do not edit the the value of \uicontrol Text in the \uicontrol Character + property, because this will break the connection, and later you won't be able + to change the text in \uicontrol {Button Content} > \uicontrol Text. + Next, you will add instances of the \e EntryField component to the \e Screen01 component and modify their properties. @@ -437,7 +319,8 @@ its properties in \uicontrol Properties. \li In \uicontrol Character > \uicontrol Font, select \e {Titillium Web ExtraLight}. - \li In \uicontrol Size, set the font size to \e 34 pixels. + \li In \uicontrol Size, first select the scale to pixels (\uicontrol px), + then set font size to \e 34 (\uicontrol px). \li In \uicontrol {Text color}, set the text color to \e #41cd52. \li In the \uicontrol States view, select the \e normal state and repeat the changes, as necessary. @@ -481,7 +364,136 @@ \image loginui1-ready.jpg "The finished UI in the 2D view" - \section2 Learn More - Component IDs + \section1 Learn More + The \e {Learn More} sections provide additional information about the + tasks performed by the wizards and about other basic tasks and concepts. + + \section2 Projects and Files + \QDS creates a set of files and folders that you need to create + a UI. The files are listed in the \l{File System} view. + + \image loginui1-project-files.png + \list + \li The \e {loginui1.qmlproject} project file defines that all + component, JavaScript, and image files in the project folder belong + to the project. Therefore, you do not need to individually list new + files when you add them to the project. + \li The \e {loginui1.qml} file defines the functionality of + the UI. For the time being, it does not do anything. + \li The \e {Screen01.ui.qml} file is a custom component created by + the wizard template. For more information, see \l {UI Files}. + + By default, this is the main file in the project, but you can + change that in the .qmlproject file. While the custom component + is a good starting point for new users, you don't have to use it. + Specifically, if you export and import designs using \QB, your main + file is most likely called something else. For more information, + see \l {Exporting from Design Tools}. + \li The \e CMakeLists.txt project configuration file allowing you to + share your project as a fully working C++ application with + developers. + \li The \e {qtquickcontrols2.conf} file specifies the selected + \l {Styling Qt Quick Controls}{UI style} and some style-specific + arguments. + \li The \e imports folder contains \e {Constants.qml} and + \e {DirectoryFontLoader.qml} files that specify a font loader + and a \e qmldir module definition file that declares the Constant + component. For more information, see + \l {Module Definition qmldir Files}. The \e EventListModel.qml and + \e EventListSimulator.qml files are not used in this example, so + you can ignore them for now. + \endlist + \l{UI Files}{UI files} define a hierarchy of components with a + highly-readable, structured layout. Every UI file consists of two parts: + an imports section and an component declaration section. The components and + functionality most common to UIs are provided in the \c QtQuick import. You + can view the code of a \e .ui.qml file in the \l{Code} view. + + + \section2 Components + + \QDS provides preset \l{glossary-component}{components} for creating + UIs, including components for creating and animating visual components, + receiving user input, and creating data models and views. + + To be able to use the functionality of preset components, the wizard template + adds the following \e import statements to the UI files (.ui.qml) that it + creates: + + \quotefromfile Loginui1/content/Screen01.ui.qml + \skipto import + \printuntil Controls + + You can view the import statements in the \uicontrol {Code} view. + + The \l Components view lists the components in each module that are + supported by \QDS. You can use the basic components to create your own + components, and they will be listed in \uicontrol {My Components}. + This section is only visible if you have created custom components. + + The \l {basic-rectangle}{Rectangle}, \l Text, and \l {Images}{Image} + components used in this tutorial are based on the \l Item component. + It is the base component for all visual elements, with implementation + of basic functions and properties, such as component type, ID, position, + size, and visibility. + + For more information, see \l{Use Case - Visual Elements In QML}. For + descriptions of all components, see \l{All QML Types} in the Qt reference + documentation. + + \section3 Regtangle Properties + + The default \l {basic-rectangle}{Rectangle} component is used for drawing + shapes with four sides and four corners. You can fill rectangles either with + a solid fill color or a gradient. You can specify the border color separately. + By setting the value of the radius property, you can create shapes with + rounded corners. + + If you want to specify the radius of each corner separately, you can use the + \l{studio-rectangle}{Rectangle} component from the + \uicontrol {Qt Quick Studio Components} module instead of the basic rectangle + component. It is available in \uicontrol Components + > \uicontrol {Qt Quick Studio Components}. + + \section3 Text Properties + + The \l Text component is used for adding static text to the UI, such as + titles and labels. You can select the font to use and specify extensive + properties for each text component, such as size in points or pixels, + weight, style, and spacing. + + If you want to create a label with a background, use the \l Label component + from the \uicontrol {Qt Quick Controls} module instead of the Text component. + + \section3 Image Properties + + The \l {Images}{Image} component is used for adding images to the UI in several + supported formats, including bitmap formats such as PNG and JPEG and vector + graphics formats such as SVG. To add an image to \uicontrol Assets, select + \inlineimage icons/plus.png + , and then select the image file. + + If you need to display animated images, use the \l {Animated Image} + component, also available in \uicontrol Components > + \uicontrol {Default Components} > \uicontrol Basic. + + \section2 UI Controls + + The \e {Custom Button} wizard template creates a button component + based on the \l {Button} control in the \l {Qt Quick Controls} module. It + is a push-button control that can be pushed or clicked by the user. Buttons + are normally used to perform an action or to answer a question. The + properties and functionality inherited from the Button component enable + you to set text, display an icon, react to mouse clicks, and so on. + + To be able to use the functionality of the Button control, the wizard template + adds the following \e import statements to the \e EntryField.ui.qml file: + + \quotefromfile Loginui1/content/EntryField.ui.qml + \skipto import + \printuntil Controls + + \section2 Component IDs Each component and each instance of a component has an \e ID that uniquely identifies it and enables other components' properties to be bound to it. diff --git a/doc/qtdesignstudio/examples/doc/loginui2.qdoc b/doc/qtdesignstudio/examples/doc/loginui2.qdoc index 8b8787cbf4d..5fbf1750449 100644 --- a/doc/qtdesignstudio/examples/doc/loginui2.qdoc +++ b/doc/qtdesignstudio/examples/doc/loginui2.qdoc @@ -76,13 +76,11 @@ rectangle at the top, while keeping its horizontal center aligned with that of the rectangle. \li Select \e tagLine in \uicontrol Navigator. - \li In \uicontrol Properties > \uicontrol Layout, deselect the - \inlineimage icons/anchor-center-vertical.png - button to remove the vertical center anchor, and then select the - \inlineimage icons/anchor-top.png - button to anchor the tag line to - \e qt_logo_green_128x128px in the \uicontrol Target field with a - 40-pixel margin. This attaches the top of the tag line to the + \li In \uicontrol Properties > \uicontrol Layout, + select the \inlineimage icons/anchor-top.png + button and then select \e qt_logo_green_128x128px + as \uicontrol Target to anchor \e tagLine with a 40-pixel margin. + This attaches the top of the tag line to the bottom of the logo, while keeping its horizontal center aligned with that of the rectangle. \image loginui2-layout-text.png "Text Layout properties" @@ -101,20 +99,6 @@ \image loginui2-loginpage.jpg "Login page in the Design mode and live preview" - \section2 Learn More - Anchors - - In an anchor-based layout, each component instance can be thought of as - having a set of invisible \e anchor lines: top, bottom, left, right, fill, - horizontal center, vertical center, and baseline. - - Anchors enable placing a component instance either adjacent to or inside of - another component instance, by attaching one or more of the instance's - anchor lines to the anchor lines of the other component instance. If a - component instance changes, the instances that are anchored to it will - adjust automatically to maintain the anchoring. - - For more information, see \l{Positioning with Anchors}. - \section1 Using Column Positioners You will now position the entry fields and buttons in columns @@ -150,7 +134,7 @@ \li Select \e fields in \uicontrol Navigator. \li In \uicontrol Properties > \uicontrol Layout, select the \inlineimage icons/anchor-top.png - button to anchor the top of the field column to + button to anchor the top of the fields column to the bottom of \e tagLine with a 170-pixel margin. \li Select the \inlineimage icons/anchor-center-horizontal.png button to anchor the column horizontally to its parent. @@ -170,7 +154,20 @@ \image loginui2-loginpage-ready.jpg "Login page in the Design mode and live preview" - \section1 Learn More - Positioners + \section1 Learn More + \section2 Anchors + In an anchor-based layout, each component instance can be thought of as + having a set of invisible \e anchor lines: top, bottom, left, right, fill, + horizontal center, vertical center, and baseline. + + Anchors enable placing a component instance either adjacent to or inside of + another component instance, by attaching one or more of the instance's + anchor lines to the anchor lines of the other component instance. If a + component instance changes, the instances that are anchored to it will + adjust automatically to maintain the anchoring. + + For more information, see \l{Positioning with Anchors}. + \section2 Positioners For many use cases, the best positioner to use is a simple grid, row, or column, and \QDS provides components that will position children in these diff --git a/doc/qtdesignstudio/examples/doc/loginui3.qdoc b/doc/qtdesignstudio/examples/doc/loginui3.qdoc index cf9d98b6cd3..59501c199dd 100644 --- a/doc/qtdesignstudio/examples/doc/loginui3.qdoc +++ b/doc/qtdesignstudio/examples/doc/loginui3.qdoc @@ -77,7 +77,7 @@ \section1 Using States to Simulate Page Changes - You will now add \l{Adding States}{states} to the UI to show and hide UI + You will now add \l{Working with States}{states} to the UI to show and hide UI components in the \uicontrol {2D} view, depending on the current page: \list 1 @@ -115,29 +115,6 @@ \image loginui3-login-state-preview.jpg "Preview of the login state" - \section2 Learn More - States - - The \l{Adding States}{state} of a particular visual component is the set of - information which describes how and where the individual parts of the visual - component are displayed within it, and all the data associated with that - state. Most visual components in a UI will have a limited number of states, - each with well-defined properties. - - For example, a list item may be either selected or not, and if - selected, it may either be the currently active single selection or it - may be part of a selection group. Each of those states may have certain - associated visual appearance (neutral, highlighted, expanded, and so on). - - Youn can apply states to trigger behavior or animations. UI components - typically have a default state that contains all of a component's initial - property values and is therefore useful for managing property values before - state changes. - - You can specify additional states by adding new states. Each state within a - component has a unique name. To change the current state of an component, - the state property is set to the name of the state. State changes can be - bound to conditions by using the \c when property. - Next, you will create connections to specify that clicking the \uicontrol {Create Account} button on the login page triggers a transition to the account creation page. @@ -162,9 +139,13 @@ \e createAccount should apply. \li Double-click the value \uicontrol Action column and select \uicontrol {Change state to createAccount} in the drop-down menu. - \image loginui3-connections.png "Connections tab" + \note Or, you can right-click the \e createAccount button in \l Navigator. + Then select \uicontrol {Connections} > \uicontrol {Add signal handler} > + \uicontrol {clicked} > \uicontrol {Change State to createAccount}. + \image loginui3-connections.png "Connections tab" \li Select \uicontrol File > \uicontrol Save or press \key {Ctrl+S} to save your changes. + \endlist In the live preview, you can now click the \uicontrol {Create Account} @@ -172,8 +153,30 @@ \image loginui3.gif "Moving between login page and account creation page" + \section1 Learn More + \section2 States + The \l{Working with States}{state} of a particular visual component is the set of + information which describes how and where the individual parts of the visual + component are displayed within it, and all the data associated with that + state. Most visual components in a UI will have a limited number of states, + each with well-defined properties. - \section2 Learn More - Signal and Event Handlers + For example, a list item may be either selected or not, and if + selected, it may either be the currently active single selection or it + may be part of a selection group. Each of those states may have certain + associated visual appearance (neutral, highlighted, expanded, and so on). + + Youn can apply states to trigger behavior or animations. UI components + typically have a default state that contains all of a component's initial + property values and is therefore useful for managing property values before + state changes. + + You can specify additional states by adding new states. Each state within a + component has a unique name. To change the current state of an component, + the state property is set to the name of the state. State changes can be + bound to conditions by using the \c when property. + + \section2 Signal and Event Handlers UI components need to communicate with each other. For example, a button needs to know that the user has clicked on it. In response, the button may diff --git a/doc/qtdesignstudio/examples/doc/loginui4.qdoc b/doc/qtdesignstudio/examples/doc/loginui4.qdoc index e9bdc4b2694..407866f5cf2 100644 --- a/doc/qtdesignstudio/examples/doc/loginui4.qdoc +++ b/doc/qtdesignstudio/examples/doc/loginui4.qdoc @@ -240,7 +240,7 @@ \section1 Binding Animation to States - You will now bring back the \l{Adding States}{states} in the + You will now bring back the \l{Working with States}{states} in the \uicontrol States view and bind them to the animation settings in \uicontrol Timeline: diff --git a/doc/qtdesignstudio/examples/doc/sidemenu.qdoc b/doc/qtdesignstudio/examples/doc/sidemenu.qdoc index 5f12aa0e2c4..d1db82c8ee2 100644 --- a/doc/qtdesignstudio/examples/doc/sidemenu.qdoc +++ b/doc/qtdesignstudio/examples/doc/sidemenu.qdoc @@ -31,7 +31,7 @@ The button can have the following states: checked, hover, pressed, and normal. We construct the button using different images for the button - background, frame, and front. We then add \l{Adding States}{states} in + background, frame, and front. We then add \l{Working with States}{states} in the \l States view for each of the button states. In each state, we turn the visibility of the appropriate images on or off in the button properties, to change the appearance of the button. diff --git a/doc/qtdesignstudio/examples/doc/washingMachineUI.qdoc b/doc/qtdesignstudio/examples/doc/washingMachineUI.qdoc index 674ee37829a..45ab960f7d0 100644 --- a/doc/qtdesignstudio/examples/doc/washingMachineUI.qdoc +++ b/doc/qtdesignstudio/examples/doc/washingMachineUI.qdoc @@ -166,7 +166,7 @@ in the top-left corner of the root component. Then, we open the \uicontrol States view to create the \e start, - \e settings, \e presets, and \e running \l{Adding States}{states} for + \e settings, \e presets, and \e running \l{Working with States}{states} for displaying a particular screen by selecting \uicontrol {Create New State}. \image washingmachineui-states.png "States view" diff --git a/doc/qtdesignstudio/src/components/qtquick-buttons.qdoc b/doc/qtdesignstudio/src/components/qtquick-buttons.qdoc index 53b801811ca..088e133d8ba 100644 --- a/doc/qtdesignstudio/src/components/qtquick-buttons.qdoc +++ b/doc/qtdesignstudio/src/components/qtquick-buttons.qdoc @@ -117,7 +117,7 @@ PNG file, as a border and a background. Use two border images and suitable graphics to change the appearance of - a button when it is clicked. You can use use \l{Adding States}{states} + a button when it is clicked. You can use use \l{Working with States}{states} to determine which image is visible depending on whether the mouse button is pressed down. You could add more images and states to change the appearance of the button depending on other mouse events, diff --git a/doc/qtdesignstudio/src/components/qtquick-component-instances.qdoc b/doc/qtdesignstudio/src/components/qtquick-component-instances.qdoc index 09f8d2e28b2..ada17fae01c 100644 --- a/doc/qtdesignstudio/src/components/qtquick-component-instances.qdoc +++ b/doc/qtdesignstudio/src/components/qtquick-component-instances.qdoc @@ -43,7 +43,7 @@ \image qmldesigner-bindings.png "Connections view Bindings tab" \li Add states to apply sets of changes to the property values of one or several component instances in the \uicontrol States view. - For more information, see \l{Adding States}. + For more information, see \l{Working with States}. \li Animate the properties of component instances in the \uicontrol Timeline view. For more information, see \l{Creating Timeline Animations}. diff --git a/doc/qtdesignstudio/src/components/qtquick-components.qdoc b/doc/qtdesignstudio/src/components/qtquick-components.qdoc index 7c3b09fc843..092a8bea3b1 100644 --- a/doc/qtdesignstudio/src/components/qtquick-components.qdoc +++ b/doc/qtdesignstudio/src/components/qtquick-components.qdoc @@ -63,7 +63,7 @@ your UI. \li Use as few components as necessary. To minimize the number of components, use \l{Adding Property Aliases}{alias properties} and - \l{Adding States}{states} to create the differences in your + \l{Working with States}{states} to create the differences in your component instances. We recommend reusing components instead of duplicating them, so the components do not need to be processed as completely new component types. This reduces loading diff --git a/doc/qtdesignstudio/src/overviews/qtquick-creating-ui-logic.qdoc b/doc/qtdesignstudio/src/overviews/qtquick-creating-ui-logic.qdoc index 3aebd6ae17d..226c2a4982d 100644 --- a/doc/qtdesignstudio/src/overviews/qtquick-creating-ui-logic.qdoc +++ b/doc/qtdesignstudio/src/overviews/qtquick-creating-ui-logic.qdoc @@ -32,7 +32,7 @@ exported as a public property of the relevant component. For example, a speedometer should have a property for speed to which the UI is bound. - You can declare various \l{Adding States}{UI states} that describe how + You can declare various \l{Working with States}{UI states} that describe how property values change from a base state. States can be a useful way of organizing your UI logic. You can associate transitions with components to define how their properties will animate when they change due to a @@ -100,7 +100,7 @@ \li \l{Adding Property Aliases} \row \li Referencing a state from within a specific component - \li \l{Adding States} + \li \l{Working with States} \row \li Switching to a state when a particular property changes \li \l{Applying States} diff --git a/doc/qtdesignstudio/src/qtdesignstudio-app-flows.qdoc b/doc/qtdesignstudio/src/qtdesignstudio-app-flows.qdoc index 7408d0a826b..5514acd2f29 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio-app-flows.qdoc +++ b/doc/qtdesignstudio/src/qtdesignstudio-app-flows.qdoc @@ -49,7 +49,7 @@ \uicontrol {Flow Decision} components from \uicontrol Components > \uicontrol {Flow View}, as described in \l{Simulating Conditions}. - \li Use \l{Adding States}{states} in flows to modify the appearance + \li Use \l{Working with States}{states} in flows to modify the appearance of components on screens in response to user interaction, as described in \l{Applying States in Flows}. \li Use \uicontrol {Flow Wildcard} components from @@ -620,7 +620,7 @@ \title Applying States in Flows - You can use \l{Adding States}{states} in flows to modify the appearance + You can use \l{Working with States}{states} in flows to modify the appearance of \l{glossary-component}{components} in flow items in response to user interaction, for example. For this purpose, you use the \uicontrol {Flow Item} components available in diff --git a/doc/qtdesignstudio/src/qtdesignstudio-components.qdocinc b/doc/qtdesignstudio/src/qtdesignstudio-components.qdocinc index 7f237dfc496..d0ab93d9289 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio-components.qdocinc +++ b/doc/qtdesignstudio/src/qtdesignstudio-components.qdocinc @@ -28,7 +28,7 @@ \endtable You can edit the properties of the controls in all the preset - \l{Adding States}{states} to apply your own style to them. + \l{Working with States}{states} to apply your own style to them. \note For buttons and check boxes, you can disable the misbehaving hover effects by selecting \l Properties > \uicontrol Control, and then disabling diff --git a/doc/qtdesignstudio/src/qtdesignstudio-terms.qdoc b/doc/qtdesignstudio/src/qtdesignstudio-terms.qdoc index d26f96652cf..dd5c1609b1e 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio-terms.qdoc +++ b/doc/qtdesignstudio/src/qtdesignstudio-terms.qdoc @@ -121,7 +121,7 @@ \list \li \l{Connections} - \li \l{Adding Connections} + \li \l{Working with Connections} \endlist \section1 Device @@ -249,7 +249,7 @@ \list \li \l{States} - \li \l{Adding States} + \li \l{Working with States} \endlist \section1 Transition diff --git a/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc b/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc index 070bf4ae25e..567bb6fcb21 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc +++ b/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc @@ -118,13 +118,13 @@ \endlist \li \l{Dynamic Behaviors} \list - \li \l{Adding Connections} + \li \l{Working with Connections} \list \li\l{Connecting Components to Signals} \li\l{Adding Bindings Between Properties} \li\l{Specifying Custom Properties} \endlist - \li \l{Adding States} + \li \l{Working with States} \endlist \li \l{Validating with Target Hardware} \list diff --git a/doc/qtdesignstudio/src/views/qtquick-adding-dynamics.qdoc b/doc/qtdesignstudio/src/views/qtquick-adding-dynamics.qdoc index bb574daefe7..ab9ed074f4a 100644 --- a/doc/qtdesignstudio/src/views/qtquick-adding-dynamics.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-adding-dynamics.qdoc @@ -13,7 +13,7 @@ when the values of other components or the UI state change. \list - \li \l {Adding Connections} + \li \l {Working with Connections} You can create connections between the UI components and the application to enable them to communicate with each other. For @@ -25,7 +25,7 @@ binding their properties together. This way, when the value of a property changes in a parent component, it can be automatically changed in all the child components, for example. - \li \l {Adding States} + \li \l {Working with States} You can declare various UI states that describe how component properties change from a base state. Therefore, states can be diff --git a/doc/qtdesignstudio/src/views/qtquick-connection-editor.qdoc b/doc/qtdesignstudio/src/views/qtquick-connection-editor.qdoc index d17dbcb003d..0512af58ad1 100644 --- a/doc/qtdesignstudio/src/views/qtquick-connection-editor.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-connection-editor.qdoc @@ -6,7 +6,7 @@ \previouspage qtquick-adding-dynamics.html \nextpage quick-signals.html - \title Adding Connections + \title Working with Connections \list \li \l{Connecting Components to Signals} diff --git a/doc/qtdesignstudio/src/views/qtquick-designer.qdoc b/doc/qtdesignstudio/src/views/qtquick-designer.qdoc index adf94ae7a5f..4dfe24718b6 100644 --- a/doc/qtdesignstudio/src/views/qtquick-designer.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-designer.qdoc @@ -75,14 +75,14 @@ \li \l{Connections} \li Enables you to add functionality to the UI by creating connections between components, signals, and component properties. - \li \l{Adding Connections} + \li \l{Working with Connections} \row \li \l States \li Displays the different states that can be applied to a component. Typically, states describe UI configurations, such as the visibility and behavior of components and the available user actions. - \li \l{Adding States} + \li \l{Working with States} \row \li \l{Transitions} \li Enables you to make movement between states smooth by animating diff --git a/doc/qtdesignstudio/src/views/qtquick-navigator.qdoc b/doc/qtdesignstudio/src/views/qtquick-navigator.qdoc index 90735297f7b..0c322c750de 100644 --- a/doc/qtdesignstudio/src/views/qtquick-navigator.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-navigator.qdoc @@ -124,7 +124,7 @@ the \l {3D} view nor access their properties in \uicontrol Properties. - If you attempt to \l{Adding States}{remove a state} that changes the + If you attempt to \l{Working with States}{remove a state} that changes the properties of a locked component, you are prompted to confirm the removal. If you have \l{Editing Animation Curves}{added easing curves} to keyframe @@ -212,7 +212,7 @@ \image qmldesigner-export-item.png You can then use the property alias in other components to - \l{Adding Connections}{create connections} to this component. + \l{Working with Connections}{create connections} to this component. \section1 Moving Within Components diff --git a/doc/qtdesignstudio/src/views/qtquick-properties-view.qdoc b/doc/qtdesignstudio/src/views/qtquick-properties-view.qdoc index d8f1f01d159..acde01a951b 100644 --- a/doc/qtdesignstudio/src/views/qtquick-properties-view.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-properties-view.qdoc @@ -86,7 +86,7 @@ The default values of properties are displayed in white color, while the values that you specify explicitly are highlighted with blue color. In - addition, property changes in \l{Adding States}{states} are highlighted + addition, property changes in \l{Working with States}{states} are highlighted with blue. This allows you to easily see which values are set in the component diff --git a/doc/qtdesignstudio/src/views/qtquick-properties.qdoc b/doc/qtdesignstudio/src/views/qtquick-properties.qdoc index d716932d943..0f7547c1195 100644 --- a/doc/qtdesignstudio/src/views/qtquick-properties.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-properties.qdoc @@ -153,7 +153,7 @@ If the \uicontrol Clip check box is selected, the component and its children are clipped to the bounding rectangle of the component. - in the \uicontrol State field, select the \l{Adding States}{state} to + in the \uicontrol State field, select the \l{Working with States}{state} to change the value of a property in that state. \section1 Picking Colors diff --git a/doc/qtdesignstudio/src/views/qtquick-states-view.qdoc b/doc/qtdesignstudio/src/views/qtquick-states-view.qdoc index 4e22c1c52a8..884b1804618 100644 --- a/doc/qtdesignstudio/src/views/qtquick-states-view.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-states-view.qdoc @@ -8,8 +8,8 @@ \title States - The \uicontrol States view displays the different \l{Adding States}{states} - of a UI. + The \uicontrol States view displays the different + \l{Working with States}{states} of a UI. \image qmldesigner-transitions.png "States view" diff --git a/doc/qtdesignstudio/src/views/qtquick-states.qdoc b/doc/qtdesignstudio/src/views/qtquick-states.qdoc index 72aba97f1f9..e4e23f899ca 100644 --- a/doc/qtdesignstudio/src/views/qtquick-states.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-states.qdoc @@ -11,7 +11,7 @@ \nextpage exporting-3d-assets.html \endif - \title Adding States + \title Working with States You can define states for components and component instances in the \l States view by selecting \inlineimage icons/plus.png diff --git a/doc/qtdesignstudio/src/views/qtquick-transition-editor.qdoc b/doc/qtdesignstudio/src/views/qtquick-transition-editor.qdoc index 120f34de834..ba8375e3656 100644 --- a/doc/qtdesignstudio/src/views/qtquick-transition-editor.qdoc +++ b/doc/qtdesignstudio/src/views/qtquick-transition-editor.qdoc @@ -12,7 +12,7 @@ \uicontrol {Transitions} to animate the changes between states. - First, you need to \l{Adding States}{add states} in the \l States view + First, you need to \l{Working with States}{add states} in the \l States view and \l{Specifying Component Properties}{edit some properties} that can be animated, such as colors or numbers, in the \l Properties view. For example, you can animate the changes in the position of a component. diff --git a/scripts/build.py b/scripts/build.py index d69c6e12868..ee83021fb95 100755 --- a/scripts/build.py +++ b/scripts/build.py @@ -188,6 +188,8 @@ def build_qtcreator(args, paths): if args.with_cpack: cmake_args += ['-DCPACK_PACKAGE_FILE_NAME=qtcreator' + args.zip_infix] + if common.is_linux_platform(): + cmake_args += ['-DCPACK_INSTALL_PREFIX=/opt'] cmake_args += args.config_args diff --git a/share/qtcreator/debugger/creatortypes.py b/share/qtcreator/debugger/creatortypes.py index 2e440d5d2bc..875cb1ad03a 100644 --- a/share/qtcreator/debugger/creatortypes.py +++ b/share/qtcreator/debugger/creatortypes.py @@ -199,22 +199,23 @@ def qdump__CPlusPlus__Internal__Value(d, value): def qdump__Utils__FilePath(d, value): data, path_len, scheme_len, host_len = d.split("{@QString}IHH", value) - if False: - scheme_enc = d.encodeString(scheme) - host_enc = d.encodeString(host) - elided, path_enc = d.encodeStringHelper(path, d.displayStringLimit) - val = "" + elided, enc = d.encodeStringHelper(data, d.displayStringLimit) + # enc is concatenated path + scheme + host + if scheme_len: + scheme_pos = path_len * 4 + host_pos = scheme_pos + scheme_len * 4 + path_enc = enc[0 : path_len * 4] + scheme_enc = enc[scheme_pos : scheme_pos + scheme_len * 4] + host_enc = enc[host_pos : host_pos + host_len * 4] slash = "2F00" dot = "2E00" colon = "3A00" - if len(scheme_enc): - val = scheme_enc + colon + slash + slash + host_enc - if not path_enc.startswith(slash): - val += slash + dot + slash + val = scheme_enc + colon + slash + slash + host_enc + if not path_enc.startswith(slash): + val += slash + dot + slash val += path_enc else: - elided, data_enc = d.encodeStringHelper(data, d.displayStringLimit) - val = data_enc + val = enc d.putValue(val, "utf16", elided=elided) d.putPlainChildren(value) diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml index 1b9a284ce14..f8c06057346 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml @@ -24,6 +24,10 @@ Item { // Array of supported externally dropped files that trigger custom import process property var dropComplexExtFiles: [] + AssetsContextMenu { + id: contextMenu + } + function clearSearchFilter() { searchBox.clear(); @@ -103,307 +107,11 @@ Item { root.selectedAssetsChanged() } - StudioControls.Menu { - id: contextMenu - - closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape - - onOpened: { - var numSelected = Object.values(root.selectedAssets).filter(p => p).length - deleteFileItem.text = numSelected > 1 ? qsTr("Delete Files") : qsTr("Delete File") - } - - StudioControls.MenuItem { - text: qsTr("Expand All") - enabled: root.allExpandedState !== 1 - visible: root.isDirContextMenu - height: visible ? implicitHeight : 0 - onTriggered: assetsModel.toggleExpandAll(true) - } - - StudioControls.MenuItem { - text: qsTr("Collapse All") - enabled: root.allExpandedState !== 2 - visible: root.isDirContextMenu - height: visible ? implicitHeight : 0 - onTriggered: assetsModel.toggleExpandAll(false) - } - - StudioControls.MenuSeparator { - visible: root.isDirContextMenu - height: visible ? StudioTheme.Values.border : 0 - } - - StudioControls.MenuItem { - id: deleteFileItem - text: qsTr("Delete File") - visible: root.contextFilePath - height: deleteFileItem.visible ? deleteFileItem.implicitHeight : 0 - onTriggered: { - assetsModel.deleteFiles(Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p])) - } - } - - StudioControls.MenuSeparator { - visible: root.contextFilePath - height: visible ? StudioTheme.Values.border : 0 - } - - StudioControls.MenuItem { - text: qsTr("Rename Folder") - visible: root.isDirContextMenu - height: visible ? implicitHeight : 0 - onTriggered: renameFolderDialog.open() - } - - StudioControls.MenuItem { - text: qsTr("New Folder") - onTriggered: newFolderDialog.open() - } - - StudioControls.MenuItem { - text: qsTr("Delete Folder") - visible: root.isDirContextMenu - height: visible ? implicitHeight : 0 - onTriggered: { - var dirEmpty = !(root.contextDir.dirsModel && root.contextDir.dirsModel.rowCount() > 0) - && !(root.contextDir.filesModel && root.contextDir.filesModel.rowCount() > 0); - - if (dirEmpty) - assetsModel.deleteFolder(root.contextDir.dirPath) - else - confirmDeleteFolderDialog.open() - } - } - } - RegExpValidator { id: folderNameValidator regExp: /^(\w[^*/> 0 ? 5 : 0 - bottomPadding: 0 - hideHeader: dirDepth === 0 - showLeftBorder: dirDepth > 0 - expanded: dirExpanded - visible: dirVisible - expandOnClick: false - useDefaulContextMenu: false - dropEnabled: true - - onToggleExpand: { - dirExpanded = !dirExpanded - } - - onDropEnter: (drag)=> { - root.updateDropExtFiles(drag) - section.highlight = drag.accepted && root.dropSimpleExtFiles.length > 0 - } - - onDropExit: { - section.highlight = false - } - - onDrop: { - section.highlight = false - rootView.handleExtFilesDrop(root.dropSimpleExtFiles, - root.dropComplexExtFiles, - dirPath) - } - - onShowContextMenu: { - root.contextFilePath = "" - root.contextDir = model - root.isDirContextMenu = true - root.allExpandedState = assetsModel.getAllExpandedState() - contextMenu.popup() - } - - Column { - spacing: 5 - leftPadding: 5 - - Repeater { - model: dirsModel - delegate: dirSection - } - - Repeater { - model: filesModel - delegate: fileSection - } - - Text { - text: qsTr("Empty folder") - color: StudioTheme.Values.themeTextColorDisabled - font.pixelSize: 12 - visible: !(dirsModel && dirsModel.rowCount() > 0) - && !(filesModel && filesModel.rowCount() > 0) - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton - onClicked: { - root.contextFilePath = "" - root.contextDir = model - root.isDirContextMenu = true - contextMenu.popup() - } - } - } - } - } - } - - Component { - id: fileSection - - Rectangle { - width: assetsView.width - - (assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) - height: img.height - color: root.selectedAssets[filePath] - ? StudioTheme.Values.themeInteraction - : (mouseArea.containsMouse ? StudioTheme.Values.themeSectionHeadBackground - : "transparent") - - Row { - spacing: 5 - - Image { - id: img - asynchronous: true - fillMode: Image.PreserveAspectFit - width: 48 - height: 48 - source: "image://qmldesigner_assets/" + filePath - } - - Text { - text: fileName - color: StudioTheme.Values.themeTextColor - font.pixelSize: 14 - anchors.verticalCenter: parent.verticalCenter - } - } - - readonly property string suffix: fileName.substr(-4) - readonly property bool isFont: suffix === ".ttf" || suffix === ".otf" - property bool currFileSelected: false - - MouseArea { - id: mouseArea - - property bool allowTooltip: true - - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.LeftButton | Qt.RightButton - - onExited: tooltipBackend.hideTooltip() - onEntered: allowTooltip = true - onCanceled: { - tooltipBackend.hideTooltip() - allowTooltip = true - } - onPositionChanged: tooltipBackend.reposition() - onPressed: (mouse)=> { - forceActiveFocus() - allowTooltip = false - tooltipBackend.hideTooltip() - var ctrlDown = mouse.modifiers & Qt.ControlModifier - if (mouse.button === Qt.LeftButton) { - if (!root.selectedAssets[filePath] && !ctrlDown) - root.selectedAssets = {} - currFileSelected = ctrlDown ? !root.selectedAssets[filePath] : true - root.selectedAssets[filePath] = currFileSelected - root.selectedAssetsChanged() - - if (currFileSelected) { - rootView.startDragAsset( - Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p]), - mapToGlobal(mouse.x, mouse.y)) - } - } else { - if (!root.selectedAssets[filePath] && !ctrlDown) - root.selectedAssets = {} - currFileSelected = root.selectedAssets[filePath] || !ctrlDown - root.selectedAssets[filePath] = currFileSelected - root.selectedAssetsChanged() - - root.contextFilePath = filePath - root.contextDir = model.fileDir - root.isDirContextMenu = false - - contextMenu.popup() - } - } - - onReleased: (mouse)=> { - allowTooltip = true - if (mouse.button === Qt.LeftButton) { - if (!(mouse.modifiers & Qt.ControlModifier)) - root.selectedAssets = {} - root.selectedAssets[filePath] = currFileSelected - root.selectedAssetsChanged() - } - } - - ToolTip { - visible: !isFont && mouseArea.containsMouse && !contextMenu.visible - text: filePath - delay: 1000 - } - - Timer { - interval: 1000 - running: mouseArea.containsMouse && mouseArea.allowTooltip - onTriggered: { - if (suffix === ".ttf" || suffix === ".otf") { - tooltipBackend.name = fileName - tooltipBackend.path = filePath - tooltipBackend.showTooltip() - } - } - } - } - } - } - } } } } diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml new file mode 100644 index 00000000000..5caa139651f --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsContextMenu.qml @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuickDesignerTheme +import HelperWidgets as HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +StudioControls.Menu { + id: contextMenu + + closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape + + onOpened: { + var numSelected = Object.values(root.selectedAssets).filter(p => p).length + deleteFileItem.text = numSelected > 1 ? qsTr("Delete Files") : qsTr("Delete File") + } + + StudioControls.MenuItem { + text: qsTr("Expand All") + enabled: root.allExpandedState !== 1 + visible: root.isDirContextMenu + height: visible ? implicitHeight : 0 + onTriggered: assetsModel.toggleExpandAll(true) + } + + StudioControls.MenuItem { + text: qsTr("Collapse All") + enabled: root.allExpandedState !== 2 + visible: root.isDirContextMenu + height: visible ? implicitHeight : 0 + onTriggered: assetsModel.toggleExpandAll(false) + } + + StudioControls.MenuSeparator { + visible: root.isDirContextMenu + height: visible ? StudioTheme.Values.border : 0 + } + + StudioControls.MenuItem { + id: deleteFileItem + text: qsTr("Delete File") + visible: root.contextFilePath + height: deleteFileItem.visible ? deleteFileItem.implicitHeight : 0 + onTriggered: { + assetsModel.deleteFiles(Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p])) + } + } + + StudioControls.MenuSeparator { + visible: root.contextFilePath + height: visible ? StudioTheme.Values.border : 0 + } + + StudioControls.MenuItem { + text: qsTr("Rename Folder") + visible: root.isDirContextMenu + height: visible ? implicitHeight : 0 + onTriggered: renameFolderDialog.open() + + RenameFolderDialog { + id: renameFolderDialog + } + } + + StudioControls.MenuItem { + text: qsTr("New Folder") + + NewFolderDialog { + id: newFolderDialog + } + + onTriggered: newFolderDialog.open() + } + + StudioControls.MenuItem { + text: qsTr("Delete Folder") + visible: root.isDirContextMenu + height: visible ? implicitHeight : 0 + + ConfirmDeleteFolderDialog { + id: confirmDeleteFolderDialog + } + + onTriggered: { + var dirEmpty = !(root.contextDir.dirsModel && root.contextDir.dirsModel.rowCount() > 0) + && !(root.contextDir.filesModel && root.contextDir.filesModel.rowCount() > 0); + + if (dirEmpty) + assetsModel.deleteFolder(root.contextDir.dirPath) + else + confirmDeleteFolderDialog.open() + } + } +} diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml new file mode 100644 index 00000000000..e1c017814d6 --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/AssetsView.qml @@ -0,0 +1,246 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuickDesignerTheme +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +ScrollView { // TODO: experiment using ListView instead of ScrollView + Column + id: assetsView + clip: true + interactive: assetsView.verticalScrollBarVisible && !contextMenu.opened + + Column { + Repeater { + model: assetsModel // context property + delegate: dirSection + } + + Component { + id: dirSection + + Section { + id: section + + width: assetsView.width - + (assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) - 5 + caption: dirName + sectionHeight: 30 + sectionFontSize: 15 + leftPadding: 0 + topPadding: dirDepth > 0 ? 5 : 0 + bottomPadding: 0 + hideHeader: dirDepth === 0 + showLeftBorder: dirDepth > 0 + expanded: dirExpanded + visible: dirVisible + expandOnClick: false + useDefaulContextMenu: false + dropEnabled: true + + onToggleExpand: { + dirExpanded = !dirExpanded + } + + onDropEnter: (drag)=> { + root.updateDropExtFiles(drag) + section.highlight = drag.accepted && root.dropSimpleExtFiles.length > 0 + } + + onDropExit: { + section.highlight = false + } + + onDrop: { + section.highlight = false + rootView.handleExtFilesDrop(root.dropSimpleExtFiles, + root.dropComplexExtFiles, + dirPath) + } + + onShowContextMenu: { + root.contextFilePath = "" + root.contextDir = model + root.isDirContextMenu = true + root.allExpandedState = assetsModel.getAllExpandedState() + contextMenu.popup() + } + + Column { + spacing: 5 + leftPadding: 5 + + Repeater { + model: dirsModel + delegate: dirSection + } + + Repeater { + model: filesModel + delegate: fileSection + } + + Text { + text: qsTr("Empty folder") + color: StudioTheme.Values.themeTextColorDisabled + font.pixelSize: 12 + visible: !(dirsModel && dirsModel.rowCount() > 0) + && !(filesModel && filesModel.rowCount() > 0) + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: { + root.contextFilePath = "" + root.contextDir = model + root.isDirContextMenu = true + contextMenu.popup() + } + } + } + } + } + } + + Component { + id: fileSection + + Rectangle { + width: assetsView.width - + (assetsView.verticalScrollBarVisible ? assetsView.verticalThickness : 0) + height: img.height + color: root.selectedAssets[filePath] + ? StudioTheme.Values.themeInteraction + : (mouseArea.containsMouse ? StudioTheme.Values.themeSectionHeadBackground + : "transparent") + + Row { + spacing: 5 + + Image { + id: img + asynchronous: true + fillMode: Image.PreserveAspectFit + width: 48 + height: 48 + source: "image://qmldesigner_assets/" + filePath + } + + Text { + text: fileName + color: StudioTheme.Values.themeTextColor + font.pixelSize: 14 + anchors.verticalCenter: parent.verticalCenter + } + } + + readonly property string suffix: fileName.substr(-4) + readonly property bool isFont: suffix === ".ttf" || suffix === ".otf" + property bool currFileSelected: false + + MouseArea { + id: mouseArea + + property bool allowTooltip: true + + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onExited: tooltipBackend.hideTooltip() + onEntered: allowTooltip = true + onCanceled: { + tooltipBackend.hideTooltip() + allowTooltip = true + } + onPositionChanged: tooltipBackend.reposition() + onPressed: (mouse)=> { + forceActiveFocus() + allowTooltip = false + tooltipBackend.hideTooltip() + var ctrlDown = mouse.modifiers & Qt.ControlModifier + if (mouse.button === Qt.LeftButton) { + if (!root.selectedAssets[filePath] && !ctrlDown) + root.selectedAssets = {} + currFileSelected = ctrlDown ? !root.selectedAssets[filePath] : true + root.selectedAssets[filePath] = currFileSelected + root.selectedAssetsChanged() + + if (currFileSelected) { + rootView.startDragAsset( + Object.keys(root.selectedAssets).filter(p => root.selectedAssets[p]), + mapToGlobal(mouse.x, mouse.y)) + } + } else { + if (!root.selectedAssets[filePath] && !ctrlDown) + root.selectedAssets = {} + currFileSelected = root.selectedAssets[filePath] || !ctrlDown + root.selectedAssets[filePath] = currFileSelected + root.selectedAssetsChanged() + + root.contextFilePath = filePath + root.contextDir = model.fileDir + root.isDirContextMenu = false + + contextMenu.popup() + } + } + + onReleased: (mouse)=> { + allowTooltip = true + if (mouse.button === Qt.LeftButton) { + if (!(mouse.modifiers & Qt.ControlModifier)) + root.selectedAssets = {} + root.selectedAssets[filePath] = currFileSelected + root.selectedAssetsChanged() + } + } + + ToolTip { + visible: !isFont && mouseArea.containsMouse && !contextMenu.visible + text: filePath + delay: 1000 + } + + Timer { + interval: 1000 + running: mouseArea.containsMouse && mouseArea.allowTooltip + onTriggered: { + if (suffix === ".ttf" || suffix === ".otf") { + tooltipBackend.name = fileName + tooltipBackend.path = filePath + tooltipBackend.showTooltip() + } + } + } + } + } + } + } +} diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFolderDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFolderDialog.qml new file mode 100644 index 00000000000..a4fd300975e --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ConfirmDeleteFolderDialog.qml @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuickDesignerTheme +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +Dialog { + id: confirmDeleteFolderDialog + + title: qsTr("Folder Not Empty") + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape + implicitWidth: 300 + modal: true + + contentItem: Column { + spacing: 20 + width: parent.width + + Text { + id: folderNotEmpty + + text: qsTr("Folder \"%1\" is not empty. Delete it anyway?") + .arg(root.contextDir ? root.contextDir.dirName : "") + color: StudioTheme.Values.themeTextColor + wrapMode: Text.WordWrap + width: confirmDeleteFolderDialog.width + leftPadding: 10 + rightPadding: 10 + + Keys.onEnterPressed: btnDelete.onClicked() + Keys.onReturnPressed: btnDelete.onClicked() + } + + Text { + text: qsTr("If the folder has assets in use, deleting it might cause the project to not work correctly.") + color: StudioTheme.Values.themeTextColor + wrapMode: Text.WordWrap + width: confirmDeleteFolderDialog.width + leftPadding: 10 + rightPadding: 10 + } + + Row { + anchors.right: parent.right + Button { + id: btnDelete + + text: qsTr("Delete") + + onClicked: { + assetsModel.deleteFolder(root.contextDir.dirPath) + confirmDeleteFolderDialog.accept() + } + } + + Button { + text: qsTr("Cancel") + onClicked: confirmDeleteFolderDialog.reject() + } + } + } + + onOpened: folderNotEmpty.forceActiveFocus() +} diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml new file mode 100644 index 00000000000..130026ddce1 --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/NewFolderDialog.qml @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuickDesignerTheme +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +Dialog { + id: newFolderDialog + + title: qsTr("Create New Folder") + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape + modal: true + + contentItem: Column { + spacing: 2 + + Row { + Text { + text: qsTr("Folder name: ") + anchors.verticalCenter: parent.verticalCenter + color: StudioTheme.Values.themeTextColor + } + + StudioControls.TextField { + id: folderName + + actionIndicator.visible: false + translationIndicator.visible: false + validator: folderNameValidator + + Keys.onEnterPressed: btnCreate.onClicked() + Keys.onReturnPressed: btnCreate.onClicked() + } + } + + Text { + text: qsTr("Folder name cannot be empty.") + color: "#ff0000" + anchors.right: parent.right + visible: folderName.text === "" + } + + Item { // spacer + width: 1 + height: 20 + } + + Row { + anchors.right: parent.right + + Button { + id: btnCreate + + text: qsTr("Create") + enabled: folderName.text !== "" + onClicked: { + assetsModel.addNewFolder(root.contextDir.dirPath + '/' + folderName.text) + newFolderDialog.accept() + } + } + + Button { + text: qsTr("Cancel") + onClicked: newFolderDialog.reject() + } + } + } + + onOpened: { + folderName.text = qsTr("New folder") + folderName.selectAll() + folderName.forceActiveFocus() + } +} diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/RenameFolderDialog.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/RenameFolderDialog.qml new file mode 100644 index 00000000000..351c0a35fc8 --- /dev/null +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/RenameFolderDialog.qml @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuickDesignerTheme +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +Dialog { + id: renameFolderDialog + + title: qsTr("Rename Folder") + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape + implicitWidth: 280 + modal: true + + property bool renameError: false + + contentItem: Column { + spacing: 2 + + StudioControls.TextField { + id: folderRename + + actionIndicator.visible: false + translationIndicator.visible: false + width: renameFolderDialog.width - 12 + validator: folderNameValidator + + onEditChanged: renameFolderDialog.renameError = false + Keys.onEnterPressed: btnRename.onClicked() + Keys.onReturnPressed: btnRename.onClicked() + } + + Text { + text: qsTr("Folder name cannot be empty.") + color: "#ff0000" + visible: folderRename.text === "" && !renameFolderDialog.renameError + } + + Text { + text: qsTr("Could not rename folder. Make sure no folder with the same name exists.") + wrapMode: Text.WordWrap + width: renameFolderDialog.width - 12 + color: "#ff0000" + visible: renameFolderDialog.renameError + } + + Item { // spacer + width: 1 + height: 10 + } + + Text { + text: qsTr("If the folder has assets in use, renaming it might cause the project to not work correctly.") + color: StudioTheme.Values.themeTextColor + wrapMode: Text.WordWrap + width: renameFolderDialog.width + leftPadding: 10 + rightPadding: 10 + } + + Item { // spacer + width: 1 + height: 20 + } + + Row { + anchors.right: parent.right + + Button { + id: btnRename + + text: qsTr("Rename") + enabled: folderRename.text !== "" + onClicked: { + var success = assetsModel.renameFolder(root.contextDir.dirPath, folderRename.text) + if (success) + renameFolderDialog.accept() + + renameFolderDialog.renameError = !success + } + } + + Button { + text: qsTr("Cancel") + onClicked: renameFolderDialog.reject() + } + } + } + + onOpened: { + folderRename.text = root.contextDir.dirName + folderRename.selectAll() + folderRename.forceActiveFocus() + renameFolderDialog.renameError = false + } +} diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/BundleMaterialItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/BundleMaterialItem.qml index 4b8b45be564..59541f47d39 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/BundleMaterialItem.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/BundleMaterialItem.qml @@ -27,6 +27,8 @@ import QtQuick 2.15 import QtQuick.Layouts 1.15 import QtQuickDesignerTheme 1.0 import HelperWidgets 2.0 +import QtQuick.Controls + import StudioTheme 1.0 as StudioTheme Item { @@ -43,7 +45,7 @@ Item { acceptedButtons: Qt.LeftButton | Qt.RightButton onPressed: (mouse) => { - if (mouse.button === Qt.LeftButton) + if (mouse.button === Qt.LeftButton && !materialBrowserBundleModel.importerRunning) rootView.startDragBundleMaterial(modelData, mapToGlobal(mouse.x, mouse.y)) else if (mouse.button === Qt.RightButton) root.showContextMenu() @@ -64,6 +66,48 @@ Item { anchors.horizontalCenter: parent.horizontalCenter source: modelData.bundleMaterialIcon cache: false + + Rectangle { // circular indicator for imported bundle materials + width: 10 + height: 10 + radius: 5 + anchors.right: img.right + anchors.top: img.top + anchors.margins: 5 + color: "#00ff00" + border.color: "#555555" + border.width: 1 + visible: modelData.bundleMaterialImported + + ToolTip { + visible: indicatorMouseArea.containsMouse + text: qsTr("Material is imported to project") + delay: 1000 + } + + MouseArea { + id: indicatorMouseArea + anchors.fill: parent + hoverEnabled: true + } + } + + IconButton { + icon: StudioTheme.Constants.plus + tooltip: qsTr("Add an instance to project") + buttonSize: 22 + property color c: StudioTheme.Values.themeIconColor + normalColor: Qt.hsla(c.hslHue, c.hslSaturation, c.hslLightness, .2) + hoverColor: Qt.hsla(c.hslHue, c.hslSaturation, c.hslLightness, .3) + pressColor: Qt.hsla(c.hslHue, c.hslSaturation, c.hslLightness, .4) + anchors.right: img.right + anchors.bottom: img.bottom + enabled: !materialBrowserBundleModel.importerRunning + + onClicked: { + materialBrowserBundleModel.addToProject(modelData) + } + } } TextInput { diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml index 4e3d9213859..f60f4f150b1 100644 --- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml @@ -24,8 +24,8 @@ Item { // Called also from C++ to close context menu on focus out function closeContextMenu() { - cxtMenu.close() - cxtMenuBundle.close() + ctxMenu.close() + ctxMenuBundle.close() } // Called from C++ to refresh a preview material after it changes @@ -57,7 +57,7 @@ Item { if (!materialBrowserModel.hasMaterialRoot && (!materialBrowserBundleModel.matBundleExists || mouse.y < userMatsSecBottom)) { root.currentMaterial = null - cxtMenu.popup() + ctxMenu.popup() } } } @@ -75,8 +75,12 @@ Item { } } + UnimportBundleMaterialDialog { + id: unimportBundleMaterialDialog + } + StudioControls.Menu { - id: cxtMenu + id: ctxMenu closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside @@ -103,7 +107,10 @@ Item { width: parent.width onAboutToShow: { - root.matSectionsModel = ["All"]; + if (root.currentMaterial.hasDynamicProperties) + root.matSectionsModel = ["All", "Custom"]; + else + root.matSectionsModel = ["All"]; switch (root.currentMaterial.materialType) { case "DefaultMaterial": @@ -180,7 +187,7 @@ Item { } StudioControls.Menu { - id: cxtMenuBundle + id: ctxMenuBundle closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside @@ -199,9 +206,22 @@ Item { StudioControls.MenuSeparator {} StudioControls.MenuItem { - text: qsTr("Add to project") + enabled: !materialBrowserBundleModel.importerRunning + text: qsTr("Add an instance to project") - onTriggered: materialBrowserBundleModel.addMaterial(root.currentBundleMaterial) + onTriggered: { + materialBrowserBundleModel.addToProject(root.currentBundleMaterial) + } + } + + StudioControls.MenuItem { + enabled: !materialBrowserBundleModel.importerRunning && root.currentBundleMaterial.bundleMaterialImported + text: qsTr("Remove from project") + + onTriggered: { + unimportBundleMaterialDialog.targetBundleMaterial = root.currentBundleMaterial + unimportBundleMaterialDialog.open() + } } } @@ -276,13 +296,14 @@ Item { height: root.height - searchBox.height clip: true visible: materialBrowserModel.hasQuick3DImport && !materialBrowserModel.hasMaterialRoot + interactive: !ctxMenu.opened && !ctxMenuBundle.opened Column { Section { id: userMaterialsSection width: root.width - caption: qsTr("User materials") + caption: qsTr("Materials") hideHeader: !materialBrowserBundleModel.matBundleExists Grid { @@ -304,7 +325,7 @@ Item { onShowContextMenu: { root.currentMaterial = model - cxtMenu.popup() + ctxMenu.popup() } } } @@ -367,7 +388,7 @@ Item { onShowContextMenu: { root.currentBundleMaterial = modelData - cxtMenuBundle.popup() + ctxMenuBundle.popup() } } } diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/UnimportBundleMaterialDialog.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/UnimportBundleMaterialDialog.qml new file mode 100644 index 00000000000..82708467a8a --- /dev/null +++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/UnimportBundleMaterialDialog.qml @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuickDesignerTheme +import HelperWidgets +import StudioControls as StudioControls +import StudioTheme as StudioTheme + +Dialog { + id: root + + title: qsTr("Bundle material might be in use") + anchors.centerIn: parent + closePolicy: Popup.CloseOnEscape + implicitWidth: 300 + modal: true + + property var targetBundleMaterial + + contentItem: Column { + spacing: 20 + width: parent.width + + Text { + id: folderNotEmpty + + text: qsTr("If the material you are removing is in use, it might cause the project to malfunction.\n\nAre you sure you want to remove the material?") + color: StudioTheme.Values.themeTextColor + wrapMode: Text.WordWrap + anchors.right: parent.right + anchors.left: parent.left + leftPadding: 10 + rightPadding: 10 + + Keys.onEnterPressed: btnRemove.onClicked() + Keys.onReturnPressed: btnRemove.onClicked() + } + + Row { + anchors.right: parent.right + Button { + id: btnRemove + + text: qsTr("Remove") + + onClicked: { + materialBrowserBundleModel.removeFromProject(root.targetBundleMaterial) + root.accept() + } + } + + Button { + text: qsTr("Cancel") + onClicked: root.reject() + } + } + } + + onOpened: folderNotEmpty.forceActiveFocus() +} diff --git a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml index cf767972e68..73e9865c194 100644 --- a/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml +++ b/share/qtcreator/qmldesigner/materialEditorQmlSources/MaterialEditorTopSection.qml @@ -175,6 +175,7 @@ Column { // Section with hidden header is used so properties are aligned with the other sections' properties hideHeader: true width: parent.width + collapsible: false SectionLayout { PropertyLabel { text: qsTr("Name") } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QML/QtObjectPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QML/QtObjectPane.qml new file mode 100644 index 00000000000..cac322fcf83 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QML/QtObjectPane.qml @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.15 +import "../QtQuick" as Original + +Original.QtObjectPane {} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml index f532fee7e37..69a10570f84 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/Section.qml @@ -29,6 +29,7 @@ Item { property int level: 0 property int levelShift: 10 property bool hideHeader: false + property bool collapsible: true property bool expandOnClick: true // if false, toggleExpand signal will be emitted instead property bool addTopPadding: true property bool addBottomPadding: true @@ -48,7 +49,10 @@ Item { Connections { target: Controller - function onCollapseAll() { section.expanded = false } + function onCollapseAll() { + if (collapsible) + section.expanded = false + } function onExpandAll() { section.expanded = true } } @@ -120,6 +124,9 @@ Item { acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: function(mouse) { if (mouse.button === Qt.LeftButton) { + if (!section.collapsible && section.expanded) + return + transition.enabled = true if (section.expandOnClick) section.expanded = !section.expanded diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/FilterComboBox.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/FilterComboBox.qml index 5da47ce386e..a1e39a94525 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/FilterComboBox.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/FilterComboBox.qml @@ -521,20 +521,27 @@ Item { ] Text { - visible: root.autocompleteString !== "" + id: tmpSelectionName + visible: root.autocompleteString !== "" && root.open text: root.autocompleteString x: textInput.leftPadding + textMetrics.advanceWidth - y: (textInput.height - Math.ceil(textMetrics.height)) / 2 + y: (textInput.height - Math.ceil(tmpSelectionTextMetrics.height)) / 2 color: "gray" // TODO proper color value font: textInput.font renderType: textInput.renderType + + TextMetrics { + id: textMetrics + font: textInput.font + text: textInput.text + } + TextMetrics { + id: tmpSelectionTextMetrics + font: tmpSelectionName.font + text: "Xq" + } } - TextMetrics { - id: textMetrics - font: textInput.font - text: textInput.text - } Rectangle { id: checkIndicator diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/MenuItem.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/MenuItem.qml index 294446a6379..e9925741b55 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/MenuItem.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/MenuItem.qml @@ -48,6 +48,17 @@ T.MenuItem { } } + arrow: T.Label { + id: arrow + x: parent.width - (StudioTheme.Values.height + arrow.width) / 2 + y: (parent.height - arrow.height) / 2 + visible: control.subMenu + text: StudioTheme.Constants.startNode + color: StudioTheme.Values.themeTextColor + font.pixelSize: 8 + font.family: StudioTheme.Constants.iconFont.family + } + background: Rectangle { implicitWidth: textLabel.implicitWidth + control.labelSpacing + shortcutLabel.implicitWidth + control.leftPadding + control.rightPadding diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml index 9339fda0daa..bc5fc4c538c 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml @@ -49,128 +49,126 @@ QtObject { readonly property string centerHorizontal: "\u0042" readonly property string centerVertical: "\u0043" readonly property string closeCross: "\u0044" - readonly property string closeLink: "\u0045" - readonly property string colorPopupClose: "\u0046" - readonly property string columnsAndRows: "\u0047" - readonly property string copyLink: "\u0048" - readonly property string copyStyle: "\u0049" - readonly property string cornerA: "\u004A" - readonly property string cornerB: "\u004B" - readonly property string cornersAll: "\u004C" - readonly property string curveDesigner: "\u004D" - readonly property string curveEditor: "\u004E" - readonly property string customMaterialEditor: "\u004F" - readonly property string decisionNode: "\u0050" - readonly property string deleteColumn: "\u0051" - readonly property string deleteMaterial: "\u0052" - readonly property string deleteRow: "\u0053" - readonly property string deleteTable: "\u0054" - readonly property string detach: "\u0055" - readonly property string distributeBottom: "\u0056" - readonly property string distributeCenterHorizontal: "\u0057" - readonly property string distributeCenterVertical: "\u0058" - readonly property string distributeLeft: "\u0059" - readonly property string distributeOriginBottomRight: "\u005A" - readonly property string distributeOriginCenter: "\u005B" - readonly property string distributeOriginNone: "\u005C" - readonly property string distributeOriginTopLeft: "\u005D" - readonly property string distributeRight: "\u005E" - readonly property string distributeSpacingHorizontal: "\u005F" - readonly property string distributeSpacingVertical: "\u0060" - readonly property string distributeTop: "\u0061" - readonly property string download: "\u0062" - readonly property string downloadUnavailable: "\u0063" - readonly property string downloadUpdate: "\u0064" - readonly property string downloaded: "\u0065" - readonly property string edit: "\u0066" - readonly property string eyeDropper: "\u0067" - readonly property string favorite: "\u0068" - readonly property string flowAction: "\u0069" - readonly property string flowTransition: "\u006A" - readonly property string fontStyleBold: "\u006B" - readonly property string fontStyleItalic: "\u006C" - readonly property string fontStyleStrikethrough: "\u006D" - readonly property string fontStyleUnderline: "\u006E" - readonly property string gradient: "\u006F" - readonly property string gridView: "\u0070" - readonly property string idAliasOff: "\u0071" - readonly property string idAliasOn: "\u0072" - readonly property string infinity: "\u0073" - readonly property string keyframe: "\u0074" - readonly property string linkTriangle: "\u0075" - readonly property string linked: "\u0076" - readonly property string listView: "\u0077" - readonly property string lockOff: "\u0078" - readonly property string lockOn: "\u0079" - readonly property string materialPreviewEnvironment: "\u007A" - readonly property string materialPreviewModel: "\u007B" - readonly property string mergeCells: "\u007C" - readonly property string minus: "\u007D" - readonly property string mirror: "\u007E" - readonly property string newMaterial: "\u007F" - readonly property string openLink: "\u0080" - readonly property string openMaterialBrowser: "\u0081" - readonly property string orientation: "\u0082" - readonly property string paddingEdge: "\u0083" - readonly property string paddingFrame: "\u0084" - readonly property string pasteStyle: "\u0085" - readonly property string pause: "\u0086" - readonly property string pin: "\u0087" - readonly property string play: "\u0088" - readonly property string plus: "\u0089" - readonly property string promote: "\u008A" - readonly property string readOnly: "\u008B" - readonly property string redo: "\u008C" - readonly property string rotationFill: "\u008D" - readonly property string rotationOutline: "\u008E" - readonly property string search: "\u008F" - readonly property string sectionToggle: "\u0090" - readonly property string splitColumns: "\u0091" - readonly property string splitRows: "\u0092" - readonly property string startNode: "\u0093" - readonly property string testIcon: "\u0094" - readonly property string textAlignBottom: "\u0095" - readonly property string textAlignCenter: "\u0096" - readonly property string textAlignJustified: "\u0097" - readonly property string textAlignLeft: "\u0098" - readonly property string textAlignMiddle: "\u0099" - readonly property string textAlignRight: "\u009A" - readonly property string textAlignTop: "\u009B" - readonly property string textBulletList: "\u009D" - readonly property string textFullJustification: "\u009E" - readonly property string textNumberedList: "\u009F" - readonly property string tickIcon: "\u00A0" - readonly property string translationCreateFiles: "\u00A1" - readonly property string translationCreateReport: "\u00A2" - readonly property string translationExport: "\u00A3" - readonly property string translationImport: "\u00A4" - readonly property string translationSelectLanguages: "\u00A5" - readonly property string translationTest: "\u00A6" - readonly property string transparent: "\u00A7" - readonly property string triState: "\u00A8" - readonly property string triangleArcA: "\u00A9" - readonly property string triangleArcB: "\u00AA" - readonly property string triangleCornerA: "\u00AB" - readonly property string triangleCornerB: "\u00AC" - readonly property string unLinked: "\u00AE" - readonly property string undo: "\u00AF" - readonly property string unpin: "\u00B0" - readonly property string upDownIcon: "\u00B1" - readonly property string upDownSquare2: "\u00B2" - readonly property string visibilityOff: "\u00B3" - readonly property string visibilityOn: "\u00B4" - readonly property string wildcard: "\u00B5" - readonly property string wizardsAutomotive: "\u00B6" - readonly property string wizardsDesktop: "\u00B7" - readonly property string wizardsGeneric: "\u00B8" - readonly property string wizardsMcuEmpty: "\u00B9" - readonly property string wizardsMcuGraph: "\u00BA" - readonly property string wizardsMobile: "\u00BB" - readonly property string wizardsUnknown: "\u00BC" - readonly property string zoomAll: "\u00BD" - readonly property string zoomIn: "\u00BE" - readonly property string zoomOut: "\u00BF" - readonly property string zoomSelection: "\u00C0" + readonly property string colorPopupClose: "\u0045" + readonly property string columnsAndRows: "\u0046" + readonly property string copyStyle: "\u0047" + readonly property string cornerA: "\u0048" + readonly property string cornerB: "\u0049" + readonly property string cornersAll: "\u004A" + readonly property string curveDesigner: "\u004B" + readonly property string curveEditor: "\u004C" + readonly property string customMaterialEditor: "\u004D" + readonly property string decisionNode: "\u004E" + readonly property string deleteColumn: "\u004F" + readonly property string deleteMaterial: "\u0050" + readonly property string deleteRow: "\u0051" + readonly property string deleteTable: "\u0052" + readonly property string detach: "\u0053" + readonly property string distributeBottom: "\u0054" + readonly property string distributeCenterHorizontal: "\u0055" + readonly property string distributeCenterVertical: "\u0056" + readonly property string distributeLeft: "\u0057" + readonly property string distributeOriginBottomRight: "\u0058" + readonly property string distributeOriginCenter: "\u0059" + readonly property string distributeOriginNone: "\u005A" + readonly property string distributeOriginTopLeft: "\u005B" + readonly property string distributeRight: "\u005C" + readonly property string distributeSpacingHorizontal: "\u005D" + readonly property string distributeSpacingVertical: "\u005E" + readonly property string distributeTop: "\u005F" + readonly property string download: "\u0060" + readonly property string downloadUnavailable: "\u0061" + readonly property string downloadUpdate: "\u0062" + readonly property string downloaded: "\u0063" + readonly property string edit: "\u0064" + readonly property string eyeDropper: "\u0065" + readonly property string favorite: "\u0066" + readonly property string flowAction: "\u0067" + readonly property string flowTransition: "\u0068" + readonly property string fontStyleBold: "\u0069" + readonly property string fontStyleItalic: "\u006A" + readonly property string fontStyleStrikethrough: "\u006B" + readonly property string fontStyleUnderline: "\u006C" + readonly property string gradient: "\u006D" + readonly property string gridView: "\u006E" + readonly property string idAliasOff: "\u006F" + readonly property string idAliasOn: "\u0070" + readonly property string imported: "\u0071" + readonly property string infinity: "\u0072" + readonly property string keyframe: "\u0073" + readonly property string linkTriangle: "\u0074" + readonly property string linked: "\u0075" + readonly property string listView: "\u0076" + readonly property string lockOff: "\u0077" + readonly property string lockOn: "\u0078" + readonly property string materialPreviewEnvironment: "\u0079" + readonly property string materialPreviewModel: "\u007A" + readonly property string mergeCells: "\u007B" + readonly property string minus: "\u007C" + readonly property string mirror: "\u007D" + readonly property string newMaterial: "\u007E" + readonly property string openMaterialBrowser: "\u007F" + readonly property string orientation: "\u0080" + readonly property string paddingEdge: "\u0081" + readonly property string paddingFrame: "\u0082" + readonly property string pasteStyle: "\u0083" + readonly property string pause: "\u0084" + readonly property string pin: "\u0085" + readonly property string play: "\u0086" + readonly property string plus: "\u0087" + readonly property string promote: "\u0088" + readonly property string readOnly: "\u0089" + readonly property string redo: "\u008A" + readonly property string rotationFill: "\u008B" + readonly property string rotationOutline: "\u008C" + readonly property string search: "\u008D" + readonly property string sectionToggle: "\u008E" + readonly property string splitColumns: "\u008F" + readonly property string splitRows: "\u0090" + readonly property string startNode: "\u0091" + readonly property string testIcon: "\u0092" + readonly property string textAlignBottom: "\u0093" + readonly property string textAlignCenter: "\u0094" + readonly property string textAlignJustified: "\u0095" + readonly property string textAlignLeft: "\u0096" + readonly property string textAlignMiddle: "\u0097" + readonly property string textAlignRight: "\u0098" + readonly property string textAlignTop: "\u0099" + readonly property string textBulletList: "\u009A" + readonly property string textFullJustification: "\u009B" + readonly property string textNumberedList: "\u009D" + readonly property string tickIcon: "\u009E" + readonly property string translationCreateFiles: "\u009F" + readonly property string translationCreateReport: "\u00A0" + readonly property string translationExport: "\u00A1" + readonly property string translationImport: "\u00A2" + readonly property string translationSelectLanguages: "\u00A3" + readonly property string translationTest: "\u00A4" + readonly property string transparent: "\u00A5" + readonly property string triState: "\u00A6" + readonly property string triangleArcA: "\u00A7" + readonly property string triangleArcB: "\u00A8" + readonly property string triangleCornerA: "\u00A9" + readonly property string triangleCornerB: "\u00AA" + readonly property string unLinked: "\u00AB" + readonly property string undo: "\u00AC" + readonly property string unpin: "\u00AE" + readonly property string upDownIcon: "\u00AF" + readonly property string upDownSquare2: "\u00B0" + readonly property string visibilityOff: "\u00B1" + readonly property string visibilityOn: "\u00B2" + readonly property string wildcard: "\u00B3" + readonly property string wizardsAutomotive: "\u00B4" + readonly property string wizardsDesktop: "\u00B5" + readonly property string wizardsGeneric: "\u00B6" + readonly property string wizardsMcuEmpty: "\u00B7" + readonly property string wizardsMcuGraph: "\u00B8" + readonly property string wizardsMobile: "\u00B9" + readonly property string wizardsUnknown: "\u00BA" + readonly property string zoomAll: "\u00BB" + readonly property string zoomIn: "\u00BC" + readonly property string zoomOut: "\u00BD" + readonly property string zoomSelection: "\u00BE" readonly property font iconFont: Qt.font({ "family": controlIcons.name, diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf index acd8df6ce3f..e3b40281c82 100644 Binary files a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf and b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf differ diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/wizard.json index 9d60367997a..3510d3e190b 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application-3d/wizard.json @@ -328,6 +328,10 @@ "source": "../common/fonts.txt", "target": "%{ProjectDirectory}/content/fonts/fonts.txt" }, + { + "source": "../common/asset_imports.txt", + "target": "%{ProjectDirectory}/asset_imports/asset_imports.txt" + }, { "source": "../common/CMakeLists.imports.txt.tpl", "target": "%{ProjectDirectory}/imports/CMakeLists.txt" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json index 6912200c14b..68ffb6cff17 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/application/wizard.json @@ -324,6 +324,10 @@ "source": "../common/fonts.txt", "target": "%{ProjectDirectory}/content/fonts/fonts.txt" }, + { + "source": "../common/asset_imports.txt", + "target": "%{ProjectDirectory}/asset_imports/asset_imports.txt" + }, { "source": "../common/CMakeLists.imports.txt.tpl", "target": "%{ProjectDirectory}/imports/CMakeLists.txt" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/common/asset_imports.txt b/share/qtcreator/qmldesigner/studio_templates/projects/common/asset_imports.txt new file mode 100644 index 00000000000..84c843f100d --- /dev/null +++ b/share/qtcreator/qmldesigner/studio_templates/projects/common/asset_imports.txt @@ -0,0 +1 @@ +Imported 3D assets and components imported from bundles will be created in this folder. diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/desktop-launcher/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/desktop-launcher/wizard.json index 20a3baba9c7..ab1830752b1 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/desktop-launcher/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/desktop-launcher/wizard.json @@ -323,6 +323,10 @@ "source": "../common/fonts.txt", "target": "%{ProjectDirectory}/content/fonts/fonts.txt" }, + { + "source": "../common/asset_imports.txt", + "target": "%{ProjectDirectory}/asset_imports/asset_imports.txt" + }, { "source": "../common/CMakeLists.imports.txt.tpl", "target": "%{ProjectDirectory}/imports/CMakeLists.txt" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-scroll/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-scroll/wizard.json index 3117c7e0544..1a9e07cb63c 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-scroll/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-scroll/wizard.json @@ -280,6 +280,10 @@ "source": "../common/fonts.txt", "target": "%{ProjectDirectory}/content/fonts/fonts.txt" }, + { + "source": "../common/asset_imports.txt", + "target": "%{ProjectDirectory}/asset_imports/asset_imports.txt" + }, { "source": "../common/CMakeLists.imports.txt.tpl", "target": "%{ProjectDirectory}/imports/CMakeLists.txt" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/wizard.json index cb083e84f4b..2e07c4725a1 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-stack/wizard.json @@ -284,6 +284,10 @@ "source": "../common/fonts.txt", "target": "%{ProjectDirectory}/content/fonts/fonts.txt" }, + { + "source": "../common/asset_imports.txt", + "target": "%{ProjectDirectory}/asset_imports/asset_imports.txt" + }, { "source": "../common/CMakeLists.imports.txt.tpl", "target": "%{ProjectDirectory}/imports/CMakeLists.txt" diff --git a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/wizard.json b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/wizard.json index 1c1137afddb..422db173497 100644 --- a/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/wizard.json +++ b/share/qtcreator/qmldesigner/studio_templates/projects/mobile-swipe/wizard.json @@ -284,6 +284,10 @@ "source": "../common/fonts.txt", "target": "%{ProjectDirectory}/content/fonts/fonts.txt" }, + { + "source": "../common/asset_imports.txt", + "target": "%{ProjectDirectory}/asset_imports/asset_imports.txt" + }, { "source": "../common/CMakeLists.imports.txt.tpl", "target": "%{ProjectDirectory}/imports/CMakeLists.txt" diff --git a/share/qtcreator/themes/dark.creatortheme b/share/qtcreator/themes/dark.creatortheme index b762ccdcc97..5f721a548d9 100644 --- a/share/qtcreator/themes/dark.creatortheme +++ b/share/qtcreator/themes/dark.creatortheme @@ -16,8 +16,8 @@ alternateBackground=ff515151 error=ffd84044 warning=ffe0b716 splitterColor=ff313131 -textColorLink=ff007af4 -textColorLinkVisited=ffa57aff +textColorLink=ff8ab4f8 +textColorLinkVisited=ffc58af9 backgroundColorDisabled=ff444444 qmlDesignerButtonColor=ff3c3e40 diff --git a/share/qtcreator/translations/qtcreator_cs.ts b/share/qtcreator/translations/qtcreator_cs.ts index 3ffa42b5276..710b67ccb34 100644 --- a/share/qtcreator/translations/qtcreator_cs.ts +++ b/share/qtcreator/translations/qtcreator_cs.ts @@ -10586,7 +10586,7 @@ ve svém .pro souboru. - ProjectExplorer::Internal::FolderNavigationWidgetFactory + Core::FolderNavigationWidgetFactory File System Souborový systém @@ -25437,7 +25437,7 @@ Proces Pdb po určité době od úspěšného spuštění spadl. - ProjectExplorer::Internal::FolderNavigationWidget + Core::FolderNavigationWidget Open Otevřít diff --git a/share/qtcreator/translations/qtcreator_da.ts b/share/qtcreator/translations/qtcreator_da.ts index df9db029fb9..4532a3a74dd 100644 --- a/share/qtcreator/translations/qtcreator_da.ts +++ b/share/qtcreator/translations/qtcreator_da.ts @@ -23538,7 +23538,7 @@ Ekskludering: %2 - ProjectExplorer::Internal::FolderNavigationWidget + Core::FolderNavigationWidget The file "%1" was renamed to "%2", but the following projects could not be automatically changed: %3 Filen "%1" blev omdøbt til "%2", men følgende projekter kunne ikke ændres automatisk: %3 @@ -23597,7 +23597,7 @@ Ekskludering: %2 - ProjectExplorer::Internal::FolderNavigationWidgetFactory + Core::FolderNavigationWidgetFactory File System Filsystem @@ -41850,7 +41850,7 @@ skal være et repository krævet SSH-autentifikation (se dokumentation på SSH o - FilePropertiesDialog + Core::FilePropertiesDialog File Properties Filegenskaber diff --git a/share/qtcreator/translations/qtcreator_es.ts b/share/qtcreator/translations/qtcreator_es.ts index 5db41c08c69..ca2522fe184 100644 --- a/share/qtcreator/translations/qtcreator_es.ts +++ b/share/qtcreator/translations/qtcreator_es.ts @@ -7257,7 +7257,7 @@ Nombre base de librería: %1 - ProjectExplorer::Internal::FolderNavigationWidgetFactory + Core::FolderNavigationWidgetFactory File System Sistema de archivos diff --git a/share/qtcreator/translations/qtcreator_fr.ts b/share/qtcreator/translations/qtcreator_fr.ts index 99135b4a43a..175dd0dbbfa 100644 --- a/share/qtcreator/translations/qtcreator_fr.ts +++ b/share/qtcreator/translations/qtcreator_fr.ts @@ -11314,7 +11314,7 @@ francis : voila une nouvelle suggestion :) - ProjectExplorer::Internal::FolderNavigationWidgetFactory + Core::FolderNavigationWidgetFactory File System Système de fichier @@ -25525,7 +25525,7 @@ avec un mot de passe, que vous pouvez renseigner ci-dessus. - ProjectExplorer::Internal::FolderNavigationWidget + Core::FolderNavigationWidget Open Ouvrir diff --git a/share/qtcreator/translations/qtcreator_hr.ts b/share/qtcreator/translations/qtcreator_hr.ts index 3053522bea0..3f7108c4c98 100644 --- a/share/qtcreator/translations/qtcreator_hr.ts +++ b/share/qtcreator/translations/qtcreator_hr.ts @@ -2224,7 +2224,7 @@ Međutim, korištenje opuštenih i proširenih pravila također znači da nije m - FilePropertiesDialog + Core::FilePropertiesDialog File Properties Svojstva datoteke @@ -28995,7 +28995,7 @@ Isključivo: %2 - ProjectExplorer::Internal::FolderNavigationWidget + Core::FolderNavigationWidget The file "%1" was renamed to "%2", but the following projects could not be automatically changed: %3 @@ -29657,7 +29657,7 @@ Rename %2 to %3 anyway? - ProjectExplorer::Internal::FolderNavigationWidgetFactory + Core::FolderNavigationWidgetFactory File System diff --git a/share/qtcreator/translations/qtcreator_hu.ts b/share/qtcreator/translations/qtcreator_hu.ts index 9f2faeca741..f7c0857fc8c 100644 --- a/share/qtcreator/translations/qtcreator_hu.ts +++ b/share/qtcreator/translations/qtcreator_hu.ts @@ -11744,7 +11744,7 @@ Ok: %2 - ProjectExplorer::Internal::FolderNavigationWidgetFactory + Core::FolderNavigationWidgetFactory File System Fájlrendszer diff --git a/share/qtcreator/translations/qtcreator_it.ts b/share/qtcreator/translations/qtcreator_it.ts index b475a4a7235..bbbd0173674 100644 --- a/share/qtcreator/translations/qtcreator_it.ts +++ b/share/qtcreator/translations/qtcreator_it.ts @@ -7190,7 +7190,7 @@ Nome di base della libreria: %1 - ProjectExplorer::Internal::FolderNavigationWidgetFactory + Core::FolderNavigationWidgetFactory File System File System diff --git a/share/qtcreator/translations/qtcreator_ja.ts b/share/qtcreator/translations/qtcreator_ja.ts index 43f860bf2f1..7f212bbe547 100644 --- a/share/qtcreator/translations/qtcreator_ja.ts +++ b/share/qtcreator/translations/qtcreator_ja.ts @@ -24100,7 +24100,7 @@ Excluding: %2 - ProjectExplorer::Internal::FolderNavigationWidget + Core::FolderNavigationWidget The file "%1" was renamed to "%2", but the following projects could not be automatically changed: %3 ファイル "%1" が "%2" に名前変更されましたが、以下のプロジェクトは自動的に変更できませんでした:"%3" @@ -24175,7 +24175,7 @@ Excluding: %2 - ProjectExplorer::Internal::FolderNavigationWidgetFactory + Core::FolderNavigationWidgetFactory File System ファイルシステム @@ -45412,7 +45412,7 @@ Output: - FilePropertiesDialog + Core::FilePropertiesDialog Group: グループ: diff --git a/share/qtcreator/translations/qtcreator_pl.ts b/share/qtcreator/translations/qtcreator_pl.ts index 0538eed8015..611cdb586c5 100644 --- a/share/qtcreator/translations/qtcreator_pl.ts +++ b/share/qtcreator/translations/qtcreator_pl.ts @@ -7437,7 +7437,7 @@ Wykluczenia: %2 - ProjectExplorer::Internal::FolderNavigationWidgetFactory + Core::FolderNavigationWidgetFactory File System System plików @@ -11577,7 +11577,7 @@ Dla projektów CMake, upewnij się, że zmienna QML_IMPORT_PATH jest obecna w CM - ProjectExplorer::Internal::FolderNavigationWidget + Core::FolderNavigationWidget Open Otwórz diff --git a/share/qtcreator/translations/qtcreator_ru.ts b/share/qtcreator/translations/qtcreator_ru.ts index 4ed61f06aa2..21f3707a4ca 100644 --- a/share/qtcreator/translations/qtcreator_ru.ts +++ b/share/qtcreator/translations/qtcreator_ru.ts @@ -19612,7 +19612,7 @@ will also disable the following plugins: - FilePropertiesDialog + Core::FilePropertiesDialog File Properties Свойства файла @@ -30461,7 +30461,7 @@ What should Qt Creator do now? - ProjectExplorer::Internal::FolderNavigationWidget + Core::FolderNavigationWidget Open "%1" Открыть «%1» @@ -30520,7 +30520,7 @@ What should Qt Creator do now? - ProjectExplorer::Internal::FolderNavigationWidgetFactory + Core::FolderNavigationWidgetFactory File System Файловая система diff --git a/share/qtcreator/translations/qtcreator_sl.ts b/share/qtcreator/translations/qtcreator_sl.ts index 753a55b51f6..7f4ef186521 100644 --- a/share/qtcreator/translations/qtcreator_sl.ts +++ b/share/qtcreator/translations/qtcreator_sl.ts @@ -7499,7 +7499,7 @@ enojen »Vstopi« za oddajo signala pa vas bo privedel neposredno do ustrezne pr - ProjectExplorer::Internal::FolderNavigationWidgetFactory + Core::FolderNavigationWidgetFactory File System Datotečni sistem @@ -16530,7 +16530,7 @@ Desetiška predznačena vrednost (najprej veliki konec): %4 - ProjectExplorer::Internal::FolderNavigationWidget + Core::FolderNavigationWidget Open Odpri diff --git a/share/qtcreator/translations/qtcreator_uk.ts b/share/qtcreator/translations/qtcreator_uk.ts index e86e108f6e3..7ae1def1d11 100644 --- a/share/qtcreator/translations/qtcreator_uk.ts +++ b/share/qtcreator/translations/qtcreator_uk.ts @@ -14857,7 +14857,7 @@ Reason: %2 - ProjectExplorer::Internal::FolderNavigationWidget + Core::FolderNavigationWidget Open Відкрити @@ -14904,7 +14904,7 @@ Reason: %2 - ProjectExplorer::Internal::FolderNavigationWidgetFactory + Core::FolderNavigationWidgetFactory File System Файлова система diff --git a/share/qtcreator/translations/qtcreator_zh_CN.ts b/share/qtcreator/translations/qtcreator_zh_CN.ts index d28eabcb22d..551984479a6 100644 --- a/share/qtcreator/translations/qtcreator_zh_CN.ts +++ b/share/qtcreator/translations/qtcreator_zh_CN.ts @@ -10454,7 +10454,7 @@ SOURCES *= .../ide/main/bin/dumper/dumper.cpp - ProjectExplorer::Internal::FolderNavigationWidgetFactory + Core::FolderNavigationWidgetFactory File System 文件系统 @@ -22789,7 +22789,7 @@ Previous decimal signed value (big endian): %4 - ProjectExplorer::Internal::FolderNavigationWidget + Core::FolderNavigationWidget Open 打开 diff --git a/share/qtcreator/translations/qtcreator_zh_TW.ts b/share/qtcreator/translations/qtcreator_zh_TW.ts index f2d9c06bfa3..1b0160912b6 100644 --- a/share/qtcreator/translations/qtcreator_zh_TW.ts +++ b/share/qtcreator/translations/qtcreator_zh_TW.ts @@ -6939,7 +6939,7 @@ Add, modify, and remove document filters, which determine the documentation set - ProjectExplorer::Internal::FolderNavigationWidgetFactory + Core::FolderNavigationWidgetFactory File System 檔案系統 @@ -13486,7 +13486,7 @@ For qmlproject projects, use the importPaths property to add import paths. - ProjectExplorer::Internal::FolderNavigationWidget + Core::FolderNavigationWidget Open 開啟 diff --git a/src/app/main.cpp b/src/app/main.cpp index db8ce6bde89..fbb02042c6b 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -220,22 +220,35 @@ static void setupInstallSettings(QString &installSettingspath) QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR), QLatin1String(Core::Constants::IDE_CASED_ID))); installSettingspath.clear(); } - // Check if the default install settings contain a setting for the actual install settings. - // This can be an absolute path, or a path relative to applicationDirPath(). - // The result is interpreted like -settingspath, but for SystemScope static const char kInstallSettingsKey[] = "Settings/InstallSettings"; QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, installSettingspath.isEmpty() ? resourcePath() : installSettingspath); - QSettings installSettings(QSettings::IniFormat, QSettings::UserScope, - QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR), - QLatin1String(Core::Constants::IDE_CASED_ID)); - if (installSettings.contains(kInstallSettingsKey)) { - QString installSettingsPath = installSettings.value(kInstallSettingsKey).toString(); - if (QDir::isRelativePath(installSettingsPath)) - installSettingsPath = applicationDirPath() + '/' + installSettingsPath; - QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, installSettingsPath); - } + // Check if the default install settings contain a setting for the actual install settings. + // This can be an absolute path, or a path relative to applicationDirPath(). + // The result is interpreted like -settingspath, but for SystemScope. + // + // Through the sdktool split that is upcoming, the new install settings might redirect + // yet a second time. So try this a few times. + // (Only the first time with QSettings::UserScope, to allow setting the install settings path + // in the user settings.) + QSettings::Scope scope = QSettings::UserScope; + int count = 0; + bool containsInstallSettingsKey = false; + do { + QSettings installSettings(QSettings::IniFormat, scope, + QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR), + QLatin1String(Core::Constants::IDE_CASED_ID)); + containsInstallSettingsKey = installSettings.contains(kInstallSettingsKey); + if (containsInstallSettingsKey) { + QString newInstallSettingsPath = installSettings.value(kInstallSettingsKey).toString(); + if (QDir::isRelativePath(newInstallSettingsPath)) + newInstallSettingsPath = applicationDirPath() + '/' + newInstallSettingsPath; + QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, newInstallSettingsPath); + } + scope = QSettings::SystemScope; // UserScope only the first time we check + ++count; + } while (containsInstallSettingsKey && count < 3); } static Utils::QtcSettings *createUserSettings() diff --git a/src/libs/tracing/qml/CategoryLabel.qml b/src/libs/tracing/qml/CategoryLabel.qml index 88dc5e89f46..35c4af3ee79 100644 --- a/src/libs/tracing/qml/CategoryLabel.qml +++ b/src/libs/tracing/qml/CategoryLabel.qml @@ -38,13 +38,14 @@ Item { id: dragArea anchors.fill: txt drag.target: dragger - cursorShape: dragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor - drag.minimumY: dragging ? 0 : -dragOffset // Account for parent change below - drag.maximumY: visibleHeight - (dragging ? 0 : dragOffset) + cursorShape: labelContainer.dragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor + // Account for parent change below + drag.minimumY: labelContainer.dragging ? 0 : -labelContainer.dragOffset + drag.maximumY: labelContainer.visibleHeight - (labelContainer.dragging ? 0 : labelContainer.dragOffset) drag.axis: Drag.YAxis hoverEnabled: true ToolTip { - text: model.tooltip || labelContainer.text + text: labelContainer.model.tooltip || labelContainer.text visible: enabled && parent.containsMouse delay: 1000 } @@ -76,30 +77,30 @@ Item { text: labelContainer.text color: Theme.color(Theme.PanelTextColorLight) - height: model ? model.defaultRowHeight : 0 + height: labelContainer.model ? labelContainer.model.defaultRowHeight : 0 verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } Column { id: labelsArea - property QtObject parentModel: model + property QtObject parentModel: labelContainer.model anchors.top: txt.bottom - visible: expanded + visible: labelContainer.expanded Repeater { - model: expanded ? labels.length : 0 + model: labelContainer.expanded ? labelContainer.labels.length : 0 Loader { id: loader // Initially y == 0 for all the items. Don't enable them until they have been moved // into place. property int offset: (index === 0 || y > 0) ? (y + txt.height) : contentHeight - active: contentBottom > offset + active: labelContainer.contentBottom > offset width: labelContainer.width height: labelsArea.parentModel ? labelsArea.parentModel.rowHeight(index + 1) : 0 sourceComponent: RowLabel { - label: labels[index]; + label: labelContainer.labels[index]; onSelectBySelectionId: { if (labelContainer.model.hasMixedTypesInExpandedState) return; @@ -127,11 +128,12 @@ Item { property var texts: [] property int currentNote: -1 Connections { - target: notesModel + target: labelContainer.notesModel function onChanged(typeId, modelId, timelineIndex) { // This will only be called if notesModel != null. - if (modelId === -1 || modelId === model.modelId) { - var notes = notesModel.byTimelineModel(model.modelId); + if (modelId === -1 || modelId === labelContainer.model.modelId) { + var notes = + labelContainer.notesModel.byTimelineModel(labelContainer.model.modelId); var newTexts = []; var newEventIds = []; for (var i in notes) { @@ -161,11 +163,11 @@ Item { anchors.verticalCenter: txt.verticalCenter anchors.right: parent.right implicitHeight: txt.height - 1 - enabled: expanded || (model && !model.empty) - imageSource: expanded ? "image://icons/close_split" : "image://icons/split" - ToolTip.text: expanded ? qsTranslate("Tracing", "Collapse category") - : qsTranslate("Tracing", "Expand category") - onClicked: model.expanded = !expanded + enabled: labelContainer.expanded || (labelContainer.model && !labelContainer.model.empty) + imageSource: labelContainer.expanded ? "image://icons/close_split" : "image://icons/split" + ToolTip.text: labelContainer.expanded ? qsTranslate("Tracing", "Collapse category") + : qsTranslate("Tracing", "Expand category") + onClicked: labelContainer.model.expanded = !labelContainer.expanded } Rectangle { @@ -199,7 +201,7 @@ Item { when: dragger.Drag.active ParentChange { target: dragger - parent: draggerParent + parent: labelContainer.draggerParent } PropertyChanges { target: dragger diff --git a/src/libs/tracing/qml/FlameGraphDelegate.qml b/src/libs/tracing/qml/FlameGraphDelegate.qml index ac36659a1a3..b80533776e0 100644 --- a/src/libs/tracing/qml/FlameGraphDelegate.qml +++ b/src/libs/tracing/qml/FlameGraphDelegate.qml @@ -26,18 +26,18 @@ Item { x: parent === null ? 0 : parent.width * FlameGraph.relativePosition Rectangle { - border.color: borderColor - border.width: borderWidth - color: Qt.hsla((level % 12) / 72, 0.9 + Math.random() / 10, + border.color: flamegraphItem.borderColor + border.width: flamegraphItem.borderWidth + color: Qt.hsla((flamegraphItem.level % 12) / 72, 0.9 + Math.random() / 10, 0.45 + Math.random() / 10, 0.9 + Math.random() / 10); - height: itemHeight; + height: flamegraphItem.itemHeight anchors.left: flamegraphItem.left anchors.right: flamegraphItem.right anchors.bottom: flamegraphItem.bottom TimelineText { id: text - visible: textVisible + visible: flamegraphItem.textVisible anchors.fill: parent anchors.margins: 5 verticalAlignment: Text.AlignVCenter @@ -45,7 +45,7 @@ Item { text: flamegraphItem.text elide: Text.ElideRight wrapMode: Text.WrapAtWordBoundaryOrAnywhere - font.bold: isSelected + font.bold: flamegraphItem.isSelected } MouseArea { diff --git a/src/libs/tracing/qml/MainView.qml b/src/libs/tracing/qml/MainView.qml index 119892a1cec..af6c259f77e 100644 --- a/src/libs/tracing/qml/MainView.qml +++ b/src/libs/tracing/qml/MainView.qml @@ -129,7 +129,7 @@ Rectangle { color: Theme.color(Theme.PanelStatusBarBackgroundColor) modelProxy: timelineModelAggregator zoomer: zoomControl - reverseSelect: shiftPressed + reverseSelect: root.shiftPressed onMoveCategories: (sourceIndex, targetIndex) => { content.moveCategories(sourceIndex, targetIndex) @@ -228,7 +228,7 @@ Rectangle { MouseArea { id: selectionRangeControl - enabled: selectionRangeMode && + enabled: root.selectionRangeMode && selectionRange.creationState !== selectionRange.creationFinished anchors.right: content.right anchors.left: buttonsBar.right @@ -269,7 +269,7 @@ Rectangle { interactive: false x: content.x y: content.y - height: (selectionRangeMode && + height: (root.selectionRangeMode && selectionRange.creationState !== selectionRange.creationInactive) ? content.height : 0 width: content.width @@ -328,7 +328,7 @@ Rectangle { endTime: zoomControl.selectionEnd referenceDuration: zoomControl.rangeDuration showDuration: selectionRange.rangeWidth > 1 - hasContents: selectionRangeMode && + hasContents: root.selectionRangeMode && selectionRange.creationState !== selectionRange.creationInactive onRecenter: { @@ -356,7 +356,7 @@ Rectangle { locked: content.selectionLocked onRecenterOnItem: { - content.select(selectedModel, selectedItem) + content.select(root.selectedModel, root.selectedItem) } onLockedChanged: { @@ -368,10 +368,11 @@ Rectangle { } onUpdateNote: (text) => { - if (timelineModelAggregator.notes && selectedModel != -1 && selectedItem != -1) { + if (timelineModelAggregator.notes && root.selectedModel != -1 + && root.selectedItem != -1) { timelineModelAggregator.notes.setText( - timelineModelAggregator.models[selectedModel].modelId, - selectedItem, text); + timelineModelAggregator.models[root.selectedModel].modelId, + root.selectedItem, text); } } diff --git a/src/libs/tracing/qml/Overview.qml b/src/libs/tracing/qml/Overview.qml index e0a02c39317..0e7a07a603d 100644 --- a/src/libs/tracing/qml/Overview.qml +++ b/src/libs/tracing/qml/Overview.qml @@ -46,8 +46,8 @@ Rectangle { } Connections { - target: zoomer - function onRangeChanged() { updateRangeMover(); } + target: overview.zoomer + function onRangeChanged() { overview.updateRangeMover(); } } TimeDisplay { @@ -59,9 +59,9 @@ Rectangle { height: 10 fontSize: 6 labelsHeight: 10 - windowStart: zoomer.traceStart - alignedWindowStart: zoomer.traceStart - rangeDuration: zoomer.traceDuration + windowStart: overview.zoomer.traceStart + alignedWindowStart: overview.zoomer.traceStart + rangeDuration: overview.zoomer.traceDuration contentX: 0 offsetX: 0 } @@ -75,35 +75,35 @@ Rectangle { id: renderArea Repeater { - model: modelProxy.models + model: overview.modelProxy.models TimelineOverviewRenderer { model: modelData zoomer: overview.zoomer - notes: modelProxy.notes + notes: overview.modelProxy.notes width: renderArea.width - height: renderArea.height / modelProxy.models.length + height: renderArea.height / overview.modelProxy.models.length } } } Repeater { id: noteSigns - property var modelsById: modelProxy.models.reduce(function(prev, model) { + property var modelsById: overview.modelProxy.models.reduce(function(prev, model) { prev[model.modelId] = model; return prev; }, {}); property int vertSpace: renderArea.height / 7 property color noteColor: Theme.color(Theme.Timeline_HighlightColor) - readonly property double spacing: parent.width / zoomer.traceDuration + readonly property double spacing: parent.width / overview.zoomer.traceDuration - model: modelProxy.notes ? modelProxy.notes.count : 0 + model: overview.modelProxy.notes ? overview.modelProxy.notes.count : 0 Item { - property int timelineIndex: modelProxy.notes.timelineIndex(index) - property int timelineModel: modelProxy.notes.timelineModel(index) + property int timelineIndex: overview.modelProxy.notes.timelineIndex(index) + property int timelineModel: overview.modelProxy.notes.timelineModel(index) property double startTime: noteSigns.modelsById[timelineModel].startTime(timelineIndex) property double endTime: noteSigns.modelsById[timelineModel].endTime(timelineIndex) - x: ((startTime + endTime) / 2 - zoomer.traceStart) * noteSigns.spacing + x: ((startTime + endTime) / 2 - overview.zoomer.traceStart) * noteSigns.spacing y: timebar.height + noteSigns.vertSpace height: noteSigns.vertSpace * 5 width: 2 @@ -156,7 +156,7 @@ Rectangle { RangeMover { id: rangeMover - visible: modelProxy.height > 0 + visible: overview.modelProxy.height > 0 onRangeLeftChanged: overview.updateZoomer() onRangeRightChanged: overview.updateZoomer() } diff --git a/src/libs/tracing/qml/RangeDetails.qml b/src/libs/tracing/qml/RangeDetails.qml index 14d6aa9e7af..6998767bea3 100644 --- a/src/libs/tracing/qml/RangeDetails.qml +++ b/src/libs/tracing/qml/RangeDetails.qml @@ -60,9 +60,9 @@ Item { Rectangle { id: titleBar width: parent.width - height: titleBarHeight + height: rangeDetails.titleBarHeight color: Theme.color(Theme.Timeline_PanelHeaderColor) - border.width: borderWidth + border.width: rangeDetails.borderWidth border.color: Theme.color(Theme.PanelTextColorMid) TimelineText { @@ -72,8 +72,8 @@ Item { verticalAlignment: Text.AlignVCenter anchors.left: parent.left anchors.right: closeIcon.left - anchors.leftMargin: outerMargin - anchors.rightMargin: innerMargin + anchors.leftMargin: rangeDetails.outerMargin + anchors.rightMargin: rangeDetails.innerMargin anchors.top: parent.top anchors.bottom: parent.bottom color: Theme.color(Theme.PanelTextColorLight) @@ -93,11 +93,11 @@ Item { ImageToolButton { id: lockIcon - imageSource: "image://icons/lock_" + (locked ? "closed" : "open") + imageSource: "image://icons/lock_" + (rangeDetails.locked ? "closed" : "open") anchors.top: closeIcon.top anchors.right: closeIcon.left implicitHeight: typeTitle.height - onClicked: locked = !locked + onClicked: rangeDetails.locked = !rangeDetails.locked ToolTip.text: qsTranslate("Tracing", "View event information on mouseover.") } @@ -121,7 +121,7 @@ Item { anchors.right: parent.right anchors.bottom: dragHandle.bottom - border.width: borderWidth + border.width: rangeDetails.borderWidth border.color: Theme.color(Theme.PanelTextColorMid) } @@ -130,17 +130,17 @@ Item { anchors.left: parent.left anchors.top: titleBar.bottom - anchors.topMargin: innerMargin - anchors.leftMargin: outerMargin - anchors.rightMargin: outerMargin + anchors.topMargin: rangeDetails.innerMargin + anchors.leftMargin: rangeDetails.outerMargin + anchors.rightMargin: rangeDetails.outerMargin - spacing: innerMargin + spacing: rangeDetails.innerMargin columns: 2 - property int minimumWidth: minimumInnerWidth + property int minimumWidth: rangeDetails.minimumInnerWidth onPositioningComplete: { // max(width of longest label * 2, minimumInnerWidth) - var result = minimumInnerWidth; + var result = rangeDetails.minimumInnerWidth; for (var i = 0; i < children.length; ++i) { if (children[i].isLabel) result = Math.max(children[i].implicitWidth * 2 + innerMargin, result); @@ -149,12 +149,14 @@ Item { minimumWidth = result + 2 * outerMargin; } - property int labelWidth: Math.ceil((minimumWidth - innerMargin) / 2) - outerMargin - property int valueWidth: dragHandle.x - labelWidth - innerMargin - outerMargin + property int labelWidth: Math.ceil((minimumWidth - rangeDetails.innerMargin) / 2) + - rangeDetails.outerMargin + property int valueWidth: dragHandle.x - labelWidth - rangeDetails.innerMargin + - rangeDetails.outerMargin onMinimumWidthChanged: { - if (dragHandle.x < minimumWidth - outerMargin) - dragHandle.x = minimumWidth - outerMargin; + if (dragHandle.x < minimumWidth - rangeDetails.outerMargin) + dragHandle.x = minimumWidth - rangeDetails.outerMargin; } Repeater { @@ -174,9 +176,9 @@ Item { anchors.left: parent.left anchors.right: parent.right - anchors.leftMargin: outerMargin - anchors.rightMargin: outerMargin - anchors.topMargin: visible ? innerMargin : 0 + anchors.leftMargin: rangeDetails.outerMargin + anchors.rightMargin: rangeDetails.outerMargin + anchors.topMargin: visible ? rangeDetails.innerMargin : 0 anchors.top: col.bottom height: visible ? implicitHeight : 0 @@ -201,7 +203,7 @@ Item { Timer { id: saveTimer onTriggered: { - if (!rangeDetails.readOnly) + if (!noteEdit.readOnly) rangeDetails.updateNote(noteEdit.text); } interval: 1000 @@ -211,15 +213,15 @@ Item { Item { id: dragHandle - width: outerMargin - height: outerMargin - x: initialWidth + width: rangeDetails.outerMargin + height: rangeDetails.outerMargin + x: rangeDetails.initialWidth anchors.top: noteEdit.bottom clip: true MouseArea { anchors.fill: parent drag.target: parent - drag.minimumX: col.minimumWidth - outerMargin + drag.minimumX: col.minimumWidth - rangeDetails.outerMargin drag.axis: Drag.XAxis cursorShape: Qt.SizeHorCursor } diff --git a/src/libs/tracing/qml/RangeMover.qml b/src/libs/tracing/qml/RangeMover.qml index c74489b2e79..eac5a1e2d45 100644 --- a/src/libs/tracing/qml/RangeMover.qml +++ b/src/libs/tracing/qml/RangeMover.qml @@ -30,7 +30,9 @@ Item { return Qt.rgba(color.r, color.g, color.b, Math.max(Math.min(color.a, 0.7), 0.3)); } - color: width > 1 ? alphaColor(dragArea.pressed ? dragColor : rangeColor) : singleLineColor + color: width > 1 ? alphaColor(dragArea.pressed ? rangeMover.dragColor + : rangeMover.rangeColor) + : rangeMover.singleLineColor } Item { @@ -49,7 +51,7 @@ Item { height: parent.height anchors.right: parent.left width: 7 - color: handleColor + color: rangeMover.handleColor visible: false Image { source: "image://icons/range_handle" @@ -72,7 +74,7 @@ Item { anchors.fill: leftBorderHandle drag.target: leftRange - drag.axis: "XAxis" + drag.axis: Drag.XAxis drag.minimumX: 0 drag.maximumX: rangeMover.width drag.onActiveChanged: drag.maximumX = rightRange.x @@ -102,7 +104,7 @@ Item { height: parent.height anchors.left: parent.right width: 7 - color: handleColor + color: rangeMover.handleColor visible: false Image { source: "image://icons/range_handle" @@ -125,7 +127,7 @@ Item { anchors.fill: rightBorderHandle drag.target: rightRange - drag.axis: "XAxis" + drag.axis: Drag.XAxis drag.minimumX: 0 drag.maximumX: rangeMover.width drag.onActiveChanged: drag.minimumX = leftRange.x @@ -150,7 +152,7 @@ Item { anchors.fill: selectedRange drag.target: leftRange - drag.axis: "XAxis" + drag.axis: Drag.XAxis drag.minimumX: 0 drag.maximumX: rangeMover.width - origWidth drag.onActiveChanged: origWidth = selectedRange.width diff --git a/src/libs/tracing/qml/RowLabel.qml b/src/libs/tracing/qml/RowLabel.qml index 37cab676619..16a70b53507 100644 --- a/src/libs/tracing/qml/RowLabel.qml +++ b/src/libs/tracing/qml/RowLabel.qml @@ -42,7 +42,7 @@ Button { onPressed: resizing = true onReleased: resizing = false - height: dragHeight + height: button.dragHeight anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right diff --git a/src/libs/tracing/qml/SelectionRange.qml b/src/libs/tracing/qml/SelectionRange.qml index 092b3b84c50..609159b7238 100644 --- a/src/libs/tracing/qml/SelectionRange.qml +++ b/src/libs/tracing/qml/SelectionRange.qml @@ -45,8 +45,8 @@ RangeMover { onRangeLeftChanged: updateZoomer() Connections { - target: zoomer - function onWindowChanged() { updateRange(); } + target: selectionRange.zoomer + function onWindowChanged() { selectionRange.updateRange(); } } function setPos(pos) { diff --git a/src/libs/tracing/qml/SelectionRangeDetails.qml b/src/libs/tracing/qml/SelectionRangeDetails.qml index e837135121b..a40e9753018 100644 --- a/src/libs/tracing/qml/SelectionRangeDetails.qml +++ b/src/libs/tracing/qml/SelectionRangeDetails.qml @@ -25,8 +25,8 @@ Item { // keep inside view Connections { target: selectionRangeDetails.parent - function onWidthChanged() { fitInView(); } - function onHeightChanged() { fitInView(); } + function onWidthChanged() { selectionRangeDetails.fitInView(); } + function onHeightChanged() { selectionRangeDetails.fitInView(); } } function fitInView() { @@ -79,14 +79,17 @@ Item { id: details property var contents: [ qsTranslate("Tracing", "Start") + ":", - TimeFormatter.format(startTime, referenceDuration), + TimeFormatter.format(selectionRangeDetails.startTime, + selectionRangeDetails.referenceDuration), (qsTranslate("Tracing", "End") + ":"), - TimeFormatter.format(endTime, referenceDuration), + TimeFormatter.format(selectionRangeDetails.endTime, + selectionRangeDetails.referenceDuration), (qsTranslate("Tracing", "Duration") + ":"), - TimeFormatter.format(duration, referenceDuration) + TimeFormatter.format(selectionRangeDetails.duration, + selectionRangeDetails.referenceDuration) ] - model: showDuration ? 6 : 2 + model: selectionRangeDetails.showDuration ? 6 : 2 Detail { isLabel: index % 2 === 0 text: details.contents[index] diff --git a/src/libs/tracing/qml/TimeDisplay.qml b/src/libs/tracing/qml/TimeDisplay.qml index 3748a6f7391..904c72ea711 100644 --- a/src/libs/tracing/qml/TimeDisplay.qml +++ b/src/libs/tracing/qml/TimeDisplay.qml @@ -40,7 +40,8 @@ Item { id: timeDisplayArea property int firstBlock: timeDisplay.offsetX / timeDisplay.pixelsPerBlock - property int offset: repeater.model > 0 ? repeater.model - (firstBlock % repeater.model) : 0; + property int offset: repeater.model > 0 ? repeater.model - (firstBlock % repeater.model) + : 0; Repeater { id: repeater diff --git a/src/libs/tracing/qml/TimeMarks.qml b/src/libs/tracing/qml/TimeMarks.qml index 8608efdbd0e..7583495d781 100644 --- a/src/libs/tracing/qml/TimeMarks.qml +++ b/src/libs/tracing/qml/TimeMarks.qml @@ -78,11 +78,11 @@ Item { property double maxVal: scope.model ? scope.model.rowMaxValue(index) : 0 property double valDiff: maxVal - minVal property bool scaleVisible: scope.model && scope.model.expanded && - height > scaleMinHeight && valDiff > 0 + height > timeMarks.scaleMinHeight && valDiff > 0 property double stepVal: { var ret = 1; - var ugly = Math.ceil(valDiff / Math.floor(height / scaleStepping)); + var ugly = Math.ceil(valDiff / Math.floor(height / timeMarks.scaleStepping)); while (isFinite(ugly) && ugly > 1) { ugly /= 2; ret *= 2; @@ -122,7 +122,8 @@ Item { anchors.bottomMargin: 2 anchors.leftMargin: 2 anchors.left: parent.left - text: prettyPrintScale(scaleItem.minVal + index * scaleItem.stepVal) + text: prettyPrintScale(scaleItem.minVal + + index * scaleItem.stepVal) } Rectangle { diff --git a/src/libs/tracing/qml/TimelineContent.qml b/src/libs/tracing/qml/TimelineContent.qml index ec26943dab8..7788f6d772d 100644 --- a/src/libs/tracing/qml/TimelineContent.qml +++ b/src/libs/tracing/qml/TimelineContent.qml @@ -96,11 +96,11 @@ Flickable { DelegateModel { id: timelineModel - model: modelProxy.models + model: flick.modelProxy.models delegate: TimelineRenderer { id: renderer model: modelData - notes: modelProxy.notes + notes: flick.modelProxy.notes zoomer: flick.zoomer selectionLocked: flick.selectionLocked x: 0 @@ -124,29 +124,29 @@ Flickable { } function recenter() { - if (modelData.endTime(selectedItem) < zoomer.rangeStart || - modelData.startTime(selectedItem) > zoomer.rangeEnd) { + if (modelData.endTime(renderer.selectedItem) < zoomer.rangeStart || + modelData.startTime(renderer.selectedItem) > zoomer.rangeEnd) { - var newStart = Math.max((modelData.startTime(selectedItem) + - modelData.endTime(selectedItem) - + var newStart = Math.max((modelData.startTime(renderer.selectedItem) + + modelData.endTime(renderer.selectedItem) - zoomer.rangeDuration) / 2, zoomer.traceStart); zoomer.setRange(newStart, Math.min(newStart + zoomer.rangeDuration, zoomer.traceEnd)); } - var row = modelData.row(selectedItem); + var row = renderer.model.row(renderer.selectedItem); var rowStart = modelData.rowOffset(row) + y; var rowEnd = rowStart + modelData.rowHeight(row); if (rowStart < flick.contentY || rowEnd - flick.height > flick.contentY) flick.contentY = (rowStart + rowEnd - flick.height) / 2; } - onSelectedItemChanged: flick.propagateSelection(index, selectedItem); + onSelectedItemChanged: flick.propagateSelection(index, renderer.selectedItem); Connections { - target: model + target: renderer.model function onDetailsChanged() { - if (selectedItem != -1) { + if (renderer.selectedItem != -1) { flick.propagateSelection(-1, -1); flick.propagateSelection(index, selectedItem); } diff --git a/src/libs/tracing/qml/TimelineLabels.qml b/src/libs/tracing/qml/TimelineLabels.qml index ea684957ab4..990d04aa96b 100644 --- a/src/libs/tracing/qml/TimelineLabels.qml +++ b/src/libs/tracing/qml/TimelineLabels.qml @@ -38,7 +38,7 @@ Flickable { // As we cannot retrieve items by visible index we keep an array of row counts here, // for the time marks to draw the row backgrounds in the right colors. - property var rowCounts: new Array(modelProxy.models.length) + property var rowCounts: new Array(categories.modelProxy.models.length) function updateRowCount(visualIndex, rowCount) { if (rowCounts[visualIndex] !== rowCount) { @@ -48,7 +48,7 @@ Flickable { } } - model: modelProxy.models + model: categories.modelProxy.models delegate: Loader { id: loader asynchronous: y < categories.contentY + categories.height && @@ -73,7 +73,7 @@ Flickable { CategoryLabel { id: label model: modelData - notesModel: modelProxy.notes + notesModel: categories.modelProxy.notes visualIndex: loader.visualIndex dragging: categories.dragging reverseSelect: categories.reverseSelect @@ -114,7 +114,7 @@ Flickable { TimeMarks { id: timeMarks model: modelData - mockup: modelProxy.height === 0 + mockup: categories.modelProxy.height === 0 anchors.right: parent.right anchors.left: label.right anchors.top: parent.top diff --git a/src/libs/tracing/qml/TimelineRulers.qml b/src/libs/tracing/qml/TimelineRulers.qml index 0cc676d0562..65e04c5281e 100644 --- a/src/libs/tracing/qml/TimelineRulers.qml +++ b/src/libs/tracing/qml/TimelineRulers.qml @@ -20,7 +20,7 @@ Item { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - height: scaleHeight + height: rulersParent.scaleHeight onClicked: (mouse) => { rulersModel.append({ @@ -36,7 +36,8 @@ Item { if (index >= 0) { rulersModel.setProperty( index, "timestamp", - (x + contentX) * viewTimePerPixel + windowStart); + (x + rulersParent.contentX) * rulersParent.viewTimePerPixel + + rulersParent.windowStart); } } } @@ -46,14 +47,15 @@ Item { Item { id: ruler - x: (timestamp - windowStart) / viewTimePerPixel - 1 - contentX + x: (timestamp - rulersParent.windowStart) / rulersParent.viewTimePerPixel + - 1 - rulersParent.contentX y: 0 width: 2 height: rulersParent.height Rectangle { id: arrow - height: scaleHeight - width: scaleHeight + height: rulersParent.scaleHeight + width: rulersParent.scaleHeight rotation: 45 anchors.verticalCenter: parent.top anchors.horizontalCenter: parent.horizontalCenter @@ -86,7 +88,7 @@ Item { Rectangle { anchors.top: arrow.bottom anchors.horizontalCenter: ruler.horizontalCenter - width: scaleHeight / 4 + width: rulersParent.scaleHeight / 4 height: width color: Theme.color(Theme.Timeline_PanelBackgroundColor) diff --git a/src/libs/tracing/qml/TimelineText.qml b/src/libs/tracing/qml/TimelineText.qml index 32b53fa10dd..55e11e5a963 100644 --- a/src/libs/tracing/qml/TimelineText.qml +++ b/src/libs/tracing/qml/TimelineText.qml @@ -11,4 +11,3 @@ Text { renderType: Text.NativeRendering color: Theme.color(Theme.Timeline_TextColor) } - diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 1d7099bc3a9..a4135401651 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -28,6 +28,7 @@ add_qtc_library(Utils delegates.cpp delegates.h detailsbutton.cpp detailsbutton.h detailswidget.cpp detailswidget.h + devicefileaccess.cpp devicefileaccess.h deviceshell.cpp deviceshell.h differ.cpp differ.h displayname.cpp displayname.h diff --git a/src/libs/utils/commandline.cpp b/src/libs/utils/commandline.cpp index 725fbeea24b..77e33fc83bc 100644 --- a/src/libs/utils/commandline.cpp +++ b/src/libs/utils/commandline.cpp @@ -1412,6 +1412,12 @@ CommandLine::CommandLine(const FilePath &exe, const QStringList &args) addArgs(args); } +CommandLine::CommandLine(const FilePath &exe, const QStringList &args, OsType osType) + : m_executable(exe) +{ + addArgs(args, osType); +} + CommandLine::CommandLine(const FilePath &exe, const QString &args, RawType) : m_executable(exe) { @@ -1438,12 +1444,23 @@ void CommandLine::addArg(const QString &arg) ProcessArgs::addArg(&m_arguments, arg, m_executable.osType()); } +void CommandLine::addArg(const QString &arg, OsType osType) +{ + ProcessArgs::addArg(&m_arguments, arg, osType); +} + void CommandLine::addArgs(const QStringList &inArgs) { for (const QString &arg : inArgs) addArg(arg); } +void CommandLine::addArgs(const QStringList &inArgs, OsType osType) +{ + for (const QString &arg : inArgs) + addArg(arg, osType); +} + // Adds cmd's executable and arguments one by one to this commandline. // Useful for 'sudo', 'nice', etc void CommandLine::addCommandLineAsArgs(const CommandLine &cmd) diff --git a/src/libs/utils/commandline.h b/src/libs/utils/commandline.h index 99558f5d147..67e08b7fe4d 100644 --- a/src/libs/utils/commandline.h +++ b/src/libs/utils/commandline.h @@ -115,12 +115,15 @@ public: CommandLine(); explicit CommandLine(const FilePath &executable); CommandLine(const FilePath &exe, const QStringList &args); + CommandLine(const FilePath &exe, const QStringList &args, OsType osType); CommandLine(const FilePath &exe, const QString &unparsedArgs, RawType); static CommandLine fromUserInput(const QString &cmdline, MacroExpander *expander = nullptr); void addArg(const QString &arg); + void addArg(const QString &arg, OsType osType); void addArgs(const QStringList &inArgs); + void addArgs(const QStringList &inArgs, OsType osType); void addArgs(const QString &inArgs, RawType); void prependArgs(const QStringList &inArgs); diff --git a/src/libs/utils/detailsbutton.cpp b/src/libs/utils/detailsbutton.cpp index 8c20ea62f68..d5ab48238f8 100644 --- a/src/libs/utils/detailsbutton.cpp +++ b/src/libs/utils/detailsbutton.cpp @@ -7,9 +7,10 @@ #include #include -#include +#include #include #include +#include #include #include @@ -69,6 +70,8 @@ DetailsButton::DetailsButton(QWidget *parent) { setText(tr("Details")); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + if (HostOsInfo::isMacHost()) + setFont(QGuiApplication::font()); } QSize DetailsButton::sizeHint() const diff --git a/src/libs/utils/detailsbutton.h b/src/libs/utils/detailsbutton.h index 7a1ac96b04d..8895418f805 100644 --- a/src/libs/utils/detailsbutton.h +++ b/src/libs/utils/detailsbutton.h @@ -5,6 +5,7 @@ #include "utils_global.h" +#include #include QT_BEGIN_NAMESPACE @@ -44,6 +45,7 @@ public: class QTCREATOR_UTILS_EXPORT DetailsButton : public ExpandButton { + Q_DECLARE_TR_FUNCTIONS(Utils::DetailsButton); public: DetailsButton(QWidget *parent = nullptr); QSize sizeHint() const override; diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp new file mode 100644 index 00000000000..6123cd6751c --- /dev/null +++ b/src/libs/utils/devicefileaccess.cpp @@ -0,0 +1,975 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "devicefileaccess.h" + +#include "algorithm.h" +#include "qtcassert.h" +#include "hostosinfo.h" +#include "commandline.h" + +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#ifdef QTCREATOR_PCH_H +#define CALLBACK WINAPI +#endif +#include +#include +#else +#include +#endif + +namespace Utils { + +// DeviceFileAccess + +DeviceFileAccess::~DeviceFileAccess() = default; + +QString DeviceFileAccess::mapToDevicePath(const FilePath &filePath) const +{ + return filePath.path(); +} + +bool DeviceFileAccess::isExecutableFile(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return false; +} + +bool DeviceFileAccess::isReadableFile(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return false; +} + +bool DeviceFileAccess::isWritableFile(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return false; +} + +bool DeviceFileAccess::isReadableDirectory(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return false; +} + +bool DeviceFileAccess::isWritableDirectory(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return false; +} + +bool DeviceFileAccess::isFile(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return false; +} + +bool DeviceFileAccess::isDirectory(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return false; +} + +bool DeviceFileAccess::isSymLink(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return false; +} + +bool DeviceFileAccess::ensureWritableDirectory(const FilePath &filePath) const +{ + if (isWritableDirectory(filePath)) + return true; + return createDirectory(filePath); +} + +bool DeviceFileAccess::ensureExistingFile(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return false; +} + +bool DeviceFileAccess::createDirectory(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return false; +} + +bool DeviceFileAccess::exists(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return false; +} + +bool DeviceFileAccess::removeFile(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return false; +} + +bool DeviceFileAccess::removeRecursively(const FilePath &filePath, QString *error) const +{ + Q_UNUSED(filePath) + Q_UNUSED(error) + QTC_CHECK(false); + return false; +} + +bool DeviceFileAccess::copyFile(const FilePath &filePath, const FilePath &target) const +{ + Q_UNUSED(filePath) + Q_UNUSED(target) + QTC_CHECK(false); + return false; +} + +bool DeviceFileAccess::renameFile(const FilePath &filePath, const FilePath &target) const +{ + Q_UNUSED(filePath) + Q_UNUSED(target) + QTC_CHECK(false); + return false; +} + +OsType DeviceFileAccess::osType(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + return OsTypeOther; +} + +FilePath DeviceFileAccess::symLinkTarget(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return {}; +} + +void DeviceFileAccess::iterateDirectory( + const FilePath &filePath, + const FilePath::IterateDirCallback &callBack, + const FileFilter &filter) const +{ + Q_UNUSED(filePath) + Q_UNUSED(callBack) + Q_UNUSED(filter) + QTC_CHECK(false); +} + +std::optional DeviceFileAccess::fileContents( + const FilePath &filePath, + qint64 limit, + qint64 offset) const +{ + Q_UNUSED(filePath) + Q_UNUSED(limit) + Q_UNUSED(offset) + QTC_CHECK(false); + return {}; +} + +bool DeviceFileAccess::writeFileContents( + const FilePath &filePath, + const QByteArray &data, + qint64 offset) const +{ + Q_UNUSED(filePath) + Q_UNUSED(data) + Q_UNUSED(offset) + QTC_CHECK(false); + return false; +} + +FilePathInfo DeviceFileAccess::filePathInfo(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return {}; +} + +QDateTime DeviceFileAccess::lastModified(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return {}; +} + +QFile::Permissions DeviceFileAccess::permissions(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return {}; +} + +bool DeviceFileAccess::setPermissions(const FilePath &filePath, QFile::Permissions) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return false; +} + +qint64 DeviceFileAccess::fileSize(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return false; +} + +qint64 DeviceFileAccess::bytesAvailable(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + QTC_CHECK(false); + return -1; +} + +QByteArray DeviceFileAccess::fileId(const FilePath &filePath) const +{ + Q_UNUSED(filePath); + QTC_CHECK(false); + return {}; +} + +void DeviceFileAccess::asyncFileContents( + const FilePath &filePath, + const Continuation> &cont, + qint64 limit, + qint64 offset) const +{ + cont(fileContents(filePath, limit, offset)); +} + +void DeviceFileAccess::asyncWriteFileContents( + const FilePath &filePath, + const Continuation &cont, + const QByteArray &data, + qint64 offset) const +{ + cont(writeFileContents(filePath, data, offset)); +} + +void DeviceFileAccess::asyncCopyFile( + const FilePath &filePath, + const Continuation &cont, + const FilePath &target) const +{ + cont(copyFile(filePath, target)); +} + + +// DesktopDeviceFileAccess + +DesktopDeviceFileAccess::~DesktopDeviceFileAccess() = default; + +DesktopDeviceFileAccess *DesktopDeviceFileAccess::instance() +{ + static DesktopDeviceFileAccess theInstance; + return &theInstance; +} + +bool DesktopDeviceFileAccess::isExecutableFile(const FilePath &filePath) const +{ + const QFileInfo fi(filePath.path()); + return fi.isExecutable() && !fi.isDir(); +} + +bool DesktopDeviceFileAccess::isReadableFile(const FilePath &filePath) const +{ + const QFileInfo fi(filePath.path()); + return fi.exists() && fi.isFile() && fi.isReadable(); +} + +bool DesktopDeviceFileAccess::isWritableFile(const FilePath &filePath) const +{ + const QFileInfo fi(filePath.path()); + return fi.exists() && fi.isFile() && fi.isWritable(); +} + +bool DesktopDeviceFileAccess::isReadableDirectory(const FilePath &filePath) const +{ + const QFileInfo fi(filePath.path()); + return fi.exists() && fi.isDir() && fi.isReadable(); +} + +bool DesktopDeviceFileAccess::isWritableDirectory(const FilePath &filePath) const +{ + const QFileInfo fi(filePath.path()); + return fi.exists() && fi.isDir() && fi.isWritable(); +} + +bool DesktopDeviceFileAccess::isFile(const FilePath &filePath) const +{ + const QFileInfo fi(filePath.path()); + return fi.isFile(); +} + +bool DesktopDeviceFileAccess::isDirectory(const FilePath &filePath) const +{ + const QFileInfo fi(filePath.path()); + return fi.isDir(); +} + +bool DesktopDeviceFileAccess::isSymLink(const FilePath &filePath) const +{ + const QFileInfo fi(filePath.path()); + return fi.isSymLink(); +} + +bool DesktopDeviceFileAccess::ensureWritableDirectory(const FilePath &filePath) const +{ + const QFileInfo fi(filePath.path()); + if (fi.isDir() && fi.isWritable()) + return true; + return QDir().mkpath(filePath.path()); +} + +bool DesktopDeviceFileAccess::ensureExistingFile(const FilePath &filePath) const +{ + QFile f(filePath.path()); + if (f.exists()) + return true; + f.open(QFile::WriteOnly); + f.close(); + return f.exists(); +} + +bool DesktopDeviceFileAccess::createDirectory(const FilePath &filePath) const +{ + QDir dir(filePath.path()); + return dir.mkpath(dir.absolutePath()); +} + +bool DesktopDeviceFileAccess::exists(const FilePath &filePath) const +{ + return !filePath.isEmpty() && QFileInfo::exists(filePath.path()); +} + +bool DesktopDeviceFileAccess::removeFile(const FilePath &filePath) const +{ + return QFile::remove(filePath.path()); +} + +bool DesktopDeviceFileAccess::removeRecursively(const FilePath &filePath, QString *error) const +{ + QTC_ASSERT(!filePath.needsDevice(), return false); + QFileInfo fileInfo = filePath.toFileInfo(); + if (!fileInfo.exists() && !fileInfo.isSymLink()) + return true; + + QFile::setPermissions(fileInfo.absoluteFilePath(), fileInfo.permissions() | QFile::WriteUser); + + if (fileInfo.isDir()) { + QDir dir(fileInfo.absoluteFilePath()); + dir.setPath(dir.canonicalPath()); + if (dir.isRoot()) { + if (error) { + *error = QCoreApplication::translate("Utils::FileUtils", + "Refusing to remove root directory."); + } + return false; + } + if (dir.path() == QDir::home().canonicalPath()) { + if (error) { + *error = QCoreApplication::translate("Utils::FileUtils", + "Refusing to remove your home directory."); + } + return false; + } + + const QStringList fileNames = dir.entryList( + QDir::Files | QDir::Hidden | QDir::System | QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &fileName : fileNames) { + if (!removeRecursively(filePath / fileName, error)) + return false; + } + if (!QDir::root().rmdir(dir.path())) { + if (error) { + *error = QCoreApplication::translate("Utils::FileUtils", "Failed to remove directory \"%1\".") + .arg(filePath.toUserOutput()); + } + return false; + } + } else { + if (!QFile::remove(filePath.toString())) { + if (error) { + *error = QCoreApplication::translate("Utils::FileUtils", "Failed to remove file \"%1\".") + .arg(filePath.toUserOutput()); + } + return false; + } + } + return true; +} + +bool DesktopDeviceFileAccess::copyFile(const FilePath &filePath, const FilePath &target) const +{ + return QFile::copy(filePath.path(), target.path()); +} + +bool DesktopDeviceFileAccess::renameFile(const FilePath &filePath, const FilePath &target) const +{ + return QFile::rename(filePath.path(), target.path()); +} + +FilePathInfo DesktopDeviceFileAccess::filePathInfo(const FilePath &filePath) const +{ + FilePathInfo result; + + QFileInfo fi(filePath.path()); + result.fileSize = fi.size(); + result.lastModified = fi.lastModified(); + result.fileFlags = (FilePathInfo::FileFlag) int(fi.permissions()); + + if (fi.isDir()) + result.fileFlags |= FilePathInfo::DirectoryType; + if (fi.isFile()) + result.fileFlags |= FilePathInfo::FileType; + if (fi.exists()) + result.fileFlags |= FilePathInfo::ExistsFlag; + if (fi.isSymbolicLink()) + result.fileFlags |= FilePathInfo::LinkType; + if (fi.isBundle()) + result.fileFlags |= FilePathInfo::BundleType; + if (fi.isHidden()) + result.fileFlags |= FilePathInfo::HiddenFlag; + if (fi.isRoot()) + result.fileFlags |= FilePathInfo::RootFlag; + + return result; +} + +FilePath DesktopDeviceFileAccess::symLinkTarget(const FilePath &filePath) const +{ + const QFileInfo info(filePath.path()); + if (!info.isSymLink()) + return {}; + return FilePath::fromString(info.symLinkTarget()); +} + +void DesktopDeviceFileAccess::iterateDirectory( + const FilePath &filePath, + const FilePath::IterateDirCallback &callBack, + const FileFilter &filter) const +{ + QDirIterator it(filePath.path(), filter.nameFilters, filter.fileFilters, filter.iteratorFlags); + while (it.hasNext()) { + const FilePath path = FilePath::fromString(it.next()); + bool res = false; + if (callBack.index() == 0) + res = std::get<0>(callBack)(path); + else + res = std::get<1>(callBack)(path, path.filePathInfo()); + if (!res) + return; + } +} + +std::optional DesktopDeviceFileAccess::fileContents( + const FilePath &filePath, + qint64 limit, + qint64 offset) const +{ + const QString path = filePath.path(); + QFile f(path); + if (!f.exists()) + return {}; + + if (!f.open(QFile::ReadOnly)) + return {}; + + if (offset != 0) + f.seek(offset); + + if (limit != -1) + return f.read(limit); + + return f.readAll(); +} + +bool DesktopDeviceFileAccess::writeFileContents( + const FilePath &filePath, + const QByteArray &data, + qint64 offset) const +{ + QFile file(filePath.path()); + QTC_ASSERT(file.open(QFile::WriteOnly | QFile::Truncate), return false); + if (offset != 0) + file.seek(offset); + qint64 res = file.write(data); + return res == data.size(); +} + +QDateTime DesktopDeviceFileAccess::lastModified(const FilePath &filePath) const +{ + return QFileInfo(filePath.path()).lastModified(); +} + +QFile::Permissions DesktopDeviceFileAccess::permissions(const FilePath &filePath) const +{ + return QFileInfo(filePath.path()).permissions(); +} + +bool DesktopDeviceFileAccess::setPermissions(const FilePath &filePath, + QFile::Permissions permissions) const +{ + return QFile(filePath.path()).setPermissions(permissions); +} + +qint64 DesktopDeviceFileAccess::fileSize(const FilePath &filePath) const +{ + return QFileInfo(filePath.path()).size(); +} + +qint64 DesktopDeviceFileAccess::bytesAvailable(const FilePath &filePath) const +{ + return QStorageInfo(filePath.path()).bytesAvailable(); +} + +// Copied from qfilesystemengine_win.cpp +#ifdef Q_OS_WIN + +// File ID for Windows up to version 7. +static inline QByteArray fileIdWin7(HANDLE handle) +{ + BY_HANDLE_FILE_INFORMATION info; + if (GetFileInformationByHandle(handle, &info)) { + char buffer[sizeof "01234567:0123456701234567\0"]; + qsnprintf(buffer, sizeof(buffer), "%lx:%08lx%08lx", + info.dwVolumeSerialNumber, + info.nFileIndexHigh, + info.nFileIndexLow); + return QByteArray(buffer); + } + return QByteArray(); +} + +// File ID for Windows starting from version 8. +static QByteArray fileIdWin8(HANDLE handle) +{ + QByteArray result; + FILE_ID_INFO infoEx; + if (GetFileInformationByHandleEx(handle, + static_cast(18), // FileIdInfo in Windows 8 + &infoEx, sizeof(FILE_ID_INFO))) { + result = QByteArray::number(infoEx.VolumeSerialNumber, 16); + result += ':'; + // Note: MinGW-64's definition of FILE_ID_128 differs from the MSVC one. + result += QByteArray(reinterpret_cast(&infoEx.FileId), int(sizeof(infoEx.FileId))).toHex(); + } + return result; +} + +static QByteArray fileIdWin(HANDLE fHandle) +{ + return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8 ? + fileIdWin8(HANDLE(fHandle)) : fileIdWin7(HANDLE(fHandle)); +} +#endif + +QByteArray DesktopDeviceFileAccess::fileId(const FilePath &filePath) const +{ + QByteArray result; + +#ifdef Q_OS_WIN + const HANDLE handle = + CreateFile((wchar_t*)filePath.toUserOutput().utf16(), 0, + FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (handle != INVALID_HANDLE_VALUE) { + result = fileIdWin(handle); + CloseHandle(handle); + } +#else // Copied from qfilesystemengine_unix.cpp + if (Q_UNLIKELY(filePath.isEmpty())) + return result; + + QT_STATBUF statResult; + if (QT_STAT(filePath.toString().toLocal8Bit().constData(), &statResult)) + return result; + result = QByteArray::number(quint64(statResult.st_dev), 16); + result += ':'; + result += QByteArray::number(quint64(statResult.st_ino)); +#endif + return result; +} + +OsType DesktopDeviceFileAccess::osType(const FilePath &filePath) const +{ + Q_UNUSED(filePath); + return HostOsInfo::hostOs(); +} + + +// UnixDeviceAccess + +UnixDeviceFileAccess::~UnixDeviceFileAccess() = default; + +bool UnixDeviceFileAccess::runInShellSuccess(const CommandLine &cmdLine, + const QByteArray &stdInData) const +{ + return runInShell(cmdLine, stdInData).exitCode == 0; +} + +bool UnixDeviceFileAccess::isExecutableFile(const FilePath &filePath) const +{ + const QString path = filePath.path(); + return runInShellSuccess({"test", {"-x", path}, OsType::OsTypeLinux}); +} + +bool UnixDeviceFileAccess::isReadableFile(const FilePath &filePath) const +{ + const QString path = filePath.path(); + return runInShellSuccess({"test", {"-r", path, "-a", "-f", path}, OsType::OsTypeLinux}); +} + +bool UnixDeviceFileAccess::isWritableFile(const FilePath &filePath) const +{ + const QString path = filePath.path(); + return runInShellSuccess({"test", {"-w", path, "-a", "-f", path}, OsType::OsTypeLinux}); +} + +bool UnixDeviceFileAccess::isReadableDirectory(const FilePath &filePath) const +{ + const QString path = filePath.path(); + return runInShellSuccess({"test", {"-r", path, "-a", "-d", path}, OsType::OsTypeLinux}); +} + +bool UnixDeviceFileAccess::isWritableDirectory(const FilePath &filePath) const +{ + const QString path = filePath.path(); + return runInShellSuccess({"test", {"-w", path, "-a", "-d", path}, OsType::OsTypeLinux}); +} + +bool UnixDeviceFileAccess::isFile(const FilePath &filePath) const +{ + const QString path = filePath.path(); + return runInShellSuccess({"test", {"-f", path}, OsType::OsTypeLinux}); +} + +bool UnixDeviceFileAccess::isDirectory(const FilePath &filePath) const +{ + const QString path = filePath.path(); + return runInShellSuccess({"test", {"-d", path}, OsType::OsTypeLinux}); +} + +bool UnixDeviceFileAccess::isSymLink(const FilePath &filePath) const +{ + const QString path = filePath.path(); + return runInShellSuccess({"test", {"-h", path}, OsType::OsTypeLinux}); +} + +bool UnixDeviceFileAccess::ensureExistingFile(const FilePath &filePath) const +{ + const QString path = filePath.path(); + return runInShellSuccess({"touch", {path}, OsType::OsTypeLinux}); +} + +bool UnixDeviceFileAccess::createDirectory(const FilePath &filePath) const +{ + const QString path = filePath.path(); + return runInShellSuccess({"mkdir", {"-p", path}, OsType::OsTypeLinux}); +} + +bool UnixDeviceFileAccess::exists(const FilePath &filePath) const +{ + const QString path = filePath.path(); + return runInShellSuccess({"test", {"-e", path}, OsType::OsTypeLinux}); +} + +bool UnixDeviceFileAccess::removeFile(const FilePath &filePath) const +{ + return runInShellSuccess({"rm", {filePath.path()}, OsType::OsTypeLinux}); +} + +bool UnixDeviceFileAccess::removeRecursively(const FilePath &filePath, QString *error) const +{ + QTC_ASSERT(filePath.path().startsWith('/'), return false); + + const QString path = filePath.cleanPath().path(); + // We are expecting this only to be called in a context of build directories or similar. + // Chicken out in some cases that _might_ be user code errors. + QTC_ASSERT(path.startsWith('/'), return false); + int levelsNeeded = path.startsWith("/home/") ? 4 : 3; + if (path.startsWith("/tmp/")) + levelsNeeded = 2; + QTC_ASSERT(path.count('/') >= levelsNeeded, return false); + + RunResult result = runInShell({"rm", {"-rf", "--", path}, OsType::OsTypeLinux}); + if (error) + *error = QString::fromUtf8(result.stdErr); + return result.exitCode == 0; +} + +bool UnixDeviceFileAccess::copyFile(const FilePath &filePath, const FilePath &target) const +{ + return runInShellSuccess({"cp", {filePath.path(), target.path()}, OsType::OsTypeLinux}); +} + +bool UnixDeviceFileAccess::renameFile(const FilePath &filePath, const FilePath &target) const +{ + return runInShellSuccess({"mv", {filePath.path(), target.path()}, OsType::OsTypeLinux}); +} + +FilePath UnixDeviceFileAccess::symLinkTarget(const FilePath &filePath) const +{ + const RunResult result = runInShell({"readlink", {"-n", "-e", filePath.path()}, OsType::OsTypeLinux}); + const QString out = QString::fromUtf8(result.stdOut); + return out.isEmpty() ? FilePath() : filePath.withNewPath(out); +} + +std::optional UnixDeviceFileAccess::fileContents( + const FilePath &filePath, + qint64 limit, + qint64 offset) const +{ + QStringList args = {"if=" + filePath.path(), "status=none"}; + if (limit > 0 || offset > 0) { + const qint64 gcd = std::gcd(limit, offset); + args += QString("bs=%1").arg(gcd); + args += QString("count=%1").arg(limit / gcd); + args += QString("seek=%1").arg(offset / gcd); + } + + const RunResult r = runInShell({"dd", args, OsType::OsTypeLinux}); + + if (r.exitCode != 0) + return {}; + + return r.stdOut; +} + +bool UnixDeviceFileAccess::writeFileContents( + const FilePath &filePath, + const QByteArray &data, + qint64 offset) const +{ + QStringList args = {"of=" + filePath.path()}; + if (offset != 0) { + args.append("bs=1"); + args.append(QString("seek=%1").arg(offset)); + } + return runInShellSuccess({"dd", args, OsType::OsTypeLinux}, data); +} + +OsType UnixDeviceFileAccess::osType(const FilePath &filePath) const +{ + Q_UNUSED(filePath) + return OsTypeLinux; +} + +QDateTime UnixDeviceFileAccess::lastModified(const FilePath &filePath) const +{ + const RunResult result = runInShell({"stat", {"-L", "-c", "%Y", filePath.path()}, OsType::OsTypeLinux}); + qint64 secs = result.stdOut.toLongLong(); + const QDateTime dt = QDateTime::fromSecsSinceEpoch(secs, Qt::UTC); + return dt; +} + +QFile::Permissions UnixDeviceFileAccess::permissions(const FilePath &filePath) const +{ + const RunResult result = runInShell({"stat", {"-L", "-c", "%a", filePath.path()}, OsType::OsTypeLinux}); + const uint bits = result.stdOut.toUInt(nullptr, 8); + QFileDevice::Permissions perm = {}; +#define BIT(n, p) if (bits & (1< bool { + if (callBack.index() == 0) + return std::get<0>(callBack)(filePath.withNewPath(entry)); + + const QString fileName = entry.mid(1, entry.lastIndexOf('\"') - 1); + const QString infos = entry.mid(fileName.length() + 3); + + const FilePathInfo fi = FileUtils::filePathInfoFromTriple(infos); + if (!fi.fileFlags) + return true; + + const FilePath fp = filePath.withNewPath(fileName); + return std::get<1>(callBack)(fp, fi); + }; + + // Remove the first line, this can be the directory we are searching in. + // as long as we do not specify "mindepth > 0" + if (entries.front() == filePath.path()) + entries.pop_front(); + + for (const QString &entry : entries) { + if (!toFilePath(entry)) + break; + } + + return true; +} + +void UnixDeviceFileAccess::findUsingLs( + const QString ¤t, + const FileFilter &filter, + QStringList *found) const +{ + const RunResult result = runInShell({"ls", {"-1", "-p", "--", current}, OsType::OsTypeLinux}); + const QStringList entries = QString::fromUtf8(result.stdOut).split('\n', Qt::SkipEmptyParts); + for (QString entry : entries) { + const QChar last = entry.back(); + if (last == '/') { + entry.chop(1); + if (filter.iteratorFlags.testFlag(QDirIterator::Subdirectories)) + findUsingLs(current + '/' + entry, filter, found); + } + found->append(entry); + } +} + +// Used on 'ls' output on unix-like systems. +static void iterateLsOutput(const FilePath &base, + const QStringList &entries, + const FileFilter &filter, + const FilePath::IterateDirCallback &callBack) +{ + const QList nameRegexps = + transform(filter.nameFilters, [](const QString &filter) { + QRegularExpression re; + re.setPattern(QRegularExpression::wildcardToRegularExpression(filter)); + QTC_CHECK(re.isValid()); + return re; + }); + + const auto nameMatches = [&nameRegexps](const QString &fileName) { + for (const QRegularExpression &re : nameRegexps) { + const QRegularExpressionMatch match = re.match(fileName); + if (match.hasMatch()) + return true; + } + return nameRegexps.isEmpty(); + }; + + // FIXME: Handle filters. For now bark on unsupported options. + QTC_CHECK(filter.fileFilters == QDir::NoFilter); + + for (const QString &entry : entries) { + if (!nameMatches(entry)) + continue; + const FilePath current = base.pathAppended(entry); + bool res = false; + if (callBack.index() == 0) + res = std::get<0>(callBack)(current); + else + res = std::get<1>(callBack)(current, current.filePathInfo()); + if (!res) + break; + } +} + +void UnixDeviceFileAccess::iterateDirectory( + const FilePath &filePath, + const FilePath::IterateDirCallback &callBack, + const FileFilter &filter) const +{ + // We try to use 'find' first, because that can filter better directly. + // Unfortunately, it's not installed on all devices by default. + if (m_tryUseFind) { + if (iterateWithFind(filePath, filter, callBack)) + return; + m_tryUseFind = false; // remember the failure for the next time and use the 'ls' fallback below. + } + + // if we do not have find - use ls as fallback + QStringList entries; + findUsingLs(filePath.path(), filter, &entries); + iterateLsOutput(filePath, entries, filter, callBack); +} + +} // Utils diff --git a/src/libs/utils/devicefileaccess.h b/src/libs/utils/devicefileaccess.h new file mode 100644 index 00000000000..4280cfc09af --- /dev/null +++ b/src/libs/utils/devicefileaccess.h @@ -0,0 +1,195 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "utils_global.h" + +#include "fileutils.h" + +namespace Utils { + +// Base class including dummy implementation usable as fallback. +class QTCREATOR_UTILS_EXPORT DeviceFileAccess +{ +public: + virtual ~DeviceFileAccess(); + +protected: + friend class FilePath; + + virtual QString mapToDevicePath(const FilePath &filePath) const; + + virtual bool isExecutableFile(const FilePath &filePath) const; + virtual bool isReadableFile(const FilePath &filePath) const; + virtual bool isWritableFile(const FilePath &filePath) const; + virtual bool isReadableDirectory(const FilePath &filePath) const; + virtual bool isWritableDirectory(const FilePath &filePath) const; + virtual bool isFile(const FilePath &filePath) const; + virtual bool isDirectory(const FilePath &filePath) const; + virtual bool isSymLink(const FilePath &filePath) const; + virtual bool ensureWritableDirectory(const FilePath &filePath) const; + virtual bool ensureExistingFile(const FilePath &filePath) const; + virtual bool createDirectory(const FilePath &filePath) const; + virtual bool exists(const FilePath &filePath) const; + virtual bool removeFile(const FilePath &filePath) const; + virtual bool removeRecursively(const FilePath &filePath, QString *error) const; + virtual bool copyFile(const FilePath &filePath, const FilePath &target) const; + virtual bool renameFile(const FilePath &filePath, const FilePath &target) const; + + virtual OsType osType(const FilePath &filePath) const; + virtual FilePath symLinkTarget(const FilePath &filePath) const; + virtual FilePathInfo filePathInfo(const FilePath &filePath) const; + virtual QDateTime lastModified(const FilePath &filePath) const; + virtual QFile::Permissions permissions(const FilePath &filePath) const; + virtual bool setPermissions(const FilePath &filePath, QFile::Permissions) const; + virtual qint64 fileSize(const FilePath &filePath) const; + virtual qint64 bytesAvailable(const FilePath &filePath) const; + virtual QByteArray fileId(const FilePath &filePath) const; + + virtual void iterateDirectory( + const FilePath &filePath, + const FilePath::IterateDirCallback &callBack, + const FileFilter &filter) const; + + virtual std::optional fileContents( + const FilePath &filePath, + qint64 limit, + qint64 offset) const; + virtual bool writeFileContents( + const FilePath &filePath, + const QByteArray &data, + qint64 offset) const; + + virtual void asyncFileContents( + const FilePath &filePath, + const Continuation> &cont, + qint64 limit, + qint64 offset) const; + + virtual void asyncWriteFileContents( + const FilePath &filePath, + const Continuation &cont, + const QByteArray &data, + qint64 offset) const; + + virtual void asyncCopyFile( + const FilePath &filePath, + const Continuation &cont, + const FilePath &target) const; +}; + +class QTCREATOR_UTILS_EXPORT DesktopDeviceFileAccess : public DeviceFileAccess +{ +public: + ~DesktopDeviceFileAccess() override; + + static DesktopDeviceFileAccess *instance(); + +protected: + bool isExecutableFile(const FilePath &filePath) const override; + bool isReadableFile(const FilePath &filePath) const override; + bool isWritableFile(const FilePath &filePath) const override; + bool isReadableDirectory(const FilePath &filePath) const override; + bool isWritableDirectory(const FilePath &filePath) const override; + bool isFile(const FilePath &filePath) const override; + bool isDirectory(const FilePath &filePath) const override; + bool isSymLink(const FilePath &filePath) const override; + bool ensureWritableDirectory(const FilePath &filePath) const override; + bool ensureExistingFile(const FilePath &filePath) const override; + bool createDirectory(const FilePath &filePath) const override; + bool exists(const FilePath &filePath) const override; + bool removeFile(const FilePath &filePath) const override; + bool removeRecursively(const FilePath &filePath, QString *error) const override; + bool copyFile(const FilePath &filePath, const FilePath &target) const override; + bool renameFile(const FilePath &filePath, const FilePath &target) const override; + + OsType osType(const FilePath &filePath) const override; + FilePath symLinkTarget(const FilePath &filePath) const override; + FilePathInfo filePathInfo(const FilePath &filePath) const override; + QDateTime lastModified(const FilePath &filePath) const override; + QFile::Permissions permissions(const FilePath &filePath) const override; + bool setPermissions(const FilePath &filePath, QFile::Permissions) const override; + qint64 fileSize(const FilePath &filePath) const override; + qint64 bytesAvailable(const FilePath &filePath) const override; + QByteArray fileId(const FilePath &filePath) const override; + + void iterateDirectory( + const FilePath &filePath, + const FilePath::IterateDirCallback &callBack, + const FileFilter &filter) const override; + + std::optional fileContents( + const FilePath &filePath, + qint64 limit, + qint64 offset) const override; + bool writeFileContents( + const FilePath &filePath, + const QByteArray &data, + qint64 offset) const override; +}; + +class QTCREATOR_UTILS_EXPORT UnixDeviceFileAccess : public DeviceFileAccess +{ +public: + ~UnixDeviceFileAccess() override; + +protected: + virtual RunResult runInShell(const CommandLine &cmdLine, + const QByteArray &inputData = {}) const = 0; + bool runInShellSuccess(const CommandLine &cmdLine, const QByteArray &stdInData = {}) const; + + bool isExecutableFile(const FilePath &filePath) const override; + bool isReadableFile(const FilePath &filePath) const override; + bool isWritableFile(const FilePath &filePath) const override; + bool isReadableDirectory(const FilePath &filePath) const override; + bool isWritableDirectory(const FilePath &filePath) const override; + bool isFile(const FilePath &filePath) const override; + bool isDirectory(const FilePath &filePath) const override; + bool isSymLink(const FilePath &filePath) const override; + bool ensureExistingFile(const FilePath &filePath) const override; + bool createDirectory(const FilePath &filePath) const override; + bool exists(const FilePath &filePath) const override; + bool removeFile(const FilePath &filePath) const override; + bool removeRecursively(const FilePath &filePath, QString *error) const override; + bool copyFile(const FilePath &filePath, const FilePath &target) const override; + bool renameFile(const FilePath &filePath, const FilePath &target) const override; + + FilePathInfo filePathInfo(const FilePath &filePath) const override; + OsType osType(const FilePath &filePath) const override; + FilePath symLinkTarget(const FilePath &filePath) const override; + QDateTime lastModified(const FilePath &filePath) const override; + QFile::Permissions permissions(const FilePath &filePath) const override; + bool setPermissions(const FilePath &filePath, QFile::Permissions) const override; + qint64 fileSize(const FilePath &filePath) const override; + qint64 bytesAvailable(const FilePath &filePath) const override; + QByteArray fileId(const FilePath &filePath) const override; + + void iterateDirectory( + const FilePath &filePath, + const FilePath::IterateDirCallback &callBack, + const FileFilter &filter) const override; + + std::optional fileContents( + const FilePath &filePath, + qint64 limit, + qint64 offset) const override; + bool writeFileContents( + const FilePath &filePath, + const QByteArray &data, + qint64 offset) const override; + +private: + bool iterateWithFind( + const FilePath &filePath, + const FileFilter &filter, + const FilePath::IterateDirCallback &callBack) const; + void findUsingLs( + const QString ¤t, + const FileFilter &filter, + QStringList *found) const; + + mutable bool m_tryUseFind = true; +}; + +} // Utils diff --git a/src/libs/utils/deviceshell.cpp b/src/libs/utils/deviceshell.cpp index 5e43d020219..4cd8642ec0f 100644 --- a/src/libs/utils/deviceshell.cpp +++ b/src/libs/utils/deviceshell.cpp @@ -14,142 +14,24 @@ Q_LOGGING_CATEGORY(deviceShellLog, "qtc.utils.deviceshell", QtWarningMsg) namespace Utils { -namespace { - /*! * The multiplex script waits for input via stdin. * - * To start a command, a message is send with the format " "" \n" + * To start a command, a message is send with + * the format " "" \n" * To stop the script, simply send "exit\n" via stdin * - * Once a message is received, two new streams are created that the new process redirects its output to ( $stdoutraw and $stderrraw ). + * Once a message is received, two new streams are created that the new process redirects + * its output to ( $stdoutraw and $stderrraw ). * * These streams are piped through base64 into the two streams stdoutenc and stderrenc. * - * Two subshells read from these base64 encoded streams, and prepend the command-id, as well as either "O:" or "E:" depending on whether its the stdout or stderr stream. + * Two subshells read from these base64 encoded streams, and prepend the command-id, + * as well as either "O:" or "E:" depending on whether its the stdout or stderr stream. * * Once the process exits its exit code is send to stdout with the command-id and the type "R". * */ -const QLatin1String r_execScript = QLatin1String(R"SCRIPT( -#!/bin/sh -FINAL_OUT=$(mktemp -u) -mkfifo "$FINAL_OUT" - -finalOutput() { - local fileInputBuffer - while read fileInputBuffer - do - if test -f "$fileInputBuffer.err"; then - cat $fileInputBuffer.err - fi - cat $fileInputBuffer - rm -f $fileInputBuffer.err $fileInputBuffer - done -} - -finalOutput < $FINAL_OUT & - -readAndMark() { - local buffer - while read buffer - do - printf '%s:%s:%s\n' "$1" "$2" "$buffer" - done -} - -base64decode() -{ - base64 -d 2>/dev/null -} - -base64encode() -{ - base64 2>/dev/null -} - -executeAndMark() -{ - PID="$1" - INDATA="$2" - shift - shift - CMD="$@" - - # LogFile - TMPFILE=$(mktemp) - - # Output Streams - stdoutenc=$(mktemp -u) - stderrenc=$(mktemp -u) - mkfifo "$stdoutenc" "$stderrenc" - - # app output streams - stdoutraw=$(mktemp -u) - stderrraw=$(mktemp -u) - mkfifo "$stdoutraw" "$stderrraw" - - # Cleanup - trap 'rm -f "$stdoutenc" "$stderrenc" "$stdoutraw" "$stderrraw"' EXIT - - # Pipe all app output through base64, and then into the output streams - cat $stdoutraw | base64encode > "$stdoutenc" & - cat $stderrraw | base64encode > "$stderrenc" & - - # Mark the app's output streams - readAndMark $PID 'O' < "$stdoutenc" >> $TMPFILE & - readAndMark $PID 'E' < "$stderrenc" >> $TMPFILE.err & - - # Start the app ... - if [ -z "$INDATA" ] - then - eval $CMD 1> "$stdoutraw" 2> "$stderrraw" - else - echo $INDATA | base64decode | eval "$CMD" 1> "$stdoutraw" 2> "$stderrraw" - fi - - exitcode=$(echo $? | base64encode) - wait - echo "$PID:R:$exitcode" >> $TMPFILE - echo $TMPFILE -} - -execute() -{ - PID="$1" - - if [ "$#" -lt "3" ]; then - TMPFILE=$(mktemp) - echo "$PID:R:MjU1Cg==" > $TMPFILE - echo $TMPFILE - else - INDATA=$(eval echo "$2") - shift - shift - CMD=$@ - executeAndMark $PID "$INDATA" "$CMD" - fi -} - -cleanup() -{ - kill -- -$$ - exit 1 -} - -trap cleanup 1 2 3 6 - -echo SCRIPT_INSTALLED >&2 - -(while read -r id inData cmd; do - if [ "$id" = "exit" ]; then - exit - fi - execute $id $inData $cmd || echo "$id:R:255" & -done) > $FINAL_OUT -)SCRIPT"); - -} // namespace DeviceShell::DeviceShell(bool forceFailScriptInstallation) : m_forceFailScriptInstallation(forceFailScriptInstallation) @@ -380,8 +262,11 @@ bool DeviceShell::installShellScript() return false; } - const static QByteArray shellScriptBase64 - = QByteArray(r_execScript.begin(), r_execScript.size()).toBase64(); + const static QByteArray shellScriptBase64 = FilePath(":/utils/scripts/deviceshell.sh") + .fileContents() + .value() + .replace("\r\n", "\n") + .toBase64(); const QByteArray scriptCmd = "(scriptData=$(echo " + shellScriptBase64 + " | base64 -d 2>/dev/null ) && /bin/sh -c \"$scriptData\") || " "echo ERROR_INSTALL_SCRIPT >&2\n"; diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index 57b3b197244..62dc52b2605 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -175,13 +175,7 @@ bool Environment::isSameExecutable(const QString &exe1, const QString &exe2) con for (const QString &i2 : exe2List) { const FilePath f1 = FilePath::fromString(i1); const FilePath f2 = FilePath::fromString(i2); - if (f1 == f2) - return true; - if (f1.needsDevice() != f2.needsDevice() || f1.scheme() != f2.scheme()) - return false; - if (f1.resolveSymlinks() == f2.resolveSymlinks()) - return true; - if (FileUtils::fileId(f1) == FileUtils::fileId(f2)) + if (f1.isSameFile(f2)) return true; } } diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp index 73b414cbac5..d8123608cd8 100644 --- a/src/libs/utils/filepath.cpp +++ b/src/libs/utils/filepath.cpp @@ -4,6 +4,7 @@ #include "filepath.h" #include "algorithm.h" +#include "devicefileaccess.h" #include "environment.h" #include "fileutils.h" #include "hostosinfo.h" @@ -15,7 +16,6 @@ #include #include #include -#include #include #include @@ -30,7 +30,6 @@ namespace Utils { static DeviceFileHooks s_deviceHooks; -static bool removeRecursivelyLocal(const FilePath &filePath, QString *error); inline bool isWindowsDriveLetter(QChar ch); @@ -147,8 +146,7 @@ FilePath FilePath::fromFileInfo(const QFileInfo &info) /// \returns a QFileInfo QFileInfo FilePath::toFileInfo() const { - QTC_ASSERT(!needsDevice(), return QFileInfo()); - return QFileInfo(cleanPath().path()); + return QFileInfo(toFSPathString()); } FilePath FilePath::fromUrl(const QUrl &url) @@ -359,136 +357,73 @@ void FilePath::setParts(const QStringView scheme, const QStringView host, const /// FilePath exists. bool FilePath::exists() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.exists, return false); - return s_deviceHooks.exists(*this); - } - return !isEmpty() && QFileInfo::exists(path()); + return fileAccess()->exists(*this); } /// \returns a bool indicating whether a path is writable. bool FilePath::isWritableDir() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isWritableDir, return false); - return s_deviceHooks.isWritableDir(*this); - } - const QFileInfo fi{path()}; - return exists() && fi.isDir() && fi.isWritable(); + return fileAccess()->isWritableDirectory(*this); } bool FilePath::isWritableFile() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isWritableFile, return false); - return s_deviceHooks.isWritableFile(*this); - } - const QFileInfo fi{path()}; - return fi.isWritable() && !fi.isDir(); + return fileAccess()->isWritableFile(*this); } bool FilePath::ensureWritableDir() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.ensureWritableDir, return false); - return s_deviceHooks.ensureWritableDir(*this); - } - const QFileInfo fi{path()}; - if (fi.isDir() && fi.isWritable()) - return true; - return QDir().mkpath(path()); + return fileAccess()->ensureWritableDirectory(*this); } bool FilePath::ensureExistingFile() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.ensureExistingFile, return false); - return s_deviceHooks.ensureExistingFile(*this); - } - QFile f(path()); - if (f.exists()) - return true; - f.open(QFile::WriteOnly); - f.close(); - return f.exists(); + return fileAccess()->ensureExistingFile(*this); } bool FilePath::isExecutableFile() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isExecutableFile, return false); - return s_deviceHooks.isExecutableFile(*this); - } - const QFileInfo fi{path()}; - return fi.isExecutable() && !fi.isDir(); + return fileAccess()->isExecutableFile(*this); } bool FilePath::isReadableFile() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isReadableFile, return false); - return s_deviceHooks.isReadableFile(*this); - } - const QFileInfo fi{path()}; - return fi.isReadable() && !fi.isDir(); + return fileAccess()->isReadableFile(*this); } bool FilePath::isReadableDir() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isReadableDir, return false); - return s_deviceHooks.isReadableDir(*this); - } - const QFileInfo fi{path()}; - return fi.isReadable() && fi.isDir(); + return fileAccess()->isReadableDirectory(*this); } bool FilePath::isFile() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isFile, return false); - return s_deviceHooks.isFile(*this); - } - const QFileInfo fi{path()}; - return fi.isFile(); + return fileAccess()->isFile(*this); } bool FilePath::isDir() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.isDir, return false); - return s_deviceHooks.isDir(*this); - } - const QFileInfo fi{path()}; - return fi.isDir(); + return fileAccess()->isDirectory(*this); +} + +bool FilePath::isSymLink() const +{ + return fileAccess()->isSymLink(*this); } bool FilePath::createDir() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.createDir, return false); - return s_deviceHooks.createDir(*this); - } - QDir dir(path()); - return dir.mkpath(dir.absolutePath()); + return fileAccess()->createDirectory(*this); } FilePaths FilePath::dirEntries(const FileFilter &filter, QDir::SortFlags sort) const { FilePaths result; - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.iterateDirectory, return {}); - const auto callBack = [&result](const FilePath &path) { result.append(path); return true; }; - s_deviceHooks.iterateDirectory(*this, callBack, filter); - } else { - QDirIterator dit(path(), filter.nameFilters, filter.fileFilters, filter.iteratorFlags); - while (dit.hasNext()) - result.append(FilePath::fromString(dit.next())); - } + const auto callBack = [&result](const FilePath &path) { result.append(path); return true; }; + iterateDirectory(callBack, filter); // FIXME: Not all flags supported here. - const QDir::SortFlags sortBy = (sort & QDir::SortByMask); if (sortBy == QDir::Name) { Utils::sort(result); @@ -515,34 +450,7 @@ FilePaths FilePath::dirEntries(QDir::Filters filters) const void FilePath::iterateDirectory(const IterateDirCallback &callBack, const FileFilter &filter) const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.iterateDirectory, return); - s_deviceHooks.iterateDirectory(*this, callBack, filter); - return; - } - - QDirIterator it(path(), filter.nameFilters, filter.fileFilters, filter.iteratorFlags); - while (it.hasNext()) { - if (!callBack(FilePath::fromString(it.next()))) - return; - } -} - -void FilePath::iterateDirectory(const IterateDirWithInfoCallback &callBack, - const FileFilter &filter) const -{ - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.iterateDirectoryWithInfo, return); - s_deviceHooks.iterateDirectoryWithInfo(*this, callBack, filter); - return; - } - - QDirIterator it(path(), filter.nameFilters, filter.fileFilters, filter.iteratorFlags); - while (it.hasNext()) { - const FilePath path = FilePath::fromString(it.next()); - if (!callBack(path, path.filePathInfo())) - return; - } + fileAccess()->iterateDirectory(*this, callBack, filter); } void FilePath::iterateDirectories(const FilePaths &dirs, @@ -555,26 +463,7 @@ void FilePath::iterateDirectories(const FilePaths &dirs, std::optional FilePath::fileContents(qint64 maxSize, qint64 offset) const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.fileContents, return {}); - return s_deviceHooks.fileContents(*this, maxSize, offset); - } - - const QString path = toString(); - QFile f(path); - if (!f.exists()) - return {}; - - if (!f.open(QFile::ReadOnly)) - return {}; - - if (offset != 0) - f.seek(offset); - - if (maxSize != -1) - return f.read(maxSize); - - return f.readAll(); + return fileAccess()->fileContents(*this, maxSize, offset); } bool FilePath::ensureReachable(const FilePath &other) const @@ -588,78 +477,30 @@ bool FilePath::ensureReachable(const FilePath &other) const return false; } - -void FilePath::asyncFileContents(const Continuation &> &cont, - qint64 maxSize, - qint64 offset) const +void FilePath::asyncFileContents( + const Continuation &> &cont, + qint64 maxSize, + qint64 offset) const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.asyncFileContents, return); - s_deviceHooks.asyncFileContents(cont, *this, maxSize, offset); - return; - } - - cont(fileContents(maxSize, offset)); + return fileAccess()->asyncFileContents(*this, cont, maxSize, offset); } bool FilePath::writeFileContents(const QByteArray &data, qint64 offset) const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.writeFileContents, return {}); - return s_deviceHooks.writeFileContents(*this, data, offset); - } - - QFile file(path()); - QTC_ASSERT(file.open(QFile::WriteOnly | QFile::Truncate), return false); - if (offset != 0) - file.seek(offset); - qint64 res = file.write(data); - return res == data.size(); + return fileAccess()->writeFileContents(*this, data, offset); } FilePathInfo FilePath::filePathInfo() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.filePathInfo, return {}); - return s_deviceHooks.filePathInfo(*this); - } - - FilePathInfo result; - - QFileInfo fi(path()); - result.fileSize = fi.size(); - result.lastModified = fi.lastModified(); - result.fileFlags = (FilePathInfo::FileFlag) int(fi.permissions()); - - if (fi.isDir()) - result.fileFlags |= FilePathInfo::DirectoryType; - if (fi.isFile()) - result.fileFlags |= FilePathInfo::FileType; - if (fi.exists()) - result.fileFlags |= FilePathInfo::ExistsFlag; - if (fi.isSymbolicLink()) - result.fileFlags |= FilePathInfo::LinkType; - if (fi.isBundle()) - result.fileFlags |= FilePathInfo::BundleType; - if (fi.isHidden()) - result.fileFlags |= FilePathInfo::HiddenFlag; - if (fi.isRoot()) - result.fileFlags |= FilePathInfo::RootFlag; - - return result; + return fileAccess()->filePathInfo(*this); } -void FilePath::asyncWriteFileContents(const Continuation &cont, - const QByteArray &data, - qint64 offset) const +void FilePath::asyncWriteFileContents( + const Continuation &cont, + const QByteArray &data, + qint64 offset) const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.asyncWriteFileContents, return); - s_deviceHooks.asyncWriteFileContents(cont, *this, data, offset); - return; - } - - cont(writeFileContents(data, offset)); + return fileAccess()->asyncWriteFileContents(*this, cont, data, offset); } bool FilePath::needsDevice() const @@ -678,26 +519,34 @@ bool FilePath::isSameDevice(const FilePath &other) const return s_deviceHooks.isSameDevice(*this, other); } +bool FilePath::isSameFile(const FilePath &other) const +{ + if (*this == other) + return true; + + if (!isSameDevice(other)) + return false; + + const QByteArray fileId = fileAccess()->fileId(*this); + const QByteArray otherFileId = fileAccess()->fileId(other); + if (fileId.isEmpty() || otherFileId.isEmpty()) + return false; + + if (fileId == otherFileId) + return true; + + return false; +} + /// \returns an empty FilePath if this is not a symbolic linl FilePath FilePath::symLinkTarget() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.symLinkTarget, return {}); - return s_deviceHooks.symLinkTarget(*this); - } - const QFileInfo info(path()); - if (!info.isSymLink()) - return {}; - return FilePath::fromString(info.symLinkTarget()); + return fileAccess()->symLinkTarget(*this); } QString FilePath::mapToDevicePath() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.mapToDevicePath, return path()); - return s_deviceHooks.mapToDevicePath(*this); - } - return path(); + return fileAccess()->mapToDevicePath(*this); } FilePath FilePath::withExecutableSuffix() const @@ -948,6 +797,23 @@ void FilePath::setFromString(const QString &unnormalizedFileName) setParts({}, {}, fileName); } +DeviceFileAccess *FilePath::fileAccess() const +{ + if (!needsDevice()) + return DesktopDeviceFileAccess::instance(); + + if (!s_deviceHooks.fileAccess) { + // Happens during startup and in tst_fsengine + QTC_CHECK(false); + return DesktopDeviceFileAccess::instance(); + } + + static DeviceFileAccess dummy; + DeviceFileAccess *access = s_deviceHooks.fileAccess(*this); + QTC_ASSERT(access, return &dummy); + return access; +} + /// Constructs a FilePath from \a filePath. The \a defaultExtension is appended /// to \a filename if that does not have an extension already. /// \a filePath is not checked for validity. @@ -1041,6 +907,8 @@ FilePath FilePath::operator+(const QString &s) const /// \returns whether FilePath is a child of \a s bool FilePath::isChildOf(const FilePath &s) const { + if (!s.isSameDevice(*this)) + return false; if (s.isEmpty()) return false; if (!path().startsWith(s.path(), caseSensitivity())) @@ -1112,28 +980,26 @@ FilePath FilePath::relativeChildPath(const FilePath &parent) const /// FilePath FilePath::relativePath(const FilePath &anchor) const { - QTC_ASSERT(!needsDevice(), return *this); + QTC_ASSERT(isSameDevice(anchor), return *this); - const QFileInfo fileInfo(toString()); - QString absolutePath; + FilePath absPath; QString filename; - if (fileInfo.isFile()) { - absolutePath = fileInfo.absolutePath(); - filename = fileInfo.fileName(); - } else if (fileInfo.isDir()) { - absolutePath = fileInfo.absoluteFilePath(); + if (isFile()) { + absPath = absolutePath(); + filename = fileName(); + } else if (isDir()) { + absPath = absoluteFilePath(); } else { return {}; } - const QFileInfo anchorInfo(anchor.toString()); - QString absoluteAnchorPath; - if (anchorInfo.isFile()) - absoluteAnchorPath = anchorInfo.absolutePath(); - else if (anchorInfo.isDir()) - absoluteAnchorPath = anchorInfo.absoluteFilePath(); + FilePath absoluteAnchorPath; + if (anchor.isFile()) + absoluteAnchorPath = anchor.absolutePath(); + else if (anchor.isDir()) + absoluteAnchorPath = anchor.absoluteFilePath(); else return {}; - QString relativeFilePath = calcRelativePath(absolutePath, absoluteAnchorPath); + QString relativeFilePath = calcRelativePath(absPath.path(), absoluteAnchorPath.path()); if (!filename.isEmpty()) { if (relativeFilePath == ".") relativeFilePath.clear(); @@ -1252,10 +1118,10 @@ FilePath FilePath::withNewPath(const QString &newPath) const */ FilePath FilePath::searchInDirectories(const FilePaths &dirs) const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.searchInPath, return {}); - return s_deviceHooks.searchInPath(*this, dirs); - } + if (isAbsolutePath()) + return *this; + // FIXME: Ramp down use. + QTC_ASSERT(!needsDevice(), return {}); return Environment::systemEnvironment().searchInDirectories(path(), dirs); } @@ -1263,6 +1129,7 @@ FilePath FilePath::searchInPath(const FilePaths &additionalDirs, PathAmending am { if (isAbsolutePath()) return *this; + // FIXME: Ramp down use. FilePaths directories = deviceEnvironment().path(); if (!additionalDirs.isEmpty()) { if (amending == AppendToPath) @@ -1270,7 +1137,8 @@ FilePath FilePath::searchInPath(const FilePaths &additionalDirs, PathAmending am else directories = additionalDirs + directories; } - return searchInDirectories(directories); + QTC_ASSERT(!needsDevice(), return {}); + return Environment::systemEnvironment().searchInDirectories(path(), directories); } Environment FilePath::deviceEnvironment() const @@ -1349,47 +1217,27 @@ size_t FilePath::hash(uint seed) const QDateTime FilePath::lastModified() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.lastModified, return {}); - return s_deviceHooks.lastModified(*this); - } - return toFileInfo().lastModified(); + return fileAccess()->lastModified(*this); } QFile::Permissions FilePath::permissions() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.permissions, return {}); - return s_deviceHooks.permissions(*this); - } - return toFileInfo().permissions(); + return fileAccess()->permissions(*this); } bool FilePath::setPermissions(QFile::Permissions permissions) const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.setPermissions, return false); - return s_deviceHooks.setPermissions(*this, permissions); - } - return QFile(path()).setPermissions(permissions); + return fileAccess()->setPermissions(*this, permissions); } OsType FilePath::osType() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.osType, return OsType::OsTypeLinux); - return s_deviceHooks.osType(*this); - } - return HostOsInfo::hostOs(); + return fileAccess()->osType(*this); } bool FilePath::removeFile() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.removeFile, return false); - return s_deviceHooks.removeFile(*this); - } - return QFile::remove(path()); + return fileAccess()->removeFile(*this); } /*! @@ -1401,11 +1249,7 @@ bool FilePath::removeFile() const */ bool FilePath::removeRecursively(QString *error) const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.removeRecursively, return false); - return s_deviceHooks.removeRecursively(*this); - } - return removeRecursivelyLocal(*this, error); + return fileAccess()->removeRecursively(*this, error); } bool FilePath::copyFile(const FilePath &target) const @@ -1417,11 +1261,7 @@ bool FilePath::copyFile(const FilePath &target) const return false; return target.writeFileContents(*ba); } - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.copyFile, return false); - return s_deviceHooks.copyFile(*this, target); - } - return QFile::copy(path(), target.path()); + return fileAccess()->copyFile(*this, target); } void FilePath::asyncCopyFile(const std::function &cont, const FilePath &target) const @@ -1431,90 +1271,24 @@ void FilePath::asyncCopyFile(const std::function &cont, const FilePa if (ba) target.asyncWriteFileContents(cont, *ba); }); - } else if (needsDevice()) { - s_deviceHooks.asyncCopyFile(cont, *this, target); - } else { - cont(copyFile(target)); + return; } + return fileAccess()->asyncCopyFile(*this, cont, target); } bool FilePath::renameFile(const FilePath &target) const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.renameFile, return false); - return s_deviceHooks.renameFile(*this, target); - } - return QFile::rename(path(), target.path()); + return fileAccess()->renameFile(*this, target); } qint64 FilePath::fileSize() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.fileSize, return false); - return s_deviceHooks.fileSize(*this); - } - return QFileInfo(path()).size(); + return fileAccess()->fileSize(*this); } qint64 FilePath::bytesAvailable() const { - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.bytesAvailable, return false); - return s_deviceHooks.bytesAvailable(*this); - } - return QStorageInfo(path()).bytesAvailable(); -} - -static bool removeRecursivelyLocal(const FilePath &filePath, QString *error) -{ - QTC_ASSERT(!filePath.needsDevice(), return false); - QFileInfo fileInfo = filePath.toFileInfo(); - if (!fileInfo.exists() && !fileInfo.isSymLink()) - return true; - - QFile::setPermissions(fileInfo.absoluteFilePath(), fileInfo.permissions() | QFile::WriteUser); - - if (fileInfo.isDir()) { - QDir dir(fileInfo.absoluteFilePath()); - dir.setPath(dir.canonicalPath()); - if (dir.isRoot()) { - if (error) { - *error = QCoreApplication::translate("Utils::FileUtils", - "Refusing to remove root directory."); - } - return false; - } - if (dir.path() == QDir::home().canonicalPath()) { - if (error) { - *error = QCoreApplication::translate("Utils::FileUtils", - "Refusing to remove your home directory."); - } - return false; - } - - const QStringList fileNames = dir.entryList( - QDir::Files | QDir::Hidden | QDir::System | QDir::Dirs | QDir::NoDotAndDotDot); - for (const QString &fileName : fileNames) { - if (!removeRecursivelyLocal(filePath / fileName, error)) - return false; - } - if (!QDir::root().rmdir(dir.path())) { - if (error) { - *error = QCoreApplication::translate("Utils::FileUtils", "Failed to remove directory \"%1\".") - .arg(filePath.toUserOutput()); - } - return false; - } - } else { - if (!QFile::remove(filePath.toString())) { - if (error) { - *error = QCoreApplication::translate("Utils::FileUtils", "Failed to remove file \"%1\".") - .arg(filePath.toUserOutput()); - } - return false; - } - } - return true; + return fileAccess()->bytesAvailable(*this); } /*! @@ -1949,19 +1723,15 @@ QStringList FileFilter::asFindArguments(const QString &path) const const QString nameOption = (filters & QDir::CaseSensitive) ? QString{"-name"} : QString{"-iname"}; if (!nameFilters.isEmpty()) { - const QRegularExpression oneChar("\\[.*?\\]"); - bool addedFirst = false; + bool isFirst = true; + filterOptions << "("; for (const QString ¤t : nameFilters) { - if (current.indexOf(oneChar) != -1) { - qDebug() << "Skipped" << current << "due to presence of [] wildcard"; - continue; - } - - if (addedFirst) + if (!isFirst) filterOptions << "-o"; filterOptions << nameOption << current; - addedFirst = true; + isFirst = false; } + filterOptions << ")"; } arguments << filterOptions; return arguments; diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index f03d89d4d8b..9bb65be2935 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -15,6 +15,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE class QDateTime; @@ -27,9 +28,12 @@ class tst_fileutils; // This becomes a friend of Utils::FilePath for testing pri namespace Utils { +class DeviceFileAccess; class Environment; class EnvironmentChange; +template using Continuation = std::function; + class QTCREATOR_UTILS_EXPORT FileFilter { public: @@ -103,6 +107,7 @@ public: bool isAbsolutePath() const { return !isRelativePath(); } bool isFile() const; bool isDir() const; + bool isSymLink() const; bool isRootPath() const; bool isNewerThan(const QDateTime &timeStamp) const; QDateTime lastModified() const; @@ -154,17 +159,20 @@ public: [[nodiscard]] FilePath onDevice(const FilePath &deviceTemplate) const; [[nodiscard]] FilePath withNewPath(const QString &newPath) const; - using IterateDirCallback = std::function; - using IterateDirWithInfoCallback - = std::function; + using IterateDirCallback + = std::variant< + std::function, + std::function + >; - void iterateDirectory(const IterateDirCallback &callBack, const FileFilter &filter) const; - void iterateDirectory(const IterateDirWithInfoCallback &callBack, - const FileFilter &filter) const; + void iterateDirectory( + const IterateDirCallback &callBack, + const FileFilter &filter) const; - static void iterateDirectories(const FilePaths &dirs, - const IterateDirCallback &callBack, - const FileFilter &filter); + static void iterateDirectories( + const FilePaths &dirs, + const IterateDirCallback &callBack, + const FileFilter &filter); enum PathAmending { AppendToPath, PrependToPath }; [[nodiscard]] FilePath searchInPath(const FilePaths &additionalDirs = {}, @@ -184,7 +192,6 @@ public: static void sort(FilePaths &files); // Asynchronous interface - template using Continuation = std::function; void asyncCopyFile(const Continuation &cont, const FilePath &target) const; void asyncFileContents(const Continuation &> &cont, qint64 maxSize = -1, @@ -201,6 +208,7 @@ public: bool needsDevice() const; bool isSameDevice(const FilePath &other) const; + bool isSameFile(const FilePath &other) const; [[nodiscard]] QFileInfo toFileInfo() const; [[nodiscard]] static FilePath fromFileInfo(const QFileInfo &info); @@ -228,6 +236,7 @@ private: static QString calcRelativePath(const QString &absolutePath, const QString &absoluteAnchorPath); void setPath(QStringView path); void setFromString(const QString &filepath); + DeviceFileAccess *fileAccess() const; [[nodiscard]] QString mapToDevicePath() const; [[nodiscard]] QString encodedHost() const; @@ -248,54 +257,11 @@ class QTCREATOR_UTILS_EXPORT DeviceFileHooks public: static DeviceFileHooks &instance(); - std::function isExecutableFile; - std::function isReadableFile; - std::function isReadableDir; - std::function isWritableDir; - std::function isWritableFile; - std::function isFile; - std::function isDir; - std::function ensureWritableDir; - std::function ensureExistingFile; - std::function createDir; - std::function exists; - std::function removeFile; - std::function removeRecursively; - std::function copyFile; - std::function renameFile; - std::function searchInPath; - std::function symLinkTarget; - std::function mapToDevicePath; - std::function - iterateDirectory; - std::function - iterateDirectoryWithInfo; - std::function(const FilePath &, qint64, qint64)> fileContents; - std::function writeFileContents; - std::function lastModified; - std::function permissions; - std::function setPermissions; - std::function osType; - std::function environment; - std::function fileSize; - std::function bytesAvailable; + std::function fileAccess; std::function deviceDisplayName; - std::function isSameDevice; - std::function filePathInfo; - - - template using Continuation = std::function; - std::function &, const FilePath &, const FilePath &)> asyncCopyFile; - std::function &> &, const FilePath &, qint64, qint64)> - asyncFileContents; - std::function &, const FilePath &, const QByteArray &, qint64)> - asyncWriteFileContents; std::function ensureReachable; + std::function environment; + std::function isSameDevice; }; } // namespace Utils diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index cb893ff4a46..6715ff731b6 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -5,7 +5,6 @@ #include "savefile.h" #include "algorithm.h" -#include "commandline.h" #include "qtcassert.h" #include "hostosinfo.h" @@ -15,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -25,7 +23,6 @@ #ifdef QT_GUI_LIB #include -#include #include #endif @@ -37,7 +34,7 @@ #include #endif -#ifdef Q_OS_OSX +#ifdef Q_OS_MACOS #include "fileutils_mac.h" #endif @@ -337,47 +334,6 @@ FilePaths FileUtils::CopyAskingForOverwrite::files() const } #endif // QT_GUI_LIB -// Copied from qfilesystemengine_win.cpp -#ifdef Q_OS_WIN - -// File ID for Windows up to version 7. -static inline QByteArray fileIdWin7(HANDLE handle) -{ - BY_HANDLE_FILE_INFORMATION info; - if (GetFileInformationByHandle(handle, &info)) { - char buffer[sizeof "01234567:0123456701234567\0"]; - qsnprintf(buffer, sizeof(buffer), "%lx:%08lx%08lx", - info.dwVolumeSerialNumber, - info.nFileIndexHigh, - info.nFileIndexLow); - return QByteArray(buffer); - } - return QByteArray(); -} - -// File ID for Windows starting from version 8. -static QByteArray fileIdWin8(HANDLE handle) -{ - QByteArray result; - FILE_ID_INFO infoEx; - if (GetFileInformationByHandleEx(handle, - static_cast(18), // FileIdInfo in Windows 8 - &infoEx, sizeof(FILE_ID_INFO))) { - result = QByteArray::number(infoEx.VolumeSerialNumber, 16); - result += ':'; - // Note: MinGW-64's definition of FILE_ID_128 differs from the MSVC one. - result += QByteArray(reinterpret_cast(&infoEx.FileId), int(sizeof(infoEx.FileId))).toHex(); - } - return result; -} - -static QByteArray fileIdWin(HANDLE fHandle) -{ - return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8 ? - fileIdWin8(HANDLE(fHandle)) : fileIdWin7(HANDLE(fHandle)); -} -#endif - FilePath FileUtils::commonPath(const FilePaths &paths) { if (paths.isEmpty()) @@ -424,33 +380,6 @@ FilePath FileUtils::commonPath(const FilePaths &paths) return result; } -QByteArray FileUtils::fileId(const FilePath &fileName) -{ - QByteArray result; - -#ifdef Q_OS_WIN - const HANDLE handle = - CreateFile((wchar_t*)fileName.toUserOutput().utf16(), 0, - FILE_SHARE_READ, NULL, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, NULL); - if (handle != INVALID_HANDLE_VALUE) { - result = fileIdWin(handle); - CloseHandle(handle); - } -#else // Copied from qfilesystemengine_unix.cpp - if (Q_UNLIKELY(fileName.isEmpty())) - return result; - - QT_STATBUF statResult; - if (QT_STAT(fileName.toString().toLocal8Bit().constData(), &statResult)) - return result; - result = QByteArray::number(quint64(statResult.st_dev), 16); - result += ':'; - result += QByteArray::number(quint64(statResult.st_ino)); -#endif - return result; -} - #ifdef Q_OS_WIN template <> void withNtfsPermissions(const std::function &task) @@ -476,6 +405,82 @@ static QWidget *dialogParent(QWidget *parent) return parent ? parent : s_dialogParentGetter ? s_dialogParentGetter() : nullptr; } + +FilePath qUrlToFilePath(const QUrl &url) +{ + if (url.isLocalFile()) + return FilePath::fromString(url.toLocalFile()); + return FilePath::fromUrl(url); +} + +QUrl filePathToQUrl(const FilePath &filePath) +{ + return QUrl::fromLocalFile(filePath.toFSPathString()); +} + +void prepareNonNativeDialog(QFileDialog &dialog) +{ + // Checking QFileDialog::itemDelegate() seems to be the only way to determine + // whether the dialog is native or not. + if (dialog.itemDelegate()) { + FilePaths sideBarPaths; + + // Check existing urls, remove paths that need a device and no longer exist. + for (const QUrl &url : dialog.sidebarUrls()) { + FilePath path = qUrlToFilePath(url); + if (!path.needsDevice() || path.exists()) + sideBarPaths.append(path); + } + + // Add all device roots that are not already in the sidebar and exist. + for (const FilePath &path : FSEngine::registeredDeviceRoots()) { + if (!sideBarPaths.contains(path) && path.exists()) + sideBarPaths.append(path); + } + + dialog.setSidebarUrls(Utils::transform(sideBarPaths, filePathToQUrl)); + dialog.setIconProvider(Utils::FileIconProvider::iconProvider()); + } +} + +FilePaths getFilePaths(QWidget *parent, + const QString &caption, + const FilePath &dir, + const QString &filter, + QString *selectedFilter, + QFileDialog::Options options, + const QStringList &supportedSchemes, + const bool forceNonNativeDialog, + QFileDialog::FileMode fileMode, + QFileDialog::AcceptMode acceptMode) +{ + QFileDialog dialog(parent, caption, dir.toFSPathString(), filter); + dialog.setFileMode(fileMode); + + if (forceNonNativeDialog) + options.setFlag(QFileDialog::DontUseNativeDialog); + + dialog.setOptions(options); + prepareNonNativeDialog(dialog); + + dialog.setSupportedSchemes(supportedSchemes); + dialog.setAcceptMode(acceptMode); + + if (selectedFilter && !selectedFilter->isEmpty()) + dialog.selectNameFilter(*selectedFilter); + if (dialog.exec() == QDialog::Accepted) { + if (selectedFilter) + *selectedFilter = dialog.selectedNameFilter(); + return Utils::transform(dialog.selectedUrls(), &qUrlToFilePath); + } + return {}; +} + +FilePath firstOrEmpty(const FilePaths &filePaths) +{ + return filePaths.isEmpty() ? FilePath() : filePaths.first(); +} + FilePath FileUtils::getOpenFilePath(QWidget *parent, const QString &caption, const FilePath &dir, @@ -484,19 +489,24 @@ FilePath FileUtils::getOpenFilePath(QWidget *parent, QFileDialog::Options options, bool fromDeviceIfShiftIsPressed) { + bool forceNonNativeDialog = dir.needsDevice(); #ifdef QT_GUI_LIB if (fromDeviceIfShiftIsPressed && qApp->queryKeyboardModifiers() & Qt::ShiftModifier) { - return getOpenFilePathFromDevice(parent, caption, dir, filter, selectedFilter, options); + forceNonNativeDialog = true; } #endif - const QString result = QFileDialog::getOpenFileName(dialogParent(parent), - caption, - dir.toString(), - filter, - selectedFilter, - options); - return FilePath::fromString(result); + const QStringList schemes = QStringList(QStringLiteral("file")); + return firstOrEmpty(getFilePaths(dialogParent(parent), + caption, + dir, + filter, + selectedFilter, + options, + schemes, + forceNonNativeDialog, + QFileDialog::ExistingFile, + QFileDialog::AcceptOpen)); } FilePath FileUtils::getSaveFilePath(QWidget *parent, @@ -506,25 +516,46 @@ FilePath FileUtils::getSaveFilePath(QWidget *parent, QString *selectedFilter, QFileDialog::Options options) { - const QString result = QFileDialog::getSaveFileName(dialogParent(parent), - caption, - dir.toString(), - filter, - selectedFilter, - options); - return FilePath::fromString(result); + bool forceNonNativeDialog = dir.needsDevice(); + + const QStringList schemes = QStringList(QStringLiteral("file")); + return firstOrEmpty(getFilePaths(dialogParent(parent), + caption, + dir, + filter, + selectedFilter, + options, + schemes, + forceNonNativeDialog, + QFileDialog::AnyFile, + QFileDialog::AcceptSave)); } FilePath FileUtils::getExistingDirectory(QWidget *parent, const QString &caption, const FilePath &dir, - QFileDialog::Options options) + QFileDialog::Options options, + bool fromDeviceIfShiftIsPressed) { - const QString result = QFileDialog::getExistingDirectory(dialogParent(parent), - caption, - dir.toString(), - options); - return FilePath::fromString(result); + bool forceNonNativeDialog = dir.needsDevice(); + +#ifdef QT_GUI_LIB + if (fromDeviceIfShiftIsPressed && qApp->queryKeyboardModifiers() & Qt::ShiftModifier) { + forceNonNativeDialog = true; + } +#endif + + const QStringList schemes = QStringList(QStringLiteral("file")); + return firstOrEmpty(getFilePaths(dialogParent(parent), + caption, + dir, + {}, + nullptr, + options, + schemes, + forceNonNativeDialog, + QFileDialog::Directory, + QFileDialog::AcceptOpen)); } FilePaths FileUtils::getOpenFilePaths(QWidget *parent, @@ -534,92 +565,23 @@ FilePaths FileUtils::getOpenFilePaths(QWidget *parent, QString *selectedFilter, QFileDialog::Options options) { - const QStringList result = QFileDialog::getOpenFileNames(dialogParent(parent), - caption, - dir.toString(), - filter, - selectedFilter, - options); - return FileUtils::toFilePathList(result); -} + bool forceNonNativeDialog = dir.needsDevice(); -FilePath FileUtils::getOpenFilePathFromDevice(QWidget *parent, - const QString &caption, - const FilePath &dir, - const QString &filter, - QString *selectedFilter, - QFileDialog::Options options) -{ - QFileDialog dialog(parent); - dialog.setOptions(options | QFileDialog::DontUseNativeDialog); - dialog.setWindowTitle(caption); - dialog.setDirectory(dir.toString()); - dialog.setNameFilter(filter); - - QList sideBarUrls = Utils::transform(Utils::filtered(FSEngine::registeredDeviceRoots(), - [](const auto &filePath) { - return filePath.exists(); - }), - [](const auto &filePath) { - return QUrl::fromLocalFile( - filePath.toFSPathString()); - }); - dialog.setSidebarUrls(sideBarUrls); - dialog.setFileMode(QFileDialog::AnyFile); - - dialog.setIconProvider(Utils::FileIconProvider::iconProvider()); - - if (dialog.exec()) { - FilePaths filePaths = Utils::transform(dialog.selectedFiles(), [](const auto &path) { - return FilePath::fromString(path); - }); - - if (selectedFilter) { - *selectedFilter = dialog.selectedNameFilter(); - } - - return filePaths.first(); - } - - return {}; + const QStringList schemes = QStringList(QStringLiteral("file")); + return getFilePaths(dialogParent(parent), + caption, + dir, + filter, + selectedFilter, + options, + schemes, + forceNonNativeDialog, + QFileDialog::ExistingFiles, + QFileDialog::AcceptOpen); } #endif // QT_WIDGETS_LIB -// Used on 'ls' output on unix-like systems. -void FileUtils::iterateLsOutput(const FilePath &base, - const QStringList &entries, - const FileFilter &filter, - const std::function &callBack) -{ - const QList nameRegexps = - transform(filter.nameFilters, [](const QString &filter) { - QRegularExpression re; - re.setPattern(QRegularExpression::wildcardToRegularExpression(filter)); - QTC_CHECK(re.isValid()); - return re; - }); - - const auto nameMatches = [&nameRegexps](const QString &fileName) { - for (const QRegularExpression &re : nameRegexps) { - const QRegularExpressionMatch match = re.match(fileName); - if (match.hasMatch()) - return true; - } - return nameRegexps.isEmpty(); - }; - - // FIXME: Handle filters. For now bark on unsupported options. - QTC_CHECK(filter.fileFilters == QDir::NoFilter); - - for (const QString &entry : entries) { - if (!nameMatches(entry)) - continue; - if (!callBack(base.pathAppended(entry))) - break; - } -} - FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString) { bool ok = false; @@ -675,148 +637,6 @@ FilePathInfo FileUtils::filePathInfoFromTriple(const QString &infos) return {size, flags, dt}; } -bool iterateWithFind(const FilePath &filePath, - const FileFilter &filter, - const std::function &runInShell, - const std::function callBack, - const QString &extraArguments) -{ - QTC_CHECK(filePath.isAbsolutePath()); - const QStringList arguments = filter.asFindArguments(filePath.path()); - - CommandLine cmdLine{"find", arguments}; - if (!extraArguments.isEmpty()) - cmdLine.addArgs(extraArguments, CommandLine::Raw); - - const RunResult result = runInShell(cmdLine); - const QString out = QString::fromUtf8(result.stdOut); - if (result.exitCode != 0) { - // Find returns non-zero exit code for any error it encounters, even if it finds some files. - - if (!out.startsWith('"' + filePath.path())) { - if (!filePath.exists()) // File does not exist, so no files to find. - return true; - - // If the output does not start with the path we are searching in, find has failed. - // Possibly due to unknown options. - return false; - } - } - - QStringList entries = out.split("\n", Qt::SkipEmptyParts); - // Remove the first line, it is always the directory we are searching in. - // as long as we do not specify "mindepth > 0" - entries.pop_front(); - for (const QString &entry : entries) { - if (!callBack(entry)) - break; - } - - return true; -} - -// returns whether 'find' could be used. -static bool iterateWithFind(const FilePath &filePath, - const FileFilter &filter, - const std::function &runInShell, - const FilePath::IterateDirCallback &callBack) -{ - const auto toFilePath = [&filePath, &callBack](const QString &entry){ - return callBack(filePath.withNewPath(entry)); - }; - - return iterateWithFind(filePath, filter, runInShell, toFilePath, {}); -} - -// returns whether 'find' could be used. -static bool iterateWithFind(const FilePath &filePath, - const FileFilter &filter, - const std::function &runInShell, - const FilePath::IterateDirWithInfoCallback &callBack) -{ - // TODO: Using stat -L will always return the link target, not the link itself. - // We may wan't to add the information that it is a link at some point. - const QString infoArgs(R"(-exec echo -n \"{}\"" " \; -exec stat -L -c "%f %Y %s" "{}" \;)"); - - const auto toFilePathAndInfo = [&filePath, &callBack](const QString &entry) { - const QString fileName = entry.mid(1, entry.lastIndexOf('\"') - 1); - const QString infos = entry.mid(fileName.length() + 3); - - const FilePathInfo fi = FileUtils::filePathInfoFromTriple(infos); - if (!fi.fileFlags) - return true; - - const FilePath fp = filePath.withNewPath(fileName); - return callBack(fp, fi); - }; - - return iterateWithFind(filePath, filter, runInShell, toFilePathAndInfo, infoArgs); -} - -static void findUsingLs(const QString ¤t, - const FileFilter &filter, - const std::function &runInShell, - QStringList *found) -{ - const RunResult result = runInShell({"ls", {"-1", "-p", "--", current}}); - const QStringList entries = QString::fromUtf8(result.stdOut).split('\n', Qt::SkipEmptyParts); - for (QString entry : entries) { - const QChar last = entry.back(); - if (last == '/') { - entry.chop(1); - if (filter.iteratorFlags.testFlag(QDirIterator::Subdirectories)) - findUsingLs(current + '/' + entry, filter, runInShell, found); - } - found->append(entry); - } -} - -void FileUtils::iterateUnixDirectory(const FilePath &filePath, - const FileFilter &filter, - bool *useFind, - const std::function &runInShell, - const FilePath::IterateDirCallback &callBack) -{ - QTC_ASSERT(callBack, return); - - // We try to use 'find' first, because that can filter better directly. - // Unfortunately, it's not installed on all devices by default. - if (useFind && *useFind) { - if (iterateWithFind(filePath, filter, runInShell, callBack)) - return; - *useFind = false; // remember the failure for the next time and use the 'ls' fallback below. - } - - // if we do not have find - use ls as fallback - QStringList entries; - findUsingLs(filePath.path(), filter, runInShell, &entries); - FileUtils::iterateLsOutput(filePath, entries, filter, callBack); -} - -void FileUtils::iterateUnixDirectory(const FilePath &filePath, - const FileFilter &filter, - bool *useFind, - const std::function &runInShell, - const FilePath::IterateDirWithInfoCallback &callBack) -{ - QTC_ASSERT(callBack, return); - - // We try to use 'find' first, because that can filter better directly. - // Unfortunately, it's not installed on all devices by default. - if (useFind && *useFind) { - if (iterateWithFind(filePath, filter, runInShell, callBack)) - return; - *useFind = false; // remember the failure for the next time and use the 'ls' fallback below. - } - - // if we do not have find - use ls as fallback - QStringList entries; - findUsingLs(filePath.path(), filter, runInShell, &entries); - FileUtils::iterateLsOutput(filePath, entries, filter, [&callBack](const FilePath & filePath){ - return callBack(filePath, filePath.filePathInfo()); - }); -} - /*! Copies the directory specified by \a srcFilePath recursively to \a tgtFilePath. \a tgtFilePath will contain the target directory, which will be created. Example usage: diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index fe3a388bb0b..f46f83d4c36 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -81,31 +81,10 @@ public: static bool isAbsolutePath(const QString &fileName) { return !isRelativePath(fileName); } static FilePath commonPath(const FilePath &oldCommonPath, const FilePath &fileName); static FilePath commonPath(const FilePaths &paths); - static QByteArray fileId(const FilePath &fileName); static FilePath homePath(); static FilePaths toFilePathList(const QStringList &paths); - static void iterateLsOutput( - const FilePath &base, - const QStringList &entries, - const FileFilter &filter, - const std::function &callBack); - - static void iterateUnixDirectory( - const FilePath &base, - const FileFilter &filter, - bool *useFind, - const std::function &runInShell, - const FilePath::IterateDirCallback &callBack); - - static void iterateUnixDirectory( - const FilePath &base, - const FileFilter &filter, - bool *useFind, - const std::function &runInShell, - const FilePath::IterateDirWithInfoCallback &callBack); - static qint64 bytesAvailableFromDFOutput(const QByteArray &dfOutput); static FilePathInfo filePathInfoFromTriple(const QString &infos); @@ -131,7 +110,8 @@ public: static FilePath getExistingDirectory(QWidget *parent, const QString &caption, const FilePath &dir = {}, - QFileDialog::Options options = QFileDialog::ShowDirsOnly); + QFileDialog::Options options = QFileDialog::ShowDirsOnly, + bool fromDeviceIfShiftIsPressed = false); static FilePaths getOpenFilePaths(QWidget *parent, const QString &caption, @@ -139,13 +119,6 @@ public: const QString &filter = {}, QString *selectedFilter = nullptr, QFileDialog::Options options = {}); - - static FilePath getOpenFilePathFromDevice(QWidget *parent, - const QString &caption, - const FilePath &dir = {}, - const QString &filter = {}, - QString *selectedFilter = nullptr, - QFileDialog::Options options = {}); #endif }; diff --git a/src/libs/utils/fsengine/filepathinfocache.h b/src/libs/utils/fsengine/filepathinfocache.h index e0b7094cbfe..5a5f2be52b9 100644 --- a/src/libs/utils/fsengine/filepathinfocache.h +++ b/src/libs/utils/fsengine/filepathinfocache.h @@ -40,7 +40,13 @@ public: if (!data) { data = new CachedData; *data = retrievalFunction(filePath); - m_cache.insert(filePath, data); + if (Q_UNLIKELY(!m_cache.insert(filePath, data))) { + // This path will never happen, but to silence coverity we + // have to check it since insert in theory could delete + // the object if a cost bigger than the cache size is + // specified. + return {}; + } } // Return a copy of the data, so it cannot be deleted by the cache @@ -60,6 +66,12 @@ public: m_cache.insert(path, new CachedData(data)); } + void invalidate(const FilePath &path) + { + QMutexLocker lk(&m_mutex); + m_cache.remove(path); + } + private: QMutex m_mutex; QCache m_cache; diff --git a/src/libs/utils/fsengine/fsengine_impl.cpp b/src/libs/utils/fsengine/fsengine_impl.cpp index f8277195b28..e09bc94609e 100644 --- a/src/libs/utils/fsengine/fsengine_impl.cpp +++ b/src/libs/utils/fsengine/fsengine_impl.cpp @@ -44,6 +44,8 @@ bool FSEngineImpl::open(QIODevice::OpenMode openMode) createCacheData); bool exists = (data.filePathInfo.fileFlags & QAbstractFileEngine::ExistsFlag); + g_filePathInfoCache.invalidate(m_filePath); + ensureStorage(); QTC_ASSERT(m_tempStorage->open(), return false); diff --git a/src/libs/utils/pathchooser.cpp b/src/libs/utils/pathchooser.cpp index 3d7ac909740..ef30703c9b7 100644 --- a/src/libs/utils/pathchooser.cpp +++ b/src/libs/utils/pathchooser.cpp @@ -199,6 +199,9 @@ FilePath PathChooserPrivate::expandedPath(const FilePath &input) const if (path.isEmpty()) return path; + if (path.isAbsolutePath()) + return path; + switch (m_acceptingKind) { case PathChooser::Command: case PathChooser::ExistingCommand: { @@ -391,7 +394,7 @@ void PathChooser::slotBrowse() case PathChooser::ExistingDirectory: newPath = FileUtils::getExistingDirectory(this, makeDialogTitle(tr("Choose Directory")), - predefined); + predefined, {}, d->m_allowPathFromDevice); break; case PathChooser::ExistingCommand: case PathChooser::Command: diff --git a/src/libs/utils/scripts/deviceshell.sh b/src/libs/utils/scripts/deviceshell.sh new file mode 100644 index 00000000000..86f096b256e --- /dev/null +++ b/src/libs/utils/scripts/deviceshell.sh @@ -0,0 +1,116 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 +FINAL_OUT=$(mktemp -u) +mkfifo "$FINAL_OUT" + +finalOutput() { + local fileInputBuffer + while read fileInputBuffer + do + if test -f "$fileInputBuffer.err"; then + cat $fileInputBuffer.err + fi + cat $fileInputBuffer + rm -f $fileInputBuffer.err $fileInputBuffer + done +} + +finalOutput < $FINAL_OUT & + +readAndMark() { + local buffer + while read buffer + do + printf '%s:%s:%s\n' "$1" "$2" "$buffer" + done +} + +base64decode() +{ + base64 -d 2>/dev/null +} + +base64encode() +{ + base64 2>/dev/null +} + +executeAndMark() +{ + PID="$1" + INDATA="$2" + shift + shift + CMD="$@" + + # LogFile + TMPFILE=$(mktemp) + + # Output Streams + stdoutenc=$(mktemp -u) + stderrenc=$(mktemp -u) + mkfifo "$stdoutenc" "$stderrenc" + + # app output streams + stdoutraw=$(mktemp -u) + stderrraw=$(mktemp -u) + mkfifo "$stdoutraw" "$stderrraw" + + # Cleanup + trap 'rm -f "$stdoutenc" "$stderrenc" "$stdoutraw" "$stderrraw"' EXIT + + # Pipe all app output through base64, and then into the output streams + cat $stdoutraw | base64encode > "$stdoutenc" & + cat $stderrraw | base64encode > "$stderrenc" & + + # Mark the app's output streams + readAndMark $PID 'O' < "$stdoutenc" >> $TMPFILE & + readAndMark $PID 'E' < "$stderrenc" >> $TMPFILE.err & + + # Start the app ... + if [ -z "$INDATA" ] + then + eval $CMD 1> "$stdoutraw" 2> "$stderrraw" + else + echo $INDATA | base64decode | eval "$CMD" 1> "$stdoutraw" 2> "$stderrraw" + fi + + exitcode=$(echo $? | base64encode) + wait + echo "$PID:R:$exitcode" >> $TMPFILE + echo $TMPFILE +} + +execute() +{ + PID="$1" + + if [ "$#" -lt "3" ]; then + TMPFILE=$(mktemp) + echo "$PID:R:MjU1Cg==" > $TMPFILE + echo $TMPFILE + else + INDATA=$(eval echo "$2") + shift + shift + CMD=$@ + executeAndMark $PID "$INDATA" "$CMD" + fi +} + +cleanup() +{ + kill -- -$$ + exit 1 +} + +trap cleanup 1 2 3 6 + +echo SCRIPT_INSTALLED >&2 + +(while read -r id inData cmd; do + if [ "$id" = "exit" ]; then + exit + fi + execute $id $inData $cmd || echo "$id:R:255" & +done) > $FINAL_OUT diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index b5da80bbc5f..4cc71591c35 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -83,6 +83,8 @@ Project { "detailsbutton.h", "detailswidget.cpp", "detailswidget.h", + "devicefileaccess.cpp", + "devicefileaccess.h", "deviceshell.cpp", "deviceshell.h", "differ.cpp", diff --git a/src/libs/utils/utils.qrc b/src/libs/utils/utils.qrc index 8685c9be9f2..1f2ef64898e 100644 --- a/src/libs/utils/utils.qrc +++ b/src/libs/utils/utils.qrc @@ -239,6 +239,7 @@ images/message@2x.png images/help.png ../3rdparty/xdg/freedesktop.org.xml + scripts/deviceshell.sh images/enum.png diff --git a/src/plugins/beautifier/clangformat/clangformatoptionspage.cpp b/src/plugins/beautifier/clangformat/clangformatoptionspage.cpp index fcefbfc257e..d6244eb466d 100644 --- a/src/plugins/beautifier/clangformat/clangformatoptionspage.cpp +++ b/src/plugins/beautifier/clangformat/clangformatoptionspage.cpp @@ -79,7 +79,6 @@ ClangFormatOptionsPageWidget::ClangFormatOptionsPageWidget(ClangFormatSettings * m_command->setCommandVersionArguments({"--version"}); m_command->setPromptDialogTitle( BeautifierPlugin::msgCommandPromptDialogTitle("Clang Format")); - m_command->setFilePath(m_settings->command()); if (m_settings->usePredefinedStyle()) m_usePredefinedStyle->setChecked(true); @@ -107,13 +106,16 @@ ClangFormatOptionsPageWidget::ClangFormatOptionsPageWidget(ClangFormatSettings * }.attachTo(this); connect(m_command, &Utils::PathChooser::validChanged, options, &QWidget::setEnabled); - connect(m_predefinedStyle, &QComboBox::currentTextChanged, [this](const QString &item) { + connect(m_predefinedStyle, &QComboBox::currentTextChanged, this, [this](const QString &item) { m_fallbackStyle->setEnabled(item == "File"); }); - connect(m_usePredefinedStyle, &QRadioButton::toggled, [this](bool checked) { + connect(m_usePredefinedStyle, &QRadioButton::toggled, this, [this](bool checked) { m_fallbackStyle->setEnabled(checked && m_predefinedStyle->currentText() == "File"); m_predefinedStyle->setEnabled(checked); }); + + // might trigger PathChooser::validChanged, so so after the connect above + m_command->setFilePath(m_settings->command()); } void ClangFormatOptionsPageWidget::apply() diff --git a/src/plugins/bookmarks/bookmarksplugin.cpp b/src/plugins/bookmarks/bookmarksplugin.cpp index f1b6a78ddf3..a48288e39a0 100644 --- a/src/plugins/bookmarks/bookmarksplugin.cpp +++ b/src/plugins/bookmarks/bookmarksplugin.cpp @@ -88,20 +88,16 @@ BookmarksPluginPrivate::BookmarksPluginPrivate() // Toggle Command *cmd = ActionManager::registerAction(&m_toggleAction, BOOKMARKS_TOGGLE_ACTION, editorManagerContext); - cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? Tr::tr("Meta+M") - : Tr::tr("Ctrl+M"))); + cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? Tr::tr("Meta+M") : Tr::tr("Ctrl+M"))); cmd->setTouchBarIcon(Utils::Icons::MACOS_TOUCHBAR_BOOKMARK.icon()); mbm->addAction(cmd); - - cmd = ActionManager::registerAction(&m_editAction, - BOOKMARKS_EDIT_ACTION, - editorManagerContext); - cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? Tr::tr("Meta+Shift+M") - : Tr::tr("Ctrl+Shift+M"))); - mbm->addAction(cmd); - touchBar->addAction(cmd, Core::Constants::G_TOUCHBAR_EDITOR); + cmd = ActionManager::registerAction(&m_editAction, BOOKMARKS_EDIT_ACTION, editorManagerContext); + cmd->setDefaultKeySequence( + QKeySequence(useMacShortcuts ? Tr::tr("Meta+Shift+M") : Tr::tr("Ctrl+Shift+M"))); + mbm->addAction(cmd); + mbm->addSeparator(); // Previous diff --git a/src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp b/src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp index d5a3a766ed4..1eedc858a33 100644 --- a/src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp +++ b/src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp @@ -6,6 +6,7 @@ #include "qdbconstants.h" #include +#include "projectexplorer/devicesupport/idevice.h" #include #include #include @@ -31,7 +32,14 @@ QdbDeployConfigurationFactory::QdbDeployConfigurationFactory() && prj->hasMakeInstallEquivalent(); }); addInitialStep(Qdb::Constants::QdbStopApplicationStepId); - addInitialStep(RemoteLinux::Constants::DirectUploadStepId); + addInitialStep(RemoteLinux::Constants::RsyncDeployStepId, [](Target *target) { + auto device = DeviceKitAspect::device(target->kit()); + return device && device->extraData(RemoteLinux::Constants::SupportsRSync).toBool(); + }); + addInitialStep(RemoteLinux::Constants::DirectUploadStepId, [](Target *target) { + auto device = DeviceKitAspect::device(target->kit()); + return device && !device->extraData(RemoteLinux::Constants::SupportsRSync).toBool(); + }); } } // namespace Internal diff --git a/src/plugins/boot2qt/qdbmakedefaultappstep.cpp b/src/plugins/boot2qt/qdbmakedefaultappstep.cpp index 8b89c3ed22b..3c5f0e3814d 100644 --- a/src/plugins/boot2qt/qdbmakedefaultappstep.cpp +++ b/src/plugins/boot2qt/qdbmakedefaultappstep.cpp @@ -92,7 +92,8 @@ public: QdbMakeDefaultAppStep(BuildStepList *bsl, Id id) : AbstractRemoteLinuxDeployStep(bsl, id) { - auto service = createDeployService(); + auto service = new QdbMakeDefaultAppService; + setDeployService(service); auto selection = addAspect(); selection->setSettingsKey("QdbMakeDefaultDeployStep.MakeDefault"); diff --git a/src/plugins/boot2qt/qdbplugin.cpp b/src/plugins/boot2qt/qdbplugin.cpp index cc01f0f9587..a1157cca01a 100644 --- a/src/plugins/boot2qt/qdbplugin.cpp +++ b/src/plugins/boot2qt/qdbplugin.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -156,6 +157,8 @@ public: QdbDeployStepFactory m_directUploadStepFactory{RemoteLinux::Constants::DirectUploadStepId}; + QdbDeployStepFactory + m_rsyncDeployStepFactory{RemoteLinux::Constants::RsyncDeployStepId}; QdbDeployStepFactory m_makeInstallStepFactory{RemoteLinux::Constants::MakeInstallStepId}; diff --git a/src/plugins/boot2qt/qdbstopapplicationstep.cpp b/src/plugins/boot2qt/qdbstopapplicationstep.cpp index 9899598d3e1..e3b3f1b2c7e 100644 --- a/src/plugins/boot2qt/qdbstopapplicationstep.cpp +++ b/src/plugins/boot2qt/qdbstopapplicationstep.cpp @@ -115,7 +115,8 @@ public: QdbStopApplicationStep(BuildStepList *bsl, Id id) : AbstractRemoteLinuxDeployStep(bsl, id) { - auto service = createDeployService(); + auto service = new QdbStopApplicationService; + setDeployService(service); setWidgetExpandedByDefault(false); diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index e43ca885c50..b6693adf689 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -260,7 +260,7 @@ public: : q(q), settings(CppEditor::ClangdProjectSettings(project).settings()) {} void findUsages(TextDocument *document, const QTextCursor &cursor, - const QString &searchTerm, + const QString &searchTerm, const std::optional &replacement, bool categorize); void handleDeclDefSwitchReplies(); @@ -462,11 +462,13 @@ void ClangdClient::findUsages(TextDocument *document, const QTextCursor &cursor, if (searchTerm.isEmpty()) return; - if (replacement) { - symbolSupport().renameSymbol(document, adjustedCursor, *replacement, - CppEditor::preferLowerCaseFileNames()); - return; - } + // TODO: Fix hard file limit in clangd, then uncomment this with version check. + // Will fix QTCREATORBUG-27978 and QTCREATORBUG-28109. + // if (replacement) { + // symbolSupport().renameSymbol(document, adjustedCursor, *replacement, + // CppEditor::preferLowerCaseFileNames()); + // return; + // } const bool categorize = CppEditor::codeModelSettings()->categorizeFindReferences(); @@ -474,19 +476,19 @@ void ClangdClient::findUsages(TextDocument *document, const QTextCursor &cursor, if (searchTerm != "operator" && Utils::allOf(searchTerm, [](const QChar &c) { return c.isLetterOrNumber() || c == '_'; })) { - d->findUsages(document, adjustedCursor, searchTerm, categorize); + d->findUsages(document, adjustedCursor, searchTerm, replacement, categorize); return; } // Otherwise get the proper spelling of the search term from clang, so we can put it into the // search widget. - const auto symbolInfoHandler = [this, doc = QPointer(document), adjustedCursor, categorize] + const auto symbolInfoHandler = [this, doc = QPointer(document), adjustedCursor, replacement, categorize] (const QString &name, const QString &, const MessageId &) { if (!doc) return; if (name.isEmpty()) return; - d->findUsages(doc.data(), adjustedCursor, name, categorize); + d->findUsages(doc.data(), adjustedCursor, name, replacement, categorize); }; requestSymbolInfo(document->filePath(), Range(adjustedCursor).start(), symbolInfoHandler); } @@ -642,9 +644,11 @@ QVersionNumber ClangdClient::versionNumber() const CppEditor::ClangdSettings::Data ClangdClient::settingsData() const { return d->settings; } void ClangdClient::Private::findUsages(TextDocument *document, - const QTextCursor &cursor, const QString &searchTerm, bool categorize) + const QTextCursor &cursor, const QString &searchTerm, + const std::optional &replacement, bool categorize) { - const auto findRefs = new ClangdFindReferences(q, document, cursor, searchTerm, categorize); + const auto findRefs = new ClangdFindReferences(q, document, cursor, searchTerm, replacement, + categorize); if (isTesting) { connect(findRefs, &ClangdFindReferences::foundReferences, q, &ClangdClient::foundReferences); @@ -732,6 +736,8 @@ void ClangdClient::updateParserConfig(const Utils::FilePath &filePath, const CppEditor::BaseEditorDocumentParser::Configuration &config) { // TODO: Also handle usePrecompiledHeaders? + // TODO: Should we write the editor defines into the json file? It seems strange + // that they should affect the index only while the file is open in the editor. const auto projectPart = !config.preferredProjectPartId.isEmpty() ? CppEditor::CppModelManager::instance()->projectPartForId( config.preferredProjectPartId) @@ -741,10 +747,15 @@ void ClangdClient::updateParserConfig(const Utils::FilePath &filePath, CppEditor::BaseEditorDocumentParser::Configuration fullConfig = config; fullConfig.preferredProjectPartId = projectPart->id(); - CppEditor::BaseEditorDocumentParser::Configuration &cachedConfig = d->parserConfigs[filePath]; - if (cachedConfig == fullConfig) + auto cachedConfig = d->parserConfigs.find(filePath); + if (cachedConfig == d->parserConfigs.end()) { + cachedConfig = d->parserConfigs.insert(filePath, fullConfig); + if (config.preferredProjectPartId.isEmpty() && config.editorDefines.isEmpty()) + return; + } else if (cachedConfig.value() == fullConfig) { return; - cachedConfig = fullConfig; + } + cachedConfig.value() = fullConfig; QJsonObject cdbChanges; const Utils::FilePath includeDir = CppEditor::ClangdSettings(d->settings).clangdIncludePath(); diff --git a/src/plugins/clangcodemodel/clangdfindreferences.cpp b/src/plugins/clangcodemodel/clangdfindreferences.cpp index ce0abac572e..f5dcaf71e19 100644 --- a/src/plugins/clangcodemodel/clangdfindreferences.cpp +++ b/src/plugins/clangcodemodel/clangdfindreferences.cpp @@ -44,12 +44,25 @@ public: ClangdAstNode ast; }; +class ReplacementData { +public: + QString oldSymbolName; + QString newSymbolName; + QSet fileRenameCandidates; +}; + class ClangdFindReferences::Private { public: Private(ClangdFindReferences *q) : q(q) {} ClangdClient *client() const { return qobject_cast(q->parent()); } + static void handleRenameRequest( + const SearchResult *search, + const ReplacementData &replacementData, + const QString &newSymbolName, + const QList &checkedItems, + bool preserveCase); void handleFindUsagesResult(const QList &locations); void finishSearch(); void reportAllSearchResultsAndFinish(); @@ -61,24 +74,50 @@ public: QMap fileData; QList pendingAstRequests; QPointer search; + std::optional replacementData; bool canceled = false; bool categorize = false; }; ClangdFindReferences::ClangdFindReferences(ClangdClient *client, TextDocument *document, - const QTextCursor &cursor, const QString &searchTerm, bool categorize) + const QTextCursor &cursor, const QString &searchTerm, + const std::optional &replacement, bool categorize) : QObject(client), d(new ClangdFindReferences::Private(this)) { d->categorize = categorize; + if (replacement) { + ReplacementData replacementData; + replacementData.oldSymbolName = searchTerm; + replacementData.newSymbolName = *replacement; + if (replacementData.newSymbolName.isEmpty()) + replacementData.newSymbolName = replacementData.oldSymbolName; + d->replacementData = replacementData; + } + d->search = SearchResultWindow::instance()->startNewSearch( tr("C++ Usages:"), {}, searchTerm, - SearchResultWindow::SearchOnly, + replacement ? SearchResultWindow::SearchAndReplace : SearchResultWindow::SearchOnly, SearchResultWindow::PreserveCaseDisabled, "CppEditor"); if (categorize) d->search->setFilter(new CppSearchResultFilter); + if (d->replacementData) { + d->search->setTextToReplace(d->replacementData->newSymbolName); + const auto renameFilesCheckBox = new QCheckBox; + renameFilesCheckBox->setVisible(false); + d->search->setAdditionalReplaceWidget(renameFilesCheckBox); + const auto renameHandler = + [search = d->search](const QString &newSymbolName, + const QList &checkedItems, + bool preserveCase) { + const auto replacementData = search->userData().value(); + Private::handleRenameRequest(search, replacementData, newSymbolName, checkedItems, + preserveCase); + }; + connect(d->search, &SearchResult::replaceButtonClicked, renameHandler); + } connect(d->search, &SearchResult::activated, [](const SearchResultItem& item) { EditorManager::openEditorAtSearchResult(item); }); @@ -112,6 +151,31 @@ ClangdFindReferences::~ClangdFindReferences() delete d; } +void ClangdFindReferences::Private::handleRenameRequest( + const SearchResult *search, + const ReplacementData &replacementData, + const QString &newSymbolName, + const QList &checkedItems, + bool preserveCase) +{ + const Utils::FilePaths filePaths = BaseFileFind::replaceAll(newSymbolName, checkedItems, + preserveCase); + if (!filePaths.isEmpty()) { + DocumentManager::notifyFilesChangedInternally(filePaths); + SearchResultWindow::instance()->hide(); + } + + const auto renameFilesCheckBox = qobject_cast(search->additionalReplaceWidget()); + QTC_ASSERT(renameFilesCheckBox, return); + if (!renameFilesCheckBox->isChecked()) + return; + + ProjectExplorerPlugin::renameFilesForSymbol( + replacementData.oldSymbolName, newSymbolName, + Utils::toList(replacementData.fileRenameCandidates), + CppEditor::preferLowerCaseFileNames()); +} + void ClangdFindReferences::Private::handleFindUsagesResult(const QList &locations) { if (!search || canceled) { @@ -154,7 +218,7 @@ void ClangdFindReferences::Private::handleFindUsagesResult(const QList } qCDebug(clangdLog) << "document count is" << fileData.size(); - if (!categorize) { + if (replacementData || !categorize) { qCDebug(clangdLog) << "skipping AST retrieval"; reportAllSearchResultsAndFinish(); return; @@ -198,6 +262,18 @@ void ClangdFindReferences::Private::finishSearch() if (!client()->testingEnabled() && search) { search->finishSearch(canceled); search->disconnect(q); + if (replacementData) { + const auto renameCheckBox = qobject_cast( + search->additionalReplaceWidget()); + QTC_CHECK(renameCheckBox); + const QSet files = replacementData->fileRenameCandidates; + renameCheckBox->setText(tr("Re&name %n files", nullptr, files.size())); + const QStringList filesForUser = Utils::transform(files, + [](const Utils::FilePath &fp) { return fp.toUserOutput(); }); + renameCheckBox->setToolTip(tr("Files:\n%1").arg(filesForUser.join('\n'))); + renameCheckBox->setVisible(true); + search->setUserData(QVariant::fromValue(*replacementData)); + } } emit q->done(); q->deleteLater(); @@ -231,6 +307,15 @@ void ClangdFindReferences::Private::addSearchResultsForFile(const FilePath &file item.setUseTextEditorFont(true); item.setLineText(rangeWithText.second); item.setContainingFunctionName(getContainingFunctionName(astPath, range)); + + if (search->supportsReplace()) { + const bool fileInSession = SessionManager::projectForFile(file); + item.setSelectForReplacement(fileInSession); + if (fileInSession && file.baseName().compare(replacementData->oldSymbolName, + Qt::CaseInsensitive) == 0) { + replacementData->fileRenameCandidates << file; + } + } items << item; } if (client()->testingEnabled()) @@ -484,3 +569,5 @@ void ClangdFindLocalReferences::Private::finish() } } // namespace ClangCodeModel::Internal + +Q_DECLARE_METATYPE(ClangCodeModel::Internal::ReplacementData) diff --git a/src/plugins/clangcodemodel/clangdfindreferences.h b/src/plugins/clangcodemodel/clangdfindreferences.h index 27d25a293ca..f45a61b25e4 100644 --- a/src/plugins/clangcodemodel/clangdfindreferences.h +++ b/src/plugins/clangcodemodel/clangdfindreferences.h @@ -25,7 +25,7 @@ class ClangdFindReferences : public QObject public: explicit ClangdFindReferences(ClangdClient *client, TextEditor::TextDocument *document, const QTextCursor &cursor, const QString &searchTerm, - bool categorize); + const std::optional &replacement, bool categorize); ~ClangdFindReferences(); signals: diff --git a/src/plugins/clangcodemodel/clangutils.cpp b/src/plugins/clangcodemodel/clangutils.cpp index 47718511881..fc7c174cbda 100644 --- a/src/plugins/clangcodemodel/clangutils.cpp +++ b/src/plugins/clangcodemodel/clangutils.cpp @@ -78,9 +78,8 @@ static QStringList projectPartArguments(const ProjectPart &projectPart) args << "-c"; if (projectPart.toolchainType != ProjectExplorer::Constants::MSVC_TOOLCHAIN_TYPEID) { args << "--target=" + projectPart.toolChainTargetTriple; - args << (projectPart.toolChainWordWidth == ProjectPart::WordWidth64Bit - ? QLatin1String("-m64") - : QLatin1String("-m32")); + if (projectPart.toolChainAbi.architecture() == Abi::X86Architecture) + args << QLatin1String(projectPart.toolChainAbi.wordWidth() == 64 ? "-m64" : "-m32"); } args << projectPart.compilerFlags; for (const ProjectExplorer::HeaderPath &headerPath : projectPart.headerPaths) { @@ -155,7 +154,7 @@ GenerateCompilationDbResult generateCompilationDB(QList p QTC_ASSERT(!projectInfoList.isEmpty(), return GenerateCompilationDbResult(QString(), "Could not retrieve project info.")); QTC_CHECK(baseDir.ensureWritableDir()); - QFile compileCommandsFile(baseDir.toString() + "/compile_commands.json"); + QFile compileCommandsFile(baseDir.pathAppended("compile_commands.json").toFSPathString()); const bool fileOpened = compileCommandsFile.open(QIODevice::WriteOnly | QIODevice::Truncate); if (!fileOpened) { return GenerateCompilationDbResult(QString(), diff --git a/src/plugins/clangformat/clangformatutils.cpp b/src/plugins/clangformat/clangformatutils.cpp index 31660de3392..dbc4a6a347e 100644 --- a/src/plugins/clangformat/clangformatutils.cpp +++ b/src/plugins/clangformat/clangformatutils.cpp @@ -34,8 +34,8 @@ clang::format::FormatStyle qtcStyle() style.AccessModifierOffset = -4; style.AlignAfterOpenBracket = FormatStyle::BAS_Align; #if LLVM_VERSION_MAJOR >= 15 - style.AlignConsecutiveAssignments = {false}; - style.AlignConsecutiveDeclarations = {false}; + style.AlignConsecutiveAssignments = {false, false, false, false, false}; + style.AlignConsecutiveDeclarations = {false, false, false, false, false}; #elif LLVM_VERSION_MAJOR >= 12 style.AlignConsecutiveAssignments = FormatStyle::ACS_None; style.AlignConsecutiveDeclarations = FormatStyle::ACS_None; diff --git a/src/plugins/clangtools/clangtool.h b/src/plugins/clangtools/clangtool.h index bd33e31ebc6..34f5eafec15 100644 --- a/src/plugins/clangtools/clangtool.h +++ b/src/plugins/clangtools/clangtool.h @@ -161,7 +161,8 @@ private: QAction *m_clear = nullptr; QAction *m_expandCollapse = nullptr; - Utils::Perspective m_perspective{ClangTidyClazyPerspectiveId, tr("Clang-Tidy and Clazy")}; + Utils::Perspective m_perspective{ClangTidyClazyPerspectiveId, + ::ClangTools::Internal::ClangTool::tr("Clang-Tidy and Clazy")}; private: const QString m_name; diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp index 7dd6dc2f973..3dc13248b41 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp @@ -175,14 +175,10 @@ CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildSystem *bs) : BuildConfiguration *bc = bs->buildConfiguration(); CMakeBuildConfiguration *cbc = static_cast(bc); - auto vbox = new QVBoxLayout(this); - vbox->setContentsMargins(0, 0, 0, 0); m_configureDetailsWidget = new DetailsWidget; updateConfigureDetailsWidgetsSummary(); - vbox->addWidget(m_configureDetailsWidget); - auto details = new QWidget(m_configureDetailsWidget); m_configureDetailsWidget->setWidget(details); @@ -333,9 +329,6 @@ CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildSystem *bs) : envWidget->setBaseEnvironmentText(cbc->baseConfigureEnvironmentText()); }); - vbox->addWidget(clearBox); - vbox->addWidget(envWidget); - using namespace Layouting; Grid cmakeConfiguration { m_filterEdit, br, @@ -372,10 +365,16 @@ CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildSystem *bs) : }, m_reconfigureButton, } - } - }.setSpacing(0) + }, + clearBox, + envWidget + } }.attachTo(details, WithoutMargins); + Column { + m_configureDetailsWidget, + }.attachTo(this, WithoutMargins); + updateAdvancedCheckBox(); setError(m_buildSystem->error()); setWarning(m_buildSystem->warning()); diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp index 4b089dff6fc..7c3db2c7403 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -607,24 +607,24 @@ void CMakeBuildSystem::updateProjectData() setError(errorMessage); qCDebug(cmakeBuildSystemLog) << "Raw project parts created." << errorMessage; - { - for (RawProjectPart &rpp : rpps) { - rpp.setQtVersion( - kitInfo.projectPartQtVersion); // TODO: Check if project actually uses Qt. - const QString includeFileBaseDir = buildConfiguration()->buildDirectory().toString(); - if (kitInfo.cxxToolChain) { - rpp.setFlagsForCxx({kitInfo.cxxToolChain, rpp.flagsForCxx.commandLineFlags, - includeFileBaseDir}); - } - if (kitInfo.cToolChain) { - rpp.setFlagsForC({kitInfo.cToolChain, rpp.flagsForC.commandLineFlags, - includeFileBaseDir}); - } - } - - m_cppCodeModelUpdater->update({p, kitInfo, buildConfiguration()->environment(), rpps}, - m_extraCompilers); + for (RawProjectPart &rpp : rpps) { + rpp.setQtVersion( + kitInfo.projectPartQtVersion); // TODO: Check if project actually uses Qt. + const QString includeFileBaseDir = buildConfiguration()->buildDirectory().toString(); + QStringList cxxFlags = rpp.flagsForCxx.commandLineFlags; + QStringList cFlags = rpp.flagsForC.commandLineFlags; + addTargetFlagForIos(cxxFlags, cFlags, this, [this] { + return m_configurationFromCMake.stringValueOf("CMAKE_OSX_DEPLOYMENT_TARGET"); + }); + if (kitInfo.cxxToolChain) + rpp.setFlagsForCxx({kitInfo.cxxToolChain, cxxFlags, includeFileBaseDir}); + if (kitInfo.cToolChain) + rpp.setFlagsForC({kitInfo.cToolChain, cFlags, includeFileBaseDir}); } + + m_cppCodeModelUpdater->update({p, kitInfo, buildConfiguration()->environment(), rpps}, + m_extraCompilers); + { const bool mergedHeaderPathsAndQmlImportPaths = kit()->value( QtSupport::KitHasMergedHeaderPathsWithQmlImportPaths::id(), false).toBool(); @@ -1093,28 +1093,28 @@ DeploymentData CMakeBuildSystem::deploymentData() const { DeploymentData result; - QDir sourceDir = project()->projectDirectory().toString(); - QDir buildDir = buildConfiguration()->buildDirectory().toString(); + FilePath sourceDir = project()->projectDirectory(); + FilePath buildDir = buildConfiguration()->buildDirectory(); QString deploymentPrefix; - QString deploymentFilePath = sourceDir.filePath("QtCreatorDeployment.txt"); + FilePath deploymentFilePath = sourceDir.pathAppended("QtCreatorDeployment.txt"); - bool hasDeploymentFile = QFileInfo::exists(deploymentFilePath); + bool hasDeploymentFile = deploymentFilePath.exists(); if (!hasDeploymentFile) { - deploymentFilePath = buildDir.filePath("QtCreatorDeployment.txt"); - hasDeploymentFile = QFileInfo::exists(deploymentFilePath); + deploymentFilePath = buildDir.pathAppended("QtCreatorDeployment.txt"); + hasDeploymentFile = deploymentFilePath.exists(); } if (!hasDeploymentFile) return result; - deploymentPrefix = result.addFilesFromDeploymentFile(deploymentFilePath, - sourceDir.absolutePath()); + deploymentPrefix = result.addFilesFromDeploymentFile(deploymentFilePath.toString(), + sourceDir.toString()); for (const CMakeBuildTarget &ct : m_buildTargets) { if (ct.targetType == ExecutableType || ct.targetType == DynamicLibraryType) { if (!ct.executable.isEmpty() && result.deployableForLocalFile(ct.executable).localFilePath() != ct.executable) { result.addFile(ct.executable, - deploymentPrefix + buildDir.relativeFilePath(ct.executable.toFileInfo().dir().path()), + deploymentPrefix + buildDir.relativeChildPath(ct.executable).toString(), DeployableFile::TypeExecutable); } } diff --git a/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp b/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp index 42eccdf7e06..523bafca68d 100644 --- a/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp +++ b/src/plugins/cmakeprojectmanager/cmakekitinformation.cpp @@ -682,8 +682,11 @@ QVariant CMakeGeneratorKitAspect::defaultValue(const Kit *k) const = Internal::CMakeProjectPlugin::projectTypeSpecificSettings(); if (settings->ninjaPath.filePath().isEmpty()) { - Environment env = k->buildEnvironment(); - return !env.searchInPath("ninja").isEmpty(); + auto findNinja = [](const Environment &env) -> bool { + return !env.searchInPath("ninja").isEmpty(); + }; + if (!findNinja(Environment::systemEnvironment())) + return findNinja(k->buildEnvironment()); } return true; }(); diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp index 33d9684acbc..eaf2261b06c 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -217,10 +218,17 @@ void CMakeManager::buildFile(Node *node) CMakeTargetNode *targetNode = dynamic_cast(fileNode->parentProjectNode()); if (!targetNode) return; + Utils::FilePath filePath = fileNode->filePath(); + if (filePath.fileName().contains(".h")) { + bool wasHeader = false; + const QString sourceFile = CppEditor::correspondingHeaderOrSource(filePath.toString(), &wasHeader); + if (wasHeader && !sourceFile.isEmpty()) + filePath = Utils::FilePath::fromString(sourceFile); + } Target *target = project->activeTarget(); QTC_ASSERT(target, return); const QString generator = CMakeGeneratorKitAspect::generator(target->kit()); - const QString relativeSource = fileNode->filePath().relativeChildPath(targetNode->filePath()).toString(); + const QString relativeSource = filePath.relativeChildPath(targetNode->filePath()).toString(); const QString objExtension = Utils::HostOsInfo::isWindowsHost() ? QString(".obj") : QString(".o"); Utils::FilePath targetBase; BuildConfiguration *bc = target->activeBuildConfiguration(); diff --git a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp index 4abee299646..da791d51642 100644 --- a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp +++ b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp @@ -264,18 +264,18 @@ QList generateBuildTargets(const PreprocessedData &input, continue; const FilePath buildDir = haveLibrariesRelativeToBuildDirectory ? buildDirectory : currentBuildDir; - FilePath tmp = buildDir.resolvePath(FilePath::fromUserInput(part)); + FilePath tmp = buildDir.resolvePath(FilePath::fromUserInput(part).onDevice(buildDir)); if (f.role == "libraries") tmp = tmp.parentDir(); if (!tmp.isEmpty() && tmp.isDir()) { // f.role is libraryPath or frameworkPath - // On Linux, exclude sub-paths from "/lib(64)", "/usr/lib(64)" and + // On *nix, exclude sub-paths from "/lib(64)", "/usr/lib(64)" and // "/usr/local/lib" since these are usually in the standard search // paths. There probably are more, but the naming schemes are arbitrary // so we'd need to ask the linker ("ld --verbose | grep SEARCH_DIR"). - if (!HostOsInfo::isLinuxHost() + if (buildDir.osType() == OsTypeWindows || !isChildOf(tmp, {"/lib", "/lib64", @@ -285,9 +285,8 @@ QList generateBuildTargets(const PreprocessedData &input, librarySeachPaths.append(tmp); // Libraries often have their import libs in ../lib and the // actual dll files in ../bin on windows. Qt is one example of that. - if (tmp.fileName() == "lib" && HostOsInfo::isWindowsHost()) { + if (tmp.fileName() == "lib" && buildDir.osType() == OsTypeWindows) { const FilePath path = tmp.parentDir().pathAppended("bin"); - if (path.isDir()) librarySeachPaths.append(path); } diff --git a/src/plugins/coreplugin/actionmanager/commandbutton.cpp b/src/plugins/coreplugin/actionmanager/commandbutton.cpp index 1d7be701414..56866ca1dcb 100644 --- a/src/plugins/coreplugin/actionmanager/commandbutton.cpp +++ b/src/plugins/coreplugin/actionmanager/commandbutton.cpp @@ -5,34 +5,42 @@ #include "actionmanager.h" #include "command.h" - #include +#include using namespace Core; using namespace Utils; +/*! + \class Core::CommandAction + \inheaderfile coreplugin/actionmanager/commandbutton.h + \inmodule QtCreator + + \brief The CommandAction class is an action associated with one of + the registered Command objects. + + It shares the icon and text of the command. + The tooltip of the action consists of toolTipBase property value and Command's + key sequence which is automatically updated when user changes it. + */ + /*! \class Core::CommandButton \inheaderfile coreplugin/actionmanager/commandbutton.h \inmodule QtCreator - \brief The CommandButton class is a tool button associated with one of + \brief The CommandButton class is an action associated with one of the registered Command objects. - Tooltip of this button consists of toolTipBase property value and Command's + The tooltip of the button consists of toolTipBase property value and Command's key sequence which is automatically updated when user changes it. */ -/*! - \property CommandButton::toolTipBase - \brief The tool tip base for the command button. -*/ - /*! \internal */ -CommandButton::CommandButton(QWidget *parent) - : QToolButton(parent) +CommandAction::CommandAction(QWidget *parent) + : QAction(parent) , m_command(nullptr) { } @@ -40,8 +48,8 @@ CommandButton::CommandButton(QWidget *parent) /*! \internal */ -CommandButton::CommandButton(Id id, QWidget *parent) - : QToolButton(parent) +CommandAction::CommandAction(Id id, QWidget *parent) + : QAction(parent) , m_command(nullptr) { setCommandId(id); @@ -50,12 +58,83 @@ CommandButton::CommandButton(Id id, QWidget *parent) /*! Sets the ID of the command associated with this tool button to \a id. */ -void CommandButton::setCommandId(Id id) +void CommandAction::setCommandId(Id id) { if (m_command) - disconnect(m_command.data(), &Command::keySequenceChanged, this, &CommandButton::updateToolTip); + disconnect(m_command.data(), + &Command::keySequenceChanged, + this, + &CommandAction::updateToolTip); m_command = ActionManager::command(id); + QTC_ASSERT(m_command, return); + + if (m_toolTipBase.isEmpty()) + m_toolTipBase = m_command->description(); + + setIcon(m_command->action()->icon()); + setIconText(m_command->action()->iconText()); + setText(m_command->action()->text()); + + updateToolTip(); + connect(m_command.data(), &Command::keySequenceChanged, this, &CommandAction::updateToolTip); +} + +/*! + The base tool tip that is extended with the command's shortcut. + Defaults to the command's description. + + \sa Command::description() +*/ +QString CommandAction::toolTipBase() const +{ + return m_toolTipBase; +} + +/*! + Sets the base tool tip that is extended with the command's shortcut. + + \sa toolTipBase() +*/ +void CommandAction::setToolTipBase(const QString &toolTipBase) +{ + m_toolTipBase = toolTipBase; + updateToolTip(); +} + +void CommandAction::updateToolTip() +{ + if (m_command) + setToolTip(Utils::ProxyAction::stringWithAppendedShortcut(m_toolTipBase, + m_command->keySequence())); +} + +/*! + \internal +*/ +CommandButton::CommandButton(QWidget *parent) + : QToolButton(parent) +{} + +/*! + \internal +*/ +CommandButton::CommandButton(Utils::Id id, QWidget *parent) + : QToolButton(parent) +{ + setCommandId(id); +} + +void CommandButton::setCommandId(Utils::Id id) +{ + if (m_command) + disconnect(m_command.data(), + &Command::keySequenceChanged, + this, + &CommandButton::updateToolTip); + + m_command = ActionManager::command(id); + QTC_ASSERT(m_command, return); if (m_toolTipBase.isEmpty()) m_toolTipBase = m_command->description(); @@ -64,11 +143,22 @@ void CommandButton::setCommandId(Id id) connect(m_command.data(), &Command::keySequenceChanged, this, &CommandButton::updateToolTip); } +/*! + The base tool tip that is extended with the command's shortcut. + Defaults to the command's description. + + \sa Command::description() +*/ QString CommandButton::toolTipBase() const { return m_toolTipBase; } +/*! + Sets the base tool tip that is extended with the command's shortcut. + + \sa toolTipBase() +*/ void CommandButton::setToolTipBase(const QString &toolTipBase) { m_toolTipBase = toolTipBase; diff --git a/src/plugins/coreplugin/actionmanager/commandbutton.h b/src/plugins/coreplugin/actionmanager/commandbutton.h index af99ca4ff1a..72fb6231331 100644 --- a/src/plugins/coreplugin/actionmanager/commandbutton.h +++ b/src/plugins/coreplugin/actionmanager/commandbutton.h @@ -7,6 +7,7 @@ #include +#include #include #include #include @@ -15,10 +16,28 @@ namespace Core { class Command; +class CORE_EXPORT CommandAction : public QAction +{ + Q_OBJECT + +public: + explicit CommandAction(QWidget *parent = nullptr); + explicit CommandAction(Utils::Id id, QWidget *parent = nullptr); + void setCommandId(Utils::Id id); + QString toolTipBase() const; + void setToolTipBase(const QString &toolTipBase); + +private: + void updateToolTip(); + + QPointer m_command; + QString m_toolTipBase; +}; + class CORE_EXPORT CommandButton : public QToolButton { Q_OBJECT - Q_PROPERTY(QString toolTipBase READ toolTipBase WRITE setToolTipBase) + public: explicit CommandButton(QWidget *parent = nullptr); explicit CommandButton(Utils::Id id, QWidget *parent = nullptr); @@ -32,5 +51,4 @@ private: QPointer m_command; QString m_toolTipBase; }; - } diff --git a/src/plugins/coreplugin/documentmanager.cpp b/src/plugins/coreplugin/documentmanager.cpp index c19e65366b5..48c2227a3b1 100644 --- a/src/plugins/coreplugin/documentmanager.cpp +++ b/src/plugins/coreplugin/documentmanager.cpp @@ -1032,11 +1032,12 @@ void DocumentManager::showFilePropertiesDialog(const FilePath &filePath) FilePaths DocumentManager::getOpenFileNames(const QString &filters, const FilePath &pathIn, - QString *selectedFilter) + QString *selectedFilter, + QFileDialog::Options options) { const FilePath path = pathIn.isEmpty() ? fileDialogInitialDirectory() : pathIn; const FilePaths files = FileUtils::getOpenFilePaths(nullptr, tr("Open File"), path, filters, - selectedFilter); + selectedFilter, options); if (!files.isEmpty()) setFileDialogLastVisitedDirectory(files.front().absolutePath()); return files; diff --git a/src/plugins/coreplugin/documentmanager.h b/src/plugins/coreplugin/documentmanager.h index c2ecf69d8d8..07d3e0b890d 100644 --- a/src/plugins/coreplugin/documentmanager.h +++ b/src/plugins/coreplugin/documentmanager.h @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -64,7 +65,8 @@ public: static Utils::FilePaths getOpenFileNames(const QString &filters, const Utils::FilePath &path = {}, - QString *selectedFilter = nullptr); + QString *selectedFilter = nullptr, + QFileDialog::Options options = {}); static Utils::FilePath getSaveFileName(const QString &title, const Utils::FilePath &pathIn, const QString &filter = {}, diff --git a/src/plugins/coreplugin/editormanager/editormanager.cpp b/src/plugins/coreplugin/editormanager/editormanager.cpp index 54622fcaf73..c0044573f50 100644 --- a/src/plugins/coreplugin/editormanager/editormanager.cpp +++ b/src/plugins/coreplugin/editormanager/editormanager.cpp @@ -3249,11 +3249,11 @@ void EditorManager::addCloseEditorListener(const std::function \sa DocumentManager::getOpenFileNames() */ -FilePaths EditorManager::getOpenFilePaths() +FilePaths EditorManager::getOpenFilePaths(QFileDialog::Options options) { QString selectedFilter; const QString &fileFilters = DocumentManager::fileDialogFilter(&selectedFilter); - return DocumentManager::getOpenFileNames(fileFilters, {}, &selectedFilter); + return DocumentManager::getOpenFileNames(fileFilters, {}, &selectedFilter, options); } static QString makeTitleUnique(QString *titlePattern) diff --git a/src/plugins/coreplugin/editormanager/editormanager.h b/src/plugins/coreplugin/editormanager/editormanager.h index ab285328b48..928f9c717bc 100644 --- a/src/plugins/coreplugin/editormanager/editormanager.h +++ b/src/plugins/coreplugin/editormanager/editormanager.h @@ -12,6 +12,7 @@ #include "utils/link.h" #include "utils/textfileformat.h" +#include #include #include @@ -90,7 +91,7 @@ public: static bool openExternalEditor(const Utils::FilePath &filePath, Utils::Id editorId); static void addCloseEditorListener(const std::function &listener); - static Utils::FilePaths getOpenFilePaths(); + static Utils::FilePaths getOpenFilePaths(QFileDialog::Options options = {}); static IDocument *currentDocument(); static IEditor *currentEditor(); diff --git a/src/plugins/coreplugin/editormanager/openeditorsview.cpp b/src/plugins/coreplugin/editormanager/openeditorsview.cpp index 87e4174d2fd..f87d40000dd 100644 --- a/src/plugins/coreplugin/editormanager/openeditorsview.cpp +++ b/src/plugins/coreplugin/editormanager/openeditorsview.cpp @@ -205,6 +205,9 @@ void ProxyModel::setSourceModel(QAbstractItemModel *sm) QVariant ProxyModel::data(const QModelIndex &index, int role) const { if (role == Qt::DecorationRole && index.column() == 0) { + const QVariant sourceDecoration = QAbstractProxyModel::data(index, role); + if (sourceDecoration.isValid()) + return sourceDecoration; const QString fileName = QAbstractProxyModel::data(index, Qt::DisplayRole).toString(); return Utils::FileIconProvider::icon(Utils::FilePath::fromString(fileName)); } diff --git a/src/plugins/coreplugin/mainwindow.cpp b/src/plugins/coreplugin/mainwindow.cpp index a7e943c1961..54c0b38a72c 100644 --- a/src/plugins/coreplugin/mainwindow.cpp +++ b/src/plugins/coreplugin/mainwindow.cpp @@ -524,8 +524,8 @@ void MainWindow::registerDefaultContainers() QIcon(), "Main TouchBar" /*never visible*/); ac->appendGroup(Constants::G_TOUCHBAR_HELP); - ac->appendGroup(Constants::G_TOUCHBAR_EDITOR); ac->appendGroup(Constants::G_TOUCHBAR_NAVIGATION); + ac->appendGroup(Constants::G_TOUCHBAR_EDITOR); ac->appendGroup(Constants::G_TOUCHBAR_OTHER); ac->touchBar()->setApplicationTouchBar(); } @@ -1078,37 +1078,7 @@ void MainWindow::openFileWith() void MainWindow::openFileFromDevice() { - QSettings *settings = PluginManager::settings(); - settings->beginGroup(QLatin1String(settingsGroup)); - QVariant dialogSettings = settings->value(QLatin1String(openFromDeviceDialogKey)); - - QFileDialog dialog; - dialog.setOption(QFileDialog::DontUseNativeDialog); - if (!dialogSettings.isNull()) { - dialog.restoreState(dialogSettings.toByteArray()); - } - QList sideBarUrls = Utils::transform(Utils::filtered(FSEngine::registeredDeviceRoots(), - [](const auto &filePath) { - return filePath.exists(); - }), - [](const auto &filePath) { - return QUrl::fromLocalFile(filePath.toFSPathString()); - }); - dialog.setSidebarUrls(sideBarUrls); - dialog.setFileMode(QFileDialog::AnyFile); - - dialog.setIconProvider(FileIconProvider::iconProvider()); - - if (dialog.exec()) { - FilePaths filePaths = Utils::transform(dialog.selectedFiles(), [](const auto &path) { - return FilePath::fromString(path); - }); - - openFiles(filePaths, ICore::SwitchMode); - } - - settings->setValue(QLatin1String(openFromDeviceDialogKey), dialog.saveState()); - settings->endGroup(); + openFiles(EditorManager::getOpenFilePaths(QFileDialog::DontUseNativeDialog), ICore::SwitchMode); } IContext *MainWindow::contextObject(QWidget *widget) const diff --git a/src/plugins/coreplugin/mimetypesettings.cpp b/src/plugins/coreplugin/mimetypesettings.cpp index 6551cd1a0e8..8f6b4a578fe 100644 --- a/src/plugins/coreplugin/mimetypesettings.cpp +++ b/src/plugins/coreplugin/mimetypesettings.cpp @@ -297,9 +297,11 @@ MimeTypeSettingsPrivate::~MimeTypeSettingsPrivate() = default; void MimeTypeSettingsPrivate::configureUi(QWidget *w) { auto filterLineEdit = new FancyLineEdit; + filterLineEdit->setObjectName("filterLineEdit"); filterLineEdit->setFiltering(true); m_mimeTypesTreeView = new QTreeView; + m_mimeTypesTreeView->setObjectName("mimeTypesTreeView"); m_mimeTypesTreeView->setEditTriggers(QAbstractItemView::DoubleClicked |QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked); m_mimeTypesTreeView->setRootIsDecorated(false); @@ -317,6 +319,7 @@ void MimeTypeSettingsPrivate::configureUi(QWidget *w) patternsLabel->setText(QCoreApplication::translate("Core::Internal::MimeTypeSettingsPage", "Patterns:", nullptr)); m_patternsLineEdit = new QLineEdit; + m_patternsLineEdit->setObjectName("patternsLineEdit"); m_patternsLineEdit->setToolTip(QCoreApplication::translate("Core::Internal::MimeTypeSettingsPage", "A semicolon-separated list of wildcarded file names.", nullptr)); m_magicHeadersTreeWidget = new QTreeWidget; diff --git a/src/plugins/cpaster/pastebindotcomprotocol.h b/src/plugins/cpaster/pastebindotcomprotocol.h index 740fbca7447..b17fc71729b 100644 --- a/src/plugins/cpaster/pastebindotcomprotocol.h +++ b/src/plugins/cpaster/pastebindotcomprotocol.h @@ -34,8 +34,6 @@ private: QNetworkReply *m_listReply = nullptr; QString m_fetchId; - int m_postId = -1; - bool m_hostChecked = false; }; } // CodePaster diff --git a/src/plugins/cpaster/pasteselectdialog.cpp b/src/plugins/cpaster/pasteselectdialog.cpp index 3454824fbf4..ed09368cd85 100644 --- a/src/plugins/cpaster/pasteselectdialog.cpp +++ b/src/plugins/cpaster/pasteselectdialog.cpp @@ -25,15 +25,19 @@ PasteSelectDialog::PasteSelectDialog(const QList &protocols, QWidget QDialog(parent), m_protocols(protocols) { + setObjectName("CodePaster.PasteSelectDialog"); resize(550, 350); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_protocolBox = new QComboBox(this); + m_protocolBox->setObjectName("protocolBox"); m_pasteEdit = new QLineEdit(this); + m_pasteEdit->setObjectName("pasteEdit"); m_pasteEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); m_listWidget = new QListWidget(this); + m_listWidget->setObjectName("listWidget"); m_listWidget->setAlternatingRowColors(true); auto buttons = new QDialogButtonBox(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); diff --git a/src/plugins/cpaster/pasteview.cpp b/src/plugins/cpaster/pasteview.cpp index 5d3d9816ef9..e9a67370fe3 100644 --- a/src/plugins/cpaster/pasteview.cpp +++ b/src/plugins/cpaster/pasteview.cpp @@ -41,10 +41,12 @@ PasteView::PasteView(const QList &protocols, m_commentPlaceHolder(Tr::tr("")), m_mimeType(mt) { + setObjectName("CodePaster.ViewDialog"); resize(670, 678); setWindowTitle(Tr::tr("Send to Codepaster")); m_protocolBox = new QComboBox; + m_protocolBox->setObjectName("protocolBox"); for (const Protocol *p : protocols) m_protocolBox->addItem(p->name()); @@ -56,6 +58,7 @@ PasteView::PasteView(const QList &protocols, m_uiUsername->setPlaceholderText(Tr::tr("")); m_uiDescription = new QLineEdit(this); + m_uiDescription->setObjectName("uiDescription"); m_uiDescription->setPlaceholderText(Tr::tr("")); QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -83,6 +86,7 @@ PasteView::PasteView(const QList &protocols, groupBox->setFlat(true); m_plainTextEdit = new QPlainTextEdit; + m_plainTextEdit->setObjectName("plainTextEdit"); m_stackedWidget = new QStackedWidget(this); m_stackedWidget->addWidget(groupBox); diff --git a/src/plugins/cppcheck/cppcheckplugin.cpp b/src/plugins/cppcheck/cppcheckplugin.cpp index c40f1ee6c66..18c4df53481 100644 --- a/src/plugins/cppcheck/cppcheckplugin.cpp +++ b/src/plugins/cppcheck/cppcheckplugin.cpp @@ -39,7 +39,8 @@ public: CppcheckOptionsPage options; DiagnosticsModel manualRunModel; CppcheckTool manualRunTool; - Utils::Perspective perspective{Constants::PERSPECTIVE_ID, CppcheckPlugin::tr("Cppcheck")}; + Utils::Perspective perspective{Constants::PERSPECTIVE_ID, + ::Cppcheck::Internal::CppcheckPlugin::tr("Cppcheck")}; QAction *manualRunAction; void startManualRun(); diff --git a/src/plugins/cppeditor/compileroptionsbuilder.cpp b/src/plugins/cppeditor/compileroptionsbuilder.cpp index a1b151de2a2..def318acb4e 100644 --- a/src/plugins/cppeditor/compileroptionsbuilder.cpp +++ b/src/plugins/cppeditor/compileroptionsbuilder.cpp @@ -227,7 +227,9 @@ QStringList createLanguageOptionGcc(ProjectFile::Kind fileKind, bool objcExt) void CompilerOptionsBuilder::addWordWidth() { - const QString argument = m_projectPart.toolChainWordWidth == ProjectPart::WordWidth64Bit + if (m_projectPart.toolChainAbi.architecture() != Abi::X86Architecture) + return; + const QString argument = m_projectPart.toolChainAbi.wordWidth() == 64 ? QLatin1String("-m64") : QLatin1String("-m32"); add(argument); diff --git a/src/plugins/cppeditor/compileroptionsbuilder_test.cpp b/src/plugins/cppeditor/compileroptionsbuilder_test.cpp index 2bc07375842..f4d84e4e03b 100644 --- a/src/plugins/cppeditor/compileroptionsbuilder_test.cpp +++ b/src/plugins/cppeditor/compileroptionsbuilder_test.cpp @@ -37,8 +37,12 @@ public: rpp.setConfigFileName(projectConfigFile); ToolChainInfo tcInfo; tcInfo.type = toolchainType; - tcInfo.wordWidth = 64; tcInfo.targetTriple = targetTriple; + tcInfo.abi = Abi::fromString(targetTriple); + if (!tcInfo.abi.isValid()) { + tcInfo.abi = Abi(Abi::X86Architecture, Abi::DarwinOS, Abi::FreeBsdFlavor, + Abi::MachOFormat, 64); + } tcInfo.isMsvc2015ToolChain = isMsvc2015; tcInfo.extraCodeModelFlags = extraFlags; tcInfo.macroInspectionRunner = [this](const QStringList &) { diff --git a/src/plugins/cppeditor/cppcodemodelinspectordialog.cpp b/src/plugins/cppeditor/cppcodemodelinspectordialog.cpp index f17f5c1f62f..0b5006e6bff 100644 --- a/src/plugins/cppeditor/cppcodemodelinspectordialog.cpp +++ b/src/plugins/cppeditor/cppcodemodelinspectordialog.cpp @@ -1789,7 +1789,7 @@ void CppCodeModelInspectorDialog::updateProjectPartData(const ProjectPart::Const {QString::fromLatin1("Build Target Type"), CMI::Utils::toString(part->buildTargetType)}, {QString::fromLatin1("ToolChain Type"), part->toolchainType.toString()}, {QString::fromLatin1("ToolChain Target Triple"), part->toolChainTargetTriple}, - {QString::fromLatin1("ToolChain Word Width"), CMI::Utils::toString(part->toolChainWordWidth)}, + {QString::fromLatin1("ToolChain Word Width"), CMI::Utils::toString(part->toolChainAbi.wordWidth())}, {QString::fromLatin1("ToolChain Install Dir"), part->toolChainInstallDir.toString()}, {QString::fromLatin1("Language Version"), CMI::Utils::toString(part->languageVersion)}, {QString::fromLatin1("Language Extensions"), CMI::Utils::toString(part->languageExtensions)}, diff --git a/src/plugins/cppeditor/cppcodemodelinspectordumper.cpp b/src/plugins/cppeditor/cppcodemodelinspectordumper.cpp index 6b4429574e8..b82c2a03e11 100644 --- a/src/plugins/cppeditor/cppcodemodelinspectordumper.cpp +++ b/src/plugins/cppeditor/cppcodemodelinspectordumper.cpp @@ -373,15 +373,15 @@ QString Utils::toString(CPlusPlus::Kind kind) return QString(); } -QString Utils::toString(ProjectPart::ToolChainWordWidth width) +QString Utils::toString(const ProjectExplorer::Abi &abi) { - switch (width) { - case ProjectPart::ToolChainWordWidth::WordWidth32Bit: + switch (abi.wordWidth()) { + case 32: return QString("32"); - case ProjectPart::ToolChainWordWidth::WordWidth64Bit: + case 64: return QString("64"); } - return QString(); + return QString("??"); } QString Utils::partsForFile(const QString &fileName) @@ -508,7 +508,7 @@ void Dumper::dumpProjectInfos(const QList &projectInfos) m_out << i3 << "Project File : " << projectFilePath << "\n"; m_out << i3 << "ToolChain Type : " << part->toolchainType.toString() << "\n"; m_out << i3 << "ToolChain Target Triple: " << part->toolChainTargetTriple << "\n"; - m_out << i3 << "ToolChain Word Width : " << part->toolChainWordWidth << "\n"; + m_out << i3 << "ToolChain Word Width : " << part->toolChainAbi.wordWidth() << "\n"; m_out << i3 << "ToolChain Install Dir : " << part->toolChainInstallDir << "\n"; m_out << i3 << "Compiler Flags : " << part->compilerFlags.join(", ") << "\n"; m_out << i3 << "Selected For Building : " << part->selectedForBuilding << "\n"; diff --git a/src/plugins/cppeditor/cppcodemodelinspectordumper.h b/src/plugins/cppeditor/cppcodemodelinspectordumper.h index 9474988af0c..872c868fee0 100644 --- a/src/plugins/cppeditor/cppcodemodelinspectordumper.h +++ b/src/plugins/cppeditor/cppcodemodelinspectordumper.h @@ -32,7 +32,7 @@ struct Utils static QString toString(const QVector &projectFiles); static QString toString(ProjectFile::Kind kind); static QString toString(CPlusPlus::Kind kind); - static QString toString(ProjectPart::ToolChainWordWidth width); + static QString toString(const ProjectExplorer::Abi &abi); static QString partsForFile(const QString &fileName); static QString unresolvedFileNameWithDelimiters(const CPlusPlus::Document::Include &include); static QString pathListToString(const QStringList &pathList); diff --git a/src/plugins/cppeditor/cppprojectinfogenerator.cpp b/src/plugins/cppeditor/cppprojectinfogenerator.cpp index 629f44c43a1..2e5c068bd77 100644 --- a/src/plugins/cppeditor/cppprojectinfogenerator.cpp +++ b/src/plugins/cppeditor/cppprojectinfogenerator.cpp @@ -137,6 +137,27 @@ ProjectPart::ConstPtr ProjectInfoGenerator::createProjectPart( tcInfo = m_projectUpdateInfo.cxxToolChainInfo; } + QString explicitTarget; + if (!tcInfo.targetTripleIsAuthoritative) { + for (int i = 0; i < flags.commandLineFlags.size(); ++i) { + const QString &flag = flags.commandLineFlags.at(i); + if (flag == "-target") { + if (i + 1 < flags.commandLineFlags.size()) + explicitTarget = flags.commandLineFlags.at(i + 1); + break; + } else if (flag.startsWith("--target=")) { + explicitTarget = flag.mid(9); + break; + } + } + } + if (!explicitTarget.isEmpty()) { + tcInfo.targetTriple = explicitTarget; + tcInfo.targetTripleIsAuthoritative = true; + if (const Abi abi = Abi::fromString(tcInfo.targetTriple); abi.isValid()) + tcInfo.abi = abi; + } + return ProjectPart::create(projectFilePath, rawProjectPart, partName, projectFiles, language, languageExtensions, flags, tcInfo); } diff --git a/src/plugins/cppeditor/projectpart.cpp b/src/plugins/cppeditor/projectpart.cpp index 464c138cc8b..a315b621b8b 100644 --- a/src/plugins/cppeditor/projectpart.cpp +++ b/src/plugins/cppeditor/projectpart.cpp @@ -145,8 +145,7 @@ ProjectPart::ProjectPart(const Utils::FilePath &topLevelProject, isMsvc2015Toolchain(tcInfo.isMsvc2015ToolChain), toolChainTargetTriple(tcInfo.targetTriple), targetTripleIsAuthoritative(tcInfo.targetTripleIsAuthoritative), - toolChainWordWidth(tcInfo.wordWidth == 64 ? ProjectPart::WordWidth64Bit - : ProjectPart::WordWidth32Bit), + toolChainAbi(tcInfo.abi), toolChainInstallDir(tcInfo.installDir), compilerFilePath(tcInfo.compilerFilePath), warningFlags(flags.warningFlags), diff --git a/src/plugins/cppeditor/projectpart.h b/src/plugins/cppeditor/projectpart.h index 6f99dcf6d7e..4845d796a25 100644 --- a/src/plugins/cppeditor/projectpart.h +++ b/src/plugins/cppeditor/projectpart.h @@ -28,11 +28,6 @@ namespace CppEditor { class CPPEDITOR_EXPORT ProjectPart { public: - enum ToolChainWordWidth { - WordWidth32Bit, - WordWidth64Bit, - }; - using ConstPtr = QSharedPointer; public: @@ -94,7 +89,7 @@ public: const bool isMsvc2015Toolchain = false; const QString toolChainTargetTriple; const bool targetTripleIsAuthoritative; - const ToolChainWordWidth toolChainWordWidth = WordWidth32Bit; + const ProjectExplorer::Abi toolChainAbi = ProjectExplorer::Abi::hostAbi(); const Utils::FilePath toolChainInstallDir; const Utils::FilePath compilerFilePath; const Utils::WarningFlags warningFlags = Utils::WarningFlags::Default; diff --git a/src/plugins/ctfvisualizer/ctfvisualizertool.h b/src/plugins/ctfvisualizer/ctfvisualizertool.h index bd31a8042d3..b3f31cbb970 100644 --- a/src/plugins/ctfvisualizer/ctfvisualizertool.h +++ b/src/plugins/ctfvisualizer/ctfvisualizertool.h @@ -46,7 +46,7 @@ private: void toggleThreadRestriction(QAction *action); Utils::Perspective m_perspective{Constants::CtfVisualizerPerspectiveId, - Tr::tr("Chrome Trace Format Visualizer")}; + ::CtfVisualizer::Tr::tr("Chrome Trace Format Visualizer")}; bool m_isLoading; QScopedPointer m_loadJson; diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp index b0da3a13637..05560351597 100644 --- a/src/plugins/debugger/cdb/cdbengine.cpp +++ b/src/plugins/debugger/cdb/cdbengine.cpp @@ -611,9 +611,6 @@ void CdbEngine::shutdownInferior() if (commandsPending()) { showMessage("Cannot shut down inferior due to pending commands.", LogWarning); STATE_DEBUG(state(), Q_FUNC_INFO, __LINE__, "notifyInferiorShutdownFinished") - } else if (!canInterruptInferior()) { - showMessage("Cannot interrupt the inferior.", LogWarning); - STATE_DEBUG(state(), Q_FUNC_INFO, __LINE__, "notifyInferiorShutdownFinished") } else { interruptInferior(); // Calls us again return; @@ -758,11 +755,6 @@ void CdbEngine::doContinueInferior() runCommand({"g", NoFlags}); } -bool CdbEngine::canInterruptInferior() const -{ - return m_effectiveStartMode != AttachToRemoteServer && inferiorPid(); -} - void CdbEngine::interruptInferior() { if (debug) @@ -771,6 +763,18 @@ void CdbEngine::interruptInferior() doInterruptInferior(); } +void CdbEngine::handleDoInterruptInferior(const QString &errorMessage) +{ + if (errorMessage.isEmpty()) { + showMessage("Interrupted " + QString::number(inferiorPid())); + } else { + showMessage(errorMessage, LogError); + notifyInferiorStopFailed(); + } + m_signalOperation->disconnect(this); + m_signalOperation.clear(); +} + void CdbEngine::doInterruptInferior(const InterruptCallback &callback) { const bool requestInterrupt = m_stopMode == NoStopRequested; @@ -787,6 +791,18 @@ void CdbEngine::doInterruptInferior(const InterruptCallback &callback) if (!requestInterrupt) return; // we already requested a stop no need to interrupt twice showMessage(QString("Interrupting process %1...").arg(inferiorPid()), LogMisc); + + QTC_ASSERT(!m_signalOperation, notifyInferiorStopFailed(); return); + if (m_effectiveStartMode != AttachToRemoteServer && device()) { + m_signalOperation = device()->signalOperation(); + if (m_signalOperation) { + connect(m_signalOperation.data(), &DeviceProcessSignalOperation::finished, + this, &CdbEngine::handleDoInterruptInferior); + m_signalOperation->setDebuggerCommand(runParameters().debugger.command.executable()); + m_signalOperation->interruptProcess(inferiorPid()); + return; + } + } m_process.interrupt(); } diff --git a/src/plugins/debugger/cdb/cdbengine.h b/src/plugins/debugger/cdb/cdbengine.h index 4f3786bc631..108cc64d160 100644 --- a/src/plugins/debugger/cdb/cdbengine.h +++ b/src/plugins/debugger/cdb/cdbengine.h @@ -85,6 +85,8 @@ private: void createFullBacktrace(); + void handleDoInterruptInferior(const QString &errorMessage); + typedef QPair SourcePathMapping; struct NormalizedSourceFileName // Struct for caching mapped/normalized source files. { @@ -129,7 +131,6 @@ private: void doContinueInferior(); void parseOutputLine(QString line); bool isCdbProcessRunning() const { return m_process.state() != QProcess::NotRunning; } - bool canInterruptInferior() const; inline void postDisassemblerCommand(quint64 address, DisassemblerAgent *agent); void postDisassemblerCommand(quint64 address, quint64 endAddress, DisassemblerAgent *agent); @@ -176,6 +177,7 @@ private: //! Debugger accessible (expecting commands) bool m_accessible = false; StopMode m_stopMode = NoStopRequested; + ProjectExplorer::DeviceProcessSignalOperation::Ptr m_signalOperation; int m_nextCommandToken = 0; QHash m_commandForToken; QString m_currentBuiltinResponse; diff --git a/src/plugins/diffeditor/unifieddiffeditorwidget.cpp b/src/plugins/diffeditor/unifieddiffeditorwidget.cpp index e182417a831..b977c159af2 100644 --- a/src/plugins/diffeditor/unifieddiffeditorwidget.cpp +++ b/src/plugins/diffeditor/unifieddiffeditorwidget.cpp @@ -77,7 +77,6 @@ UnifiedDiffEditorWidget::~UnifiedDiffEditorWidget() void UnifiedDiffEditorWidget::setDocument(DiffEditorDocument *document) { - m_controller.setBusyShowing(true); m_controller.setDocument(document); clear(); setDiff(document ? document->diffFiles() : QList()); diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 87815a7b3fd..8561764688c 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -113,6 +114,21 @@ private: FilePath m_devicePath; }; +class DockerDeviceFileAccess : public UnixDeviceFileAccess +{ +public: + DockerDeviceFileAccess(DockerDevicePrivate *dev) + : m_dev(dev) + {} + + RunResult runInShell(const CommandLine &cmdLine, + const QByteArray &stdInData) const override; + QString mapToDevicePath(const FilePath &filePath) const override; + OsType osType(const FilePath &filePath) const override; + + DockerDevicePrivate *m_dev = nullptr; +}; + class DockerDevicePrivate : public QObject { public: @@ -125,11 +141,6 @@ public: ~DockerDevicePrivate() { stopCurrentContainer(); } RunResult runInShell(const CommandLine &cmd, const QByteArray &stdInData = {}); - bool runInShellSuccess(const CommandLine &cmd, const QByteArray &stdInData = {}) { - return runInShell(cmd, stdInData).exitCode == 0; - } - - std::optional fileContents(const FilePath &filePath, qint64 limit, qint64 offset); void updateContainerAccess(); void changeMounts(QStringList newMounts); @@ -177,8 +188,8 @@ public: QString m_container; Environment m_cachedEnviroment; - bool m_useFind = true; // prefer find over ls and hacks, but be able to use ls as fallback bool m_isShutdown = false; + DockerDeviceFileAccess m_fileAccess{this}; }; class DockerProcessImpl : public Utils::ProcessInterface @@ -337,9 +348,36 @@ Tasks DockerDevicePrivate::validateMounts() const return result; } +RunResult DockerDeviceFileAccess::runInShell(const CommandLine &cmdLine, + const QByteArray &stdInData) const +{ + QTC_ASSERT(m_dev, return {}); + return m_dev->runInShell(cmdLine, stdInData); +} + +QString DockerDeviceFileAccess::mapToDevicePath(const FilePath &filePath) const +{ + // make sure to convert windows style paths to unix style paths with the file system case: + // C:/dev/src -> /c/dev/src + const FilePath normalized = FilePath::fromString(filePath.path()).normalizedPathName(); + QString path = normalized.path(); + if (HostOsInfo::isWindowsHost() && normalized.startsWithDriveLetter()) { + const QChar lowerDriveLetter = path.at(0); + path = '/' + lowerDriveLetter + path.mid(2); // strip C: + } + return path; +} + +OsType DockerDeviceFileAccess::osType(const FilePath &filePath) const +{ + QTC_ASSERT(m_dev, return UnixDeviceFileAccess::osType(filePath)); + return m_dev->q->osType(); +} + DockerDevice::DockerDevice(DockerSettings *settings, const DockerDeviceData &data) : d(new DockerDevicePrivate(this, settings, data)) { + setFileAccess(&d->m_fileAccess); setDisplayType(Tr::tr("Docker")); setOsType(OsTypeOtherUnix); setDefaultDisplayName(Tr::tr("Docker Image")); @@ -423,7 +461,7 @@ CommandLine DockerDevicePrivate::withDockerExecCmd(const CommandLine &cmd, bool args << m_container; CommandLine dcmd{m_settings->dockerBinaryPath.filePath(), args}; - dcmd.addCommandLineAsArgs(cmd); + dcmd.addCommandLineAsArgs(cmd, CommandLine::Raw); return dcmd; } @@ -448,20 +486,6 @@ void DockerDevicePrivate::stopCurrentContainer() m_cachedEnviroment.clear(); } -static QString getLocalIPv4Address() -{ - const QList addresses = QNetworkInterface::allAddresses(); - for (auto &a : addresses) { - if (a.isInSubnet(QHostAddress("192.168.0.0"), 16)) - return a.toString(); - if (a.isInSubnet(QHostAddress("10.0.0.0"), 8)) - return a.toString(); - if (a.isInSubnet(QHostAddress("172.16.0.0"), 12)) - return a.toString(); - } - return QString(); -} - bool DockerDevicePrivate::prepareForBuild(const Target *target) { QTC_ASSERT(QThread::currentThread() == thread(), return false); @@ -483,18 +507,21 @@ QString escapeMountPathWin(const FilePath &fp) return result; } +QString escapeMountPath(const FilePath &fp) +{ + if (HostOsInfo::isWindowsHost()) + return escapeMountPathWin(fp); + + return escapeMountPathUnix(fp); +} + QStringList toMountArg(const DockerDevicePrivate::TemporaryMountInfo &mi) { QString escapedPath; QString escapedContainerPath; - if (HostOsInfo::isWindowsHost()) { - escapedPath = escapeMountPathWin(mi.path); - escapedContainerPath = escapeMountPathWin(mi.containerPath); - } else { - escapedPath = escapeMountPathUnix(mi.path); - escapedContainerPath = escapeMountPathUnix(mi.containerPath); - } + escapedPath = escapeMountPath(mi.path); + escapedContainerPath = escapeMountPath(mi.containerPath); const QString mountArg = QString(R"(type=bind,"source=%1","destination=%2")") .arg(escapedPath) @@ -505,8 +532,18 @@ QStringList toMountArg(const DockerDevicePrivate::TemporaryMountInfo &mi) bool isValidMountInfo(const DockerDevicePrivate::TemporaryMountInfo &mi) { - return !mi.path.isEmpty() && !mi.containerPath.isEmpty() && mi.path.isAbsolutePath() - && mi.containerPath.isAbsolutePath(); + if (mi.path.needsDevice()) + return false; + + if (mi.path.isEmpty() || mi.containerPath.isEmpty()) + return false; + if (!mi.path.isAbsolutePath() || !mi.containerPath.isAbsolutePath()) + return false; + + if (!mi.path.exists()) + return false; + + return true; } QStringList DockerDevicePrivate::createMountArgs() const @@ -530,7 +567,7 @@ bool DockerDevicePrivate::createContainer() return false; const QString display = HostOsInfo::isLinuxHost() ? QString(":0") - : QString(getLocalIPv4Address() + ":0.0"); + : QString("host.docker.internal:0"); CommandLine dockerCreate{m_settings->dockerBinaryPath.filePath(), {"create", "-i", @@ -744,19 +781,6 @@ FilePath DockerDevice::mapToGlobalPath(const FilePath &pathOnDevice) const // result.setHost(id().toString()); } -QString DockerDevice::mapToDevicePath(const Utils::FilePath &globalPath) const -{ - // make sure to convert windows style paths to unix style paths with the file system case: - // C:/dev/src -> /c/dev/src - const FilePath normalized = FilePath::fromString(globalPath.path()).normalizedPathName(); - QString path = normalized.path(); - if (HostOsInfo::isWindowsHost() && normalized.startsWithDriveLetter()) { - const QChar lowerDriveLetter = path.at(0).toLower(); - path = '/' + lowerDriveLetter + path.mid(2); // strip C: - } - return path; -} - Utils::FilePath DockerDevice::rootPath() const { return FilePath::fromParts(Constants::DOCKER_DEVICE_SCHEME, d->repoAndTag(), u"/"); @@ -778,167 +802,11 @@ bool DockerDevice::handlesFile(const FilePath &filePath) const return false; } -bool DockerDevice::isExecutableFile(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"test", {"-x", path}}); -} - -bool DockerDevice::isReadableFile(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"test", {"-r", path, "-a", "-f", path}}); -} - -bool DockerDevice::isWritableFile(const Utils::FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"test", {"-w", path, "-a", "-f", path}}); -} - -bool DockerDevice::isReadableDirectory(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"test", {"-r", path, "-a", "-d", path}}); -} - -bool DockerDevice::isWritableDirectory(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"test", {"-w", path, "-a", "-d", path}}); -} - -bool DockerDevice::isFile(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"test", {"-f", path}}); -} - -bool DockerDevice::isDirectory(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"test", {"-d", path}}); -} - -bool DockerDevice::createDirectory(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"mkdir", {"-p", path}}); -} - -bool DockerDevice::exists(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"test", {"-e", path}}); -} - -bool DockerDevice::ensureExistingFile(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"touch", {path}}); -} - -bool DockerDevice::removeFile(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - return d->runInShellSuccess({"rm", {filePath.path()}}); -} - -bool DockerDevice::removeRecursively(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - QTC_ASSERT(filePath.path().startsWith('/'), return false); - - const QString path = filePath.cleanPath().path(); - // We are expecting this only to be called in a context of build directories or similar. - // Chicken out in some cases that _might_ be user code errors. - QTC_ASSERT(path.startsWith('/'), return false); - const int levelsNeeded = path.startsWith("/home/") ? 4 : 3; - QTC_ASSERT(path.count('/') >= levelsNeeded, return false); - - return d->runInShellSuccess({"rm", {"-rf", "--", path}}); -} - -bool DockerDevice::copyFile(const FilePath &filePath, const FilePath &target) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - QTC_ASSERT(handlesFile(target), return false); - return d->runInShellSuccess({"cp", {filePath.path(), target.path()}}); -} - -bool DockerDevice::renameFile(const FilePath &filePath, const FilePath &target) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - QTC_ASSERT(handlesFile(target), return false); - return d->runInShellSuccess({"mv", {filePath.path(), target.path()}}); -} - -QDateTime DockerDevice::lastModified(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - const RunResult result = d->runInShell({"stat", {"-L", "-c", "%Y", filePath.path()}}); - qint64 secs = result.stdOut.toLongLong(); - const QDateTime dt = QDateTime::fromSecsSinceEpoch(secs, Qt::UTC); - return dt; -} - -FilePath DockerDevice::symLinkTarget(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - const RunResult result = d->runInShell({"readlink", {"-n", "-e", filePath.path()}}); - const QString out = QString::fromUtf8(result.stdOut); - return out.isEmpty() ? FilePath() : filePath.withNewPath(out); -} - -qint64 DockerDevice::fileSize(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return -1); - const RunResult result = d->runInShell({"stat", {"-L", "-c", "%s", filePath.path()}}); - return result.stdOut.toLongLong(); -} - -QFileDevice::Permissions DockerDevice::permissions(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - - const RunResult result = d->runInShell({"stat", {"-L", "-c", "%a", filePath.path()}}); - const uint bits = result.stdOut.toUInt(nullptr, 8); - QFileDevice::Permissions perm = {}; -#define BIT(n, p) if (bits & (1<ensureReachable(other.parentDir()); } -void DockerDevice::iterateDirectory(const FilePath &filePath, - const FilePath::IterateDirCallback &callBack, - const FileFilter &filter) const -{ - QTC_ASSERT(handlesFile(filePath), return); - auto runInShell = [this](const CommandLine &cmd) { return d->runInShell(cmd); }; - FileUtils::iterateUnixDirectory(filePath, filter, &d->m_useFind, runInShell, callBack); -} - -void DockerDevice::iterateDirectory(const FilePath &filePath, - const FilePath::IterateDirWithInfoCallback &callBack, - const FileFilter &filter) const -{ - QTC_ASSERT(handlesFile(filePath), return); - auto runInShell = [this](const CommandLine &cmd) { return d->runInShell(cmd); }; - FileUtils::iterateUnixDirectory(filePath, filter, &d->m_useFind, runInShell, callBack); -} - -std::optional DockerDevice::fileContents(const FilePath &filePath, - qint64 limit, - qint64 offset) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - return d->fileContents(filePath, limit, offset); -} - -bool DockerDevice::writeFileContents(const FilePath &filePath, - const QByteArray &data, - qint64 offset) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - CommandLine cmd({"dd", {"of=" + filePath.path()}}); - if (offset != 0) { - cmd.addArg("bs=1"); - cmd.addArg(QString("seek=%1").arg(offset)); - } - return d->runInShellSuccess(cmd, data); -} - -FilePathInfo DockerDevice::filePathInfo(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - const RunResult stat = d->runInShell({"stat", {"-L", "-c", "%f %Y %s", filePath.path()}}); - return FileUtils::filePathInfoFromTriple(QString::fromLatin1(stat.stdOut)); -} - Environment DockerDevice::systemEnvironment() const { return d->environment(); @@ -1004,28 +826,6 @@ void DockerDevice::aboutToBeRemoved() const detector.undoAutoDetect(id().toString()); } -std::optional DockerDevicePrivate::fileContents(const FilePath &filePath, - qint64 limit, - qint64 offset) -{ - updateContainerAccess(); - - QStringList args = {"if=" + filePath.path(), "status=none"}; - if (limit > 0 || offset > 0) { - const qint64 gcd = std::gcd(limit, offset); - args += {QString("bs=%1").arg(gcd), - QString("count=%1").arg(limit / gcd), - QString("seek=%1").arg(offset / gcd)}; - } - - const RunResult r = m_shell->runInShell({"dd", args}); - - if (r.exitCode != 0) - return {}; - - return r.stdOut; -} - void DockerDevicePrivate::fetchSystemEnviroment() { updateContainerAccess(); @@ -1277,8 +1077,12 @@ bool DockerDevicePrivate::addTemporaryMount(const FilePath &path, const FilePath if (alreadyAdded) return false; + const TemporaryMountInfo newMount{path, containerPath}; + + QTC_ASSERT(isValidMountInfo(newMount), return false); + qCDebug(dockerDeviceLog) << "Adding temporary mount:" << path; - m_temporaryMounts.append({path, containerPath}); + m_temporaryMounts.append(newMount); stopCurrentContainer(); // Force re-start with new mounts. return true; } diff --git a/src/plugins/docker/dockerdevice.h b/src/plugins/docker/dockerdevice.h index 8398ee4e235..c455185117e 100644 --- a/src/plugins/docker/dockerdevice.h +++ b/src/plugins/docker/dockerdevice.h @@ -79,44 +79,10 @@ public: bool usableAsBuildDevice() const override; Utils::FilePath mapToGlobalPath(const Utils::FilePath &pathOnDevice) const override; - QString mapToDevicePath(const Utils::FilePath &globalPath) const override; Utils::FilePath rootPath() const override; bool handlesFile(const Utils::FilePath &filePath) const override; - bool isExecutableFile(const Utils::FilePath &filePath) const override; - bool isReadableFile(const Utils::FilePath &filePath) const override; - bool isWritableFile(const Utils::FilePath &filePath) const override; - bool isReadableDirectory(const Utils::FilePath &filePath) const override; - bool isWritableDirectory(const Utils::FilePath &filePath) const override; - bool isFile(const Utils::FilePath &filePath) const override; - bool isDirectory(const Utils::FilePath &filePath) const override; - bool createDirectory(const Utils::FilePath &filePath) const override; - bool exists(const Utils::FilePath &filePath) const override; - bool ensureExistingFile(const Utils::FilePath &filePath) const override; - bool removeFile(const Utils::FilePath &filePath) const override; - bool removeRecursively(const Utils::FilePath &filePath) const override; - bool copyFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override; - bool renameFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override; - Utils::FilePath symLinkTarget(const Utils::FilePath &filePath) const override; - void iterateDirectory(const Utils::FilePath &filePath, - const Utils::FilePath::IterateDirCallback &callBack, - const Utils::FileFilter &filter) const override; - void iterateDirectory(const Utils::FilePath &filePath, - const Utils::FilePath::IterateDirWithInfoCallback &callBack, - const Utils::FileFilter &filter) const override; - std::optional fileContents(const Utils::FilePath &filePath, - qint64 limit, - qint64 offset) const override; - bool writeFileContents(const Utils::FilePath &filePath, - const QByteArray &data, - qint64 offset) const override; - Utils::FilePathInfo filePathInfo(const Utils::FilePath &filePath) const override; - QDateTime lastModified(const Utils::FilePath &filePath) const override; - qint64 fileSize(const Utils::FilePath &filePath) const override; - QFileDevice::Permissions permissions(const Utils::FilePath &filePath) const override; - bool setPermissions(const Utils::FilePath &filePath, - QFileDevice::Permissions permissions) const override; bool ensureReachable(const Utils::FilePath &other) const override; Utils::Environment systemEnvironment() const override; diff --git a/src/plugins/docker/dockerdevicewidget.cpp b/src/plugins/docker/dockerdevicewidget.cpp index 26a349a56bb..ce88373306a 100644 --- a/src/plugins/docker/dockerdevicewidget.cpp +++ b/src/plugins/docker/dockerdevicewidget.cpp @@ -81,7 +81,7 @@ DockerDeviceWidget::DockerDeviceWidget(const IDevice::Ptr &device) m_runAsOutsideUser->setToolTip(Tr::tr("Uses user ID and group ID of the user running Qt Creator " "in the docker container.")); m_runAsOutsideUser->setChecked(m_data.useLocalUidGid); - m_runAsOutsideUser->setEnabled(HostOsInfo::isLinuxHost()); + m_runAsOutsideUser->setEnabled(HostOsInfo::isAnyUnixHost()); connect(m_runAsOutsideUser, &QCheckBox::toggled, this, [this, dockerDevice](bool on) { m_data.useLocalUidGid = on; diff --git a/src/plugins/docker/kitdetector.cpp b/src/plugins/docker/kitdetector.cpp index 18df41e8bae..252bd76d23a 100644 --- a/src/plugins/docker/kitdetector.cpp +++ b/src/plugins/docker/kitdetector.cpp @@ -233,7 +233,7 @@ QtVersions KitDetectorPrivate::autoDetectQtVersions() const emit q->logOutput(ProjectExplorer::Tr::tr("Searching for qmake executables...")); - const QStringList candidates = {"qmake-qt6", "qmake-qt5", "qmake"}; + const QStringList candidates = {"qmake6", "qmake-qt6", "qmake-qt5", "qmake"}; for (const FilePath &searchPath : m_searchPaths) { searchPath.iterateDirectory(handleQmake, {candidates, diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp index 50ba5047813..d3ffbac050d 100644 --- a/src/plugins/git/gitclient.cpp +++ b/src/plugins/git/gitclient.cpp @@ -278,7 +278,8 @@ public: : GitBaseDiffEditorController(document, leftCommit, rightCommit) { setReloader([this, extraArgs] { - runCommand({addConfigurationArguments(baseArguments() << extraArgs)}); + runCommand({addConfigurationArguments(baseArguments() << extraArgs)}, + VcsBaseEditor::getCodec(workingDirectory(), {})); }); } }; @@ -413,7 +414,7 @@ public: argLists << addConfigurationArguments(baseArguments() << "--" << unstagedFiles); if (!argLists.isEmpty()) - runCommand(argLists); + runCommand(argLists, VcsBaseEditor::getCodec(workingDirectory(), stagedFiles + unstagedFiles)); }); } }; @@ -1353,9 +1354,10 @@ VcsBaseEditorWidget *GitClient::annotate( editor->setWorkingDirectory(workingDir); QStringList arguments = {"blame", "--root"}; - arguments << argWidget->arguments() << "--" << file; + arguments << argWidget->arguments(); if (!revision.isEmpty()) arguments << revision; + arguments << "--" << file; editor->setDefaultLineNumber(lineNumber); vcsExec(workingDir, arguments, editor); return editor; diff --git a/src/plugins/imageviewer/imageviewer.cpp b/src/plugins/imageviewer/imageviewer.cpp index a448f2fe478..441d33dc6ba 100644 --- a/src/plugins/imageviewer/imageviewer.cpp +++ b/src/plugins/imageviewer/imageviewer.cpp @@ -28,8 +28,9 @@ #include #include #include +#include #include -#include +#include #include using namespace Core; @@ -44,16 +45,17 @@ struct ImageViewerPrivate ImageView *imageView; QWidget *toolbar; - CommandButton *toolButtonExportImage; - CommandButton *toolButtonMultiExportImages; - CommandButton *toolButtonCopyDataUrl; - CommandButton *toolButtonBackground; - CommandButton *toolButtonOutline; - CommandButton *toolButtonFitToScreen; - CommandButton *toolButtonOriginalSize; - CommandButton *toolButtonZoomIn; - CommandButton *toolButtonZoomOut; - CommandButton *toolButtonPlayPause; + QToolButton *shareButton; + CommandAction *actionExportImage; + CommandAction *actionMultiExportImages; + CommandAction *actionButtonCopyDataUrl; + CommandAction *actionBackground; + CommandAction *actionOutline; + CommandAction *actionFitToScreen; + CommandAction *actionOriginalSize; + CommandAction *actionZoomIn; + CommandAction *actionZoomOut; + CommandAction *actionPlayPause; QLabel *labelImageSize; QLabel *labelInfo; }; @@ -63,12 +65,12 @@ struct ImageViewerPrivate from the current theme. Returns \c true if icon is updated, \c false otherwise. */ -static bool updateButtonIconByTheme(QAbstractButton *button, const QString &name) +static bool updateIconByTheme(QAction *action, const QString &name) { QTC_ASSERT(!name.isEmpty(), return false); if (QIcon::hasThemeIcon(name)) { - button->setIcon(QIcon::fromTheme(name)); + action->setIcon(QIcon::fromTheme(name)); return true; } @@ -100,135 +102,127 @@ void ImageViewer::ctor() setDuplicateSupported(true); // toolbar - d->toolbar = new QWidget; + d->toolbar = new StyledBar; - d->toolButtonExportImage = new CommandButton; - d->toolButtonMultiExportImages = new CommandButton; - d->toolButtonCopyDataUrl = new CommandButton; - d->toolButtonBackground = new CommandButton; - d->toolButtonOutline = new CommandButton; - d->toolButtonFitToScreen = new CommandButton; - d->toolButtonOriginalSize = new CommandButton; - d->toolButtonZoomIn = new CommandButton; - d->toolButtonZoomOut = new CommandButton; - d->toolButtonPlayPause = new CommandButton; + d->actionExportImage = new CommandAction(Constants::ACTION_EXPORT_IMAGE, d->toolbar); + d->actionMultiExportImages = new CommandAction(Constants::ACTION_EXPORT_MULTI_IMAGES, + d->toolbar); + d->actionButtonCopyDataUrl = new CommandAction(Constants::ACTION_COPY_DATA_URL, d->toolbar); + d->shareButton = new QToolButton; + d->shareButton->setToolTip(Tr::tr("Export")); + d->shareButton->setPopupMode(QToolButton::InstantPopup); + d->shareButton->setIcon(Icons::EXPORTFILE_TOOLBAR.icon()); + d->shareButton->setProperty("noArrow", true); + auto shareMenu = new QMenu(d->shareButton); + shareMenu->addAction(d->actionExportImage); + shareMenu->addAction(d->actionMultiExportImages); + shareMenu->addAction(d->actionButtonCopyDataUrl); + d->shareButton->setMenu(shareMenu); - d->toolButtonBackground->setCheckable(true); - d->toolButtonBackground->setChecked(settings.showBackground); + d->actionBackground = new CommandAction(Constants::ACTION_BACKGROUND, d->toolbar); + d->actionOutline = new CommandAction(Constants::ACTION_OUTLINE, d->toolbar); + d->actionFitToScreen = new CommandAction(Constants::ACTION_FIT_TO_SCREEN, d->toolbar); + d->actionOriginalSize = new CommandAction(Core::Constants::ZOOM_RESET, d->toolbar); + d->actionZoomIn = new CommandAction(Core::Constants::ZOOM_IN, d->toolbar); + d->actionZoomOut = new CommandAction(Core::Constants::ZOOM_OUT, d->toolbar); + d->actionPlayPause = new CommandAction(Constants::ACTION_TOGGLE_ANIMATION, d->toolbar); - d->toolButtonOutline->setCheckable(true); - d->toolButtonOutline->setChecked(settings.showOutline); + d->actionBackground->setCheckable(true); + d->actionBackground->setChecked(settings.showBackground); - d->toolButtonFitToScreen->setCheckable(true); - d->toolButtonFitToScreen->setChecked(settings.fitToScreen); + d->actionOutline->setCheckable(true); + d->actionOutline->setChecked(settings.showOutline); - d->toolButtonZoomIn->setAutoRepeat(true); + d->actionFitToScreen->setCheckable(true); + d->actionFitToScreen->setChecked(settings.fitToScreen); - d->toolButtonZoomOut->setAutoRepeat(true); + d->actionZoomIn->setAutoRepeat(true); - d->toolButtonExportImage->setToolTipBase(Tr::tr("Export as Image")); - d->toolButtonMultiExportImages->setToolTipBase(Tr::tr("Export Images of Multiple Sizes")); - d->toolButtonOutline->setToolTipBase(Tr::tr("Show Outline")); - d->toolButtonFitToScreen->setToolTipBase(Tr::tr("Fit to Screen")); - d->toolButtonOriginalSize->setToolTipBase(Tr::tr("Original Size")); - d->toolButtonZoomIn->setToolTipBase(Tr::tr("Zoom In")); - d->toolButtonZoomOut->setToolTipBase(Tr::tr("Zoom Out")); + d->actionZoomOut->setAutoRepeat(true); - d->toolButtonExportImage->setIcon(Icons::EXPORTFILE_TOOLBAR.icon()); - d->toolButtonMultiExportImages->setIcon(Icons::MULTIEXPORTFILE_TOOLBAR.icon()); - d->toolButtonCopyDataUrl->setIcon(Icons::COPY_TOOLBAR.icon()); const Icon backgroundIcon({{":/utils/images/desktopdevicesmall.png", Theme::IconsBaseColor}}); - d->toolButtonBackground->setIcon(backgroundIcon.icon()); - d->toolButtonOutline->setIcon(Icons::BOUNDING_RECT.icon()); - d->toolButtonZoomIn->setIcon( - ActionManager::command(Core::Constants::ZOOM_IN)->action()->icon()); - d->toolButtonZoomOut->setIcon( - ActionManager::command(Core::Constants::ZOOM_OUT)->action()->icon()); - d->toolButtonOriginalSize->setIcon( - ActionManager::command(Core::Constants::ZOOM_RESET)->action()->icon()); - d->toolButtonFitToScreen->setIcon(Icons::FITTOVIEW_TOOLBAR.icon()); + d->actionBackground->setIcon(backgroundIcon.icon()); + d->actionOutline->setIcon(Icons::BOUNDING_RECT.icon()); + d->actionZoomIn->setIcon(ActionManager::command(Core::Constants::ZOOM_IN)->action()->icon()); + d->actionZoomOut->setIcon(ActionManager::command(Core::Constants::ZOOM_OUT)->action()->icon()); + d->actionOriginalSize->setIcon( + ActionManager::command(Core::Constants::ZOOM_RESET)->action()->icon()); + d->actionFitToScreen->setIcon(Icons::FITTOVIEW_TOOLBAR.icon()); // icons update - try to use system theme - updateButtonIconByTheme(d->toolButtonFitToScreen, QLatin1String("zoom-fit-best")); + updateIconByTheme(d->actionFitToScreen, QLatin1String("zoom-fit-best")); // a display - something is on the background - updateButtonIconByTheme(d->toolButtonBackground, QLatin1String("video-display")); + updateIconByTheme(d->actionBackground, QLatin1String("video-display")); // "emblem to specify the directory where the user stores photographs" // (photograph has outline - piece of paper) - updateButtonIconByTheme(d->toolButtonOutline, QLatin1String("emblem-photos")); + updateIconByTheme(d->actionOutline, QLatin1String("emblem-photos")); - auto setAsDefaultButton = new QToolButton; - auto setAsDefault = new QAction(Tr::tr("Set as Default"), setAsDefaultButton); - setAsDefault->setToolTip(Tr::tr("Use the current settings for background, outline, and fitting " - "to screen as the default for new image viewers.")); - setAsDefaultButton->setDefaultAction(setAsDefault); - - d->toolButtonExportImage->setCommandId(Constants::ACTION_EXPORT_IMAGE); - d->toolButtonMultiExportImages->setCommandId(Constants::ACTION_EXPORT_MULTI_IMAGES); - d->toolButtonCopyDataUrl->setCommandId(Constants::ACTION_COPY_DATA_URL); - d->toolButtonZoomIn->setCommandId(Core::Constants::ZOOM_IN); - d->toolButtonZoomOut->setCommandId(Core::Constants::ZOOM_OUT); - d->toolButtonOriginalSize->setCommandId(Core::Constants::ZOOM_RESET); - d->toolButtonFitToScreen->setCommandId(Constants::ACTION_FIT_TO_SCREEN); - d->toolButtonBackground->setCommandId(Constants::ACTION_BACKGROUND); - d->toolButtonOutline->setCommandId(Constants::ACTION_OUTLINE); - d->toolButtonPlayPause->setCommandId(Constants::ACTION_TOGGLE_ANIMATION); + auto setAsDefault = new QAction(Tr::tr("Set as Default"), d->toolbar); + const auto updateSetAsDefaultToolTip = [this, setAsDefault] { + const ImageView::Settings settings = d->imageView->settings(); + const QString on = Tr::tr("on"); + const QString off = Tr::tr("off"); + setAsDefault->setToolTip( + "

" + + Tr::tr("Use the current settings for background, outline, and fitting " + "to screen as the default for new image viewers. Current default:") + + "

  • " + Tr::tr("Background: %1").arg(settings.showBackground ? on : off) + + "
  • " + Tr::tr("Outline: %1").arg(settings.showOutline ? on : off) + "
  • " + + Tr::tr("Fit to Screen: %1").arg(settings.fitToScreen ? on : off) + "
"); + }; + updateSetAsDefaultToolTip(); d->labelImageSize = new QLabel; d->labelInfo = new QLabel; + auto bar = new QToolBar; + bar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); + + bar->addWidget(d->shareButton); + bar->addSeparator(); + bar->addAction(d->actionOriginalSize); + bar->addAction(d->actionZoomIn); + bar->addAction(d->actionZoomOut); + bar->addAction(d->actionPlayPause); + bar->addAction(d->actionPlayPause); + bar->addSeparator(); + bar->addAction(d->actionBackground); + bar->addAction(d->actionOutline); + bar->addAction(d->actionFitToScreen); + bar->addAction(setAsDefault); + auto horizontalLayout = new QHBoxLayout(d->toolbar); horizontalLayout->setSpacing(0); horizontalLayout->setContentsMargins(0, 0, 0, 0); - horizontalLayout->addWidget(d->toolButtonExportImage); - horizontalLayout->addWidget(d->toolButtonMultiExportImages); - horizontalLayout->addWidget(d->toolButtonCopyDataUrl); - horizontalLayout->addWidget(new StyledSeparator); - horizontalLayout->addWidget(d->toolButtonBackground); - horizontalLayout->addWidget(d->toolButtonOutline); - horizontalLayout->addWidget(d->toolButtonFitToScreen); - horizontalLayout->addWidget(setAsDefaultButton); - horizontalLayout->addWidget(new StyledSeparator); - horizontalLayout->addWidget(d->toolButtonOriginalSize); - horizontalLayout->addWidget(d->toolButtonZoomIn); - horizontalLayout->addWidget(d->toolButtonZoomOut); - horizontalLayout->addWidget(d->toolButtonPlayPause); - horizontalLayout->addWidget(d->toolButtonPlayPause); - horizontalLayout->addWidget(new StyledSeparator); - horizontalLayout->addItem(new QSpacerItem(315, 20, QSizePolicy::Expanding, QSizePolicy::Minimum)); + horizontalLayout->addWidget(bar); + horizontalLayout->addItem( + new QSpacerItem(315, 20, QSizePolicy::Expanding, QSizePolicy::Minimum)); horizontalLayout->addWidget(new StyledSeparator); horizontalLayout->addWidget(d->labelImageSize); horizontalLayout->addWidget(new StyledSeparator); horizontalLayout->addWidget(d->labelInfo); // connections - connect(d->toolButtonExportImage, &QAbstractButton::clicked, - d->imageView, &ImageView::exportImage); - connect(d->toolButtonMultiExportImages, &QAbstractButton::clicked, - d->imageView, &ImageView::exportMultiImages); - connect(d->toolButtonCopyDataUrl, &QAbstractButton::clicked, - d->imageView, &ImageView::copyDataUrl); - connect(d->toolButtonZoomIn, &QAbstractButton::clicked, - d->imageView, &ImageView::zoomIn); - connect(d->toolButtonZoomOut, &QAbstractButton::clicked, - d->imageView, &ImageView::zoomOut); - connect(d->toolButtonFitToScreen, - &QAbstractButton::toggled, + connect(d->actionExportImage, &QAction::triggered, d->imageView, &ImageView::exportImage); + connect(d->actionMultiExportImages, + &QAction::triggered, d->imageView, - &ImageView::setFitToScreen); + &ImageView::exportMultiImages); + connect(d->actionButtonCopyDataUrl, &QAction::triggered, d->imageView, &ImageView::copyDataUrl); + connect(d->actionZoomIn, &QAction::triggered, d->imageView, &ImageView::zoomIn); + connect(d->actionZoomOut, &QAction::triggered, d->imageView, &ImageView::zoomOut); + connect(d->actionFitToScreen, &QAction::triggered, d->imageView, &ImageView::setFitToScreen); connect(d->imageView, &ImageView::fitToScreenChanged, - d->toolButtonFitToScreen, - &QAbstractButton::setChecked); - connect(d->toolButtonOriginalSize, - &QAbstractButton::clicked, + d->actionFitToScreen, + &QAction::setChecked); + connect(d->actionOriginalSize, + &QAction::triggered, d->imageView, &ImageView::resetToOriginalSize); - connect(d->toolButtonBackground, &QAbstractButton::toggled, - d->imageView, &ImageView::setViewBackground); - connect(d->toolButtonOutline, &QAbstractButton::toggled, - d->imageView, &ImageView::setViewOutline); - connect(d->toolButtonPlayPause, &CommandButton::clicked, - this, &ImageViewer::playToggled); + connect(d->actionBackground, &QAction::toggled, d->imageView, &ImageView::setViewBackground); + connect(d->actionOutline, &QAction::toggled, d->imageView, &ImageView::setViewOutline); + connect(d->actionPlayPause, &QAction::triggered, this, &ImageViewer::playToggled); connect(d->file.data(), &ImageViewerFile::imageSizeChanged, this, &ImageViewer::imageSizeUpdated); connect(d->file.data(), &ImageViewerFile::openFinished, @@ -243,8 +237,9 @@ void ImageViewer::ctor() this, &ImageViewer::updatePauseAction); connect(d->imageView, &ImageView::scaleFactorChanged, this, &ImageViewer::scaleFactorUpdate); - connect(setAsDefault, &QAction::triggered, d->imageView, [this] { + connect(setAsDefault, &QAction::triggered, d->imageView, [this, updateSetAsDefaultToolTip] { d->imageView->writeSettings(ICore::settings()); + updateSetAsDefaultToolTip(); }); } @@ -280,18 +275,18 @@ IEditor *ImageViewer::duplicate() void ImageViewer::exportImage() { if (d->file->type() == ImageViewerFile::TypeSvg) - d->toolButtonExportImage->click(); + d->actionExportImage->trigger(); } void ImageViewer::exportMultiImages() { if (d->file->type() == ImageViewerFile::TypeSvg) - d->toolButtonMultiExportImages->click(); + d->actionMultiExportImages->trigger(); } void ImageViewer::copyDataUrl() { - d->toolButtonCopyDataUrl->click(); + d->actionButtonCopyDataUrl->trigger(); } void ImageViewer::imageSizeUpdated(const QSize &size) @@ -310,45 +305,45 @@ void ImageViewer::scaleFactorUpdate(qreal factor) void ImageViewer::switchViewBackground() { - d->toolButtonBackground->click(); + d->actionBackground->trigger(); } void ImageViewer::switchViewOutline() { - d->toolButtonOutline->click(); + d->actionOutline->trigger(); } void ImageViewer::zoomIn() { - d->toolButtonZoomIn->click(); + d->actionZoomIn->trigger(); } void ImageViewer::zoomOut() { - d->toolButtonZoomOut->click(); + d->actionZoomOut->trigger(); } void ImageViewer::resetToOriginalSize() { - d->toolButtonOriginalSize->click(); + d->actionOriginalSize->trigger(); } void ImageViewer::fitToScreen() { - d->toolButtonFitToScreen->click(); + d->actionFitToScreen->trigger(); } void ImageViewer::updateToolButtons() { const bool isSvg = d->file->type() == ImageViewerFile::TypeSvg; - d->toolButtonExportImage->setEnabled(isSvg); - d->toolButtonMultiExportImages->setEnabled(isSvg); + d->actionExportImage->setEnabled(isSvg); + d->actionMultiExportImages->setEnabled(isSvg); updatePauseAction(); } void ImageViewer::togglePlay() { - d->toolButtonPlayPause->click(); + d->actionPlayPause->trigger(); } void ImageViewer::playToggled() @@ -360,12 +355,12 @@ void ImageViewer::updatePauseAction() { bool isMovie = d->file->type() == ImageViewerFile::TypeMovie; if (isMovie && !d->file->isPaused()) { - d->toolButtonPlayPause->setToolTipBase(Tr::tr("Pause Animation")); - d->toolButtonPlayPause->setIcon(Icons::INTERRUPT_SMALL_TOOLBAR.icon()); + d->actionPlayPause->setToolTipBase(Tr::tr("Pause Animation")); + d->actionPlayPause->setIcon(Icons::INTERRUPT_SMALL_TOOLBAR.icon()); } else { - d->toolButtonPlayPause->setToolTipBase(Tr::tr("Play Animation")); - d->toolButtonPlayPause->setIcon(Icons::RUN_SMALL_TOOLBAR.icon()); - d->toolButtonPlayPause->setEnabled(isMovie); + d->actionPlayPause->setToolTipBase(Tr::tr("Play Animation")); + d->actionPlayPause->setIcon(Icons::RUN_SMALL_TOOLBAR.icon()); + d->actionPlayPause->setEnabled(isMovie); } } diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index be68f933639..d61f3c720a7 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -879,6 +879,7 @@ void Client::deactivateDocument(TextEditor::TextDocument *document) TextEditor::TextEditorWidget *widget = textEditor->editorWidget(); widget->removeHoverHandler(&d->m_hoverHandler); widget->setExtraSelections(TextEditor::TextEditorWidget::CodeSemanticsSelection, {}); + updateEditorToolBar(editor); } } } diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp index 47624ca5f29..d4f5c442642 100644 --- a/src/plugins/languageclient/languageclientmanager.cpp +++ b/src/plugins/languageclient/languageclientmanager.cpp @@ -35,6 +35,7 @@ namespace LanguageClient { static Q_LOGGING_CATEGORY(Log, "qtc.languageclient.manager", QtWarningMsg) static LanguageClientManager *managerInstance = nullptr; +static bool g_shuttingDown = false; LanguageClientManager::LanguageClientManager(QObject *parent) : QObject (parent) @@ -112,7 +113,7 @@ void LanguageClientManager::clientStarted(Client *client) qCDebug(Log) << "client started: " << client->name() << client; QTC_ASSERT(managerInstance, return); QTC_ASSERT(client, return); - if (managerInstance->m_shuttingDown) { + if (g_shuttingDown) { clientFinished(client); return; } @@ -131,7 +132,7 @@ void LanguageClientManager::clientFinished(Client *client) && client->state() != Client::ShutdownRequested; if (unexpectedFinish) { - if (!managerInstance->m_shuttingDown) { + if (!g_shuttingDown) { const QList &clientDocs = managerInstance->m_clientForDocument.keys(client); if (client->reset()) { @@ -153,7 +154,7 @@ void LanguageClientManager::clientFinished(Client *client) } } deleteClient(client); - if (managerInstance->m_shuttingDown && managerInstance->m_clients.isEmpty()) + if (g_shuttingDown && managerInstance->m_clients.isEmpty()) emit managerInstance->shutdownFinished(); } @@ -198,10 +199,10 @@ void LanguageClientManager::shutdownClient(Client *client) if (!client) return; qCDebug(Log) << "request client shutdown: " << client->name() << client; - // reset the documents for that client already when requesting the shutdown so they can get - // reassigned to another server right after this request to another server + // reset and deactivate the documents for that client by assigning a null client already when + // requesting the shutdown so they can get reassigned to another server right after this request for (TextEditor::TextDocument *document : managerInstance->m_clientForDocument.keys(client)) - managerInstance->m_clientForDocument.remove(document); + openDocumentWithClient(document, nullptr); if (client->reachable()) client->shutdown(); else if (client->state() != Client::Shutdown && client->state() != Client::ShutdownRequested) @@ -218,17 +219,17 @@ void LanguageClientManager::deleteClient(Client *client) for (QList &clients : managerInstance->m_clientsForSetting) clients.removeAll(client); client->deleteLater(); - if (!managerInstance->m_shuttingDown) + if (!g_shuttingDown) emit instance()->clientRemoved(client); } void LanguageClientManager::shutdown() { QTC_ASSERT(managerInstance, return); - if (managerInstance->m_shuttingDown) + if (g_shuttingDown) return; qCDebug(Log) << "shutdown manager"; - managerInstance->m_shuttingDown = true; + g_shuttingDown = true; const auto clients = managerInstance->clients(); for (Client *client : clients) shutdownClient(client); @@ -242,7 +243,7 @@ void LanguageClientManager::shutdown() bool LanguageClientManager::isShuttingDown() { - return managerInstance->m_shuttingDown; + return g_shuttingDown; } LanguageClientManager *LanguageClientManager::instance() @@ -408,6 +409,7 @@ void LanguageClientManager::openDocumentWithClient(TextEditor::TextDocument *doc Client *currentClient = clientForDocument(document); if (client == currentClient) return; + managerInstance->m_clientForDocument.remove(document); if (currentClient) currentClient->deactivateDocument(document); managerInstance->m_clientForDocument[document] = client; diff --git a/src/plugins/languageclient/languageclientmanager.h b/src/plugins/languageclient/languageclientmanager.h index 9e3aa0b30c1..7ce055f1fb2 100644 --- a/src/plugins/languageclient/languageclientmanager.h +++ b/src/plugins/languageclient/languageclientmanager.h @@ -100,7 +100,6 @@ private: QList reachableClients(); - bool m_shuttingDown = false; QList m_clients; QList m_currentSettings; // owned QMap> m_clientsForSetting; diff --git a/src/plugins/mcusupport/mcupackage.cpp b/src/plugins/mcusupport/mcupackage.cpp index 4deda660503..f4e301354b1 100644 --- a/src/plugins/mcusupport/mcupackage.cpp +++ b/src/plugins/mcusupport/mcupackage.cpp @@ -38,15 +38,13 @@ McuPackage::McuPackage(const SettingsHandler::Ptr &settingsHandler, const QStringList &versions, const QString &downloadUrl, const McuPackageVersionDetector *versionDetector, - const bool addToSystemPath, - const FilePath &relativePathModifier) + const bool addToSystemPath) : settingsHandler(settingsHandler) , m_label(label) , m_defaultPath(settingsHandler->getPath(settingsKey, QSettings::SystemScope, defaultPath)) , m_detectionPath(detectionPath) , m_settingsKey(settingsKey) , m_versionDetector(versionDetector) - , m_relativePathModifier(relativePathModifier) , m_versions(versions) , m_cmakeVariableName(cmakeVarName) , m_environmentVariableName(envVarName) @@ -101,7 +99,7 @@ FilePath McuPackage::basePath() const FilePath McuPackage::path() const { - return (basePath() / m_relativePathModifier.path()).cleanPath(); + return basePath().cleanPath(); } FilePath McuPackage::defaultPath() const @@ -230,7 +228,7 @@ bool McuPackage::writeToSettings() const QWidget *McuPackage::widget() { auto *widget = new QWidget; - m_fileChooser = new PathChooser; + m_fileChooser = new PathChooser(widget); m_fileChooser->lineEdit()->setButtonIcon(FancyLineEdit::Right, Icons::RESET.icon()); m_fileChooser->lineEdit()->setButtonVisible(FancyLineEdit::Right, true); connect(m_fileChooser->lineEdit(), &FancyLineEdit::rightButtonClicked, this, [&] { @@ -239,10 +237,10 @@ QWidget *McuPackage::widget() auto layout = new QGridLayout(widget); layout->setContentsMargins(0, 0, 0, 0); - m_infoLabel = new InfoLabel(); + m_infoLabel = new InfoLabel(widget); if (!m_downloadUrl.isEmpty()) { - auto downLoadButton = new QToolButton; + auto downLoadButton = new QToolButton(widget); downLoadButton->setIcon(Icons::ONLINE.icon()); downLoadButton->setToolTip(tr("Download from \"%1\"").arg(m_downloadUrl)); QObject::connect(downLoadButton, &QToolButton::pressed, this, [this] { diff --git a/src/plugins/mcusupport/mcupackage.h b/src/plugins/mcusupport/mcupackage.h index 5d336b782fb..84abac0b9cb 100644 --- a/src/plugins/mcusupport/mcupackage.h +++ b/src/plugins/mcusupport/mcupackage.h @@ -40,8 +40,7 @@ public: const QStringList &versions = {}, const QString &downloadUrl = {}, const McuPackageVersionDetector *versionDetector = nullptr, - const bool addToPath = false, - const Utils::FilePath &relativePathModifier = Utils::FilePath()); + const bool addToPath = false); ~McuPackage() override = default; @@ -87,7 +86,6 @@ private: QScopedPointer m_versionDetector; Utils::FilePath m_path; - Utils::FilePath m_relativePathModifier; // relative path to m_path to be returned by path() QString m_detectedVersion; QStringList m_versions; const QString m_cmakeVariableName; diff --git a/src/plugins/mcusupport/mcusupportoptionspage.cpp b/src/plugins/mcusupport/mcusupportoptionspage.cpp index 52e3a6ee434..704ca9af5d2 100644 --- a/src/plugins/mcusupport/mcusupportoptionspage.cpp +++ b/src/plugins/mcusupport/mcusupportoptionspage.cpp @@ -86,7 +86,7 @@ McuSupportOptionsWidget::McuSupportOptionsWidget(McuSupportOptions &options, } { - m_qtForMCUsSdkGroupBox = new QGroupBox(m_options.qtForMCUsSdkPackage->label()); + m_qtForMCUsSdkGroupBox = new QGroupBox(tr("Qt for MCUs SDK")); m_qtForMCUsSdkGroupBox->setFlat(true); auto *layout = new QVBoxLayout(m_qtForMCUsSdkGroupBox); layout->addWidget(m_options.qtForMCUsSdkPackage->widget()); @@ -251,15 +251,13 @@ void McuSupportOptionsWidget::showMcuTargetPackages() return; while (m_packagesLayout->rowCount() > 0) { - QFormLayout::TakeRowResult row = m_packagesLayout->takeRow(0); - row.labelItem->widget()->hide(); - row.fieldItem->widget()->hide(); + m_packagesLayout->removeRow(0); } - for (const auto &package : std::as_const(m_options.sdkRepository.packages)) { - QWidget *packageWidget = package->widget(); - if (!mcuTarget->packages().contains(package) || package->label().isEmpty()) + for (const auto &package : mcuTarget->packages()) { + if (package->label().isEmpty()) continue; + QWidget *packageWidget = package->widget(); m_packagesLayout->addRow(package->label(), packageWidget); packageWidget->show(); } diff --git a/src/plugins/mcusupport/mcusupportsdk.cpp b/src/plugins/mcusupport/mcusupportsdk.cpp index 27d633771af..afa1d4d4884 100644 --- a/src/plugins/mcusupport/mcusupportsdk.cpp +++ b/src/plugins/mcusupport/mcusupportsdk.cpp @@ -361,15 +361,12 @@ McuPackagePtr createStm32CubeProgrammerPackage(const SettingsHandler::Ptr &setti { FilePath defaultPath; const QString cubePath = "STMicroelectronics/STM32Cube/STM32CubeProgrammer"; - if (HostOsInfo::isWindowsHost()) { - const FilePath programPath = findInProgramFiles(cubePath); - if (!programPath.isEmpty()) - defaultPath = programPath; - } else { - const FilePath programPath = FileUtils::homePath() / cubePath; - if (programPath.exists()) - defaultPath = programPath; - } + if (HostOsInfo::isWindowsHost()) + defaultPath = findInProgramFiles(cubePath) / "bin"; + else + defaultPath = FileUtils::homePath() / cubePath / "bin"; + if (!defaultPath.exists()) + FilePath defaultPath = {}; const FilePath detectionPath = FilePath::fromUserInput( QLatin1String(Utils::HostOsInfo::isWindowsHost() ? "/bin/STM32_Programmer_CLI.exe" @@ -386,8 +383,7 @@ McuPackagePtr createStm32CubeProgrammerPackage(const SettingsHandler::Ptr &setti {}, // versions "https://www.st.com/en/development-tools/stm32cubeprog.html", // download url nullptr, // version detector - true, // add to path - "/bin" // relative path modifier + true // add to path )}; } @@ -542,7 +538,7 @@ static McuAbstractTargetFactory::Ptr createFactory(bool isLegacy, {"arm-greenhills", McuPackagePtr{new McuPackage{settingsHandler, {}, - toolchainFilePrefix / "arm-ghs.cmake", + toolchainFilePrefix / "ghs-arm.cmake", {}, {}, Legacy::Constants::TOOLCHAIN_FILE_CMAKE_VARIABLE, diff --git a/src/plugins/mcusupport/test/armgcc_stm32f469i_discovery_baremetal_json.h b/src/plugins/mcusupport/test/armgcc_stm32f469i_discovery_baremetal_json.h index 763e039b548..27f8a85d96d 100644 --- a/src/plugins/mcusupport/test/armgcc_stm32f469i_discovery_baremetal_json.h +++ b/src/plugins/mcusupport/test/armgcc_stm32f469i_discovery_baremetal_json.h @@ -18,6 +18,7 @@ constexpr auto armgcc_stm32f469i_discovery_baremetal_json = R"( "id": "STM32CubeProgrammer_PATH", "label": "STM32CubeProgrammer", "type": "path", + "setting": "Stm32CubeProgrammer", "defaultValue": { "windows": "%{Env:PROGRAMSANDFILES}/STMicroelectronics/STM32Cube/STM32CubeProgrammer/", "unix": "%{Env:HOME}/STMicroelectronics/STM32Cube/STM32CubeProgrammer/" diff --git a/src/plugins/mcusupport/test/ghs_rh850_d1m1a_baremetal_json.h b/src/plugins/mcusupport/test/ghs_rh850_d1m1a_baremetal_json.h index bac50527196..40127e894f0 100644 --- a/src/plugins/mcusupport/test/ghs_rh850_d1m1a_baremetal_json.h +++ b/src/plugins/mcusupport/test/ghs_rh850_d1m1a_baremetal_json.h @@ -17,14 +17,15 @@ constexpr auto ghs_rh850_d1m1a_baremetal_json = R"( { "id": "FlashProgrammer_path", "setting": "FlashProgrammerPath", - "label": "Path to Renesas Flash Programmer", + "label": "Renesas Flash Programmer", "type": "path", + "setting": "RenesasFlashProgrammer", "cmakeVar": "RENESAS_FLASH_PROGRAMMER_PATH", "defaultValue": { "windows": "%{Env:PROGRAMSANDFILES}/Renesas Electronics/Programming Tools/Renesas Flash Programmer V3.09", "unix": "%{Env:HOME}" }, - "envVar": "RenesasFlashProgrammer_PATH", + "envVar": "RENESAS_FLASH_PROGRAMMER_PATH", "optional": true, "addToSystemPath": true } diff --git a/src/plugins/mcusupport/test/iar_stm32f469i_discovery_baremetal_json.h b/src/plugins/mcusupport/test/iar_stm32f469i_discovery_baremetal_json.h index 74f1012f61c..6b1edf70b0d 100644 --- a/src/plugins/mcusupport/test/iar_stm32f469i_discovery_baremetal_json.h +++ b/src/plugins/mcusupport/test/iar_stm32f469i_discovery_baremetal_json.h @@ -18,6 +18,7 @@ constexpr auto iar_stm32f469i_discovery_baremetal_json = R"( "id": "STM32CubeProgrammer_PATH", "label": "STM32CubeProgrammer", "type": "path", + "setting": "Stm32CubeProgrammer", "defaultValue": { "windows": "%{Env:PROGRAMSANDFILES}/STMicroelectronics/STM32Cube/STM32CubeProgrammer/", "unix": "%{Env:HOME}/STMicroelectronics/STM32Cube/STM32CubeProgrammer/" diff --git a/src/plugins/mcusupport/test/unittest.cpp b/src/plugins/mcusupport/test/unittest.cpp index 64301f3009d..016b9eb7805 100644 --- a/src/plugins/mcusupport/test/unittest.cpp +++ b/src/plugins/mcusupport/test/unittest.cpp @@ -3,6 +3,7 @@ #include "unittest.h" +#include "armgcc_ek_ra6m3g_baremetal_json.h" #include "armgcc_mimxrt1050_evk_freertos_json.h" #include "armgcc_mimxrt1064_evk_freertos_json.h" #include "armgcc_mimxrt1170_evk_freertos_json.h" @@ -11,6 +12,7 @@ #include "errors_json.h" #include "gcc_desktop_json.h" #include "ghs_rh850_d1m1a_baremetal_json.h" +#include "ghs_tviic2d6m_baremetal_json.h" #include "iar_mimxrt1064_evk_freertos_json.h" #include "iar_stm32f469i_discovery_baremetal_json.h" #include "msvc_desktop_json.h" @@ -123,6 +125,43 @@ const char vendor[]{"target_vendor"}; const QString settingsPrefix = QLatin1String(Constants::SETTINGS_GROUP) + '/' + QLatin1String(Constants::SETTINGS_KEY_PACKAGE_PREFIX); +const char defaultToolPath[]{"/opt/biz/foo"}; +const char xpressoIdePath[]{"/usr/local/mcuxpressoide"}; +const char xpressoIdeLabel[]{"MCUXpresso IDE"}; +const char xpressoIdeSetting[]{"MCUXpressoIDE"}; +const char xpressoIdeCmakeVar[]{"MCUXPRESSO_IDE_PATH"}; +const char xpressoIdeEnvVar[]{"MCUXpressoIDE_PATH"}; +const char xpressoIdeDetectionPath[]{"ide/binaries/crt_emu_cm_redlink"}; + +const char stmCubeProgrammerSetting[]{"Stm32CubeProgrammer"}; +const char stmCubeProgrammerLabel[]{"STM32CubeProgrammer"}; +const QString stmCubeProgrammerPath{QString{defaultToolPath} + "/bin"}; +const QString stmCubeProgrammerDetectionPath{"/bin/STM32_Programmer.sh"}; + +const char renesasProgrammerSetting[]{"RenesasFlashProgrammer"}; +const char renesasProgrammerCmakeVar[]{"RENESAS_FLASH_PROGRAMMER_PATH"}; +const QString renesasProgrammerEnvVar{renesasProgrammerCmakeVar}; +const char renesasProgrammerLabel[]{"Renesas Flash Programmer"}; +const char renesasProgrammerDetectionPath[]{"rfp-cli"}; + +const char renesasE2StudioCmakeVar[]{"EK_RA6M3G_E2_PROJECT_PATH"}; +const char renesasE2StudioDefaultPath[]{"%{Env:HOME}/e2_studio/workspace"}; +const QString renesasE2StudioPath{(FileUtils::homePath() / "/e2_studio/workspace").toUserOutput()}; +const char renesasE2StudioLabel[]{"Path to project for Renesas e2 Studio"}; +const char renesasE2StudioSetting[]{"RenesasE2StudioPath"}; + +const char cypressProgrammerSetting[]{"CypressAutoFlashUtil"}; +const char cypressProgrammerCmakeVar[]{"INFINEON_AUTO_FLASH_UTILITY_DIR"}; +const char cypressProgrammerEnvVar[]{"CYPRESS_AUTO_FLASH_UTILITY_DIR"}; +const char cypressProgrammerLabel[]{"Cypress Auto Flash Utility"}; +const char cypressProgrammerDetectionPath[]{"/bin/openocd"}; + +const char jlinkPath[]{"/opt/SEGGER/JLink"}; +const char jlinkSetting[]{"JLinkPath"}; +const char jlinkCmakeVar[]{"JLINK_PATH"}; +const char jlinkEnvVar[]{"JLINK_PATH"}; +const char jlinkLabel[]{"Path to SEGGER J-Link"}; + const QString unsupportedToolchainFilePath = QString{qtForMcuSdkPath} + "/lib/cmake/Qul/toolchain/unsupported.cmake"; @@ -282,6 +321,7 @@ void verifyPackage(const McuPackagePtr &package, const QString &cmakeVar, const QString &envVar, const QString &label, + const QString &detectionPath, const QStringList &versions) { QVERIFY(package); @@ -290,6 +330,7 @@ void verifyPackage(const McuPackagePtr &package, QCOMPARE(package->cmakeVariableName(), cmakeVar); QCOMPARE(package->environmentVariableName(), envVar); QCOMPARE(package->label(), label); + QCOMPARE(package->detectionPath().toString(), detectionPath); QCOMPARE(package->settingsKey(), setting); QCOMPARE(package->versions(), versions); } @@ -928,11 +969,12 @@ void McuSupportTest::test_createTargetWithToolchainPackages() verifyPackage(qtForMCUsSDK, qtForMcuSdkPath, - {}, // qtForMcuSdkPath + {}, Constants::SETTINGS_KEY_PACKAGE_QT_FOR_MCUS_SDK, QUL_CMAKE_VAR, QUL_ENV_VAR, QUL_LABEL, + {}, {}); verifyTargetToolchains(targets, @@ -1372,76 +1414,164 @@ void McuSupportTest::test_resolveCmakeVariablesInDefaultPath() void McuSupportTest::test_legacy_createThirdPartyPackage_data() { - const QString defaultToolPath{"/opt/biz/foo"}; - - const char xpressoIdeSetting[]{"MCUXpressoIDE"}; - const char xpressoIdeCmakeVar[]{"MCUXPRESSO_IDE_PATH"}; - const char xpressoIdeEnvVar[]{"MCUXpressoIDE_PATH"}; - - const char stmCubeProgrammerSetting[]{"Stm32CubeProgrammer"}; - const QString stmCubeProgrammerPath{defaultToolPath + "/bin"}; - - const char renesasProgrammerSetting[]{"RenesasFlashProgrammer"}; - const char renesasProgrammerCmakeVar[]{"RENESAS_FLASH_PROGRAMMER_PATH"}; - const QString renesasProgrammerEnvVar{renesasProgrammerCmakeVar}; - - const char cypressProgrammerSetting[]{"CypressAutoFlashUtil"}; - const char cypressProgrammerCmakeVar[]{"INFINEON_AUTO_FLASH_UTILITY_DIR"}; - const char cypressProgrammerEnvVar[]{"CYPRESS_AUTO_FLASH_UTILITY_DIR"}; - QTest::addColumn("creator"); + QTest::addColumn("json"); QTest::addColumn("path"); QTest::addColumn("defaultPath"); QTest::addColumn("setting"); QTest::addColumn("cmakeVar"); QTest::addColumn("envVar"); + QTest::addColumn("label"); + QTest::addColumn("detectionPath"); - QTest::newRow("mcuXpresso") << PackageCreator{[this]() { - return Legacy::createMcuXpressoIdePackage(settingsMockPtr); - }} << defaultToolPath << defaultToolPath - << xpressoIdeSetting << xpressoIdeCmakeVar << xpressoIdeEnvVar; - QTest::newRow("stmCubeProgrammer") << PackageCreator{[this]() { - return Legacy::createStm32CubeProgrammerPackage(settingsMockPtr); - }} << stmCubeProgrammerPath << defaultToolPath - << stmCubeProgrammerSetting << empty << empty; + QTest::newRow("armgcc_mimxrt1050_evk_freertos_json mcuXpresso") + << PackageCreator{[this]() { return Legacy::createMcuXpressoIdePackage(settingsMockPtr); }} + << armgcc_mimxrt1050_evk_freertos_json << xpressoIdePath << xpressoIdePath + << xpressoIdeSetting << xpressoIdeCmakeVar << xpressoIdeEnvVar << xpressoIdeLabel + << xpressoIdeDetectionPath; - QTest::newRow("renesasProgrammer") << PackageCreator{[this]() { - return Legacy::createRenesasProgrammerPackage(settingsMockPtr); - }} << defaultToolPath << defaultToolPath - << renesasProgrammerSetting << renesasProgrammerCmakeVar - << renesasProgrammerEnvVar; + QTest::newRow("armgcc_mimxrt1064_evk_freertos_json mcuXpresso") + << PackageCreator{[this]() { return Legacy::createMcuXpressoIdePackage(settingsMockPtr); }} + << armgcc_mimxrt1064_evk_freertos_json << xpressoIdePath << xpressoIdePath + << xpressoIdeSetting << xpressoIdeCmakeVar << xpressoIdeEnvVar << xpressoIdeLabel + << xpressoIdeDetectionPath; - QTest::newRow("cypressProgrammer") << PackageCreator{[this]() { - return Legacy::createCypressProgrammerPackage(settingsMockPtr); - }} << defaultToolPath << defaultToolPath - << cypressProgrammerSetting << cypressProgrammerCmakeVar - << cypressProgrammerEnvVar; + QTest::newRow("armgcc_mimxrt1170_evk_freertos_json mcuXpresso") + << PackageCreator{[this]() { return Legacy::createMcuXpressoIdePackage(settingsMockPtr); }} + << armgcc_mimxrt1170_evk_freertos_json << xpressoIdePath << xpressoIdePath + << xpressoIdeSetting << xpressoIdeCmakeVar << xpressoIdeEnvVar << xpressoIdeLabel + << xpressoIdeDetectionPath; + + QTest::newRow("armgcc_stm32h750b_discovery_baremetal_json stmCubeProgrammer") + << PackageCreator{[this]() { + return Legacy::createStm32CubeProgrammerPackage(settingsMockPtr); + }} + << armgcc_stm32h750b_discovery_baremetal_json << stmCubeProgrammerPath + << stmCubeProgrammerPath << stmCubeProgrammerSetting << empty << empty + << stmCubeProgrammerLabel << stmCubeProgrammerDetectionPath; + QTest::newRow("armgcc_stm32f769i_discovery_freertos_json stmCubeProgrammer") + << PackageCreator{[this]() { + return Legacy::createStm32CubeProgrammerPackage(settingsMockPtr); + }} + << armgcc_stm32f769i_discovery_freertos_json << stmCubeProgrammerPath + << stmCubeProgrammerPath << stmCubeProgrammerSetting << empty << empty + << stmCubeProgrammerLabel << stmCubeProgrammerDetectionPath; + QTest::newRow("ghs_rh850_d1m1a_baremetal_json renesasProgrammer") + << PackageCreator{[this]() { + return Legacy::createRenesasProgrammerPackage(settingsMockPtr); + }} + << ghs_rh850_d1m1a_baremetal_json << defaultToolPath << defaultToolPath + << renesasProgrammerSetting << renesasProgrammerCmakeVar << renesasProgrammerEnvVar + << renesasProgrammerLabel << renesasProgrammerDetectionPath; } void McuSupportTest::test_legacy_createThirdPartyPackage() { QFETCH(PackageCreator, creator); + QFETCH(QString, json); QFETCH(QString, path); QFETCH(QString, defaultPath); QFETCH(QString, setting); QFETCH(QString, cmakeVar); QFETCH(QString, envVar); - - if (!envVar.isEmpty()) - QVERIFY(qputenv(envVar.toLocal8Bit(), defaultPath.toLocal8Bit())); + QFETCH(QString, label); + QFETCH(QString, detectionPath); EXPECT_CALL(*settingsMockPtr, getPath(QString{setting}, _, _)) .Times(2) .WillRepeatedly(Return(FilePath::fromUserInput(defaultPath))); - McuPackagePtr thirdPartyPacakge{creator()}; - QVERIFY(thirdPartyPacakge); - QCOMPARE(thirdPartyPacakge->settingsKey(), setting); - QCOMPARE(thirdPartyPacakge->environmentVariableName(), envVar); - QCOMPARE(thirdPartyPacakge->path().toString(), path); + McuPackagePtr thirdPartyPackage{creator()}; + verifyPackage(thirdPartyPackage, + path, + defaultPath, + setting, + cmakeVar, + envVar, + label, + detectionPath, + {}); +} - if (!envVar.isEmpty()) - QVERIFY(qunsetenv(envVar.toLocal8Bit())); +void McuSupportTest::test_createThirdPartyPackage_data() +{ + test_legacy_createThirdPartyPackage_data(); +} + +void McuSupportTest::test_createThirdPartyPackage() +{ + QFETCH(QString, json); + QFETCH(QString, path); + QFETCH(QString, defaultPath); + QFETCH(QString, setting); + QFETCH(QString, cmakeVar); + QFETCH(QString, envVar); + QFETCH(QString, label); + + McuTargetDescription targetDescription{parseDescriptionJson(json.toLocal8Bit())}; + + EXPECT_CALL(*settingsMockPtr, getPath(QString{setting}, QSettings::SystemScope, _)) + .Times(testing::AtMost(1)) + .WillOnce(Return(FilePath::fromUserInput(defaultPath))); + + EXPECT_CALL(*settingsMockPtr, getPath(QString{setting}, QSettings::UserScope, _)) + .Times(testing::AtMost(1)) + .WillOnce(Return(FilePath::fromUserInput(path))); + + auto [targets, packages] = targetFactory.createTargets(targetDescription, sdkPackagePtr); + + auto thirdPartyPackage = findOrDefault(packages, [&setting](const McuPackagePtr &pkg) { + return (pkg->settingsKey() == setting); + }); + + verifyPackage(thirdPartyPackage, path, defaultPath, setting, cmakeVar, envVar, label, {}, {}); +} + +void McuSupportTest::test_legacy_createCypressProgrammer3rdPartyPackage() +{ + EXPECT_CALL(*settingsMockPtr, getPath(QString{cypressProgrammerSetting}, _, _)) + .Times(2) + .WillRepeatedly(Return(FilePath::fromUserInput(defaultToolPath))); + + McuPackagePtr thirdPartyPackage{Legacy::createCypressProgrammerPackage(settingsMockPtr)}; + verifyPackage(thirdPartyPackage, + defaultToolPath, + defaultToolPath, + cypressProgrammerSetting, + cypressProgrammerCmakeVar, + cypressProgrammerEnvVar, + cypressProgrammerLabel, + cypressProgrammerDetectionPath, + {}); +} + +void McuSupportTest::test_createJLink3rdPartyPackage() +{ + McuTargetDescription targetDescription{parseDescriptionJson(armgcc_ek_ra6m3g_baremetal_json)}; + + EXPECT_CALL(*settingsMockPtr, getPath(QString{jlinkSetting}, QSettings::SystemScope, _)) + .Times(testing::AtMost(1)) + .WillOnce(Return(FilePath::fromUserInput(jlinkPath))); + + EXPECT_CALL(*settingsMockPtr, getPath(QString{jlinkSetting}, QSettings::UserScope, _)) + .Times(testing::AtMost(1)) + .WillOnce(Return(FilePath::fromUserInput(jlinkPath))); + + auto [targets, packages] = targetFactory.createTargets(targetDescription, sdkPackagePtr); + + auto thirdPartyPackage = findOrDefault(packages, [](const McuPackagePtr &pkg) { + return (pkg->settingsKey() == jlinkSetting); + }); + + verifyPackage(thirdPartyPackage, + jlinkPath, + jlinkPath, + jlinkSetting, + jlinkCmakeVar, + jlinkEnvVar, + jlinkLabel, + {}, + {}); } void McuSupportTest::test_defaultValueForEachOperationSystem() diff --git a/src/plugins/mcusupport/test/unittest.h b/src/plugins/mcusupport/test/unittest.h index be46281beba..1752f52f1be 100644 --- a/src/plugins/mcusupport/test/unittest.h +++ b/src/plugins/mcusupport/test/unittest.h @@ -94,6 +94,10 @@ private slots: void test_legacy_createThirdPartyPackage_data(); void test_legacy_createThirdPartyPackage(); + void test_createThirdPartyPackage_data(); + void test_createThirdPartyPackage(); + void test_legacy_createCypressProgrammer3rdPartyPackage(); + void test_createJLink3rdPartyPackage(); void test_defaultValueForEachOperationSystem(); void test_addToSystemPathFlag(); diff --git a/src/plugins/mesonprojectmanager/mesonactionsmanager.h b/src/plugins/mesonprojectmanager/mesonactionsmanager.h index 8cf00599f2c..55c1dd51814 100644 --- a/src/plugins/mesonprojectmanager/mesonactionsmanager.h +++ b/src/plugins/mesonprojectmanager/mesonactionsmanager.h @@ -14,7 +14,8 @@ class MesonActionsManager : public QObject { Q_OBJECT Utils::ParameterAction buildTargetContextAction{ - Tr::tr("Build"), Tr::tr("Build \"%1\""), + ::MesonProjectManager::Tr::tr("Build"), + ::MesonProjectManager::Tr::tr("Build \"%1\""), Utils::ParameterAction::AlwaysEnabled /*handled manually*/ }; QAction configureActionMenu; diff --git a/src/plugins/modeleditor/modeleditor_plugin.cpp b/src/plugins/modeleditor/modeleditor_plugin.cpp index 86ed69757e1..bcaf8794f0d 100644 --- a/src/plugins/modeleditor/modeleditor_plugin.cpp +++ b/src/plugins/modeleditor/modeleditor_plugin.cpp @@ -83,7 +83,6 @@ void ModelEditorPlugin::extensionsInitialized() ExtensionSystem::IPlugin::ShutdownFlag ModelEditorPlugin::aboutToShutdown() { d->settingsController.save(Core::ICore::settings()); - QApplication::clipboard()->clear(); return SynchronousShutdown; } diff --git a/src/plugins/perfprofiler/perfprofilertool.h b/src/plugins/perfprofiler/perfprofilertool.h index bfa6598ca0a..4face4dfe5e 100644 --- a/src/plugins/perfprofiler/perfprofilertool.h +++ b/src/plugins/perfprofiler/perfprofilertool.h @@ -79,7 +79,7 @@ private: void finalize(); Utils::Perspective m_perspective{Constants::PerfProfilerPerspectiveId, - Tr::tr("Performance Analyzer")}; + ::PerfProfiler::Tr::tr("Performance Analyzer")}; QAction *m_startAction = nullptr; QAction *m_stopAction = nullptr; diff --git a/src/plugins/projectexplorer/customtoolchain.cpp b/src/plugins/projectexplorer/customtoolchain.cpp index 2d311b43604..4c2904d3cd6 100644 --- a/src/plugins/projectexplorer/customtoolchain.cpp +++ b/src/plugins/projectexplorer/customtoolchain.cpp @@ -131,13 +131,14 @@ ToolChain::BuiltInHeaderPathsRunner CustomToolChain::createBuiltInHeaderPathsRun void CustomToolChain::addToEnvironment(Environment &env) const { - if (!m_compilerCommand.isEmpty()) { - const FilePath path = m_compilerCommand.parentDir(); - env.prependOrSetPath(path); - const FilePath makePath = m_makeCommand.parentDir(); - if (makePath != path) - env.prependOrSetPath(makePath); - } + const FilePath compiler = compilerCommand(); + if (compiler.isEmpty()) + return; + const FilePath path = compiler.parentDir(); + env.prependOrSetPath(path); + const FilePath makePath = m_makeCommand.parentDir(); + if (makePath != path) + env.prependOrSetPath(makePath); } QStringList CustomToolChain::suggestedMkspecList() const @@ -252,8 +253,7 @@ bool CustomToolChain::operator ==(const ToolChain &other) const return false; auto customTc = static_cast(&other); - return m_compilerCommand == customTc->m_compilerCommand - && m_makeCommand == customTc->m_makeCommand + return m_makeCommand == customTc->m_makeCommand && targetAbi() == customTc->targetAbi() && m_predefinedMacros == customTc->m_predefinedMacros && m_builtInHeaderPaths == customTc->m_builtInHeaderPaths; diff --git a/src/plugins/projectexplorer/customtoolchain.h b/src/plugins/projectexplorer/customtoolchain.h index d05e4c9998b..38a529d1b36 100644 --- a/src/plugins/projectexplorer/customtoolchain.h +++ b/src/plugins/projectexplorer/customtoolchain.h @@ -83,7 +83,6 @@ private: CustomParserSettings customParserSettings() const; - Utils::FilePath m_compilerCommand; Utils::FilePath m_makeCommand; Macros m_predefinedMacros; diff --git a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp index 105e80e34f1..e65e9892a18 100644 --- a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -27,6 +28,8 @@ namespace ProjectExplorer { DesktopDevice::DesktopDevice() { + setFileAccess(DesktopDeviceFileAccess::instance()); + setupId(IDevice::AutoDetected, DESKTOP_DEVICE_ID); setType(DESKTOP_DEVICE_TYPE); setDefaultDisplayName(tr("Local PC")); @@ -136,32 +139,6 @@ bool DesktopDevice::handlesFile(const FilePath &filePath) const return !filePath.needsDevice(); } -void DesktopDevice::iterateDirectory(const FilePath &filePath, - const std::function &callBack, - const FileFilter &filter) const -{ - QTC_CHECK(!filePath.needsDevice()); - filePath.iterateDirectory(callBack, filter); -} - -qint64 DesktopDevice::fileSize(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return -1); - return filePath.fileSize(); -} - -QFile::Permissions DesktopDevice::permissions(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - return filePath.permissions(); -} - -bool DesktopDevice::setPermissions(const FilePath &filePath, QFile::Permissions permissions) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - return filePath.setPermissions(permissions); -} - FilePath DesktopDevice::mapToGlobalPath(const Utils::FilePath &pathOnDevice) const { QTC_CHECK(!pathOnDevice.needsDevice()); @@ -173,117 +150,4 @@ Environment DesktopDevice::systemEnvironment() const return Environment::systemEnvironment(); } -bool DesktopDevice::isExecutableFile(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - return filePath.isExecutableFile(); -} - -bool DesktopDevice::isReadableFile(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - return filePath.isReadableFile(); -} - -bool DesktopDevice::isWritableFile(const Utils::FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - return filePath.isWritableFile(); -} - -bool DesktopDevice::isReadableDirectory(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - return filePath.isReadableDir(); -} - -bool DesktopDevice::isWritableDirectory(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - return filePath.isWritableDir(); -} - -bool DesktopDevice::isFile(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - return filePath.isFile(); -} - -bool DesktopDevice::isDirectory(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - return filePath.isDir(); -} - -bool DesktopDevice::createDirectory(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - return filePath.createDir(); -} - -bool DesktopDevice::exists(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - return filePath.exists(); -} - -bool DesktopDevice::ensureExistingFile(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - return filePath.ensureExistingFile(); -} - -bool DesktopDevice::removeFile(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - return filePath.removeFile(); -} - -bool DesktopDevice::removeRecursively(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - return filePath.removeRecursively(); -} - -bool DesktopDevice::copyFile(const FilePath &filePath, const FilePath &target) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - return filePath.copyFile(target); -} - -bool DesktopDevice::renameFile(const FilePath &filePath, const FilePath &target) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - QTC_ASSERT(handlesFile(target), return false); - return filePath.renameFile(target); -} - -QDateTime DesktopDevice::lastModified(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - return filePath.lastModified(); -} - -FilePath DesktopDevice::symLinkTarget(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - return filePath.symLinkTarget(); -} - -std::optional DesktopDevice::fileContents(const FilePath &filePath, - qint64 limit, - qint64 offset) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - return filePath.fileContents(limit, offset); -} - -bool DesktopDevice::writeFileContents(const FilePath &filePath, - const QByteArray &data, - qint64 offset) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - return filePath.writeFileContents(data, offset); -} - } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/devicesupport/desktopdevice.h b/src/plugins/projectexplorer/devicesupport/desktopdevice.h index a2a17237ac3..7144eba8eea 100644 --- a/src/plugins/projectexplorer/devicesupport/desktopdevice.h +++ b/src/plugins/projectexplorer/devicesupport/desktopdevice.h @@ -34,34 +34,6 @@ public: bool handlesFile(const Utils::FilePath &filePath) const override; Utils::Environment systemEnvironment() const override; - bool isExecutableFile(const Utils::FilePath &filePath) const override; - bool isReadableFile(const Utils::FilePath &filePath) const override; - bool isWritableFile(const Utils::FilePath &filePath) const override; - bool isReadableDirectory(const Utils::FilePath &filePath) const override; - bool isWritableDirectory(const Utils::FilePath &filePath) const override; - bool isFile(const Utils::FilePath &filePath) const override; - bool isDirectory(const Utils::FilePath &filePath) const override; - bool ensureExistingFile(const Utils::FilePath &filePath) const override; - bool createDirectory(const Utils::FilePath &filePath) const override; - bool exists(const Utils::FilePath &filePath) const override; - bool removeFile(const Utils::FilePath &filePath) const override; - bool removeRecursively(const Utils::FilePath &filePath) const override; - bool copyFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override; - bool renameFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override; - QDateTime lastModified(const Utils::FilePath &filePath) const override; - Utils::FilePath symLinkTarget(const Utils::FilePath &filePath) const override; - void iterateDirectory(const Utils::FilePath &filePath, - const std::function &callBack, - const Utils::FileFilter &filter) const override; - std::optional fileContents(const Utils::FilePath &filePath, - qint64 limit, - qint64 offset) const override; - bool writeFileContents(const Utils::FilePath &filePath, - const QByteArray &data, - qint64 offset) const override; - qint64 fileSize(const Utils::FilePath &filePath) const override; - QFile::Permissions permissions(const Utils::FilePath &filePath) const override; - bool setPermissions(const Utils::FilePath &filePath, QFile::Permissions) const override; Utils::FilePath mapToGlobalPath(const Utils::FilePath &pathOnDevice) const override; protected: diff --git a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp index c5a7f4e8102..2d7d060427b 100644 --- a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp @@ -9,7 +9,9 @@ #include #include + #include +#include #include #include #include @@ -405,30 +407,6 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_uniqueisExecutableFile(filePath); - }; - - deviceHooks.isReadableFile = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return false); - return device->isReadableFile(filePath); - }; - - deviceHooks.isReadableDir = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return false); - return device->isReadableDirectory(filePath); - }; - - deviceHooks.isWritableDir = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return false); - return device->isWritableDirectory(filePath); - }; - deviceHooks.isSameDevice = [](const FilePath &left, const FilePath &right) { auto leftDevice = DeviceManager::deviceForPath(left); auto rightDevice = DeviceManager::deviceForPath(right); @@ -436,158 +414,12 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_unique DeviceFileAccess * { + if (!filePath.needsDevice()) + return DesktopDeviceFileAccess::instance(); auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return false); - return device->isWritableFile(filePath); - }; - - deviceHooks.isFile = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return false); - return device->isFile(filePath); - }; - - deviceHooks.isDir = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return false); - return device->isDirectory(filePath); - }; - - deviceHooks.ensureWritableDir = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return false); - return device->ensureWritableDirectory(filePath); - }; - - deviceHooks.ensureExistingFile = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return false); - return device->ensureExistingFile(filePath); - }; - - deviceHooks.createDir = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return false); - return device->createDirectory(filePath); - }; - - deviceHooks.exists = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return false); - return device->exists(filePath); - }; - - deviceHooks.removeFile = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return false); - return device->removeFile(filePath); - }; - - deviceHooks.removeRecursively = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return false); - return device->removeRecursively(filePath); - }; - - deviceHooks.copyFile = [](const FilePath &filePath, const FilePath &target) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return false); - return device->copyFile(filePath, target); - }; - - deviceHooks.renameFile = [](const FilePath &filePath, const FilePath &target) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return false); - return device->renameFile(filePath, target); - }; - - deviceHooks.searchInPath = [](const FilePath &filePath, const FilePaths &dirs) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return FilePath{}); - return device->searchExecutable(filePath.path(), dirs); - }; - - deviceHooks.symLinkTarget = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return FilePath{}); - return device->symLinkTarget(filePath); - }; - - deviceHooks.mapToDevicePath = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return QString{}); - return device->mapToDevicePath(filePath); - }; - - deviceHooks.iterateDirectory = [](const FilePath &filePath, - const FilePath::IterateDirCallback &callBack, - const FileFilter &filter) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return ); - device->iterateDirectory(filePath, callBack, filter); - }; - - deviceHooks.iterateDirectoryWithInfo = [](const FilePath &filePath, - const FilePath::IterateDirWithInfoCallback &callBack, - const FileFilter &filter) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return ); - device->iterateDirectory(filePath, callBack, filter); - }; - - deviceHooks.fileContents = - [](const FilePath &filePath, qint64 maxSize, qint64 offset) -> std::optional { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return {}); - return device->fileContents(filePath, maxSize, offset); - }; - - deviceHooks.asyncFileContents = [](const Continuation> &cont, - const FilePath &filePath, - qint64 maxSize, - qint64 offset) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return); - device->asyncFileContents(cont, filePath, maxSize, offset); - }; - - deviceHooks.writeFileContents = [](const FilePath &filePath, - const QByteArray &data, - qint64 offset) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return false); - return device->writeFileContents(filePath, data, offset); - }; - - deviceHooks.filePathInfo = [](const FilePath &filePath) -> FilePathInfo { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return {}); - return device->filePathInfo(filePath); - }; - - deviceHooks.lastModified = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return QDateTime()); - return device->lastModified(filePath); - }; - - deviceHooks.permissions = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return QFile::Permissions()); - return device->permissions(filePath); - }; - - deviceHooks.setPermissions = [](const FilePath &filePath, QFile::Permissions permissions) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return false); - return device->setPermissions(filePath, permissions); - }; - - deviceHooks.osType = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return OsTypeOther); - return device->osType(); + QTC_ASSERT(device, return nullptr); + return device->fileAccess(); }; deviceHooks.environment = [](const FilePath &filePath) { @@ -596,18 +428,6 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_uniquesystemEnvironment(); }; - deviceHooks.fileSize = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return qint64(-1)); - return device->fileSize(filePath); - }; - - deviceHooks.bytesAvailable = [](const FilePath &filePath) { - auto device = DeviceManager::deviceForPath(filePath); - QTC_ASSERT(device, return qint64(-1)); - return device->bytesAvailable(filePath); - }; - deviceHooks.deviceDisplayName = [](const FilePath &filePath) { auto device = DeviceManager::deviceForPath(filePath); QTC_ASSERT(device, return QString()); diff --git a/src/plugins/projectexplorer/devicesupport/filetransfer.cpp b/src/plugins/projectexplorer/devicesupport/filetransfer.cpp index 54a135faf9e..bfbfdcf824c 100644 --- a/src/plugins/projectexplorer/devicesupport/filetransfer.cpp +++ b/src/plugins/projectexplorer/devicesupport/filetransfer.cpp @@ -98,12 +98,17 @@ void FileTransferPrivate::start() return startFailed(tr("No files to transfer.")); const FileTransferDirection direction = transferDirection(m_setup.m_files); - if (direction == FileTransferDirection::Invalid) - return startFailed(tr("Mixing different types of transfer in one go.")); - const IDeviceConstPtr device = matchedDevice(direction, m_setup.m_files); - if (!device) - return startFailed(tr("Trying to transfer into / from not matching device.")); + IDeviceConstPtr device; + if (direction != FileTransferDirection::Invalid) + device = matchedDevice(direction, m_setup.m_files); + + if (!device) { + // Fall back to generic copy. + const FilePath &filePath = m_setup.m_files.first().m_target; + device = DeviceManager::deviceForPath(filePath); + m_setup.m_method = FileTransferMethod::GenericCopy; + } run(m_setup, device); } @@ -190,6 +195,7 @@ QString FileTransfer::transferMethodName(FileTransferMethod method) switch (method) { case FileTransferMethod::Sftp: return FileTransfer::tr("sftp"); case FileTransferMethod::Rsync: return FileTransfer::tr("rsync"); + case FileTransferMethod::GenericCopy: return FileTransfer::tr("generic file copy"); } QTC_CHECK(false); return {}; diff --git a/src/plugins/projectexplorer/devicesupport/filetransferinterface.h b/src/plugins/projectexplorer/devicesupport/filetransferinterface.h index ad4ccc200ef..543e1ce6c29 100644 --- a/src/plugins/projectexplorer/devicesupport/filetransferinterface.h +++ b/src/plugins/projectexplorer/devicesupport/filetransferinterface.h @@ -20,6 +20,7 @@ enum class FileTransferDirection { enum class FileTransferMethod { Sftp, Rsync, + GenericCopy, Default = Sftp }; diff --git a/src/plugins/projectexplorer/devicesupport/idevice.cpp b/src/plugins/projectexplorer/devicesupport/idevice.cpp index 294be9f18a0..74e8add824c 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/idevice.cpp @@ -15,6 +15,7 @@ #include +#include #include #include #include @@ -130,6 +131,7 @@ public: IDevice::DeviceState deviceState = IDevice::DeviceStateUnknown; IDevice::MachineType machineType = IDevice::Hardware; OsType osType = OsTypeOther; + DeviceFileAccess *fileAccess = nullptr; int version = 0; // This is used by devices that have been added by the SDK. QReadWriteLock lock; // Currently used to protect sshParameters only @@ -153,6 +155,8 @@ IDevice::IDevice() : d(new Internal::IDevicePrivate) { } +IDevice::~IDevice() = default; + void IDevice::setOpenTerminal(const IDevice::OpenTerminal &openTerminal) { d->openTerminal = openTerminal; @@ -191,6 +195,11 @@ bool IDevice::isAnyUnixDevice() const return d->osType == OsTypeLinux || d->osType == OsTypeMac || d->osType == OsTypeOtherUnix; } +DeviceFileAccess *IDevice::fileAccess() const +{ + return d->fileAccess; +} + FilePath IDevice::mapToGlobalPath(const FilePath &pathOnDevice) const { if (pathOnDevice.needsDevice()) { @@ -202,11 +211,6 @@ FilePath IDevice::mapToGlobalPath(const FilePath &pathOnDevice) const return FilePath::fromParts(u"device", id().toString(), pathOnDevice.path()); } -QString IDevice::mapToDevicePath(const FilePath &globalPath) const -{ - return globalPath.path(); -} - FilePath IDevice::filePath(const QString &pathOnDevice) const { return mapToGlobalPath(FilePath::fromString(pathOnDevice)); @@ -219,113 +223,6 @@ bool IDevice::handlesFile(const FilePath &filePath) const return false; } -bool IDevice::isExecutableFile(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - QTC_CHECK(false); - return false; -} - -bool IDevice::isReadableFile(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - QTC_CHECK(false); - return false; -} - -bool IDevice::isWritableFile(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - QTC_CHECK(false); - return false; -} - -bool IDevice::isReadableDirectory(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - QTC_CHECK(false); - return false; -} - -bool IDevice::isWritableDirectory(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - QTC_CHECK(false); - return false; -} - -bool IDevice::isFile(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - QTC_CHECK(false); - return false; -} - -bool IDevice::isDirectory(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - QTC_CHECK(false); - return false; -} - -bool IDevice::ensureWritableDirectory(const FilePath &filePath) const -{ - if (isWritableDirectory(filePath)) - return true; - return createDirectory(filePath); -} - -bool IDevice::ensureExistingFile(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - QTC_CHECK(false); - return false; -} - -bool IDevice::createDirectory(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - QTC_CHECK(false); - return false; -} - -bool IDevice::exists(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - QTC_CHECK(false); - return false; -} - -bool IDevice::removeFile(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - QTC_CHECK(false); - return false; -} - -bool IDevice::removeRecursively(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - QTC_CHECK(false); - return false; -} - -bool IDevice::copyFile(const FilePath &filePath, const FilePath &target) const -{ - Q_UNUSED(filePath); - Q_UNUSED(target); - QTC_CHECK(false); - return false; -} - -bool IDevice::renameFile(const FilePath &filePath, const FilePath &target) const -{ - Q_UNUSED(filePath); - Q_UNUSED(target); - QTC_CHECK(false); - return false; -} - FilePath IDevice::searchExecutableInPath(const QString &fileName) const { FilePaths paths; @@ -341,117 +238,13 @@ FilePath IDevice::searchExecutable(const QString &fileName, const FilePaths &dir dir = mapToGlobalPath(dir); QTC_CHECK(handlesFile(dir)); const FilePath candidate = dir / fileName; - if (isExecutableFile(candidate)) + if (candidate.isExecutableFile()) return candidate; } return {}; } -FilePath IDevice::symLinkTarget(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - QTC_CHECK(false); - return {}; -} - -void IDevice::iterateDirectory(const FilePath &filePath, - const FilePath::IterateDirCallback &callBack, - const FileFilter &filter) const -{ - Q_UNUSED(filePath); - Q_UNUSED(callBack); - Q_UNUSED(filter); - QTC_CHECK(false); -} - -void IDevice::iterateDirectory(const FilePath &filePath, - const FilePath::IterateDirWithInfoCallback &callBack, - const FileFilter &filter) const -{ - iterateDirectory(filePath, [callBack](const FilePath &path) { - return callBack(path, path.filePathInfo()); - }, filter); -} - -std::optional IDevice::fileContents(const FilePath &filePath, - qint64 limit, - qint64 offset) const -{ - Q_UNUSED(filePath); - Q_UNUSED(limit); - Q_UNUSED(offset); - QTC_CHECK(false); - return {}; -} - -void IDevice::asyncFileContents(const Continuation> &cont, - const FilePath &filePath, - qint64 limit, - qint64 offset) const -{ - cont(fileContents(filePath, limit, offset)); -} - -bool IDevice::writeFileContents(const FilePath &filePath, const QByteArray &data, qint64 offset) const -{ - Q_UNUSED(filePath); - Q_UNUSED(data); - Q_UNUSED(offset); - QTC_CHECK(false); - return {}; -} - -FilePathInfo IDevice::filePathInfo(const Utils::FilePath &filePath) const -{ - bool exists = filePath.exists(); - if (!exists) - return {}; - - FilePathInfo result { - filePath.fileSize(), - {FilePathInfo::ExistsFlag}, - filePath.lastModified(), - }; - - if (filePath.isDir()) - result.fileFlags |= FilePathInfo::DirectoryType; - if (filePath.isFile()) - result.fileFlags |= FilePathInfo::FileType; - if (filePath.isRootPath()) - result.fileFlags |= FilePathInfo::RootFlag; - - return result; -} - -void IDevice::asyncWriteFileContents(const Continuation &cont, - const FilePath &filePath, - const QByteArray &data, - qint64 offset) const -{ - cont(writeFileContents(filePath, data, offset)); -} - -QDateTime IDevice::lastModified(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - return {}; -} - -QFileDevice::Permissions IDevice::permissions(const FilePath &filePath) const -{ - Q_UNUSED(filePath); - QTC_CHECK(false); - return {}; -} - -bool IDevice::setPermissions(const FilePath &filePath, QFile::Permissions) const -{ - Q_UNUSED(filePath); - QTC_CHECK(false); - return false; -} - ProcessInterface *IDevice::createProcessInterface() const { QTC_CHECK(false); @@ -472,22 +265,6 @@ Environment IDevice::systemEnvironment() const return Environment::systemEnvironment(); } -qint64 IDevice::fileSize(const FilePath &filePath) const -{ - Q_UNUSED(filePath) - QTC_CHECK(false); - return -1; -} - -qint64 IDevice::bytesAvailable(const Utils::FilePath &filePath) const -{ - Q_UNUSED(filePath) - QTC_CHECK(false); - return -1; -} - -IDevice::~IDevice() = default; - /*! Specifies a free-text name for the device to be displayed in GUI elements. */ @@ -522,6 +299,11 @@ void IDevice::setOsType(OsType osType) d->osType = osType; } +void IDevice::setFileAccess(DeviceFileAccess *fileAccess) +{ + d->fileAccess = fileAccess; +} + IDevice::DeviceInfo IDevice::deviceInformation() const { const QString key = QCoreApplication::translate("ProjectExplorer::IDevice", "Device"); diff --git a/src/plugins/projectexplorer/devicesupport/idevice.h b/src/plugins/projectexplorer/devicesupport/idevice.h index 57f2ba0a3c7..704894b7607 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.h +++ b/src/plugins/projectexplorer/devicesupport/idevice.h @@ -27,6 +27,7 @@ QT_END_NAMESPACE namespace Utils { class CommandLine; +class DeviceFileAccess; class Environment; class Icon; class PortList; @@ -214,65 +215,22 @@ public: bool isMacDevice() const { return osType() == Utils::OsTypeMac; } bool isAnyUnixDevice() const; - virtual Utils::FilePath mapToGlobalPath(const Utils::FilePath &pathOnDevice) const; - virtual QString mapToDevicePath(const Utils::FilePath &globalPath) const; - + Utils::DeviceFileAccess *fileAccess() const; virtual bool handlesFile(const Utils::FilePath &filePath) const; - virtual bool isExecutableFile(const Utils::FilePath &filePath) const; - virtual bool isReadableFile(const Utils::FilePath &filePath) const; - virtual bool isWritableFile(const Utils::FilePath &filePath) const; - virtual bool isReadableDirectory(const Utils::FilePath &filePath) const; - virtual bool isWritableDirectory(const Utils::FilePath &filePath) const; - virtual bool isFile(const Utils::FilePath &filePath) const; - virtual bool isDirectory(const Utils::FilePath &filePath) const; - virtual bool ensureWritableDirectory(const Utils::FilePath &filePath) const; - virtual bool ensureExistingFile(const Utils::FilePath &filePath) const; - virtual bool createDirectory(const Utils::FilePath &filePath) const; - virtual bool exists(const Utils::FilePath &filePath) const; - virtual bool removeFile(const Utils::FilePath &filePath) const; - virtual bool removeRecursively(const Utils::FilePath &filePath) const; - virtual bool copyFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const; - virtual bool renameFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const; + + virtual Utils::FilePath mapToGlobalPath(const Utils::FilePath &pathOnDevice) const; + virtual Utils::FilePath searchExecutableInPath(const QString &fileName) const; virtual Utils::FilePath searchExecutable(const QString &fileName, const Utils::FilePaths &dirs) const; - virtual Utils::FilePath symLinkTarget(const Utils::FilePath &filePath) const; - virtual void iterateDirectory(const Utils::FilePath &filePath, - const Utils::FilePath::IterateDirCallback &callBack, - const Utils::FileFilter &filter) const; - virtual void iterateDirectory(const Utils::FilePath &filePath, - const Utils::FilePath::IterateDirWithInfoCallback &callBack, - const Utils::FileFilter &filter) const; - - virtual std::optional fileContents(const Utils::FilePath &filePath, - qint64 limit, - qint64 offset) const; - virtual bool writeFileContents(const Utils::FilePath &filePath, - const QByteArray &data, - qint64 offset) const; - virtual Utils::FilePathInfo filePathInfo(const Utils::FilePath &filePath) const; - virtual QDateTime lastModified(const Utils::FilePath &filePath) const; - virtual QFile::Permissions permissions(const Utils::FilePath &filePath) const; - virtual bool setPermissions(const Utils::FilePath &filePath, QFile::Permissions) const; virtual Utils::ProcessInterface *createProcessInterface() const; virtual FileTransferInterface *createFileTransferInterface( const FileTransferSetupData &setup) const; virtual Utils::Environment systemEnvironment() const; - virtual qint64 fileSize(const Utils::FilePath &filePath) const; - virtual qint64 bytesAvailable(const Utils::FilePath &filePath) const; virtual void aboutToBeRemoved() const {} - virtual void asyncFileContents(const Continuation> &cont, - const Utils::FilePath &filePath, - qint64 limit, - qint64 offset) const; - virtual void asyncWriteFileContents(const Continuation &cont, - const Utils::FilePath &filePath, - const QByteArray &data, - qint64 offset) const; - virtual bool ensureReachable(const Utils::FilePath &other) const; virtual bool prepareForBuild(const Target *target); @@ -287,6 +245,7 @@ protected: void setOpenTerminal(const OpenTerminal &openTerminal); void setDisplayType(const QString &type); void setOsType(Utils::OsType osType); + void setFileAccess(Utils::DeviceFileAccess *fileAccess); private: IDevice(const IDevice &) = delete; diff --git a/src/plugins/projectexplorer/gcctoolchain.cpp b/src/plugins/projectexplorer/gcctoolchain.cpp index dae5d277fd2..bdce4800105 100644 --- a/src/plugins/projectexplorer/gcctoolchain.cpp +++ b/src/plugins/projectexplorer/gcctoolchain.cpp @@ -1118,8 +1118,7 @@ static FilePaths findCompilerCandidates(const ToolchainDetector &detector, return true; }; const FilePath globalDir = device->mapToGlobalPath(deviceDir); - device->iterateDirectory(globalDir, callBack, - {nameFilters, QDir::Files | QDir::Executable}); + globalDir.iterateDirectory(callBack, {nameFilters, QDir::Files | QDir::Executable}); } } else { // The normal, local host case. @@ -1197,8 +1196,8 @@ Toolchains GccToolChainFactory::autoDetectToolchains( && HostOsInfo::isWindowsHost() && !existingCommand.needsDevice() && !compilerPath.needsDevice()) { - existingTcMatches = existingCommand.toFileInfo().size() - == compilerPath.toFileInfo().size(); + existingTcMatches = existingCommand.fileSize() + == compilerPath.fileSize(); } } if (existingTcMatches) { diff --git a/src/plugins/projectexplorer/jsonwizard/jsonwizardscannergenerator.cpp b/src/plugins/projectexplorer/jsonwizard/jsonwizardscannergenerator.cpp index a7477d0a39e..d0bf834fea4 100644 --- a/src/plugins/projectexplorer/jsonwizard/jsonwizardscannergenerator.cpp +++ b/src/plugins/projectexplorer/jsonwizard/jsonwizardscannergenerator.cpp @@ -77,7 +77,7 @@ Core::GeneratedFiles JsonWizardScannerGenerator::fileList(Utils::MacroExpander * [](const Utils::FilePath &filePath) { return int(filePath.path().count('/')); }; int minDepth = std::numeric_limits::max(); for (auto it = result.begin(); it != result.end(); ++it) { - const Utils::FilePath relPath = projectDir.relativePath(it->filePath()); + const Utils::FilePath relPath = it->filePath().relativePath(projectDir); it->setBinary(binaryPattern.match(relPath.toString()).hasMatch()); bool found = ProjectManager::canOpenProjectForMimeType(Utils::mimeTypeForFile(relPath)); if (found) { @@ -119,7 +119,7 @@ Core::GeneratedFiles JsonWizardScannerGenerator::scan(const Utils::FilePath &dir const Utils::FilePaths entries = dir.dirEntries({{}, QDir::AllEntries | QDir::NoDotAndDotDot}, QDir::DirsLast | QDir::Name); for (const Utils::FilePath &fi : entries) { - const Utils::FilePath relativePath = base.relativePath(fi); + const Utils::FilePath relativePath = fi.relativePath(base); if (fi.isDir() && matchesSubdirectoryPattern(relativePath)) { result += scan(fi, base); } else { diff --git a/src/plugins/projectexplorer/msvctoolchain.cpp b/src/plugins/projectexplorer/msvctoolchain.cpp index d86a0ce116c..125eb269722 100644 --- a/src/plugins/projectexplorer/msvctoolchain.cpp +++ b/src/plugins/projectexplorer/msvctoolchain.cpp @@ -2094,6 +2094,7 @@ std::optional MsvcToolChain::generateEnvironmentSettings(const Utils::E if (Utils::HostOsInfo::isWindowsHost()) saver.write("chcp 65001\r\n"); saver.write("set VSCMD_SKIP_SENDTELEMETRY=1\r\n"); + saver.write("set CLINK_NOAUTORUN=1\r\n"); saver.write(call + "\r\n"); saver.write("@echo " + marker.toLocal8Bit() + "\r\n"); saver.write("set\r\n"); @@ -2116,7 +2117,7 @@ std::optional MsvcToolChain::generateEnvironmentSettings(const Utils::E if (cmdPath.isEmpty()) cmdPath = env.searchInPath(QLatin1String("cmd.exe")); // Windows SDK setup scripts require command line switches for environment expansion. - CommandLine cmd(cmdPath, {"/E:ON", "/V:ON", "/c", saver.filePath().toUserOutput()}); + CommandLine cmd(cmdPath, {"/D", "/E:ON", "/V:ON", "/c", saver.filePath().toUserOutput()}); qCDebug(Log) << "readEnvironmentSetting: " << call << cmd.toUserOutput() << " Env: " << runEnv.toStringList().size(); run.setCodec(QTextCodec::codecForName("UTF-8")); diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp index db57d6f2bca..c6ee39013b0 100644 --- a/src/plugins/projectexplorer/projectexplorer.cpp +++ b/src/plugins/projectexplorer/projectexplorer.cpp @@ -2239,7 +2239,7 @@ void ProjectExplorerPlugin::extensionsInitialized() BuildManager::extensionsInitialized(); TaskHub::addCategory(Constants::TASK_CATEGORY_SANITIZER, tr("Sanitizer", "Category for sanitizer issues listed under 'Issues'")); - TaskHub::addCategory(Constants::TASK_CATEGORY_TASKLIST_ID, tr("My Tasks")); + TaskHub::addCategory(Constants::TASK_CATEGORY_TASKLIST_ID, TaskFile::tr("My Tasks")); SshSettings::loadSettings(Core::ICore::settings()); const auto searchPathRetriever = [] { diff --git a/src/plugins/projectexplorer/projectnodes.cpp b/src/plugins/projectexplorer/projectnodes.cpp index 27223f066ed..9245f7645ff 100644 --- a/src/plugins/projectexplorer/projectnodes.cpp +++ b/src/plugins/projectexplorer/projectnodes.cpp @@ -39,37 +39,24 @@ static FolderNode *recursiveFindOrCreateFolderNode(FolderNode *folder, const FilePath &overrideBaseDir, const FolderNode::FolderNodeFactory &factory) { - Utils::FilePath path = overrideBaseDir.isEmpty() ? folder->filePath() : overrideBaseDir; + QList paths; + const Utils::FilePath basePath = overrideBaseDir.isEmpty() ? folder->filePath() + : overrideBaseDir; + Utils::FilePath path = basePath.isEmpty() ? directory : basePath.relativeChildPath(directory); - Utils::FilePath directoryWithoutPrefix; - bool isRelative = false; - - if (path.isEmpty() || path.isRootPath()) { - directoryWithoutPrefix = directory; - isRelative = false; - } else { - if (directory.isChildOf(path) || directory == path) { - isRelative = true; - directoryWithoutPrefix = directory.relativeChildPath(path); - } else { - isRelative = false; - path.clear(); - directoryWithoutPrefix = directory; - } + while (!path.isEmpty()) { + paths.append(path); + path = path.parentDir(); } - QStringList parts = directoryWithoutPrefix.toString().split('/', Qt::SkipEmptyParts); - if (!Utils::HostOsInfo::isWindowsHost() && !isRelative && !parts.isEmpty()) - parts[0].prepend('/'); + std::reverse(std::begin(paths), std::end(paths)); - ProjectExplorer::FolderNode *parent = folder; - for (const QString &part : std::as_const(parts)) { - path = path.pathAppended(part); - // Find folder in subFolders - FolderNode *next = parent->folderNode(path); + FolderNode *parent = folder; + for (const auto ¤tPath : paths) { + FolderNode *next = parent->folderNode(currentPath); if (!next) { // No FolderNode yet, so create it - auto tmp = factory(path); - tmp->setDisplayName(part); + auto tmp = factory(currentPath); + tmp->setDisplayName(currentPath.fileName()); next = tmp.get(); parent->addNode(std::move(tmp)); } diff --git a/src/plugins/projectexplorer/rawprojectpart.cpp b/src/plugins/projectexplorer/rawprojectpart.cpp index 35962516171..673a19e859c 100644 --- a/src/plugins/projectexplorer/rawprojectpart.cpp +++ b/src/plugins/projectexplorer/rawprojectpart.cpp @@ -5,10 +5,12 @@ #include "abi.h" #include "buildconfiguration.h" +#include "buildsystem.h" #include "kitinformation.h" #include "project.h" #include "target.h" +#include #include namespace ProjectExplorer { @@ -158,7 +160,7 @@ ToolChainInfo::ToolChainInfo(const ToolChain *toolChain, // Keep the following cheap/non-blocking for the ui thread... type = toolChain->typeId(); isMsvc2015ToolChain = toolChain->targetAbi().osFlavor() == Abi::WindowsMsvc2015Flavor; - wordWidth = toolChain->targetAbi().wordWidth(); + abi = toolChain->targetAbi(); targetTriple = toolChain->effectiveCodeModelTargetTriple(); targetTripleIsAuthoritative = !toolChain->explicitCodeModelTargetTriple().isEmpty(); extraCodeModelFlags = toolChain->extraCodeModelFlags(); @@ -191,4 +193,28 @@ ProjectUpdateInfo::ProjectUpdateInfo(Project *project, } } +// We do not get the -target flag from qmake or cmake on macOS; see QTCREATORBUG-28278. +void addTargetFlagForIos(QStringList &cFlags, QStringList &cxxFlags, const BuildSystem *bs, + const std::function &getDeploymentTarget) +{ + const Utils::Id deviceType = DeviceTypeKitAspect::deviceTypeId(bs->target()->kit()); + if (deviceType != Ios::Constants::IOS_DEVICE_TYPE + && deviceType != Ios::Constants::IOS_SIMULATOR_TYPE) { + return; + } + const bool isSim = deviceType == Ios::Constants::IOS_SIMULATOR_TYPE; + QString targetTriple(QLatin1String(isSim ? "x86_64" : "arm64")); + targetTriple.append("-apple-ios").append(getDeploymentTarget()); + if (isSim) + targetTriple.append("-simulator"); + const auto addTargetFlag = [&targetTriple](QStringList &flags) { + if (!flags.contains("-target") && !Utils::contains(flags, + [](const QString &flag) { return flag.startsWith("--target="); })) { + flags << "-target" << targetTriple; + } + }; + addTargetFlag(cxxFlags); + addTargetFlag(cFlags); +} + } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/rawprojectpart.h b/src/plugins/projectexplorer/rawprojectpart.h index fa36dc6c4a8..6b06cdc6d82 100644 --- a/src/plugins/projectexplorer/rawprojectpart.h +++ b/src/plugins/projectexplorer/rawprojectpart.h @@ -21,9 +21,17 @@ namespace ProjectExplorer { +class BuildSystem; class Kit; class Project; +void PROJECTEXPLORER_EXPORT addTargetFlagForIos( + QStringList &cFlags, + QStringList &cxxFlags, + const BuildSystem *bs, + const std::function &getDeploymentTarget + ); + class PROJECTEXPLORER_EXPORT RawProjectPartFlags { public: @@ -133,7 +141,7 @@ public: Utils::Id type; bool isMsvc2015ToolChain = false; bool targetTripleIsAuthoritative = false; - unsigned wordWidth = 0; + Abi abi; QString targetTriple; Utils::FilePath compilerFilePath; Utils::FilePath installDir; diff --git a/src/plugins/projectexplorer/taskfile.cpp b/src/plugins/projectexplorer/taskfile.cpp index c3f89cfc3b1..9ba6a3617a3 100644 --- a/src/plugins/projectexplorer/taskfile.cpp +++ b/src/plugins/projectexplorer/taskfile.cpp @@ -104,8 +104,8 @@ static bool parseTaskFile(QString *errorString, const FilePath &name) { QFile tf(name.toString()); if (!tf.open(QIODevice::ReadOnly)) { - *errorString = ProjectExplorerPlugin::tr("Cannot open task file %1: %2").arg( - name.toUserOutput(), tf.errorString()); + *errorString = TaskFile::tr("Cannot open task file %1: %2") + .arg(name.toUserOutput(), tf.errorString()); return false; } diff --git a/src/plugins/projectexplorer/taskfile.h b/src/plugins/projectexplorer/taskfile.h index 9676c1553d5..5286267dd3f 100644 --- a/src/plugins/projectexplorer/taskfile.h +++ b/src/plugins/projectexplorer/taskfile.h @@ -20,6 +20,7 @@ public: class TaskFile : public Core::IDocument { + Q_DECLARE_TR_FUNCTIONS(TaskList::Internal::TaskListPlugin) public: TaskFile(QObject *parent); diff --git a/src/plugins/projectexplorer/toolchainsettingsaccessor.cpp b/src/plugins/projectexplorer/toolchainsettingsaccessor.cpp index 8090867f5cc..41a6b584737 100644 --- a/src/plugins/projectexplorer/toolchainsettingsaccessor.cpp +++ b/src/plugins/projectexplorer/toolchainsettingsaccessor.cpp @@ -12,11 +12,16 @@ #include +#include +#include + using namespace Utils; namespace ProjectExplorer { namespace Internal { +static Q_LOGGING_CATEGORY(Log, "qtc.projectexplorer.toolchain.autodetection", QtWarningMsg) + // -------------------------------------------------------------------- // ToolChainSettingsUpgraders: // -------------------------------------------------------------------- @@ -49,8 +54,12 @@ struct ToolChainOperations static Toolchains autoDetectToolChains(const ToolchainDetector &detector) { Toolchains result; - for (ToolChainFactory *f : ToolChainFactory::allToolChainFactories()) + for (ToolChainFactory *f : ToolChainFactory::allToolChainFactories()) { + QElapsedTimer et; + et.start(); result.append(f->autoDetect(detector)); + qCDebug(Log) << f->displayName() << "auto detection took: " << et.elapsed() << "ms"; + } // Remove invalid toolchains that might have sneaked in. return Utils::filtered(result, [](const ToolChain *tc) { return tc->isValid(); }); diff --git a/src/plugins/python/pythonsettings.cpp b/src/plugins/python/pythonsettings.cpp index bc9a906e493..313e7db70de 100644 --- a/src/plugins/python/pythonsettings.cpp +++ b/src/plugins/python/pythonsettings.cpp @@ -835,9 +835,13 @@ void PythonSettings::initFromSettings(QSettings *settings) m_interpreters << interpreter; } - m_interpreters = Utils::filtered(m_interpreters, [](const Interpreter &interpreter){ - return !interpreter.autoDetected || interpreter.command.isExecutableFile(); - }); + const auto keepInterpreter = [](const Interpreter &interpreter) { + return !interpreter.autoDetected // always keep user added interpreters + || interpreter.command.needsDevice() // remote devices might not be reachable at startup + || interpreter.command.isExecutableFile(); + }; + + m_interpreters = Utils::filtered(m_interpreters, keepInterpreter); m_defaultInterpreterId = settings->value(defaultKey).toString(); diff --git a/src/plugins/qbsprojectmanager/qbssession.cpp b/src/plugins/qbsprojectmanager/qbssession.cpp index b083cc5cf3d..cf5c86dc1c1 100644 --- a/src/plugins/qbsprojectmanager/qbssession.cpp +++ b/src/plugins/qbsprojectmanager/qbssession.cpp @@ -187,8 +187,12 @@ void QbsSession::initialize() connect(d->packetReader, &PacketReader::packetReceived, this, &QbsSession::handlePacket); d->state = State::Initializing; const FilePath qbsExe = QbsSettings::qbsExecutableFilePath(); - if (qbsExe.isEmpty() || !qbsExe.exists()) { - QTimer::singleShot(0, this, [this] { setError(Error::QbsFailedToStart); }); + if (qbsExe.isEmpty()) { + QTimer::singleShot(0, this, [this] { setError(Error::NoQbsPath); }); + return; + } + if (!qbsExe.isExecutableFile()) { + QTimer::singleShot(0, this, [this] { setError(Error::InvalidQbsExecutable); }); return; } d->qbsProcess->setCommand({qbsExe, {"session"}}); @@ -223,6 +227,12 @@ std::optional QbsSession::lastError() const QString QbsSession::errorString(QbsSession::Error error) { switch (error) { + case Error::NoQbsPath: + return Tr::tr("No qbs executable was found, please set the path in the settings."); + case Error::InvalidQbsExecutable: + return Tr::tr("The qbs executable was not found at the specified path, or it is not " + "executable (\"%1\").") + .arg(QbsSettings::qbsExecutableFilePath().toUserOutput()); case Error::QbsQuit: return Tr::tr("The qbs process quit unexpectedly."); case Error::QbsFailedToStart: @@ -231,7 +241,8 @@ QString QbsSession::errorString(QbsSession::Error error) return Tr::tr("The qbs process sent unexpected data."); case Error::VersionMismatch: return Tr::tr("The qbs API level is not compatible with " - "what %1 expects.").arg(Core::Constants::IDE_DISPLAY_NAME); + "what %1 expects.") + .arg(Core::Constants::IDE_DISPLAY_NAME); } return QString(); // For dumb compilers. } diff --git a/src/plugins/qbsprojectmanager/qbssession.h b/src/plugins/qbsprojectmanager/qbssession.h index 8ad9972d6d8..0452bfdd575 100644 --- a/src/plugins/qbsprojectmanager/qbssession.h +++ b/src/plugins/qbsprojectmanager/qbssession.h @@ -100,7 +100,14 @@ public: ~QbsSession() override; enum class State { Initializing, Active, Inactive }; - enum class Error { QbsFailedToStart, QbsQuit, ProtocolError, VersionMismatch }; + enum class Error { + NoQbsPath, + InvalidQbsExecutable, + QbsFailedToStart, + QbsQuit, + ProtocolError, + VersionMismatch + }; std::optional lastError() const; static QString errorString(Error error); diff --git a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp index 6df3ff16fe7..8467b62bd6b 100644 --- a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp @@ -1557,6 +1557,7 @@ QmakeEvalResultPtr QmakeProFile::evaluate(const QmakeEvalInput &input) result->newVarValues[Variable::AndroidAbis] = exactReader->values(QLatin1String(Android::Constants::ANDROID_ABIS)); result->newVarValues[Variable::AndroidApplicationArgs] = exactReader->values(QLatin1String(Android::Constants::ANDROID_APPLICATION_ARGUMENTS)); result->newVarValues[Variable::AndroidExtraLibs] = exactReader->values(QLatin1String(Android::Constants::ANDROID_EXTRA_LIBS)); + result->newVarValues[Variable::IosDeploymentTarget] = exactReader->values("QMAKE_IOS_DEPLOYMENT_TARGET"); result->newVarValues[Variable::AppmanPackageDir] = exactReader->values(QLatin1String("AM_PACKAGE_DIR")); result->newVarValues[Variable::AppmanManifest] = exactReader->values(QLatin1String("AM_MANIFEST")); result->newVarValues[Variable::IsoIcons] = exactReader->values(QLatin1String("ISO_ICONS")); diff --git a/src/plugins/qmakeprojectmanager/qmakeparsernodes.h b/src/plugins/qmakeprojectmanager/qmakeparsernodes.h index d481dda64ce..e0e6cdd1331 100644 --- a/src/plugins/qmakeprojectmanager/qmakeparsernodes.h +++ b/src/plugins/qmakeprojectmanager/qmakeparsernodes.h @@ -80,6 +80,7 @@ enum class Variable { AndroidPackageSourceDir, AndroidExtraLibs, AndroidApplicationArgs, + IosDeploymentTarget, AppmanPackageDir, AppmanManifest, IsoIcons, diff --git a/src/plugins/qmakeprojectmanager/qmakeproject.cpp b/src/plugins/qmakeprojectmanager/qmakeproject.cpp index 605c4c19bb2..47adf657e9a 100644 --- a/src/plugins/qmakeprojectmanager/qmakeproject.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeproject.cpp @@ -350,10 +350,15 @@ void QmakeBuildSystem::updateCppCodeModel() }; const QStringList extraCxxArgs = getExtraFlagsFromCompilerVar(Variable::QmakeCxx); + cxxArgs << extraCxxArgs; const QStringList extraCArgs = getExtraFlagsFromCompilerVar(Variable::QmakeCc); + cArgs << extraCArgs; + addTargetFlagForIos(cArgs, cxxArgs, this, [pro] { + return pro->variableValue(Variable::IosDeploymentTarget).join(QString()); + }); - rpp.setFlagsForCxx({kitInfo.cxxToolChain, cxxArgs << extraCxxArgs, includeFileBaseDir}); - rpp.setFlagsForC({kitInfo.cToolChain, cArgs << extraCArgs, includeFileBaseDir}); + rpp.setFlagsForCxx({kitInfo.cxxToolChain, cxxArgs, includeFileBaseDir}); + rpp.setFlagsForC({kitInfo.cToolChain, cArgs, includeFileBaseDir}); rpp.setMacros(ProjectExplorer::Macro::toMacros(pro->cxxDefines())); rpp.setPreCompiledHeaders(pro->variableValue(Variable::PrecompiledHeader)); rpp.setSelectedForBuilding(pro->includedInExactParse()); diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp index fc4a17eb118..ab2348e6e17 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarymodel.cpp @@ -35,6 +35,25 @@ static Q_LOGGING_CATEGORY(assetsLibraryBenchmark, "qtc.assetsLibrary.setRoot", Q namespace QmlDesigner { +AssetsLibraryModel::AssetsLibraryModel(Utils::FileSystemWatcher *fileSystemWatcher, QObject *parent) + : QAbstractListModel(parent) + , m_fileSystemWatcher(fileSystemWatcher) +{ + // add role names + int role = 0; + const QMetaObject meta = AssetsLibraryDir::staticMetaObject; + for (int i = meta.propertyOffset(); i < meta.propertyCount(); ++i) + m_roleNames.insert(role++, meta.property(i).name()); +} + +void AssetsLibraryModel::setSearchText(const QString &searchText) +{ + if (m_searchText != searchText) { + m_searchText = searchText; + refresh(); + } +} + void AssetsLibraryModel::saveExpandedState(bool expanded, const QString &assetPath) { m_expandedStateHash.insert(assetPath, expanded); @@ -186,66 +205,18 @@ QObject *AssetsLibraryModel::rootDir() const return m_assetsDir; } -const QStringList &AssetsLibraryModel::supportedImageSuffixes() +bool AssetsLibraryModel::isEmpty() const { - static QStringList retList; - if (retList.isEmpty()) { - const QList suffixes = QImageReader::supportedImageFormats(); - for (const QByteArray &suffix : suffixes) - retList.append("*." + QString::fromUtf8(suffix)); + return m_isEmpty; +}; + +void AssetsLibraryModel::setIsEmpty(bool empty) +{ + if (m_isEmpty != empty) { + m_isEmpty = empty; + emit isEmptyChanged(); } - return retList; -} - -const QStringList &AssetsLibraryModel::supportedFragmentShaderSuffixes() -{ - static const QStringList retList {"*.frag", "*.glsl", "*.glslf", "*.fsh"}; - return retList; -} - -const QStringList &AssetsLibraryModel::supportedShaderSuffixes() -{ - static const QStringList retList {"*.frag", "*.vert", - "*.glsl", "*.glslv", "*.glslf", - "*.vsh", "*.fsh"}; - return retList; -} - -const QStringList &AssetsLibraryModel::supportedFontSuffixes() -{ - static const QStringList retList {"*.ttf", "*.otf"}; - return retList; -} - -const QStringList &AssetsLibraryModel::supportedAudioSuffixes() -{ - static const QStringList retList {"*.wav", "*.mp3"}; - return retList; -} - -const QStringList &AssetsLibraryModel::supportedVideoSuffixes() -{ - static const QStringList retList {"*.mp4"}; - return retList; -} - -const QStringList &AssetsLibraryModel::supportedTexture3DSuffixes() -{ - // These are file types only supported by 3D textures - static QStringList retList {"*.hdr", "*.ktx"}; - return retList; -} - -AssetsLibraryModel::AssetsLibraryModel(Utils::FileSystemWatcher *fileSystemWatcher, QObject *parent) - : QAbstractListModel(parent) - , m_fileSystemWatcher(fileSystemWatcher) -{ - // add role names - int role = 0; - const QMetaObject meta = AssetsLibraryDir::staticMetaObject; - for (int i = meta.propertyOffset(); i < meta.propertyCount(); ++i) - m_roleNames.insert(role++, meta.property(i).name()); -} +}; QVariant AssetsLibraryModel::data(const QModelIndex &index, int role) const { @@ -353,12 +324,54 @@ void AssetsLibraryModel::setRootPath(const QString &path) qCInfo(assetsLibraryBenchmark) << "model reset:" << time.elapsed(); } -void AssetsLibraryModel::setSearchText(const QString &searchText) +const QStringList &AssetsLibraryModel::supportedImageSuffixes() { - if (m_searchText != searchText) { - m_searchText = searchText; - refresh(); + static QStringList retList; + if (retList.isEmpty()) { + const QList suffixes = QImageReader::supportedImageFormats(); + for (const QByteArray &suffix : suffixes) + retList.append("*." + QString::fromUtf8(suffix)); } + return retList; +} + +const QStringList &AssetsLibraryModel::supportedFragmentShaderSuffixes() +{ + static const QStringList retList {"*.frag", "*.glsl", "*.glslf", "*.fsh"}; + return retList; +} + +const QStringList &AssetsLibraryModel::supportedShaderSuffixes() +{ + static const QStringList retList {"*.frag", "*.vert", + "*.glsl", "*.glslv", "*.glslf", + "*.vsh", "*.fsh"}; + return retList; +} + +const QStringList &AssetsLibraryModel::supportedFontSuffixes() +{ + static const QStringList retList {"*.ttf", "*.otf"}; + return retList; +} + +const QStringList &AssetsLibraryModel::supportedAudioSuffixes() +{ + static const QStringList retList {"*.wav", "*.mp3"}; + return retList; +} + +const QStringList &AssetsLibraryModel::supportedVideoSuffixes() +{ + static const QStringList retList {"*.mp4"}; + return retList; +} + +const QStringList &AssetsLibraryModel::supportedTexture3DSuffixes() +{ + // These are file types only supported by 3D textures + static QStringList retList {"*.hdr", "*.ktx"}; + return retList; } const QSet &AssetsLibraryModel::supportedSuffixes() @@ -379,19 +392,6 @@ const QSet &AssetsLibraryModel::supportedSuffixes() return allSuffixes; } -bool AssetsLibraryModel::isEmpty() const -{ - return m_isEmpty; -}; - -void AssetsLibraryModel::setIsEmpty(bool empty) -{ - if (m_isEmpty != empty) { - m_isEmpty = empty; - emit isEmptyChanged(); - } -}; - const QSet &AssetsLibraryModel::previewableSuffixes() const { static QSet previewableSuffixes; diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp index 5dbb7102f4d..e652fcd74f3 100644 --- a/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibrarywidget.cpp @@ -69,6 +69,8 @@ bool AssetsLibraryWidget::eventFilter(QObject *obj, QEvent *event) m_assetsToDrag.clear(); } } + } else if (event->type() == QMouseEvent::MouseButtonRelease) { + m_assetsToDrag.clear(); } return QObject::eventFilter(obj, event); diff --git a/src/plugins/qmldesigner/components/componentcore/theme.h b/src/plugins/qmldesigner/components/componentcore/theme.h index 4864e6e4c94..c2a408bff05 100644 --- a/src/plugins/qmldesigner/components/componentcore/theme.h +++ b/src/plugins/qmldesigner/components/componentcore/theme.h @@ -57,10 +57,8 @@ public: centerHorizontal, centerVertical, closeCross, - closeLink, colorPopupClose, columnsAndRows, - copyLink, copyStyle, cornerA, cornerB, @@ -103,6 +101,7 @@ public: gridView, idAliasOff, idAliasOn, + imported, infinity, keyframe, linkTriangle, @@ -116,7 +115,6 @@ public: minus, mirror, newMaterial, - openLink, openMaterialBrowser, orientation, paddingEdge, diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 91e70e34d0b..beb616d53e3 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -13,6 +13,7 @@ #include "qmlvisualnode.h" #include "viewmanager.h" #include +#include #include #include @@ -209,14 +210,21 @@ void Edit3DWidget::updateCreateSubMenu(const QStringList &keys, void Edit3DWidget::onCreateAction() { // QAction *action = qobject_cast(sender()); - // if (!action) + // if (!action || !m_view || !m_view->model()) // return; // m_view->executeInTransaction(__FUNCTION__, [&] { - // int activeScene = m_view->rootModelNode().auxiliaryData("active3dScene@Internal").toInt(); + // ItemLibraryEntry entry = m_nameToEntry.value(action->data().toString()); + // Import import = Import::createLibraryImport(entry.requiredImport(), + // QString::number(entry.majorVersion()) + // + QLatin1Char('.') + // + QString::number(entry.minorVersion())); + // if (!m_view->model()->hasImport(import)) + // m_view->model()->changeImports({import}, {}); - // auto modelNode = QmlVisualNode::createQml3DNode(m_view, m_nameToEntry.value(action->data().toString()), - // activeScene, m_contextMenuPos3d).modelNode(); + // int activeScene = m_view->rootModelNode().auxiliaryData("active3dScene@Internal").toInt(); + // auto modelNode = QmlVisualNode::createQml3DNode(m_view, entry, + // activeScene, m_contextMenuPos3d).modelNode(); // QTC_ASSERT(modelNode.isValid(), return); // m_view->setSelectedModelNode(modelNode); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp index cb955174afb..dac6a0b8aa1 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp @@ -273,8 +273,10 @@ void ItemLibraryWidget::delayedUpdateModel() void ItemLibraryWidget::setModel(Model *model) { m_model = model; - if (!model) + if (!model) { + m_itemToDrag = {}; return; + } setItemLibraryInfo(model->metaInfo().itemLibraryInfo()); diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp index 62d122d14c0..2a31fa8885f 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp @@ -97,9 +97,10 @@ QString BundleImporter::importComponent(const QString &qmlFile, FilePath qmlSourceFile = bundleImportPath.resolvePath(FilePath::fromString(qmlFile)); const bool qmlFileExists = qmlSourceFile.exists(); const QString qmlType = qmlSourceFile.baseName(); - m_pendingTypes.append(QStringLiteral("%1.%2.%3") - .arg(QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), - m_bundleId, qmlType)); + const QString fullTypeName = QStringLiteral("%1.%2.%3") + .arg(QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), m_bundleId, qmlType); + if (m_pendingTypes.contains(fullTypeName) && !m_pendingTypes[fullTypeName]) + return QStringLiteral("Unable to import while unimporting the same type: '%1'").arg(fullTypeName); if (!qmldirContent.contains(qmlFile)) { qmldirContent.append(qmlType); qmldirContent.append(" 1.0 "); @@ -162,6 +163,7 @@ QString BundleImporter::importComponent(const QString &qmlFile, m_importAddPending = true; } } + m_pendingTypes.insert(fullTypeName, true); m_importTimerCount = 0; m_importTimer.start(); @@ -175,8 +177,16 @@ void BundleImporter::handleImportTimer() m_fullReset = false; m_importAddPending = false; m_importTimerCount = 0; - m_pendingTypes.clear(); - emit importFinished({}); + + // Emit dummy finished signals for all pending types + const QStringList pendingTypes = m_pendingTypes.keys(); + for (const QString &pendingType : pendingTypes) { + m_pendingTypes.remove(pendingType); + if (m_pendingTypes[pendingType]) + emit importFinished({}); + else + emit unimportFinished({}); + } }; auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); @@ -210,12 +220,17 @@ void BundleImporter::handleImportTimer() } // Detect when the code model has the new material(s) fully available - const QStringList pendingTypes = m_pendingTypes; + const QStringList pendingTypes = m_pendingTypes.keys(); for (const QString &pendingType : pendingTypes) { NodeMetaInfo metaInfo = model->metaInfo(pendingType.toUtf8()); - if (metaInfo.isValid() && !metaInfo.superClasses().empty()) { - m_pendingTypes.removeAll(pendingType); - emit importFinished(metaInfo); + const bool isImport = m_pendingTypes[pendingType]; + const bool typeComplete = metaInfo.isValid() && !metaInfo.superClasses().empty(); + if (isImport == typeComplete) { + m_pendingTypes.remove(pendingType); + if (isImport) + emit importFinished(metaInfo); + else + emit unimportFinished(metaInfo); } } @@ -257,14 +272,14 @@ QString BundleImporter::unimportComponent(const QString &qmlFile) { FilePath bundleImportPath = resolveBundleImportPath(); if (bundleImportPath.isEmpty()) - return "Failed to resolve bundle import folder"; + return QStringLiteral("Failed to resolve bundle import folder for: '%1'").arg(qmlFile); if (!bundleImportPath.exists()) - return {}; + return QStringLiteral("Unable to find bundle path: '%1'").arg(bundleImportPath.toString()); FilePath qmlFilePath = bundleImportPath.resolvePath(qmlFile); if (!qmlFilePath.exists()) - return {}; + return QStringLiteral("Unable to find specified file: '%1'").arg(qmlFilePath.toString()); QStringList removedFiles; removedFiles.append(qmlFile); @@ -272,9 +287,15 @@ QString BundleImporter::unimportComponent(const QString &qmlFile) FilePath qmldirPath = bundleImportPath.resolvePath(QStringLiteral("qmldir")); const std::optional qmldirContent = qmldirPath.fileContents(); QByteArray newContent; + + QString qmlType = qmlFilePath.baseName(); + const QString fullTypeName = QStringLiteral("%1.%2.%3") + .arg(QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), m_bundleId, qmlType); + if (m_pendingTypes.contains(fullTypeName) && m_pendingTypes[fullTypeName]) + return QStringLiteral("Unable to unimport while importing the same type: '%1'").arg(fullTypeName); + if (qmldirContent) { - QByteArray qmlType = qmlFilePath.baseName().toUtf8(); - int typeIndex = qmldirContent->indexOf(qmlType); + int typeIndex = qmldirContent->indexOf(qmlType.toUtf8()); if (typeIndex != -1) { int newLineIndex = qmldirContent->indexOf('\n', typeIndex); newContent = qmldirContent->left(typeIndex); @@ -287,6 +308,8 @@ QString BundleImporter::unimportComponent(const QString &qmlFile) } } + m_pendingTypes.insert(fullTypeName, false); + QVariantHash assetRefMap = loadAssetRefMap(bundleImportPath); bool writeAssetRefs = false; const auto keys = assetRefMap.keys(); diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.h b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.h index b22d0edd591..64344915ce1 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.h +++ b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.h @@ -51,18 +51,19 @@ public: QString importComponent(const QString &qmlFile, const QStringList &files); QString unimportComponent(const QString &qmlFile); + Utils::FilePath resolveBundleImportPath(); signals: // The metaInfo parameter will be invalid if an error was encountered during // asynchronous part of the import. In this case all remaining pending imports have been // terminated, and will not receive separate importFinished notifications. void importFinished(const QmlDesigner::NodeMetaInfo &metaInfo); + void unimportFinished(const QmlDesigner::NodeMetaInfo &metaInfo); private: void handleImportTimer(); QVariantHash loadAssetRefMap(const Utils::FilePath &bundlePath); void writeAssetRefMap(const Utils::FilePath &bundlePath, const QVariantHash &assetRefMap); - Utils::FilePath resolveBundleImportPath(); Utils::FilePath m_bundleDir; QString m_bundleId; @@ -72,7 +73,7 @@ private: int m_importTimerCount = 0; bool m_importAddPending = false; bool m_fullReset = false; - QStringList m_pendingTypes; + QHash m_pendingTypes; // }; } // namespace QmlDesigner::Internal diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.cpp b/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.cpp index b5bc19e785c..6cee76475ac 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.cpp @@ -70,4 +70,20 @@ bool BundleMaterial::visible() const return m_visible; } +bool BundleMaterial::setImported(bool imported) +{ + if (m_imported != imported) { + m_imported = imported; + emit materialImportedChanged(); + return true; + } + + return false; +} + +bool BundleMaterial::imported() const +{ + return m_imported; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.h b/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.h index ae74a13d75c..64d79aa65ee 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.h +++ b/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.h @@ -40,6 +40,7 @@ class BundleMaterial : public QObject Q_PROPERTY(QString bundleMaterialName MEMBER m_name CONSTANT) Q_PROPERTY(QUrl bundleMaterialIcon MEMBER m_icon CONSTANT) Q_PROPERTY(bool bundleMaterialVisible MEMBER m_visible NOTIFY materialVisibleChanged) + Q_PROPERTY(bool bundleMaterialImported READ imported WRITE setImported NOTIFY materialImportedChanged) public: BundleMaterial(QObject *parent, @@ -57,8 +58,12 @@ public: QStringList files() const; bool visible() const; + bool setImported(bool imported); + bool imported() const; + signals: void materialVisibleChanged(); + void materialImportedChanged(); private: QString m_name; @@ -68,6 +73,7 @@ private: QStringList m_files; bool m_visible = true; + bool m_imported = false; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.cpp b/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.cpp index 9a18ff75014..716fb57dbf5 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.cpp @@ -37,6 +37,16 @@ void BundleMaterialCategory::addBundleMaterial(BundleMaterial *bundleMat) m_categoryMaterials.append(bundleMat); } +bool BundleMaterialCategory::updateImportedState(const QStringList &importedMats) +{ + bool changed = false; + + for (BundleMaterial *mat : std::as_const(m_categoryMaterials)) + changed |= mat->setImported(importedMats.contains(mat->qml().chopped(4))); + + return changed; +} + bool BundleMaterialCategory::filter(const QString &searchText) { bool visible = false; diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.h b/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.h index 0a3a0f7c028..2336a03d010 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.h +++ b/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.h @@ -42,6 +42,7 @@ public: BundleMaterialCategory(QObject *parent, const QString &name); void addBundleMaterial(BundleMaterial *bundleMat); + bool updateImportedState(const QStringList &importedMats); bool filter(const QString &searchText); QString name() const; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp index 4e70008f1ab..a43a23a1d1d 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp @@ -122,7 +122,7 @@ void MaterialBrowserBundleModel::loadMaterialBundle() m_matBundleExists = true; - const QString bundleId = m_matBundleObj.value("id").toString(); + QString bundleId = m_matBundleObj.value("id").toString(); const QJsonObject catsObj = m_matBundleObj.value("categories").toObject(); const QStringList categories = catsObj.keys(); @@ -160,8 +160,17 @@ void MaterialBrowserBundleModel::loadMaterialBundle() m_importer = new Internal::BundleImporter(matBundleDir.path(), bundleId, sharedFiles); connect(m_importer, &Internal::BundleImporter::importFinished, this, [&](const QmlDesigner::NodeMetaInfo &metaInfo) { + m_importerRunning = false; + emit importerRunningChanged(); if (metaInfo.isValid()) - emit addBundleMaterialToProjectRequested(metaInfo); + emit bundleMaterialImported(metaInfo); + }); + + connect(m_importer, &Internal::BundleImporter::unimportFinished, this, [&](const QmlDesigner::NodeMetaInfo &metaInfo) { + Q_UNUSED(metaInfo) + m_importerRunning = false; + emit importerRunningChanged(); + emit bundleMaterialUnimported(metaInfo); }); } @@ -193,6 +202,11 @@ void MaterialBrowserBundleModel::setHasMaterialRoot(bool b) emit hasMaterialRootChanged(); } +Internal::BundleImporter *MaterialBrowserBundleModel::bundleImporter() const +{ + return m_importer; +} + void MaterialBrowserBundleModel::setSearchText(const QString &searchText) { QString lowerSearchText = searchText.toLower(); @@ -219,6 +233,16 @@ void MaterialBrowserBundleModel::setSearchText(const QString &searchText) resetModel(); } +void MaterialBrowserBundleModel::updateImportedState(const QStringList &importedMats) +{ + bool changed = false; + for (BundleMaterialCategory *cat : std::as_const(m_bundleCategories)) + changed |= cat->updateImportedState(importedMats); + + if (changed) + resetModel(); +} + void MaterialBrowserBundleModel::resetModel() { beginResetModel(); @@ -230,12 +254,30 @@ void MaterialBrowserBundleModel::applyToSelected(BundleMaterial *mat, bool add) emit applyToSelectedTriggered(mat, add); } -void MaterialBrowserBundleModel::addMaterial(BundleMaterial *mat) +void MaterialBrowserBundleModel::addToProject(BundleMaterial *mat) { QString err = m_importer->importComponent(mat->qml(), mat->files()); - if (!err.isEmpty()) + if (err.isEmpty()) { + m_importerRunning = true; + emit importerRunningChanged(); + } else { qWarning() << __FUNCTION__ << err; + } +} + +void MaterialBrowserBundleModel::removeFromProject(BundleMaterial *mat) +{ + emit bundleMaterialAboutToUnimport(mat->type()); + + QString err = m_importer->unimportComponent(mat->qml()); + + if (err.isEmpty()) { + m_importerRunning = true; + emit importerRunningChanged(); + } else { + qWarning() << __FUNCTION__ << err; + } } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h index 60b48f1c9b9..8197ebd78b0 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h @@ -50,6 +50,7 @@ class MaterialBrowserBundleModel : public QAbstractListModel Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged) Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged) Q_PROPERTY(bool hasMaterialRoot READ hasMaterialRoot WRITE setHasMaterialRoot NOTIFY hasMaterialRootChanged) + Q_PROPERTY(bool importerRunning MEMBER m_importerRunning NOTIFY importerRunningChanged) public: MaterialBrowserBundleModel(QObject *parent = nullptr); @@ -59,6 +60,7 @@ public: QHash roleNames() const override; void setSearchText(const QString &searchText); + void updateImportedState(const QStringList &importedMats); bool hasQuick3DImport() const; void setHasQuick3DImport(bool b); @@ -66,10 +68,13 @@ public: bool hasMaterialRoot() const; void setHasMaterialRoot(bool b); + Internal::BundleImporter *bundleImporter() const; + void resetModel(); Q_INVOKABLE void applyToSelected(QmlDesigner::BundleMaterial *mat, bool add = false); - Q_INVOKABLE void addMaterial(QmlDesigner::BundleMaterial *mat); + Q_INVOKABLE void addToProject(QmlDesigner::BundleMaterial *mat); + Q_INVOKABLE void removeFromProject(QmlDesigner::BundleMaterial *mat); signals: void isEmptyChanged(); @@ -77,7 +82,10 @@ signals: void hasMaterialRootChanged(); void materialVisibleChanged(); void applyToSelectedTriggered(QmlDesigner::BundleMaterial *mat, bool add = false); - void addBundleMaterialToProjectRequested(const QmlDesigner::NodeMetaInfo &metaInfo); + void bundleMaterialImported(const QmlDesigner::NodeMetaInfo &metaInfo); + void bundleMaterialAboutToUnimport(const QmlDesigner::TypeName &type); + void bundleMaterialUnimported(const QmlDesigner::NodeMetaInfo &metaInfo); + void importerRunningChanged(); private: void loadMaterialBundle(); @@ -93,6 +101,7 @@ private: bool m_hasMaterialRoot = false; bool m_matBundleExists = false; bool m_probeMatBundleDir = false; + bool m_importerRunning = false; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp index c075a582e41..58c867782dc 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp @@ -51,6 +51,9 @@ QVariant MaterialBrowserModel::data(const QModelIndex &index, int role) const return matType; } + if (roleName == "hasDynamicProperties") + return !m_materialList.at(index.row()).dynamicProperties().isEmpty(); + return {}; } @@ -121,7 +124,8 @@ QHash MaterialBrowserModel::roleNames() const {Qt::UserRole + 1, "materialName"}, {Qt::UserRole + 2, "materialInternalId"}, {Qt::UserRole + 3, "materialVisible"}, - {Qt::UserRole + 4, "materialType"} + {Qt::UserRole + 4, "materialType"}, + {Qt::UserRole + 5, "hasDynamicProperties"} }; return roles; } @@ -338,33 +342,47 @@ void MaterialBrowserModel::copyMaterialProperties(int idx, const QString §io setCopiedMaterialType(matType); m_allPropsCopied = section == "All"; + bool dynamicPropsCopied = section == "Custom"; QmlObjectNode mat(m_copiedMaterial); QSet validProps; + QHash dynamicProps; PropertyNameList copiedProps; - // Base state properties are always valid - const auto baseProps = m_copiedMaterial.propertyNames(); - for (const auto &baseProp : baseProps) - validProps.insert(baseProp); - - if (!mat.isInBaseState()) { - QmlPropertyChanges changes = mat.propertyChangeForCurrentState(); - if (changes.isValid()) { - const QList changedProps = changes.targetProperties(); - for (const auto &changedProp : changedProps) - validProps.insert(changedProp.name()); + if (dynamicPropsCopied || m_allPropsCopied) { + // Dynamic properties must always be set in base state + const QList dynProps = m_copiedMaterial.dynamicProperties(); + for (const auto &prop : dynProps) { + dynamicProps.insert(prop.name(), prop.dynamicTypeName()); + validProps.insert(prop.name()); } } - if (mat.timelineIsActive()) { - const QList keyframeGroups - = mat.currentTimeline().keyframeGroupsForTarget(m_copiedMaterial); - for (const auto &kfg : keyframeGroups) - validProps.insert(kfg.propertyName()); - } + if (!dynamicPropsCopied) { + // Base state properties are always valid + const auto baseProps = m_copiedMaterial.propertyNames(); + for (const auto &baseProp : baseProps) + validProps.insert(baseProp); - if (m_allPropsCopied || m_propertyGroupsObj.empty()) { + if (!mat.isInBaseState()) { + QmlPropertyChanges changes = mat.propertyChangeForCurrentState(); + if (changes.isValid()) { + const QList changedProps = changes.targetProperties(); + for (const auto &changedProp : changedProps) + validProps.insert(changedProp.name()); + } + } + + if (mat.timelineIsActive()) { + const QList keyframeGroups + = mat.currentTimeline().keyframeGroupsForTarget(m_copiedMaterial); + for (const auto &kfg : keyframeGroups) + validProps.insert(kfg.propertyName()); + } + } + validProps.remove("objectName"); + + if (m_allPropsCopied || dynamicPropsCopied || m_propertyGroupsObj.empty()) { copiedProps = validProps.values(); } else { QJsonObject propsSpecObj = m_propertyGroupsObj.value(m_copiedMaterialType).toObject(); @@ -389,8 +407,10 @@ void MaterialBrowserModel::copyMaterialProperties(int idx, const QString §io PropertyCopyData data; data.name = propName; data.isValid = m_allPropsCopied || validProps.contains(propName); - data.isBinding = mat.hasBindingProperty(propName); if (data.isValid) { + if (dynamicProps.contains(propName)) + data.dynamicTypeName = dynamicProps[propName]; + data.isBinding = mat.hasBindingProperty(propName); if (data.isBinding) data.value = mat.expression(propName); else diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h index 8635c1f1b02..2e4a4fc5cba 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.h @@ -75,6 +75,7 @@ public: struct PropertyCopyData { PropertyName name; + TypeName dynamicTypeName; QVariant value; bool isBinding = false; bool isValid = false; diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp index 3f645ed721e..c3e47eb6f8e 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp @@ -2,8 +2,10 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "materialbrowserview.h" + #include "bindingproperty.h" #include "bundlematerial.h" +#include "bundleimporter.h" #include "materialbrowsermodel.h" #include "materialbrowserwidget.h" #include "materialbrowserbundlemodel.h" @@ -18,6 +20,7 @@ #include #include #include +#include #include #include @@ -83,13 +86,19 @@ WidgetInfo MaterialBrowserView::widgetInfo() // remove current properties PropertyNameList propNames; if (mat.isInBaseState()) { - propNames = material.propertyNames(); + const QList baseProps = material.properties(); + for (const auto &baseProp : baseProps) { + if (!baseProp.isDynamic()) + propNames.append(baseProp.name()); + } } else { QmlPropertyChanges changes = mat.propertyChangeForCurrentState(); if (changes.isValid()) { const QList changedProps = changes.targetProperties(); - for (const auto &changedProp : changedProps) - propNames.append(changedProp.name()); + for (const auto &changedProp : changedProps) { + if (!changedProp.isDynamic()) + propNames.append(changedProp.name()); + } } } for (const PropertyName &propName : std::as_const(propNames)) { @@ -100,14 +109,29 @@ WidgetInfo MaterialBrowserView::widgetInfo() // apply pasted properties for (const QmlDesigner::MaterialBrowserModel::PropertyCopyData &propData : propDatas) { - if (propData.name == "objectName") - continue; - if (propData.isValid) { - if (propData.isBinding) + const bool isDynamic = !propData.dynamicTypeName.isEmpty(); + const bool isBaseState = currentState().isBaseState(); + const bool hasProperty = mat.hasProperty(propData.name); + if (propData.isBinding) { + if (isDynamic && (!hasProperty || isBaseState)) { + mat.modelNode().bindingProperty(propData.name) + .setDynamicTypeNameAndExpression( + propData.dynamicTypeName, propData.value.toString()); + continue; + } mat.setBindingProperty(propData.name, propData.value.toString()); - else + } else { + const bool isRecording = mat.timelineIsActive() + && mat.currentTimeline().isRecording(); + if (isDynamic && (!hasProperty || (isBaseState && !isRecording))) { + mat.modelNode().variantProperty(propData.name) + .setDynamicTypeNameAndValue( + propData.dynamicTypeName, propData.value); + continue; + } mat.setVariantProperty(propData.name, propData.value); + } } else { mat.removeProperty(propData.name); } @@ -124,23 +148,38 @@ WidgetInfo MaterialBrowserView::widgetInfo() connect(matBrowserBundleModel, &MaterialBrowserBundleModel::applyToSelectedTriggered, this, [&] (BundleMaterial *bundleMat, bool add) { - if (!m_selectedModel.isValid()) + if (m_selectedModels.isEmpty()) return; - m_bundleMaterialDropTarget = m_selectedModel; + m_bundleMaterialTargets = m_selectedModels; m_bundleMaterialAddToSelected = add; ModelNode defaultMat = getBundleMaterialDefaultInstance(bundleMat->type()); if (defaultMat.isValid()) applyBundleMaterialToDropTarget(defaultMat); else - m_widget->materialBrowserBundleModel()->addMaterial(bundleMat); + m_widget->materialBrowserBundleModel()->addToProject(bundleMat); }); - connect(matBrowserBundleModel, &MaterialBrowserBundleModel::addBundleMaterialToProjectRequested, this, + connect(matBrowserBundleModel, &MaterialBrowserBundleModel::bundleMaterialImported, this, [&] (const QmlDesigner::NodeMetaInfo &metaInfo) { applyBundleMaterialToDropTarget({}, metaInfo); + updateBundleMaterialsImportedState(); }); + + connect(matBrowserBundleModel, &MaterialBrowserBundleModel::bundleMaterialAboutToUnimport, this, + [&] (const QmlDesigner::TypeName &type) { + // delete instances of the bundle material that is about to be unimported + executeInTransaction("MaterialBrowserView::widgetInfo", [&] { + Utils::reverseForeach(m_widget->materialBrowserModel()->materials(), [&](const ModelNode &mat) { + if (mat.isValid() && mat.type() == type) + QmlObjectNode(mat).destroy(); + }); + }); + }); + + connect(matBrowserBundleModel, &MaterialBrowserBundleModel::bundleMaterialUnimported, this, + &MaterialBrowserView::updateBundleMaterialsImportedState); } return createWidgetInfo(m_widget.data(), @@ -180,40 +219,42 @@ void MaterialBrowserView::applyBundleMaterialToDropTarget(const ModelNode &bundl newMatNode = bundleMat; } - if (m_bundleMaterialDropTarget.isValid() - && m_bundleMaterialDropTarget.metaInfo().isQtQuick3DModel()) { - QmlObjectNode qmlObjNode(m_bundleMaterialDropTarget); - if (m_bundleMaterialAddToSelected) { - // TODO: unify this logic as it exist elsewhere also - auto expToList = [](const QString &exp) { - QString copy = exp; - copy = copy.remove("[").remove("]"); + // TODO: unify this logic as it exist elsewhere also + auto expToList = [](const QString &exp) { + QString copy = exp; + copy = copy.remove("[").remove("]"); - QStringList tmp = copy.split(',', Qt::SkipEmptyParts); - for (QString &str : tmp) - str = str.trimmed(); + QStringList tmp = copy.split(',', Qt::SkipEmptyParts); + for (QString &str : tmp) + str = str.trimmed(); - return tmp; - }; + return tmp; + }; - auto listToExp = [](QStringList &stringList) { - if (stringList.size() > 1) - return QString("[" + stringList.join(",") + "]"); + auto listToExp = [](QStringList &stringList) { + if (stringList.size() > 1) + return QString("[" + stringList.join(",") + "]"); - if (stringList.size() == 1) - return stringList.first(); + if (stringList.size() == 1) + return stringList.first(); - return QString(); - }; - QStringList matList = expToList(qmlObjNode.expression("materials")); - matList.append(newMatNode.id()); - QString updatedExp = listToExp(matList); - qmlObjNode.setBindingProperty("materials", updatedExp); - } else { - qmlObjNode.setBindingProperty("materials", newMatNode.id()); + return QString(); + }; + + for (const ModelNode &target : std::as_const(m_bundleMaterialTargets)) { + if (target.isValid() && target.metaInfo().isQtQuick3DModel()) { + QmlObjectNode qmlObjNode(target); + if (m_bundleMaterialAddToSelected) { + QStringList matList = expToList(qmlObjNode.expression("materials")); + matList.append(newMatNode.id()); + QString updatedExp = listToExp(matList); + qmlObjNode.setBindingProperty("materials", updatedExp); + } else { + qmlObjNode.setBindingProperty("materials", newMatNode.id()); + } } - m_bundleMaterialDropTarget = {}; + m_bundleMaterialTargets = {}; m_bundleMaterialAddToSelected = false; } }); @@ -228,6 +269,7 @@ void MaterialBrowserView::modelAttached(Model *model) rootModelNode().metaInfo().isQtQuick3DMaterial()); m_hasQuick3DImport = model->hasImport("QtQuick3D"); + updateBundleMaterialsImportedState(); // Project load is already very busy and may even trigger puppet reset, so let's wait a moment // before refreshing the model @@ -276,24 +318,20 @@ void MaterialBrowserView::modelAboutToBeDetached(Model *model) void MaterialBrowserView::selectedNodesChanged(const QList &selectedNodeList, [[maybe_unused]] const QList &lastSelectedNodeList) { - m_selectedModel = {}; + m_selectedModels = Utils::filtered(selectedNodeList, [](const ModelNode &node) { + return node.metaInfo().isQtQuick3DModel(); + }); - for (const ModelNode &node : selectedNodeList) { - if (node.metaInfo().isQtQuick3DModel()) { - m_selectedModel = node; - break; - } - } - - m_widget->materialBrowserModel()->setHasModelSelection(m_selectedModel.isValid()); + m_widget->materialBrowserModel()->setHasModelSelection(!m_selectedModels.isEmpty()); + // the logic below selects the material of the first selected model if auto selection is on if (!m_autoSelectModelMaterial) return; - if (selectedNodeList.size() > 1 || !m_selectedModel.isValid()) + if (selectedNodeList.size() > 1 || m_selectedModels.isEmpty()) return; - QmlObjectNode qmlObjNode(m_selectedModel); + QmlObjectNode qmlObjNode(m_selectedModels.at(0)); QString matExp = qmlObjNode.expression("materials"); if (matExp.isEmpty()) return; @@ -387,6 +425,25 @@ void QmlDesigner::MaterialBrowserView::loadPropertyGroups() m_propertyGroupsLoaded = m_widget->materialBrowserModel()->loadPropertyGroups(matPropsPath); } +void MaterialBrowserView::updateBundleMaterialsImportedState() +{ + using namespace Utils; + + if (!m_widget->materialBrowserBundleModel()->bundleImporter()) + return; + + QStringList importedBundleMats; + + FilePath materialBundlePath = m_widget->materialBrowserBundleModel()->bundleImporter()->resolveBundleImportPath(); + + if (materialBundlePath.exists()) { + importedBundleMats = transform(materialBundlePath.dirEntries({{"*.qml"}, QDir::Files}), + [](const FilePath &f) { return f.fileName().chopped(4); }); + } + + m_widget->materialBrowserBundleModel()->updateImportedState(importedBundleMats); +} + ModelNode MaterialBrowserView::getBundleMaterialDefaultInstance(const TypeName &type) { const QList materials = m_widget->materialBrowserModel()->materials(); @@ -444,13 +501,13 @@ void MaterialBrowserView::customNotification(const AbstractView *view, } else if (identifier == "delete_selected_material") { m_widget->materialBrowserModel()->deleteSelectedMaterial(); } else if (identifier == "drop_bundle_material") { - m_bundleMaterialDropTarget = nodeList.first(); + m_bundleMaterialTargets = nodeList; ModelNode defaultMat = getBundleMaterialDefaultInstance(m_draggedBundleMaterial->type()); if (defaultMat.isValid()) applyBundleMaterialToDropTarget(defaultMat); else - m_widget->materialBrowserBundleModel()->addMaterial(m_draggedBundleMaterial); + m_widget->materialBrowserBundleModel()->addToProject(m_draggedBundleMaterial); m_draggedBundleMaterial = nullptr; } diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h index ebd6f637c9d..8b5b56a622f 100644 --- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h +++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h @@ -46,13 +46,15 @@ private: void refreshModel(bool updateImages); bool isMaterial(const ModelNode &node) const; void loadPropertyGroups(); + void updateBundleMaterialsImportedState(); void applyBundleMaterialToDropTarget(const ModelNode &bundleMat, const NodeMetaInfo &metaInfo = {}); ModelNode getBundleMaterialDefaultInstance(const TypeName &type); QPointer m_widget; - ModelNode m_bundleMaterialDropTarget; - ModelNode m_selectedModel; // first selected 3D model node + QList m_bundleMaterialTargets; + QList m_selectedModels; // selected 3D model nodes BundleMaterial *m_draggedBundleMaterial = nullptr; + bool m_bundleMaterialAddToSelected = false; bool m_hasQuick3DImport = false; bool m_autoSelectModelMaterial = false; // TODO: wire this to some action diff --git a/src/plugins/qmldesigner/designercore/include/modelnode.h b/src/plugins/qmldesigner/designercore/include/modelnode.h index 2ab045716a8..80a26a7b985 100644 --- a/src/plugins/qmldesigner/designercore/include/modelnode.h +++ b/src/plugins/qmldesigner/designercore/include/modelnode.h @@ -129,6 +129,7 @@ public: QList nodeListProperties() const; QList bindingProperties() const; QList signalProperties() const; + QList dynamicProperties() const; PropertyNameList propertyNames() const; bool hasProperties() const; diff --git a/src/plugins/qmldesigner/designercore/model/modelnode.cpp b/src/plugins/qmldesigner/designercore/model/modelnode.cpp index efdbdaad198..09a5d653b30 100644 --- a/src/plugins/qmldesigner/designercore/model/modelnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/modelnode.cpp @@ -623,6 +623,18 @@ QList ModelNode::signalProperties() const return propertyList; } +QList ModelNode::dynamicProperties() const +{ + QList propertyList; + + const QList abstractProperties = properties(); + for (const AbstractProperty &abstractProperty : abstractProperties) { + if (abstractProperty.isDynamic()) + propertyList.append(abstractProperty); + } + return propertyList; +} + /*! \brief removes a property from this node \param name name of the property diff --git a/src/plugins/qmlprofiler/qml/QmlProfilerFlameGraphView.qml b/src/plugins/qmlprofiler/qml/QmlProfilerFlameGraphView.qml index 9926ce460b1..40c95a14cf0 100644 --- a/src/plugins/qmlprofiler/qml/QmlProfilerFlameGraphView.qml +++ b/src/plugins/qmlprofiler/qml/QmlProfilerFlameGraphView.qml @@ -28,7 +28,6 @@ FlameGraphView { QmlProfilerFlameGraphModel.CallCountRole, qsTranslate("QmlProfiler", "Calls"), QmlProfilerFlameGraphModel.DetailsRole, qsTranslate("QmlProfiler", "Details"), QmlProfilerFlameGraphModel.TimePerCallRole, qsTranslate("QmlProfiler", "Mean Time"), - QmlProfilerFlameGraphModel.TimeInPercentRole, qsTranslate("QmlProfiler", "In Percent"), QmlProfilerFlameGraphModel.LocationRole, qsTranslate("QmlProfiler", "Location"), QmlProfilerFlameGraphModel.AllocationsRole, qsTranslate("QmlProfiler", "Allocations"), QmlProfilerFlameGraphModel.MemoryRole, qsTranslate("QmlProfiler", "Memory") diff --git a/src/plugins/qmlprofiler/qmlprofilertextmark.cpp b/src/plugins/qmlprofiler/qmlprofilertextmark.cpp index c35a18a8926..2775c5771bf 100644 --- a/src/plugins/qmlprofiler/qmlprofilertextmark.cpp +++ b/src/plugins/qmlprofiler/qmlprofilertextmark.cpp @@ -34,13 +34,6 @@ void QmlProfilerTextMark::addTypeId(int typeId) setLineAnnotation(statisticsView->summary(m_typeIds)); } -void QmlProfilerTextMark::clicked() -{ - int typeId = m_typeIds.takeFirst(); - m_typeIds.append(typeId); - emit m_viewManager->typeSelected(typeId); -} - QmlProfilerTextMarkModel::QmlProfilerTextMarkModel(QObject *parent) : QObject(parent) { } diff --git a/src/plugins/qmlprofiler/qmlprofilertextmark.h b/src/plugins/qmlprofiler/qmlprofilertextmark.h index de049afbf24..75ed95ded76 100644 --- a/src/plugins/qmlprofiler/qmlprofilertextmark.h +++ b/src/plugins/qmlprofiler/qmlprofilertextmark.h @@ -17,8 +17,6 @@ public: const Utils::FilePath &fileName, int lineNumber); void addTypeId(int typeId); - void clicked() override; - bool isClickable() const override { return true; } bool addToolTipContent(QLayout *target) const override; private: diff --git a/src/plugins/qtsupport/qtoptionspage.cpp b/src/plugins/qtsupport/qtoptionspage.cpp index ada5f3c842e..f1cbc250b3b 100644 --- a/src/plugins/qtsupport/qtoptionspage.cpp +++ b/src/plugins/qtsupport/qtoptionspage.cpp @@ -236,6 +236,7 @@ QtOptionsPageWidget::QtOptionsPageWidget() resize(446, 450); m_qtdirList = new QTreeView(this); + m_qtdirList->setObjectName("qtDirList"); m_qtdirList->setUniformRowHeights(true); m_versionInfoWidget = new DetailsWidget(this); @@ -911,9 +912,9 @@ void QtOptionsPageWidget::apply() &QtOptionsPageWidget::updateQtVersions); } -// TODO whenever we move the output of sdktool to a different location in the installer, -// this needs to be adapted accordingly const QStringList kSubdirsToCheck = {"", + "Tools/sdktool", // macOS + "Tools/sdktool/share/qtcreator", // Windows/Linux "Qt Creator.app/Contents/Resources", "Contents/Resources", "Tools/QtCreator/share/qtcreator", diff --git a/src/plugins/remotelinux/abstractremotelinuxdeploystep.h b/src/plugins/remotelinux/abstractremotelinuxdeploystep.h index 69c1983994a..4946a0849bb 100644 --- a/src/plugins/remotelinux/abstractremotelinuxdeploystep.h +++ b/src/plugins/remotelinux/abstractremotelinuxdeploystep.h @@ -32,17 +32,9 @@ protected: void setInternalInitializer(const std::function &init); void setRunPreparer(const std::function &prep); - - template - T *createDeployService() - { - T *service = new T; - setDeployService(service); - return service; - } + void setDeployService(AbstractRemoteLinuxDeployService *service); private: - void setDeployService(AbstractRemoteLinuxDeployService *service); void handleProgressMessage(const QString &message); void handleErrorMessage(const QString &message); void handleWarningMessage(const QString &message); diff --git a/src/plugins/remotelinux/customcommanddeploystep.cpp b/src/plugins/remotelinux/customcommanddeploystep.cpp index 4bc5a715283..8a7104c42df 100644 --- a/src/plugins/remotelinux/customcommanddeploystep.cpp +++ b/src/plugins/remotelinux/customcommanddeploystep.cpp @@ -92,7 +92,8 @@ public: CustomCommandDeployStep(BuildStepList *bsl, Id id) : AbstractRemoteLinuxDeployStep(bsl, id) { - auto service = createDeployService(); + auto service = new CustomCommandDeployService; + setDeployService(service); auto commandLine = addAspect(); commandLine->setSettingsKey("RemoteLinuxCustomCommandDeploymentStep.CommandLine"); diff --git a/src/plugins/remotelinux/genericdirectuploadstep.cpp b/src/plugins/remotelinux/genericdirectuploadstep.cpp index 85383f1e213..c01b21c3384 100644 --- a/src/plugins/remotelinux/genericdirectuploadstep.cpp +++ b/src/plugins/remotelinux/genericdirectuploadstep.cpp @@ -20,7 +20,8 @@ GenericDirectUploadStep::GenericDirectUploadStep(BuildStepList *bsl, Utils::Id i bool offerIncrementalDeployment) : AbstractRemoteLinuxDeployStep(bsl, id) { - auto service = createDeployService(); + auto service = new GenericDirectUploadService; + setDeployService(service); BoolAspect *incremental = nullptr; if (offerIncrementalDeployment) { diff --git a/src/plugins/remotelinux/killappstep.cpp b/src/plugins/remotelinux/killappstep.cpp index af19055d509..09670155961 100644 --- a/src/plugins/remotelinux/killappstep.cpp +++ b/src/plugins/remotelinux/killappstep.cpp @@ -106,7 +106,8 @@ class KillAppStep : public AbstractRemoteLinuxDeployStep public: KillAppStep(BuildStepList *bsl, Id id) : AbstractRemoteLinuxDeployStep(bsl, id) { - auto service = createDeployService(); + auto service = new Internal::KillAppService; + setDeployService(service); setWidgetExpandedByDefault(false); diff --git a/src/plugins/remotelinux/linuxdevice.cpp b/src/plugins/remotelinux/linuxdevice.cpp index 60959d99291..6aee5591945 100644 --- a/src/plugins/remotelinux/linuxdevice.cpp +++ b/src/plugins/remotelinux/linuxdevice.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -349,6 +350,20 @@ private: // LinuxDevicePrivate class ShellThreadHandler; +class LinuxDevicePrivate; + +class LinuxDeviceFileAccess : public UnixDeviceFileAccess +{ +public: + LinuxDeviceFileAccess(LinuxDevicePrivate *dev) + : m_dev(dev) + {} + + RunResult runInShell(const CommandLine &cmdLine, + const QByteArray &stdInData) const override; + + LinuxDevicePrivate *m_dev; +}; class LinuxDevicePrivate { @@ -358,9 +373,6 @@ public: bool setupShell(); RunResult runInShell(const CommandLine &cmd, const QByteArray &stdInData = {}); - bool runInShellSuccess(const CommandLine &cmd, const QByteArray &stdInData = {}) { - return runInShell(cmd, stdInData).exitCode == 0; - } void attachToSharedConnection(SshConnectionHandle *connectionHandle, const SshParameters &sshParameters); @@ -370,9 +382,15 @@ public: ShellThreadHandler *m_handler = nullptr; mutable QMutex m_shellMutex; QList m_terminals; - bool m_useFind = true; + LinuxDeviceFileAccess m_fileAccess{this}; }; +RunResult LinuxDeviceFileAccess::runInShell(const CommandLine &cmdLine, + const QByteArray &stdInData) const +{ + return m_dev->runInShell(cmdLine, stdInData); +} + // SshProcessImpl class SshProcessInterfacePrivate : public QObject @@ -465,7 +483,7 @@ qint64 SshProcessInterface::processId() const bool SshProcessInterface::runInShell(const CommandLine &command, const QByteArray &data) { - return d->m_devicePrivate->runInShellSuccess(command, data); + return d->m_devicePrivate->runInShell(command, data).exitCode == 0; } void SshProcessInterface::start() @@ -904,6 +922,7 @@ private: LinuxDevice::LinuxDevice() : d(new LinuxDevicePrivate(this)) { + setFileAccess(&d->m_fileAccess); setDisplayType(Tr::tr("Remote Linux")); setDefaultDisplayName(Tr::tr("Remote Linux Device")); setOsType(OsTypeLinux); @@ -1121,232 +1140,6 @@ void LinuxDevicePrivate::attachToSharedConnection(SshConnectionHandle *connectio emit connectionHandle->connected(socketFilePath); } -bool LinuxDevice::isExecutableFile(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"test", {"-x", path}}); -} - -bool LinuxDevice::isReadableFile(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"test", {"-r", path, "-a", "-f", path}}); -} - -bool LinuxDevice::isWritableFile(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"test", {"-w", path, "-a", "-f", path}}); -} - -bool LinuxDevice::isReadableDirectory(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"test", {"-r", path, "-a", "-d", path}}); -} - -bool LinuxDevice::isWritableDirectory(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"test", {"-w", path, "-a", "-d", path}}); -} - -bool LinuxDevice::isFile(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"test", {"-f", path}}); -} - -bool LinuxDevice::isDirectory(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"test", {"-d", path}}); -} - -bool LinuxDevice::createDirectory(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"mkdir", {"-p", path}}); -} - -bool LinuxDevice::exists(const FilePath &filePath) const -{ - DEBUG("filepath " << filePath.path()); - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"test", {"-e", path}}); -} - -bool LinuxDevice::ensureExistingFile(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - const QString path = filePath.path(); - return d->runInShellSuccess({"touch", {path}}); -} - -bool LinuxDevice::removeFile(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - return d->runInShellSuccess({"rm", {filePath.path()}}); -} - -bool LinuxDevice::removeRecursively(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - QTC_ASSERT(filePath.path().startsWith('/'), return false); - - const QString path = filePath.cleanPath().path(); - // We are expecting this only to be called in a context of build directories or similar. - // Chicken out in some cases that _might_ be user code errors. - QTC_ASSERT(path.startsWith('/'), return false); - const int levelsNeeded = path.startsWith("/home/") ? 3 : 2; - QTC_ASSERT(path.count('/') >= levelsNeeded, return false); - - return d->runInShellSuccess({"rm", {"-rf", "--", path}}); -} - -bool LinuxDevice::copyFile(const FilePath &filePath, const FilePath &target) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - QTC_ASSERT(handlesFile(target), return false); - return d->runInShellSuccess({"cp", {filePath.path(), target.path()}}); -} - -bool LinuxDevice::renameFile(const FilePath &filePath, const FilePath &target) const -{ - QTC_ASSERT(handlesFile(filePath), return false); - QTC_ASSERT(handlesFile(target), return false); - return d->runInShellSuccess({"mv", {filePath.path(), target.path()}}); -} - -QDateTime LinuxDevice::lastModified(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - const RunResult result = d->runInShell({"stat", {"-L", "-c", "%Y", filePath.path()}}); - const qint64 secs = result.stdOut.toLongLong(); - const QDateTime dt = QDateTime::fromSecsSinceEpoch(secs, Qt::UTC); - return dt; -} - -FilePath LinuxDevice::symLinkTarget(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - const RunResult result = d->runInShell({"readlink", {"-n", "-e", filePath.path()}}); - return result.stdOut.isEmpty() ? FilePath() - : filePath.withNewPath(QString::fromUtf8(result.stdOut)); -} - -qint64 LinuxDevice::fileSize(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return -1); - const RunResult result = d->runInShell({"stat", {"-L", "-c", "%s", filePath.path()}}); - return result.stdOut.toLongLong(); -} - -qint64 LinuxDevice::bytesAvailable(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return -1); - CommandLine cmd("df", {"-k"}); - cmd.addArg(filePath.path()); - const RunResult result = d->runInShell(cmd); - return FileUtils::bytesAvailableFromDFOutput(result.stdOut); -} - -QFileDevice::Permissions LinuxDevice::permissions(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - const RunResult result = d->runInShell({"stat", {"-L", "-c", "%a", filePath.path()}}); - const uint bits = result.stdOut.toUInt(nullptr, 8); - QFileDevice::Permissions perm = {}; -#define BIT(n, p) if (bits & (1<runInShellSuccess({"chmod", {QString::number(flags, 16), filePath.path()}}); -} - -void LinuxDevice::iterateDirectory(const FilePath &filePath, - const FilePath::IterateDirCallback &callBack, - const FileFilter &filter) const -{ - QTC_ASSERT(handlesFile(filePath), return); - auto runInShell = [this](const CommandLine &cmd) { return d->runInShell(cmd); }; - FileUtils::iterateUnixDirectory(filePath, filter, &d->m_useFind, runInShell, callBack); -} - -void LinuxDevice::iterateDirectory(const FilePath &filePath, - const FilePath::IterateDirWithInfoCallback &callBack, - const FileFilter &filter) const -{ - QTC_ASSERT(handlesFile(filePath), return); - auto runInShell = [this](const CommandLine &cmd) { return d->runInShell(cmd); }; - FileUtils::iterateUnixDirectory(filePath, filter, &d->m_useFind, runInShell, callBack); -} - -FilePathInfo LinuxDevice::filePathInfo(const FilePath &filePath) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - const RunResult stat = d->runInShell({"stat", {"-L", "-c", "%f %Y %s", filePath.path()}}); - return FileUtils::filePathInfoFromTriple(QString::fromLatin1(stat.stdOut)); -} - -std::optional LinuxDevice::fileContents(const FilePath &filePath, - qint64 limit, - qint64 offset) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - QString args = "if=" + filePath.path() + " status=none"; - if (limit > 0 || offset > 0) { - const qint64 gcd = std::gcd(limit, offset); - args += QString(" bs=%1 count=%2 seek=%3").arg(gcd).arg(limit / gcd).arg(offset / gcd); - } - CommandLine cmd(FilePath::fromString("dd"), args, CommandLine::Raw); - - const RunResult result = d->runInShell(cmd); - if (result.exitCode != 0) { - DEBUG("fileContents failed"); - return {}; - } - - DEBUG(result.stdOut << QByteArray::fromHex(result.stdOut)); - return result.stdOut; -} - -bool LinuxDevice::writeFileContents(const FilePath &filePath, - const QByteArray &data, - qint64 offset) const -{ - QTC_ASSERT(handlesFile(filePath), return {}); - CommandLine cmd({"dd", {"of=" + filePath.path()}}); - if (offset != 0) { - cmd.addArg("bs=1"); - cmd.addArg(QString("seek=%1").arg(offset)); - } - return d->runInShellSuccess(cmd, data); -} - static FilePaths dirsToCreate(const FilesToTransfer &files) { FilePaths dirs; @@ -1635,12 +1428,73 @@ private: int m_currentIndex = 0; }; +class GenericTransferImpl : public FileTransferInterface +{ +public: + GenericTransferImpl(const FileTransferSetupData &setup, LinuxDevicePrivate *) + : FileTransferInterface(setup) + {} + +private: + void start() final + { + m_fileCount = m_setup.m_files.size(); + m_currentIndex = 0; + m_checkedDirectories.clear(); + nextFile(); + } + + void nextFile() + { + ProcessResultData result; + if (m_currentIndex >= m_fileCount) { + emit done(result); + return; + } + + const FileToTransfer &file = m_setup.m_files.at(m_currentIndex); + const FilePath &source = file.m_source; + const FilePath &target = file.m_target; + ++m_currentIndex; + + const FilePath targetDir = target.parentDir(); + if (!m_checkedDirectories.contains(targetDir)) { + emit progress(tr("Creating directory: %1") + .arg(targetDir.toUserOutput())); + if (!targetDir.ensureWritableDir()) { + result.m_errorString = tr("Failed."); + result.m_exitCode = -1; // Random pick + emit done(result); + return; + } + m_checkedDirectories.insert(targetDir); + } + + emit progress(tr("Copying %1/%2: %3 -> %4") + .arg(m_currentIndex).arg(m_fileCount).arg(source.toUserOutput(), target.toUserOutput())); + if (!source.copyFile(target)) { + result.m_errorString = tr("Failed."); + result.m_exitCode = -1; // Random pick + emit done(result); + return; + } + + // FIXME: Use asyncCopyFile instead + nextFile(); + } + + int m_currentIndex = 0; + int m_fileCount = 0; + QSet m_checkedDirectories; +}; + FileTransferInterface *LinuxDevice::createFileTransferInterface( const FileTransferSetupData &setup) const { switch (setup.m_method) { case FileTransferMethod::Sftp: return new SftpTransferImpl(setup, d); case FileTransferMethod::Rsync: return new RsyncTransferImpl(setup, d); + case FileTransferMethod::GenericCopy: return new GenericTransferImpl(setup, d); } QTC_CHECK(false); return {}; diff --git a/src/plugins/remotelinux/linuxdevice.h b/src/plugins/remotelinux/linuxdevice.h index 2e9aeee3616..51517ed8622 100644 --- a/src/plugins/remotelinux/linuxdevice.h +++ b/src/plugins/remotelinux/linuxdevice.h @@ -37,43 +37,11 @@ public: Utils::FilePath rootPath() const override; bool handlesFile(const Utils::FilePath &filePath) const override; - bool isExecutableFile(const Utils::FilePath &filePath) const override; - bool isReadableFile(const Utils::FilePath &filePath) const override; - bool isWritableFile(const Utils::FilePath &filePath) const override; - bool isReadableDirectory(const Utils::FilePath &filePath) const override; - bool isWritableDirectory(const Utils::FilePath &filePath) const override; - bool isFile(const Utils::FilePath &filePath) const override; - bool isDirectory(const Utils::FilePath &filePath) const override; - bool createDirectory(const Utils::FilePath &filePath) const override; - bool exists(const Utils::FilePath &filePath) const override; - bool ensureExistingFile(const Utils::FilePath &filePath) const override; - bool removeFile(const Utils::FilePath &filePath) const override; - bool removeRecursively(const Utils::FilePath &filePath) const override; - bool copyFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override; - bool renameFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override; - Utils::FilePath symLinkTarget(const Utils::FilePath &filePath) const override; - void iterateDirectory(const Utils::FilePath &filePath, - const Utils::FilePath::IterateDirCallback &callBack, - const Utils::FileFilter &filter) const override; - void iterateDirectory(const Utils::FilePath &filePath, - const Utils::FilePath::IterateDirWithInfoCallback &callBack, - const Utils::FileFilter &filter) const override; - std::optional fileContents(const Utils::FilePath &filePath, - qint64 limit, - qint64 offset) const override; - bool writeFileContents(const Utils::FilePath &filePath, - const QByteArray &data, - qint64 offset) const override; - Utils::FilePathInfo filePathInfo(const Utils::FilePath &filePath) const override; - QDateTime lastModified(const Utils::FilePath &filePath) const override; + Utils::ProcessInterface *createProcessInterface() const override; ProjectExplorer::FileTransferInterface *createFileTransferInterface( const ProjectExplorer::FileTransferSetupData &setup) const override; Utils::Environment systemEnvironment() const override; - qint64 fileSize(const Utils::FilePath &filePath) const override; - qint64 bytesAvailable(const Utils::FilePath &filePath) const override; - QFileDevice::Permissions permissions(const Utils::FilePath &filePath) const override; - bool setPermissions(const Utils::FilePath &filePath, QFileDevice::Permissions permissions) const override; protected: LinuxDevice(); diff --git a/src/plugins/remotelinux/linuxdevicetester.cpp b/src/plugins/remotelinux/linuxdevicetester.cpp index 32875a54e28..e03b322bff3 100644 --- a/src/plugins/remotelinux/linuxdevicetester.cpp +++ b/src/plugins/remotelinux/linuxdevicetester.cpp @@ -236,6 +236,7 @@ void GenericLinuxDeviceTester::testFileTransfer(FileTransferMethod method) switch (method) { case FileTransferMethod::Sftp: d->state = TestingSftp; break; case FileTransferMethod::Rsync: d->state = TestingRsync; break; + case FileTransferMethod::GenericCopy: QTC_CHECK(false) /* not tested */; break; } emit progressMessage(Tr::tr("Checking whether \"%1\" works...") .arg(FileTransfer::transferMethodName(method))); diff --git a/src/plugins/remotelinux/makeinstallstep.cpp b/src/plugins/remotelinux/makeinstallstep.cpp index bdd3abdfe7f..7fa08a14e17 100644 --- a/src/plugins/remotelinux/makeinstallstep.cpp +++ b/src/plugins/remotelinux/makeinstallstep.cpp @@ -187,32 +187,22 @@ bool MakeInstallStep::init() void MakeInstallStep::finish(bool success) { if (success) { - const bool hack = makeCommand().needsDevice(); const FilePath rootDir = installRoot().onDevice(makeCommand()); m_deploymentData = DeploymentData(); m_deploymentData.setLocalInstallRoot(rootDir); - const int startPos = rootDir.toString().length(); + const int startPos = rootDir.path().length(); const auto appFileNames = transform>(buildSystem()->applicationTargets(), [](const BuildTargetInfo &appTarget) { return appTarget.targetFilePath.fileName(); }); - auto handleFile = [this, &appFileNames, startPos, hack](const FilePath &filePath) { + auto handleFile = [this, &appFileNames, startPos](const FilePath &filePath) { const DeployableFile::Type type = appFileNames.contains(filePath.fileName()) ? DeployableFile::TypeExecutable : DeployableFile::TypeNormal; - QString targetDir = filePath.parentDir().toString().mid(startPos); - // FIXME: This is conceptually the wrong place, but currently "downstream" like - // the rsync step doesn't handle full remote paths here. - targetDir = FilePath::fromString(targetDir).path(); - - // FIXME: Hack, Part#2: If the build was indeed not local, drop the remoteness. - // As we rely on shared build directory, this "maps" to the host. - if (hack) - m_deploymentData.addFile(FilePath::fromString(filePath.path()), targetDir, type); - else - m_deploymentData.addFile(filePath, targetDir, type); + const QString targetDir = filePath.parentDir().path().mid(startPos); + m_deploymentData.addFile(filePath, targetDir, type); return true; }; rootDir.iterateDirectory(handleFile, diff --git a/src/plugins/remotelinux/remotelinuxdeployconfiguration.cpp b/src/plugins/remotelinux/remotelinuxdeployconfiguration.cpp index 0bcdd6682b0..862925deb22 100644 --- a/src/plugins/remotelinux/remotelinuxdeployconfiguration.cpp +++ b/src/plugins/remotelinux/remotelinuxdeployconfiguration.cpp @@ -39,13 +39,24 @@ RemoteLinuxDeployConfigurationFactory::RemoteLinuxDeployConfigurationFactory() addInitialStep(Constants::MakeInstallStepId, needsMakeInstall); addInitialStep(Constants::KillAppStepId); + + // Todo: Check: Instead of having two different steps here, have one + // and shift the logic into the implementation there? addInitialStep(Constants::RsyncDeployStepId, [](Target *target) { - auto device = DeviceKitAspect::device(target->kit()); - return device && device->extraData(Constants::SupportsRSync).toBool(); + auto runDevice = DeviceKitAspect::device(target->kit()); + auto buildDevice = BuildDeviceKitAspect::device(target->kit()); + if (runDevice == buildDevice) + return false; + // FIXME: That's not the full truth, we need support from the build + // device, too. + return runDevice && runDevice->extraData(Constants::SupportsRSync).toBool(); }); addInitialStep(Constants::DirectUploadStepId, [](Target *target) { - auto device = DeviceKitAspect::device(target->kit()); - return device && !device->extraData(Constants::SupportsRSync).toBool(); + auto runDevice = DeviceKitAspect::device(target->kit()); + auto buildDevice = BuildDeviceKitAspect::device(target->kit()); + if (runDevice == buildDevice) + return true; + return runDevice && !runDevice->extraData(Constants::SupportsRSync).toBool(); }); } diff --git a/src/plugins/remotelinux/remotelinuxplugin.cpp b/src/plugins/remotelinux/remotelinuxplugin.cpp index 4da224be93a..5034eb908cf 100644 --- a/src/plugins/remotelinux/remotelinuxplugin.cpp +++ b/src/plugins/remotelinux/remotelinuxplugin.cpp @@ -58,7 +58,7 @@ public: TarPackageCreationStepFactory tarPackageCreationStepFactory; TarPackageDeployStepFactory tarPackageDeployStepFactory; GenericDeployStepFactory genericDirectUploadStepFactory; - RsyncDeployStepFactory rsyncDeployStepFactory; + GenericDeployStepFactory rsyncDeployStepFactory; CustomCommandDeployStepFactory customCommandDeployStepFactory; KillAppStepFactory killAppStepFactory; GenericDeployStepFactory makeInstallStepFactory; diff --git a/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp b/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp index 106b8b25871..99ff7d7ff2c 100644 --- a/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp +++ b/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp @@ -53,13 +53,22 @@ RemoteLinuxRunConfiguration::RemoteLinuxRunConfiguration(Target *target, Id id) if (HostOsInfo::isAnyUnixHost()) addAspect(macroExpander()); - setUpdater([this, target, exeAspect, symbolsAspect] { + auto libAspect = addAspect(); + libAspect->setValue(false); + connect(libAspect, &UseLibraryPathsAspect::changed, + envAspect, &EnvironmentAspect::environmentChanged); + + setUpdater([this, target, exeAspect, symbolsAspect, libAspect] { BuildTargetInfo bti = buildTargetInfo(); const FilePath localExecutable = bti.targetFilePath; DeployableFile depFile = target->deploymentData().deployableForLocalFile(localExecutable); exeAspect->setExecutable(FilePath::fromString(depFile.remoteFilePath())); symbolsAspect->setFilePath(localExecutable); + + const IDeviceConstPtr buildDevice = BuildDeviceKitAspect::device(target->kit()); + const IDeviceConstPtr runDevice = DeviceKitAspect::device(target->kit()); + libAspect->setEnabled(buildDevice == runDevice); }); setRunnableModifier([this](Runnable &r) { @@ -67,6 +76,12 @@ RemoteLinuxRunConfiguration::RemoteLinuxRunConfiguration(Target *target, Id id) r.extraData.insert("Ssh.X11ForwardToDisplay", forwardingAspect->display()); }); + envAspect->addModifier([this, libAspect](Environment &env) { + BuildTargetInfo bti = buildTargetInfo(); + if (bti.runEnvModifier) + bti.runEnvModifier(env, libAspect->value()); + }); + connect(target, &Target::buildSystemUpdated, this, &RunConfiguration::update); connect(target, &Target::deploymentDataChanged, this, &RunConfiguration::update); connect(target, &Target::kitChanged, this, &RunConfiguration::update); diff --git a/src/plugins/remotelinux/rsyncdeploystep.cpp b/src/plugins/remotelinux/rsyncdeploystep.cpp index 29985a1c3f6..cfef60c80a2 100644 --- a/src/plugins/remotelinux/rsyncdeploystep.cpp +++ b/src/plugins/remotelinux/rsyncdeploystep.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -23,7 +24,7 @@ using namespace ProjectExplorer; using namespace Utils; -namespace RemoteLinux::Internal { +namespace RemoteLinux { class RsyncDeployService : public AbstractRemoteLinuxDeployService { @@ -90,6 +91,7 @@ private: void RsyncDeployService::setDeployableFiles(const QList &files) { + m_files.clear(); for (const DeployableFile &f : files) m_files.append({f.localFilePath(), deviceConfiguration()->filePath(f.remoteFilePath())}); } @@ -135,46 +137,51 @@ void RsyncDeployService::setFinished() // RsyncDeployStep -class RsyncDeployStep : public AbstractRemoteLinuxDeployStep -{ -public: - RsyncDeployStep(BuildStepList *bsl, Id id) +RsyncDeployStep::RsyncDeployStep(BuildStepList *bsl, Id id) : AbstractRemoteLinuxDeployStep(bsl, id) - { - auto service = createDeployService(); - - auto flags = addAspect(); - flags->setDisplayStyle(StringAspect::LineEditDisplay); - flags->setSettingsKey("RemoteLinux.RsyncDeployStep.Flags"); - flags->setLabelText(Tr::tr("Flags:")); - flags->setValue(FileTransferSetupData::defaultRsyncFlags()); - - auto ignoreMissingFiles = addAspect(); - ignoreMissingFiles->setSettingsKey("RemoteLinux.RsyncDeployStep.IgnoreMissingFiles"); - ignoreMissingFiles->setLabel(Tr::tr("Ignore missing files:"), - BoolAspect::LabelPlacement::InExtraLabel); - ignoreMissingFiles->setValue(false); - - setInternalInitializer([service, flags, ignoreMissingFiles] { - service->setIgnoreMissingFiles(ignoreMissingFiles->value()); - service->setFlags(flags->value()); - return service->isDeploymentPossible(); - }); - - setRunPreparer([this, service] { - service->setDeployableFiles(target()->deploymentData().allFiles()); - }); - } -}; - -// RsyncDeployStepFactory - -RsyncDeployStepFactory::RsyncDeployStepFactory() { - registerStep(Constants::RsyncDeployStepId); - setDisplayName(Tr::tr("Deploy files via rsync")); - setSupportedConfiguration(RemoteLinux::Constants::DeployToGenericLinux); - setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_DEPLOY); + auto service = new RsyncDeployService; + setDeployService(service); + + auto flags = addAspect(); + flags->setDisplayStyle(StringAspect::LineEditDisplay); + flags->setSettingsKey("RemoteLinux.RsyncDeployStep.Flags"); + flags->setLabelText(Tr::tr("Flags:")); + flags->setValue(FileTransferSetupData::defaultRsyncFlags()); + + auto ignoreMissingFiles = addAspect(); + ignoreMissingFiles->setSettingsKey("RemoteLinux.RsyncDeployStep.IgnoreMissingFiles"); + ignoreMissingFiles->setLabel(Tr::tr("Ignore missing files:"), + BoolAspect::LabelPlacement::InExtraLabel); + ignoreMissingFiles->setValue(false); + + setInternalInitializer([this, service, flags, ignoreMissingFiles] { + if (BuildDeviceKitAspect::device(kit()) == DeviceKitAspect::device(kit())) { + // rsync transfer on the same device currently not implemented + // and typically not wanted. + return CheckResult::failure( + Tr::tr("rsync is only supported for transfers between different devices.")); + } + service->setIgnoreMissingFiles(ignoreMissingFiles->value()); + service->setFlags(flags->value()); + return service->isDeploymentPossible(); + }); + + setRunPreparer([this, service] { + service->setDeployableFiles(target()->deploymentData().allFiles()); + }); } -} // RemoteLinux::Internal +RsyncDeployStep::~RsyncDeployStep() = default; + +Utils::Id RsyncDeployStep::stepId() +{ + return Constants::RsyncDeployStepId; +} + +QString RsyncDeployStep::displayName() +{ + return Tr::tr("Deploy files via rsync"); +} + +} // RemoteLinux diff --git a/src/plugins/remotelinux/rsyncdeploystep.h b/src/plugins/remotelinux/rsyncdeploystep.h index 7816c58ba29..5775fb5bde2 100644 --- a/src/plugins/remotelinux/rsyncdeploystep.h +++ b/src/plugins/remotelinux/rsyncdeploystep.h @@ -3,14 +3,20 @@ #pragma once -#include +#include "remotelinux_export.h" -namespace RemoteLinux::Internal { +#include "abstractremotelinuxdeploystep.h" -class RsyncDeployStepFactory : public ProjectExplorer::BuildStepFactory +namespace RemoteLinux { + +class REMOTELINUX_EXPORT RsyncDeployStep : public AbstractRemoteLinuxDeployStep { public: - RsyncDeployStepFactory(); + RsyncDeployStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id); + ~RsyncDeployStep() override; + + static Utils::Id stepId(); + static QString displayName(); }; -} // RemoteLinux::Internal +} // namespace RemoteLinux diff --git a/src/plugins/remotelinux/tarpackagedeploystep.cpp b/src/plugins/remotelinux/tarpackagedeploystep.cpp index 0d7d8f729c7..5dd3def96fb 100644 --- a/src/plugins/remotelinux/tarpackagedeploystep.cpp +++ b/src/plugins/remotelinux/tarpackagedeploystep.cpp @@ -192,7 +192,8 @@ public: TarPackageDeployStep(BuildStepList *bsl, Id id) : AbstractRemoteLinuxDeployStep(bsl, id) { - auto service = createDeployService(); + auto service = new TarPackageDeployService; + setDeployService(service); setWidgetExpandedByDefault(false); diff --git a/src/plugins/squish/squishfilehandler.cpp b/src/plugins/squish/squishfilehandler.cpp index 99ee316bf2b..3ae0cd8edf2 100644 --- a/src/plugins/squish/squishfilehandler.cpp +++ b/src/plugins/squish/squishfilehandler.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include namespace Squish { @@ -74,17 +75,15 @@ public: setWindowTitle(Tr::tr("Recording Settings")); auto squishTools = SquishTools::instance(); - connect(squishTools, &SquishTools::queryFinished, this, - [this] (const QString &out, const QString &) { + QApplication::setOverrideCursor(Qt::WaitCursor); + + squishTools->queryServerSettings([this] (const QString &out, const QString &) { SquishServerSettings s; s.setFromXmlOutput(out); QApplication::restoreOverrideCursor(); for (const QString &app : s.mappedAuts.keys()) aut.addItem(app); }); - - QApplication::setOverrideCursor(Qt::WaitCursor); - squishTools->queryServerSettings(); } @@ -439,6 +438,8 @@ void SquishFileHandler::addSharedFolder() return; m_sharedFolders.append(chosen); + updateSquishServerGlobalScripts(); + SquishTestTreeItem *item = new SquishTestTreeItem(chosen.toUserOutput(), SquishTestTreeItem::SquishSharedFolder); item->setFilePath(chosen); @@ -446,10 +447,32 @@ void SquishFileHandler::addSharedFolder() emit testTreeItemCreated(item); } +void SquishFileHandler::setSharedFolders(const Utils::FilePaths &folders) +{ + emit clearedSharedFolders(); + m_sharedFolders.clear(); + + for (const Utils::FilePath &folder : folders) { + if (m_sharedFolders.contains(folder)) + continue; + + m_sharedFolders.append(folder); + SquishTestTreeItem *item = new SquishTestTreeItem(folder.toUserOutput(), + SquishTestTreeItem::SquishSharedFolder); + item->setFilePath(folder); + addAllEntriesRecursively(item); + emit testTreeItemCreated(item); + } +} + bool SquishFileHandler::removeSharedFolder(const Utils::FilePath &folder) { - if (m_sharedFolders.contains(folder)) - return m_sharedFolders.removeOne(folder); + if (m_sharedFolders.contains(folder)) { + if (m_sharedFolders.removeOne(folder)) { + updateSquishServerGlobalScripts(); + return true; + } + } return false; } @@ -457,6 +480,7 @@ bool SquishFileHandler::removeSharedFolder(const Utils::FilePath &folder) void SquishFileHandler::removeAllSharedFolders() { m_sharedFolders.clear(); + updateSquishServerGlobalScripts(); } void SquishFileHandler::openObjectsMap(const QString &suiteName) @@ -493,6 +517,20 @@ void SquishFileHandler::onSessionLoaded() } } +void SquishFileHandler::updateSquishServerGlobalScripts() +{ + auto squishTools = SquishTools::instance(); + if (squishTools->state() != SquishTools::Idle) { + // postpone - we can't queue this currently + QTimer::singleShot(1500, [this]() { + updateSquishServerGlobalScripts(); + }); + return; + } + + squishTools->requestSetSharedFolders(m_sharedFolders); +} + QStringList SquishFileHandler::suitePathsAsStringList() const { return Utils::transform(m_suites.values(), &Utils::FilePath::toString); diff --git a/src/plugins/squish/squishfilehandler.h b/src/plugins/squish/squishfilehandler.h index 0a23f918efc..960374059b9 100644 --- a/src/plugins/squish/squishfilehandler.h +++ b/src/plugins/squish/squishfilehandler.h @@ -29,11 +29,13 @@ public: void runTestSuite(const QString &suiteName); void recordTestCase(const QString &suiteName, const QString &testCaseName); void addSharedFolder(); + void setSharedFolders(const Utils::FilePaths &folders); bool removeSharedFolder(const Utils::FilePath &folder); void removeAllSharedFolders(); void openObjectsMap(const QString &suiteName); signals: + void clearedSharedFolders(); void testTreeItemCreated(SquishTestTreeItem *item); void suiteTreeItemRemoved(const QString &suiteName); void suiteTreeItemModified(SquishTestTreeItem *item, const QString &displayName); @@ -42,6 +44,7 @@ signals: private: void closeAllInternal(); void onSessionLoaded(); + void updateSquishServerGlobalScripts(); QStringList suitePathsAsStringList() const; void modifySuiteItem(const QString &suiteName, diff --git a/src/plugins/squish/squishnavigationwidget.cpp b/src/plugins/squish/squishnavigationwidget.cpp index 21becd62323..537aad11949 100644 --- a/src/plugins/squish/squishnavigationwidget.cpp +++ b/src/plugins/squish/squishnavigationwidget.cpp @@ -282,7 +282,7 @@ void SquishNavigationWidget::onRowsRemoved(const QModelIndex &parent, int, int) void SquishNavigationWidget::onRemoveSharedFolderTriggered(int row, const QModelIndex &parent) { - const auto folder = Utils::FilePath::fromVariant(m_model->index(row, 0, parent).data(LinkRole)); + const auto folder = Utils::FilePath::fromVariant(m_sortModel->index(row, 0, parent).data(LinkRole)); QTC_ASSERT(!folder.isEmpty(), return ); if (QMessageBox::question(Core::ICore::dialogParent(), @@ -293,7 +293,7 @@ void SquishNavigationWidget::onRemoveSharedFolderTriggered(int row, const QModel return; } - const QModelIndex &realIdx = m_sortModel->mapToSource(m_model->index(row, 0, parent)); + const QModelIndex &realIdx = m_sortModel->mapToSource(m_sortModel->index(row, 0, parent)); if (SquishFileHandler::instance()->removeSharedFolder(folder)) m_model->removeTreeItem(realIdx.row(), realIdx.parent()); } diff --git a/src/plugins/squish/squishplugin.cpp b/src/plugins/squish/squishplugin.cpp index ced619fbca5..f2e486770c9 100644 --- a/src/plugins/squish/squishplugin.cpp +++ b/src/plugins/squish/squishplugin.cpp @@ -4,7 +4,7 @@ #include "squishplugin.h" #include "objectsmapeditor.h" -#include "squish/squishwizardpages.h" +#include "squishfilehandler.h" #include "squishnavigationwidget.h" #include "squishoutputpane.h" #include "squishresultmodel.h" @@ -12,6 +12,7 @@ #include "squishtesttreemodel.h" #include "squishtools.h" #include "squishtr.h" +#include "squishwizardpages.h" #include #include @@ -21,6 +22,7 @@ #include +#include #include #include @@ -39,6 +41,7 @@ public: ~SquishPluginPrivate(); void initializeMenuEntries(); + bool initializeGlobalScripts(); SquishSettings m_squishSettings; SquishSettingsPage m_settingsPage{&m_squishSettings}; @@ -102,6 +105,28 @@ void SquishPluginPrivate::initializeMenuEntries() toolsMenu->addMenu(menu); } +bool SquishPluginPrivate::initializeGlobalScripts() +{ + QTC_ASSERT(dd->m_squishTools, return false); + SquishFileHandler::instance()->setSharedFolders({}); + + const Utils::FilePath squishserver = dd->m_squishSettings.squishPath.filePath().pathAppended( + Utils::HostOsInfo::withExecutableSuffix("bin/squishserver")); + if (!squishserver.isExecutableFile()) + return false; + + dd->m_squishTools->queryGlobalScripts([](const QString &output, const QString &error) { + if (output.isEmpty() || !error.isEmpty()) + return; // ignore (for now?) + + // FIXME? comma, special characters in paths + const Utils::FilePaths globalDirs = Utils::transform( + output.trimmed().split(',', Qt::SkipEmptyParts), &Utils::FilePath::fromString); + SquishFileHandler::instance()->setSharedFolders(globalDirs); + }); + return true; +} + bool SquishPlugin::initialize(const QStringList &, QString *) { dd = new SquishPluginPrivate; @@ -109,6 +134,15 @@ bool SquishPlugin::initialize(const QStringList &, QString *) return true; } +bool SquishPlugin::delayedInitialize() +{ + + connect(&dd->m_squishSettings, &SquishSettings::squishPathChanged, + dd, &SquishPluginPrivate::initializeGlobalScripts); + + return dd->initializeGlobalScripts(); +} + ExtensionSystem::IPlugin::ShutdownFlag SquishPlugin::aboutToShutdown() { if (dd->m_squishTools) { diff --git a/src/plugins/squish/squishplugin.h b/src/plugins/squish/squishplugin.h index 44e49c2ab43..3373bbf2d62 100644 --- a/src/plugins/squish/squishplugin.h +++ b/src/plugins/squish/squishplugin.h @@ -23,6 +23,7 @@ public: static SquishSettings *squishSettings(); bool initialize(const QStringList &arguments, QString *errorString) override; + bool delayedInitialize() override; ShutdownFlag aboutToShutdown() override; }; diff --git a/src/plugins/squish/squishsettings.cpp b/src/plugins/squish/squishsettings.cpp index d6dc68d91ae..40c2c8a1539 100644 --- a/src/plugins/squish/squishsettings.cpp +++ b/src/plugins/squish/squishsettings.cpp @@ -42,6 +42,17 @@ SquishSettings::SquishSettings() squishPath.setDisplayStyle(StringAspect::PathChooserDisplay); squishPath.setExpectedKind(PathChooser::ExistingDirectory); squishPath.setPlaceHolderText(Tr::tr("Path to Squish installation")); + squishPath.setValidationFunction([this](FancyLineEdit *edit, QString *error) { + QTC_ASSERT(edit, return false); + if (!squishPath.pathChooser()->defaultValidationFunction()(edit, error)) + return false; + const FilePath squishServer = FilePath::fromString(edit->text()) + .pathAppended(HostOsInfo::withExecutableSuffix("bin/squishserver")); + const bool valid = squishServer.isExecutableFile(); + if (!valid && error) + *error = Tr::tr("Path does not contain server executable at its default location."); + return valid; + }); registerAspect(&licensePath); licensePath.setSettingsKey("LicensePath"); @@ -84,6 +95,8 @@ SquishSettings::SquishSettings() serverHost.setEnabled(!checked); serverPort.setEnabled(!checked); }); + connect(&squishPath, &Utils::StringAspect::valueChanged, + this, &SquishSettings::squishPathChanged); } Utils::FilePath SquishSettings::scriptsPath(Language language) const @@ -406,15 +419,13 @@ SquishServerSettingsWidget::SquishServerSettingsWidget(QWidget *parent) // query settings SquishTools *squishTools = SquishTools::instance(); - connect(squishTools, &SquishTools::queryFinished, this, - [this, progress] (const QString &out, const QString &) { + squishTools->queryServerSettings([this, progress] (const QString &out, const QString &) { m_serverSettings.setFromXmlOutput(out); m_originalSettings.setFromXmlOutput(out); repopulateApplicationView(); progress->hide(); setEnabled(true); }); - squishTools->queryServerSettings(); } void SquishServerSettingsWidget::repopulateApplicationView() diff --git a/src/plugins/squish/squishsettings.h b/src/plugins/squish/squishsettings.h index 1f411cde60a..02b84d94937 100644 --- a/src/plugins/squish/squishsettings.h +++ b/src/plugins/squish/squishsettings.h @@ -38,6 +38,7 @@ public: class SquishSettings : public Utils::AspectContainer { + Q_OBJECT public: SquishSettings(); @@ -50,6 +51,9 @@ public: Utils::BoolAspect local; Utils::BoolAspect verbose; Utils::BoolAspect minimizeIDE; + +signals: + void squishPathChanged(); }; class SquishSettingsPage final : public Core::IOptionsPage diff --git a/src/plugins/squish/squishtesttreemodel.cpp b/src/plugins/squish/squishtesttreemodel.cpp index 8df29777363..3733d10f8fb 100644 --- a/src/plugins/squish/squishtesttreemodel.cpp +++ b/src/plugins/squish/squishtesttreemodel.cpp @@ -186,6 +186,9 @@ SquishTestTreeModel::SquishTestTreeModel(QObject *parent) &SquishFileHandler::suiteTreeItemRemoved, this, &SquishTestTreeModel::onSuiteTreeItemRemoved); + connect(m_squishFileHandler, + &SquishFileHandler::clearedSharedFolders, + this, [this]() { m_squishSharedFolders->removeChildren(); }); m_instance = this; } diff --git a/src/plugins/squish/squishtools.cpp b/src/plugins/squish/squishtools.cpp index 614ac1f1e82..4a33d45cf3a 100644 --- a/src/plugins/squish/squishtools.cpp +++ b/src/plugins/squish/squishtools.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -260,10 +261,32 @@ void SquishTools::runTestCases(const FilePath &suitePath, startSquishServer(RunTestRequested); } -void SquishTools::queryServerSettings() +void SquishTools::queryGlobalScripts(QueryCallback callback) +{ + m_queryCallback = callback; + queryServer(GetGlobalScriptDirs); +} + +void SquishTools::queryServerSettings(QueryCallback callback) +{ + m_queryCallback = callback; + queryServer(ServerInfo); +} + +void SquishTools::requestSetSharedFolders(const Utils::FilePaths &sharedFolders) +{ + // when sharedFolders is empty we need to pass an (explicit) empty string + // otherwise a list of paths, for convenience we quote each path + m_queryParameter = '"' + Utils::transform(sharedFolders, &FilePath::toUserOutput).join("\",\"") + '"'; + queryServer(SetGlobalScriptDirs); +} + + +void SquishTools::queryServer(RunnerQuery query) { if (m_shutdownInitiated) return; + if (m_state != Idle) { QMessageBox::critical(Core::ICore::dialogParent(), Tr::tr("Error"), @@ -274,6 +297,7 @@ void SquishTools::queryServerSettings() } m_perspective.setPerspectiveMode(SquishPerspective::Querying); m_fullRunnerOutput.clear(); + m_query = query; startSquishServer(RunnerQueryRequested); } @@ -610,7 +634,9 @@ void SquishTools::startSquishRunner() m_autId = 0; if (m_request == RecordTestRequested) m_closeRunnerOnEndRecord = true; - setupAndStartSquishRunnerProcess(args); + + Utils::CommandLine cmdLine = {toolsSettings.runnerPath, args}; + setupAndStartSquishRunnerProcess(cmdLine); } void SquishTools::setupAndStartRecorder() @@ -661,7 +687,26 @@ void SquishTools::executeRunnerQuery() if (!isValidToStartRunner() || !setupRunnerPath()) return; - setupAndStartSquishRunnerProcess({ "--port", QString::number(m_serverPort), "--info", "all"}); + QStringList arguments = { "--port", QString::number(m_serverPort) }; + Utils::CommandLine cmdLine = {toolsSettings.runnerPath, arguments}; + switch (m_query) { + case ServerInfo: + cmdLine.addArg("--info"); + cmdLine.addArg("all"); + break; + case GetGlobalScriptDirs: + cmdLine.addArg("--config"); + cmdLine.addArg("getGlobalScriptDirs"); + break; + case SetGlobalScriptDirs: + cmdLine.addArg("--config"); + cmdLine.addArg("setGlobalScriptDirs"); + cmdLine.addArgs(m_queryParameter, Utils::CommandLine::Raw); + break; + default: + QTC_ASSERT(false, return); + } + setupAndStartSquishRunnerProcess(cmdLine); } Environment SquishTools::squishEnvironment() @@ -686,9 +731,12 @@ void SquishTools::onRunnerFinished() if (m_request == RunnerQueryRequested) { const QString error = m_licenseIssues ? Tr::tr("Could not get Squish license from server.") : QString(); - emit queryFinished(m_fullRunnerOutput, error); + if (m_queryCallback) + m_queryCallback(m_fullRunnerOutput, error); setState(RunnerStopped); m_fullRunnerOutput.clear(); + m_queryCallback = {}; + m_queryParameter.clear(); return; } @@ -1391,9 +1439,9 @@ bool SquishTools::setupRunnerPath() return true; } -void SquishTools::setupAndStartSquishRunnerProcess(const QStringList &args) +void SquishTools::setupAndStartSquishRunnerProcess(const Utils::CommandLine &cmdLine) { - m_runnerProcess.setCommand({toolsSettings.runnerPath, args}); + m_runnerProcess.setCommand(cmdLine); m_runnerProcess.setEnvironment(squishEnvironment()); setState(RunnerStarting); diff --git a/src/plugins/squish/squishtools.h b/src/plugins/squish/squishtools.h index 72825514e44..1f459d95d08 100644 --- a/src/plugins/squish/squishtools.h +++ b/src/plugins/squish/squishtools.h @@ -61,12 +61,16 @@ public: Finished }; + using QueryCallback = std::function; + State state() const { return m_state; } void runTestCases(const Utils::FilePath &suitePath, const QStringList &testCases = QStringList()); void recordTestCase(const Utils::FilePath &suitePath, const QString &testCaseName, const SuiteConf &suiteConf); - void queryServerSettings(); + void queryGlobalScripts(QueryCallback callback); + void queryServerSettings(QueryCallback callback); + void requestSetSharedFolders(const Utils::FilePaths &sharedFolders); void writeServerSettingsChanges(const QList &changes); void requestExpansion(const QString &name); @@ -77,7 +81,6 @@ signals: void squishTestRunStarted(); void squishTestRunFinished(); void resultOutputCreated(const QByteArray &output); - void queryFinished(const QString &output, const QString &error); void configChangesFailed(QProcess::ProcessError error); void configChangesWritten(); void localsUpdated(const QString &output); @@ -97,6 +100,8 @@ private: KillOldBeforeQueryRunner }; + enum RunnerQuery { ServerInfo, GetGlobalScriptDirs, SetGlobalScriptDirs }; + void setState(State state); void handleSetStateStartAppRunner(); void handleSetStateQueryRunner(); @@ -106,6 +111,7 @@ private: void startSquishRunner(); void setupAndStartRecorder(); void stopRecorder(); + void queryServer(RunnerQuery query); void executeRunnerQuery(); static Utils::Environment squishEnvironment(); void onServerFinished(); @@ -132,7 +138,7 @@ private: QStringList serverArgumentsFromSettings() const; QStringList runnerArgumentsFromSettings(); bool setupRunnerPath(); - void setupAndStartSquishRunnerProcess(const QStringList &arg); + void setupAndStartSquishRunnerProcess(const Utils::CommandLine &cmdLine); SquishPerspective m_perspective; std::unique_ptr m_xmlOutputHandler; @@ -150,6 +156,7 @@ private: Utils::FilePaths m_reportFiles; Utils::FilePath m_currentResultsDirectory; QString m_fullRunnerOutput; // used when querying the server + QString m_queryParameter; Utils::FilePath m_currentTestCasePath; Utils::FilePath m_currentRecorderSnippetFile; QFile *m_currentResultsXML = nullptr; @@ -161,6 +168,8 @@ private: QTimer *m_requestVarsTimer = nullptr; qint64 m_readResultsCount; int m_autId = 0; + QueryCallback m_queryCallback; + RunnerQuery m_query = ServerInfo; bool m_shutdownInitiated = false; bool m_closeRunnerOnEndRecord = false; bool m_licenseIssues = false; diff --git a/src/plugins/squish/squishwizardpages.cpp b/src/plugins/squish/squishwizardpages.cpp index b40a037cf45..62535b5a3ce 100644 --- a/src/plugins/squish/squishwizardpages.cpp +++ b/src/plugins/squish/squishwizardpages.cpp @@ -126,8 +126,8 @@ void SquishToolkitsPage::fetchServerSettings() auto squishTools = SquishTools::instance(); QTC_ASSERT(squishTools, return); - connect(squishTools, &SquishTools::queryFinished, this, - [this] (const QString &out, const QString &error) { + QApplication::setOverrideCursor(Qt::WaitCursor); + squishTools->queryServerSettings([this](const QString &out, const QString &error) { SquishServerSettings s; s.setFromXmlOutput(out); QApplication::restoreOverrideCursor(); @@ -149,8 +149,6 @@ void SquishToolkitsPage::fetchServerSettings() m_errorLabel->setVisible(true); } }); - QApplication::setOverrideCursor(Qt::WaitCursor); - squishTools->queryServerSettings(); } /********************************* ScriptLanguagePage ********************************************/ diff --git a/src/plugins/texteditor/highlightersettingspage.cpp b/src/plugins/texteditor/highlightersettingspage.cpp index bee80c1a243..777bff397ba 100644 --- a/src/plugins/texteditor/highlightersettingspage.cpp +++ b/src/plugins/texteditor/highlightersettingspage.cpp @@ -28,6 +28,7 @@ namespace Internal { class HighlighterSettingsPageWidget : public QWidget { + Q_DECLARE_TR_FUNCTIONS(TextEditor::Internal::HighlighterSettingsPage) public: QLabel *definitionsInfolabel; QPushButton *downloadDefinitions; @@ -55,6 +56,7 @@ public: downloadDefinitions->setToolTip(tr("Download missing and update existing syntax definition files.")); updateStatus = new QLabel; + updateStatus->setObjectName("updateStatus"); definitionFilesPath = new PathChooser; definitionFilesPath->setExpectedKind(PathChooser::ExistingDirectory); diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 7f69768d288..1d4d0c116e5 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -5707,8 +5707,8 @@ void TextEditorWidget::mouseDoubleClickEvent(QMouseEvent *e) } } - QTextCursor oldCursor = multiTextCursor().mainCursor(); - const int oldPosition = oldCursor.position(); + QTextCursor eventCursor = cursorForPosition(QPoint(e->pos().x(), e->pos().y())); + const int eventDocumentPosition = eventCursor.position(); QPlainTextEdit::mouseDoubleClickEvent(e); @@ -5716,19 +5716,19 @@ void TextEditorWidget::mouseDoubleClickEvent(QMouseEvent *e) // event is triggered on a position that is inbetween two whitespaces this event selects the // previous word or nothing if the whitespaces are at the block start. Replace this behavior // with selecting the whitespaces starting from the previous word end to the next word. - const QChar character = characterAt(oldPosition); - const QChar prevCharacter = characterAt(oldPosition - 1); + const QChar character = characterAt(eventDocumentPosition); + const QChar prevCharacter = characterAt(eventDocumentPosition - 1); if (character.isSpace() && prevCharacter.isSpace()) { if (prevCharacter != QChar::ParagraphSeparator) { - oldCursor.movePosition(QTextCursor::PreviousWord); - oldCursor.movePosition(QTextCursor::EndOfWord); + eventCursor.movePosition(QTextCursor::PreviousWord); + eventCursor.movePosition(QTextCursor::EndOfWord); } else if (character == QChar::ParagraphSeparator) { return; // no special handling for empty lines } - oldCursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor); + eventCursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor); MultiTextCursor cursor = multiTextCursor(); - cursor.replaceMainCursor(oldCursor); + cursor.replaceMainCursor(eventCursor); setMultiTextCursor(cursor); } } diff --git a/src/plugins/vcsbase/submiteditorwidget.cpp b/src/plugins/vcsbase/submiteditorwidget.cpp index 4d01d1f549f..a2b751433b3 100644 --- a/src/plugins/vcsbase/submiteditorwidget.cpp +++ b/src/plugins/vcsbase/submiteditorwidget.cpp @@ -645,6 +645,11 @@ bool SubmitEditorWidget::canSubmit(QString *whyNot) const return res; } +bool SubmitEditorWidget::isEdited() const +{ + return !d->m_description.trimmed().isEmpty() || checkedFilesCount() > 0; +} + void SubmitEditorWidget::setUpdateInProgress(bool value) { d->m_updateInProgress = value; diff --git a/src/plugins/vcsbase/submiteditorwidget.h b/src/plugins/vcsbase/submiteditorwidget.h index 63fd8337104..d3a2c290ae6 100644 --- a/src/plugins/vcsbase/submiteditorwidget.h +++ b/src/plugins/vcsbase/submiteditorwidget.h @@ -67,6 +67,7 @@ public: QList submitFieldWidgets() const; virtual bool canSubmit(QString *whyNot = nullptr) const; + bool isEdited() const; void setUpdateInProgress(bool value); bool updateInProgress() const; diff --git a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp index 3f481fc347f..4973e645aff 100644 --- a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp +++ b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp @@ -501,7 +501,7 @@ VcsBaseSubmitEditor::PromptSubmitResult VcsBaseSubmitEditor::promptSubmit(VcsBas Core::EditorManager::activateEditor(this, Core::EditorManager::IgnoreNavigationHistory); - if (!submitWidget->isEnabled()) + if (!submitWidget->isEnabled() || !submitWidget->isEdited()) return SubmitDiscarded; QString errorMessage; diff --git a/src/shared/qbs b/src/shared/qbs index 09ff740b4e0..f200b701f73 160000 --- a/src/shared/qbs +++ b/src/shared/qbs @@ -1 +1 @@ -Subproject commit 09ff740b4e0520374e84415cfc97561809152270 +Subproject commit f200b701f7307a2935f37d39db8e192d98ff7047 diff --git a/src/tools/sdktool/CMakeLists.txt b/src/tools/sdktool/CMakeLists.txt index 050940aca96..ffd578dbcac 100644 --- a/src/tools/sdktool/CMakeLists.txt +++ b/src/tools/sdktool/CMakeLists.txt @@ -69,6 +69,7 @@ extend_qtc_library(sdktoolLib PUBLIC_DEFINES UTILS_STATIC_LIBRARY SOURCES commandline.cpp commandline.h + devicefileaccess.cpp devicefileaccess.h environment.cpp environment.h filepath.cpp filepath.h fileutils.cpp fileutils.h diff --git a/src/tools/sdktool/sdktoollib.qbs b/src/tools/sdktool/sdktoollib.qbs index 4352c1f6c06..3c27d823023 100644 --- a/src/tools/sdktool/sdktoollib.qbs +++ b/src/tools/sdktool/sdktoollib.qbs @@ -92,6 +92,7 @@ QtcLibrary { prefix: libsDir + "/utils/" files: [ "commandline.cpp", "commandline.h", + "devicefileaccess.cpp", "devicefileaccess.h", "environment.cpp", "environment.h", "filepath.cpp", "filepath.h", "fileutils.cpp", "fileutils.h", diff --git a/tests/auto/utils/fileutils/tst_fileutils.cpp b/tests/auto/utils/fileutils/tst_fileutils.cpp index 0248fb44891..4d600a68c2f 100644 --- a/tests/auto/utils/fileutils/tst_fileutils.cpp +++ b/tests/auto/utils/fileutils/tst_fileutils.cpp @@ -101,6 +101,9 @@ private slots: void cleanPath_data(); void cleanPath(); + void isSameFile_data(); + void isSameFile(); + private: QTemporaryDir tempDir; QString rootPath; @@ -1114,6 +1117,48 @@ void tst_fileutils::cleanPath() QCOMPARE(cleaned, expected); } +void tst_fileutils::isSameFile_data() +{ + QTest::addColumn("left"); + QTest::addColumn("right"); + QTest::addColumn("shouldBeEqual"); + + QTest::addRow("/==/") + << FilePath::fromString("/") << FilePath::fromString("/") << true; + QTest::addRow("/!=tmp") + << FilePath::fromString("/") << FilePath::fromString(tempDir.path()) << false; + + + QDir dir(tempDir.path()); + touch(dir, "target-file", false); + + QFile file(dir.absoluteFilePath("target-file")); + if (file.link(dir.absoluteFilePath("source-file"))) { + QTest::addRow("real==link") + << FilePath::fromString(file.fileName()) + << FilePath::fromString(dir.absoluteFilePath("target-file")) + << true; + } + + QTest::addRow("/!=non-existing") + << FilePath::fromString("/") << FilePath::fromString("/this-path/does-not-exist") << false; + + QTest::addRow("two-devices") << FilePath::fromString( + "docker://boot2qt-raspberrypi4-64:6.5.0/opt/toolchain/sysroots/aarch64-pokysdk-linux/usr/" + "bin/aarch64-poky-linux/aarch64-poky-linux-g++") + << FilePath::fromString("docker://qt-linux:6/usr/bin/g++") + << false; +} + +void tst_fileutils::isSameFile() +{ + QFETCH(FilePath, left); + QFETCH(FilePath, right); + QFETCH(bool, shouldBeEqual); + + QCOMPARE(left.isSameFile(right), shouldBeEqual); +} + QTEST_GUILESS_MAIN(tst_fileutils) #include "tst_fileutils.moc" diff --git a/tests/auto/utils/fsengine/tst_fsengine.cpp b/tests/auto/utils/fsengine/tst_fsengine.cpp index 09cbe4ac0ec..e9a21353f99 100644 --- a/tests/auto/utils/fsengine/tst_fsengine.cpp +++ b/tests/auto/utils/fsengine/tst_fsengine.cpp @@ -2,8 +2,8 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include -#include #include +#include #include #include @@ -58,109 +58,6 @@ void tst_fsengine::initTestCase() if (HostOsInfo::isWindowsHost()) QSKIP("The fsengine tests are not supported on Windows."); - DeviceFileHooks &deviceHooks = DeviceFileHooks::instance(); - - deviceHooks.fileContents = - [](const FilePath &path, qint64 maxSize, qint64 offset) -> std::optional { - return FilePath::fromString(path.path()).fileContents(maxSize, offset); - }; - - deviceHooks.isExecutableFile = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).isExecutableFile(); - }; - deviceHooks.isReadableFile = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).isReadableFile(); - }; - deviceHooks.isReadableDir = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).isReadableDir(); - }; - deviceHooks.isWritableDir = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).isWritableDir(); - }; - deviceHooks.isWritableFile = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).isWritableFile(); - }; - deviceHooks.isFile = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).isFile(); - }; - deviceHooks.isDir = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).isDir(); - }; - deviceHooks.ensureWritableDir = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).ensureWritableDir(); - }; - deviceHooks.ensureExistingFile = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).ensureExistingFile(); - }; - deviceHooks.createDir = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).createDir(); - }; - deviceHooks.exists = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).exists(); - }; - deviceHooks.removeFile = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).removeFile(); - }; - deviceHooks.removeRecursively = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).removeRecursively(); - }; - deviceHooks.copyFile = [](const FilePath &filePath, const FilePath &target) { - return FilePath::fromString(filePath.path()).copyFile(target); - }; - deviceHooks.renameFile = [](const FilePath &filePath, const FilePath &target) { - return FilePath::fromString(filePath.path()).renameFile(target); - }; - deviceHooks.searchInPath = [](const FilePath &filePath, const FilePaths &dirs) { - return FilePath::fromString(filePath.path()).searchInPath(dirs); - }; - deviceHooks.symLinkTarget = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).symLinkTarget(); - }; - deviceHooks.iterateDirectory = [](const FilePath &filePath, - const std::function &callBack, - const FileFilter &filter) { - return FilePath::fromString(filePath.path()) - .iterateDirectory( - [&filePath, &callBack](const FilePath &path) -> bool { - const FilePath devicePath = path.onDevice(filePath); - - return callBack(devicePath); - }, - filter); - }; - deviceHooks.asyncFileContents = [](const Continuation &> &cont, - const FilePath &filePath, - qint64 maxSize, - qint64 offset) { - return FilePath::fromString(filePath.path()).asyncFileContents(cont, maxSize, offset); - }; - deviceHooks.writeFileContents = [](const FilePath &filePath, - const QByteArray &data, - qint64 offset) { - return FilePath::fromString(filePath.path()).writeFileContents(data, offset); - }; - deviceHooks.lastModified = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).lastModified(); - }; - deviceHooks.permissions = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).permissions(); - }; - deviceHooks.setPermissions = [](const FilePath &filePath, QFile::Permissions permissions) { - return FilePath::fromString(filePath.path()).setPermissions(permissions); - }; - deviceHooks.osType = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).osType(); - }; - // deviceHooks.environment = [](const FilePath &filePath) -> Environment {return {};}; - deviceHooks.fileSize = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).fileSize(); - }; - deviceHooks.bytesAvailable = [](const FilePath &filePath) { - return FilePath::fromString(filePath.path()).bytesAvailable(); - }; - - deviceHooks.mapToDevicePath = [](const FilePath &filePath) { return filePath.path(); }; - FSEngine::addDevice(FilePath::fromString("device://test")); tempFolder = QDir::tempPath(); diff --git a/tests/system/objects.map b/tests/system/objects.map index 93c2c8a5af2..da30b655651 100644 --- a/tests/system/objects.map +++ b/tests/system/objects.map @@ -92,7 +92,7 @@ :Minimal required Qt version:_QLabel {text='Minimum required Qt version:' type='QLabel' unnamed='1' visible='1' window=':New_ProjectExplorer::JsonWizard'} :New Text File.Add to project:_QLabel {name='projectLabel' text='Add to project:' type='QLabel' visible='1' window=':New_ProjectExplorer::JsonWizard'} :New Text File.nameLineEdit_Utils::FileNameValidatingLineEdit {name='nameLineEdit' type='Utils::FileNameValidatingLineEdit' visible='1' window=':New_ProjectExplorer::JsonWizard'} -:New.comboBox_QComboBox {name='comboBox' type='QComboBox' visible='1' window=':New_Core::Internal::NewDialog'} +:New.comboBox_QComboBox {type='QComboBox' unnamed='1' visible='1' window=':New_Core::Internal::NewDialog'} :New.frame_QFrame {name='frame' type='QFrame' visible='1' window=':New_Core::Internal::NewDialog'} :New.templateCategoryView_QTreeView {name='templateCategoryView' type='QTreeView' visible='1' window=':New_Core::Internal::NewDialog'} :New_Core::Internal::NewDialog {name='Core.NewDialog' type='Core::Internal::NewDialogWidget' visible='1' windowTitle?='New*'} @@ -111,7 +111,7 @@ :PasteSelectDialog.listWidget_QListWidget {name='listWidget' type='QListWidget' visible='1' window=':PasteSelectDialog_CodePaster::PasteSelectDialog'} :PasteSelectDialog.pasteEdit_QLineEdit {name='pasteEdit' type='QLineEdit' visible='1' window=':PasteSelectDialog_CodePaster::PasteSelectDialog'} :PasteSelectDialog.protocolBox_QComboBox {name='protocolBox' type='QComboBox' visible='1' window=':PasteSelectDialog_CodePaster::PasteSelectDialog'} -:PasteSelectDialog_CodePaster::PasteSelectDialog {name='CodePaster__Internal__PasteSelectDialog' type='CodePaster::PasteSelectDialog' visible='1'} +:PasteSelectDialog_CodePaster::PasteSelectDialog {name='CodePaster.PasteSelectDialog' type='QDialog' visible='1'} :Path.Utils_BaseValidatingLineEdit {container=':qt_tabwidget_stackedwidget_QWidget' name='LineEdit' type='Utils::FancyLineEdit' visible='1'} :Projects.ProjectNavigationTreeView {container=':*Qt Creator.ProjectSelectorDockWidget_QDockWidget' name='ProjectNavigation' type='Utils::BaseTreeView' visible='1'} :QML Debugging.No_QPushButton {text='No' type='QPushButton' unnamed='1' visible='1' window=':QML Debugging_QMessageBox'} @@ -184,15 +184,13 @@ :Select a Git Commit.changeNumberEdit_Utils::CompletingLineEdit {name='changeNumberEdit' type='Utils::CompletingLineEdit' visible='1' window=':Select a Git Commit_Git::Internal::ChangeSelectionDialog'} :Select a Git Commit.detailsText_QPlainTextEdit {name='detailsText' type='QPlainTextEdit' visible='1' window=':Select a Git Commit_Git::Internal::ChangeSelectionDialog'} :Select a Git Commit.workingDirectoryEdit_QLineEdit {type='Utils::FancyLineEdit' unnamed='1' visible='1' window=':Select a Git Commit_Git::Internal::ChangeSelectionDialog'} -:Select a Git Commit_Git::Internal::ChangeSelectionDialog {name='Git.ChangeSelectionDialog' type='Git::Internal::ChangeSelectionDialog' visible='1' windowTitle='Select a Git Commit'} +:Select a Git Commit_Git::Internal::ChangeSelectionDialog {name='Git.ChangeSelectionDialog' type='QDialog' visible='1' windowTitle='Select a Git Commit'} :Select signal.signalList_QTreeView {container=':Go to slot.Select signal_QGroupBox' name='signalList' type='QTreeView' visible='1'} :Send to Codepaster.Cancel_QPushButton {text='Cancel' type='QPushButton' unnamed='1' visible='1' window=':Send to Codepaster_CodePaster::PasteView'} -:Send to Codepaster.Description:_QLabel {name='descriptionLabel' text='Description:' type='QLabel' visible='1' window=':Send to Codepaster_CodePaster::PasteView'} :Send to Codepaster.Paste_QPushButton {text='Paste' type='QPushButton' unnamed='1' visible='1' window=':Send to Codepaster_CodePaster::PasteView'} :Send to Codepaster.protocolBox_QComboBox {name='protocolBox' type='QComboBox' visible='1' window=':Send to Codepaster_CodePaster::PasteView'} :Send to Codepaster.qt_spinbox_lineedit_QLineEdit {name='qt_spinbox_lineedit' type='QLineEdit' visible='1' window=':Send to Codepaster_CodePaster::PasteView'} -:Send to Codepaster.stackedWidget_QStackedWidget {name='stackedWidget' type='QStackedWidget' visible='1' window=':Send to Codepaster_CodePaster::PasteView'} -:Send to Codepaster_CodePaster::PasteView {name='CodePaster__Internal__ViewDialog' type='CodePaster::PasteView' visible='1' windowTitle='Send to Codepaster'} +:Send to Codepaster_CodePaster::PasteView {name='CodePaster.ViewDialog' type='QDialog' visible='1' windowTitle='Send to Codepaster'} :Session Manager_ProjectExplorer::Internal::SessionDialog {name='ProjectExplorer__Internal__SessionDialog' type='ProjectExplorer::Internal::SessionDialog' visible='1' windowTitle='Session Manager'} :Startup.contextHelpComboBox_QComboBox {container=':Form.Startup_QGroupBox' name='contextHelpComboBox' type='QComboBox' visible='1'} :User Interface.languageBox_QComboBox {container=':Core__Internal__GeneralSettings.User Interface_QGroupBox' name='languageBox' type='QComboBox' visible='1'} @@ -213,8 +211,8 @@ :qt_tabwidget_stackedwidget.QtSupport__Internal__QtVersionManager_QtSupport::Internal::QtOptionsPageWidget {container=':Options.qt_tabwidget_stackedwidget_QStackedWidget' type='QScrollArea' unnamed='1' visible='1'} :qt_tabwidget_stackedwidget_QScrollArea {container=':Options.qt_tabwidget_stackedwidget_QStackedWidget' type='QScrollArea' unnamed='1' visible='1'} :qt_tabwidget_stackedwidget_QWidget {container=':Options.qt_tabwidget_stackedwidget_QStackedWidget' type='QWidget' unnamed='1' visible='1'} -:qtdirList_QTreeView {container=':qt_tabwidget_stackedwidget_QScrollArea' name='qtdirList' type='QTreeView' visible='1'} -:scrollArea.Details_Utils::DetailsButton {text='Details' type='Utils::DetailsButton' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow'} +:qtdirList_QTreeView {container=':qt_tabwidget_stackedwidget_QScrollArea' name='qtDirList' type='QTreeView' visible='1'} +:scrollArea.Details_Utils::DetailsButton {text='Details' type='QToolButton' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow'} :scrollArea.Edit build configuration:_QComboBox {leftWidget=':scrollArea.Edit build configuration:_QLabel' type='QComboBox' unnamed='1' visible='1'} :scrollArea.Edit build configuration:_QLabel {text='Edit build configuration:' type='QLabel' unnamed='1' visible='1'} :scrollArea.Library not available_QLabel {name='qmlDebuggingWarningText' text?='Library not available*' type='QLabel' visible='1' window=':Qt Creator_Core::Internal::MainWindow'} @@ -222,5 +220,5 @@ :splitter.Commit File(s)_VcsBase::QActionPushButton {text~='(Commit .+/.+ File.*)' type='QToolButton' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow'} :splitter.Description_QGroupBox {container=':Qt Creator.splitter_QSplitter' name='descriptionBox' title='Description' type='QGroupBox' visible='1'} :splitter.Files_QGroupBox {container=':Qt Creator.splitter_QSplitter' name='groupBox' title='Files' type='QGroupBox' visible='1'} -:stackedWidget.plainTextEdit_QPlainTextEdit {container=':Send to Codepaster.stackedWidget_QStackedWidget' name='plainTextEdit' type='QPlainTextEdit' visible='1'} -:uiDescription_QLineEdit {buddy=':Send to Codepaster.Description:_QLabel' name='uiDescription' type='QLineEdit' visible='1'} +:stackedWidget.plainTextEdit_QPlainTextEdit {name='plainTextEdit' type='QPlainTextEdit' visible='1' window=':Send to Codepaster_CodePaster::PasteView'} +:uiDescription_QLineEdit {name='uiDescription' type='QLineEdit' visible='1'} diff --git a/tests/system/shared/clang.py b/tests/system/shared/clang.py index 3687e776da6..97de36b16ab 100644 --- a/tests/system/shared/clang.py +++ b/tests/system/shared/clang.py @@ -43,5 +43,5 @@ def getCodeModelString(useClang): def checkCodeModelSettings(useClang): __openCodeModelOptions__() test.log("Verifying whether 'Ignore pre-compiled headers' is unchecked by default.") - verifyChecked("{name='ignorePCHCheckBox' type='QCheckBox' visible='1'}", False) + verifyChecked("{text='Ignore precompiled headers' type='QCheckBox' visible='1'}", False) clickButton(waitForObject(":Options.OK_QPushButton")) diff --git a/tests/system/shared/debugger.py b/tests/system/shared/debugger.py index 338d83cd758..2c4a44ae987 100644 --- a/tests/system/shared/debugger.py +++ b/tests/system/shared/debugger.py @@ -32,7 +32,7 @@ def handleDebuggerWarnings(config, isMsvcBuild=False): def takeDebuggerLog(): invokeMenuItem("View", "Views", "Global Debugger Log") debuggerLogWindow = waitForObject("{container=':DebugModeWidget.Debugger Log_QDockWidget' " - "type='Debugger::Internal::DebuggerPane' unnamed='1' visible='1'}") + "type='QPlainTextEdit' unnamed='1' visible='1'}") debuggerLog = str(debuggerLogWindow.plainText) mouseClick(debuggerLogWindow) invokeContextMenuItem(debuggerLogWindow, "Clear Contents") diff --git a/tests/system/suite_APTW/tst_APTW03/test.py b/tests/system/suite_APTW/tst_APTW03/test.py index 1202558a223..246dc37607e 100644 --- a/tests/system/suite_APTW/tst_APTW03/test.py +++ b/tests/system/suite_APTW/tst_APTW03/test.py @@ -112,7 +112,7 @@ def main(): invokeContextMenuItem(editor, "Toggle Comment Selection") virtualFunctionsAdded = True invokeMenuItem('File', 'Save All') - selectFromLocator("t rebuild", "Rebuild (Rebuild Project)") + selectFromLocator("t rebuild", "Rebuild Project") waitForCompile(10000) checkCompile() diff --git a/tests/system/suite_debugger/tst_cli_output_console/test.py b/tests/system/suite_debugger/tst_cli_output_console/test.py index 20db24d1866..95ccc298188 100644 --- a/tests/system/suite_debugger/tst_cli_output_console/test.py +++ b/tests/system/suite_debugger/tst_cli_output_console/test.py @@ -44,8 +44,7 @@ def main(): test.log("Running application") setRunInTerminal(kit, False) clickButton(waitForObject(":*Qt Creator.Run_Core::Internal::FancyToolButton")) - outputButton = waitForObject(":Qt Creator_AppOutput_Core::Internal::OutputPaneToggleButton") - waitFor("outputButton.checked", 20000) # Not ensureChecked(), avoid race condition + ensureChecked(":Qt Creator_AppOutput_Core::Internal::OutputPaneToggleButton") outputWindow = waitForObject(":Qt Creator_Core::OutputWindow") waitFor("'exited with code' in str(outputWindow.plainText) or \ 'The program has unexpectedly finished' in str(outputWindow.plainText)", 20000) diff --git a/tests/system/suite_debugger/tst_debug_empty_main/test.py b/tests/system/suite_debugger/tst_debug_empty_main/test.py index 92d79ca2288..0bbf7fa755b 100644 --- a/tests/system/suite_debugger/tst_debug_empty_main/test.py +++ b/tests/system/suite_debugger/tst_debug_empty_main/test.py @@ -78,7 +78,7 @@ def performDebugging(projectName): test.log("Selecting '%s' as build config" % config) verifyBuildConfig(kit, config, True, True, buildSystem="qmake") waitForObject(":*Qt Creator.Build Project_Core::Internal::FancyToolButton") - selectFromLocator("t rebuild", "Rebuild (Rebuild All Projects)") + selectFromLocator("t rebuild", "Rebuild All Projects") waitForCompile() isMsvc = isMsvcConfig(kit) clickButton(waitForObject(":*Qt Creator.Start Debugging_Core::Internal::FancyToolButton")) diff --git a/tests/system/suite_debugger/tst_simple_analyze/test.py b/tests/system/suite_debugger/tst_simple_analyze/test.py index 8cb3abec1a1..3bd04a6a6ee 100644 --- a/tests/system/suite_debugger/tst_simple_analyze/test.py +++ b/tests/system/suite_debugger/tst_simple_analyze/test.py @@ -42,11 +42,11 @@ def performTest(workingDir, projectName, availableConfigs): for kit, config in availableConfigs: # switching from MSVC to MinGW build will fail on the clean step of 'Rebuild All Projects' # because of differences between MSVC's and MinGW's Makefile (so clean before changing kit) - selectFromLocator("t clean", "Clean (Clean Project)") + selectFromLocator("t clean", "Clean Project") verifyBuildConfig(kit, config, True, True, True) test.log("Selected kit '%s'" % Targets.getStringForTarget(kit)) # explicitly build before start debugging for adding the executable as allowed program to WinFW - selectFromLocator("t rebuild", "Rebuild (Rebuild All Projects)") + selectFromLocator("t rebuild", "Rebuild All Projects") waitForCompile() if not checkCompile(): test.fatal("Compile had errors... Skipping current build config") diff --git a/tests/system/suite_debugger/tst_simple_debug/test.py b/tests/system/suite_debugger/tst_simple_debug/test.py index 7b3c166028e..4cfbaa30d6b 100644 --- a/tests/system/suite_debugger/tst_simple_debug/test.py +++ b/tests/system/suite_debugger/tst_simple_debug/test.py @@ -37,7 +37,7 @@ def main(): test.log("Selecting '%s' as build config" % config) verifyBuildConfig(kit, config, True, True, True) # explicitly build before start debugging for adding the executable as allowed program to WinFW - selectFromLocator("t rebuild", "Rebuild (Rebuild All Projects)") + selectFromLocator("t rebuild", "Rebuild All Projects") waitForCompile(300000) if not checkCompile(): test.fatal("Compile had errors... Skipping current build config") diff --git a/tests/system/suite_editors/tst_clean_whitespaces/test.py b/tests/system/suite_editors/tst_clean_whitespaces/test.py index 7ffbf41e79e..49dd44397b3 100644 --- a/tests/system/suite_editors/tst_clean_whitespaces/test.py +++ b/tests/system/suite_editors/tst_clean_whitespaces/test.py @@ -114,8 +114,10 @@ def ignoredFilesFromSettings(): waitForObject("{container=':Options.qt_tabwidget_tabbar_QTabBar' type='TabItem' " "text='Behavior'}") clickOnTab(":Options.qt_tabwidget_tabbar_QTabBar", "Behavior") - ensureChecked("{type='QCheckBox' name='skipTrailingWhitespace'}") - ignoredLineEdit = waitForObject("{type='QLineEdit' name='ignoreFileTypes'}") + cleanWhiteSpaceCB = "{type='QCheckBox' text='Skip clean whitespace for file types:'}" + ensureChecked(cleanWhiteSpaceCB) + ignoredLE = "{type='QLineEdit' leftWidget=%s}" % cleanWhiteSpaceCB + ignoredLineEdit = waitForObject(ignoredLE) ignoredFiles = str(ignoredLineEdit.text).split(',') test.log("Ignored files: %s" % str(ignoredFiles)) clickButton(":Options.Cancel_QPushButton") diff --git a/tests/system/suite_editors/tst_generic_highlighter/test.py b/tests/system/suite_editors/tst_generic_highlighter/test.py index 7e7296bd1ef..f1f154376ec 100644 --- a/tests/system/suite_editors/tst_generic_highlighter/test.py +++ b/tests/system/suite_editors/tst_generic_highlighter/test.py @@ -35,8 +35,8 @@ def getOrModifyFilePatternsFor(mimeType, filter='', toBePresent=None): waitForObject("{container=':Options.qt_tabwidget_tabbar_QTabBar' type='TabItem' " "text='MIME Types'}") clickOnTab(":Options.qt_tabwidget_tabbar_QTabBar", "MIME Types") - replaceEditorContent(waitForObject("{name='filterLineEdit' type='QLineEdit' visible='1'}"), - filter) + replaceEditorContent(waitForObject("{name='filterLineEdit' type='Utils::FancyLineEdit' " + "visible='1'}"), filter) mimeTypeTable = waitForObject("{name='mimeTypesTreeView' type='QTreeView' visible='1'}") model = mimeTypeTable.model() if filter == '': @@ -99,7 +99,7 @@ def addHighlighterDefinition(*languages): "text='Generic Highlighter'}") clickOnTab(":Options.qt_tabwidget_tabbar_QTabBar", "Generic Highlighter") - clickButton("{text='Download Definitions' type='QPushButton' name='downloadDefinitions' visible='1'}") + clickButton("{text='Download Definitions' type='QPushButton' unnamed='1' visible='1'}") updateStatus = "{name='updateStatus' type='QLabel' visible='1'}" waitFor("object.exists(updateStatus)", 5000) if waitFor('str(findObject(updateStatus).text) == "Download finished"', 5000): diff --git a/tests/system/suite_general/tst_build_speedcrunch/test.py b/tests/system/suite_general/tst_build_speedcrunch/test.py index 0618f058914..5a606a837d7 100644 --- a/tests/system/suite_general/tst_build_speedcrunch/test.py +++ b/tests/system/suite_general/tst_build_speedcrunch/test.py @@ -36,7 +36,7 @@ def main(): test.log("Testing build configuration: " + config) invokeMenuItem("Build", "Run qmake") waitForCompile() - selectFromLocator("t rebuild", "Rebuild (Rebuild All Projects)") + selectFromLocator("t rebuild", "Rebuild All Projects") waitForCompile(300000) checkCompile() checkLastBuild() diff --git a/tests/system/suite_general/tst_cmake_speedcrunch/test.py b/tests/system/suite_general/tst_cmake_speedcrunch/test.py index 380993e233d..b74c3d5cc9b 100644 --- a/tests/system/suite_general/tst_cmake_speedcrunch/test.py +++ b/tests/system/suite_general/tst_cmake_speedcrunch/test.py @@ -44,7 +44,7 @@ def main(): compareProjectTree(naviTreeView % "speedcrunch( \[\S+\])?", treeFile) # Invoke a rebuild of the application - selectFromLocator("t rebuild", "Rebuild (Rebuild All Projects)") + selectFromLocator("t rebuild", "Rebuild All Projects") # Wait for, and test if the build succeeded waitForCompile(300000) diff --git a/tests/system/suite_tools/tst_git_clone/test.py b/tests/system/suite_tools/tst_git_clone/test.py index af2248b062b..3a8678e52d7 100644 --- a/tests/system/suite_tools/tst_git_clone/test.py +++ b/tests/system/suite_tools/tst_git_clone/test.py @@ -47,8 +47,8 @@ def verifyVersionControlView(targetDir, canceled): "Searching for target directory in clone log") test.verify(" ".join(["clone", "--progress", cloneUrl, cloneDir]) in vcsLog, "Searching for git parameters in clone log") - test.verify(canceled == (" terminated abnormally" in vcsLog), - "Searching for result in clone log") + test.compare(canceled, " terminated abnormally" in vcsLog, + "Searching for result in clone log") clickButton(waitForObject(":*Qt Creator.Clear_QToolButton")) def verifyFiles(targetDir): @@ -117,5 +117,5 @@ def main(): test.fail("The checked out project was not being opened.", str(waitForObject(":Cannot Open Project_QTextEdit").plainText)) clickButton(waitForObject(":Cannot Open Project.OK_QPushButton")) - verifyVersionControlView(targetDir, button != ":Git Repository Clone.Finish_QPushButton") + verifyVersionControlView(targetDir, button == "Cancel immediately") invokeMenuItem("File", "Exit")