diff options
author | Eike Ziller <eike.ziller@qt.io> | 2021-10-08 16:00:32 +0200 |
---|---|---|
committer | Eike Ziller <eike.ziller@qt.io> | 2021-10-08 16:00:32 +0200 |
commit | 2ae0d4d0b1548d2d1ab61582a946cf3766856ca5 (patch) | |
tree | 98f0a7678810a5d06056743b9b965ae69a4f9b68 | |
parent | 9cb0bd94f0473077269078cc0da34d5e1fb600a0 (diff) | |
parent | 7bb21fcfff2b0fe6c6248ff85cdafae452ef7c8c (diff) |
Merge remote-tracking branch 'origin/6.0'
Change-Id: I655155b35747082ac891a4b95e1680871d9b3234
179 files changed, 3174 insertions, 1099 deletions
diff --git a/.github/workflows/build_cmake.yml b/.github/workflows/build_cmake.yml index e3a7750119..15cab0d19f 100644 --- a/.github/workflows/build_cmake.yml +++ b/.github/workflows/build_cmake.yml @@ -3,7 +3,7 @@ name: CMake Build Matrix on: [push, pull_request] env: - QT_VERSION: 5.15.2 + QT_VERSION: 6.2.0 CLANG_VERSION: 130 ELFUTILS_VERSION: 0.175 CMAKE_VERSION: 3.21.1 @@ -111,24 +111,20 @@ jobs: set(qt_package_arch_suffix "win64_msvc2019_64") set(qt_dir_prefix "${qt_version}/msvc2019_64") set(qt_package_suffix "-Windows-Windows_10-MSVC2019-Windows-Windows_10-X86_64") - elseif ("${{ matrix.config.environment_script }}" MATCHES "vcvars32.bat") - set(qt_package_arch_suffix "win32_msvc2019") - set(qt_dir_prefix "${qt_version}/msvc2019") - set(qt_package_suffix "-Windows-Windows_10-MSVC2019-Windows-Windows_10-X86") endif() elseif ("${{ runner.os }}" STREQUAL "Linux") set(url_os "linux_x64") set(qt_package_arch_suffix "gcc_64") set(qt_dir_prefix "${qt_version}/gcc_64") - set(qt_package_suffix "-Linux-RHEL_7_6-GCC-Linux-RHEL_7_6-X86_64") + set(qt_package_suffix "-Linux-RHEL_8_2-GCC-Linux-RHEL_8_2-X86_64") elseif ("${{ runner.os }}" STREQUAL "macOS") set(url_os "mac_x64") set(qt_package_arch_suffix "clang_64") - set(qt_dir_prefix "${qt_version}/clang_64") - set(qt_package_suffix "-MacOS-MacOS_10_13-Clang-MacOS-MacOS_10_13-X86_64") + set(qt_dir_prefix "${qt_version}/macos") + set(qt_package_suffix "-MacOS-MacOS_11_00-Clang-MacOS-MacOS_11_00-X86_64-ARM64") endif() - set(qt_base_url "https://\${qt_mirror}/online/qtsdkrepository/${url_os}/desktop/qt5_${qt_version_dotless}") + set(qt_base_url "https://\${qt_mirror}/online/qtsdkrepository/${url_os}/desktop/qt6_${qt_version_dotless}") foreach(qt_mirror $ENV{QT_MIRRORS}) cmake_language(EVAL CODE " message(\"Downloading: ${qt_base_url}/Updates.xml\") @@ -141,13 +137,13 @@ jobs: endforeach() file(READ ./Updates.xml updates_xml) - string(REGEX MATCH "<Name>qt.qt5.*<Version>([0-9+-.]+)</Version>" updates_xml_output "${updates_xml}") + string(REGEX MATCH "<Name>qt.qt6.*<Version>([0-9+-.]+)</Version>" updates_xml_output "${updates_xml}") set(qt_package_version ${CMAKE_MATCH_1}) - file(MAKE_DIRECTORY qt5) + file(MAKE_DIRECTORY qt6) # Save the path for other steps - file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/qt5/${qt_dir_prefix}" qt_dir) + file(TO_CMAKE_PATH "$ENV{GITHUB_WORKSPACE}/qt6/${qt_dir_prefix}" qt_dir) message("::set-output name=qt_dir::${qt_dir}") function(downloadAndExtract url archive) @@ -161,19 +157,26 @@ jobs: break() endif() endforeach() - execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ../${archive} WORKING_DIRECTORY qt5) + execute_process(COMMAND ${CMAKE_COMMAND} -E tar xvf ../${archive} WORKING_DIRECTORY qt6) endfunction() - foreach(package qtbase qtdeclarative qttools qtsvg qtserialport qtquickcontrols qtquickcontrols2 qtgraphicaleffects qtlocation qtimageformats qttranslations) + foreach(package qtbase qtdeclarative qttools qtsvg qttranslations) + downloadAndExtract( + "${qt_base_url}/qt.qt6.${qt_version_dotless}.${qt_package_arch_suffix}/${qt_package_version}${package}${qt_package_suffix}.7z" + ${package}.7z + ) + endforeach() + + foreach(package qtimageformats qtserialport) downloadAndExtract( - "${qt_base_url}/qt.qt5.${qt_version_dotless}.${qt_package_arch_suffix}/${qt_package_version}${package}${qt_package_suffix}.7z" + "${qt_base_url}/qt.qt6.${qt_version_dotless}.addons.${package}.${qt_package_arch_suffix}/${qt_package_version}${package}${qt_package_suffix}.7z" ${package}.7z ) endforeach() - foreach(package qtquicktimeline qtquick3d qtscript) + foreach(package qtquicktimeline qtquick3d qt5compat qtshadertools) downloadAndExtract( - "${qt_base_url}/qt.qt5.${qt_version_dotless}.${package}.${qt_package_arch_suffix}/${qt_package_version}${package}${qt_package_suffix}.7z" + "${qt_base_url}/qt.qt6.${qt_version_dotless}.${package}.${qt_package_arch_suffix}/${qt_package_version}${package}${qt_package_suffix}.7z" ${package}.7z ) endforeach() @@ -181,17 +184,17 @@ jobs: # uic depends on libicu56.so if ("${{ runner.os }}" STREQUAL "Linux") downloadAndExtract( - "${qt_base_url}/qt.qt5.${qt_version_dotless}.${qt_package_arch_suffix}/${qt_package_version}icu-linux-Rhel7.2-x64.7z" + "${qt_base_url}/qt.qt6.${qt_version_dotless}.${qt_package_arch_suffix}/${qt_package_version}icu-linux-Rhel7.2-x64.7z" icu.7z ) endif() - file(READ "qt5/${qt_dir_prefix}/mkspecs/qconfig.pri" qtconfig) + file(READ "qt6/${qt_dir_prefix}/mkspecs/qconfig.pri" qtconfig) string(REPLACE "Enterprise" "OpenSource" qtconfig "${qtconfig}") string(REPLACE "licheck.exe" "" qtconfig "${qtconfig}") string(REPLACE "licheck64" "" qtconfig "${qtconfig}") string(REPLACE "licheck_mac" "" qtconfig "${qtconfig}") - file(WRITE "qt5/${qt_dir_prefix}/mkspecs/qconfig.pri" "${qtconfig}") + file(WRITE "qt6/${qt_dir_prefix}/mkspecs/qconfig.pri" "${qtconfig}") if ("${{ runner.os }}" STREQUAL "Windows") # deploy "system" runtimes into Qt, so they get deployed as well @@ -199,7 +202,7 @@ jobs: # deploy MinGW foreach(file libwinpthread-1.dll libstdc++-6.dll libgcc_s_seh-1.dll) file(INSTALL "C:/ProgramData/chocolatey/lib/mingw/tools/install/mingw64/bin/${file}" - DESTINATION "qt5/${qt_dir_prefix}/bin" + DESTINATION "qt6/${qt_dir_prefix}/bin" USE_SOURCE_PERMISSIONS) endforeach() else() @@ -207,7 +210,7 @@ jobs: foreach(file vcruntime140.dll concrt140.dll msvcp140_1.dll msvcp140_2.dll msvcp140_codecvt_ids.dll vcruntime140_1.dll msvcp140.dll) file(INSTALL "C:/Windows/System32/${file}" - DESTINATION "qt5/${qt_dir_prefix}/bin") + DESTINATION "qt6/${qt_dir_prefix}/bin") endforeach() endif() endif() @@ -377,7 +380,7 @@ jobs: COMMAND sudo apt update ) execute_process( - COMMAND sudo apt install libgl1-mesa-dev + COMMAND sudo apt install libgl1-mesa-dev libvulkan-dev libxcb-xinput-dev libxcb-xinerama0-dev libxkbcommon-dev libxkbcommon-x11-dev RESULT_VARIABLE result ) if (NOT result EQUAL 0) diff --git a/dist/changes-6.0.0.md b/dist/changes-6.0.0.md index c16f46c3cf..90db19cc57 100644 --- a/dist/changes-6.0.0.md +++ b/dist/changes-6.0.0.md @@ -24,6 +24,7 @@ Editing ### C++ +* Updated to LLVM 13 * Added completion and function hint to `clangd` support * Added option for saving open files automatically after refactoring (QTCREATORBUG-25924) @@ -36,6 +37,11 @@ Editing * Improved wizards for Qt 6.2 (QTCREATORBUG-26170) * Simplified wizards +### Language Server Protocol + +* Added support for `activeParameter` of signature information + (QTCREATORBUG-26346) + Projects -------- @@ -56,7 +62,13 @@ Projects `Issues` pane (QTCREATORBUG-26231) * Fixed header file handling when mentioned in target sources (QTCREATORBUG-23783, QTCREATORBUG-23843, QTCREATORBUG-26201, - QTCREATORBUG-26238) + QTCREATORBUG-26238, QTCREATORBUG-21452, QTCREATORBUG-25644, + QTCREATORBUG-25782) +* Fixed that generated files were selected for analyzing (QTCREATORBUG-25125) + +### qmake + +* Fixed crash when canceling parsing (QTCREATORBUG-26333) Version Control Systems ----------------------- @@ -87,6 +99,7 @@ Platforms * Removed device selection dialog in favor of device selection in target selector (QTCREATORBUG-23991) * Added details to device settings (QTCREATORBUG-23991) +* Added filter field for Android SDK manager ### Docker @@ -103,6 +116,7 @@ André Pönitz Artem Sokolovskii Artur Shepilko Assam Boudjelthia +Christiaan Janssen Christian Kandeler Christian Stenger Cristian Adam @@ -112,14 +126,17 @@ Fawzi Mohamed Henning Gruendl Ihor Dutchak Jaroslaw Kobus +Johanna Vanhatapio Jonas Karlsson Kai Köhne Kama Wójcik +Knud Dollereder Li Xi Loren Burkholder Mahmoud Badri Marco Bubke Martin Kampas +Miikka Heikkinen Miina Puuronen Orgad Shaneh Petar Perisin @@ -129,6 +146,7 @@ Shantanu Tushar Tasuku Suzuki Thiago Macieira Thomas Hartmann +Tim Jenssen Tony Leinonen Tor Arne Vestbø Vladimir Serdyuk diff --git a/doc/qtcreator/images/extraimages/images/9MqUCP6JLCQ.jpg b/doc/qtcreator/images/extraimages/images/9MqUCP6JLCQ.jpg Binary files differnew file mode 100644 index 0000000000..db8ba3d920 --- /dev/null +++ b/doc/qtcreator/images/extraimages/images/9MqUCP6JLCQ.jpg diff --git a/doc/qtcreator/images/extraimages/qtdesignstudio-extraimages.qdocconf b/doc/qtcreator/images/extraimages/qtdesignstudio-extraimages.qdocconf index a0c485b95f..7a4f2d4dce 100644 --- a/doc/qtcreator/images/extraimages/qtdesignstudio-extraimages.qdocconf +++ b/doc/qtcreator/images/extraimages/qtdesignstudio-extraimages.qdocconf @@ -12,4 +12,5 @@ images/pEETxSxYazg.jpg \ images/V3Po15bNErw.jpg \ images/bMXeeQw6BYs.jpg \ - images/u3kZJjlk3CY.jpg + images/u3kZJjlk3CY.jpg \ + images/9MqUCP6JLCQ.jpg diff --git a/doc/qtcreator/src/qtquick/library/qtquick-preset-components.qdoc b/doc/qtcreator/src/qtquick/library/qtquick-preset-components.qdoc index cc896f78c9..488dbbdfe0 100644 --- a/doc/qtcreator/src/qtquick/library/qtquick-preset-components.qdoc +++ b/doc/qtcreator/src/qtquick/library/qtquick-preset-components.qdoc @@ -86,6 +86,7 @@ \li \l {Morph Target} \li \l {Repeater3D} \li \l {Loader3D} + \li \l {Particles} \endlist When you import 3D scenes from files that you exported from 3D graphics diff --git a/doc/qtcreator/src/qtquick/qtquick-library.qdoc b/doc/qtcreator/src/qtquick/qtquick-library.qdoc index 3c73fcfa4a..b1d03a72e6 100644 --- a/doc/qtcreator/src/qtquick/qtquick-library.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-library.qdoc @@ -85,6 +85,7 @@ \li \l{Scene Environment} \li \l{Morph Target} \li \l{Repeater3D} + \li \l{Particles} \endlist For more information about creating your own components, see diff --git a/doc/qtdesignstudio/images/icons/attractor-16px.png b/doc/qtdesignstudio/images/icons/attractor-16px.png Binary files differnew file mode 100644 index 0000000000..f2d49e7fd2 --- /dev/null +++ b/doc/qtdesignstudio/images/icons/attractor-16px.png diff --git a/doc/qtdesignstudio/images/icons/emit-burst-16px.png b/doc/qtdesignstudio/images/icons/emit-burst-16px.png Binary files differnew file mode 100644 index 0000000000..d425974154 --- /dev/null +++ b/doc/qtdesignstudio/images/icons/emit-burst-16px.png diff --git a/doc/qtdesignstudio/images/icons/emitter-16px.png b/doc/qtdesignstudio/images/icons/emitter-16px.png Binary files differnew file mode 100644 index 0000000000..1fce677afe --- /dev/null +++ b/doc/qtdesignstudio/images/icons/emitter-16px.png diff --git a/doc/qtdesignstudio/images/icons/gravity-16px.png b/doc/qtdesignstudio/images/icons/gravity-16px.png Binary files differnew file mode 100644 index 0000000000..4d182620cf --- /dev/null +++ b/doc/qtdesignstudio/images/icons/gravity-16px.png diff --git a/doc/qtdesignstudio/images/icons/item-ellipse-16px.png b/doc/qtdesignstudio/images/icons/item-ellipse-16px.png Binary files differnew file mode 100644 index 0000000000..ab0e225db1 --- /dev/null +++ b/doc/qtdesignstudio/images/icons/item-ellipse-16px.png diff --git a/doc/qtdesignstudio/images/icons/item-polygon-16px.png b/doc/qtdesignstudio/images/icons/item-polygon-16px.png Binary files differnew file mode 100644 index 0000000000..b56c7bb714 --- /dev/null +++ b/doc/qtdesignstudio/images/icons/item-polygon-16px.png diff --git a/doc/qtdesignstudio/images/icons/model-blend-particle-16px.png b/doc/qtdesignstudio/images/icons/model-blend-particle-16px.png Binary files differnew file mode 100644 index 0000000000..0f77c30cf2 --- /dev/null +++ b/doc/qtdesignstudio/images/icons/model-blend-particle-16px.png diff --git a/doc/qtdesignstudio/images/icons/model-particle-16px.png b/doc/qtdesignstudio/images/icons/model-particle-16px.png Binary files differnew file mode 100644 index 0000000000..39cf9eeb23 --- /dev/null +++ b/doc/qtdesignstudio/images/icons/model-particle-16px.png diff --git a/doc/qtdesignstudio/images/icons/model-shape-16px.png b/doc/qtdesignstudio/images/icons/model-shape-16px.png Binary files differnew file mode 100644 index 0000000000..4113a5070b --- /dev/null +++ b/doc/qtdesignstudio/images/icons/model-shape-16px.png diff --git a/doc/qtdesignstudio/images/icons/particle-shape-16px.png b/doc/qtdesignstudio/images/icons/particle-shape-16px.png Binary files differnew file mode 100644 index 0000000000..e8242fb449 --- /dev/null +++ b/doc/qtdesignstudio/images/icons/particle-shape-16px.png diff --git a/doc/qtdesignstudio/images/icons/particle-system-16px.png b/doc/qtdesignstudio/images/icons/particle-system-16px.png Binary files differnew file mode 100644 index 0000000000..66a0396750 --- /dev/null +++ b/doc/qtdesignstudio/images/icons/particle-system-16px.png diff --git a/doc/qtdesignstudio/images/icons/point-rotator-16px.png b/doc/qtdesignstudio/images/icons/point-rotator-16px.png Binary files differnew file mode 100644 index 0000000000..f44a7f83f5 --- /dev/null +++ b/doc/qtdesignstudio/images/icons/point-rotator-16px.png diff --git a/doc/qtdesignstudio/images/icons/sprite-particle-16px.png b/doc/qtdesignstudio/images/icons/sprite-particle-16px.png Binary files differnew file mode 100644 index 0000000000..14c6142b86 --- /dev/null +++ b/doc/qtdesignstudio/images/icons/sprite-particle-16px.png diff --git a/doc/qtdesignstudio/images/icons/sprite-sequence-16px.png b/doc/qtdesignstudio/images/icons/sprite-sequence-16px.png Binary files differnew file mode 100644 index 0000000000..0174962ca4 --- /dev/null +++ b/doc/qtdesignstudio/images/icons/sprite-sequence-16px.png diff --git a/doc/qtdesignstudio/images/icons/target-direction-16px.png b/doc/qtdesignstudio/images/icons/target-direction-16px.png Binary files differnew file mode 100644 index 0000000000..4295336833 --- /dev/null +++ b/doc/qtdesignstudio/images/icons/target-direction-16px.png diff --git a/doc/qtdesignstudio/images/icons/trail-emitter-16px.png b/doc/qtdesignstudio/images/icons/trail-emitter-16px.png Binary files differnew file mode 100644 index 0000000000..284bf9af02 --- /dev/null +++ b/doc/qtdesignstudio/images/icons/trail-emitter-16px.png diff --git a/doc/qtdesignstudio/images/icons/vector-direction-16px.png b/doc/qtdesignstudio/images/icons/vector-direction-16px.png Binary files differnew file mode 100644 index 0000000000..ef8e871df0 --- /dev/null +++ b/doc/qtdesignstudio/images/icons/vector-direction-16px.png diff --git a/doc/qtdesignstudio/images/icons/wander-16px.png b/doc/qtdesignstudio/images/icons/wander-16px.png Binary files differnew file mode 100644 index 0000000000..b2d43ed5c0 --- /dev/null +++ b/doc/qtdesignstudio/images/icons/wander-16px.png diff --git a/doc/qtdesignstudio/images/studio-3d-particle-system-navigator.png b/doc/qtdesignstudio/images/studio-3d-particle-system-navigator.png Binary files differnew file mode 100644 index 0000000000..01e4519caa --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-particle-system-navigator.png diff --git a/doc/qtdesignstudio/images/studio-3d-particles-fire-assets.png b/doc/qtdesignstudio/images/studio-3d-particles-fire-assets.png Binary files differnew file mode 100644 index 0000000000..ad3a94c162 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-particles-fire-assets.png diff --git a/doc/qtdesignstudio/images/studio-3d-particles-fire-components.png b/doc/qtdesignstudio/images/studio-3d-particles-fire-components.png Binary files differnew file mode 100644 index 0000000000..e0f112b33b --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-particles-fire-components.png diff --git a/doc/qtdesignstudio/images/studio-3d-particles-fire-emitter1.png b/doc/qtdesignstudio/images/studio-3d-particles-fire-emitter1.png Binary files differnew file mode 100644 index 0000000000..ee56c599c7 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-particles-fire-emitter1.png diff --git a/doc/qtdesignstudio/images/studio-3d-particles-fire-properties-particle-emitter.png b/doc/qtdesignstudio/images/studio-3d-particles-fire-properties-particle-emitter.png Binary files differnew file mode 100644 index 0000000000..9b8106a8b7 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-particles-fire-properties-particle-emitter.png diff --git a/doc/qtdesignstudio/images/studio-3d-particles-fire-properties-particle-system.png b/doc/qtdesignstudio/images/studio-3d-particles-fire-properties-particle-system.png Binary files differnew file mode 100644 index 0000000000..9a36cba2d8 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-particles-fire-properties-particle-system.png diff --git a/doc/qtdesignstudio/images/studio-3d-particles-fire-properties-particle.png b/doc/qtdesignstudio/images/studio-3d-particles-fire-properties-particle.png Binary files differnew file mode 100644 index 0000000000..6b9282c650 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-particles-fire-properties-particle.png diff --git a/doc/qtdesignstudio/images/studio-3d-particles-fire-properties-sprite-particle.png b/doc/qtdesignstudio/images/studio-3d-particles-fire-properties-sprite-particle.png Binary files differnew file mode 100644 index 0000000000..1781d98524 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-particles-fire-properties-sprite-particle.png diff --git a/doc/qtdesignstudio/images/studio-3d-particles-fire-properties-vector-direction.png b/doc/qtdesignstudio/images/studio-3d-particles-fire-properties-vector-direction.png Binary files differnew file mode 100644 index 0000000000..fe4d990a0d --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-particles-fire-properties-vector-direction.png diff --git a/doc/qtdesignstudio/images/studio-3d-particles-fire.gif b/doc/qtdesignstudio/images/studio-3d-particles-fire.gif Binary files differnew file mode 100644 index 0000000000..292379041b --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-particles-fire.gif diff --git a/doc/qtdesignstudio/images/studio-3d-particles.png b/doc/qtdesignstudio/images/studio-3d-particles.png Binary files differnew file mode 100644 index 0000000000..48f963ede6 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-particles.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-model-blend-particle.png b/doc/qtdesignstudio/images/studio-3d-properties-model-blend-particle.png Binary files differnew file mode 100644 index 0000000000..a0de57542d --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-model-blend-particle.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-model-particle.png b/doc/qtdesignstudio/images/studio-3d-properties-model-particle.png Binary files differnew file mode 100644 index 0000000000..bfbaf3a360 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-model-particle.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-affector.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-affector.png Binary files differnew file mode 100644 index 0000000000..16000bdb24 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle-affector.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-attractor.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-attractor.png Binary files differnew file mode 100644 index 0000000000..fb46ef050e --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle-attractor.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-emit-burst.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-emit-burst.png Binary files differnew file mode 100644 index 0000000000..3638646773 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle-emit-burst.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-emitter.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-emitter.png Binary files differnew file mode 100644 index 0000000000..291d413290 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle-emitter.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-gravity.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-gravity.png Binary files differnew file mode 100644 index 0000000000..b945aa85c1 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle-gravity.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-model-shape.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-model-shape.png Binary files differnew file mode 100644 index 0000000000..910b73ae37 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle-model-shape.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-point-rotator.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-point-rotator.png Binary files differnew file mode 100644 index 0000000000..4069a35bda --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle-point-rotator.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-rotation.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-rotation.png Binary files differnew file mode 100644 index 0000000000..207d0a6763 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle-rotation.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-shape.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-shape.png Binary files differnew file mode 100644 index 0000000000..b84095fa69 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle-shape.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-sprite-sequence.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-sprite-sequence.png Binary files differnew file mode 100644 index 0000000000..8a41409b12 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle-sprite-sequence.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-system.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-system.png Binary files differnew file mode 100644 index 0000000000..1d2205c7cc --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle-system.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-target-direction.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-target-direction.png Binary files differnew file mode 100644 index 0000000000..a5d7b63010 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle-target-direction.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-vector-direction.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-vector-direction.png Binary files differnew file mode 100644 index 0000000000..898fd75f15 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle-vector-direction.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-wander-global.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-wander-global.png Binary files differnew file mode 100644 index 0000000000..bbf45be008 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle-wander-global.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-wander-unique.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-wander-unique.png Binary files differnew file mode 100644 index 0000000000..dabef3c86f --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle-wander-unique.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle-wander.png b/doc/qtdesignstudio/images/studio-3d-properties-particle-wander.png Binary files differnew file mode 100644 index 0000000000..0ce910c09f --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle-wander.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-particle.png b/doc/qtdesignstudio/images/studio-3d-properties-particle.png Binary files differnew file mode 100644 index 0000000000..0615eb7733 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-particle.png diff --git a/doc/qtdesignstudio/images/studio-3d-properties-sprite-particle.png b/doc/qtdesignstudio/images/studio-3d-properties-sprite-particle.png Binary files differnew file mode 100644 index 0000000000..6a3a7705f5 --- /dev/null +++ b/doc/qtdesignstudio/images/studio-3d-properties-sprite-particle.png diff --git a/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc b/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc index f917223dfc..dc8dea8f13 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc +++ b/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc @@ -105,6 +105,14 @@ \li \l{Morph Target} \li \l{Repeater3D} \li \l{Loader3D} + \li \l{Particles} + \list + \li \l {Particle System} + \li \l {Logical Particles} + \li \l {Particle Emitters} + \li \l {Particle Affectors} + \li \l {Particle Directions} + \endlist \endlist \li \l {Creating Component Instances} \li \l {Creating Custom Components} diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-lights.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-lights.qdoc index 239a168970..8fd1539d51 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-lights.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-lights.qdoc @@ -60,7 +60,7 @@ \li More Information \row - \li \inlineimage spot.png + \li \inlineimage directional.png \li Directional Light \li \li \l{DirectionalLight}{Light Directional} diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-particles.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-particles.qdoc new file mode 100644 index 0000000000..06e08b267a --- /dev/null +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-particles.qdoc @@ -0,0 +1,1088 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Design Studio. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \page studio-3d-particles.html + \previouspage studio-3d-loader-3d.html + \nextpage studio-3d-particle-system.html + + \title Particles + + A \e {particle system} enables you to use sprites, 3D models, or images to + create effects that are hard to reproduce with conventional rendering + techniques. This includes chaotic systems, natural phenomena, or processes + caused by chemical reactions. For example, you can simulate fire, smoke, + sparks, explosions, flowing water, fog, snow, stars, and galaxies. + + The \QDS particle system contains the following main types of components: + + \list + \li \l {Particle System} + \li \l {Logical Particles} + \li \l {Particle Emitters} + \li \l {Particle Affectors} + \li \l {Particle Directions} + \endlist + + \section1 Adding a Particle System + + Preset particle components are available in \l Library > + \uicontrol Components > \uicontrol {Qt Quick 3D Particles 3D} + after you add the \uicontrol {QtQuick3D.Particles3D} module to + your project, as instructed in \l{Adding and Removing Modules}. + + \image studio-3d-particles.png "3D Particles" + + When you add an instance of the \uicontrol {Particle System} component to a + scene, \QDS automatically adds instances of the \uicontrol {Sprite Particle}, + \uicontrol Emitter, and \uicontrol {Vector Direction} components for you. + + \image studio-3d-particle-system-navigator.png "Particle System in Navigator" + + The \uicontrol {Sprite Particle} is a visual \l{Textures}{2D texture} + particle. To use \l{3D Models}{3D model} particles, add instances of the + \uicontrol {Model Particle} component to the particle system. It is + important to define the amount of particles to use in \uicontrol Properties + > \uicontrol Particle > \uicontrol {Max amount}, so that the optimal buffer + sizes get allocated. You can also specify the color and opacity of the + particles, as well as the speed at which they fade in and out. + + The \uicontrol Emitter component emits the sprite or model particles. + You can specify particle shape and the area where they are emitted. Use + variation in particle size and rotation for more natural results. Further, + define the life span and initial direction of speed of the emitted + particles. + + Usually, affectors are used to make particle movement more interesting. + For example, you could add an instance of the \uicontrol Gravity component + to simulate falling objects, an instance of the \uicontrol Wander component + to simulate flying objects that follow wavy curves, or an instance of the + \uicontrol {Point Rotator} to simulate windy weather. + + To add a particle system that emits sprite particles: + + \list 1 + \li Select \uicontrol Library > \uicontrol Assets > \inlineimage plus.png + to add your sprites, 3D models, textures, and other graphical + \l{Assets}{assets} to the project. + \li Drag-and-drop an instance of the \uicontrol {Particle System} + component from \uicontrol Library to a scene component instance + in \l Navigator. + \li Drag-and-drop an instance of the \uicontrol Texture component + from \uicontrol Library > \uicontrol Components > + \uicontrol {Qt Quick 3D} to the sprite particle instance + in \uicontrol Navigator. + \li Drag-and-drop the sprite image from \uicontrol Library > + \uicontrol Assets to the texture instance in \uicontrol Navigator. + \li Select the sprite particle instance in \uicontrol Navigator to + display its properties in \l Properties. + \li In \uicontrol Sprite, select the texture instance. + \li Select the emitter instance in \uicontrol Navigator to + display its properties in \uicontrol Properties. + \li In \uicontrol Particle, select the particle instance to emit. + \li Select the vector 3D instance in \uicontrol Navigator to + display its properties in \uicontrol Properties. + \li In \uicontrol Direction, set the emitted particle velocity + towards the target vector. + \endlist + + Add instances of other components according to your use case. The following + section describes how to modify particle system component property values by + using the simulation of fire as an example. + + \section1 Example: Fire + + \image studio-3d-particles-fire.gif "Fire simulated by using particles" + + In this section, we explore using particle system components and modifying + their property values, such as particle source images and their color, life + span, and fading effects, to simulate fire. + + We will need the following \l{Assets}{assets}: + + \list + \li Two images of flames: \e flame_01.png and \e flame_03.png + \li An image of smoke: \e smoke_01.png + \endlist + + \image studio-3d-particles-fire-assets.png "The assets in Library" + + We will also need instances of the following components: + + \list + \li \l {Particle System} (one instance) + \li \l {Sprite Particle} (four instances) + \li \l {Emitter} (four instances) + \li \l {Vector Direction} (four instances) + \li \l {Textures}{Texture} (four instances) + \endlist + + We start by creating a \l{Using Project Wizards}{Qt Quick 3D Application} + project and adding the assets to it. We then follow the instructions above + to add the \uicontrol {Particle System} component instance, which provides + us with our first \uicontrol Emitter, \uicontrol {Sprite Particle}, and + \uicontrol {Vector Direction} instances. + + We add a \uicontrol Texture component instance with \e flame_01.png as the + source, and then add the texture as the sprite to use for the sprite + particle. We now have all the component instances we need for our first + emitter. To keep all the component instances together, we drag-and-drop the + sprite particle instance to the emitter instance in \uicontrol Navigator. + + \image studio-3d-particles-fire-emitter1.png "First emitter in fire example in Navigator" + + We need more than one particle to realistically simulate a fire, so we will + add three more emitter instances to the particle system. We can copy-paste + the first instance in \uicontrol Navigator, and just modify two of the + texture instances to use \e flame_03.png as the source and one of them to + use \e smoke_01.png as the source. We use three different flame particles + to be able to layer them and to hide the fact that two of them actually use + the same texture. + + \image studio-3d-particles-fire-components.png "Fire particle system component instances in Navigator" + + We can now start playing with the particle system component properties to + achieve the artistic effect that we want. To see how the changes in property + values affect the simulation, we will open the live preview by selecting + \inlineimage live_preview.png + on the main toolbar (or by pressing \key {Alt+P}). + + First, we will specify property values for the \uicontrol {Particle System} + component. We want to spawn some particles before the simulation starts, so + that the fire will be roaring at start. To achieve this, we set the value of + \uicontrol Properties > \uicontrol {Particle System} > + \uicontrol {Start Time} to \e 2000 milliseconds. We will + use the default values for the other properties. + + \image studio-3d-particles-fire-properties-particle-system.png "Particle System properties" + + Then, we will specify how individual particles are spawned. We set the + value of \uicontrol Properties > \uicontrol {Particle Emitter} > + \uicontrol {Emit rate} to \e 40.00 for the flame particles and \e 20.00 + for the smoke particle, because we want to have more flames than smoke. + + \image studio-3d-particles-fire-properties-particle-emitter.png "Particle Emitter properties" + + To increase the visibility of the smoke at the top, we set the value of + \uicontrol {Life span} to \e 1200 milliseconds for the smoke particles + and \e 900, \e 1000, and \e 600 milliseconds for the flame particles. + Further, we set \uicontrol {Life span variation} to \e 100, \e 200, and + \e 300 for the flame particles to have some of them expire sooner than + others. + + To scale the particles during their lifespan, we set + \uicontrol {Particle scale} to \e 2.00 for all particles and + \uicontrol {Particle end scale} to \e 5.00 for the flame + particles and to \e 6.00 for the smoke particles. + + We can now modify the appearance of the particles by setting their color in + \uicontrol Properties > \uicontrol Particle. We select transparent yellow, + orange, and transparent gray in \uicontrol Color and set values for + \uicontrol {Color variation} to use slightly different colors for the + individual particles. + + \image studio-3d-particles-fire-properties-particle.png "Particle properties" + + We set \uicontrol {Fade in effect} and \uicontrol {Fade out effect} values + to \uicontrol FadeOpacity to change particle opacity between 0 and 1 over + the time specified in milliseconds in \uicontrol {Fade in duration} and + \uicontrol {Fade out duration}. Fading duration is calculated into the + particle lifespan, and therefore the smoke particles are actually never + fully visible with our settings. + + To specify that the particle texture should always be aligned face towards + the screen, we enable \uicontrol Properties > \uicontrol {Sprite Particle} > + \uicontrol Billboard for all the particle component instances. + + \image studio-3d-particles-fire-properties-sprite-particle.png "Sprite Particle properties" + + Finally, we will specify the direction in which the particles move by + modifying the property values of the \uicontrol {Vector Direction} component + instances in \uicontrol Properties > \uicontrol {Particle Vector Direction}. + + In \uicontrol Direction, we set \uicontrol Y to \e 100.00 to make particles + move \e up, and \uicontrol Z to \e -100.00 to make them move in the + direction opposite to the target vector. We set the \uicontrol Direction + value to \e -50.00 for one of the flame particles and to \e -80.00 for the + smoke particle. + + \image studio-3d-particles-fire-properties-vector-direction.png "Vector Direction properties" + + We set the \uicontrol {Direction Variation} values for the different + vector direction instances to \e 10.00, \e 8.00, and \e 12.00 to make some + flames spread wider than others. You can try different values until you + get the effect you want. + + \section1 Performance Considerations + + The particles are designed to be usable on a variety of hardware on + desktops, as well as mobile and embedded devices. However, in addition + to rendering the maximum amount of particle elements on the screen, + extensibility to different use-cases, rendering quality, integration + with the other UI elements, are also important. + + Currently, the rendering runs on GPU, while the particle system logic + runs on CPU. However, the \e {stateless particle system} enables you + to move the system logic onto GPU if that seems beneficial. The initial + measurements indicate that the system is quite well balanced between + CPU and GPU. The stateless system also enables animating particles by + using a \l{Timeline}{timeline}. The model particles use instanced rendering + to boost the performance. Therefore, OpenGL ES 2.0 isn't sufficient to make + rendering performant, and at least OpenGL ES 3.0, Vulkan, or some other + modern backend is required. + + To get a more concrete view on the actual performance, the video below shows + a particles Testbed application running on four different Android devices. + These devices and their chipsets and GPUs could be considered to be + lower-end to mid-range, confirming that the particles can perform well also + on affordable hardware. + + \youtube 9MqUCP6JLCQ + + \section1 Summary of 3D Particles + + \note The \uicontrol {Particles 3D} components are released as a tech + preview feature in \QDS 2.2, and their functionality will be improved + in future releases. + + The following table lists preset particle components. + + \table + \header + \li Icon + \li Name + \li Purpose + \row + \li \inlineimage icons/attractor-16px.png + \li Attractor + \li Attracts particles towards a specific point. + \row + \li \inlineimage icons/emit-burst-16px.png + \li Emit Burst + \li Generates declarative emitter bursts. + \row + \li \inlineimage icons/emitter-16px.png + \li Emitter + \li Emits logical particles. + \row + \li \inlineimage icons/gravity-16px.png + \li Gravity + \li Accelerates particles to a vector of the specified magnitude in the + specified direction. + \row + \li \inlineimage icons/model-blend-particle-16px.png + \li Model Blend Particle + \li Blends a particle effect with an instance of a \uicontrol Model + component. + \row + \li \inlineimage icons/model-particle-16px.png + \li Model Particle + \li Creates a particle that uses an instance of a \uicontrol Model + component. + \row + \li \inlineimage icons/model-shape-16px.png + \li Model Shape + \li Provides 3D shapes from a model to emitters and affectors. + \row + \li \inlineimage icons/particle-shape-16px.png + \li Particle Shape + \li Provides 3D shapes to emitters and affectors. + \row + \li \inlineimage icons/particle-system-16px.png + \li Particle System + \li Creates a particle system that includes particle, emitter, and + affector components. + \row + \li \inlineimage icons/point-rotator-16px.png + \li Point Rotator + \li Rotates particles around a pivot point. + \row + \li \inlineimage icons/sprite-particle-16px.png + \li Sprite Particle + \li Creates particles that use a 2D sprite texture. + \row + \li \inlineimage icons/sprite-sequence-16px.png + \li Sprite Sequence + \li Provides image sequence features for \uicontrol {Sprite Particle} + component instances. + \row + \li \inlineimage icons/target-direction-16px.png + \li Target Direction + \li Specifies a direction towards the target position. + \row + \li \inlineimage icons/trail-emitter-16px.png + \li Trail Emitter + \li Emits logical particles from other particles. + \row + \li \inlineimage icons/vector-direction-16px.png + \li Vector Direction + \li Specifies a direction towards the target direction. + \row + \li \inlineimage icons/wander-16px.png + \li Wander + \li Applies random wave curves to particles. + \endtable +*/ + +/*! + \page studio-3d-particle-system.html + \previouspage studio-3d-particles.html + \nextpage studio-3d-logical-particles.html + + \title Particle System + + The preset \uicontrol {Particle System} component is the root of the + particle system. It ties all the other components together and manages + the shared progression in time. Emitters and affectors must either be + children of the same \uicontrol {Particle System} component or refer + to the same \uicontrol System to be able to interact with each other. + + You can add several \uicontrol {Particle System} components. Typically, you + would use separate \uicontrol {Particle System} instances for the components + that interact with each other. Or, you can use just one instance if the + total number of components is small enough to be controllable. + + Specify settings for the particle system in \l Properties > + \uicontrol {Particle System}. + + \image studio-3d-properties-particle-system.png "Particle System properties" + + You can freely animate the particle system property values using a timeline, + which enables you to synchronize particles with other animations, for + example. + + In \uicontrol {Start time}, set the time in milliseconds where the system + starts. This can be useful to warm up the system so that a set of particles + has already been emitted when the simulation starts. For example, if you set + the start time to 2000 and animate \uicontrol Time from 0 to 1000, the + animation shows particles from 2000 ms to 3000 ms. + + In \uicontrol Time, set the time in milliseconds for the system. If + you modify the value of this property, you should usually disable + \uicontrol Running to stop the simulation. All particles are destroyed + when you select it again. + + To temporarily stop the simulation, select \uicontrol Paused. Particles + are not destroyed, and when you deselect the check box, the simulation + resumes from the point where you paused it. + + Select \uicontrol Logging to collect particle system statistics, such as + the current and maximum amounts of particles in the system or the average + time in milliseconds used for emitting and animating particles in each + frame. Logging data can be useful when developing and optimizing the + particle effects. + + \note Logging can negatively affect performance, so it should be disabled + before packaging applications for release and delivery to users. + + Select \uicontrol {Use random seed} to randomize the particle system + with the seed that you specify in \uicontrol Seed to get an identical + pixel-perfect particle effect on every run. You should not modify the + seed value during particle animation. +*/ + +/*! + \page studio-3d-logical-particles.html + \previouspage studio-3d-particle-system.html + \nextpage studio-3d-particle-emitters.html + + \title Logical Particles + + All the particle system components act on \e {logical particles}. Each + particle has a logical representation within the particle system, and this + is what the components act upon. Not every logical particle needs to be + visualized, and some logical particles could lead to multiple visual + particles being drawn on screen. + + Two different logical particle components are supported: + \uicontrol {Sprite Particle} for \l{Textures}{2D texture} particles and + \uicontrol {Model Particle} for \l{3D Models}{3D model} particles. Model + particles use \l{Instanced Rendering}{instanced rendering} to enabled the + rendering of thousands of particles, with full \l{3D Materials}{materials} + and \l{Lights}{lights} support. + + The following components are available for adding logical particles and + for modifying their actions and appearance: + + \list + \li \l{Sprite Particle} + \li \l{Sprite Sequence} + \li \l{Model Particle} + \li \l{Model Blend Particle} + \endlist + + \section1 Sprite Particle + + Specify properties for sprite particles in \uicontrol Properties > + \uicontrol {Sprite Particle}. + + \image studio-3d-properties-sprite-particle.png "Sprite Particle properties" + + \uicontrol {Blend mode} determines whether particles are blended using + source over, screen, or multiply mode. If you select \uicontrol SourceOver, + the pixel component values from a foreground source are written over the + source by using alpha blending. If you select \uicontrol Screen, the values + are negated, then multiplied, negated again, and written. If you select + \uicontrol Multiply, they are multiplied and written. + + \uicontrol Sprite defines the \l{Textures}{Texture} component used for the + particles. For example, to use an image of a snowflake to simulate snow, + create an instance of the \uicontrol Texture component with the image + as the \uicontrol Source. + + In \uicontrol {Sprite sequence}, select the \l{Sprite Sequence} component + instance for the particle if the sprite texture contains a frame sequence. + If your image only has a single sprite frame, don't set this value. + + Select \uicontrol Billboard to specify that the particle texture should + always be aligned face towards the screen. Enabling this property + automatically disables \uicontrol Particle > \uicontrol {Align mode}. + + \uicontrol {Particle scale} specifies the scale multiplier of the particles. + To adjust particle sizes in the emitter, set \uicontrol {Particle Emitter} + properties. + + In \uicontrol {Color table}, select the \uicontrol Texture component that is + used for coloring the particles. The image can be a 1D or a 2D texture. + Horizontal pixels determine the particle color over the value you set in + \uicontrol {Particle Emitter} > \uicontrol {Life span}. For example, when + the particle is halfway through its life, it will have the color specified + halfway across the image. If the image is 2D, a vertical row is randomly + selected for each particle. For example, a \c {256 x 4} image contains 4 + different coloring options for particles. + + \section1 Sprite Sequence + + Specify properties for a sprite particle sequence that contains a frame + sequence in \uicontrol Properties > \uicontrol {Particle Sprite Sequence}. + + \image studio-3d-properties-particle-sprite-sequence.png "Particle Sprite Sequence properties" + + \uicontrol {Frame count} specifies the amount of image frames in a sprite. + A particle animates through these frames during its duration. The frames + should be laid out horizontally in the same image file. For example, the + sprite could be a 512x64 image, with the frame count of 8. This would make + each particle frame size 64x64 pixels. + + \uicontrol {Frame index} specifies the initial index of the frame. + This is the position between frames where the animation is started. For + example, when the frame index is 5 and \uicontrol {Animation direction} is + set to \uicontrol Normal, the first rendered frame is 5. If the animation + direction is set to \uicontrol Reverse, the first rendered frame is 4. + + The value of \uicontrol {Frame index} must be between 0 and the value of + \uicontrol {Frame count} minus 1. When \uicontrol {Animation direction} is + set to \uicontrol SingleFrame and \uicontrol {Random start} is disabled, all + the particles will render sprites with the frame index. + + Enable \uicontrol {Random start} to start the animation from a random frame + between 0 and \uicontrol {Frame count} minus 1. This allows animations to + not look like they all just started when the animation begins. + + \uicontrol Interpolate determines whether sprites are blended + between frames to make the animation appear smoother. + + \uicontrol Duration specifies the time in milliseconds that it + takes for the sprite sequence to animate. For example, if the duration + is 400 and the \uicontrol {Frame count} is 8, each frame will be shown + for 50 milliseconds. When the value is -1, \uicontrol Particle > + \uicontrol {Life span} is used as the duration. + + \uicontrol {Duration variation} defines the duration variation in + milliseconds. The actual duration of the animation is between + duration minus duration variation and duration plus duration variation. + + \uicontrol {Animation direction} defines the animation playback direction + of the sequence. Select \uicontrol Normal to play the animation from the + first frame to the last frame and to jump back to the first frame from the + last one. Select \uicontrol Reverse to reverse the normal order. Select + \uicontrol Alternate or \uicontrol AlternateReverse to alternate between + normal and reversed orders. + + If you don't want to animate the frame, select \uicontrol SingleFrame. + When \uicontrol {Random start} is disabled, the frame set in + \uicontrol {Frame index} is rendered. When it is enabled, each particle + renders a random frame. + + \section1 Model Particle + + Specify properties for model particles in \uicontrol Properties > + \uicontrol {Model Particle}. + + \image studio-3d-properties-model-particle.png "Model Particle properties" + + In \uicontrol Delegate, select the \uicontrol {3D Model} component that + defines each object instantiated by the particle. + + \uicontrol {Instance table} provides you with access to the internal + instancing table of the model particle that is used to implement efficient + rendering. This table can be applied to the instancing property of models + that are not part of the particle system. + + You can use this feature also to provide an instancing table without + showing any particles. This is done by omitting the delegate. + + \section1 Model Blend Particle + + Specify properties for model blend particles in \uicontrol Properties > + \uicontrol {Model Blend Particle}. + + \image studio-3d-properties-model-blend-particle.png "Model Blend Particle properties" + + The \uicontrol {Model Blend Particle} component blends a particle effect + with a \uicontrol {3D Model} component. The provided model needs to be + triangle-based. Each triangle in the model is converted into a particle, + which are then used by the emitter. Instead of particle shader, the model + is shaded using the material specified in the model. The way the effect is + blended is determined by \uicontrol {Model blend mode}. + + The possible blend modes are: + + \list + \li \uicontrol Construct, where the model is constructed from the + particles. + \li \uicontrol Explode, where the model is converted into particles. + \li \uicontrol Transfer, where \uicontrol Construct and + \uicontrol Explode are combined to create an effect where the + model is transferred from one place to another. + \endlist + + The particles are emitted in the order they are specified in the model + unless \uicontrol {Activation node} is set or \uicontrol Random is enabled. + + In \uicontrol Delegate, select the \uicontrol {3D Model} component that + defines each object instantiated by the particle. + + \uicontrol {End node} specifies the transformation for the model at the end + of a particle effect. It defines the size, position, and rotation where the + model is constructed when you set \uicontrol {Model blend mode} to + \uicontrol Construct or \uicontrol Explode. + + \uicontrol {End time} specifies the end time of the particle in + milliseconds. The end time is used during construction and defines + duration from particle lifetime at the end where the effect is blended + with the model positions. Before the end time, the particles' positions + are defined only by the particle effect, but during the end time the + particle position is blended linearly with the model end position. + + In \uicontrol {Activation node}, select the component instance that + activates particles and overrides the regular emit routine. The activation + node can be used to control how the particles are emitted spatially when + the model is exploded or constructed from the particles. The activation + node emits a particle if the center of that particle is on the positive + half of the z-axis of the activation node. Animating the activation node + to move trough the model will cause the particles to be emitted sequentially + along the path the activation node moves. + + To emit particles in random order instead of in the order in which they are + specified in the model, select \uicontrol Random. + + \section1 Common Particle Properties + + The properties that you specify for logical particles in + \uicontrol Properties > \uicontrol Particle determine the common + appearance of all particles. + + \image studio-3d-properties-particle.png "Particle properties" + + \uicontrol {Max amount} allocates data for particles. Setting this value + instead of just growing the data based on \uicontrol {Particle Emitter} > + \uicontrol {Emit rate}, \uicontrol {Life span}, and \uicontrol {Emit Bursts} + enables you to optimize memory usage and to modify the emit rate and life + span without reallocation. + + \uicontrol Color determines the base color for particles. You can use the + \l{Picking Colors}{Color Picker} to select colors. For color variation, set + values in \uicontrol {Color variation}. The values are in RGBA order (X=red, + Y=green, Z=blue, and W=alpha), and each value should be between 0.00 + (no variation) and 1.00 (full variation). + + To apply color variation uniformly for all the color channels, enable + \uicontrol {Unified color variation}. This applies all variations with + the same random amount. + + \uicontrol {Fade in effect} and \uicontrol {Fade out effect} define the + fading effect used when the particles appear. Fading is implemented by + changing the value of opacity or scale between 0 and 1 over the time + specified in milliseconds in \uicontrol {Fade in duration} and + \uicontrol {Fade out duration}. Fading duration is calculated into the + particle lifespan. For example, if \uicontrol {Particle Emitter} > + \uicontrol {Life span} is 3000, \uicontrol {Fade in duration} is 500, and + \uicontrol {Fade out duration} is 500, the particle will be fully visible + for 2000 ms. + + \uicontrol {Align mode} determines the direction that particles face: + + \list + \li Select \uicontrol AlignNone to use the value set for the emitter + component in \uicontrol {Particle Rotation} > \uicontrol Rotation. + \li Select \uicontrol AlignTowardsTarget to align the particles towards + the direction set in \uicontrol {Align target position}. + \li Select \uicontrol AlignTowardsStartVelocity to align the particles + towards their starting velocity direction. + \endlist + + This value takes no effect if \uicontrol {Sprite Particle} > + \uicontrol Billboard is enabled. + + Unlike the materials used with the models, particles default to being + rendered with assuming semi-transparency, and so with blending enabled. + This is the desired behavior most of the time due to particle textures, + color (alpha) variations, fadings, and so on. If you don't need the + blending, disable \uicontrol {Has transparency} for possible performance + gain. + + \uicontrol {Sort mode} determines the order in which the particles are + drawn. You can sort particles based on their distance from the camera, + farthest first, or lifetime, newest or oldest first. + + The particles are emitted from the location of the \l Emitter or + \l {Trail Emitter} component instance. +*/ + +/*! + \page studio-3d-particle-emitters.html + \previouspage studio-3d-logical-particles.html + \nextpage studio-3d-particle-affectors.html + + \title Particle Emitters + + The \uicontrol {Particle Emitter} component emits logical particles into + the system. You can determine how individual particles will look like and + how they are emitted. Many of the properties have \e variation counterparts, + such as \uicontrol {Color variation}, for adding variation to the particles. + + The \uicontrol {Trail Emitter} component emits particles from the location + of other logicial particles. Any logical particle of the followed component + within the bounds of a trail emitter will cause particle emission from its + location, as if there were an emitter on it with the same properties as the + trail emitter. + + Emitter components can use instances of the \l {Particle Shape} or + \l {Model Shape} component to emit particles from the surface of the + selected shape. + + You always need one emitter. If the \uicontrol {Particle System} component + instance is the direct parent of the emitter component instance, you don't + need to specify the particle system separately. However, you always need + to select the logical particle to emit, or nothing is emitted. + + The following components are available for emitting particles: + + \list + \li \l Emitter + \li \l {Trail Emitter} + \li \l {Emit Burst} + \li \l {Model Shape} + \li \l {Particle Shape} + \endlist + + \section1 Emitter + + Specify properties for particle emitters in \uicontrol Properties > + \uicontrol {Particle Emitter}. You need at least one emitter. + + \image studio-3d-properties-particle-emitter.png "Particle Emitter properties" + + \uicontrol Emitter emits \l{Logical Particles}{logical particles} that you + select in \uicontrol Particle, as defined by the other properties. If the + \uicontrol {Particle System} component instance is not the direct parent of + the emitter component instance, you need to select it in \uicontrol System. + + You can control the amount of particles emitted per second by setting the + value of \uicontrol {Emit rate} or add \uicontrol {Emit Burst} component + instances in \uicontrol {Emit bursts} to emit bursts of specified amounts + of particles at the specified point in time for the specified duration. + + In \uicontrol Velocity, set a starting velocity for emitted particles. + If velocity is not set, particles start motionless, and velocity is + determined by \l{Particle Affectors}{particle affectors}. + + Use \uicontrol Enabled to turn an emitter on or off. Usually, this property + is used in code to conditionally turn emitters on and off. To continue + emitting bursts, set \uicontrol {Emit rate} to 0 instead of disabling + \uicontrol Enabled. + + In \uicontrol Shape, select the instance of the \l {Particle Shape} or + \l {Model Shape} component to use. The shape is scaled, positioned, and + rotated based on the emitter node properties. When the shape \uicontrol Fill + property is set to false, particles are emitted only from the surface of the + shape. When the shape is not defined, particles are emitted from the center + point of the emitter. + + In \uicontrol {Life span}, specify the lifespan of a single particle + in milliseconds. Specify variation in the particle lifespan in + \uicontrol {Life span variation}. For example, to emit particles that + will exist from three to four seconds, set \uicontrol {Life span} to + 3500 ms and \uicontrol {Life span variation} to 500 ms. + + \uicontrol {Particle scale} and \uicontrol {Particle end scale} specify the + scale multiplier of the particles at the beginning and end. For variation + in particle size, specify values for \uicontrol {Particle scale variation} + and \uicontrol {Particle end scale variation}. + + \uicontrol {Depth bias} specifies the the depth bias of the emitter. Depth + bias is added to the object's distance from camera when sorting objects. + This can be used to force the rendering order of objects that are located + close to each other if it might otherwise change between frames. Negative + values cause the sorting value to move closer to the camera while positive + values move it further from the camera. + + \section1 Trail Emitter + + Specify additional properties for particle trail emitters in + \uicontrol Properties > \uicontrol {Particle Trail Emitter}. + + Select the logical particle component to follow in \uicontrol Follow. + + \section1 Particle Rotation + + Specify properties for the rotation of particles in \uicontrol Properties > + \uicontrol {Particle Rotation} + + \image studio-3d-properties-particle-rotation.png "Particle Rotation properties" + + \uicontrol Rotation specifies the rotation of the particles in the + beginning. Rotation is defined as degrees in euler angles. For variation + in rotation, specify values for \uicontrol Variation. + + \uicontrol Velocity specifies the rotation velocity of the particles in the + beginning. Rotation velocity is defined as degrees per second in euler + angles. For variation in velocity, specify values in + \uicontrol {Velocity variation}. + + \section1 Emit Burst + + Specify properties for emit bursts in \uicontrol Properties > + \uicontrol {Emit Burst}. + + \image studio-3d-properties-particle-emit-burst.png "Particle Emit Burst properties" + + \uicontrol Time specifies the time in milliseconds when emitting the burst + starts, and \uicontrol Amount specifies the amount of particles emitted + during the time specified in milliseconds in \uicontrol Duration. + + For example, you could use two \uicontrol {Emit Burst} instances to emit 100 + particles at the beginning and 50 particles at 2 seconds, so that both + bursts take 200 milliseconds. For one instance, set \uicontrol Time to 0, + \uicontrol Amount to 100, and \uicontrol Duration to 200. For the other + instance, set \uicontrol Time to 2000, \uicontrol Amount to 50, and + \uicontrol Duration to 200. + + \section1 Particle Shape + + The \uicontrol {Particle Shape} component supports shapes, such as cube, + sphere, and cylinder, for emitting particles from their area. + + Specify properties for particle shape in \uicontrol Properties > + \uicontrol {Particle Shape}. + + \image studio-3d-properties-particle-shape.png "Particle Shape properties" + + In \uicontrol Type, select the shape to use. + + Select \uicontrol Fill to fill the shape instead of just displaying its + outline. + + \uicontrol Extents determines the extent coordinates of the shape geometry. + + \section1 Model Shape + + The \uicontrol {Model Shape} component specifies a template for defining the + model. + + Specify properties for model shape in \uicontrol Properties > + \uicontrol {Particle Model Shape}. + + \image studio-3d-properties-particle-model-shape.png "Particle Model Shape properties" + + In \uicontrol Delegate, select the \uicontrol {3D Model} component that + defines each object instantiated by the particle. + + Select \uicontrol Fill to fill the shape instead of just displaying its + outline. +*/ + +/*! + \page studio-3d-particle-affectors.html + \previouspage studio-3d-particle-emitters.html + \nextpage studio-3d-particle-directions.html + + \title Particle Affectors + + Affectors are an optional component of a particle system. They can perform + a variety of manipulations to the simulation, such as altering the + trajectory of particles or prematurely ending their life in the simulation. + For performance reasons, it is recommended not to use affectors in + high-volume particle systems. + + The following affector components control how the particles are animated + during their lifetime: + + \list + \li \l Attractor attracts particles towards a specific point. + \li \l Gravity accelerates particles to a vector of the specified + magnitude in the specified direction. + \li \l {Point Rotator} rotates particles around a pivot point. + \li \l Wander applies random wave curves to particles. + \endlist + + If the system has multiple affectors, the order of affectors may result in + different outcome, as affectors are applied one after another. + + By default, affectors affect all particles in the system, but you can limit + this to the particles listed in \l Properties > \l {Particle Affector} > + \uicontrol Particles. + + \section1 Attractor + + The \uicontrol Attractor component attracts particles towards a position + inside the \l{3D Views}{View 3D} component instance. To model the gravity + of a massive object whose center of gravity is far away, use an instance of + the \l Gravity component. + + The attraction position is defined either by using the position of the + attractor and the value of \uicontrol {Position variation} or by selecting + an instance of the \uicontrol {Particle Shape} or \uicontrol {Model Shape} + component in \uicontrol Shape. If both position and shape are defined, the + shape is used. + + Specify settings for \uicontrol Attractor components in + \uicontrol Properties > \uicontrol {Particle Attractor}. + + \image studio-3d-properties-particle-attractor.png "Particle Attractor properties" + + \uicontrol {Position variation} specifies the variation on attract position. + Instead of attracting particles into a single point, it attracts them + randomly towards a wider area. For example, to attract particles into some + random point inside a (50, 50, 50) cube at position (100, 0, 0) within 2 to + 4 seconds, set \uicontrol X, \uicontrol Y, and \uicontrol Z to 50.00, + \uicontrol Duration to 3000, and \uicontrol {Duration variation} to 1000. + + In \uicontrol Shape, select an instance of the \l {Particle Shape} or + \l {Model Shape} component to attract particles into a random position + inside the shape. + + \uicontrol Duration specifies the duration in milliseconds that it takes + for particles to reach the attraction position. When the value is -1, + \uicontrol {Particle Emitter} > \uicontrol {Life span} is used as + the duration. If you specify \uicontrol {Duration variation}, the actual + duration to reach the attractor is between duration minus duration variation + and duration plus duration variation. + + Select \uicontrol {Hide at end} to make the particle disappear when it + reaches the attractor. + + Select \uicontrol {Use cached positions} to cache possible positions within + the attractor's \uicontrol Shape. Cached positions give less random results + but are better for performance. + + \uicontrol {Positions amount} specifies the amount of possible positions + stored within the attractor's \uicontrol Shape. By default, the amount + equals the particle count, but you can specify a lower amount for a smaller + cache. Specify a higher amount for additional randomization. + + \section1 Gravity + + The \uicontrol Gravity component models the gravity of a massive object + whose center of gravity is far away, and thus the gravitational pull is + effectively constant across the scene. To model the gravity of an object + near or inside the scene, use an \l Attractor component instance. + + Specify settings for \uicontrol Gravity component instances in + \uicontrol Properties > \uicontrol {Particle Gravity}. + + \image studio-3d-properties-particle-gravity.png "Particle Gravity properties" + + \uicontrol Magnitude defines the magnitude in particle position change in + degrees per second. A negative value accelerates in the opposite way from + the direction specified in \uicontrol Direction. Direction \uicontrol X, + \uicontrol Y, and \uicontrol Z values are automatically normalized to a unit + vector. + + \section1 Point Rotator + + Specify settings for \uicontrol {Point Rotator} component instances in + \uicontrol Properties > \uicontrol {Point Rotator}. + + \image studio-3d-properties-particle-point-rotator.png "Particle Point Rotator properties" + + The \uicontrol {Point Rotator} component rotates particles around the + pivot point specified in \uicontrol {Pivot point} towards the direction + specified in \uicontrol Direction. Direction \uicontrol X, \uicontrol Y, and + \uicontrol Z values are automatically normalized to a unit vector. + + \uicontrol Magnitude defines the magnitude in particle position change in + degrees per second. A negative value accelerates in the opposite way from + the direction specified in \uicontrol Direction. + + \section1 Wander + + The \uicontrol Wander component applies random wave curves to particles. + Curves can combine \l {Global Wander Properties}{global} values that are + the same for all particles and \l{Unique Wander Properties}{unique} values + that differ randomly. + + Specify settings for \uicontrol Wander component instances in + \uicontrol Properties > \uicontrol {Particle Wander}. + + \image studio-3d-properties-particle-wander.png "Particle Wander properties" + + \uicontrol {Fade in duration} specifies the duration in milliseconds for + fading in the affector. After this duration, the wandering will be in full + effect. Setting this value can be useful to emit from a specific position or + shape, otherwise wander will affect the position also at the beginning. + + \uicontrol {Fade out duration} specifies the duration in milliseconds for + fading out the affector. Setting this value can be useful to reduce the + wander when the particle life time ends, for example when combined with an + instance of the \l Attractor component so that the end positions will match + the shape. + + \section2 Global Wander Properties + + Specify global settings for \uicontrol Wander component instances in + \uicontrol Properties > \uicontrol {Global}. + + \image studio-3d-properties-particle-wander-global.png "Global Particle Wander properties" + + \uicontrol Amount specifies the distance that each particle moves + at the ends of curves. For example, if you set the value of \uicontrol X to + 100.00, \uicontrol Y to 10.00, and \uicontrol Z to 0.00, all particles + wander between (100, 10, 0) and (-100, -10, 0). + + \uicontrol Pace defines the frequency at which each particle wanders in + curves per second, starting from \uicontrol {Pace start}. The meaningful + range for pace start is between 0 .. 2 * PI. + + \section2 Unique Wander Properties + + Specify unique settings for the \uicontrol Wander component instances in + \uicontrol Properties > \uicontrol {Unique}. + + \image studio-3d-properties-particle-wander-unique.png "Unique Particle Wander properties" + + \uicontrol Amount specifies the distance that each particle moves + at the ends of curves. Specify amount variation for each particle between + 0.00 and 1.00 in \uicontrol {Amount variation}. When the amount variation is + 0.00, all particles reach the maximum amount. When it is 0.50, every + particle reaches between 0.50 and 1.50 of the amount. For example, if you + set \uicontrol Amount \uicontrol X to 100.00, \uicontrol Y to 50.00, and + \uicontrol Z to 20.00) and \uicontrol {Amount variation} to 0.10, the + particles' maximum wave distances are something random between (110, 55, 22) + and (90, 45, 18). + + \uicontrol Pace defines the frequency at which each particle wanders in + curves per second. Specify unique pace variation for each particle between + 0.00 and 1.00 in \uicontrol {Pace variation}. When the variation is 0.00, + all particles wander at the same frequency. For example, if you set + \uicontrol Pace \uicontrol X to 1.00, \uicontrol Y to 2.00, and \uicontrol Z + to 4.00 and \uicontrol {Pace variation} to 0.50, the particles' wave paces + are something random between (2.00, 4.00, 8.00) and (0.50, 1.00, 2.00). + + \section1 Particle Affector + + Specify common settings for particle affectors in \uicontrol Properties > + \l {Particle Affector}. + + \image studio-3d-properties-particle-affector.png "Affector properties" + + If the affector is not a direct child component of the particle system, + select the \uicontrol {Particle System} component instance to affect in + \uicontrol System. + + To only affect some of the particles in the particle system, select + them in \uicontrol Particles. Select \inlineimage plus.png + to add logical particles to the list. + + Deselect \uicontrol Enabled to turn the affector off. Usually, this + property is used in code to conditionally turn affectors off and on. +*/ + +/*! + \page studio-3d-particle-directions.html + \previouspage studio-3d-particle-affectors.html + \nextpage quick-component-instances.html + + \title Particle Directions + + Directions can be specified by specifying the emitted particle velocity + towards either the target position or the target vector. + + \section1 Target Direction + + The \uicontrol {Target Direction} component sets emitted particle velocity + towards the target position. + + Specify common settings for particle target direction in + \uicontrol Properties > \uicontrol {Particle Target Direction}. + + \image studio-3d-properties-particle-target-direction.png "Particle Target Direction properties" + + \uicontrol Position specifies the position of the target of + the particle. For variation in the target position, specify + \uicontrol {Position variation}. + + \uicontrol Normalized determines whether the distance to the position + affects the magnitude of the particle's velocity. Enable + \uicontrol Normalize to derive the velocity amount only from + \uicontrol Magnitude and \uicontrol {Magnitude variation}. + + \uicontrol Magnitude specifies the magnitude in \uicontrol Position change + per second. A negative value accelerates the in the opposite direction + from the position. When \uicontrol Normalized is disabled, this value is + multiplied with the distance to the target position. For variation in the + magnitude, specify \uicontrol {Magnitude variation}. + + For example, to emit particles towards the target position (100, 0, 0) with + random magnitude between 10..20, set \uicontrol Magnitude to 15.00 and + \uicontrol {Magnitude variation} to 5.00. Further, enable + \uicontrol Normalized. + + \section1 Vector Direction + + The \uicontrol {Vector Direction} component sets emitted particle velocity + towards the target vector. The length of the direction vector is used as + the velocity magnitude. + + Specify common settings for particle vector direction in + \uicontrol Properties > \uicontrol {Particle Vector Direction}. + + \image studio-3d-properties-particle-vector-direction.png "Vector Direction properties" + + \uicontrol Direction specifies the direction of the target of the particle. + A positive \uicontrol Y value means \e up, while a negative value means + \e down. A negative \uicontrol Z value causes the particles to move in the + direction opposite to the target vector. + + For variation in the target direction, specify + \uicontrol {Direction variation}. Enable \uicontrol Normalized to + normalize direction after applying the variation. When it is disabled, + variation affects the magnitude of the particles' velocity. When + it is enabled, variation affects the direction, but the magnitude is + determined by the original direction length. +*/ diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp index 1fda59c964..bb80c2e2e1 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp @@ -397,8 +397,19 @@ void NodeInstanceServer::reparentInstances(const QVector<ReparentContainer> &con if (hasInstanceForId(container.instanceId())) { ServerNodeInstance instance = instanceForId(container.instanceId()); if (instance.isValid()) { - instance.reparent(instanceForId(container.oldParentInstanceId()), container.oldParentProperty(), - instanceForId(container.newParentInstanceId()), container.newParentProperty()); + ServerNodeInstance newParent = instanceForId(container.newParentInstanceId()); + PropertyName newParentProperty = container.newParentProperty(); + if (!isInformationServer()) { + // Children of the component wraps are left out of the node tree to avoid + // incorrectly rendering them + if (newParent.isComponentWrap()) { + newParent = {}; + newParentProperty.clear(); + } + } + instance.reparent(instanceForId(container.oldParentInstanceId()), + container.oldParentProperty(), + newParent, newParentProperty); } } } @@ -1287,8 +1298,15 @@ PixmapChangedCommand NodeInstanceServer::createPixmapChangedCommand(const QList< QVector<ImageContainer> imageVector; for (const ServerNodeInstance &instance : instanceList) { - if (instance.isValid() && instance.hasContent()) - imageVector.append(ImageContainer(instance.instanceId(), instance.renderImage(), instance.instanceId())); + if (!instance.isValid()) + continue; + + QImage renderImage; + // We need to return empty image if instance has no content to correctly update the + // item image in case the instance changed from having content to not having content. + if (instance.hasContent()) + renderImage = instance.renderImage(); + imageVector.append(ImageContainer(instance.instanceId(), renderImage, instance.instanceId())); } return PixmapChangedCommand(imageVector); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.cpp index ab764a98b3..4eb8cdd65f 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.cpp @@ -415,6 +415,16 @@ bool ObjectNodeInstance::isLockedInEditor() const return m_isLockedInEditor; } +bool ObjectNodeInstance::isComponentWrap() const +{ + return m_isComponentWrap; +} + +void ObjectNodeInstance::setComponentWrap(bool wrap) +{ + m_isComponentWrap = wrap; +} + void ObjectNodeInstance::setModifiedFlag(bool b) { m_isModified = b; @@ -732,6 +742,10 @@ QObject *ObjectNodeInstance::createComponentWrap(const QString &nodeSource, cons QQmlComponent *component = new QQmlComponent(context->engine()); QByteArray data(nodeSource.toUtf8()); + if (data.isEmpty()) { + // Add a fake root element as an empty component is not valid and crashes in some cases + data.append("QtObject{}"); + } data.prepend(importCode); component->setData(data, context->baseUrl().resolved(QUrl("createComponent.qml"))); QObject *object = component; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.h index c66365d392..67e1663496 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.h @@ -202,6 +202,9 @@ public: virtual void setLockedInEditor(bool b); bool isLockedInEditor() const; + bool isComponentWrap() const; + void setComponentWrap(bool wrap); + void setModifiedFlag(bool b); protected: @@ -234,6 +237,7 @@ private: bool m_isModified = false; bool m_isLockedInEditor = false; bool m_isHiddenInEditor = false; + bool m_isComponentWrap = false; static QHash<EnumerationName, QVariant> m_enumationValueHash; }; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.cpp index 89e8c5d2db..2e1e03fa63 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.cpp @@ -37,6 +37,7 @@ #include <addimportcontainer.h> #include <createscenecommand.h> #include <reparentinstancescommand.h> +#include <removeinstancescommand.h> #include <clearscenecommand.h> #include <QDebug> @@ -193,6 +194,32 @@ QList<QQuickItem*> Qt5NodeInstanceServer::allItems() const return QList<QQuickItem*>(); } +void Qt5NodeInstanceServer::markRepeaterParentDirty(qint32 id) const +{ + if (!hasInstanceForId(id)) + return; + + ServerNodeInstance instance = instanceForId(id); + if (!instance.isValid()) + return; + + ServerNodeInstance parentInstance = instance.parent(); + if (!parentInstance.isValid()) + return; + + // If a Repeater instance was moved/removed, the old parent must be marked dirty to rerender it + const QByteArray type("QQuickRepeater"); + if (ServerNodeInstance::isSubclassOf(instance.internalObject(), type)) + DesignerSupport::addDirty(parentInstance.rootQuickItem(), QQuickDesignerSupport::Content); + + // Repeater's parent must also be dirtied when a child of a repeater was moved/removed. + if (ServerNodeInstance::isSubclassOf(parentInstance.internalObject(), type)) { + ServerNodeInstance parentsParent = parentInstance.parent(); + if (parentsParent.isValid()) + DesignerSupport::addDirty(parentsParent.rootQuickItem(), QQuickDesignerSupport::Content); + } +} + bool Qt5NodeInstanceServer::initRhi(RenderViewData &viewData) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) @@ -523,9 +550,23 @@ void Qt5NodeInstanceServer::clearScene(const ClearSceneCommand &command) void Qt5NodeInstanceServer::reparentInstances(const ReparentInstancesCommand &command) { + const QVector<ReparentContainer> &containerVector = command.reparentInstances(); + for (const ReparentContainer &container : containerVector) + markRepeaterParentDirty(container.instanceId()); + NodeInstanceServer::reparentInstances(command.reparentInstances()); startRenderTimer(); } +void Qt5NodeInstanceServer::removeInstances(const RemoveInstancesCommand &command) +{ + const QVector<qint32> &idVector = command.instanceIds(); + for (const qint32 id : idVector) + markRepeaterParentDirty(id); + + NodeInstanceServer::removeInstances(command); + startRenderTimer(); +} + } // QmlDesigner diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.h index 4af451b61a..d93ecad84e 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.h @@ -67,6 +67,7 @@ public: void createScene(const CreateSceneCommand &command) override; void clearScene(const ClearSceneCommand &command) override; void reparentInstances(const ReparentInstancesCommand &command) override; + void removeInstances(const RemoveInstancesCommand &command) override; QImage grabWindow() override; QImage grabItem(QQuickItem *item) override; @@ -79,6 +80,7 @@ protected: void resetAllItems(); void setupScene(const CreateSceneCommand &command) override; QList<QQuickItem*> allItems() const; + void markRepeaterParentDirty(qint32 id) const; struct RenderViewData { QPointer<QQuickWindow> window = nullptr; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp index daa1cab473..fb3113bdcd 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp @@ -101,6 +101,10 @@ void Qt5PreviewNodeInstanceServer::changeState(const ChangeStateCommand &/*comma QImage Qt5PreviewNodeInstanceServer::renderPreviewImage() { + // Ensure the state preview image is always clipped properly to root item dimensions + if (auto rootItem = qobject_cast<QQuickItem *>(rootNodeInstance().internalObject())) + rootItem->setClip(true); + rootNodeInstance().updateDirtyNodeRecursive(); QRectF boundingRect = rootNodeInstance().boundingRect(); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.cpp index 2046a2039e..1dae931af3 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.cpp @@ -779,6 +779,9 @@ void QuickItemNodeInstance::reparent(const ObjectNodeInstance::Pointer &oldParen ObjectNodeInstance::reparent(oldParentInstance, oldParentProperty, newParentInstance, newParentProperty); + if (!newParentInstance) + quickItem()->setParentItem(nullptr); + if (instanceIsValidLayoutable(newParentInstance, newParentProperty)) { setInLayoutable(true); setMovable(false); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.cpp index be4c3ea626..2aa17d9e2b 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.cpp @@ -150,6 +150,11 @@ bool ServerNodeInstance::holdsGraphical() const return m_nodeInstance->isQuickItem(); } +bool ServerNodeInstance::isComponentWrap() const +{ + return m_nodeInstance->isComponentWrap(); +} + void ServerNodeInstance::updateDirtyNodeRecursive() { m_nodeInstance->updateAllDirtyNodesRecursive(); @@ -280,6 +285,8 @@ ServerNodeInstance ServerNodeInstance::create(NodeInstanceServer *nodeInstanceSe instance.internalInstance()->setInstanceId(instanceContainer.instanceId()); + instance.internalInstance()->setComponentWrap(componentWrap == WrapAsComponent); + instance.internalInstance()->initialize(instance.m_nodeInstance, instanceContainer.metaFlags()); // Handle hidden state to initialize pickable state diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.h index 786a09fd47..52ead77e12 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.h @@ -178,6 +178,8 @@ public: void updateDirtyNodeRecursive(); bool holdsGraphical() const; + bool isComponentWrap() const; + private: // functions ServerNodeInstance(const QSharedPointer<Internal::ObjectNodeInstance> &abstractInstance); diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/LoaderSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/LoaderSpecifics.qml new file mode 100644 index 0000000000..2417c54587 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/LoaderSpecifics.qml @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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.Layouts 1.15 +import HelperWidgets 2.0 +import StudioTheme 1.0 as StudioTheme + +Column { + anchors.left: parent.left + anchors.right: parent.right + + Section { + caption: qsTr("Loader") + anchors.left: parent.left + anchors.right: parent.right + + SectionLayout { + PropertyLabel { + text: qsTr("Active") + tooltip: qsTr("This property is true if the Loader is currently active.") + } + + SecondColumnLayout { + CheckBox { + text: backendValues.active.valueToString + backendValue: backendValues.active + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + ExpandingSpacer {} + } + + PropertyLabel { + text: qsTr("Source") + tooltip: qsTr("This property holds the URL of the QML component to instantiate.") + } + + SecondColumnLayout { + UrlChooser { + filter: "*.qml" + backendValue: backendValues.source + } + + ExpandingSpacer {} + } + + PropertyLabel { + text: qsTr("Source Component") + tooltip: qsTr("This property holds the component to instantiate.") + } + + SecondColumnLayout { + ItemFilterComboBox { + typeFilter: "Component" + validator: RegExpValidator { regExp: /(^$|^[a-z_]\w*)/ } + backendValue: backendValues.sourceComponent + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + ExpandingSpacer {} + } + + PropertyLabel { + text: qsTr("Asynchronous") + tooltip: qsTr("This property holds whether the component will be instantiated asynchronously.") + } + + SecondColumnLayout { + CheckBox { + text: backendValues.asynchronous.valueToString + backendValue: backendValues.asynchronous + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + ExpandingSpacer {} + } + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/RepeaterSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/RepeaterSpecifics.qml new file mode 100644 index 0000000000..33dfc387b1 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/RepeaterSpecifics.qml @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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.Layouts 1.15 +import HelperWidgets 2.0 +import StudioTheme 1.0 as StudioTheme + +Column { + anchors.left: parent.left + anchors.right: parent.right + + Section { + caption: qsTr("Repeater") + anchors.left: parent.left + anchors.right: parent.right + + SectionLayout { + PropertyLabel { + text: qsTr("Model") + tooltip: qsTr("The model providing data for the repeater. This can simply specify the number of delegate instances to create or it can be bound to an actual model.") + } + + SecondColumnLayout { + LineEdit { + backendValue: backendValues.model + showTranslateCheckBox: false + writeAsExpression: true + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + width: implicitWidth + } + + ExpandingSpacer {} + } + + PropertyLabel { + text: qsTr("Delegate") + tooltip: qsTr("The delegate provides a template defining each object instantiated by the repeater.") + } + + SecondColumnLayout { + ItemFilterComboBox { + typeFilter: "Component" + validator: RegExpValidator { regExp: /(^$|^[a-z_]\w*)/ } + backendValue: backendValues.delegate + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + } + + ExpandingSpacer {} + } + } + } +} diff --git a/share/qtcreator/templates/wizards/projects/qtquickuiprototype/wizard.json b/share/qtcreator/templates/wizards/projects/qtquickuiprototype/wizard.json index 366ec1e4cd..78dbbd9956 100644 --- a/share/qtcreator/templates/wizards/projects/qtquickuiprototype/wizard.json +++ b/share/qtcreator/templates/wizards/projects/qtquickuiprototype/wizard.json @@ -14,6 +14,7 @@ "options": [ { "key": "QmlProjectFileName", "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'qmlproject')}" }, + { "key": "IsQt6", "value": "%{JS: value('QtVersion').IsQt6}" }, { "key": "MainQmlFileName", "value": "%{JS: Util.fileName(value('ProjectName'), 'qml')}" }, { "key": "QtQuickVersion", "value": "%{JS: value('QtVersion').QtQuickVersion}" }, { "key": "QtQuickWindowVersion", "value": "%{JS: value('QtVersion').QtQuickWindowVersion}" }, @@ -50,7 +51,8 @@ { "QtQuickVersion": "", "QtQuickWindowVersion": "", - "QtQuickVirtualKeyboardImport": "" + "QtQuickVirtualKeyboardImport": "", + "IsQt6": true } }, { @@ -59,7 +61,8 @@ { "QtQuickVersion": "2.15", "QtQuickWindowVersion": "2.15", - "QtQuickVirtualKeyboardImport": "QtQuick.VirtualKeyboard 2.15" + "QtQuickVirtualKeyboardImport": "QtQuick.VirtualKeyboard 2.15", + "IsQt6": false } }, { @@ -68,7 +71,8 @@ { "QtQuickVersion": "2.14", "QtQuickWindowVersion": "2.14", - "QtQuickVirtualKeyboardImport": "QtQuick.VirtualKeyboard 2.14" + "QtQuickVirtualKeyboardImport": "QtQuick.VirtualKeyboard 2.14", + "IsQt6": false } }, { @@ -77,7 +81,8 @@ { "QtQuickVersion": "2.13", "QtQuickWindowVersion": "2.13", - "QtQuickVirtualKeyboardImport": "QtQuick.VirtualKeyboard 2.4" + "QtQuickVirtualKeyboardImport": "QtQuick.VirtualKeyboard 2.4", + "IsQt6": false } }, { @@ -86,7 +91,8 @@ { "QtQuickVersion": "2.12", "QtQuickWindowVersion": "2.12", - "QtQuickVirtualKeyboardImport": "QtQuick.VirtualKeyboard 2.4" + "QtQuickVirtualKeyboardImport": "QtQuick.VirtualKeyboard 2.4", + "IsQt6": false } } ] diff --git a/src/libs/qmljs/qmljscheck.cpp b/src/libs/qmljs/qmljscheck.cpp index d63ae11611..3ce402589c 100644 --- a/src/libs/qmljs/qmljscheck.cpp +++ b/src/libs/qmljs/qmljscheck.cpp @@ -617,7 +617,6 @@ class UnsupportedTypesByQmlUi : public QStringList { public: UnsupportedTypesByQmlUi() : QStringList({"ShaderEffect", - "Component", "Drawer"}) { append(UnsupportedTypesByVisualDesigner()); diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index 2d3d6edf3b..76f32ccb98 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -136,8 +136,7 @@ void Environment::setupEnglishOutput() set("LANGUAGE", "en_US:en"); } -static FilePath searchInDirectory(const Environment &env, - const QStringList &execs, +static FilePath searchInDirectory(const QStringList &execs, const FilePath &directory, QSet<FilePath> &alreadyChecked) { @@ -226,7 +225,7 @@ static FilePath searchInDirectoriesHelper(const Environment &env, QSet<FilePath> alreadyChecked; for (const FilePath &dir : dirs) { - FilePath tmp = searchInDirectory(env, execs, dir, alreadyChecked); + FilePath tmp = searchInDirectory(execs, dir, alreadyChecked); if (!tmp.isEmpty() && (!func || func(tmp))) return tmp; } @@ -236,7 +235,7 @@ static FilePath searchInDirectoriesHelper(const Environment &env, return FilePath(); for (const FilePath &p : env.path()) { - FilePath tmp = searchInDirectory(env, execs, p, alreadyChecked); + FilePath tmp = searchInDirectory(execs, p, alreadyChecked); if (!tmp.isEmpty() && (!func || func(tmp))) return tmp; } @@ -281,14 +280,14 @@ FilePaths Environment::findAllInPath(const QString &executable, QSet<FilePath> result; QSet<FilePath> alreadyChecked; for (const FilePath &dir : additionalDirs) { - FilePath tmp = searchInDirectory(*this, execs, dir, alreadyChecked); + FilePath tmp = searchInDirectory(execs, dir, alreadyChecked); if (!tmp.isEmpty() && (!func || func(tmp))) result << tmp; } if (!executable.contains('/')) { for (const FilePath &p : path()) { - FilePath tmp = searchInDirectory(*this, execs, p, alreadyChecked); + FilePath tmp = searchInDirectory(execs, p, alreadyChecked); if (!tmp.isEmpty() && (!func || func(tmp))) result << tmp; } diff --git a/src/libs/utils/pathlisteditor.cpp b/src/libs/utils/pathlisteditor.cpp index 61d215fd83..d24be4995b 100644 --- a/src/libs/utils/pathlisteditor.cpp +++ b/src/libs/utils/pathlisteditor.cpp @@ -130,6 +130,7 @@ PathListEditor::PathListEditor(QWidget *parent) : }); addButton(tr("Delete Line"), this, [this] { deletePathAtCursor(); }); addButton(tr("Clear"), this, [this] { d->edit->clear(); }); + connect(d->edit, &QPlainTextEdit::textChanged, this, &PathListEditor::changed); } PathListEditor::~PathListEditor() diff --git a/src/libs/utils/pathlisteditor.h b/src/libs/utils/pathlisteditor.h index 07392fbe6e..edd70336c0 100644 --- a/src/libs/utils/pathlisteditor.h +++ b/src/libs/utils/pathlisteditor.h @@ -58,6 +58,9 @@ public: void setPathList(const QString &pathString); void setFileDialogTitle(const QString &l); +signals: + void changed(); + protected: // Index after which to insert further "Add" buttons static const int lastInsertButtonIndex; diff --git a/src/plugins/android/androidavdmanager.cpp b/src/plugins/android/androidavdmanager.cpp index 27af7b76db..1b30803910 100644 --- a/src/plugins/android/androidavdmanager.cpp +++ b/src/plugins/android/androidavdmanager.cpp @@ -203,6 +203,7 @@ bool AndroidAvdManager::removeAvd(const QString &name) const qCDebug(avdManagerLog) << "Running command (removeAvd):" << command.toUserOutput(); QtcProcess proc; proc.setTimeoutS(5); + proc.setEnvironment(AndroidConfigurations::toolsEnvironment(m_config)); proc.setCommand(command); proc.runBlocking(); return proc.result() == QtcProcess::FinishedWithSuccess; diff --git a/src/plugins/android/androidbuildapkstep.cpp b/src/plugins/android/androidbuildapkstep.cpp index 4c47418c85..dc6b42754c 100644 --- a/src/plugins/android/androidbuildapkstep.cpp +++ b/src/plugins/android/androidbuildapkstep.cpp @@ -196,7 +196,7 @@ QWidget *AndroidBuildApkWidget::createApplicationGroup() }); auto formLayout = new QFormLayout(group); - formLayout->addRow(tr("Android build SDK:"), targetSDKComboBox); + formLayout->addRow(tr("Android build platform SDK:"), targetSDKComboBox); auto createAndroidTemplatesButton = new QPushButton(tr("Create Templates")); createAndroidTemplatesButton->setToolTip( @@ -939,7 +939,8 @@ QVariant AndroidBuildApkStep::data(Utils::Id id) const } if (id == Constants::SdkLocation) return QVariant::fromValue(AndroidConfigurations::currentConfig().sdkLocation()); - if (id == Constants::AndroidABIs) + + if (id == Constants::AndroidMkSpecAbis) return AndroidManager::applicationAbis(target()); return AbstractProcessStep::data(id); diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp index f1eb6379d6..c769a744d2 100644 --- a/src/plugins/android/androidconfigurations.cpp +++ b/src/plugins/android/androidconfigurations.cpp @@ -1127,10 +1127,9 @@ void AndroidConfigurations::removeOldToolChains() void AndroidConfigurations::removeUnusedDebuggers() { - const QList<BaseQtVersion *> qtVersions - = QtVersionManager::versions([](const BaseQtVersion *v) { - return v->type() == Constants::ANDROIDQT; - }); + const QList<BaseQtVersion*> qtVersions = QtVersionManager::versions([](const BaseQtVersion *v) { + return v->type() == Constants::ANDROID_QT_TYPE; + }); QVector<FilePath> uniqueNdks; for (const BaseQtVersion *qt : qtVersions) { @@ -1267,8 +1266,8 @@ void AndroidConfigurations::updateAutomaticKitList() removeUnusedDebuggers(); QHash<Abi, QList<const BaseQtVersion *> > qtVersionsForArch; - const QList<BaseQtVersion *> qtVersions = QtVersionManager::versions([](const BaseQtVersion *v) { - return v->type() == Constants::ANDROIDQT; + const QList<BaseQtVersion*> qtVersions = QtVersionManager::versions([](const BaseQtVersion *v) { + return v->type() == Constants::ANDROID_QT_TYPE; }); for (const BaseQtVersion *qtVersion : qtVersions) { const Abis qtAbis = qtVersion->qtAbis(); @@ -1322,9 +1321,8 @@ void AndroidConfigurations::updateAutomaticKitList() QStringList abis = static_cast<const AndroidQtVersion *>(qt)->androidAbis(); Debugger::DebuggerKitAspect::setDebugger(k, findOrRegisterDebugger(tc, abis)); - k->setSticky(ToolChainKitAspect::id(), true); + BuildDeviceKitAspect::setDeviceId(k, DeviceManager::defaultDesktopDevice()->id()); k->setSticky(QtKitAspect::id(), true); - k->setSticky(DeviceKitAspect::id(), true); k->setMutable(DeviceKitAspect::id(), true); k->setSticky(DeviceTypeKitAspect::id(), true); diff --git a/src/plugins/android/androidconstants.h b/src/plugins/android/androidconstants.h index df8c821f98..02bb50b348 100644 --- a/src/plugins/android/androidconstants.h +++ b/src/plugins/android/androidconstants.h @@ -42,9 +42,9 @@ namespace Internal { namespace Constants { const char ANDROID_SETTINGS_ID[] = "BB.Android Configurations"; const char ANDROID_TOOLCHAIN_TYPEID[] = "Qt4ProjectManager.ToolChain.Android"; -const char ANDROIDQT[] = "Qt4ProjectManager.QtVersion.Android"; +const char ANDROID_QT_TYPE[] = "Qt4ProjectManager.QtVersion.Android"; -const char ANDROID_AMSTARTARGS[] = "Android.AmStartArgs"; +const char ANDROID_AM_START_ARGS[] = "Android.AmStartArgs"; // Note: Can be set on RunConfiguration using an aspect and/or // the AndroidRunnerWorker using recordData() const char ANDROID_PRESTARTSHELLCMDLIST[] = "Android.PreStartShellCmdList"; @@ -67,32 +67,33 @@ const char ANDROID_ARCHITECTURE[] = "Android.Architecture"; const char ANDROID_PACKAGE_SOURCE_DIR[] = "ANDROID_PACKAGE_SOURCE_DIR"; const char ANDROID_EXTRA_LIBS[] = "ANDROID_EXTRA_LIBS"; const char ANDROID_ABI[] = "ANDROID_ABI"; +const char ANDROID_TARGET_ARCH[] = "ANDROID_TARGET_ARCH"; const char ANDROID_ABIS[] = "ANDROID_ABIS"; const char ANDROID_APPLICATION_ARGUMENTS[] = "ANDROID_APPLICATION_ARGUMENTS"; -const char QT_ANDROID_APPLICATION_ARGUMENTS[] = "QT_ANDROID_APPLICATION_ARGUMENTS"; const char ANDROID_DEPLOYMENT_SETTINGS_FILE[] = "ANDROID_DEPLOYMENT_SETTINGS_FILE"; const char ANDROID_SO_LIBS_PATHS[] = "ANDROID_SO_LIBS_PATHS"; -const char ANDROID_PACKAGENAME[] = "Android.PackageName"; -const char ANDROID_PACKAGE_INSTALLATION_STEP_ID[] - = "Qt4ProjectManager.AndroidPackageInstallationStep"; +const char ANDROID_PACKAGE_INSTALL_STEP_ID[] = "Qt4ProjectManager.AndroidPackageInstallationStep"; const char ANDROID_BUILD_APK_ID[] = "QmakeProjectManager.AndroidBuildApkStep"; const char ANDROID_DEPLOY_QT_ID[] = "Qt4ProjectManager.AndroidDeployQtStep"; const char AndroidPackageSourceDir[] = "AndroidPackageSourceDir"; // QString const char AndroidDeploySettingsFile[] = "AndroidDeploySettingsFile"; // QString const char AndroidExtraLibs[] = "AndroidExtraLibs"; // QStringList -// REMOVE ME -const char AndroidArch[] = "AndroidArch"; // QString +const char AndroidAbi[] = "AndroidAbi"; // QString +const char AndroidAbis[] = "AndroidAbis"; // QStringList +const char AndroidMkSpecAbis[] = "AndroidMkSpecAbis"; // QStringList const char AndroidSoLibPath[] = "AndroidSoLibPath"; // QStringList const char AndroidTargets[] = "AndroidTargets"; // QStringList +const char AndroidApplicationArgs[] = "AndroidApplicationArgs"; // QString + +// For qbs support const char AndroidApk[] = "Android.APK"; // QStringList const char AndroidManifest[] = "Android.Manifest"; // QStringList const char AndroidNdkPlatform[] = "AndroidNdkPlatform"; //QString const char NdkLocation[] = "NdkLocation"; // FileName const char SdkLocation[] = "SdkLocation"; // FileName -const char AndroidABIs[] = "AndroidABIs"; // QString // Android Device const Utils::Id AndroidSerialNumber = "AndroidSerialNumber"; diff --git a/src/plugins/android/androiddeployqtstep.cpp b/src/plugins/android/androiddeployqtstep.cpp index 12d3b3e651..8c50c7a52c 100644 --- a/src/plugins/android/androiddeployqtstep.cpp +++ b/src/plugins/android/androiddeployqtstep.cpp @@ -149,14 +149,14 @@ bool AndroidDeployQtStep::init() info = androidDeployQtStep->m_deviceInfo; const BuildSystem *bs = buildSystem(); - auto selectedAbis = bs->property(Constants::ANDROID_ABIS).toStringList(); + auto selectedAbis = bs->property(Constants::AndroidAbis).toStringList(); const QString buildKey = target()->activeBuildKey(); if (selectedAbis.isEmpty()) - selectedAbis = bs->extraData(buildKey, Constants::ANDROID_ABIS).toStringList(); + selectedAbis = bs->extraData(buildKey, Constants::AndroidAbis).toStringList(); if (selectedAbis.isEmpty()) - selectedAbis.append(bs->extraData(buildKey, Constants::AndroidArch).toString()); + selectedAbis.append(bs->extraData(buildKey, Constants::AndroidAbi).toString()); if (!info.isValid()) { const IDevice *dev = DeviceKitAspect::device(kit()).data(); diff --git a/src/plugins/android/androidmanager.cpp b/src/plugins/android/androidmanager.cpp index 7d2a568303..e8944fba17 100644 --- a/src/plugins/android/androidmanager.cpp +++ b/src/plugins/android/androidmanager.cpp @@ -408,7 +408,7 @@ QString AndroidManager::apkDevicePreferredAbi(const Target *target) auto libsPath = androidBuildDirectory(target).pathAppended("libs"); if (!libsPath.exists()) { if (const ProjectNode *node = currentProjectNode(target)) - return preferredAbi(node->data(Android::Constants::ANDROID_ABIS).toStringList(), + return preferredAbi(node->data(Android::Constants::AndroidAbis).toStringList(), target); } QStringList apkAbis; diff --git a/src/plugins/android/androidpackageinstallationstep.cpp b/src/plugins/android/androidpackageinstallationstep.cpp index cc951859ea..e1eb82e0cc 100644 --- a/src/plugins/android/androidpackageinstallationstep.cpp +++ b/src/plugins/android/androidpackageinstallationstep.cpp @@ -196,7 +196,7 @@ void AndroidPackageInstallationStep::doRun() AndroidPackageInstallationFactory::AndroidPackageInstallationFactory() { - registerStep<AndroidPackageInstallationStep>(Constants::ANDROID_PACKAGE_INSTALLATION_STEP_ID); + registerStep<AndroidPackageInstallationStep>(Constants::ANDROID_PACKAGE_INSTALL_STEP_ID); setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_BUILD); setSupportedDeviceType(Android::Constants::ANDROID_DEVICE_TYPE); setRepeatable(false); diff --git a/src/plugins/android/androidpotentialkit.cpp b/src/plugins/android/androidpotentialkit.cpp index 128c853c84..79a1afebec 100644 --- a/src/plugins/android/androidpotentialkit.cpp +++ b/src/plugins/android/androidpotentialkit.cpp @@ -74,7 +74,7 @@ bool AndroidPotentialKit::isEnabled() const } return QtSupport::QtVersionManager::version([](const QtSupport::BaseQtVersion *v) { - return v->isValid() && v->type() == QString::fromLatin1(Constants::ANDROIDQT); + return v->isValid() && v->type() == QString::fromLatin1(Constants::ANDROID_QT_TYPE); }); } diff --git a/src/plugins/android/androidqtversion.cpp b/src/plugins/android/androidqtversion.cpp index 3cc9b06ccd..b53dc9e8d0 100644 --- a/src/plugins/android/androidqtversion.cpp +++ b/src/plugins/android/androidqtversion.cpp @@ -197,7 +197,7 @@ void AndroidQtVersion::parseMkSpec(ProFileEvaluator *evaluator) const { m_androidAbis = evaluator->values("ALL_ANDROID_ABIS"); if (m_androidAbis.isEmpty()) - m_androidAbis = QStringList{evaluator->value("ANDROID_TARGET_ARCH")}; + m_androidAbis = QStringList{evaluator->value(Constants::ANDROID_TARGET_ARCH)}; const QString androidPlatform = evaluator->value("ANDROID_PLATFORM"); if (!androidPlatform.isEmpty()) { const QRegularExpression regex("android-(\\d+)"); @@ -232,7 +232,7 @@ QSet<Utils::Id> AndroidQtVersion::targetDeviceTypes() const AndroidQtVersionFactory::AndroidQtVersionFactory() { setQtVersionCreator([] { return new AndroidQtVersion; }); - setSupportedType(Constants::ANDROIDQT); + setSupportedType(Constants::ANDROID_QT_TYPE); setPriority(90); setRestrictionChecker([](const SetupData &setup) { diff --git a/src/plugins/android/androidrunconfiguration.cpp b/src/plugins/android/androidrunconfiguration.cpp index 5b834fa673..e1477c2615 100644 --- a/src/plugins/android/androidrunconfiguration.cpp +++ b/src/plugins/android/androidrunconfiguration.cpp @@ -82,13 +82,13 @@ AndroidRunConfiguration::AndroidRunConfiguration(Target *target, Utils::Id id) if (target->buildConfigurations().first()->buildType() == BuildConfiguration::BuildType::Release) { const QString buildKey = target->activeBuildKey(); target->buildSystem()->setExtraData(buildKey, - Android::Constants::ANDROID_APPLICATION_ARGUMENTS, - extraAppArgsAspect->arguments(target->macroExpander())); + Android::Constants::AndroidApplicationArgs, + extraAppArgsAspect->arguments(target->macroExpander())); } }); auto amStartArgsAspect = addAspect<StringAspect>(); - amStartArgsAspect->setId(Constants::ANDROID_AMSTARTARGS); + amStartArgsAspect->setId(Constants::ANDROID_AM_START_ARGS); amStartArgsAspect->setSettingsKey("Android.AmStartArgsKey"); amStartArgsAspect->setLabelText(tr("Activity manager start options:")); amStartArgsAspect->setDisplayStyle(StringAspect::LineEditDisplay); diff --git a/src/plugins/android/androidrunnerworker.cpp b/src/plugins/android/androidrunnerworker.cpp index db28291511..83c512a46a 100644 --- a/src/plugins/android/androidrunnerworker.cpp +++ b/src/plugins/android/androidrunnerworker.cpp @@ -279,7 +279,7 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunWorker *runner, const QString &packa m_extraAppParams = runControl->runnable().command.arguments(); } - if (auto aspect = runControl->aspect(Constants::ANDROID_AMSTARTARGS)) { + if (auto aspect = runControl->aspect(Constants::ANDROID_AM_START_ARGS)) { QTC_CHECK(aspect->value().type() == QVariant::String); const QString startArgs = aspect->value().toString(); m_amStartExtraArgs = ProcessArgs::splitArgs(startArgs, OsTypeOtherUnix); diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index fea5314001..5b3e76bee8 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -66,14 +66,18 @@ #include <utils/runextensions.h> #include <QCheckBox> +#include <QDateTime> +#include <QElapsedTimer> #include <QFile> #include <QHash> #include <QPair> #include <QPointer> #include <QRegularExpression> +#include <QtConcurrent> #include <set> #include <unordered_map> +#include <utility> using namespace CPlusPlus; using namespace Core; @@ -89,6 +93,7 @@ static Q_LOGGING_CATEGORY(clangdLog, "qtc.clangcodemodel.clangd", QtWarningMsg); static Q_LOGGING_CATEGORY(clangdLogServer, "qtc.clangcodemodel.clangd.server", QtWarningMsg); static Q_LOGGING_CATEGORY(clangdLogAst, "qtc.clangcodemodel.clangd.ast", QtWarningMsg); static Q_LOGGING_CATEGORY(clangdLogHighlight, "qtc.clangcodemodel.clangd.highlight", QtWarningMsg); +static Q_LOGGING_CATEGORY(clangdLogTiming, "qtc.clangcodemodel.clangd.timing", QtWarningMsg); static QString indexingToken() { return "backgroundIndexProgress"; } class AstNode : public JsonObject @@ -171,6 +176,12 @@ public: bool isNamespace() const { return role() == "declaration" && kind() == "Namespace"; } + bool isTemplateParameterDeclaration() const + { + return role() == "declaration" && (kind() == "TemplateTypeParm" + || kind() == "NonTypeTemplateParm"); + }; + QString type() const { const Utils::optional<QString> arcanaString = arcana(); @@ -305,14 +316,6 @@ static QList<AstNode> getAstPath(const AstNode &root, const Range &range) return path; } -static AstNode getAstNode(const AstNode &root, const Range &range) -{ - const QList<AstNode> path = getAstPath(root, range); - if (!path.isEmpty()) - return path.last(); - return {}; -} - static Usage::Type getUsageType(const QList<AstNode> &path) { bool potentialWrite = false; @@ -773,6 +776,109 @@ private: std::unordered_map<DocType, VersionedDocData<DocType, DataType>> m_data; }; +class TaskTimer +{ +public: + TaskTimer(const QString &task) : m_task(task) {} + + void stopTask() + { + // This can happen due to the RAII mechanism employed with SubtaskTimer. + // The subtask timers will expire immediately after, so this does not distort + // the timing data. + if (m_subtasks > 0) { + QTC_CHECK(m_timer.isValid()); + m_elapsedMs += m_timer.elapsed(); + m_timer.invalidate(); + m_subtasks = 0; + } + m_started = false; + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": took " << m_elapsedMs + << " ms in UI thread"; + } + void startSubtask() + { + // We have some callbacks that are either synchronous or asynchronous, depending on + // dynamic conditions. In the sync case, we will have nested subtasks, in which case + // the inner ones must not collect timing data, as their code blocks are already covered. + if (++m_subtasks > 1) + return; + if (!m_started) { + QTC_ASSERT(m_elapsedMs == 0, m_elapsedMs = 0); + m_started = true; + m_finalized = false; + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": starting"; + } + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": subtask started at " + << QDateTime::currentDateTime().toString(); + QTC_CHECK(!m_timer.isValid()); + m_timer.start(); + } + void stopSubtask(bool isFinalizing) + { + if (m_subtasks == 0) // See stopTask(). + return; + if (isFinalizing) + m_finalized = true; + if (--m_subtasks > 0) // See startSubtask(). + return; + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": subtask stopped at " + << QDateTime::currentDateTime().toString(); + QTC_CHECK(m_timer.isValid()); + m_elapsedMs += m_timer.elapsed(); + m_timer.invalidate(); + if (m_finalized) + stopTask(); + } + +private: + const QString m_task; + QElapsedTimer m_timer; + qint64 m_elapsedMs = 0; + int m_subtasks = 0; + bool m_started = false; + bool m_finalized = false; +}; + +class SubtaskTimer +{ +public: + SubtaskTimer(TaskTimer &timer) : m_timer(timer) { m_timer.startSubtask(); } + ~SubtaskTimer() { m_timer.stopSubtask(m_isFinalizing); } + +protected: + void makeFinalizing() { m_isFinalizing = true; } + +private: + TaskTimer &m_timer; + bool m_isFinalizing = false; +}; + +class FinalizingSubtaskTimer : public SubtaskTimer +{ +public: + FinalizingSubtaskTimer(TaskTimer &timer) : SubtaskTimer(timer) { makeFinalizing(); } +}; + +class ThreadedSubtaskTimer +{ +public: + ThreadedSubtaskTimer(const QString &task) : m_task(task) + { + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": starting thread"; + m_timer.start(); + } + + ~ThreadedSubtaskTimer() + { + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": took " << m_timer.elapsed() + << " ms in dedicated thread"; + } +private: + const QString m_task; + QElapsedTimer m_timer; +}; + class ClangdClient::Private { public: @@ -812,7 +918,7 @@ public: using TextDocOrFile = const Utils::variant<const TextDocument *, Utils::FilePath>; using AstHandler = const std::function<void(const AstNode &ast, const MessageId &)>; MessageId getAndHandleAst(TextDocOrFile &doc, AstHandler &astHandler, - AstCallbackMode callbackMode); + AstCallbackMode callbackMode, const Range &range = {}); ClangdClient * const q; const CppEditor::ClangdSettings::Data settings; @@ -821,9 +927,13 @@ public: Utils::optional<SwitchDeclDefData> switchDeclDefData; Utils::optional<LocalRefsData> localRefsData; Utils::optional<QVersionNumber> versionNumber; - std::unordered_map<TextDocument *, CppEditor::SemanticHighlighter> highlighters; + + // The highlighters are owned by their respective documents. + std::unordered_map<TextDocument *, CppEditor::SemanticHighlighter *> highlighters; + VersionedDataCache<const TextDocument *, AstNode> astCache; VersionedDataCache<Utils::FilePath, AstNode> externalAstCache; + TaskTimer highlightingTimer{"highlighting"}; quint64 nextJobId = 0; bool isFullyIndexed = false; bool isTesting = false; @@ -1453,16 +1563,16 @@ void ClangdClient::followSymbol(TextDocument *document, return; } - const auto astHandler = [this, id = d->followSymbolData->id, range = Range(cursor)] + const auto astHandler = [this, id = d->followSymbolData->id] (const AstNode &ast, const MessageId &) { qCDebug(clangdLog) << "received ast response for cursor"; if (!d->followSymbolData || d->followSymbolData->id != id) return; - d->followSymbolData->cursorNode = getAstNode(ast, range); + d->followSymbolData->cursorNode = ast; if (d->followSymbolData->defLink.hasValidTarget()) d->handleGotoDefinitionResult(); }; - d->getAndHandleAst(document, astHandler, Private::AstCallbackMode::SyncIfPossible); + d->getAndHandleAst(document, astHandler, Private::AstCallbackMode::AlwaysAsync, Range(cursor)); } void ClangdClient::switchDeclDef(TextDocument *document, const QTextCursor &cursor, @@ -1912,18 +2022,19 @@ void ClangdClient::Private::handleGotoImplementationResult( : TextDocOrFile(defLinkFilePath); const Position defLinkPos(followSymbolData->defLink.targetLine - 1, followSymbolData->defLink.targetColumn); - const auto astHandler = [this, range = Range(defLinkPos, defLinkPos), id = followSymbolData->id] + const auto astHandler = [this, id = followSymbolData->id] (const AstNode &ast, const MessageId &) { qCDebug(clangdLog) << "received ast response for def link"; if (!followSymbolData || followSymbolData->id != id) return; - followSymbolData->defLinkNode = getAstNode(ast, range); + followSymbolData->defLinkNode = ast; if (followSymbolData->pendingSymbolInfoRequests.isEmpty() && followSymbolData->pendingGotoDefRequests.isEmpty()) { handleDocumentInfoResults(); } }; - getAndHandleAst(defLinkDocVariant, astHandler, AstCallbackMode::SyncIfPossible); + getAndHandleAst(defLinkDocVariant, astHandler, AstCallbackMode::AlwaysAsync, + Range(defLinkPos, defLinkPos)); } void ClangdClient::Private::handleDocumentInfoResults() @@ -2013,446 +2124,37 @@ void ClangdClient::Private::setHelpItemForTooltip(const MessageId &token, const q->hoverHandler()->setHelpItem(token, helpItem); } -static void collectExtraResults(QFutureInterface<HighlightingResult> &future, - HighlightingResults &results, const AstNode &ast, - QTextDocument *doc, const QString &docContent) +class ExtraHighlightingResultsCollector { - if (!ast.isValid()) - return; - - static const auto lessThan = [](const HighlightingResult &r1, - const HighlightingResult &r2) { - return r1.line < r2.line || (r1.line == r2.line && r1.column < r2.column) - || (r1.line == r2.line && r1.column == r2.column && r1.length < r2.length); - }; - const auto insert = [&](const HighlightingResult &result) { - if (!result.isValid()) // Some nodes don't have a range. - return; - const auto it = std::lower_bound(results.begin(), results.end(), result, lessThan); - if (it == results.end() || *it != result) { - qCDebug(clangdLogHighlight) << "adding additional highlighting result" - << result.line << result.column << result.length; - results.insert(it, result); - return; - } - - // This is for conversion operators, whose type part is only reported as a type by clangd. - if ((it->textStyles.mainStyle == C_TYPE - || it->textStyles.mainStyle == C_PRIMITIVE_TYPE) - && !result.textStyles.mixinStyles.empty() - && result.textStyles.mixinStyles.at(0) == C_OPERATOR) { - it->textStyles.mixinStyles = result.textStyles.mixinStyles; - } - }; - const auto setFromRange = [doc](HighlightingResult &result, const Range &range) { - if (!range.isValid()) - return; - const Position startPos = range.start(); - const Position endPos = range.end(); - result.line = startPos.line() + 1; - result.column = startPos.character() + 1; - result.length = endPos.toPositionInDocument(doc) - startPos.toPositionInDocument(doc); - }; - static const auto onlyIndexOf = [](const QStringView &view, const QStringView &s, - int from = 0) { - const int firstIndex = view.indexOf(s, from); - if (firstIndex == -1) - return -1; - const int nextIndex = view.indexOf(s, firstIndex + 1); - - // The second condion deals with the off-by-one error in TemplateSpecialization nodes; - // see below. - return nextIndex == -1 || nextIndex == firstIndex + 1 ? firstIndex : -1; - }; - - QList<AstNode> nodes = {ast}; - while (!nodes.isEmpty()) { - if (future.isCanceled()) - return; - const AstNode node = nodes.takeFirst(); - const QList<AstNode> children = node.children().value_or(QList<AstNode>()); - nodes << children; - - if (node.kind().endsWith("Literal")) { - HighlightingResult result; - result.useTextSyles = true; - const bool isStringLike = node.kind().startsWith("String") - || node.kind().startsWith("Character"); - result.textStyles.mainStyle = isStringLike ? C_STRING : C_NUMBER; - setFromRange(result, node.range()); - insert(result); - continue; - } - if (node.role() == "type" && node.kind() == "Builtin") { - HighlightingResult result; - result.useTextSyles = true; - result.textStyles.mainStyle = C_PRIMITIVE_TYPE; - setFromRange(result, node.range()); - insert(result); - continue; - } - if (node.role() == "attribute" && (node.kind() == "Override" || node.kind() == "Final")) { - HighlightingResult result; - result.useTextSyles = true; - result.textStyles.mainStyle = C_KEYWORD; - setFromRange(result, node.range()); - insert(result); - continue; - } - - const bool isExpression = node.role() == "expression"; - const bool isDeclaration = node.role() == "declaration"; - - // Unfortunately, the exact position of a specific token is usually not - // recorded in the AST, so if we need that, we have to search for it textually. - // In corner cases, this might get sabotaged by e.g. comments, in which case we give up. - const auto posForNodeStart = [doc](const AstNode &node) { - return Utils::Text::positionInText(doc, node.range().start().line() + 1, - node.range().start().character() + 1); - }; - const auto posForNodeEnd = [doc](const AstNode &node) { - return Utils::Text::positionInText(doc, node.range().end().line() + 1, - node.range().end().character() + 1); - }; - const int nodeStartPos = posForNodeStart(node); - const int nodeEndPos = posForNodeEnd(node); - - // Match question mark and colon in ternary operators. - if (isExpression && node.kind() == "ConditionalOperator") { - if (children.size() != 3) - continue; - - // The question mark is between sub-expressions 1 and 2, the colon is between - // sub-expressions 2 and 3. - const int searchStartPosQuestionMark = posForNodeEnd(children.first()); - const int searchEndPosQuestionMark = posForNodeStart(children.at(1)); - QStringView content = QStringView(docContent).mid(searchStartPosQuestionMark, - searchEndPosQuestionMark - searchStartPosQuestionMark); - const int questionMarkPos = onlyIndexOf(content, QStringView(QStringLiteral("?"))); - if (questionMarkPos == -1) - continue; - const int searchStartPosColon = posForNodeEnd(children.at(1)); - const int searchEndPosColon = posForNodeStart(children.at(2)); - content = QStringView(docContent).mid(searchStartPosColon, - searchEndPosColon - searchStartPosColon); - const int colonPos = onlyIndexOf(content, QStringView(QStringLiteral(":"))); - if (colonPos == -1) - continue; - - const int absQuestionMarkPos = searchStartPosQuestionMark + questionMarkPos; - const int absColonPos = searchStartPosColon + colonPos; - if (absQuestionMarkPos > absColonPos) - continue; - - HighlightingResult result; - result.useTextSyles = true; - result.textStyles.mainStyle = C_PUNCTUATION; - result.textStyles.mixinStyles.push_back(C_OPERATOR); - Utils::Text::convertPosition(doc, absQuestionMarkPos, &result.line, &result.column); - result.length = 1; - result.kind = CppEditor::SemanticHighlighter::TernaryIf; - insert(result); - Utils::Text::convertPosition(doc, absColonPos, &result.line, &result.column); - result.kind = CppEditor::SemanticHighlighter::TernaryElse; - insert(result); - continue; - } - - // The following functions are for matching the "<" and ">" brackets of template - // declarations, specializations and instantiations. - const auto insertAngleBracketInfo = [&docContent, doc, &insert]( - int searchStart1, int searchEnd1, int searchStart2, int searchEnd2) { - const int openingAngleBracketPos = onlyIndexOf( - QStringView(docContent).mid(searchStart1, searchEnd1 - searchStart1), - QStringView(QStringLiteral("<"))); - if (openingAngleBracketPos == -1) - return; - const int absOpeningAngleBracketPos = searchStart1 + openingAngleBracketPos; - if (absOpeningAngleBracketPos > searchStart2) - searchStart2 = absOpeningAngleBracketPos + 1; - if (searchStart2 >= searchEnd2) - return; - const int closingAngleBracketPos = onlyIndexOf( - QStringView(docContent).mid(searchStart2, searchEnd2 - searchStart2), - QStringView(QStringLiteral(">"))); - if (closingAngleBracketPos == -1) - return; - - const int absClosingAngleBracketPos = searchStart2 + closingAngleBracketPos; - if (absOpeningAngleBracketPos > absClosingAngleBracketPos) - return; - - HighlightingResult result; - result.useTextSyles = true; - result.textStyles.mainStyle = C_PUNCTUATION; - Utils::Text::convertPosition(doc, absOpeningAngleBracketPos, - &result.line, &result.column); - result.length = 1; - result.kind = CppEditor::SemanticHighlighter::AngleBracketOpen; - insert(result); - Utils::Text::convertPosition(doc, absClosingAngleBracketPos, - &result.line, &result.column); - result.kind = CppEditor::SemanticHighlighter::AngleBracketClose; - insert(result); - }; - - if (isDeclaration && (node.kind() == "FunctionTemplate" - || node.kind() == "ClassTemplate")) { - // The child nodes are the template parameters and and the function or class. - // The opening angle bracket is before the first child node, the closing angle - // bracket is before the function child node and after the last param node. - const QString classOrFunctionKind = QLatin1String(node.kind() == "FunctionTemplate" - ? "Function" : "CXXRecord"); - const auto functionOrClassIt = std::find_if(children.begin(), children.end(), - [&classOrFunctionKind](const AstNode &n) { - return n.role() == "declaration" && n.kind() == classOrFunctionKind; - }); - if (functionOrClassIt == children.end() || functionOrClassIt == children.begin()) - continue; - const int firstTemplateParamStartPos = posForNodeStart(children.first()); - const int lastTemplateParamEndPos = posForNodeEnd(*(functionOrClassIt - 1)); - const int functionOrClassStartPos = posForNodeStart(*functionOrClassIt); - insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, - lastTemplateParamEndPos, functionOrClassStartPos); - continue; - } - - static const auto findTemplateParam = [](const AstNode &n) { - return n.role() == "declaration" && (n.kind() == "TemplateTypeParm" - || n.kind() == "NonTypeTemplateParm"); - }; - - if (isDeclaration && node.kind() == "TypeAliasTemplate") { - // Children are one node of type TypeAlias and the template parameters. - // The opening angle bracket is before the first parameter and the closing - // angle bracket is after the last parameter. - // The TypeAlias node seems to appear first in the AST, even though lexically - // is comes after the parameters. We don't rely on the order here. - // Note that there is a second pair of angle brackets. That one is part of - // a TemplateSpecialization, which is handled further below. - const auto firstTemplateParam = std::find_if(children.begin(), children.end(), - findTemplateParam); - if (firstTemplateParam == children.end()) - continue; - const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(), - findTemplateParam); - QTC_ASSERT(lastTemplateParam != children.rend(), continue); - const auto typeAlias = std::find_if(children.begin(), children.end(), - [](const AstNode &n) { return n.kind() == "TypeAlias"; }); - if (typeAlias == children.end()) - continue; - - const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam); - const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam); - const int searchEndPos = posForNodeStart(*typeAlias); - insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, - lastTemplateParamEndPos, searchEndPos); - continue; - } - - if (isDeclaration && node.kind() == "ClassTemplateSpecialization") { - // There is one child of kind TemplateSpecialization. The first pair - // of angle brackets comes before that. - if (children.size() == 1) { - const int childNodePos = posForNodeStart(children.first()); - insertAngleBracketInfo(nodeStartPos, childNodePos, nodeStartPos, childNodePos); - } - continue; - } - - if (isDeclaration && node.kind() == "TemplateTemplateParm") { - // The child nodes are template arguments and template parameters. - // Arguments seem to appear before parameters in the AST, even though they - // come after them in the source code. We don't rely on the order here. - const auto firstTemplateParam = std::find_if(children.begin(), children.end(), - findTemplateParam); - if (firstTemplateParam == children.end()) - continue; - const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(), - findTemplateParam); - QTC_ASSERT(lastTemplateParam != children.rend(), continue); - const auto templateArg = std::find_if(children.begin(), children.end(), - [](const AstNode &n) { return n.role() == "template argument"; }); - - const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam); - const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam); - const int searchEndPos = templateArg == children.end() - ? nodeEndPos : posForNodeStart(*templateArg); - insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, - lastTemplateParamEndPos, searchEndPos); - continue; - } - - // {static,dynamic,reinterpret}_cast<>(). - if (isExpression && node.kind().startsWith("CXX") && node.kind().endsWith("Cast")) { - // First child is type, second child is expression. - // The opening angle bracket is before the first child, the closing angle bracket - // is between the two children. - if (children.size() == 2) { - insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.first()), - posForNodeEnd(children.first()), - posForNodeStart(children.last())); - } - continue; - } - - if (node.kind() == "TemplateSpecialization") { - // First comes the template type, then the template arguments. - // The opening angle bracket is before the first template argument, - // the closing angle bracket is after the last template argument. - // The first child node has no range, so we start searching at the parent node. - if (children.size() >= 2) { - int searchStart2 = posForNodeEnd(children.last()); - int searchEnd2 = nodeEndPos; - - // There is a weird off-by-one error on the clang side: If there is a - // nested template instantiation *and* there is no space between - // the closing angle brackets, then the inner TemplateSpecialization node's range - // will extend one character too far, covering the outer's closing angle bracket. - // This is what we are correcting for here. - // This issue is tracked at https://github.com/clangd/clangd/issues/871. - if (searchStart2 == searchEnd2) - --searchStart2; - insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.at(1)), - searchStart2, searchEnd2); - } - continue; - } - - if (!isExpression && !isDeclaration) - continue; - - // Operators, overloaded ones in particular. - static const QString operatorPrefix = "operator"; - QString detail = node.detail().value_or(QString()); - const bool isCallToNew = node.kind() == "CXXNew"; - const bool isCallToDelete = node.kind() == "CXXDelete"; - if (!isCallToNew && !isCallToDelete - && (!detail.startsWith(operatorPrefix) || detail == operatorPrefix)) { - continue; - } - - if (!isCallToNew && !isCallToDelete) - detail.remove(0, operatorPrefix.length()); - - HighlightingResult result; - result.useTextSyles = true; - const bool isConversionOp = node.kind() == "CXXConversion"; - const bool isOverloaded = !isConversionOp - && (isDeclaration || ((!isCallToNew && !isCallToDelete) - || node.arcanaContains("CXXMethod"))); - result.textStyles.mainStyle = isConversionOp - ? C_PRIMITIVE_TYPE - : isCallToNew || isCallToDelete || detail.at(0).isSpace() - ? C_KEYWORD : C_PUNCTUATION; - result.textStyles.mixinStyles.push_back(C_OPERATOR); - if (isOverloaded) - result.textStyles.mixinStyles.push_back(C_OVERLOADED_OPERATOR); - if (isDeclaration) - result.textStyles.mixinStyles.push_back(C_DECLARATION); - - const QStringView nodeText = QStringView(docContent) - .mid(nodeStartPos, nodeEndPos - nodeStartPos); - - if (isCallToNew || isCallToDelete) { - result.line = node.range().start().line() + 1; - result.column = node.range().start().character() + 1; - result.length = isCallToNew ? 3 : 6; - insert(result); - if (node.arcanaContains("array")) { - const int openingBracketOffset = nodeText.indexOf('['); - if (openingBracketOffset == -1) - continue; - const int closingBracketOffset = nodeText.lastIndexOf(']'); - if (closingBracketOffset == -1 || closingBracketOffset < openingBracketOffset) - continue; - - result.textStyles.mainStyle = C_PUNCTUATION; - result.length = 1; - Utils::Text::convertPosition(doc, - nodeStartPos + openingBracketOffset, - &result.line, &result.column); - insert(result); - Utils::Text::convertPosition(doc, - nodeStartPos + closingBracketOffset, - &result.line, &result.column); - insert(result); - } - continue; - } - - if (isExpression && (detail == QLatin1String("()") || detail == QLatin1String("[]"))) { - result.line = node.range().start().line() + 1; - result.column = node.range().start().character() + 1; - result.length = 1; - insert(result); - result.line = node.range().end().line() + 1; - result.column = node.range().end().character(); - insert(result); - continue; - } - - const int opStringLen = detail.at(0).isSpace() ? detail.length() - 1 : detail.length(); - - // The simple case: Call to operator+, +=, * etc. - if (nodeEndPos - nodeStartPos == opStringLen) { - setFromRange(result, node.range()); - insert(result); - continue; - } - - const int prefixOffset = nodeText.indexOf(operatorPrefix); - if (prefixOffset == -1) - continue; - - const bool isArray = detail == "[]"; - const bool isCall = detail == "()"; - const bool isArrayNew = detail == " new[]"; - const bool isArrayDelete = detail == " delete[]"; - const QStringView searchTerm = isArray || isCall - ? QStringView(detail).chopped(1) : isArrayNew || isArrayDelete - ? QStringView(detail).chopped(2) : detail; - const int opStringOffset = nodeText.indexOf(searchTerm, prefixOffset - + operatorPrefix.length()); - if (opStringOffset == -1 || nodeText.indexOf(operatorPrefix, opStringOffset) != -1) - continue; - - const int opStringOffsetInDoc = nodeStartPos + opStringOffset - + detail.length() - opStringLen; - Utils::Text::convertPosition(doc, opStringOffsetInDoc, &result.line, &result.column); - result.length = opStringLen; - if (isArray || isCall) - result.length = 1; - else if (isArrayNew || isArrayDelete) - result.length -= 2; - if (!isArray && !isCall) - insert(result); - if (!isArray && !isCall && !isArrayNew && !isArrayDelete) - continue; +public: + ExtraHighlightingResultsCollector(QFutureInterface<HighlightingResult> &future, + HighlightingResults &results, const AstNode &ast, + const QTextDocument *doc, const QString &docContent); - result.textStyles.mainStyle = C_PUNCTUATION; - result.length = 1; - const int openingParenOffset = nodeText.indexOf( - isCall ? '(' : '[', prefixOffset + operatorPrefix.length()); - if (openingParenOffset == -1) - continue; - const int closingParenOffset = nodeText.indexOf(isCall ? ')' : ']', openingParenOffset + 1); - if (closingParenOffset == -1 || closingParenOffset < openingParenOffset) - continue; - Utils::Text::convertPosition(doc, nodeStartPos + openingParenOffset, - &result.line, &result.column); - insert(result); - Utils::Text::convertPosition(doc, nodeStartPos + closingParenOffset, - &result.line, &result.column); - insert(result); - } -} + void collect(); +private: + static bool lessThan(const HighlightingResult &r1, const HighlightingResult &r2); + static int onlyIndexOf(const QStringView &text, const QStringView &subString, int from = 0); + int posForNodeStart(const AstNode &node) const; + int posForNodeEnd(const AstNode &node) const; + void insertResult(const HighlightingResult &result); + void insertAngleBracketInfo(int searchStart1, int searchEnd1, int searchStart2, int searchEnd2); + void setResultPosFromRange(HighlightingResult &result, const Range &range); + void collectFromNode(const AstNode &node); + void visitNode(const AstNode&node); + + QFutureInterface<HighlightingResult> &m_future; + HighlightingResults &m_results; + const AstNode &m_ast; + const QTextDocument * const m_doc; + const QString &m_docContent; +}; // clangd reports also the #ifs, #elses and #endifs around the disabled code as disabled, // and not even in a consistent manner. We don't want this, so we have to clean up here. // But note that we require this behavior, as otherwise we would not be able to grey out // e.g. empty lines after an #fdef, due to the lack of symbols. -static QList<BlockRange> cleanupDisabledCode(HighlightingResults &results, QTextDocument *doc, +static QList<BlockRange> cleanupDisabledCode(HighlightingResults &results, const QTextDocument *doc, const QString &docContent) { QList<BlockRange> ifdefedOutRanges; @@ -2513,12 +2215,13 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future, const QPointer<TextEditorWidget> &widget, int docRevision, const QVersionNumber &clangdVersion) { + ThreadedSubtaskTimer t("highlighting"); if (future.isCanceled()) { future.reportFinished(); return; } - QTextDocument doc(docContents); + const QTextDocument doc(docContents); const auto isOutputParameter = [&ast](const ExpandedSemanticToken &token) { if (token.modifiers.contains("usedAsMutableReference")) return true; @@ -2545,8 +2248,8 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future, return false; }; - const auto toResult = [&ast, &isOutputParameter, &clangdVersion] - (const ExpandedSemanticToken &token) { + const std::function<HighlightingResult(const ExpandedSemanticToken &)> toResult + = [&ast, &isOutputParameter, &clangdVersion](const ExpandedSemanticToken &token) { TextStyles styles; if (token.type == "variable") { if (token.modifiers.contains("functionScope")) { @@ -2629,13 +2332,13 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future, return HighlightingResult(token.line, token.column, token.length, styles); }; - HighlightingResults results = Utils::transform(tokens, toResult); + auto results = QtConcurrent::blockingMapped<HighlightingResults>(tokens, toResult); const QList<BlockRange> ifdefedOutBlocks = cleanupDisabledCode(results, &doc, docContents); QMetaObject::invokeMethod(widget, [widget, ifdefedOutBlocks, docRevision] { if (widget && widget->textDocument()->document()->revision() == docRevision) widget->setIfdefedOutBlocks(ifdefedOutBlocks); }, Qt::QueuedConnection); - collectExtraResults(future, results, ast, &doc, docContents); + ExtraHighlightingResultsCollector(future, results, ast, &doc, docContents).collect(); if (!future.isCanceled()) { qCDebug(clangdLog) << "reporting" << results.size() << "highlighting results"; future.reportResults(QVector<HighlightingResult>(results.cbegin(), @@ -2659,12 +2362,14 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future, void ClangdClient::Private::handleSemanticTokens(TextDocument *doc, const QList<ExpandedSemanticToken> &tokens) { + SubtaskTimer t(highlightingTimer); qCDebug(clangdLog()) << "handling LSP tokens" << doc->filePath() << tokens.size(); for (const ExpandedSemanticToken &t : tokens) qCDebug(clangdLogHighlight()) << '\t' << t.line << t.column << t.length << t.type << t.modifiers; const auto astHandler = [this, tokens, doc](const AstNode &ast, const MessageId &) { + FinalizingSubtaskTimer t(highlightingTimer); if (!q->documentOpen(doc)) return; if (clangdLogAst().isDebugEnabled()) @@ -2692,12 +2397,13 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc, auto it = highlighters.find(doc); if (it == highlighters.end()) { - it = highlighters.emplace(doc, doc).first; + it = highlighters.insert(std::make_pair(doc, new CppEditor::SemanticHighlighter(doc))) + .first; } else { - it->second.updateFormatMapFromFontSettings(); + it->second->updateFormatMapFromFontSettings(); } - it->second.setHighlightingRunner(runner); - it->second.run(); + it->second->setHighlightingRunner(runner); + it->second->run(); }; getAndHandleAst(doc, astHandler, AstCallbackMode::SyncIfPossible); } @@ -3014,26 +2720,30 @@ void ClangdCompletionItem::apply(TextDocumentManipulatorInterface &manipulator, MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc, const AstHandler &astHandler, - AstCallbackMode callbackMode) + AstCallbackMode callbackMode, const Range &range) { const auto textDocPtr = Utils::get_if<const TextDocument *>(&doc); const TextDocument * const textDoc = textDocPtr ? *textDocPtr : nullptr; const Utils::FilePath filePath = textDoc ? textDoc->filePath() : Utils::get<Utils::FilePath>(doc); - // If the document's AST is in the cache and is up to date, call the handler. - if (const auto ast = textDoc ? astCache.get(textDoc) : externalAstCache.get(filePath)) { - qCDebug(clangdLog) << "using AST from cache"; - switch (callbackMode) { - case AstCallbackMode::SyncIfPossible: - astHandler(*ast, {}); - break; - case AstCallbackMode::AlwaysAsync: - QMetaObject::invokeMethod(q, [ast, astHandler] { astHandler(*ast, {}); }, + // If the entire AST is requested and the document's AST is in the cache and it is up to date, + // call the handler. + const bool fullAstRequested = !range.isValid(); + if (fullAstRequested) { + if (const auto ast = textDoc ? astCache.get(textDoc) : externalAstCache.get(filePath)) { + qCDebug(clangdLog) << "using AST from cache"; + switch (callbackMode) { + case AstCallbackMode::SyncIfPossible: + astHandler(*ast, {}); + break; + case AstCallbackMode::AlwaysAsync: + QMetaObject::invokeMethod(q, [ast, astHandler] { astHandler(*ast, {}); }, Qt::QueuedConnection); - break; + break; + } + return {}; } - return {}; } // Otherwise retrieve the AST from clangd. @@ -3041,7 +2751,6 @@ MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc, class AstParams : public JsonObject { public: - AstParams() {} AstParams(const TextDocumentIdentifier &document, const Range &range = {}) { setTextDocument(document); @@ -3071,19 +2780,22 @@ MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc, explicit AstRequest(const AstParams ¶ms) : Request("textDocument/ast", params) {} }; - AstRequest request(AstParams(TextDocumentIdentifier(DocumentUri::fromFilePath(filePath)))); + AstRequest request(AstParams(TextDocumentIdentifier(DocumentUri::fromFilePath(filePath)), + range)); request.setResponseCallback([this, filePath, guardedTextDoc = QPointer(textDoc), astHandler, - docRev = textDoc ? getRevision(textDoc) : -1, + fullAstRequested, docRev = textDoc ? getRevision(textDoc) : -1, fileRev = getRevision(filePath), reqId = request.id()] (AstRequest::Response response) { qCDebug(clangdLog) << "retrieved AST from clangd"; const auto result = response.result(); const AstNode ast = result ? *result : AstNode(); - if (guardedTextDoc) { - if (docRev == getRevision(guardedTextDoc)) - astCache.insert(guardedTextDoc, ast); - } else if (fileRev == getRevision(filePath) && !q->documentForFilePath(filePath)) { - externalAstCache.insert(filePath, ast); + if (fullAstRequested) { + if (guardedTextDoc) { + if (docRev == getRevision(guardedTextDoc)) + astCache.insert(guardedTextDoc, ast); + } else if (fileRev == getRevision(filePath) && !q->documentForFilePath(filePath)) { + externalAstCache.insert(filePath, ast); + } } astHandler(ast, reqId); }); @@ -3092,6 +2804,465 @@ MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc, return request.id(); } +ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector( + QFutureInterface<HighlightingResult> &future, HighlightingResults &results, + const AstNode &ast, const QTextDocument *doc, const QString &docContent) + : m_future(future), m_results(results), m_ast(ast), m_doc(doc), m_docContent(docContent) +{ + +} + +void ExtraHighlightingResultsCollector::collect() +{ + if (!m_ast.isValid()) + return; + visitNode(m_ast); +} + +bool ExtraHighlightingResultsCollector::lessThan(const HighlightingResult &r1, + const HighlightingResult &r2) +{ + return r1.line < r2.line || (r1.line == r2.line && r1.column < r2.column) + || (r1.line == r2.line && r1.column == r2.column && r1.length < r2.length); +} + +int ExtraHighlightingResultsCollector::onlyIndexOf(const QStringView &text, + const QStringView &subString, int from) +{ + const int firstIndex = text.indexOf(subString, from); + if (firstIndex == -1) + return -1; + const int nextIndex = text.indexOf(subString, firstIndex + 1); + + // The second condion deals with the off-by-one error in TemplateSpecialization nodes; + // see collectFromNode(). + return nextIndex == -1 || nextIndex == firstIndex + 1 ? firstIndex : -1; +} + +// Unfortunately, the exact position of a specific token is usually not +// recorded in the AST, so if we need that, we have to search for it textually. +// In corner cases, this might get sabotaged by e.g. comments, in which case we give up. +int ExtraHighlightingResultsCollector::posForNodeStart(const AstNode &node) const +{ + return Utils::Text::positionInText(m_doc, node.range().start().line() + 1, + node.range().start().character() + 1); +} + +int ExtraHighlightingResultsCollector::posForNodeEnd(const AstNode &node) const +{ + return Utils::Text::positionInText(m_doc, node.range().end().line() + 1, + node.range().end().character() + 1); +} + +void ExtraHighlightingResultsCollector::insertResult(const HighlightingResult &result) +{ + if (!result.isValid()) // Some nodes don't have a range. + return; + const auto it = std::lower_bound(m_results.begin(), m_results.end(), result, lessThan); + if (it == m_results.end() || *it != result) { + qCDebug(clangdLogHighlight) << "adding additional highlighting result" + << result.line << result.column << result.length; + m_results.insert(it, result); + return; + } + + // This is for conversion operators, whose type part is only reported as a type by clangd. + if ((it->textStyles.mainStyle == C_TYPE + || it->textStyles.mainStyle == C_PRIMITIVE_TYPE) + && !result.textStyles.mixinStyles.empty() + && result.textStyles.mixinStyles.at(0) == C_OPERATOR) { + it->textStyles.mixinStyles = result.textStyles.mixinStyles; + } +} + +// For matching the "<" and ">" brackets of template declarations, specializations +// and instantiations. +void ExtraHighlightingResultsCollector::insertAngleBracketInfo(int searchStart1, int searchEnd1, + int searchStart2, int searchEnd2) +{ + const int openingAngleBracketPos = onlyIndexOf( + QStringView(m_docContent).mid(searchStart1, searchEnd1 - searchStart1), + QStringView(QStringLiteral("<"))); + if (openingAngleBracketPos == -1) + return; + const int absOpeningAngleBracketPos = searchStart1 + openingAngleBracketPos; + if (absOpeningAngleBracketPos > searchStart2) + searchStart2 = absOpeningAngleBracketPos + 1; + if (searchStart2 >= searchEnd2) + return; + const int closingAngleBracketPos = onlyIndexOf( + QStringView(m_docContent).mid(searchStart2, searchEnd2 - searchStart2), + QStringView(QStringLiteral(">"))); + if (closingAngleBracketPos == -1) + return; + + const int absClosingAngleBracketPos = searchStart2 + closingAngleBracketPos; + if (absOpeningAngleBracketPos > absClosingAngleBracketPos) + return; + + HighlightingResult result; + result.useTextSyles = true; + result.textStyles.mainStyle = C_PUNCTUATION; + Utils::Text::convertPosition(m_doc, absOpeningAngleBracketPos, &result.line, &result.column); + result.length = 1; + result.kind = CppEditor::SemanticHighlighter::AngleBracketOpen; + insertResult(result); + Utils::Text::convertPosition(m_doc, absClosingAngleBracketPos, &result.line, &result.column); + result.kind = CppEditor::SemanticHighlighter::AngleBracketClose; + insertResult(result); +} + +void ExtraHighlightingResultsCollector::setResultPosFromRange(HighlightingResult &result, + const Range &range) +{ + if (!range.isValid()) + return; + const Position startPos = range.start(); + const Position endPos = range.end(); + result.line = startPos.line() + 1; + result.column = startPos.character() + 1; + result.length = endPos.toPositionInDocument(m_doc) - startPos.toPositionInDocument(m_doc); +} + +void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node) +{ + if (node.kind().endsWith("Literal")) { + HighlightingResult result; + result.useTextSyles = true; + const bool isStringLike = node.kind().startsWith("String") + || node.kind().startsWith("Character"); + result.textStyles.mainStyle = isStringLike ? C_STRING : C_NUMBER; + setResultPosFromRange(result, node.range()); + insertResult(result); + return; + } + if (node.role() == "type" && node.kind() == "Builtin") { + HighlightingResult result; + result.useTextSyles = true; + result.textStyles.mainStyle = C_PRIMITIVE_TYPE; + setResultPosFromRange(result, node.range()); + insertResult(result); + return; + } + if (node.role() == "attribute" && (node.kind() == "Override" || node.kind() == "Final")) { + HighlightingResult result; + result.useTextSyles = true; + result.textStyles.mainStyle = C_KEYWORD; + setResultPosFromRange(result, node.range()); + insertResult(result); + return; + } + + const bool isExpression = node.role() == "expression"; + const bool isDeclaration = node.role() == "declaration"; + + const int nodeStartPos = posForNodeStart(node); + const int nodeEndPos = posForNodeEnd(node); + const QList<AstNode> children = node.children().value_or(QList<AstNode>()); + + // Match question mark and colon in ternary operators. + if (isExpression && node.kind() == "ConditionalOperator") { + if (children.size() != 3) + return; + + // The question mark is between sub-expressions 1 and 2, the colon is between + // sub-expressions 2 and 3. + const int searchStartPosQuestionMark = posForNodeEnd(children.first()); + const int searchEndPosQuestionMark = posForNodeStart(children.at(1)); + QStringView content = QStringView(m_docContent).mid( + searchStartPosQuestionMark, + searchEndPosQuestionMark - searchStartPosQuestionMark); + const int questionMarkPos = onlyIndexOf(content, QStringView(QStringLiteral("?"))); + if (questionMarkPos == -1) + return; + const int searchStartPosColon = posForNodeEnd(children.at(1)); + const int searchEndPosColon = posForNodeStart(children.at(2)); + content = QStringView(m_docContent).mid(searchStartPosColon, + searchEndPosColon - searchStartPosColon); + const int colonPos = onlyIndexOf(content, QStringView(QStringLiteral(":"))); + if (colonPos == -1) + return; + + const int absQuestionMarkPos = searchStartPosQuestionMark + questionMarkPos; + const int absColonPos = searchStartPosColon + colonPos; + if (absQuestionMarkPos > absColonPos) + return; + + HighlightingResult result; + result.useTextSyles = true; + result.textStyles.mainStyle = C_PUNCTUATION; + result.textStyles.mixinStyles.push_back(C_OPERATOR); + Utils::Text::convertPosition(m_doc, absQuestionMarkPos, &result.line, &result.column); + result.length = 1; + result.kind = CppEditor::SemanticHighlighter::TernaryIf; + insertResult(result); + Utils::Text::convertPosition(m_doc, absColonPos, &result.line, &result.column); + result.kind = CppEditor::SemanticHighlighter::TernaryElse; + insertResult(result); + return; + } + + if (isDeclaration && (node.kind() == "FunctionTemplate" + || node.kind() == "ClassTemplate")) { + // The child nodes are the template parameters and and the function or class. + // The opening angle bracket is before the first child node, the closing angle + // bracket is before the function child node and after the last param node. + const QString classOrFunctionKind = QLatin1String(node.kind() == "FunctionTemplate" + ? "Function" : "CXXRecord"); + const auto functionOrClassIt = std::find_if(children.begin(), children.end(), + [&classOrFunctionKind](const AstNode &n) { + return n.role() == "declaration" && n.kind() == classOrFunctionKind; + }); + if (functionOrClassIt == children.end() || functionOrClassIt == children.begin()) + return; + const int firstTemplateParamStartPos = posForNodeStart(children.first()); + const int lastTemplateParamEndPos = posForNodeEnd(*(functionOrClassIt - 1)); + const int functionOrClassStartPos = posForNodeStart(*functionOrClassIt); + insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, + lastTemplateParamEndPos, functionOrClassStartPos); + return; + } + + const auto isTemplateParamDecl = [](const AstNode &node) { + return node.isTemplateParameterDeclaration(); + }; + if (isDeclaration && node.kind() == "TypeAliasTemplate") { + // Children are one node of type TypeAlias and the template parameters. + // The opening angle bracket is before the first parameter and the closing + // angle bracket is after the last parameter. + // The TypeAlias node seems to appear first in the AST, even though lexically + // is comes after the parameters. We don't rely on the order here. + // Note that there is a second pair of angle brackets. That one is part of + // a TemplateSpecialization, which is handled further below. + const auto firstTemplateParam = std::find_if(children.begin(), children.end(), + isTemplateParamDecl); + if (firstTemplateParam == children.end()) + return; + const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(), + isTemplateParamDecl); + QTC_ASSERT(lastTemplateParam != children.rend(), return); + const auto typeAlias = std::find_if(children.begin(), children.end(), + [](const AstNode &n) { return n.kind() == "TypeAlias"; }); + if (typeAlias == children.end()) + return; + + const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam); + const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam); + const int searchEndPos = posForNodeStart(*typeAlias); + insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, + lastTemplateParamEndPos, searchEndPos); + return; + } + + if (isDeclaration && node.kind() == "ClassTemplateSpecialization") { + // There is one child of kind TemplateSpecialization. The first pair + // of angle brackets comes before that. + if (children.size() == 1) { + const int childNodePos = posForNodeStart(children.first()); + insertAngleBracketInfo(nodeStartPos, childNodePos, nodeStartPos, childNodePos); + } + return; + } + + if (isDeclaration && node.kind() == "TemplateTemplateParm") { + // The child nodes are template arguments and template parameters. + // Arguments seem to appear before parameters in the AST, even though they + // come after them in the source code. We don't rely on the order here. + const auto firstTemplateParam = std::find_if(children.begin(), children.end(), + isTemplateParamDecl); + if (firstTemplateParam == children.end()) + return; + const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(), + isTemplateParamDecl); + QTC_ASSERT(lastTemplateParam != children.rend(), return); + const auto templateArg = std::find_if(children.begin(), children.end(), + [](const AstNode &n) { return n.role() == "template argument"; }); + + const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam); + const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam); + const int searchEndPos = templateArg == children.end() + ? nodeEndPos : posForNodeStart(*templateArg); + insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, + lastTemplateParamEndPos, searchEndPos); + return; + } + + // {static,dynamic,reinterpret}_cast<>(). + if (isExpression && node.kind().startsWith("CXX") && node.kind().endsWith("Cast")) { + // First child is type, second child is expression. + // The opening angle bracket is before the first child, the closing angle bracket + // is between the two children. + if (children.size() == 2) { + insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.first()), + posForNodeEnd(children.first()), + posForNodeStart(children.last())); + } + return; + } + + if (node.kind() == "TemplateSpecialization") { + // First comes the template type, then the template arguments. + // The opening angle bracket is before the first template argument, + // the closing angle bracket is after the last template argument. + // The first child node has no range, so we start searching at the parent node. + if (children.size() >= 2) { + int searchStart2 = posForNodeEnd(children.last()); + int searchEnd2 = nodeEndPos; + + // There is a weird off-by-one error on the clang side: If there is a + // nested template instantiation *and* there is no space between + // the closing angle brackets, then the inner TemplateSpecialization node's range + // will extend one character too far, covering the outer's closing angle bracket. + // This is what we are correcting for here. + // This issue is tracked at https://github.com/clangd/clangd/issues/871. + if (searchStart2 == searchEnd2) + --searchStart2; + insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.at(1)), + searchStart2, searchEnd2); + } + return; + } + + if (!isExpression && !isDeclaration) + return; + + // Operators, overloaded ones in particular. + static const QString operatorPrefix = "operator"; + QString detail = node.detail().value_or(QString()); + const bool isCallToNew = node.kind() == "CXXNew"; + const bool isCallToDelete = node.kind() == "CXXDelete"; + if (!isCallToNew && !isCallToDelete + && (!detail.startsWith(operatorPrefix) || detail == operatorPrefix)) { + return; + } + + if (!isCallToNew && !isCallToDelete) + detail.remove(0, operatorPrefix.length()); + + HighlightingResult result; + result.useTextSyles = true; + const bool isConversionOp = node.kind() == "CXXConversion"; + const bool isOverloaded = !isConversionOp + && (isDeclaration || ((!isCallToNew && !isCallToDelete) + || node.arcanaContains("CXXMethod"))); + result.textStyles.mainStyle = isConversionOp + ? C_PRIMITIVE_TYPE + : isCallToNew || isCallToDelete || detail.at(0).isSpace() + ? C_KEYWORD : C_PUNCTUATION; + result.textStyles.mixinStyles.push_back(C_OPERATOR); + if (isOverloaded) + result.textStyles.mixinStyles.push_back(C_OVERLOADED_OPERATOR); + if (isDeclaration) + result.textStyles.mixinStyles.push_back(C_DECLARATION); + + const QStringView nodeText = QStringView(m_docContent) + .mid(nodeStartPos, nodeEndPos - nodeStartPos); + + if (isCallToNew || isCallToDelete) { + result.line = node.range().start().line() + 1; + result.column = node.range().start().character() + 1; + result.length = isCallToNew ? 3 : 6; + insertResult(result); + if (node.arcanaContains("array")) { + const int openingBracketOffset = nodeText.indexOf('['); + if (openingBracketOffset == -1) + return; + const int closingBracketOffset = nodeText.lastIndexOf(']'); + if (closingBracketOffset == -1 || closingBracketOffset < openingBracketOffset) + return; + + result.textStyles.mainStyle = C_PUNCTUATION; + result.length = 1; + Utils::Text::convertPosition(m_doc, + nodeStartPos + openingBracketOffset, + &result.line, &result.column); + insertResult(result); + Utils::Text::convertPosition(m_doc, + nodeStartPos + closingBracketOffset, + &result.line, &result.column); + insertResult(result); + } + return; + } + + if (isExpression && (detail == QLatin1String("()") || detail == QLatin1String("[]"))) { + result.line = node.range().start().line() + 1; + result.column = node.range().start().character() + 1; + result.length = 1; + insertResult(result); + result.line = node.range().end().line() + 1; + result.column = node.range().end().character(); + insertResult(result); + return; + } + + const int opStringLen = detail.at(0).isSpace() ? detail.length() - 1 : detail.length(); + + // The simple case: Call to operator+, +=, * etc. + if (nodeEndPos - nodeStartPos == opStringLen) { + setResultPosFromRange(result, node.range()); + insertResult(result); + return; + } + + const int prefixOffset = nodeText.indexOf(operatorPrefix); + if (prefixOffset == -1) + return; + + const bool isArray = detail == "[]"; + const bool isCall = detail == "()"; + const bool isArrayNew = detail == " new[]"; + const bool isArrayDelete = detail == " delete[]"; + const QStringView searchTerm = isArray || isCall + ? QStringView(detail).chopped(1) : isArrayNew || isArrayDelete + ? QStringView(detail).chopped(2) : detail; + const int opStringOffset = nodeText.indexOf(searchTerm, prefixOffset + + operatorPrefix.length()); + if (opStringOffset == -1 || nodeText.indexOf(operatorPrefix, opStringOffset) != -1) + return; + + const int opStringOffsetInDoc = nodeStartPos + opStringOffset + + detail.length() - opStringLen; + Utils::Text::convertPosition(m_doc, opStringOffsetInDoc, &result.line, &result.column); + result.length = opStringLen; + if (isArray || isCall) + result.length = 1; + else if (isArrayNew || isArrayDelete) + result.length -= 2; + if (!isArray && !isCall) + insertResult(result); + if (!isArray && !isCall && !isArrayNew && !isArrayDelete) + return; + + result.textStyles.mainStyle = C_PUNCTUATION; + result.length = 1; + const int openingParenOffset = nodeText.indexOf( + isCall ? '(' : '[', prefixOffset + operatorPrefix.length()); + if (openingParenOffset == -1) + return; + const int closingParenOffset = nodeText.indexOf(isCall ? ')' : ']', openingParenOffset + 1); + if (closingParenOffset == -1 || closingParenOffset < openingParenOffset) + return; + Utils::Text::convertPosition(m_doc, nodeStartPos + openingParenOffset, + &result.line, &result.column); + insertResult(result); + Utils::Text::convertPosition(m_doc, nodeStartPos + closingParenOffset, + &result.line, &result.column); + insertResult(result); +} + +void ExtraHighlightingResultsCollector::visitNode(const AstNode &node) +{ + if (m_future.isCanceled()) + return; + collectFromNode(node); + const auto children = node.children(); + if (!children) + return; + for (const AstNode &childNode : *children) + visitNode(childNode); +} + } // namespace Internal } // namespace ClangCodeModel diff --git a/src/plugins/clangformat/CMakeLists.txt b/src/plugins/clangformat/CMakeLists.txt index eb6e0687e6..5db5f8111a 100644 --- a/src/plugins/clangformat/CMakeLists.txt +++ b/src/plugins/clangformat/CMakeLists.txt @@ -8,6 +8,7 @@ add_qtc_plugin(ClangFormat clangformatchecks.ui clangformatconfigwidget.cpp clangformatconfigwidget.h clangformatconfigwidget.ui clangformatconstants.h + clangformatfile.cpp clangformatfile.h clangformatindenter.cpp clangformatindenter.h clangformatplugin.cpp clangformatplugin.h clangformatsettings.cpp clangformatsettings.h diff --git a/src/plugins/clangformat/clangformat.pro b/src/plugins/clangformat/clangformat.pro index 4d751db2ba..da7645613c 100644 --- a/src/plugins/clangformat/clangformat.pro +++ b/src/plugins/clangformat/clangformat.pro @@ -15,6 +15,7 @@ unix:!macos:QMAKE_LFLAGS += -Wl,--exclude-libs,ALL SOURCES += \ clangformatconfigwidget.cpp \ + clangformatfile.cpp \ clangformatindenter.cpp \ clangformatplugin.cpp \ clangformatsettings.cpp \ @@ -22,6 +23,7 @@ SOURCES += \ HEADERS += \ clangformatconfigwidget.h \ + clangformatfile.h \ clangformatindenter.h \ clangformatplugin.h \ clangformatsettings.h \ diff --git a/src/plugins/clangformat/clangformat.qbs b/src/plugins/clangformat/clangformat.qbs index ef8ae44781..fcd77b7c1b 100644 --- a/src/plugins/clangformat/clangformat.qbs +++ b/src/plugins/clangformat/clangformat.qbs @@ -33,6 +33,8 @@ QtcPlugin { "clangformatconfigwidget.h", "clangformatconfigwidget.ui", "clangformatconstants.h", + "clangformatfile.cpp", + "clangformatfile.h", "clangformatindenter.cpp", "clangformatindenter.h", "clangformatplugin.cpp", diff --git a/src/plugins/clangformat/clangformatconfigwidget.cpp b/src/plugins/clangformat/clangformatconfigwidget.cpp index f0cc5d9700..0dc6d4c54f 100644 --- a/src/plugins/clangformat/clangformatconfigwidget.cpp +++ b/src/plugins/clangformat/clangformatconfigwidget.cpp @@ -27,6 +27,7 @@ #include "clangformatconstants.h" #include "clangformatindenter.h" +#include "clangformatfile.h" #include "clangformatsettings.h" #include "clangformatutils.h" #include "ui_clangformatchecks.h" @@ -121,6 +122,12 @@ ClangFormatConfigWidget::ClangFormatConfigWidget(ProjectExplorer::Project *proje { m_ui->setupUi(this); + Utils::FilePath filePath = Core::ICore::userResourcePath(); + if (m_project) + filePath = filePath / "clang-format/" / currentProjectUniqueId(); + filePath = filePath / QLatin1String(Constants::SETTINGS_FILE_NAME); + m_config = std::make_unique<ClangFormatFile>(filePath); + initChecksAndPreview(); if (m_project) { @@ -147,6 +154,8 @@ ClangFormatConfigWidget::ClangFormatConfigWidget(ProjectExplorer::Project *proje connectChecks(); } +ClangFormatConfigWidget::~ClangFormatConfigWidget() = default; + void ClangFormatConfigWidget::initChecksAndPreview() { m_checksScrollArea = new QScrollArea(); @@ -191,7 +200,7 @@ void ClangFormatConfigWidget::connectChecks() continue; } - auto button = qobject_cast<QPushButton *>(child); + const auto button = qobject_cast<QPushButton *>(child); if (button != nullptr) connect(button, &QPushButton::clicked, this, &ClangFormatConfigWidget::onTableChanged); } @@ -202,15 +211,7 @@ void ClangFormatConfigWidget::onTableChanged() if (m_disableTableUpdate) return; - const std::string newConfig = tableToString(sender()); - if (newConfig.empty()) - return; - const std::string oldConfig = m_project ? currentProjectConfigText() - : currentGlobalConfigText(); - saveConfig(newConfig); - fillTable(); - updatePreview(); - saveConfig(oldConfig); + saveChanges(sender()); } void ClangFormatConfigWidget::hideGlobalCheckboxes() @@ -379,16 +380,17 @@ void ClangFormatConfigWidget::fillTable() } } -std::string ClangFormatConfigWidget::tableToString(QObject *sender) +void ClangFormatConfigWidget::saveChanges(QObject *sender) { - std::stringstream content; - content << "---"; - if (sender->objectName() == "BasedOnStyle") { - auto *basedOnStyle = m_checksWidget->findChild<QComboBox *>("BasedOnStyle"); - content << "\nBasedOnStyle: " << basedOnStyle->currentText().toStdString() << '\n'; + const auto *basedOnStyle = m_checksWidget->findChild<QComboBox *>("BasedOnStyle"); + m_config->setBasedOnStyle(basedOnStyle->currentText()); } else { + QList<ClangFormatFile::Field> fields; + for (QObject *child : m_checksWidget->children()) { + if (child->objectName() == "BasedOnStyle") + continue; auto *label = qobject_cast<QLabel *>(child); if (!label) continue; @@ -396,7 +398,7 @@ std::string ClangFormatConfigWidget::tableToString(QObject *sender) QWidget *valueWidget = m_checksWidget->findChild<QWidget *>(label->text().trimmed()); if (!valueWidget) { // Currently BraceWrapping only. - content << '\n' << label->text().toStdString() << ":"; + fields.append({label->text(), ""}); continue; } @@ -410,46 +412,34 @@ std::string ClangFormatConfigWidget::tableToString(QObject *sender) if (plainText->toPlainText().trimmed().isEmpty()) continue; - content << '\n' << label->text().toStdString() << ":"; + + std::stringstream content; QStringList list = plainText->toPlainText().split('\n'); for (const QString &line : list) content << "\n " << line.toStdString(); + + fields.append({label->text(), QString::fromStdString(content.str())}); } else { - auto *comboBox = qobject_cast<QComboBox *>(valueWidget); - std::string text; - if (comboBox) { - text = comboBox->currentText().toStdString(); + QString text; + if (auto *comboBox = qobject_cast<QComboBox *>(valueWidget)) { + text = comboBox->currentText(); } else { auto *lineEdit = qobject_cast<QLineEdit *>(valueWidget); QTC_ASSERT(lineEdit, continue;); - text = lineEdit->text().toStdString(); + text = lineEdit->text(); } - if (!text.empty() && text != "Default") - content << '\n' << label->text().toStdString() << ": " << text; + if (!text.isEmpty() && text != "Default") + fields.append({label->text(), text}); } } - content << '\n'; + m_config->changeFields(fields); } - std::string text = content.str(); - clang::format::FormatStyle style; - style.Language = clang::format::FormatStyle::LK_Cpp; - const std::error_code error = clang::format::parseConfiguration(text, &style); - if (error.value() != static_cast<int>(clang::format::ParseError::Success)) { - QMessageBox::warning(this, - tr("Error in ClangFormat configuration"), - QString::fromStdString(error.message())); - fillTable(); - updatePreview(); - return std::string(); - } - - return text; + fillTable(); + updatePreview(); } -ClangFormatConfigWidget::~ClangFormatConfigWidget() = default; - void ClangFormatConfigWidget::apply() { ClangFormatSettings &settings = ClangFormatSettings::instance(); @@ -466,28 +456,7 @@ void ClangFormatConfigWidget::apply() if (!m_checksWidget->isVisible()) return; - const std::string config = tableToString(this); - if (config.empty()) - return; - - saveConfig(config); - fillTable(); - updatePreview(); -} - -void ClangFormatConfigWidget::saveConfig(const std::string &text) const -{ - QString filePath = Core::ICore::userResourcePath().toString(); - if (m_project) - filePath += "/clang-format/" + currentProjectUniqueId(); - filePath += "/" + QLatin1String(Constants::SETTINGS_FILE_NAME); - - QFile file(filePath); - if (!file.open(QFile::WriteOnly)) - return; - - file.write(text.c_str()); - file.close(); + saveChanges(this); } } // namespace ClangFormat diff --git a/src/plugins/clangformat/clangformatconfigwidget.h b/src/plugins/clangformat/clangformatconfigwidget.h index 869ddeccb7..4c261f9992 100644 --- a/src/plugins/clangformat/clangformatconfigwidget.h +++ b/src/plugins/clangformat/clangformatconfigwidget.h @@ -44,6 +44,7 @@ namespace Ui { class ClangFormatConfigWidget; class ClangFormatChecksWidget; } +class ClangFormatFile; class ClangFormatConfigWidget : public TextEditor::CodeStyleEditorWidget { @@ -66,18 +67,17 @@ private: void connectChecks(); void fillTable(); - std::string tableToString(QObject *sender); + void saveChanges(QObject *sender); void hideGlobalCheckboxes(); void showGlobalCheckboxes(); - - void saveConfig(const std::string &text) const; void updatePreview(); ProjectExplorer::Project *m_project; QWidget *m_checksWidget; QScrollArea *m_checksScrollArea; TextEditor::SnippetEditorWidget *m_preview; + std::unique_ptr<ClangFormatFile> m_config; std::unique_ptr<Ui::ClangFormatChecksWidget> m_checks; std::unique_ptr<Ui::ClangFormatConfigWidget> m_ui; bool m_disableTableUpdate = false; diff --git a/src/plugins/clangformat/clangformatfile.cpp b/src/plugins/clangformat/clangformatfile.cpp new file mode 100644 index 0000000000..8e532ea797 --- /dev/null +++ b/src/plugins/clangformat/clangformatfile.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#include "clangformatfile.h" +#include <sstream> + +using namespace ClangFormat; + +ClangFormatFile::ClangFormatFile(Utils::FilePath filePath) + : m_filePath(filePath) +{ + if (!m_filePath.exists()) { + resetStyleToLLVM(); + return; + } + + m_style.Language = clang::format::FormatStyle::LK_Cpp; + const std::error_code error + = clang::format::parseConfiguration(m_filePath.fileContents().toStdString(), &m_style); + if (error.value() != static_cast<int>(clang::format::ParseError::Success)) { + resetStyleToLLVM(); + } +} + +clang::format::FormatStyle ClangFormatFile::format() { + return m_style; +} + +Utils::FilePath ClangFormatFile::filePath() +{ + return m_filePath; +} + +void ClangFormatFile::setStyle(clang::format::FormatStyle style) +{ + m_style = style; + saveNewFormat(); +} + +void ClangFormatFile::resetStyleToLLVM() +{ + m_style = clang::format::getLLVMStyle(); + saveNewFormat(); +} + +void ClangFormatFile::setBasedOnStyle(QString styleName) +{ + changeField({"BasedOnStyle", styleName}); + saveNewFormat(); +} + +QString ClangFormatFile::setStyle(QString style) +{ + const std::error_code error = clang::format::parseConfiguration(style.toStdString(), &m_style); + if (error.value() != static_cast<int>(clang::format::ParseError::Success)) { + return QString::fromStdString(error.message()); + } + + saveNewFormat(style.toUtf8()); + return ""; +} + +QString ClangFormatFile::changeField(Field field) +{ + return changeFields({field}); +} + +QString ClangFormatFile::changeFields(QList<Field> fields) +{ + std::stringstream content; + content << "---" << "\n"; + + for (const auto &field : fields) { + content << field.first.toStdString() << ": " << field.second.toStdString() << "\n"; + } + + return setStyle(QString::fromStdString(content.str())); +} + +void ClangFormatFile::saveNewFormat() +{ + std::string style = clang::format::configurationAsText(m_style); + + // workaround: configurationAsText() add comment "# " before BasedOnStyle line + const int pos = style.find("# BasedOnStyle"); + if (pos < style.size()) + style.erase(pos, 2); + m_filePath.writeFileContents(QByteArray::fromStdString(style)); +} + +void ClangFormatFile::saveNewFormat(QByteArray style) +{ + m_filePath.writeFileContents(style); +} diff --git a/src/plugins/clangformat/clangformatfile.h b/src/plugins/clangformat/clangformatfile.h new file mode 100644 index 0000000000..3fe9010f44 --- /dev/null +++ b/src/plugins/clangformat/clangformatfile.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#pragma once + +#include "utils/filepath.h" +#include <clang/Format/Format.h> + +namespace ClangFormat { + +class ClangFormatFile +{ +public: + explicit ClangFormatFile(Utils::FilePath file); + clang::format::FormatStyle format(); + + Utils::FilePath filePath(); + void resetStyleToLLVM(); + void setBasedOnStyle(QString styleName); + void setStyle(clang::format::FormatStyle style); + QString setStyle(QString style); + void clearBasedOnStyle(); + + using Field = std::pair<QString, QString>; + QString changeFields(QList<Field> fields); + QString changeField(Field field); + +private: + void saveNewFormat(); + void saveNewFormat(QByteArray style); + +private: + Utils::FilePath m_filePath; + clang::format::FormatStyle m_style; +}; + + +} // namespace ClangFormat diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp index 5371aae25a..f43decd940 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp @@ -936,7 +936,7 @@ CMakeBuildConfiguration::CMakeBuildConfiguration(Target *target, Id id) initialArgs.append("-DCMAKE_TOOLCHAIN_FILE:PATH=" + ndkLocation.pathAppended("build/cmake/android.toolchain.cmake").path()); - auto androidAbis = bs->data(Android::Constants::AndroidABIs).toStringList(); + auto androidAbis = bs->data(Android::Constants::AndroidMkSpecAbis).toStringList(); QString preferredAbi; if (androidAbis.contains(ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A)) { preferredAbi = ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A; diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp index 6c84e3d872..86d97babfe 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp @@ -128,20 +128,26 @@ QVariant CMakeTargetNode::data(Utils::Id role) const return {}; }; + if (role == Android::Constants::AndroidAbi) + return value(Android::Constants::ANDROID_ABI); + + if (role == Android::Constants::AndroidAbis) + return value(Android::Constants::ANDROID_ABIS); + + // TODO: Concerns the variables below. Qt 6 uses target properties which cannot be read + // by the current mechanism, and the variables start with "Qt_" prefix. + if (role == Android::Constants::AndroidPackageSourceDir) return value(Android::Constants::ANDROID_PACKAGE_SOURCE_DIR); - if (role == Android::Constants::AndroidDeploySettingsFile) - return value(Android::Constants::ANDROID_DEPLOYMENT_SETTINGS_FILE); - if (role == Android::Constants::AndroidExtraLibs) return value(Android::Constants::ANDROID_EXTRA_LIBS); - if (role == Android::Constants::ANDROID_APPLICATION_ARGUMENTS) - return value(Android::Constants::QT_ANDROID_APPLICATION_ARGUMENTS); + if (role == Android::Constants::AndroidDeploySettingsFile) + return value(Android::Constants::ANDROID_DEPLOYMENT_SETTINGS_FILE); - if (role == Android::Constants::AndroidArch) - return value(Android::Constants::ANDROID_ABI); + if (role == Android::Constants::AndroidApplicationArgs) + return value(Android::Constants::ANDROID_APPLICATION_ARGUMENTS); if (role == Android::Constants::ANDROID_ABIS) return value(Android::Constants::ANDROID_ABIS); @@ -152,6 +158,9 @@ QVariant CMakeTargetNode::data(Utils::Id role) const if (role == Android::Constants::AndroidTargets) return values("TARGETS_BUILD_PATH"); + if (role == Android::Constants::AndroidApk) + return {}; + if (role == Ios::Constants::IosTarget) { // For some reason the artifact is e.g. "Debug/untitled.app/untitled" which is wrong. // It actually is e.g. "Debug-iphonesimulator/untitled.app/untitled". diff --git a/src/plugins/cmakeprojectmanager/cmaketool.cpp b/src/plugins/cmakeprojectmanager/cmaketool.cpp index 51daa1bb5f..d407adeb66 100644 --- a/src/plugins/cmakeprojectmanager/cmaketool.cpp +++ b/src/plugins/cmakeprojectmanager/cmaketool.cpp @@ -360,7 +360,7 @@ Utils::optional<CMakeTool::ReaderType> CMakeTool::readerType() const FilePath CMakeTool::searchQchFile(const FilePath &executable) { - if (executable.isEmpty()) + if (executable.isEmpty() || executable.needsDevice()) // do not register docs from devices return {}; FilePath prefixDir = executable.parentDir().parentDir(); diff --git a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp index d893370028..cabac0b3b9 100644 --- a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp +++ b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp @@ -322,8 +322,14 @@ static QStringList splitFragments(const QStringList &fragments) return result; } +bool isPchFile(const FilePath &buildDirectory, const FilePath &path) +{ + return path.isChildOf(buildDirectory) && path.fileName().startsWith("cmake_pch"); +} + RawProjectParts generateRawProjectParts(const PreprocessedData &input, - const FilePath &sourceDirectory) + const FilePath &sourceDirectory, + const FilePath &buildDirectory) { RawProjectParts rpps; @@ -331,8 +337,12 @@ RawProjectParts generateRawProjectParts(const PreprocessedData &input, for (const TargetDetails &t : input.targetDetails) { QDir sourceDir(sourceDirectory.toString()); + // Do not tread generated files and CMake precompiled headers as project files + const auto sourceFiles = Utils::filtered(t.sources, [buildDirectory](const SourceInfo &si) { + return !si.isGenerated && !isPchFile(buildDirectory, FilePath::fromString(si.path)); + }); CppEditor::ProjectFileCategorizer - categorizer({}, transform<QList>(t.sources, [&sourceDir](const SourceInfo &si) { + categorizer({}, transform<QList>(sourceFiles, [&sourceDir](const SourceInfo &si) { return sourceDir.absoluteFilePath(si.path); })); @@ -531,6 +541,11 @@ void addCompileGroups(ProjectNode *targetRoot, auto node = std::make_unique<FileNode>(sourcePath, Node::fileTypeForFileName(sourcePath)); node->setIsGenerated(si.isGenerated); + // CMake pch files are generated at configured time, but not marked as generated + // so that a "clean" step won't remove them and at a subsequent build they won't exist. + if (isPchFile(buildDirectory, sourcePath)) + node->setIsGenerated(true); + // Where does the file node need to go? if (sourcePath.isChildOf(buildDirectory) && !inSourceBuild) { buildFileNodes.emplace_back(std::move(node)); @@ -638,9 +653,16 @@ std::unique_ptr<CMakeProjectNode> generateRootProjectNode( void setupLocationInfoForTargets(CMakeProjectNode *rootNode, const QList<CMakeBuildTarget> &targets) { + const QSet<QString> titles = Utils::transform<QSet>(targets, &CMakeBuildTarget::title); + QHash<QString, FolderNode *> buildKeyToNode; + rootNode->forEachGenericNode([&buildKeyToNode, &titles](Node *node) { + FolderNode *folderNode = node->asFolderNode(); + const QString &buildKey = node->buildKey(); + if (folderNode && titles.contains(buildKey)) + buildKeyToNode.insert(buildKey, folderNode); + }); for (const CMakeBuildTarget &t : targets) { - FolderNode *folderNode = static_cast<FolderNode *>( - rootNode->findNode(Utils::equal(&Node::buildKey, t.title))); + FolderNode *folderNode = buildKeyToNode.value(t.title); if (folderNode) { QSet<std::pair<FilePath, int>> locations; auto dedup = [&locations](const Backtrace &bt) { @@ -705,7 +727,7 @@ FileApiQtcData extractData(FileApiData &input, result.buildTargets = generateBuildTargets(data, sourceDirectory, buildDirectory, haveLibrariesRelativeToBuildDirectory); result.cmakeFiles = std::move(data.cmakeFiles); - result.projectParts = generateRawProjectParts(data, sourceDirectory); + result.projectParts = generateRawProjectParts(data, sourceDirectory, buildDirectory); auto rootProjectNode = generateRootProjectNode(data, sourceDirectory, buildDirectory); ProjectTree::applyTreeManager(rootProjectNode.get()); // QRC nodes diff --git a/src/plugins/coreplugin/dialogs/newdialogwidget.cpp b/src/plugins/coreplugin/dialogs/newdialogwidget.cpp index f8a77c4e8d..66569b85cf 100644 --- a/src/plugins/coreplugin/dialogs/newdialogwidget.cpp +++ b/src/plugins/coreplugin/dialogs/newdialogwidget.cpp @@ -32,7 +32,6 @@ #include <utils/qtcassert.h> #include <utils/utilsicons.h> -#include <QAbstractProxyModel> #include <QDebug> #include <QItemDelegate> #include <QKeyEvent> diff --git a/src/plugins/coreplugin/locator/filesystemfilter.cpp b/src/plugins/coreplugin/locator/filesystemfilter.cpp index 95c9bf16ea..9e0751e570 100644 --- a/src/plugins/coreplugin/locator/filesystemfilter.cpp +++ b/src/plugins/coreplugin/locator/filesystemfilter.cpp @@ -176,8 +176,9 @@ void FileSystemFilter::accept(LocatorFilterEntry selection, { Q_UNUSED(selectionLength) if (selection.filePath.isDir()) { - const QString value = shortcutString() + ' ' - + selection.filePath.absoluteFilePath().toUserOutput() + '/'; + const QString value + = shortcutString() + ' ' + + selection.filePath.absoluteFilePath().cleanPath().pathAppended("/").toUserOutput(); *newText = value; *selectionStart = value.length(); } else { diff --git a/src/plugins/coreplugin/manhattanstyle.cpp b/src/plugins/coreplugin/manhattanstyle.cpp index 9a8934b989..50837d9f15 100644 --- a/src/plugins/coreplugin/manhattanstyle.cpp +++ b/src/plugins/coreplugin/manhattanstyle.cpp @@ -73,14 +73,20 @@ bool styleEnabled(const QWidget *widget) return true; } +static bool isInDialogOrPopup(const QWidget *widget) +{ + // Do not style dialogs or explicitly ignored widgets + const Qt::WindowType windowType = widget->window()->windowType(); + return (windowType == Qt::Dialog || windowType == Qt::Popup); +} + // Consider making this a QStyle state bool panelWidget(const QWidget *widget) { if (!widget) return false; - // Do not style dialogs or explicitly ignored widgets - if ((widget->window()->windowFlags() & Qt::WindowType_Mask) == Qt::Dialog) + if (isInDialogOrPopup(widget)) return false; if (qobject_cast<const FancyMainWindow *>(widget)) @@ -107,8 +113,7 @@ bool lightColored(const QWidget *widget) if (!widget) return false; - // Don't style dialogs or explicitly ignored widgets - if ((widget->window()->windowFlags() & Qt::WindowType_Mask) == Qt::Dialog) + if (isInDialogOrPopup(widget)) return false; const QWidget *p = widget; diff --git a/src/plugins/cppcheck/cppcheckoptions.cpp b/src/plugins/cppcheck/cppcheckoptions.cpp index 735557daf2..d6c7b0196c 100644 --- a/src/plugins/cppcheck/cppcheckoptions.cpp +++ b/src/plugins/cppcheck/cppcheckoptions.cpp @@ -39,9 +39,10 @@ #include <debugger/analyzer/analyzericons.h> #include <QCheckBox> -#include <QDir> #include <QFormLayout> +using namespace Utils; + namespace Cppcheck { namespace Internal { @@ -102,7 +103,7 @@ OptionsWidget::OptionsWidget(QWidget *parent) void OptionsWidget::load(const CppcheckOptions &options) { - m_binary->setPath(options.binary); + m_binary->setFilePath(options.binary); m_customArguments->setText(options.customArguments); m_ignorePatterns->setText(options.ignoredPatterns); m_warning->setChecked(options.warning); @@ -121,7 +122,7 @@ void OptionsWidget::load(const CppcheckOptions &options) void OptionsWidget::save(CppcheckOptions &options) const { - options.binary = m_binary->filePath().toString(); + options.binary = m_binary->filePath(); options.customArguments = m_customArguments->text(); options.ignoredPatterns = m_ignorePatterns->text(); options.warning = m_warning->isChecked(); @@ -149,14 +150,13 @@ CppcheckOptionsPage::CppcheckOptionsPage(CppcheckTool &tool, CppcheckTrigger &tr setCategoryIconPath(Analyzer::Icons::SETTINGSCATEGORY_ANALYZER); CppcheckOptions options; - if (Utils::HostOsInfo::isAnyUnixHost()) { + if (HostOsInfo::isAnyUnixHost()) { options.binary = "cppcheck"; } else { - QString programFiles = QDir::fromNativeSeparators( - QString::fromLocal8Bit(qgetenv("PROGRAMFILES"))); + FilePath programFiles = FilePath::fromUserInput(qEnvironmentVariable("PROGRAMFILES")); if (programFiles.isEmpty()) programFiles = "C:/Program Files"; - options.binary = programFiles + "/Cppcheck/cppcheck.exe"; + options.binary = programFiles / "Cppcheck/cppcheck.exe"; } load(options); @@ -190,7 +190,7 @@ void CppcheckOptionsPage::save(const CppcheckOptions &options) const QSettings *s = Core::ICore::settings(); QTC_ASSERT(s, return); s->beginGroup(Constants::SETTINGS_ID); - s->setValue(Constants::SETTINGS_BINARY, options.binary); + s->setValue(Constants::SETTINGS_BINARY, options.binary.toString()); s->setValue(Constants::SETTINGS_CUSTOM_ARGUMENTS, options.customArguments); s->setValue(Constants::SETTINGS_IGNORE_PATTERNS, options.ignoredPatterns); s->setValue(Constants::SETTINGS_WARNING, options.warning); @@ -213,8 +213,8 @@ void CppcheckOptionsPage::load(CppcheckOptions &options) const QSettings *s = Core::ICore::settings(); QTC_ASSERT(s, return); s->beginGroup(Constants::SETTINGS_ID); - options.binary = s->value(Constants::SETTINGS_BINARY, - options.binary).toString(); + options.binary = FilePath::fromString(s->value(Constants::SETTINGS_BINARY, + options.binary.toString()).toString()); options.customArguments = s->value(Constants::SETTINGS_CUSTOM_ARGUMENTS, options.customArguments).toString(); options.ignoredPatterns = s->value(Constants::SETTINGS_IGNORE_PATTERNS, diff --git a/src/plugins/cppcheck/cppcheckoptions.h b/src/plugins/cppcheck/cppcheckoptions.h index 639ffca6c9..4d4ca83cb6 100644 --- a/src/plugins/cppcheck/cppcheckoptions.h +++ b/src/plugins/cppcheck/cppcheckoptions.h @@ -26,6 +26,7 @@ #pragma once #include <coreplugin/dialogs/ioptionspage.h> +#include <utils/filepath.h> #include <QCoreApplication> #include <QPointer> @@ -36,9 +37,7 @@ class QLineEdit; class QCheckBox; QT_END_NAMESPACE -namespace Utils { -class PathChooser; -} +namespace Utils { class PathChooser; } namespace Cppcheck { namespace Internal { @@ -50,7 +49,7 @@ class OptionsWidget; class CppcheckOptions final { public: - QString binary; + Utils::FilePath binary; bool warning = true; bool style = true; diff --git a/src/plugins/cppcheck/cppcheckrunner.cpp b/src/plugins/cppcheck/cppcheckrunner.cpp index 2f6cffb1eb..261ccb3b63 100644 --- a/src/plugins/cppcheck/cppcheckrunner.cpp +++ b/src/plugins/cppcheck/cppcheckrunner.cpp @@ -74,7 +74,7 @@ CppcheckRunner::~CppcheckRunner() m_queueTimer.stop(); } -void CppcheckRunner::reconfigure(const QString &binary, const QString &arguments) +void CppcheckRunner::reconfigure(const FilePath &binary, const QString &arguments) { m_binary = binary; m_arguments = arguments; @@ -157,7 +157,7 @@ void CppcheckRunner::checkQueued() else m_queue.begin().value() = files; - m_process->setCommand(CommandLine(FilePath::fromString(m_binary), arguments, CommandLine::Raw)); + m_process->setCommand(CommandLine(m_binary, arguments, CommandLine::Raw)); m_process->start(); } diff --git a/src/plugins/cppcheck/cppcheckrunner.h b/src/plugins/cppcheck/cppcheckrunner.h index 83aca95192..d49a9016de 100644 --- a/src/plugins/cppcheck/cppcheckrunner.h +++ b/src/plugins/cppcheck/cppcheckrunner.h @@ -45,7 +45,7 @@ public: explicit CppcheckRunner(CppcheckTool &tool); ~CppcheckRunner() override; - void reconfigure(const QString &binary, const QString &arguments); + void reconfigure(const Utils::FilePath &binary, const QString &arguments); void addToQueue(const Utils::FilePaths &files, const QString &additionalArguments = {}); void removeFromQueue(const Utils::FilePaths &files); @@ -63,7 +63,7 @@ private: CppcheckTool &m_tool; Utils::QtcProcess *m_process = nullptr; - QString m_binary; + Utils::FilePath m_binary; QString m_arguments; QHash<QString, Utils::FilePaths> m_queue; Utils::FilePaths m_currentFiles; diff --git a/src/plugins/cppeditor/CppEditor.json.in b/src/plugins/cppeditor/CppEditor.json.in index 437e85c35f..b3c0149291 100644 --- a/src/plugins/cppeditor/CppEditor.json.in +++ b/src/plugins/cppeditor/CppEditor.json.in @@ -61,7 +61,7 @@ \" <!-- Find include guards of header files without extension, for\", \" example, STL ones like <string>. Those can have a big initial\", \" comment exceeding 1000 chars, though. -->\", - \" <magic priority=\'50\'>\", + \" <magic priority=\'40\'>\", \" <match value=\'#ifndef \' type=\'string\' offset=\'0:2000\'/>\", \" <match value=\'#if \' type=\'string\' offset=\'0:2000\'/>\", \" <match value=\'#include \' type=\'string\' offset=\'0:2000\'/>\", diff --git a/src/plugins/cppeditor/semantichighlighter.cpp b/src/plugins/cppeditor/semantichighlighter.cpp index b4c1a6ff1f..f3112a725b 100644 --- a/src/plugins/cppeditor/semantichighlighter.cpp +++ b/src/plugins/cppeditor/semantichighlighter.cpp @@ -34,6 +34,7 @@ #include <utils/algorithm.h> #include <utils/qtcassert.h> +#include <QElapsedTimer> #include <QLoggingCategory> #include <QTextDocument> @@ -165,6 +166,8 @@ void SemanticHighlighter::onHighlighterResultAvailable(int from, int to) return; // aborted qCDebug(log) << "onHighlighterResultAvailable()" << from << to; + QElapsedTimer t; + t.start(); SyntaxHighlighter *highlighter = m_baseTextDocument->syntaxHighlighter(); QTC_ASSERT(highlighter, return); @@ -234,11 +237,17 @@ void SemanticHighlighter::onHighlighterResultAvailable(int from, int to) } if (parentheses.first.isValid()) TextDocumentLayout::setParentheses(parentheses.first, parentheses.second); + + qCDebug(log) << "onHighlighterResultAvailable() took" << t.elapsed() << "ms"; } void SemanticHighlighter::onHighlighterFinished() { QTC_ASSERT(m_watcher, return); + + QElapsedTimer t; + t.start(); + if (!m_watcher->isCanceled() && documentRevision() == m_revision) { SyntaxHighlighter *highlighter = m_baseTextDocument->syntaxHighlighter(); if (QTC_GUARD(highlighter)) { @@ -273,6 +282,7 @@ void SemanticHighlighter::onHighlighterFinished() } m_watcher.reset(); + qCDebug(log) << "onHighlighterFinished() took" << t.elapsed() << "ms"; } void SemanticHighlighter::connectWatcher() diff --git a/src/plugins/debugger/shared/cdbsymbolpathlisteditor.cpp b/src/plugins/debugger/shared/cdbsymbolpathlisteditor.cpp index 2e67bf675e..18fcdce5dc 100644 --- a/src/plugins/debugger/shared/cdbsymbolpathlisteditor.cpp +++ b/src/plugins/debugger/shared/cdbsymbolpathlisteditor.cpp @@ -35,7 +35,6 @@ #include "symbolpathsdialog.h" #include <QCheckBox> -#include <QDir> #include <QDebug> #include <QAction> #include <QFormLayout> @@ -165,7 +164,7 @@ void CdbSymbolPathListEditor::addSymbolPath(CdbSymbolPathListEditor::SymbolPathM { FilePath cacheDir; if (promptCacheDirectory(this, &cacheDir)) - insertPathAtCursor(CdbSymbolPathListEditor::symbolPath(cacheDir.path(), mode)); + insertPathAtCursor(CdbSymbolPathListEditor::symbolPath(cacheDir, mode)); } void CdbSymbolPathListEditor::setupSymbolPaths() @@ -174,13 +173,13 @@ void CdbSymbolPathListEditor::setupSymbolPaths() const int indexOfSymbolServer = indexOfSymbolPath(currentPaths, SymbolServerPath); const int indexOfSymbolCache = indexOfSymbolPath(currentPaths, SymbolCachePath); - QString path; + FilePath path; if (indexOfSymbolServer != -1) - path = currentPaths.at(indexOfSymbolServer); + path = FilePath::fromString(currentPaths.at(indexOfSymbolServer)); if (path.isEmpty() && indexOfSymbolCache != -1) - path = currentPaths.at(indexOfSymbolCache); + path = FilePath::fromString(currentPaths.at(indexOfSymbolCache)); if (path.isEmpty()) - path = TemporaryDirectory::masterDirectoryPath() + "/symbolcache"; + path = FilePath::fromString(TemporaryDirectory::masterDirectoryPath() + "/symbolcache"); bool useSymbolServer = true; bool useSymbolCache = true; @@ -193,20 +192,20 @@ void CdbSymbolPathListEditor::setupSymbolPaths() if (useSymbolCache) { insertPathAtCursor(CdbSymbolPathListEditor::symbolPath(path, SymbolCachePath)); if (useSymbolServer) - insertPathAtCursor(CdbSymbolPathListEditor::symbolPath(QString(), SymbolServerPath)); + insertPathAtCursor(CdbSymbolPathListEditor::symbolPath({}, SymbolServerPath)); } else if (useSymbolServer) { insertPathAtCursor(CdbSymbolPathListEditor::symbolPath(path, SymbolServerPath)); } } -QString CdbSymbolPathListEditor::symbolPath(const QString &cacheDir, +QString CdbSymbolPathListEditor::symbolPath(const FilePath &cacheDir, CdbSymbolPathListEditor::SymbolPathMode mode) { if (mode == SymbolCachePath) - return symbolCachePrefixC + QDir::toNativeSeparators(cacheDir); + return symbolCachePrefixC + cacheDir.toUserOutput(); QString s = QLatin1String(symbolServerPrefixC); if (!cacheDir.isEmpty()) - s += QDir::toNativeSeparators(cacheDir) + '*'; + s += cacheDir.toUserOutput() + '*'; s += QLatin1String(symbolServerPostfixC); return s; } diff --git a/src/plugins/debugger/shared/cdbsymbolpathlisteditor.h b/src/plugins/debugger/shared/cdbsymbolpathlisteditor.h index df64b35bfe..f7dffcdda0 100644 --- a/src/plugins/debugger/shared/cdbsymbolpathlisteditor.h +++ b/src/plugins/debugger/shared/cdbsymbolpathlisteditor.h @@ -53,7 +53,7 @@ public: static bool promptCacheDirectory(QWidget *parent, Utils::FilePath *cacheDirectory); // Format a symbol path specification - static QString symbolPath(const QString &cacheDir, SymbolPathMode mode); + static QString symbolPath(const Utils::FilePath &cacheDir, SymbolPathMode mode); // Check for a symbol server path and extract local cache directory static bool isSymbolServerPath(const QString &path, QString *cacheDir = nullptr); // Check for a symbol cache path and extract local cache directory diff --git a/src/plugins/debugger/shared/symbolpathsdialog.cpp b/src/plugins/debugger/shared/symbolpathsdialog.cpp index db0354cca4..be8ef21afe 100644 --- a/src/plugins/debugger/shared/symbolpathsdialog.cpp +++ b/src/plugins/debugger/shared/symbolpathsdialog.cpp @@ -26,10 +26,14 @@ #include "symbolpathsdialog.h" #include "ui_symbolpathsdialog.h" +#include <utils/filepath.h> + #include <QMessageBox> -using namespace Debugger; -using namespace Internal; +using namespace Utils; + +namespace Debugger { +namespace Internal { SymbolPathsDialog::SymbolPathsDialog(QWidget *parent) : QDialog(parent), @@ -54,9 +58,9 @@ bool SymbolPathsDialog::useSymbolServer() const return ui->useSymbolServer->isChecked(); } -QString SymbolPathsDialog::path() const +FilePath SymbolPathsDialog::path() const { - return ui->pathChooser->filePath().toString(); + return ui->pathChooser->filePath(); } void SymbolPathsDialog::setUseSymbolCache(bool useSymbolCache) @@ -69,13 +73,13 @@ void SymbolPathsDialog::setUseSymbolServer(bool useSymbolServer) ui->useSymbolServer->setChecked(useSymbolServer); } -void SymbolPathsDialog::setPath(const QString &path) +void SymbolPathsDialog::setPath(const FilePath &path) { - ui->pathChooser->setPath(path); + ui->pathChooser->setFilePath(path); } bool SymbolPathsDialog::useCommonSymbolPaths(bool &useSymbolCache, bool &useSymbolServer, - QString &path) + FilePath &path) { SymbolPathsDialog dialog; dialog.setUseSymbolCache(useSymbolCache); @@ -87,3 +91,6 @@ bool SymbolPathsDialog::useCommonSymbolPaths(bool &useSymbolCache, bool &useSymb path = dialog.path(); return ret == QDialog::Accepted; } + +} // Internal +} // Debugger diff --git a/src/plugins/debugger/shared/symbolpathsdialog.h b/src/plugins/debugger/shared/symbolpathsdialog.h index c3534fbcf6..c77e112ea4 100644 --- a/src/plugins/debugger/shared/symbolpathsdialog.h +++ b/src/plugins/debugger/shared/symbolpathsdialog.h @@ -28,6 +28,8 @@ #include <QDialog> #include <QString> +namespace Utils { class FilePath; } + namespace Debugger { namespace Internal { @@ -43,15 +45,15 @@ public: bool useSymbolCache() const; bool useSymbolServer() const; - QString path() const; + Utils::FilePath path() const; bool doNotAskAgain() const; void setUseSymbolCache(bool useSymbolCache); void setUseSymbolServer(bool useSymbolServer); - void setPath(const QString &path); + void setPath(const Utils::FilePath &path); void setDoNotAskAgain(bool doNotAskAgain) const; - static bool useCommonSymbolPaths(bool &useSymbolCache, bool &useSymbolServer, QString &path); + static bool useCommonSymbolPaths(bool &useSymbolCache, bool &useSymbolServer, Utils::FilePath &path); private: Ui::SymbolPathsDialog *ui; diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 624f3eab49..638fb91db0 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -49,17 +49,18 @@ #include <utils/algorithm.h> #include <utils/basetreeview.h> #include <utils/environment.h> +#include <utils/fileutils.h> #include <utils/hostosinfo.h> -#include <utils/utilsicons.h> #include <utils/layoutbuilder.h> #include <utils/overridecursor.h> +#include <utils/pathlisteditor.h> #include <utils/port.h> #include <utils/qtcassert.h> #include <utils/qtcprocess.h> #include <utils/stringutils.h> #include <utils/temporaryfile.h> #include <utils/treemodel.h> -#include <utils/fileutils.h> +#include <utils/utilsicons.h> #include <QApplication> #include <QCheckBox> @@ -75,8 +76,8 @@ #include <QRandomGenerator> #include <QRegularExpression> #include <QTextBrowser> -#include <QToolButton> #include <QThread> +#include <QToolButton> #include <numeric> @@ -394,15 +395,13 @@ public: dockerDevice->tryCreateLocalFileAccess(); }); - m_pathsLineEdit = new QLineEdit; - m_pathsLineEdit->setText(data.repo); - m_pathsLineEdit->setToolTip(tr("Paths in this semi-colon separated list will be " - "mapped one-to-one into the Docker container.")); - m_pathsLineEdit->setText(data.mounts.join(';')); - m_pathsLineEdit->setPlaceholderText(tr("List project source directories here")); + m_pathsListEdit = new PathListEditor; + m_pathsListEdit->setToolTip(tr("Paths in this list will be mapped one-to-one into the " + "Docker container.")); + m_pathsListEdit->setPathList(data.mounts); - connect(m_pathsLineEdit, &QLineEdit::textChanged, this, [dockerDevice](const QString &text) { - dockerDevice->setMounts(text.split(';', Qt::SkipEmptyParts)); + connect(m_pathsListEdit, &PathListEditor::changed, this, [dockerDevice, this]() { + dockerDevice->setMounts(m_pathsListEdit->pathList()); }); auto logView = new QTextBrowser; @@ -443,7 +442,10 @@ public: daemonStateLabel, m_daemonReset, m_daemonState, Break(), m_runAsOutsideUser, Break(), m_usePathMapping, Break(), - tr("Paths to mount:"), m_pathsLineEdit, Break(), + Column { + new QLabel(tr("Paths to mount:")), + m_pathsListEdit, + }, Break(), Column { Space(20), Row { autoDetectButton, undoAutoDetectButton, listAutoDetectedButton, Stretch() }, @@ -463,7 +465,7 @@ private: QLabel *m_daemonState; QCheckBox *m_runAsOutsideUser; QCheckBox *m_usePathMapping; - QLineEdit *m_pathsLineEdit; + Utils::PathListEditor *m_pathsListEdit; KitDetector m_kitItemDetector; }; @@ -813,34 +815,51 @@ static QString getLocalIPv4Address() void DockerDevicePrivate::startContainer() { - QString tempFileName; - - { - TemporaryFile temp("qtc-docker-XXXXXX"); - temp.open(); - tempFileName = temp.fileName(); - } - const QString display = HostOsInfo::isWindowsHost() ? QString(getLocalIPv4Address() + ":0.0") : QString(":0"); - CommandLine dockerRun{"docker", {"run", "-i", "--cidfile=" + tempFileName, - "--rm", - "-e", QString("DISPLAY=%1").arg(display), - "-e", "XAUTHORITY=/.Xauthority", - "--net", "host"}}; + CommandLine dockerCreate{"docker", {"create", + "-i", + "--rm", + "-e", QString("DISPLAY=%1").arg(display), + "-e", "XAUTHORITY=/.Xauthority", + "--net", "host"}}; #ifdef Q_OS_UNIX + // no getuid() and getgid() on Windows. if (m_data.useLocalUidGid) - dockerRun.addArgs({"-u", QString("%1:%2").arg(getuid()).arg(getgid())}); + dockerCreate.addArgs({"-u", QString("%1:%2").arg(getuid()).arg(getgid())}); #endif - for (const QString &mount : qAsConst(m_data.mounts)) { - if (!mount.isEmpty()) - dockerRun.addArgs({"-v", mount + ':' + mount}); + for (QString mount : qAsConst(m_data.mounts)) { + if (mount.isEmpty()) + continue; + // make sure to convert windows style paths to unix style paths with the file system case: + // C:/dev/src -> /c/dev/src + if (const FilePath mountPath = FilePath::fromUserInput(mount).normalizedPathName(); + mountPath.startsWithDriveLetter()) { + const QChar lowerDriveLetter = mountPath.path().at(0).toLower(); + const FilePath path = FilePath::fromUserInput(mountPath.path().mid(2)); // strip C: + mount = '/' + lowerDriveLetter + path.path(); + } + dockerCreate.addArgs({"-v", mount + ':' + mount}); } - dockerRun.addArgs({"--entrypoint", "/bin/sh", m_data.imageId}); + dockerCreate.addArgs({"--entrypoint", "/bin/sh", m_data.imageId}); + + LOG("RUNNING: " << dockerCreate.toUserOutput()); + QtcProcess createProcess; + createProcess.setCommand(dockerCreate); + createProcess.runBlocking(); + + if (createProcess.result() != QtcProcess::FinishedWithSuccess) + return; + + m_container = createProcess.stdOut().trimmed(); + if (m_container.isEmpty()) + return; + LOG("Container via process: " << m_container); + CommandLine dockerRun{"docker", {"container" , "start", "-i", "-a", m_container}}; LOG("RUNNING: " << dockerRun.toUserOutput()); QPointer<QtcProcess> shell = new QtcProcess(ProcessMode::Writer); connect(shell, &QtcProcess::finished, this, [this, shell] { @@ -875,23 +894,6 @@ void DockerDevicePrivate::startContainer() return; } - LOG("CHECKING: " << tempFileName); - for (int i = 0; i <= 20; ++i) { - QFile file(tempFileName); - if (file.open(QIODevice::ReadOnly)) { - m_container = QString::fromUtf8(file.readAll()).trimmed(); - if (!m_container.isEmpty()) { - LOG("Container: " << m_container); - break; - } - } - if (i == 20 || !DockerPlugin::isDaemonRunning().value_or(true)) { - qWarning("Docker cid file empty."); - return; // No - } - qApp->processEvents(); // FIXME turn this for-loop into - QThread::msleep(100); - } DockerPlugin::setGlobalDaemonState(true); } @@ -1288,12 +1290,8 @@ QDateTime DockerDevice::lastModified(const FilePath &filePath) const return res; } - QtcProcess proc; - proc.setCommand({"stat", {"-c", "%Y", filePath.path()}}); - runProcess(proc); - proc.waitForFinished(); - - const qint64 secs = proc.rawStdOut().toLongLong(); + const QString output = d->outputForRunInShell({"stat", {"-c", "%Y", filePath.path()}}); + qint64 secs = output.toLongLong(); const QDateTime dt = QDateTime::fromSecsSinceEpoch(secs, Qt::UTC); return dt; } @@ -1615,6 +1613,12 @@ void DockerDevice::aboutToBeRemoved() const void DockerDevicePrivate::fetchSystemEnviroment() { + if (m_shell) { + const QString remoteOutput = outputForRunInShell({"env", {}}); + m_cachedEnviroment = Environment(remoteOutput.split('\n', Qt::SkipEmptyParts), q->osType()); + return; + } + QtcProcess proc; proc.setCommand({"env", {}}); diff --git a/src/plugins/help/litehtmlhelpviewer.cpp b/src/plugins/help/litehtmlhelpviewer.cpp index c33ebf62ef..8d54e01b69 100644 --- a/src/plugins/help/litehtmlhelpviewer.cpp +++ b/src/plugins/help/litehtmlhelpviewer.cpp @@ -235,8 +235,10 @@ bool LiteHtmlHelpViewer::eventFilter(QObject *src, QEvent *e) { if (isScrollWheelZoomingEnabled() && e->type() == QEvent::Wheel) { auto we = static_cast<QWheelEvent *>(e); - if (we->modifiers() == Qt::ControlModifier) + if (we->modifiers() == Qt::ControlModifier) { + e->ignore(); return true; + } } return HelpViewer::eventFilter(src, e); } diff --git a/src/plugins/imageviewer/imageviewerfile.cpp b/src/plugins/imageviewer/imageviewerfile.cpp index 2a6204e11d..3fe26890d0 100644 --- a/src/plugins/imageviewer/imageviewerfile.cpp +++ b/src/plugins/imageviewer/imageviewerfile.cpp @@ -126,7 +126,15 @@ Core::IDocument::OpenResult ImageViewerFile::openImpl(QString *errorString, m_type = TypeMovie; m_movie = new QMovie(fileName, QByteArray(), this); m_movie->setCacheMode(QMovie::CacheAll); - connect(m_movie, &QMovie::finished, m_movie, &QMovie::start); + connect( + m_movie, + &QMovie::finished, + m_movie, + [this] { + if (m_movie->isValid()) + m_movie->start(); + }, + Qt::QueuedConnection); connect(m_movie, &QMovie::resized, this, &ImageViewerFile::imageSizeChanged); m_movie->start(); m_isPaused = false; // force update diff --git a/src/plugins/mcusupport/mcusupportoptions.cpp b/src/plugins/mcusupport/mcusupportoptions.cpp index 6fa37d5a83..ba6107d6fc 100644 --- a/src/plugins/mcusupport/mcusupportoptions.cpp +++ b/src/plugins/mcusupport/mcusupportoptions.cpp @@ -72,15 +72,15 @@ namespace Internal { static const int KIT_VERSION = 8; // Bumps up whenever details in Kit creation change -static QString packagePathFromSettings(const QString &settingsKey, - QSettings::Scope scope = QSettings::UserScope, - const QString &defaultPath = {}) +static FilePath packagePathFromSettings(const QString &settingsKey, + QSettings::Scope scope = QSettings::UserScope, + const FilePath &defaultPath = {}) { QSettings *settings = Core::ICore::settings(scope); const QString key = QLatin1String(Constants::SETTINGS_GROUP) + '/' + QLatin1String(Constants::SETTINGS_KEY_PACKAGE_PREFIX) + settingsKey; - const QString path = settings->value(key, defaultPath).toString(); - return FilePath::fromUserInput(path).toString(); + const QString path = settings->value(key, defaultPath.toString()).toString(); + return FilePath::fromUserInput(path); } static bool automaticKitCreationFromSettings(QSettings::Scope scope = QSettings::UserScope) @@ -99,7 +99,7 @@ static bool kitNeedsQtVersion() return !HostOsInfo::isWindowsHost(); } -McuPackage::McuPackage(const QString &label, const QString &defaultPath, +McuPackage::McuPackage(const QString &label, const FilePath &defaultPath, const QString &detectionPath, const QString &settingsKey, const McuPackageVersionDetector *versionDetector) : m_label(label) @@ -112,14 +112,14 @@ McuPackage::McuPackage(const QString &label, const QString &defaultPath, m_automaticKitCreation = automaticKitCreationFromSettings(QSettings::UserScope); } -QString McuPackage::basePath() const +FilePath McuPackage::basePath() const { - return m_fileChooser != nullptr ? m_fileChooser->filePath().toString() : m_path; + return m_fileChooser != nullptr ? m_fileChooser->filePath() : m_path; } -QString McuPackage::path() const +FilePath McuPackage::path() const { - return QFileInfo(basePath() + m_relativePathModifier).absoluteFilePath(); + return basePath().resolvePath(m_relativePathModifier).absoluteFilePath(); } QString McuPackage::label() const @@ -127,7 +127,7 @@ QString McuPackage::label() const return m_label; } -QString McuPackage::defaultPath() const +FilePath McuPackage::defaultPath() const { return m_defaultPath; } @@ -148,7 +148,7 @@ QWidget *McuPackage::widget() Icons::RESET.icon()); m_fileChooser->lineEdit()->setButtonVisible(FancyLineEdit::Right, true); connect(m_fileChooser->lineEdit(), &FancyLineEdit::rightButtonClicked, this, [&] { - m_fileChooser->setPath(m_defaultPath); + m_fileChooser->setFilePath(m_defaultPath); }); auto layout = new QGridLayout(m_widget); @@ -168,7 +168,7 @@ QWidget *McuPackage::widget() layout->addWidget(m_fileChooser, 0, 0, 1, 2); layout->addWidget(m_infoLabel, 1, 0, 1, -1); - m_fileChooser->setPath(m_path); + m_fileChooser->setFilePath(m_path); QObject::connect(this, &McuPackage::statusChanged, this, [this] { updateStatusUi(); @@ -228,7 +228,7 @@ void McuPackage::writeGeneralSettings() const bool McuPackage::writeToSettings() const { - const QString savedPath = packagePathFromSettings(m_settingsKey, QSettings::UserScope, m_defaultPath); + const FilePath savedPath = packagePathFromSettings(m_settingsKey, QSettings::UserScope, m_defaultPath); const QString key = QLatin1String(Constants::SETTINGS_GROUP) + '/' + QLatin1String(Constants::SETTINGS_KEY_PACKAGE_PREFIX) + m_settingsKey; Core::ICore::settings()->setValueWithDefault(key, m_path, m_defaultPath); @@ -258,17 +258,18 @@ void McuPackage::setAutomaticKitCreationEnabled(const bool enabled) void McuPackage::updatePath() { - m_path = m_fileChooser->rawPath(); + m_path = m_fileChooser->rawFilePath(); m_fileChooser->lineEdit()->button(FancyLineEdit::Right)->setEnabled(m_path != m_defaultPath); updateStatus(); } void McuPackage::updateStatus() { - bool validPath = !m_path.isEmpty() && FilePath::fromString(m_path).exists(); - const FilePath detectionPath = FilePath::fromString(basePath() + "/" + m_detectionPath); + bool validPath = !m_path.isEmpty() && m_path.exists(); + const FilePath detectionPath = basePath() / m_detectionPath; const bool validPackage = m_detectionPath.isEmpty() || detectionPath.exists(); - m_detectedVersion = validPath && validPackage && m_versionDetector ? m_versionDetector->parseVersion(basePath()) : QString(); + m_detectedVersion = validPath && validPackage && m_versionDetector + ? m_versionDetector->parseVersion(basePath().toString()) : QString(); const bool validVersion = m_detectedVersion.isEmpty() || m_versions.isEmpty() || m_versions.contains(m_detectedVersion); @@ -293,7 +294,7 @@ void McuPackage::updateStatusUi() QString McuPackage::statusText() const { - const QString displayPackagePath = FilePath::fromString(m_path).toUserOutput(); + const QString displayPackagePath = m_path.toUserOutput(); const QString displayVersions = QStringList(m_versions.toList()).join(" or "); const QString displayRequiredPath = QString("%1 %2").arg( FilePath::fromString(m_detectionPath).toUserOutput(), @@ -339,7 +340,7 @@ QString McuPackage::statusText() const } McuToolChainPackage::McuToolChainPackage(const QString &label, - const QString &defaultPath, + const FilePath &defaultPath, const QString &detectionPath, const QString &settingsKey, McuToolChainPackage::Type type, @@ -447,11 +448,9 @@ ToolChain *McuToolChainPackage::toolChain(Id language) const else { const QLatin1String compilerName( language == ProjectExplorer::Constants::C_LANGUAGE_ID ? "gcc" : "g++"); - const FilePath compiler = FilePath::fromUserInput( - HostOsInfo::withExecutableSuffix( - path() + ( - m_type == TypeArmGcc - ? "/bin/arm-none-eabi-%1" : "/bar/foo-keil-%1")).arg(compilerName)); + const QString comp = QLatin1String(m_type == TypeArmGcc ? "/bin/arm-none-eabi-%1" : "/bar/foo-keil-%1") + .arg(compilerName); + const FilePath compiler = path().pathAppended(comp).withExecutableSuffix(); tc = armGccToolChain(compiler, language); } @@ -479,11 +478,10 @@ QVariant McuToolChainPackage::debuggerId() const { using namespace Debugger; - const FilePath command = FilePath::fromUserInput( - HostOsInfo::withExecutableSuffix(path() + ( - m_type == TypeArmGcc - ? "/bin/arm-none-eabi-gdb-py" : m_type == TypeIAR - ? "../common/bin/CSpyBat" : "/bar/foo-keil-gdb"))); + QString sub = QString::fromLatin1(m_type == TypeArmGcc ? "bin/arm-none-eabi-gdb-py" + : m_type == TypeIAR ? "../common/bin/CSpyBat" : "bar/foo-keil-gdb"); + + const FilePath command = path().pathAppended(sub).withExecutableSuffix(); const DebuggerItem *debugger = DebuggerItemManager::findByCommand(command); QVariant debuggerId; if (!debugger) { @@ -591,7 +589,7 @@ McuSupportOptions::~McuSupportOptions() void McuSupportOptions::populatePackagesAndTargets() { - setQulDir(FilePath::fromUserInput(qtForMCUsSdkPackage->path())); + setQulDir(qtForMCUsSdkPackage->path()); } static FilePath qulDocsDir() @@ -665,13 +663,12 @@ void McuSupportOptions::setQulDir(const FilePath &dir) FilePath McuSupportOptions::qulDirFromSettings() { - return FilePath::fromUserInput( - packagePathFromSettings(Constants::SETTINGS_KEY_PACKAGE_QT_FOR_MCUS_SDK, - QSettings::UserScope)); + return packagePathFromSettings(Constants::SETTINGS_KEY_PACKAGE_QT_FOR_MCUS_SDK, + QSettings::UserScope); } static void setKitProperties(const QString &kitName, Kit *k, const McuTarget *mcuTarget, - const QString &sdkPath) + const FilePath &sdkPath) { using namespace Constants; @@ -688,7 +685,7 @@ static void setKitProperties(const QString &kitName, Kit *k, const McuTarget *mc if (mcuTarget->toolChainPackage()->isDesktopToolchain()) k->setDeviceTypeForIcon(DEVICE_TYPE); k->setValue(QtSupport::SuppliesQtQuickImportPath::id(), true); - k->setValue(QtSupport::KitQmlImportPath::id(), QVariant(sdkPath + "/include/qul")); + k->setValue(QtSupport::KitQmlImportPath::id(), sdkPath.pathAppended("include/qul").toVariant()); k->setValue(QtSupport::KitHasMergedHeaderPathsWithQmlImportPaths::id(), true); QSet<Id> irrelevant = { SysRootKitAspect::id(), @@ -750,14 +747,13 @@ static void setKitEnvironment(Kit *k, const McuTarget *mcuTarget, // feature of the run configuration. Otherwise, we just prepend the path, here. if (mcuTarget->toolChainPackage()->isDesktopToolchain() && !CMakeProjectManager::CMakeToolManager::defaultCMakeTool()->hasFileApi()) - pathAdditions.append(QDir::toNativeSeparators(qtForMCUsSdkPackage->path() + "/bin")); + pathAdditions.append(qtForMCUsSdkPackage->path().pathAppended("bin").toUserOutput()); auto processPackage = [&pathAdditions, &changes](const McuPackage *package) { if (package->addToPath()) - pathAdditions.append(QDir::toNativeSeparators(package->path())); + pathAdditions.append(package->path().toUserOutput()); if (!package->environmentVariableName().isEmpty()) - changes.append({package->environmentVariableName(), - QDir::toNativeSeparators(package->path())}); + changes.append({package->environmentVariableName(), package->path().toUserOutput()}); }; for (auto package : mcuTarget->packages()) processPackage(package); @@ -808,7 +804,7 @@ static void updateKitEnvironment(Kit *k, const McuTarget *mcuTarget) return item.name == varName; }); const EnvironmentItem item = {package->environmentVariableName(), - QDir::toNativeSeparators(package->path())}; + package->path().toUserOutput()}; if (index != -1) changes.replace(index, item); else @@ -819,7 +815,7 @@ static void updateKitEnvironment(Kit *k, const McuTarget *mcuTarget) EnvironmentKitAspect::setEnvironmentChanges(k, changes); } -static void setKitCMakeOptions(Kit *k, const McuTarget* mcuTarget, const QString &qulDir) +static void setKitCMakeOptions(Kit *k, const McuTarget* mcuTarget, const FilePath &qulDir) { using namespace CMakeProjectManager; @@ -832,7 +828,7 @@ static void setKitCMakeOptions(Kit *k, const McuTarget* mcuTarget, const QString } if (!mcuTarget->toolChainPackage()->isDesktopToolchain()) { - const FilePath cMakeToolchainFile = FilePath::fromString(qulDir + "/lib/cmake/Qul/toolchain/" + const FilePath cMakeToolchainFile = qulDir.pathAppended("lib/cmake/Qul/toolchain/" + mcuTarget->toolChainPackage()->cmakeToolChainFileName()); config.append(CMakeConfigItem( @@ -844,7 +840,7 @@ static void setKitCMakeOptions(Kit *k, const McuTarget* mcuTarget, const QString } } - const FilePath generatorsPath = FilePath::fromString(qulDir + "/lib/cmake/Qul/QulGenerators.cmake"); + const FilePath generatorsPath = qulDir.pathAppended("/lib/cmake/Qul/QulGenerators.cmake"); config.append(CMakeConfigItem("QUL_GENERATORS", generatorsPath.toString().toUtf8())); if (!generatorsPath.exists()) { @@ -947,7 +943,7 @@ QList<Kit *> McuSupportOptions::kitsWithMismatchedDependencies(const McuTarget * EnvironmentKitAspect::environmentChanges(kit))); return Utils::anyOf(mcuTarget->packages(), [&environment](const McuPackage *package) { return !package->environmentVariableName().isEmpty() && - environment.value(package->environmentVariableName()) != QDir::toNativeSeparators(package->path()); + environment.value(package->environmentVariableName()) != package->path().toUserOutput(); }); }); } @@ -1003,13 +999,13 @@ QVersionNumber McuSupportOptions::kitQulVersion(const Kit *kit) .toString()); } -QString kitDependencyPath(const Kit *kit, const QString &variableName) +static FilePath kitDependencyPath(const Kit *kit, const QString &variableName) { for (const NameValueItem &nameValueItem : EnvironmentKitAspect::environmentChanges(kit)) { if (nameValueItem.name == variableName) - return nameValueItem.value; + return FilePath::fromUserInput(nameValueItem.value); } - return QString(); + return FilePath(); } bool McuSupportOptions::kitUpToDate(const Kit *kit, const McuTarget *mcuTarget, @@ -1052,13 +1048,13 @@ void McuSupportOptions::createAutomaticKits() const QString displayPath = FilePath::fromString(qtForMCUsPackage->detectionPath()) .toUserOutput(); printMessage(tr("Path %1 exists, but does not contain %2.") - .arg(qtForMCUsPackage->path(), displayPath), + .arg(qtForMCUsPackage->path().toUserOutput(), displayPath), true); break; } case McuPackage::InvalidPath: { printMessage(tr("Path %1 does not exist. Add the path in Tools > Options > Devices > MCU.") - .arg(qtForMCUsPackage->path()), + .arg(qtForMCUsPackage->path().toUserOutput()), true); break; } @@ -1079,7 +1075,7 @@ void McuSupportOptions::createAutomaticKits() return; } - auto dir = FilePath::fromUserInput(qtForMCUsPackage->path()); + FilePath dir = qtForMCUsPackage->path(); QVector<McuPackage*> packages; QVector<McuTarget*> mcuTargets; Sdk::targetsAndPackages(dir, &packages, &mcuTargets); @@ -1131,7 +1127,7 @@ void McuSupportOptions::upgradeKits(UpgradeOption upgradeOption) auto qtForMCUsPackage = Sdk::createQtForMCUsPackage(); - auto dir = FilePath::fromUserInput(qtForMCUsPackage->path()); + auto dir = qtForMCUsPackage->path(); QVector<McuPackage*> packages; QVector<McuTarget*> mcuTargets; Sdk::targetsAndPackages(dir, &packages, &mcuTargets); @@ -1169,7 +1165,7 @@ void McuSupportOptions::fixKitsDependencies() { auto qtForMCUsPackage = Sdk::createQtForMCUsPackage(); - auto dir = FilePath::fromUserInput(qtForMCUsPackage->path()); + FilePath dir = qtForMCUsPackage->path(); QVector<McuPackage*> packages; QVector<McuTarget*> mcuTargets; Sdk::targetsAndPackages(dir, &packages, &mcuTargets); @@ -1245,7 +1241,7 @@ void McuSupportOptions::fixExistingKits() auto qtForMCUsPackage = Sdk::createQtForMCUsPackage(); qtForMCUsPackage->updateStatus(); if (qtForMCUsPackage->validStatus()) { - auto dir = FilePath::fromUserInput(qtForMCUsPackage->path()); + FilePath dir = qtForMCUsPackage->path(); QVector<McuPackage*> packages; QVector<McuTarget*> mcuTargets; Sdk::targetsAndPackages(dir, &packages, &mcuTargets); diff --git a/src/plugins/mcusupport/mcusupportoptions.h b/src/plugins/mcusupport/mcusupportoptions.h index f70722412d..346c2b9de5 100644 --- a/src/plugins/mcusupport/mcusupportoptions.h +++ b/src/plugins/mcusupport/mcusupportoptions.h @@ -66,15 +66,15 @@ public: ValidPackage }; - McuPackage(const QString &label, const QString &defaultPath, + McuPackage(const QString &label, const Utils::FilePath &defaultPath, const QString &detectionPath, const QString &settingsKey, const McuPackageVersionDetector *versionDetector = nullptr); virtual ~McuPackage() = default; - QString basePath() const; - QString path() const; + Utils::FilePath basePath() const; + Utils::FilePath path() const; QString label() const; - QString defaultPath() const; + Utils::FilePath defaultPath() const; QString detectionPath() const; QString statusText() const; void updateStatus(); @@ -110,12 +110,12 @@ private: Utils::InfoLabel *m_infoLabel = nullptr; const QString m_label; - const QString m_defaultPath; + const Utils::FilePath m_defaultPath; const QString m_detectionPath; const QString m_settingsKey; const McuPackageVersionDetector *m_versionDetector; - QString m_path; + Utils::FilePath m_path; QString m_relativePathModifier; // relative path to m_path to be returned by path() QString m_detectedVersion; QVector<QString> m_versions; @@ -142,7 +142,7 @@ public: }; McuToolChainPackage(const QString &label, - const QString &defaultPath, + const Utils::FilePath &defaultPath, const QString &detectionPath, const QString &settingsKey, Type type, diff --git a/src/plugins/mcusupport/mcusupportoptionspage.cpp b/src/plugins/mcusupport/mcusupportoptionspage.cpp index 9ccffd63e0..71eff41c24 100644 --- a/src/plugins/mcusupport/mcusupportoptionspage.cpp +++ b/src/plugins/mcusupport/mcusupportoptionspage.cpp @@ -193,7 +193,7 @@ void McuSupportOptionsWidget::updateStatus() m_mcuTargetsInfoLabel->setVisible(valid && m_options.mcuTargets.isEmpty()); if (m_mcuTargetsInfoLabel->isVisible()) { m_mcuTargetsInfoLabel->setType(Utils::InfoLabel::NotOk); - const auto sdkPath = Utils::FilePath::fromString(m_options.qtForMCUsSdkPackage->basePath()); + const Utils::FilePath sdkPath = m_options.qtForMCUsSdkPackage->basePath(); QString deprecationMessage; if (Sdk::checkDeprecatedSdkError(sdkPath, deprecationMessage)) m_mcuTargetsInfoLabel->setText(deprecationMessage); diff --git a/src/plugins/mcusupport/mcusupportsdk.cpp b/src/plugins/mcusupport/mcusupportsdk.cpp index b805b4a91a..291f0647b2 100644 --- a/src/plugins/mcusupport/mcusupportsdk.cpp +++ b/src/plugins/mcusupport/mcusupportsdk.cpp @@ -49,15 +49,14 @@ namespace McuSupport { namespace Internal { namespace Sdk { -static QString findInProgramFiles(const QString &folder) +static FilePath findInProgramFiles(const QString &folder) { for (auto envVar : {"ProgramFiles", "ProgramFiles(x86)", "ProgramW6432"}) { if (!qEnvironmentVariableIsSet(envVar)) continue; - const Utils::FilePath dir = - Utils::FilePath::fromUserInput(qEnvironmentVariable(envVar) + "/" + folder); + const FilePath dir = FilePath::fromUserInput(qEnvironmentVariable(envVar)) / folder; if (dir.exists()) - return dir.toString(); + return dir; } return {}; } @@ -66,7 +65,7 @@ McuPackage *createQtForMCUsPackage() { auto result = new McuPackage( McuPackage::tr("Qt for MCUs SDK"), - QDir::homePath(), + FileUtils::homePath(), FilePath("bin/qmltocpp").withExecutableSuffix().toString(), Constants::SETTINGS_KEY_PACKAGE_QT_FOR_MCUS_SDK); result->setEnvironmentVariableName("Qul_DIR"); @@ -92,22 +91,21 @@ static McuToolChainPackage *createArmGccPackage() { const char envVar[] = "ARMGCC_DIR"; - QString defaultPath; + FilePath defaultPath; if (qEnvironmentVariableIsSet(envVar)) - defaultPath = qEnvironmentVariable(envVar); - if (defaultPath.isEmpty() && Utils::HostOsInfo::isWindowsHost()) { - const QDir installDir(findInProgramFiles("/GNU Tools ARM Embedded/")); + defaultPath = FilePath::fromUserInput(qEnvironmentVariable(envVar)); + if (defaultPath.isEmpty() && HostOsInfo::isWindowsHost()) { + const FilePath installDir = findInProgramFiles("GNU Tools ARM Embedded"); if (installDir.exists()) { // If GNU Tools installation dir has only one sub dir, // select the sub dir, otherwise the installation dir. - const QFileInfoList subDirs = - installDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + const FilePaths subDirs = installDir.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot); if (subDirs.count() == 1) - defaultPath = subDirs.first().filePath() + '/'; + defaultPath = subDirs.first(); } } if (defaultPath.isEmpty()) - defaultPath = QDir::homePath(); + defaultPath = FileUtils::homePath(); const QString detectionPath = Utils::HostOsInfo::withExecutableSuffix("bin/arm-none-eabi-g++"); const auto versionDetector = new McuPackageExecutableVersionDetector( @@ -131,8 +129,8 @@ static McuToolChainPackage *createGhsToolchainPackage() { const char envVar[] = "GHS_COMPILER_DIR"; - const QString defaultPath = - qEnvironmentVariableIsSet(envVar) ? qEnvironmentVariable(envVar) : QDir::homePath(); + const FilePath defaultPath = qEnvironmentVariableIsSet(envVar) + ? FilePath::fromUserInput(qEnvironmentVariable(envVar)) : FileUtils::homePath(); const auto versionDetector = new McuPackageExecutableVersionDetector( Utils::HostOsInfo::withExecutableSuffix("as850"), @@ -155,8 +153,8 @@ static McuToolChainPackage *createGhsArmToolchainPackage() { const char envVar[] = "GHS_ARM_COMPILER_DIR"; - const QString defaultPath = - qEnvironmentVariableIsSet(envVar) ? qEnvironmentVariable(envVar) : QDir::homePath(); + const FilePath defaultPath = qEnvironmentVariableIsSet(envVar) + ? FilePath::fromUserInput(qEnvironmentVariable(envVar)) : FileUtils::homePath(); const auto versionDetector = new McuPackageExecutableVersionDetector( Utils::HostOsInfo::withExecutableSuffix("asarm"), @@ -179,20 +177,20 @@ static McuToolChainPackage *createIarToolChainPackage() { const char envVar[] = "IAR_ARM_COMPILER_DIR"; - QString defaultPath; + FilePath defaultPath; if (qEnvironmentVariableIsSet(envVar)) - defaultPath = qEnvironmentVariable(envVar); + defaultPath = FilePath::fromUserInput(qEnvironmentVariable(envVar)); else { const ProjectExplorer::ToolChain *tc = ProjectExplorer::ToolChainManager::toolChain([](const ProjectExplorer::ToolChain *t) { return t->typeId() == BareMetal::Constants::IAREW_TOOLCHAIN_TYPEID; }); if (tc) { - const Utils::FilePath compilerExecPath = tc->compilerCommand(); - defaultPath = compilerExecPath.parentDir().parentDir().toString(); + const FilePath compilerExecPath = tc->compilerCommand(); + defaultPath = compilerExecPath.parentDir().parentDir(); } else - defaultPath = QDir::homePath(); + defaultPath = FileUtils::homePath(); } const QString detectionPath = Utils::HostOsInfo::withExecutableSuffix("bin/iccarm"); @@ -217,17 +215,17 @@ static McuPackage *createRGLPackage() { const char envVar[] = "RGL_DIR"; - QString defaultPath; + FilePath defaultPath; if (qEnvironmentVariableIsSet(envVar)) { - defaultPath = qEnvironmentVariable(envVar); + defaultPath = FilePath::fromUserInput(qEnvironmentVariable(envVar)); } else if (Utils::HostOsInfo::isWindowsHost()) { - defaultPath = QDir::rootPath() + "Renesas_Electronics/D1x_RGL"; - if (QFileInfo::exists(defaultPath)) { - const QFileInfoList subDirs = - QDir(defaultPath).entryInfoList({QLatin1String("rgl_ghs_D1Mx_*")}, + defaultPath = FilePath::fromUserInput(QDir::rootPath() + "Renesas_Electronics/D1x_RGL"); + if (defaultPath.exists()) { + const FilePaths subDirs = + defaultPath.dirEntries({QLatin1String("rgl_ghs_D1Mx_*")}, QDir::Dirs | QDir::NoDotAndDotDot); if (subDirs.count() == 1) - defaultPath = subDirs.first().filePath() + '/'; + defaultPath = subDirs.first(); } } @@ -242,15 +240,15 @@ static McuPackage *createRGLPackage() static McuPackage *createStm32CubeProgrammerPackage() { - QString defaultPath = QDir::homePath(); - const QString cubePath = "/STMicroelectronics/STM32Cube/STM32CubeProgrammer/"; - if (Utils::HostOsInfo::isWindowsHost()) { - const QString programPath = findInProgramFiles(cubePath); + FilePath defaultPath = FileUtils::homePath(); + const QString cubePath = "STMicroelectronics/STM32Cube/STM32CubeProgrammer"; + if (HostOsInfo::isWindowsHost()) { + const FilePath programPath = findInProgramFiles(cubePath); if (!programPath.isEmpty()) defaultPath = programPath; } else { - const QString programPath = QDir::homePath() + cubePath; - if (QFileInfo::exists(programPath)) + const FilePath programPath = FileUtils::homePath() / cubePath; + if (programPath.exists()) defaultPath = programPath; } auto result = new McuPackage( @@ -270,18 +268,18 @@ static McuPackage *createMcuXpressoIdePackage() { const char envVar[] = "MCUXpressoIDE_PATH"; - QString defaultPath; + FilePath defaultPath; if (qEnvironmentVariableIsSet(envVar)) { - defaultPath = qEnvironmentVariable(envVar); - } else if (Utils::HostOsInfo::isWindowsHost()) { - defaultPath = QDir::rootPath() + "nxp"; - if (QFileInfo::exists(defaultPath)) { + defaultPath = FilePath::fromUserInput(qEnvironmentVariable(envVar)); + } else if (HostOsInfo::isWindowsHost()) { + defaultPath = FilePath::fromString(QDir::rootPath() + "nxp"); + if (defaultPath.exists()) { // If default dir has exactly one sub dir that could be the IDE path, pre-select that. - const QFileInfoList subDirs = - QDir(defaultPath).entryInfoList({QLatin1String("MCUXpressoIDE*")}, + const FilePaths subDirs = + defaultPath.dirEntries({QLatin1String("MCUXpressoIDE*")}, QDir::Dirs | QDir::NoDotAndDotDot); if (subDirs.count() == 1) - defaultPath = subDirs.first().filePath() + '/'; + defaultPath = subDirs.first(); } } else { defaultPath = "/usr/local/mcuxpressoide/"; @@ -301,21 +299,20 @@ static McuPackage *createCypressProgrammerPackage() { const char envVar[] = "CYPRESS_AUTO_FLASH_UTILITY_DIR"; - QString defaultPath; + FilePath defaultPath; if (qEnvironmentVariableIsSet(envVar)) { - defaultPath = qEnvironmentVariable(envVar); - } else if (Utils::HostOsInfo::isWindowsHost()) { - auto candidate = findInProgramFiles(QLatin1String("/Cypress/Cypress Auto Flash Utility 1.0/")); - if (QFileInfo::exists(candidate)) { + defaultPath = FilePath::fromUserInput(qEnvironmentVariable(envVar)); + } else if (HostOsInfo::isWindowsHost()) { + FilePath candidate = findInProgramFiles("Cypress/Cypress Auto Flash Utility 1.0"); + if (candidate.exists()) { defaultPath = candidate; } } else { - defaultPath = QLatin1String("/usr"); + defaultPath = "/usr"; } - if (defaultPath.isEmpty()) { - defaultPath = QDir::homePath(); - } + if (defaultPath.isEmpty()) + defaultPath = FileUtils::homePath(); auto result = new McuPackage( "Cypress Auto Flash Utility", @@ -378,18 +375,16 @@ static McuPackage *createBoardSdkPackage(const McuTargetDescription& desc) }; const QString sdkName = desc.boardSdkName.isEmpty() ? generateSdkName(desc.boardSdkEnvVar) : desc.boardSdkName; - const QString defaultPath = [&] { + const FilePath defaultPath = [&] { const auto envVar = desc.boardSdkEnvVar.toLatin1(); - if (qEnvironmentVariableIsSet(envVar)) { - return qEnvironmentVariable(envVar); - } + if (qEnvironmentVariableIsSet(envVar)) + return FilePath::fromUserInput(qEnvironmentVariable(envVar)); if (!desc.boardSdkDefaultPath.isEmpty()) { - QString defaultPath = QDir::rootPath() + desc.boardSdkDefaultPath; - if (QFileInfo::exists(defaultPath)) { + FilePath defaultPath = FilePath::fromUserInput(QDir::rootPath() + desc.boardSdkDefaultPath); + if (defaultPath.exists()) return defaultPath; - } } - return QDir::homePath(); + return FileUtils::homePath(); }(); const auto versionDetector = generatePackageVersionDetector(desc.boardSdkEnvVar); @@ -404,18 +399,18 @@ static McuPackage *createBoardSdkPackage(const McuTargetDescription& desc) return result; } -static McuPackage *createFreeRTOSSourcesPackage(const QString &envVar, const QString &boardSdkDir, +static McuPackage *createFreeRTOSSourcesPackage(const QString &envVar, const FilePath &boardSdkDir, const QString &freeRTOSBoardSdkSubDir) { const QString envVarPrefix = envVar.chopped(int(strlen("_FREERTOS_DIR"))); - QString defaultPath; + FilePath defaultPath; if (qEnvironmentVariableIsSet(envVar.toLatin1())) - defaultPath = qEnvironmentVariable(envVar.toLatin1()); + defaultPath = FilePath::fromUserInput(qEnvironmentVariable(envVar.toLatin1())); else if (!boardSdkDir.isEmpty() && !freeRTOSBoardSdkSubDir.isEmpty()) - defaultPath = boardSdkDir + "/" + freeRTOSBoardSdkSubDir; + defaultPath = boardSdkDir / freeRTOSBoardSdkSubDir; else - defaultPath = QDir::homePath(); + defaultPath = FileUtils::homePath(); auto result = new McuPackage( QString::fromLatin1("FreeRTOS Sources (%1)").arg(envVarPrefix), @@ -473,7 +468,7 @@ protected: if (vendorPkgs.contains(desc.platformVendor)) required3rdPartyPkgs.push_back(vendorPkgs.value(desc.platformVendor)); - QString boardSdkDefaultPath; + FilePath boardSdkDefaultPath; if (!desc.boardSdkEnvVar.isEmpty()) { if (!boardSdkPkgs.contains(desc.boardSdkEnvVar)) { auto boardSdkPkg = desc.boardSdkEnvVar != "RGL_DIR" @@ -551,7 +546,7 @@ protected: required3rdPartyPkgs.push_back(vendorPkgs.value(desc.platformVendor)); // Board SDK specific settings - QString boardSdkDefaultPath; + FilePath boardSdkDefaultPath; if (!desc.boardSdkEnvVar.isEmpty()) { if (!boardSdkPkgs.contains(desc.boardSdkEnvVar)) { auto boardSdkPkg = createBoardSdkPackage(desc); diff --git a/src/plugins/projectexplorer/kitmanager.cpp b/src/plugins/projectexplorer/kitmanager.cpp index 3f77fee414..1c338d730a 100644 --- a/src/plugins/projectexplorer/kitmanager.cpp +++ b/src/plugins/projectexplorer/kitmanager.cpp @@ -238,12 +238,15 @@ void KitManager::restoreKits() kitsToCheck.clear(); // Remove replacement kits for which the original kit has turned up again. - Utils::erase(resultList, [&resultList](const std::unique_ptr<Kit> &k) { - return k->isReplacementKit() - && contains(resultList, [&k](const std::unique_ptr<Kit> &other) { - return other->id() == k->id() && other != k; - }); - }); + for (auto it = resultList.begin(); it != resultList.end();) { + const auto &k = *it; + if (k->isReplacementKit() && contains(resultList, [&k](const std::unique_ptr<Kit> &other) { + return other->id() == k->id() && other != k; })) { + it = resultList.erase(it); + } else { + ++it; + } + } static const auto kitMatchesAbiList = [](const Kit *kit, const Abis &abis) { const QList<ToolChain *> toolchains = ToolChainKitAspect::toolChains(kit); diff --git a/src/plugins/projectexplorer/miniprojecttargetselector.cpp b/src/plugins/projectexplorer/miniprojecttargetselector.cpp index b73d12c578..44fc5ae511 100644 --- a/src/plugins/projectexplorer/miniprojecttargetselector.cpp +++ b/src/plugins/projectexplorer/miniprojecttargetselector.cpp @@ -238,6 +238,18 @@ public: protected: void resetOptimalWidth() { + if (m_resetScheduled) + return; + m_resetScheduled = true; + QMetaObject::invokeMethod(this, &SelectorView::doResetOptimalWidth, Qt::QueuedConnection); + } + +private: + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + void doResetOptimalWidth() + { + m_resetScheduled = false; int width = 0; QFontMetrics fn(font()); theModel()->forItemsAtLevel<1>([this, &width, &fn](const GenericItem *item) { @@ -246,12 +258,9 @@ protected: setOptimalWidth(width); } -private: - void keyPressEvent(QKeyEvent *event) override; - void keyReleaseEvent(QKeyEvent *event) override; - int m_maxCount = 0; int m_optimalWidth = 0; + bool m_resetScheduled = false; }; class ProjectListView : public SelectorView diff --git a/src/plugins/qbsprojectmanager/qbsnodes.cpp b/src/plugins/qbsprojectmanager/qbsnodes.cpp index 2a155d2128..2d6049b570 100644 --- a/src/plugins/qbsprojectmanager/qbsnodes.cpp +++ b/src/plugins/qbsprojectmanager/qbsnodes.cpp @@ -218,7 +218,7 @@ QVariant QbsProductNode::data(Id role) const return m_productData.value("module-properties").toObject() .value("Qt.core.enableKeywords").toBool(); - if (role == Android::Constants::ANDROID_ABIS) { + if (role == Android::Constants::AndroidAbis) { // Try using qbs.architectures QStringList qbsAbis; QMap<QString, QString> archToAbi { diff --git a/src/plugins/qmakeprojectmanager/profilecompletionassist.cpp b/src/plugins/qmakeprojectmanager/profilecompletionassist.cpp index 419b4044de..ff08bb61b9 100644 --- a/src/plugins/qmakeprojectmanager/profilecompletionassist.cpp +++ b/src/plugins/qmakeprojectmanager/profilecompletionassist.cpp @@ -33,21 +33,22 @@ const TextEditor::Keywords &QmakeProjectManager::Internal::qmakeKeywords() { static TextEditor::Keywords keywords( QStringList{ // variables - "ANDROID_ABIS", + Android::Constants::ANDROID_ABI, + Android::Constants::ANDROID_ABIS, "ANDROID_API_VERSION", - QLatin1String(Android::Constants::ANDROID_APPLICATION_ARGUMENTS), + Android::Constants::ANDROID_APPLICATION_ARGUMENTS, "ANDROID_BUNDLED_JAR_DEPENDENCIES", "ANDROID_DEPLOYMENT_DEPENDENCIES", - QLatin1String(Android::Constants::ANDROID_DEPLOYMENT_SETTINGS_FILE), - QLatin1String(Android::Constants::ANDROID_EXTRA_LIBS), + Android::Constants::ANDROID_DEPLOYMENT_SETTINGS_FILE, + Android::Constants::ANDROID_EXTRA_LIBS, "ANDROID_EXTRA_PLUGINS", "ANDROID_FEATURES", "ANDROID_LIB_DEPENDENCIES", "ANDROID_MIN_SDK_VERSION", - QLatin1String(Android::Constants::ANDROID_PACKAGE_SOURCE_DIR), + Android::Constants::ANDROID_PACKAGE_SOURCE_DIR, "ANDROID_PERMISSIONS", "ANDROID_TARGET_SDK_VERSION", - "ANDROID_TARGET_ARCH", + Android::Constants::ANDROID_TARGET_ARCH, "ANDROID_VERSION_CODE", "ANDROID_VERSION_NAME", "ARGC", diff --git a/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp b/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp index beef0a160d..6b28aa6b44 100644 --- a/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp +++ b/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp @@ -157,7 +157,7 @@ QmakeBuildConfiguration::QmakeBuildConfiguration(Target *target, Utils::Id id) if (DeviceTypeKitAspect::deviceTypeId(target->kit()) == Android::Constants::ANDROID_DEVICE_TYPE) { - buildSteps()->appendStep(Android::Constants::ANDROID_PACKAGE_INSTALLATION_STEP_ID); + buildSteps()->appendStep(Android::Constants::ANDROID_PACKAGE_INSTALL_STEP_ID); buildSteps()->appendStep(Android::Constants::ANDROID_BUILD_APK_ID); } diff --git a/src/plugins/qmakeprojectmanager/qmakenodes.cpp b/src/plugins/qmakeprojectmanager/qmakenodes.cpp index 1214b79d64..46e95f0a68 100644 --- a/src/plugins/qmakeprojectmanager/qmakenodes.cpp +++ b/src/plugins/qmakeprojectmanager/qmakenodes.cpp @@ -364,16 +364,16 @@ QStringList QmakeProFileNode::targetApplications() const QVariant QmakeProFileNode::data(Utils::Id role) const { - if (role == Android::Constants::ANDROID_ABIS) + if (role == Android::Constants::AndroidAbis) return variableValue(Variable::AndroidAbis); + if (role == Android::Constants::AndroidAbi) + return singleVariableValue(Variable::AndroidAbi); + if (role == Android::Constants::AndroidExtraLibs) + return variableValue(Variable::AndroidExtraLibs); if (role == Android::Constants::AndroidPackageSourceDir) return singleVariableValue(Variable::AndroidPackageSourceDir); if (role == Android::Constants::AndroidDeploySettingsFile) return singleVariableValue(Variable::AndroidDeploySettingsFile); - if (role == Android::Constants::AndroidExtraLibs) - return variableValue(Variable::AndroidExtraLibs); - if (role == Android::Constants::AndroidArch) - return singleVariableValue(Variable::AndroidArch); if (role == Android::Constants::AndroidSoLibPath) { TargetInformation info = targetInformation(); QStringList res = {info.buildDir.toString()}; @@ -431,8 +431,9 @@ bool QmakeProFileNode::setData(Utils::Id role, const QVariant &value) const if (Target *target = m_buildSystem->target()) { QtSupport::BaseQtVersion *version = QtSupport::QtKitAspect::qtVersion(target->kit()); if (version && !version->supportsMultipleQtAbis()) { - const QString arch = pro->singleVariableValue(Variable::AndroidArch); - scope = "contains(ANDROID_TARGET_ARCH," + arch + ')'; + const QString arch = pro->singleVariableValue(Variable::AndroidAbi); + scope = QString("contains(%1,%2)").arg(Android::Constants::ANDROID_TARGET_ARCH) + .arg(arch); flags |= QmakeProjectManager::Internal::ProWriter::MultiLine; } } @@ -443,7 +444,7 @@ bool QmakeProFileNode::setData(Utils::Id role, const QVariant &value) const if (role == Android::Constants::AndroidPackageSourceDir) return pro->setProVariable(QLatin1String(Android::Constants::ANDROID_PACKAGE_SOURCE_DIR), {value.toString()}, scope, flags); - if (role == Android::Constants::ANDROID_APPLICATION_ARGUMENTS) + if (role == Android::Constants::AndroidApplicationArgs) return pro->setProVariable(QLatin1String(Android::Constants::ANDROID_APPLICATION_ARGUMENTS), {value.toString()}, scope, flags); diff --git a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp index 6cc4634375..f64aecd296 100644 --- a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp @@ -1562,11 +1562,11 @@ QmakeEvalResult *QmakeProFile::evaluate(const QmakeEvalInput &input) = exactReader->values(QLatin1String("TARGET_VERSION_EXT")); result->newVarValues[Variable::StaticLibExtension] = exactReader->values(QLatin1String("QMAKE_EXTENSION_STATICLIB")); result->newVarValues[Variable::ShLibExtension] = exactReader->values(QLatin1String("QMAKE_EXTENSION_SHLIB")); - result->newVarValues[Variable::AndroidArch] = exactReader->values(QLatin1String("ANDROID_TARGET_ARCH")); + result->newVarValues[Variable::AndroidAbi] = exactReader->values(QLatin1String(Android::Constants::ANDROID_TARGET_ARCH)); result->newVarValues[Variable::AndroidDeploySettingsFile] = exactReader->values(QLatin1String(Android::Constants::ANDROID_DEPLOYMENT_SETTINGS_FILE)); result->newVarValues[Variable::AndroidPackageSourceDir] = exactReader->values(QLatin1String(Android::Constants::ANDROID_PACKAGE_SOURCE_DIR)); - result->newVarValues[Variable::AndroidAbis] = exactReader->values(QLatin1String("ANDROID_ABIS")); - result->newVarValues[Variable::AndroidApplicationArguments] = exactReader->values(QLatin1String(Android::Constants::ANDROID_APPLICATION_ARGUMENTS)); + 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::AppmanPackageDir] = exactReader->values(QLatin1String("AM_PACKAGE_DIR")); result->newVarValues[Variable::AppmanManifest] = exactReader->values(QLatin1String("AM_MANIFEST")); diff --git a/src/plugins/qmakeprojectmanager/qmakeparsernodes.h b/src/plugins/qmakeprojectmanager/qmakeparsernodes.h index fc90e0759d..cfa86b181f 100644 --- a/src/plugins/qmakeprojectmanager/qmakeparsernodes.h +++ b/src/plugins/qmakeprojectmanager/qmakeparsernodes.h @@ -96,12 +96,12 @@ enum class Variable { TargetVersionExt, StaticLibExtension, ShLibExtension, - AndroidArch, - AndroidDeploySettingsFile, + AndroidAbi, AndroidAbis, + AndroidDeploySettingsFile, AndroidPackageSourceDir, AndroidExtraLibs, - AndroidApplicationArguments, + AndroidApplicationArgs, AppmanPackageDir, AppmanManifest, IsoIcons, diff --git a/src/plugins/qmakeprojectmanager/qmakeproject.cpp b/src/plugins/qmakeprojectmanager/qmakeproject.cpp index 793ec73ba8..54395bfc37 100644 --- a/src/plugins/qmakeprojectmanager/qmakeproject.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeproject.cpp @@ -674,8 +674,9 @@ void QmakeBuildSystem::asyncUpdate() connect(watcher, &QFutureWatcher<void>::canceled, this, [this, watcher] { if (!m_qmakeGlobals) return; - watcher->disconnect(); m_qmakeGlobals->killProcesses(); + watcher->disconnect(); + watcher->deleteLater(); }); connect(watcher, &QFutureWatcher<void>::finished, this, [watcher] { watcher->disconnect(); diff --git a/src/plugins/qmakeprojectmanager/qmakestep.cpp b/src/plugins/qmakeprojectmanager/qmakestep.cpp index 7cc65532af..9e7c7f2230 100644 --- a/src/plugins/qmakeprojectmanager/qmakestep.cpp +++ b/src/plugins/qmakeprojectmanager/qmakestep.cpp @@ -632,7 +632,7 @@ void QMakeStep::abisChanged() if (BaseQtVersion *qtVersion = QtKitAspect::qtVersion(target()->kit())) { if (qtVersion->hasAbi(Abi::LinuxOS, Abi::AndroidLinuxFlavor)) { - const QString prefix = "ANDROID_ABIS="; + const QString prefix = QString("%1=").arg(Android::Constants::ANDROID_ABIS); QStringList args = m_extraArgs; for (auto it = args.begin(); it != args.end(); ++it) { if (it->startsWith(prefix)) { @@ -643,8 +643,7 @@ void QMakeStep::abisChanged() if (!m_selectedAbis.isEmpty()) args << prefix + '"' + m_selectedAbis.join(' ') + '"'; setExtraArguments(args); - - buildSystem()->setProperty(Android::Constants::ANDROID_ABIS, m_selectedAbis); + buildSystem()->setProperty(Android::Constants::AndroidAbis, m_selectedAbis); } else if (qtVersion->hasAbi(Abi::DarwinOS) && !isIos(target()->kit())) { const QString prefix = "QMAKE_APPLE_DEVICE_ARCHS="; QStringList args = m_extraArgs; diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h index 4fd6fdf159..49505d2ca7 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h +++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h @@ -222,6 +222,9 @@ const char addImagesDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources const char addFontsDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", "Font Files"); const char addSoundsDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", "Sound Files"); const char addShadersDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", "Shader Files"); +const char add3DAssetsDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", "3D Assets"); +const char addQt3DSPresentationsDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", + "Qt 3D Studio Presentations"); const char addCustomEffectDialogDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", "Add Custom Effect"); diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index d452889363..8ca977e742 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -262,7 +262,7 @@ bool DesignerActionManager::externalDragHasSupportedAssets(const QMimeData *mime return false; } -void DesignerActionManager::handleExternalAssetsDrop(const QMimeData *mimeData) const +QHash<QString, QStringList> DesignerActionManager::handleExternalAssetsDrop(const QMimeData *mimeData) const { const QList<AddResourceHandler> handlers = addResourceHandler(); // create suffix to categry and category to operation hashes @@ -283,13 +283,19 @@ void DesignerActionManager::handleExternalAssetsDrop(const QMimeData *mimeData) categoryFiles[category].append(url.toLocalFile()); } + QHash<QString, QStringList> addedCategoryFiles; + // run operations const QStringList categories = categoryFiles.keys(); for (const QString &category : categories) { AddResourceOperation operation = categoryOperation.value(category); QStringList files = categoryFiles.value(category); - operation(files, {}); + bool success = operation(files, {}); + if (success) + addedCategoryFiles.insert(category, files); } + + return addedCategoryFiles; } class VisiblityModelNodeAction : public ModelNodeContextMenuAction diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.h b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.h index e0fe93f601..60e2c7562f 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.h +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.h @@ -137,7 +137,7 @@ public: bool hasModelNodePreviewHandler(const ModelNode &node) const; ModelNodePreviewImageOperation modelNodePreviewOperation(const ModelNode &node) const; bool externalDragHasSupportedAssets(const QMimeData *data) const; - void handleExternalAssetsDrop(const QMimeData *data) const; + QHash<QString, QStringList> handleExternalAssetsDrop(const QMimeData *data) const; private: void addTransitionEffectAction(const TypeName &typeName); diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index 81066504de..ab2853354c 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -995,9 +995,8 @@ Utils::FilePath projectFilePath() static bool addFilesToProject(const QStringList &fileNames, const QString &defaultDirectory) { QString directory = AddImagesDialog::getDirectory(fileNames, defaultDirectory); - if (directory.isEmpty()) - return true; + return false; bool allSuccessful = true; QList<QPair<QString, QString>> copyList; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 025b622789..835ba07b5b 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -23,14 +23,15 @@ ** ****************************************************************************/ -#include "edit3dwidget.h" -#include "edit3dview.h" -#include "edit3dcanvas.h" -#include "edit3dactions.h" - -#include "qmldesignerplugin.h" #include "designersettings.h" +#include "edit3dactions.h" +#include "edit3dcanvas.h" +#include "edit3dview.h" +#include "edit3dwidget.h" +#include "metainfo.h" #include "qmldesignerconstants.h" +#include "qmldesignerplugin.h" +#include "qmlvisualnode.h" #include "viewmanager.h" #include <coreplugin/actionmanager/actionmanager.h> @@ -174,7 +175,20 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent) { const DesignerActionManager &actionManager = QmlDesignerPlugin::instance() ->viewManager().designerActionManager(); - actionManager.handleExternalAssetsDrop(dropEvent->mimeData()); + QHash<QString, QStringList> addedAssets = actionManager.handleExternalAssetsDrop(dropEvent->mimeData()); + + // add 3D assets to 3d editor (QtQuick3D import will be added if missing) + ItemLibraryInfo *itemLibInfo = m_view->model()->metaInfo().itemLibraryInfo(); + + const QStringList added3DAssets = addedAssets.value(ComponentCoreConstants::add3DAssetsDisplayString); + for (const QString &assetPath : added3DAssets) { + QString fileName = QFileInfo(assetPath).baseName(); + fileName = fileName.at(0).toUpper() + fileName.mid(1); // capitalize first letter + QString type = QString("Quick3DAssets.%1.%1").arg(fileName); + QList<ItemLibraryEntry> entriesForType = itemLibInfo->entriesForType(type.toLatin1()); + if (!entriesForType.isEmpty()) // should always be true, but just in case + QmlVisualNode::createQml3DNode(view(), entriesForType.at(0), m_canvas->activeScene()).modelNode(); + } } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp index 8200558d18..4f000e1295 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp @@ -99,8 +99,7 @@ FormEditorItem::FormEditorItem(const QmlItemNode &qmlItemNode, FormEditorScene* m_borderWidth(1.0), m_highlightBoundingRect(false), m_blurContent(false), - m_isContentVisible(true), - m_isFormEditorVisible(true) + m_isContentVisible(true) { setCacheMode(QGraphicsItem::NoCache); setup(); @@ -208,17 +207,6 @@ bool FormEditorItem::isContentVisible() const return m_isContentVisible; } - -bool FormEditorItem::isFormEditorVisible() const -{ - return m_isFormEditorVisible; -} -void FormEditorItem::setFormEditorVisible(bool isVisible) -{ - m_isFormEditorVisible = isVisible; - setVisible(isVisible); -} - QPointF FormEditorItem::center() const { return mapToScene(qmlItemNode().instanceBoundingRect().center()); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.h b/src/plugins/qmldesigner/components/formeditor/formeditoritem.h index 769488aa4a..2213f42a66 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.h @@ -104,9 +104,6 @@ public: void setContentVisible(bool visible); bool isContentVisible() const; - bool isFormEditorVisible() const; - void setFormEditorVisible(bool isVisible); - QPointF center() const; qreal selectionWeigth(const QPointF &point, int iteration); @@ -152,7 +149,6 @@ private: // variables bool m_highlightBoundingRect; bool m_blurContent; bool m_isContentVisible; - bool m_isFormEditorVisible; }; class FormEditorFlowItem : public FormEditorItem diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp index 0ee760b9a1..10e1373dde 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp @@ -91,6 +91,9 @@ void FormEditorView::modelAttached(Model *model) //This function does the setup of the initial FormEditorItem tree in the scene void FormEditorView::setupFormEditorItemTree(const QmlItemNode &qmlItemNode) { + if (!qmlItemNode.hasFormEditorItem()) + return; + if (qmlItemNode.isFlowTransition()) { m_scene->addFormEditorItem(qmlItemNode, FormEditorScene::FlowTransition); if (qmlItemNode.hasNodeParent()) @@ -199,16 +202,6 @@ void FormEditorView::removeNodeFromScene(const QmlItemNode &qmlItemNode) m_currentTool->itemsAboutToRemoved(removedItemList); } -void FormEditorView::hideNodeFromScene(const QmlItemNode &qmlItemNode) -{ - if (FormEditorItem *item = m_scene->itemForQmlItemNode(qmlItemNode)) { - QList<FormEditorItem*> removedItems = scene()->itemsForQmlItemNodes(qmlItemNode.allSubModelNodes()); - removedItems.append(item); - m_currentTool->itemsAboutToRemoved(removedItems); - item->setFormEditorVisible(false); - } -} - void FormEditorView::createFormEditorWidget() { m_formEditorWidget = QPointer<FormEditorWidget>(new FormEditorWidget(this)); @@ -248,10 +241,7 @@ void FormEditorView::temporaryBlockView(int duration) void FormEditorView::nodeCreated(const ModelNode &node) { - //If the node has source for components/custom parsers we ignore it. - if (QmlItemNode::isValidQmlItemNode(node) && node.nodeSourceType() == ModelNode::NodeWithoutSource) //only setup QmlItems - setupFormEditorItemTree(QmlItemNode(node)); - else if (QmlVisualNode::isFlowTransition(node)) + if (QmlVisualNode::isFlowTransition(node)) setupFormEditorItemTree(QmlItemNode(node)); } @@ -349,8 +339,26 @@ static inline bool hasNodeSourceParent(const ModelNode &node) void FormEditorView::nodeReparented(const ModelNode &node, const NodeAbstractProperty &/*newPropertyParent*/, const NodeAbstractProperty &/*oldPropertyParent*/, AbstractView::PropertyChangeFlags /*propertyChange*/) { - if (hasNodeSourceParent(node)) - hideNodeFromScene(node); + // If node is not connected to scene root, don't do anything yet to avoid duplicated effort, + // as any removal or addition will remove or add descendants as well. + if (!node.isInHierarchy()) + return; + + QmlItemNode itemNode(node); + if (hasNodeSourceParent(node)) { + if (FormEditorItem *item = m_scene->itemForQmlItemNode(itemNode)) { + QList<FormEditorItem *> removed = scene()->itemsForQmlItemNodes(itemNode.allSubModelNodes()); + removed.append(item); + m_currentTool->itemsAboutToRemoved(removed); + removeNodeFromScene(itemNode); + } + } else if (itemNode.isValid() && node.nodeSourceType() == ModelNode::NodeWithoutSource) { + if (!m_scene->itemForQmlItemNode(itemNode)) { + setupFormEditorItemTree(itemNode); + // Simulate selection change to refresh tools + selectedNodesChanged(selectedModelNodes(), {}); + } + } } WidgetInfo FormEditorView::widgetInfo() @@ -603,8 +611,7 @@ void FormEditorView::auxiliaryDataChanged(const ModelNode &node, const PropertyN if (name == "invisible") { if (FormEditorItem *item = scene()->itemForQmlItemNode(QmlItemNode(node))) { bool isInvisible = data.toBool(); - if (item->isFormEditorVisible()) - item->setVisible(!isInvisible); + item->setVisible(!isInvisible); ModelNode newNode(node); if (isInvisible) newNode.deselectNode(); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.h b/src/plugins/qmldesigner/components/formeditor/formeditorview.h index ba2b79df99..04b7d1e83e 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorview.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.h @@ -144,7 +144,6 @@ protected: private: void setupFormEditorItemTree(const QmlItemNode &qmlItemNode); void removeNodeFromScene(const QmlItemNode &qmlItemNode); - void hideNodeFromScene(const QmlItemNode &qmlItemNode); void createFormEditorWidget(); void temporaryBlockView(int duration = 1000); void resetNodeInstanceView(); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp index 1f9fc4487b..6b81d1f92e 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp @@ -597,7 +597,14 @@ void FormEditorWidget::dropEvent(QDropEvent *dropEvent) { const DesignerActionManager &actionManager = QmlDesignerPlugin::instance() ->viewManager().designerActionManager(); - actionManager.handleExternalAssetsDrop(dropEvent->mimeData()); + QHash<QString, QStringList> addedAssets = actionManager.handleExternalAssetsDrop(dropEvent->mimeData()); + + // add image assets to Form Editor + const QStringList addedImages = addedAssets.value(ComponentCoreConstants::addImagesDisplayString); + for (const QString &imgPath : addedImages) { + QmlItemNode::createQmlItemNodeFromImage(m_formEditorView, imgPath, {}, + m_formEditorView->scene()->rootFormEditorItem()->qmlItemNode()); + } } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/formeditor/selectiontool.cpp b/src/plugins/qmldesigner/components/formeditor/selectiontool.cpp index 7230e97a1c..f240b32f76 100644 --- a/src/plugins/qmldesigner/components/formeditor/selectiontool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/selectiontool.cpp @@ -34,6 +34,8 @@ #include <nodemetainfo.h> +#include <utils/algorithm.h> + #include <QGraphicsSceneMouseEvent> #include <QDebug> @@ -242,9 +244,23 @@ void SelectionTool::dragMoveEvent(const QList<QGraphicsItem*> &/*itemList*/, QGr { } -void SelectionTool::itemsAboutToRemoved(const QList<FormEditorItem*> &/*itemList*/) +void SelectionTool::itemsAboutToRemoved(const QList<FormEditorItem*> &itemList) { - + const QList<FormEditorItem *> current = items(); + + QList<FormEditorItem *> remaining = Utils::filtered(current, [&itemList](FormEditorItem *item) { + return !itemList.contains(item); + }); + + if (!remaining.isEmpty()) { + m_selectionIndicator.setItems(remaining); + m_resizeIndicator.setItems(remaining); + m_rotationIndicator.setItems(remaining); + m_anchorIndicator.setItems(remaining); + m_bindingIndicator.setItems(remaining); + } else { + clear(); + } } void SelectionTool::clear() diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp index 3bf7447a34..06b84d83f8 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp @@ -240,35 +240,38 @@ void ItemLibraryView::updateImport3DSupport(const QVariantMap &supportMap) DesignerActionManager *actionManager = &QmlDesignerPlugin::instance()->viewManager().designerActionManager(); - // All things importable by QSSGAssetImportManager are considered to be in the same category - // so we don't get multiple separate import dialogs when different file types are imported. - const QString category = tr("3D Assets"); - if (!m_importableExtensions3DMap.isEmpty()) - actionManager->unregisterAddResourceHandlers(category); + actionManager->unregisterAddResourceHandlers(ComponentCoreConstants::add3DAssetsDisplayString); m_importableExtensions3DMap = extMap; - auto handle3DModel = [this](const QStringList &fileNames, const QString &defaultDir) -> bool { + auto import3DModelOperation = [this](const QStringList &fileNames, const QString &defaultDir) -> bool { auto importDlg = new ItemLibraryAssetImportDialog(fileNames, defaultDir, m_importableExtensions3DMap, m_importOptions3DMap, {}, {}, Core::ICore::mainWindow()); - importDlg->show(); + importDlg->exec(); return true; }; - auto add3DHandler = [&](const QString &category, const QString &ext) { + auto add3DHandler = [&](const QString &group, const QString &ext) { const QString filter = QStringLiteral("*.%1").arg(ext); actionManager->registerAddResourceHandler( - AddResourceHandler(category, filter, handle3DModel, 10)); + AddResourceHandler(group, filter, + import3DModelOperation, 10)); + }; + + const QHash<QString, QString> groupNames { + {"3D Scene", ComponentCoreConstants::add3DAssetsDisplayString}, + {"Qt 3D Studio Presentation", ComponentCoreConstants::addQt3DSPresentationsDisplayString} }; const auto groups = extMap.keys(); for (const auto &group : groups) { const QStringList exts = extMap[group].toStringList(); + const QString grp = groupNames.contains(group) ? groupNames.value(group) : group; for (const auto &ext : exts) - add3DHandler(category, ext); + add3DHandler(grp, ext); } } diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index 5abc38eba6..08dfc9fd03 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -732,17 +732,22 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in ModelNode targetNode = targetProperty.parentModelNode(); NodeMetaInfo metaInfo = targetNode.metaInfo(); TypeName typeName = newModelNode.type(); - const PropertyNameList nameList = targetNode.metaInfo().directPropertyNames(); - for (const auto &propertyName : nameList) { - auto testType = metaInfo.propertyTypeName(propertyName); - if (testType == typeName || newModelNode.isSubclassOf(testType)) { - ChooseFromPropertyListDialog *dialog = nullptr; - dialog = new ChooseFromPropertyListDialog(targetNode, testType, Core::ICore::dialogParent()); - dialog->exec(); - if (dialog->result() == QDialog::Accepted) - targetNode.bindingProperty(dialog->selectedProperty()).setExpression(newModelNode.validId()); - delete dialog; - break; + + // Empty components are not supported and having one as property value is generally + // unstable, so let's not offer user to put a fresh Component into a property + if (typeName != "QtQml.Component") { + const PropertyNameList nameList = targetNode.metaInfo().directPropertyNames(); + for (const auto &propertyName : nameList) { + auto testType = metaInfo.propertyTypeName(propertyName); + if (testType == typeName || newModelNode.isSubclassOf(testType)) { + ChooseFromPropertyListDialog *dialog = nullptr; + dialog = new ChooseFromPropertyListDialog(targetNode, testType, Core::ICore::dialogParent()); + dialog->exec(); + if (dialog->result() == QDialog::Accepted) + targetNode.bindingProperty(dialog->selectedProperty()).setExpression(newModelNode.validId()); + delete dialog; + break; + } } } } @@ -1085,10 +1090,24 @@ void NavigatorTreeModel::moveNodesInteractive(NodeAbstractProperty &parentProper if (modelNode.isValid() && modelNode != parentProperty.parentModelNode() && !modelNode.isAncestorOf(parentProperty.parentModelNode()) - && (modelNode.metaInfo().isSubclassOf(propertyQmlType) || propertyQmlType == "alias")) { + && (modelNode.metaInfo().isSubclassOf(propertyQmlType) + || propertyQmlType == "alias" + || parentProperty.name() == "data" + || (parentProperty.parentModelNode().metaInfo().defaultPropertyName() == parentProperty.name() + && propertyQmlType == "<cpp>.QQmlComponent"))) { //### todo: allowing alias is just a heuristic //once the MetaInfo is part of instances we can do this right + // We assume above that "data" property in parent accepts all types. + // This is a workaround for Component parents to accept children, even though they + // do not have an actual "data" property or apparently any other default property. + // When the actual reparenting happens, model will create the "data" property if + // it is missing. + + // We allow move even if target property type doesn't match, if the target property + // is the default property of the parent and is of Component type. + // In that case an implicit component will be created. + bool nodeCanBeMovedToParentProperty = removeModelNodeFromNodeProperty(parentProperty, modelNode); if (nodeCanBeMovedToParentProperty) { reparentModelNodeToNodeProperty(parentProperty, modelNode); diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp b/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp index aecab7c42c..2664f2c7a9 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp @@ -861,6 +861,7 @@ void QmlAnchorBindingProxy::anchorVertical() m_qmlItemNode.anchors().setAnchor(AnchorLineVerticalCenter, m_verticalTarget, AnchorLineVerticalCenter); } + backupPropertyAndRemove(modelNode(), "y"); m_locked = false; } @@ -874,6 +875,7 @@ void QmlAnchorBindingProxy::anchorHorizontal() } else if (m_relativeVerticalTarget == Center) { m_qmlItemNode.anchors().setAnchor(AnchorLineHorizontalCenter, m_horizontalTarget, AnchorLineHorizontalCenter); } + backupPropertyAndRemove(modelNode(), "x"); m_locked = false; } @@ -993,6 +995,7 @@ void QmlAnchorBindingProxy::setVerticalCentered(bool centered) if (!centered) { m_qmlItemNode.anchors().removeAnchor(AnchorLineVerticalCenter); m_qmlItemNode.anchors().removeMargin(AnchorLineVerticalCenter); + restoreProperty(m_qmlItemNode, "y"); } else { m_relativeVerticalTarget = Center; @@ -1020,6 +1023,7 @@ void QmlAnchorBindingProxy::setHorizontalCentered(bool centered) if (!centered) { m_qmlItemNode.anchors().removeAnchor(AnchorLineHorizontalCenter); m_qmlItemNode.anchors().removeMargin(AnchorLineHorizontalCenter); + restoreProperty(m_qmlItemNode, "x"); } else { m_relativeHorizontalTarget = Center; diff --git a/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp b/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp index 12fff40c79..997852cfb2 100644 --- a/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp +++ b/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp @@ -46,6 +46,7 @@ #include <qmlstate.h> #include <annotationeditor/annotationeditor.h> #include <utils/algorithm.h> +#include <utils/qtcassert.h> namespace QmlDesigner { @@ -103,6 +104,7 @@ void StatesEditorView::removeState(int nodeId) const auto propertyChanges = modelState.propertyChanges(); for (const QmlPropertyChanges &change : propertyChanges) { const ModelNode target = change.target(); + QTC_ASSERT(target.isValid(), continue); if (target.locked()) lockedTargets.push_back(target.id()); } diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp index 704d7c2b0d..ad2dfd88f6 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp @@ -70,14 +70,6 @@ namespace QmlDesigner { -static int deleteKey() -{ - if (Utils::HostOsInfo::isMacHost()) - return Qt::Key_Backspace; - - return Qt::Key_Delete; -} - QList<QmlTimelineKeyframeGroup> allTimelineFrames(const QmlTimeline &timeline) { QList<QmlTimelineKeyframeGroup> returnList; @@ -674,7 +666,7 @@ void TimelineGraphicsScene::keyReleaseEvent(QKeyEvent *keyEvent) return; } - if (deleteKey() == keyEvent->key()) + if (TimelineUtils::isDeleteKey(keyEvent->key())) handleKeyframeDeletion(); QGraphicsScene::keyReleaseEvent(keyEvent); @@ -838,7 +830,7 @@ bool TimelineGraphicsScene::event(QEvent *event) { switch (event->type()) { case QEvent::ShortcutOverride: - if (static_cast<QKeyEvent *>(event)->key() == deleteKey()) { + if (TimelineUtils::isDeleteKey(static_cast<QKeyEvent *>(event)->key())) { QGraphicsScene::keyPressEvent(static_cast<QKeyEvent *>(event)); event->accept(); return true; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineutils.h b/src/plugins/qmldesigner/components/timelineeditor/timelineutils.h index ec0f4cec5b..b31bd8f422 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineutils.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineutils.h @@ -39,6 +39,11 @@ namespace TimelineUtils { enum class Side { Top, Right, Bottom, Left }; +inline bool isDeleteKey(int key) +{ + return (key == Qt::Key_Backspace) || (key == Qt::Key_Delete); +} + template<typename T> inline T clamp(const T &value, const T &lo, const T &hi) { diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp index ab82744cc6..e2b9578356 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp @@ -39,6 +39,7 @@ #include "timelineplaceholder.h" #include "timelinepropertyitem.h" #include "timelinesectionitem.h" +#include "timelineutils.h" #include <designdocumentview.h> #include <exception.h> @@ -73,14 +74,6 @@ namespace QmlDesigner { -static int deleteKey() -{ - if (Utils::HostOsInfo::isMacHost()) - return Qt::Key_Backspace; - - return Qt::Key_Delete; -} - TransitionEditorGraphicsScene::TransitionEditorGraphicsScene(TransitionEditorWidget *parent) : AbstractScrollGraphicsScene(parent) , m_parent(parent) @@ -459,7 +452,7 @@ bool TransitionEditorGraphicsScene::event(QEvent *event) { switch (event->type()) { case QEvent::ShortcutOverride: - if (static_cast<QKeyEvent *>(event)->key() == deleteKey()) { + if (TimelineUtils::isDeleteKey(static_cast<QKeyEvent *>(event)->key())) { QGraphicsScene::keyPressEvent(static_cast<QKeyEvent *>(event)); event->accept(); return true; diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp index 97dc457f39..ccf84152b5 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp @@ -98,6 +98,10 @@ void TransitionEditorView::nodeRemoved(const ModelNode & removedNode, { if (parentProperty.name() == "transitions") widget()->updateData(removedNode); + + const ModelNode parent = parentProperty.parentModelNode(); + if (parent.isValid() && parent.metaInfo().isSubclassOf("QtQuick.Transition")) + asyncUpdate(parent); } void TransitionEditorView::nodeReparented(const ModelNode &node, @@ -110,7 +114,6 @@ void TransitionEditorView::nodeReparented(const ModelNode &node, const ModelNode parent = newPropertyParent.parentModelNode(); - // qDebug() << Q_FUNC_INFO << parent; if (parent.isValid() && parent.metaInfo().isValid() && parent.metaInfo().isSubclassOf("QtQuick.Transition")) { asyncUpdate(parent); diff --git a/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h b/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h index c44467f002..8244fbb489 100644 --- a/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h +++ b/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h @@ -100,7 +100,8 @@ class QMLDESIGNERCORE_EXPORT ItemLibraryInfo : public QObject public: QList<ItemLibraryEntry> entries() const; - QList<ItemLibraryEntry> entriesForType(const QByteArray &typeName, int majorVersion, int minorVersion) const; + QList<ItemLibraryEntry> entriesForType(const QByteArray &typeName, int majorVersion = 1, + int minorVersion = 0) const; void addEntries(const QList<ItemLibraryEntry> &entries, bool overwriteDuplicate = false); bool containsEntry(const ItemLibraryEntry &entry); diff --git a/src/plugins/qmldesigner/designercore/include/nodehints.h b/src/plugins/qmldesigner/designercore/include/nodehints.h index a22dd40f40..16fe18320c 100644 --- a/src/plugins/qmldesigner/designercore/include/nodehints.h +++ b/src/plugins/qmldesigner/designercore/include/nodehints.h @@ -62,6 +62,7 @@ public: bool canBeDroppedInView3D() const; bool isMovable() const; bool isResizable() const; + bool hasFormEditorItem() const; bool isStackedContainer() const; bool canBeReparentedTo(const ModelNode &potenialParent); QString indexPropertyForStackedContainer() const; diff --git a/src/plugins/qmldesigner/designercore/include/qmlitemnode.h b/src/plugins/qmldesigner/designercore/include/qmlitemnode.h index 579a4ca7b8..9be07edfbf 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlitemnode.h +++ b/src/plugins/qmldesigner/designercore/include/qmlitemnode.h @@ -105,6 +105,7 @@ public: bool modelIsResizable() const; bool modelIsRotatable() const; bool modelIsInLayout() const; + bool hasFormEditorItem() const; QRectF instanceBoundingRect() const; QRectF instanceSceneBoundingRect() const; diff --git a/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h b/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h index 59f99bbe39..7b1cfbe86e 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h +++ b/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h @@ -128,14 +128,13 @@ public: virtual bool isBlocked(const PropertyName &propName) const; friend auto qHash(const QmlObjectNode &node) { return qHash(node.modelNode()); } + QList<QmlModelState> allDefinedStates() const; + QList<QmlModelStateOperation> allInvalidStateOperations() const; protected: NodeInstance nodeInstance() const; QmlObjectNode nodeForInstance(const NodeInstance &instance) const; QmlItemNode itemForInstance(const NodeInstance &instance) const; - -protected: - QList<QmlModelState> allDefinedStates() const; }; QMLDESIGNERCORE_EXPORT QList<ModelNode> toModelNodeList(const QList<QmlObjectNode> &fxObjectNodeList); diff --git a/src/plugins/qmldesigner/designercore/include/qmlstate.h b/src/plugins/qmldesigner/designercore/include/qmlstate.h index bd0aad854b..ce9a8bf3f7 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlstate.h +++ b/src/plugins/qmldesigner/designercore/include/qmlstate.h @@ -50,6 +50,7 @@ public: QList<QmlModelStateOperation> stateOperations(const ModelNode &node) const; QList<QmlPropertyChanges> propertyChanges() const; QList<QmlModelStateOperation> stateOperations() const; + QList<QmlModelStateOperation> allInvalidStateOperations() const; bool hasPropertyChanges(const ModelNode &node) const; diff --git a/src/plugins/qmldesigner/designercore/include/qmltimelinekeyframegroup.h b/src/plugins/qmldesigner/designercore/include/qmltimelinekeyframegroup.h index a20eeae55e..6aee5ea0d5 100644 --- a/src/plugins/qmldesigner/designercore/include/qmltimelinekeyframegroup.h +++ b/src/plugins/qmldesigner/designercore/include/qmltimelinekeyframegroup.h @@ -72,6 +72,7 @@ public: static bool isValidKeyframe(const ModelNode &node); static bool checkKeyframesType(const ModelNode &node); static QmlTimelineKeyframeGroup keyframeGroupForKeyframe(const ModelNode &node); + static QList<QmlTimelineKeyframeGroup> allInvalidTimelineKeyframeGroups(AbstractView *view); void moveAllKeyframes(qreal offset); void scaleAllKeyframes(qreal factor); @@ -84,6 +85,8 @@ public: void toogleRecording(bool b) const; QmlTimeline timeline() const; + + bool isDangling() const; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 6ecde66a30..078a74a534 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -644,7 +644,7 @@ void NodeInstanceView::nodeSourceChanged(const ModelNode &node, const QString & m_nodeInstanceServer->changeNodeSource(changeNodeSourceCommand); // Puppet doesn't deal with node source changes properly, so just reset the puppet for now - delayedRestartProcess(); // TODO: Remove this once the issue is properly fixed (QDS-4955) + resetPuppet(); // TODO: Remove this once the issue is properly fixed (QDS-4955) } } diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp index 26b06cdc42..84cc565074 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp @@ -183,6 +183,11 @@ bool NodeHints::isResizable() const return evaluateBooleanExpression("isResizable", true); } +bool NodeHints::hasFormEditorItem() const +{ + return evaluateBooleanExpression("hasFormEditorItem", true); +} + bool NodeHints::isStackedContainer() const { if (!isValid()) diff --git a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp index 03ae0ecd1d..02ecb64efd 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp @@ -395,6 +395,11 @@ bool QmlItemNode::modelIsInLayout() const return false; } +bool QmlItemNode::hasFormEditorItem() const +{ + return NodeHints::fromModelNode(modelNode()).hasFormEditorItem(); +} + QRectF QmlItemNode::instanceBoundingRect() const { return QRectF(QPointF(0, 0), nodeInstance().size()); diff --git a/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp index 556a7624ba..55305c72d1 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp @@ -328,16 +328,58 @@ QmlPropertyChanges QmlObjectNode::propertyChangeForCurrentState() const static void removeStateOperationsForChildren(const QmlObjectNode &node) { if (node.isValid()) { - foreach (QmlModelStateOperation stateOperation, node.allAffectingStatesOperations()) { + for (QmlModelStateOperation stateOperation : node.allAffectingStatesOperations()) { stateOperation.modelNode().destroy(); //remove of belonging StatesOperations } - foreach (const QmlObjectNode &childNode, node.modelNode().directSubModelNodes()) { + for (const QmlObjectNode &childNode : node.modelNode().directSubModelNodes()) { removeStateOperationsForChildren(childNode); } } } +static void removeAnimationsFromAnimation(const ModelNode &animation) +{ + QTC_ASSERT(animation.isValid(), return); + + const QList<ModelNode> propertyAnimations = animation.subModelNodesOfType( + "QtQuick.PropertyAnimation"); + + for (const ModelNode &child : propertyAnimations) { + if (!child.hasBindingProperty("target")) { + ModelNode nonConst = animation; + nonConst.destroy(); + return; + } + } +} + +static void removeAnimationsFromTransition(const ModelNode &transition, const QmlObjectNode &node) +{ + QTC_ASSERT(node.isValid(), return); + QTC_ASSERT(transition.isValid(), return); + + const auto children = transition.directSubModelNodes(); + for (const ModelNode ¶llel : children) + removeAnimationsFromAnimation(parallel); +} + +static void removeDanglingAnimationsFromTransitions(const QmlObjectNode &node) +{ + QTC_ASSERT(node.isValid(), return); + + auto root = node.view()->rootModelNode(); + + if (root.isValid() && root.hasProperty("transitions")) { + NodeAbstractProperty transitions = root.nodeAbstractProperty("transitions"); + if (transitions.isValid()) { + const auto transitionNodes = transitions.directSubNodes(); + for (const auto &transition : transitionNodes) + removeAnimationsFromTransition(transition, node); + } + } +} + static void removeAliasExports(const QmlObjectNode &node) { @@ -368,6 +410,14 @@ static void removeLayerEnabled(const ModelNode &node) } } +static void deleteAllReferencesToNodeAndChildren(const ModelNode &node) +{ + BindingProperty::deleteAllReferencesTo(node); + const auto subNodes = node.allSubModelNodes(); + for (const ModelNode &child : subNodes) + BindingProperty::deleteAllReferencesTo(child); +} + /*! Deletes this object's node and its dependencies from the model. Everything that belongs to this Object, the ModelNode, and ChangeOperations @@ -406,7 +456,9 @@ void QmlObjectNode::destroy() } removeStateOperationsForChildren(modelNode()); - BindingProperty::deleteAllReferencesTo(modelNode()); + deleteAllReferencesToNodeAndChildren(modelNode()); + + removeDanglingAnimationsFromTransitions(modelNode()); QmlFlowViewNode root(view()->rootModelNode()); @@ -515,6 +567,16 @@ QList<QmlModelState> QmlObjectNode::allDefinedStates() const return returnList; } +QList<QmlModelStateOperation> QmlObjectNode::allInvalidStateOperations() const +{ + QList<QmlModelStateOperation> result; + + const auto allStates = allDefinedStates(); + for (const auto &state : allStates) + result.append(state.allInvalidStateOperations()); + return result; +} + /*! Removes a variant property of the object specified by \a name from the diff --git a/src/plugins/qmldesigner/designercore/model/qmlstate.cpp b/src/plugins/qmldesigner/designercore/model/qmlstate.cpp index ea15ba1d81..26fff118d2 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlstate.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlstate.cpp @@ -34,6 +34,7 @@ #include "qmlitemnode.h" #include "annotation.h" +#include <utils/algorithm.h> #include <utils/qtcassert.h> namespace QmlDesigner { @@ -136,6 +137,12 @@ QList<QmlModelStateOperation> QmlModelState::stateOperations() const return returnList; } +QList<QmlModelStateOperation> QmlModelState::allInvalidStateOperations() const +{ + return Utils::filtered(stateOperations(), [](const QmlModelStateOperation &operation) { + return !operation.target().isValid(); + }); +} /*! Adds a change set for \a node to this state, but only if it does not diff --git a/src/plugins/qmldesigner/designercore/model/qmltimelinekeyframegroup.cpp b/src/plugins/qmldesigner/designercore/model/qmltimelinekeyframegroup.cpp index ad0855ad95..85eb51de44 100644 --- a/src/plugins/qmldesigner/designercore/model/qmltimelinekeyframegroup.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmltimelinekeyframegroup.cpp @@ -153,6 +153,13 @@ QmlTimeline QmlTimelineKeyframeGroup::timeline() const return {}; } +bool QmlTimelineKeyframeGroup::isDangling() const +{ + QTC_ASSERT(isValid(), return false); + + return !target().isValid() || keyframes().isEmpty(); +} + void QmlTimelineKeyframeGroup::setValue(const QVariant &value, qreal currentFrame) { QTC_ASSERT(isValid(), return ); @@ -294,6 +301,22 @@ QmlTimelineKeyframeGroup QmlTimelineKeyframeGroup::keyframeGroupForKeyframe(cons return QmlTimelineKeyframeGroup(); } +QList<QmlTimelineKeyframeGroup> QmlTimelineKeyframeGroup::allInvalidTimelineKeyframeGroups(AbstractView *view) +{ + QList<QmlTimelineKeyframeGroup> ret; + + QTC_ASSERT(view, return ret); + QTC_ASSERT(view->model(), return ret); + QTC_ASSERT(view->rootModelNode().isValid(), return ret); + + const auto groups = view->rootModelNode().subModelNodesOfType("QtQuick.Timeline.KeyframeGroup"); + for (const QmlTimelineKeyframeGroup &group : groups) { + if (group.isDangling()) + ret.append(group); + } + return ret; +} + void QmlTimelineKeyframeGroup::moveAllKeyframes(qreal offset) { for (const ModelNode &childNode : modelNode().defaultNodeListProperty().toModelNodeList()) { diff --git a/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp index 802740fc1d..544471b03c 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp @@ -309,7 +309,11 @@ QmlObjectNode QmlVisualNode::createQmlObjectNode(AbstractView *view, } } - newQmlObjectNode = QmlObjectNode(view->createModelNode(itemLibraryEntry.typeName(), majorVersion, minorVersion, propertyPairList)); + ModelNode::NodeSourceType nodeSourceType = ModelNode::NodeWithoutSource; + if (itemLibraryEntry.typeName() == "QtQml.Component") + nodeSourceType = ModelNode::NodeWithComponentSource; + + newQmlObjectNode = QmlObjectNode(view->createModelNode(itemLibraryEntry.typeName(), majorVersion, minorVersion, propertyPairList, {}, {}, nodeSourceType)); } else { newQmlObjectNode = createQmlObjectNodeFromSource(view, itemLibraryEntry.qmlSource(), position); } diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp index 0209c7a0a7..c678f3b06d 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp @@ -1191,9 +1191,11 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, if (isComponentType(typeName) || isImplicitComponent) setupComponentDelayed(modelNode, differenceHandler.isAmender()); - - if (isCustomParserType(typeName)) + else if (isCustomParserType(typeName)) setupCustomParserNodeDelayed(modelNode, differenceHandler.isAmender()); + else if (!modelNode.nodeSource().isEmpty() || modelNode.nodeSourceType() != ModelNode::NodeWithoutSource) + clearImplicitComponentDelayed(modelNode, differenceHandler.isAmender()); + context->enterScope(astNode); @@ -2079,18 +2081,23 @@ void TextToModelMerger::setupComponent(const ModelNode &node) QString componentText = m_rewriterView->extractText({node}).value(node); - if (componentText.isEmpty()) + if (componentText.isEmpty() && node.nodeSource().isEmpty()) return; QString result = extractComponentFromQml(componentText); - if (result.isEmpty()) + if (result.isEmpty() && node.nodeSource().isEmpty()) return; //No object definition found if (node.nodeSource() != result) ModelNode(node).setNodeSource(result, ModelNode::NodeWithComponentSource); } +void TextToModelMerger::clearImplicitComponent(const ModelNode &node) +{ + ModelNode(node).setNodeSource({}, ModelNode::NodeWithoutSource); +} + void TextToModelMerger::collectLinkErrors(QList<DocumentMessage> *errors, const ReadingContext &ctxt) { foreach (const QmlJS::DiagnosticMessage &diagnosticMessage, ctxt.diagnosticLinkMessages()) { @@ -2237,9 +2244,9 @@ void TextToModelMerger::addIsoIconQrcMapping(const QUrl &fileUrl) } while (dir.cdUp()); } -void TextToModelMerger::setupComponentDelayed(const ModelNode &node, bool synchron) +void TextToModelMerger::setupComponentDelayed(const ModelNode &node, bool synchronous) { - if (synchron) { + if (synchronous) { setupComponent(node); } else { m_setupComponentList.insert(node); @@ -2254,7 +2261,7 @@ void TextToModelMerger::setupCustomParserNode(const ModelNode &node) QString modelText = m_rewriterView->extractText({node}).value(node); - if (modelText.isEmpty()) + if (modelText.isEmpty() && node.nodeSource().isEmpty()) return; if (node.nodeSource() != modelText) @@ -2262,11 +2269,11 @@ void TextToModelMerger::setupCustomParserNode(const ModelNode &node) } -void TextToModelMerger::setupCustomParserNodeDelayed(const ModelNode &node, bool synchron) +void TextToModelMerger::setupCustomParserNodeDelayed(const ModelNode &node, bool synchronous) { Q_ASSERT(isCustomParserType(node.type())); - if (synchron) { + if (synchronous) { setupCustomParserNode(node); } else { m_setupCustomParserList.insert(node); @@ -2274,15 +2281,32 @@ void TextToModelMerger::setupCustomParserNodeDelayed(const ModelNode &node, bool } } +void TextToModelMerger::clearImplicitComponentDelayed(const ModelNode &node, bool synchronous) +{ + Q_ASSERT(!isComponentType(node.type())); + + if (synchronous) { + clearImplicitComponent(node); + } else { + m_clearImplicitComponentList.insert(node); + m_setupTimer.start(); + } +} + void TextToModelMerger::delayedSetup() { - foreach (const ModelNode node, m_setupComponentList) + for (const ModelNode &node : std::as_const(m_setupComponentList)) setupComponent(node); - foreach (const ModelNode node, m_setupCustomParserList) + for (const ModelNode &node : std::as_const(m_setupCustomParserList)) setupCustomParserNode(node); + + for (const ModelNode &node : std::as_const(m_clearImplicitComponentList)) + clearImplicitComponent(node); + m_setupCustomParserList.clear(); m_setupComponentList.clear(); + m_clearImplicitComponentList.clear(); } QSet<QPair<QString, QString> > TextToModelMerger::qrcMapping() const diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.h b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.h index f2e308d02f..ed48d4ebc8 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.h +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.h @@ -128,8 +128,9 @@ public: ReadingContext *context, DifferenceHandler &differenceHandler); - void setupComponentDelayed(const ModelNode &node, bool synchron); - void setupCustomParserNodeDelayed(const ModelNode &node, bool synchron); + void setupComponentDelayed(const ModelNode &node, bool synchronous); + void setupCustomParserNodeDelayed(const ModelNode &node, bool synchronous); + void clearImplicitComponentDelayed(const ModelNode &node, bool synchronous); void delayedSetup(); @@ -140,6 +141,7 @@ public: private: void setupCustomParserNode(const ModelNode &node); void setupComponent(const ModelNode &node); + void clearImplicitComponent(const ModelNode &node); void collectLinkErrors(QList<DocumentMessage> *errors, const ReadingContext &ctxt); void collectImportErrors(QList<DocumentMessage> *errors); void collectSemanticErrorsAndWarnings(QList<DocumentMessage> *errors, @@ -163,6 +165,7 @@ private: QTimer m_setupTimer; QSet<ModelNode> m_setupComponentList; QSet<ModelNode> m_setupCustomParserList; + QSet<ModelNode> m_clearImplicitComponentList; QmlJS::ViewerContext m_vContext; QSet<QPair<QString, QString> > m_qrcMapping; QSet<QmlJS::ImportKey> m_possibleImportKeys; diff --git a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo index bbd91d40a8..019be09973 100644 --- a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo +++ b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo @@ -432,4 +432,51 @@ MetaInfo { } } + Type { + name: "QtQml.Component" + icon: ":/qtquickplugin/images/item-icon16.png" + + Hints { + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + } + + ItemLibraryEntry { + name: "Component" + category: "e.Qt Quick - Component" + libraryIcon: ":/qtquickplugin/images/item-icon.png" + version: "2.0" + } + } + + Type { + name: "QtQuick.Loader" + icon: ":/qtquickplugin/images/item-icon16.png" + + ItemLibraryEntry { + name: "Loader" + category: "e.Qt Quick - Component" + libraryIcon: ":/qtquickplugin/images/item-icon.png" + version: "2.0" + Property { name: "width"; type: "int"; value: 200; } + Property { name: "height"; type: "int"; value: 200; } + } + } + + Type { + name: "QtQuick.Repeater" + icon: ":/qtquickplugin/images/item-icon16.png" + + Hints { + canBeDroppedInFormEditor: false + hasFormEditorItem: false + } + + ItemLibraryEntry { + name: "Repeater" + category: "e.Qt Quick - Component" + libraryIcon: ":/qtquickplugin/images/item-icon.png" + version: "2.0" + } + } } diff --git a/src/plugins/qmljseditor/qmljseditorplugin.cpp b/src/plugins/qmljseditor/qmljseditorplugin.cpp index e0d5703102..59cafdcfc1 100644 --- a/src/plugins/qmljseditor/qmljseditorplugin.cpp +++ b/src/plugins/qmljseditor/qmljseditorplugin.cpp @@ -110,6 +110,7 @@ QmlJSEditorPlugin::QmlJSEditorPlugin() QmlJSEditorPlugin::~QmlJSEditorPlugin() { + delete QmlJS::Icons::instance(); // delete object held by singleton delete d; d = nullptr; m_instance = nullptr; @@ -226,8 +227,6 @@ void QmlJSEditorPlugin::extensionsInitialized() ExtensionSystem::IPlugin::ShutdownFlag QmlJSEditorPlugin::aboutToShutdown() { - delete QmlJS::Icons::instance(); // delete object held by singleton - return IPlugin::aboutToShutdown(); } diff --git a/src/plugins/texteditor/basefilefind.cpp b/src/plugins/texteditor/basefilefind.cpp index abe8777b34..f0fe19cc34 100644 --- a/src/plugins/texteditor/basefilefind.cpp +++ b/src/plugins/texteditor/basefilefind.cpp @@ -236,7 +236,7 @@ static QString displayText(const QString &line) QString result = line; auto end = result.end(); for (auto it = result.begin(); it != end; ++it) { - if (!it->isPrint()) + if (!it->isSpace() && !it->isPrint()) *it = QChar('?'); } return result; diff --git a/src/plugins/valgrind/memcheckerrorview.cpp b/src/plugins/valgrind/memcheckerrorview.cpp index 0e45b76d43..19e3199b72 100644 --- a/src/plugins/valgrind/memcheckerrorview.cpp +++ b/src/plugins/valgrind/memcheckerrorview.cpp @@ -46,6 +46,7 @@ #include <QAction> +using namespace Utils; using namespace Valgrind::XmlProtocol; namespace Valgrind { @@ -56,10 +57,10 @@ MemcheckErrorView::MemcheckErrorView(QWidget *parent) { m_suppressAction = new QAction(this); m_suppressAction->setText(tr("Suppress Error")); - const QIcon icon = Utils::Icon({ - {":/utils/images/eye_open.png", Utils::Theme::TextColorNormal}, - {":/valgrind/images/suppressoverlay.png", Utils::Theme::IconsErrorColor}}, - Utils::Icon::Tint | Utils::Icon::PunchEdges).icon(); + const QIcon icon = Icon({ + {":/utils/images/eye_open.png", Theme::TextColorNormal}, + {":/valgrind/images/suppressoverlay.png", Theme::IconsErrorColor}}, + Icon::Tint | Icon::PunchEdges).icon(); m_suppressAction->setIcon(icon); m_suppressAction->setShortcuts({QKeySequence::Delete, QKeySequence::Backspace}); m_suppressAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); @@ -69,12 +70,12 @@ MemcheckErrorView::MemcheckErrorView(QWidget *parent) MemcheckErrorView::~MemcheckErrorView() = default; -void MemcheckErrorView::setDefaultSuppressionFile(const QString &suppFile) +void MemcheckErrorView::setDefaultSuppressionFile(const FilePath &suppFile) { m_defaultSuppFile = suppFile; } -QString MemcheckErrorView::defaultSuppressionFile() const +FilePath MemcheckErrorView::defaultSuppressionFile() const { return m_defaultSuppFile; } diff --git a/src/plugins/valgrind/memcheckerrorview.h b/src/plugins/valgrind/memcheckerrorview.h index 6a22dbf907..888140fc45 100644 --- a/src/plugins/valgrind/memcheckerrorview.h +++ b/src/plugins/valgrind/memcheckerrorview.h @@ -28,6 +28,8 @@ #include <debugger/analyzer/detailederrorview.h> +#include <utils/filepath.h> + #include <QListView> namespace Valgrind { @@ -43,8 +45,8 @@ public: MemcheckErrorView(QWidget *parent = nullptr); ~MemcheckErrorView() override; - void setDefaultSuppressionFile(const QString &suppFile); - QString defaultSuppressionFile() const; + void setDefaultSuppressionFile(const Utils::FilePath &suppFile); + Utils::FilePath defaultSuppressionFile() const; ValgrindBaseSettings *settings() const { return m_settings; } void settingsChanged(ValgrindBaseSettings *settings); @@ -53,7 +55,7 @@ private: QList<QAction *> customActions() const override; QAction *m_suppressAction; - QString m_defaultSuppFile; + Utils::FilePath m_defaultSuppFile; ValgrindBaseSettings *m_settings = nullptr; }; diff --git a/src/plugins/valgrind/memchecktool.cpp b/src/plugins/valgrind/memchecktool.cpp index 95bfb7e756..ff97b153b6 100644 --- a/src/plugins/valgrind/memchecktool.cpp +++ b/src/plugins/valgrind/memchecktool.cpp @@ -128,7 +128,7 @@ public: void start() override; void stop() override; - const QStringList suppressionFiles() const; + const Utils::FilePaths suppressionFiles() const; signals: void internalParserError(const QString &errorString); @@ -212,8 +212,8 @@ QStringList MemcheckToolRunner::toolArguments() const } arguments << "--leak-check=" + leakCheckValue; - for (const QString &file : m_settings.suppressions.value()) - arguments << QString("--suppressions=%1").arg(file); + for (const FilePath &file : m_settings.suppressions.value()) + arguments << QString("--suppressions=%1").arg(file.path()); arguments << QString("--num-callers=%1").arg(m_settings.numCallers.value()); @@ -225,7 +225,7 @@ QStringList MemcheckToolRunner::toolArguments() const return arguments; } -const QStringList MemcheckToolRunner::suppressionFiles() const +const FilePaths MemcheckToolRunner::suppressionFiles() const { return m_settings.suppressions.value(); } @@ -991,15 +991,15 @@ void MemcheckToolPrivate::setupRunner(MemcheckToolRunner *runTool) clearErrorView(); m_loadExternalLogFile->setDisabled(true); - QString dir = runControl->project()->projectDirectory().toString() + '/'; + const FilePath dir = runControl->project()->projectDirectory(); const QString name = runTool->executable().fileName(); - m_errorView->setDefaultSuppressionFile(dir + name + ".supp"); + m_errorView->setDefaultSuppressionFile(dir.pathAppended(name + ".supp")); - const QStringList suppressionFiles = runTool->suppressionFiles(); - for (const QString &file : suppressionFiles) { - QAction *action = m_filterMenu->addAction(FilePath::fromString(file).fileName()); - action->setToolTip(file); + const FilePaths suppressionFiles = runTool->suppressionFiles(); + for (const FilePath &file : suppressionFiles) { + QAction *action = m_filterMenu->addAction(file.fileName()); + action->setToolTip(file.toUserOutput()); connect(action, &QAction::triggered, this, [file] { EditorManager::openEditorAt(file, 0); }); @@ -1425,13 +1425,13 @@ void HeobDialog::updateProfile() int leakRecording = settings->value(heobLeakRecordingC, 2).toInt(); bool attach = settings->value(heobAttachC, false).toBool(); const QString extraArgs = settings->value(heobExtraArgsC).toString(); - QString path = settings->value(heobPathC).toString(); + FilePath path = FilePath::fromVariant(settings->value(heobPathC)); settings->endGroup(); if (path.isEmpty()) { const QString heobPath = QStandardPaths::findExecutable("heob32.exe"); if (!heobPath.isEmpty()) - path = QFileInfo(heobPath).path(); + path = FilePath::fromUserInput(heobPath); } m_xmlEdit->setText(xml); @@ -1444,7 +1444,7 @@ void HeobDialog::updateProfile() m_leakRecordingCombo->setCurrentIndex(leakRecording); m_attachCheck->setChecked(attach); m_extraArgsEdit->setText(extraArgs); - m_pathChooser->setPath(path); + m_pathChooser->setFilePath(path); } void HeobDialog::updateEnabled() diff --git a/src/plugins/valgrind/suppressiondialog.cpp b/src/plugins/valgrind/suppressiondialog.cpp index 4f5b427f09..bde173edcc 100644 --- a/src/plugins/valgrind/suppressiondialog.cpp +++ b/src/plugins/valgrind/suppressiondialog.cpp @@ -51,6 +51,7 @@ #include <QPlainTextEdit> #include <QPushButton> +using namespace Utils; using namespace Valgrind::XmlProtocol; namespace Valgrind { @@ -63,7 +64,7 @@ static QString suppressionText(const Error &error) // workaround: https://bugs.kde.org/show_bug.cgi?id=255822 if (sup.frames().size() >= 24) sup.setFrames(sup.frames().mid(0, 23)); - QTC_ASSERT(sup.frames().size() < 24, /**/); + QTC_CHECK(sup.frames().size() < 24); // try to set some useful name automatically, instead of "insert_name_here" // we take the last stack frame and append the suppression kind, e.g.: @@ -117,7 +118,7 @@ SuppressionDialog::SuppressionDialog(MemcheckErrorView *view, const QList<Error> m_settings(view->settings()), m_cleanupIfCanceled(false), m_errors(errors), - m_fileChooser(new Utils::PathChooser(this)), + m_fileChooser(new PathChooser(this)), m_suppressionEdit(new QPlainTextEdit(this)) { setWindowTitle(tr("Save Suppression")); @@ -140,27 +141,23 @@ SuppressionDialog::SuppressionDialog(MemcheckErrorView *view, const QList<Error> formLayout->addRow(m_suppressionEdit); formLayout->addRow(m_buttonBox); - QFile defaultSuppFile(view->defaultSuppressionFile()); - if (!defaultSuppFile.exists()) { - if (defaultSuppFile.open(QIODevice::WriteOnly)) { - defaultSuppFile.close(); - m_cleanupIfCanceled = true; - } - } + const FilePath defaultSuppFile = view->defaultSuppressionFile(); + if (!defaultSuppFile.exists() && defaultSuppFile.ensureExistingFile()) + m_cleanupIfCanceled = true; - m_fileChooser->setExpectedKind(Utils::PathChooser::File); + m_fileChooser->setExpectedKind(PathChooser::File); m_fileChooser->setHistoryCompleter("Valgrind.Suppression.History"); m_fileChooser->setPath(defaultSuppFile.fileName()); m_fileChooser->setPromptDialogFilter("*.supp"); m_fileChooser->setPromptDialogTitle(tr("Select Suppression File")); QString suppressions; - foreach (const Error &error, m_errors) + for (const Error &error : qAsConst(m_errors)) suppressions += suppressionText(error); m_suppressionEdit->setPlainText(suppressions); - connect(m_fileChooser, &Utils::PathChooser::validChanged, + connect(m_fileChooser, &PathChooser::validChanged, this, &SuppressionDialog::validate); connect(m_suppressionEdit->document(), &QTextDocument::contentsChanged, this, &SuppressionDialog::validate); @@ -178,7 +175,7 @@ void SuppressionDialog::maybeShow(MemcheckErrorView *view) indices.append(view->selectionModel()->currentIndex()); QList<XmlProtocol::Error> errors; - foreach (const QModelIndex &index, indices) { + for (const QModelIndex &index : qAsConst(indices)) { Error error = view->model()->data(index, ErrorListModel::ErrorRole).value<Error>(); if (!error.suppression().isNull()) errors.append(error); @@ -193,11 +190,11 @@ void SuppressionDialog::maybeShow(MemcheckErrorView *view) void SuppressionDialog::accept() { - const Utils::FilePath path = m_fileChooser->filePath(); + const FilePath path = m_fileChooser->filePath(); QTC_ASSERT(!path.isEmpty(), return); QTC_ASSERT(!m_suppressionEdit->toPlainText().trimmed().isEmpty(), return); - Utils::FileSaver saver(path, QIODevice::Append); + FileSaver saver(path, QIODevice::Append); if (!saver.hasError()) { QTextStream stream(saver.file()); stream << m_suppressionEdit->toPlainText(); @@ -216,14 +213,14 @@ void SuppressionDialog::accept() } } - m_settings->suppressions.addSuppressionFile(path.toString()); + m_settings->suppressions.addSuppressionFile(path); QModelIndexList indices = m_view->selectionModel()->selectedRows(); Utils::sort(indices, [](const QModelIndex &l, const QModelIndex &r) { return l.row() > r.row(); }); QAbstractItemModel *model = m_view->model(); - foreach (const QModelIndex &index, indices) { + for (const QModelIndex &index : qAsConst(indices)) { bool removed = model->removeRow(index.row()); QTC_ASSERT(removed, qt_noop()); Q_UNUSED(removed) @@ -234,7 +231,7 @@ void SuppressionDialog::accept() const Error rowError = model->data( model->index(row, 0), ErrorListModel::ErrorRole).value<Error>(); - foreach (const Error &error, m_errors) { + for (const Error &error : qAsConst(m_errors)) { if (equalSuppression(rowError, error)) { bool removed = model->removeRow(row); QTC_CHECK(removed); @@ -254,7 +251,7 @@ void SuppressionDialog::accept() void SuppressionDialog::reject() { if (m_cleanupIfCanceled) - QFile::remove(m_view->defaultSuppressionFile()); + m_view->defaultSuppressionFile().removeFile(); QDialog::reject(); } diff --git a/src/plugins/valgrind/valgrindsettings.cpp b/src/plugins/valgrind/valgrindsettings.cpp index 352ad060ef..21d056e3fd 100644 --- a/src/plugins/valgrind/valgrindsettings.cpp +++ b/src/plugins/valgrind/valgrindsettings.cpp @@ -73,9 +73,9 @@ public: QStandardItemModel m_model; // The volatile value of this aspect. }; -void SuppressionAspect::addSuppressionFile(const QString &suppression) +void SuppressionAspect::addSuppressionFile(const FilePath &suppression) { - QStringList val = value(); + FilePaths val = value(); val.append(suppression); setValue(val); } @@ -141,14 +141,14 @@ SuppressionAspect::~SuppressionAspect() delete d; } -QStringList SuppressionAspect::value() const +FilePaths SuppressionAspect::value() const { - return BaseAspect::value().toStringList(); + return Utils::transform(BaseAspect::value().toStringList(), &FilePath::fromString); } -void SuppressionAspect::setValue(const QStringList &val) +void SuppressionAspect::setValue(const FilePaths &val) { - BaseAspect::setValue(val); + BaseAspect::setValue(Utils::transform<QStringList>(val, &FilePath::toString)); } void SuppressionAspect::addToLayout(LayoutBuilder &builder) @@ -180,7 +180,7 @@ void SuppressionAspect::addToLayout(LayoutBuilder &builder) }; builder.addItem(Span { 2, group }); - setVolatileValue(value()); + setVolatileValue(BaseAspect::value()); } void SuppressionAspect::fromMap(const QVariantMap &map) diff --git a/src/plugins/valgrind/valgrindsettings.h b/src/plugins/valgrind/valgrindsettings.h index 79fc68136e..7d4f01f79e 100644 --- a/src/plugins/valgrind/valgrindsettings.h +++ b/src/plugins/valgrind/valgrindsettings.h @@ -46,8 +46,8 @@ public: explicit SuppressionAspect(bool global); ~SuppressionAspect() final; - QStringList value() const; - void setValue(const QStringList &val); + Utils::FilePaths value() const; + void setValue(const Utils::FilePaths &val); void addToLayout(Utils::LayoutBuilder &builder) final; @@ -57,7 +57,7 @@ public: QVariant volatileValue() const final; void setVolatileValue(const QVariant &val) final; - void addSuppressionFile(const QString &suppressionFile); + void addSuppressionFile(const Utils::FilePath &suppressionFile); private: friend class ValgrindBaseSettings; diff --git a/src/shared/proparser/qmakebuiltins.cpp b/src/shared/proparser/qmakebuiltins.cpp index a697133896..c47bc1b2e7 100644 --- a/src/shared/proparser/qmakebuiltins.cpp +++ b/src/shared/proparser/qmakebuiltins.cpp @@ -474,12 +474,8 @@ void QMakeEvaluator::runProcess(QProcess *proc, const QString &command) const } # endif # ifdef PROEVALUATOR_THREAD_SAFE - m_option->mutex.lock(); - if (m_option->canceled) { - m_option->mutex.unlock(); + if (m_option->canceled) return; - } - m_option->runningProcs << proc; # endif # ifdef Q_OS_WIN proc->setNativeArguments(QLatin1String("/v:off /s /c \"") + command + QLatin1Char('"')); @@ -488,12 +484,21 @@ void QMakeEvaluator::runProcess(QProcess *proc, const QString &command) const proc->start(QLatin1String("/bin/sh"), QStringList() << QLatin1String("-c") << command); # endif # ifdef PROEVALUATOR_THREAD_SAFE - m_option->mutex.unlock(); -# endif - proc->waitForFinished(-1); -# ifdef PROEVALUATOR_THREAD_SAFE - QMutexLocker(&m_option->mutex); - m_option->runningProcs.removeOne(proc); + while (true) { + if (proc->waitForFinished(100)) + break; + if (m_option->canceled) { + proc->terminate(); + if (proc->waitForFinished(1000)) + break; + proc->kill(); + proc->waitForFinished(1000); + break; + } + } +# else + proc->waitForFinished(-1); // If have have single thread we can't cancel it using + // synchronous API of QProcess # endif } #endif diff --git a/src/shared/proparser/qmakeglobals.cpp b/src/shared/proparser/qmakeglobals.cpp index ea429145e3..ca22bdde8b 100644 --- a/src/shared/proparser/qmakeglobals.cpp +++ b/src/shared/proparser/qmakeglobals.cpp @@ -92,11 +92,7 @@ QMakeGlobals::~QMakeGlobals() void QMakeGlobals::killProcesses() { #ifdef PROEVALUATOR_THREAD_SAFE - QMutexLocker lock(&mutex); canceled = true; - for (QProcess * const proc : runningProcs) - proc->kill(); - runningProcs.clear(); #endif } diff --git a/src/shared/proparser/qmakeglobals.h b/src/shared/proparser/qmakeglobals.h index 2fa9a33213..2baeab4bf5 100644 --- a/src/shared/proparser/qmakeglobals.h +++ b/src/shared/proparser/qmakeglobals.h @@ -159,8 +159,7 @@ private: #ifdef PROEVALUATOR_THREAD_SAFE QMutex mutex; - bool canceled = false; - QList<QProcess *> runningProcs; + std::atomic_bool canceled = false; #endif QHash<QMakeBaseKey, QMakeBaseEnv *> baseEnvs; diff --git a/src/shared/qbs b/src/shared/qbs -Subproject 4592eff289852e3e5a81596d1cc6b0c2488e6bc +Subproject a35ff56175cc8c993b54bb1d92ff71ba4532fc8 diff --git a/src/shared/shared.pro b/src/shared/shared.pro index b4c64a02fb..4e4c860c82 100644 --- a/src/shared/shared.pro +++ b/src/shared/shared.pro @@ -6,9 +6,12 @@ QBS_DIRS = \ qbslibexec \ qbsmsbuildlib \ qbsplugins \ - qbsstatic + qbsstatic \ + qbspkgconfig +qbspkgconfig.subdir = qbs/src/lib/pkgconfig qbscorelib.subdir = qbs/src/lib/corelib +qbscorelib.depends = qbspkgconfig qbsapps.subdir = qbs/src/app qbsapps.depends = qbscorelib qbslibexec.subdir = qbs/src/libexec diff --git a/src/tools/qml2puppet/qml2puppet.qbs b/src/tools/qml2puppet/qml2puppet.qbs index 7eff7ba02b..83acc77ba3 100644 --- a/src/tools/qml2puppet/qml2puppet.qbs +++ b/src/tools/qml2puppet/qml2puppet.qbs @@ -16,6 +16,7 @@ QtcTool { ] } Depends { name: "Qt.quick3d-private"; required: false } + Depends { name: "Qt.quick3dparticles-private"; required: false } property bool useQuick3d: Utilities.versionCompare(Qt.core.version, "5.15") >= 0 && Qt["quick3d-private"].present property bool useParticle3d: Utilities.versionCompare(Qt.core.version, "6.2") >= 0 diff --git a/tests/manual/debugger/simple/simple_test_app.cpp b/tests/manual/debugger/simple/simple_test_app.cpp index cbe4294389..d4df6f0411 100644 --- a/tests/manual/debugger/simple/simple_test_app.cpp +++ b/tests/manual/debugger/simple/simple_test_app.cpp @@ -4433,7 +4433,11 @@ namespace qvariant { // FIXME: Known to break //QString type = var.typeName(); var.setValue(my); +#if QT_VERSION >= 0x051500 + const char *name = QMetaType(var.userType()).name(); +#else const char *name = QMetaType::typeName(var.userType()); +#endif BREAK_HERE; // Expand my my.0 my.0.value my.1 my.1.value var var.data var.data.0 var.data.0.value var.data.1 var.data.1.value. // Check my <2 items> qvariant::MyType. @@ -4706,6 +4710,7 @@ namespace noargs { { public: Goo(const QString &str, const int n) : str_(str), n_(n) {} + int n() {return n_;} private: QString str_; int n_; @@ -5459,7 +5464,7 @@ namespace basic { const int &b = a; typedef int &Ref; const int c = 44; - const Ref d = a; + Ref d = a; BREAK_HERE; // Check a 43 int. // Check b 43 int &. @@ -5475,7 +5480,7 @@ namespace basic { const QString &b = fooxx(); typedef QString &Ref; const QString c = "world"; - const Ref d = a; + Ref d = a; BREAK_HERE; // Check a "hello" QString. // Check b "bababa" QString &. @@ -5489,7 +5494,7 @@ namespace basic { { const QString &b = a; typedef QString &Ref; - const Ref d = const_cast<Ref>(a); + Ref d = const_cast<Ref>(a); BREAK_HERE; // Check a "hello" QString &. // Check b "hello" QString &. diff --git a/tests/system/suite_general/tst_rename_file/test.py b/tests/system/suite_general/tst_rename_file/test.py index 5f867f1f98..7d846d3013 100644 --- a/tests/system/suite_general/tst_rename_file/test.py +++ b/tests/system/suite_general/tst_rename_file/test.py @@ -87,14 +87,16 @@ def renameFile(projectDir, proFile, branch, oldname, newname): oldFilePath = os.path.join(projectDir, oldname) newFilePath = os.path.join(projectDir, newname) oldFileText = readFile(oldFilePath) - itemText = branch + "." + oldname.replace(".", "\\.") + oldItemText = branch + "." + oldname.replace(".", "\\.") + newItemText = branch + "." + newname.replace(".", "\\.") treeview = waitForObject(":Qt Creator_Utils::NavigationTreeView") try: - openItemContextMenu(treeview, itemText, 5, 5, 0) + openItemContextMenu(treeview, oldItemText, 5, 5, 0) except: - itemWithWildcard = addBranchWildcardToRoot(itemText) - waitForObjectItem(treeview, itemWithWildcard, 10000) - openItemContextMenu(treeview, itemWithWildcard, 5, 5, 0) + oldItemText = addBranchWildcardToRoot(oldItemText) + newItemText = addBranchWildcardToRoot(newItemText) + waitForObjectItem(treeview, oldItemText, 10000) + openItemContextMenu(treeview, oldItemText, 5, 5, 0) if oldname.lower().endswith(".qrc"): menu = ":Qt Creator.Project.Menu.Folder_QMenu" else: @@ -111,10 +113,18 @@ def renameFile(projectDir, proFile, branch, oldname, newname): " windowTitle='Rename More Files?'}}")) test.verify(waitFor("os.path.exists(newFilePath)", 1000), "Verify that file with new name exists: %s" % newFilePath) - test.compare(readFile(newFilePath), oldFileText, - "Comparing content of file before and after renaming") test.verify(waitFor("' ' + newname in safeReadFile(proFile)", 2000), "Verify that new filename '%s' was added to pro-file." % newname) + if oldname.endswith(".h"): + # Creator updates include guards in renamed header files and changes line breaks + oldFileText = oldFileText.replace("\r\n", "\n") + includeGuard = " " + newname.upper().replace(".", "_") + if not includeGuard.endswith("_H"): + includeGuard += "_H" + oldFileText = oldFileText.replace(" " + oldname.upper().replace(".", "_"), includeGuard) + waitFor("includeGuard in safeReadFile(newFilePath)", 2000) + test.compare(readFile(newFilePath), oldFileText, + "Comparing content of file before and after renaming") if oldname not in newname: test.verify(oldname not in readFile(proFile), "Verify that old filename '%s' was removed from pro-file." % oldname) @@ -122,6 +132,13 @@ def renameFile(projectDir, proFile, branch, oldname, newname): test.verify(oldname not in os.listdir(projectDir), "Verify that file with old name does not exist: %s" % oldFilePath) + if newItemText.endswith("\\.qml"): + newItemText = newItemText.replace(".Other files.", ".QML.") + else: + newItemText = newItemText.replace(".QML.", ".Other files.") + waitForObjectItem(treeview, newItemText) + + def safeReadFile(filename): text = "" while text == "": diff --git a/tests/unit/unittest/CMakeLists.txt b/tests/unit/unittest/CMakeLists.txt index 5699475432..6252f4517d 100644 --- a/tests/unit/unittest/CMakeLists.txt +++ b/tests/unit/unittest/CMakeLists.txt @@ -481,8 +481,16 @@ get_filename_component( ABSOLUTE ) -if(EXISTS ${QMLDOM_STANDALONE_CMAKELISTS}) - add_subdirectory(${QMLDOM_STANDALONE_CMAKELISTS} qmldom_standalone) + +if (EXISTS ../../../../qmldom_standalone/src/qmldom/standalone) + add_subdirectory( + ../../../../qmldom_standalone/src/qmldom/standalone + ${CMAKE_CURRENT_BINARY_DIR}/qmldom_standalone) + + set_target_properties(qmldomlib PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "$<TARGET_PROPERTY:QmlJS,RUNTIME_OUTPUT_DIRECTORY>" + LIBRARY_OUTPUT_DIRECTORY "$<TARGET_PROPERTY:QmlJS,LIBRARY_OUTPUT_DIRECTORY>") + extend_qtc_test(unittest DEPENDS qmldomlib SOURCES |