Merge remote-tracking branch 'origin/14.0'

Conflicts:
	src/plugins/qmljseditor/qmljseditorsettings.cpp

Change-Id: I443424afdfe48cdfc3d083d8e91335e937fcfdb6
This commit is contained in:
Eike Ziller
2024-06-27 11:41:54 +02:00
108 changed files with 1869 additions and 1222 deletions

View File

@@ -49,13 +49,12 @@ jobs:
}
- {
name: "macOS Latest Clang", artifact: "macos-universal",
# TODO: move back to macos-latest when macos-latest is 13 or higher
os: macos-13,
os: macos-latest,
cc: "clang", cxx: "clang++"
}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Checkout submodules
id: git
shell: cmake -P {0}
@@ -63,10 +62,10 @@ jobs:
execute_process(COMMAND git submodule set-url -- perfparser https://code.qt.io/qt-creator/perfparser.git)
execute_process(COMMAND git submodule update --init --recursive)
file(MAKE_DIRECTORY release)
if (${{github.ref}} MATCHES "tags/v([0-9.]+)")
if (${{github.ref}} MATCHES "tags/v(([0-9.]+).*)")
file(APPEND "$ENV{GITHUB_OUTPUT}" "tag=${CMAKE_MATCH_1}\n")
if (EXISTS "dist/changelog/changes-${CMAKE_MATCH_1}.md")
file(READ "dist/changelog/changes-${CMAKE_MATCH_1}.md" changelog_md)
if (EXISTS "dist/changelog/changes-${CMAKE_MATCH_2}.md")
file(READ "dist/changelog/changes-${CMAKE_MATCH_2}.md" changelog_md)
endif()
file(WRITE "release/changelog.md" "These packages are not officially supported, for official packages please check out https://download.qt.io/official_releases/qtcreator\n\n")
file(APPEND "release/changelog.md" "${changelog_md}")
@@ -695,41 +694,41 @@ jobs:
endif()
- name: Upload
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
path: build/qtcreator-${{ matrix.config.artifact }}-${{ steps.git.outputs.tag }}.7z
name: qtcreator-${{ matrix.config.artifact }}-${{ steps.git.outputs.tag }}.7z
- name: Upload Devel
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
path: build/qtcreator-${{ matrix.config.artifact }}-${{ steps.git.outputs.tag }}_dev.7z
name: qtcreator-${{ matrix.config.artifact }}-${{ steps.git.outputs.tag }}_dev.7z
- name: Upload wininterrupt
if: runner.os == 'Windows'
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
path: build/wininterrupt-${{ matrix.config.artifact }}-${{ steps.git.outputs.tag }}.7z
name: wininterrupt-${{ matrix.config.artifact }}-${{ steps.git.outputs.tag }}.7z
- name: Upload qtcreatorcdbext
if: runner.os == 'Windows' && matrix.config.is_msvc
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
path: build/qtcreatorcdbext-${{ matrix.config.artifact }}-${{ steps.git.outputs.tag }}.7z
name: qtcreatorcdbext-${{ matrix.config.artifact }}-${{ steps.git.outputs.tag }}.7z
- name: Upload Debian package
if: runner.os == 'Linux'
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
path: build/build/qtcreator-${{ matrix.config.artifact }}-${{ steps.git.outputs.tag }}.deb
name: qtcreator-${{ matrix.config.artifact }}-${{ steps.git.outputs.tag }}.deb
- name: Upload disk image
if: runner.os == 'macOS' && contains(github.ref, 'tags/v')
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
path: build/qt-creator-${{ matrix.config.artifact }}-${{ steps.git.outputs.tag }}.dmg
name: qt-creator-${{ matrix.config.artifact }}-${{ steps.git.outputs.tag }}.dmg
@@ -739,14 +738,14 @@ jobs:
run: cmake -E tar cf ../${{ steps.ccache.outputs.archive_name }}.tar .
- name: Upload ccache archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
path: ./${{ steps.ccache.outputs.archive_name }}.tar
name: ${{ steps.ccache.outputs.archive_name }}
- name: Upload Release Changelog
if: contains(github.ref, 'tags/v')
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
path: ./release/changelog.md
name: changelog.md
@@ -758,7 +757,7 @@ jobs:
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
path: release-with-dirs
@@ -770,7 +769,7 @@ jobs:
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:

View File

@@ -20,7 +20,7 @@ instructions:
maxTimeBetweenOutput: 360
userMessageOnFailure: "Failed to download elfutils package, check logs."
- type: ExecuteCommand
command: "/usr/bin/7z x -y {{.AgentWorkingDir}}/build/qt_temp/elfutils-release_0.175qt-linux-x86_64.7z -o{{.AgentWorkingDir}}/build/qt_temp/elfutils"
command: "7z x -y {{.AgentWorkingDir}}/build/qt_temp/elfutils-release_0.175qt-linux-x86_64.7z -o{{.AgentWorkingDir}}/build/qt_temp/elfutils"
maxTimeInSeconds: 3600
maxTimeBetweenOutput: 360
userMessageOnFailure: "Failed to extract elfutils package, check logs."
@@ -30,7 +30,7 @@ instructions:
maxTimeBetweenOutput: 360
userMessageOnFailure: "Failed to download LLVM package, check logs."
- type: ExecuteCommand
command: "/usr/bin/7z x -y {{.AgentWorkingDir}}/build/qt_temp/libclang.7z -o{{.AgentWorkingDir}}/build/qt_temp/"
command: "7z x -y {{.AgentWorkingDir}}/build/qt_temp/libclang.7z -o{{.AgentWorkingDir}}/build/qt_temp/"
maxTimeInSeconds: 3600
maxTimeBetweenOutput: 360
userMessageOnFailure: "Failed to extract LLVM package, check logs."
@@ -42,7 +42,7 @@ instructions:
- type: ChangeDirectory
directory: "{{.AgentWorkingDir}}/build/tqtc-qtsdk/packaging_tools"
- type: ExecuteCommand
command: "python3 -m pipenv run python -u bld_sdktool.py --qt-url {{.Env.QTC_SDKTOOL_QT_BASE_URL}}{{.Env.QTC_SDKTOOL_QT_EXT}} --qt-build {{.AgentWorkingDir}}/build/sdktool/qt --src {{.AgentWorkingDir}}/qt-creator/qt-creator/src/tools/sdktool --build {{.AgentWorkingDir}}/build/sdktool/build --install {{.AgentWorkingDir}}/build/sdktool/install --make-command make"
command: "python3 -m pipenv run python -u bld_sdktool.py --qt-url {{.Env.QTC_SDKTOOL_QT_BASE_URL}}{{.Env.QTC_SDKTOOL_QT_EXT}} --qt-build {{.AgentWorkingDir}}/build/sdktool/qt --src {{.AgentWorkingDir}}/qt-creator/qt-creator/src/tools/sdktool --build {{.AgentWorkingDir}}/build/sdktool/build --install {{.AgentWorkingDir}}/build/sdktool/install --make-command ninja"
maxTimeInSeconds: 36000
maxTimeBetweenOutput: 3600
userMessageOnFailure: "Failed to build sdktool, check logs."
@@ -64,7 +64,7 @@ instructions:
maxTimeBetweenOutput: 360
userMessageOnFailure: "Failed to download LLVM package, check logs."
- type: ExecuteCommand
command: "/usr/local/bin/7z x -y {{.AgentWorkingDir}}/build/qt_temp/libclang.7z -o{{.AgentWorkingDir}}/build/qt_temp/"
command: "7z x -y {{.AgentWorkingDir}}/build/qt_temp/libclang.7z -o{{.AgentWorkingDir}}/build/qt_temp/"
maxTimeInSeconds: 3600
maxTimeBetweenOutput: 360
userMessageOnFailure: "Failed to extract LLVM package, check logs."
@@ -79,7 +79,7 @@ instructions:
variableName: MACOSX_DEPLOYMENT_TARGET
variableValue: "{{.Env.SDKTOOL_MACOSX_DEPLOYMENT_TARGET}}"
- type: ExecuteCommand
command: "python3 -m pipenv run python -u bld_sdktool.py --qt-url {{.Env.QTC_SDKTOOL_QT_BASE_URL}}{{.Env.QTC_SDKTOOL_QT_EXT}} --qt-build {{.AgentWorkingDir}}/build/sdktool/qt --src {{.AgentWorkingDir}}/qt-creator/qt-creator/src/tools/sdktool --build {{.AgentWorkingDir}}/build/sdktool/build --install {{.AgentWorkingDir}}/build/sdktool/install --make-command make"
command: "python3 -m pipenv run python -u bld_sdktool.py --qt-url {{.Env.QTC_SDKTOOL_QT_BASE_URL}}{{.Env.QTC_SDKTOOL_QT_EXT}} --qt-build {{.AgentWorkingDir}}/build/sdktool/qt --src {{.AgentWorkingDir}}/qt-creator/qt-creator/src/tools/sdktool --build {{.AgentWorkingDir}}/build/sdktool/build --install {{.AgentWorkingDir}}/build/sdktool/install --make-command ninja --universal"
maxTimeInSeconds: 36000
maxTimeBetweenOutput: 3600
userMessageOnFailure: "Failed to build sdktool, check logs."
@@ -96,7 +96,7 @@ instructions:
maxTimeBetweenOutput: 360
userMessageOnFailure: "Failed to download elfutils package, check logs."
- type: ExecuteCommand
command: "C:\\Utils\\sevenzip\\7z.exe x -y {{.AgentWorkingDir}}\\build\\qt_temp\\elfutils-release_0.175qt-windows-x86_64.7z -o{{.AgentWorkingDir}}\\build\\qt_temp\\elfutils"
command: "7z.exe x -y {{.AgentWorkingDir}}\\build\\qt_temp\\elfutils-release_0.175qt-windows-x86_64.7z -o{{.AgentWorkingDir}}\\build\\qt_temp\\elfutils"
maxTimeInSeconds: 3600
maxTimeBetweenOutput: 360
userMessageOnFailure: "Failed to extract elfutils package, check logs."
@@ -106,7 +106,7 @@ instructions:
maxTimeBetweenOutput: 360
userMessageOnFailure: "Failed to download python package, check logs."
- type: ExecuteCommand
command: "C:\\Utils\\sevenzip\\7z.exe x -y {{.AgentWorkingDir}}\\build\\qt_temp\\Python38-win-x64.7z -o{{.AgentWorkingDir}}\\build\\qt_temp\\python"
command: "7z.exe x -y {{.AgentWorkingDir}}\\build\\qt_temp\\Python38-win-x64.7z -o{{.AgentWorkingDir}}\\build\\qt_temp\\python"
maxTimeInSeconds: 3600
maxTimeBetweenOutput: 360
userMessageOnFailure: "Failed to extract python package, check logs."
@@ -116,7 +116,7 @@ instructions:
maxTimeBetweenOutput: 360
userMessageOnFailure: "Failed to download LLVM package, check logs."
- type: ExecuteCommand
command: "C:\\Utils\\sevenzip\\7z.exe x -y {{.AgentWorkingDir}}\\build\\qt_temp\\libclang.7z -o{{.AgentWorkingDir}}\\build\\qt_temp\\"
command: "7z.exe x -y {{.AgentWorkingDir}}\\build\\qt_temp\\libclang.7z -o{{.AgentWorkingDir}}\\build\\qt_temp\\"
maxTimeInSeconds: 3600
maxTimeBetweenOutput: 360
userMessageOnFailure: "Failed to extract LLVM package, check logs."
@@ -128,7 +128,7 @@ instructions:
- type: ChangeDirectory
directory: "{{.AgentWorkingDir}}\\build\\tqtc-qtsdk\\packaging_tools"
- type: ExecuteCommand
command: "python -m pipenv run python -u bld_sdktool.py --qt-url {{.Env.QTC_SDKTOOL_QT_BASE_URL}}{{.Env.QTC_SDKTOOL_QT_EXT}} --qt-build {{.AgentWorkingDir}}\\build\\sdktool\\qt --src {{.AgentWorkingDir}}\\qt-creator\\qt-creator\\src\\tools\\sdktool --build {{.AgentWorkingDir}}\\build\\sdktool\\build --install {{.AgentWorkingDir}}\\build\\sdktool\\install --make-command nmake"
command: "python -m pipenv run python -u bld_sdktool.py --qt-url {{.Env.QTC_SDKTOOL_QT_BASE_URL}}{{.Env.QTC_SDKTOOL_QT_EXT}} --qt-build {{.AgentWorkingDir}}\\build\\sdktool\\qt --src {{.AgentWorkingDir}}\\qt-creator\\qt-creator\\src\\tools\\sdktool --build {{.AgentWorkingDir}}\\build\\sdktool\\build --install {{.AgentWorkingDir}}\\build\\sdktool\\install --make-command ninja"
maxTimeInSeconds: 36000
maxTimeBetweenOutput: 3600
userMessageOnFailure: "Failed to build sdktool, check logs."

View File

@@ -19,10 +19,10 @@ instructions:
variableValue: 11.0
- type: EnvironmentVariable
variableName: SDKTOOL_MACOSX_DEPLOYMENT_TARGET
variableValue: 10.14
variableValue: 11.0
- type: EnvironmentVariable
variableName: QTC_SDKTOOL_QT_BASE_URL
variableValue: "https://ci-files02-hki.ci.qt.io/packages/jenkins/archive/qt/5.15/5.15.2-final-released/latest/src/submodules/qtbase-everywhere-src-5.15.2"
variableValue: "https://ci-files02-hki.ci.qt.io/packages/jenkins/archive/qt/6.6/6.6.0-released/Qt/src/submodules/qtbase-everywhere-src-6.6.0"
- type: Group
instructions:
- type: EnvironmentVariable

View File

@@ -20,7 +20,7 @@ instructions:
maxTimeInSeconds: 600
maxTimeBetweenOutput: 600
project: qtsdk/tqtc-qtsdk
ref: master
ref: production
directory: "build/tqtc-qtsdk"
userMessageOnFailure: "Failed to install tqtc-qtsdk, check logs"
- type: Group

View File

@@ -5,16 +5,16 @@ Qt Creator version 14 contains bug fixes and new features.
The most important changes are listed in this document. For a complete list of
changes, see the Git log for the Qt Creator sources that you can check out from
the public Git repository. For example:
the public Git repository or view online at
git clone git://code.qt.io/qt-creator/qt-creator.git
git log --cherry-pick --pretty=oneline origin/13.0..v14.0.0
https://code.qt.io/cgit/qt-creator/qt-creator.git/log/?id=13.0..v14.0.0
General
-------
* Started work on supporting Lua based plugins (registering language servers,
actions, preferences, and wizards)
([Documentation](https://doc-snapshots.qt.io/qtcreator-extending/lua-extensions.html))
* Added `Clear` and `Save Contents` to context menus of all output views
* Locator
* Added the option to show results relative to project root
@@ -42,6 +42,8 @@ Editing
### C++
* Made C++ code model settings configurable per project
* Added a setting for the naming of include guards
([QTCREATORBUG-25117](https://bugreports.qt.io/browse/QTCREATORBUG-25117))
* Fixed indentation after function calls with subscript operator
([QTCREATORBUG-29225](https://bugreports.qt.io/browse/QTCREATORBUG-29225))
* Refactoring
@@ -58,6 +60,7 @@ Editing
[Documentation](https://doc.qt.io/qtcreator/creator-reference-cpp-quick-fixes.html)
* Clangd
* Updated the prebuilt binaries to LLVM 18.1.7
* Increased the minimum version to LLVM 17
* Added the `Per-project index location` and `Per-session index location`
options in `Preferences` > `C++` > `Clangd` for setting the index location
@@ -88,14 +91,19 @@ Editing
([QTCREATORBUG-19226](https://bugreports.qt.io/browse/QTCREATORBUG-19226))
* Added `Qt Design Studio` to `Open With` for `.ui.qml` files
([Documentation](https://doc.qt.io/qtcreator/creator-quick-ui-forms.html))
* Fixed that the color preview did not work on named colors
([QTCREATORBUG-30594](https://bugreports.qt.io/browse/QTCREATORBUG-30594))
* Language Server
* Switched on by default
* Added option for generating `qmlls.ini` files for CMake projects
* Switched on by default for Qt 6.8 and later
* Added an option for generating `qmlls.ini` files for CMake projects in
`Preferences` > `Qt Quick`> `QML/JS Editing`
([QTCREATORBUG-30394](https://bugreports.qt.io/browse/QTCREATORBUG-30394))
([Qt Documentation](https://doc.qt.io/qt-6/cmake-variable-qt-qml-generate-qmlls-ini.html))
* Fixed that tool tips from the built-in model were shown instead of tool tips
from the server
[Documentation](https://doc.qt.io/qtcreator/creator-how-to-use-qml-language-server.html)
### Python
* Added options for updating Python Language Server
@@ -114,6 +122,10 @@ Editing
`Compiler Explorer`
[Documentation](https://doc.qt.io/qtcreator/creator-how-to-create-compiler-explorer-sessions.html)
### Markdown
* Fixed the navigation history
### Models
* Added more visual attributes for relations
@@ -135,6 +147,8 @@ Projects
from the list in the `Projects` mode
* Added support for user comments in the environment editor
([Documentation](https://doc-snapshots.qt.io/qtcreator-14.0/creator-how-to-edit-environment-settings.html))
* Added the setting `Time to wait before force-stopping applications`
([QTCREATORBUG-31025](https://bugreports.qt.io/browse/QTCREATORBUG-31025))
* Fixed the parsing of file links when color was used for the output
([QTCREATORBUG-30774](https://bugreports.qt.io/browse/QTCREATORBUG-30774))
* Fixed that the column information was not used when opening files from links
@@ -151,12 +165,18 @@ Projects
* Implemented `Open Online Documentation` for CMake documentation
* Added `Clear CMake Configuration` to the context menu in the `Projects` view
([QTCREATORBUG-24658](https://bugreports.qt.io/browse/QTCREATORBUG-24658))
* Added support for the `CROSSCOMPILING_EMULATOR` target property
([QTCREATORBUG-29880](https://bugreports.qt.io/browse/QTCREATORBUG-29880))
([CMake Documentation](https://cmake.org/cmake/help/latest/prop_tgt/CROSSCOMPILING_EMULATOR.html#crosscompiling-emulator))
* Fixed that the package manager auto-setup files were not removed with
`Clear CMake Configuration`
([QTCREATORBUG-30771](https://bugreports.qt.io/browse/QTCREATORBUG-30771))
* Fixed that files generated by the Qt QML CMake API were not filtered as
generated files
([QTCREATORBUG-29631](https://bugreports.qt.io/browse/QTCREATORBUG-29631))
* Fixed a crash when triggering `Follow Symbol` in a CMake file that does not
belong to a project
([QTCREATORBUG-31077](https://bugreports.qt.io/browse/QTCREATORBUG-31077))
* Presets
* Made CMake settings configurable
([QTCREATORBUG-25972](https://bugreports.qt.io/browse/QTCREATORBUG-25972),
@@ -200,7 +220,15 @@ Analyzer
### Axivion
* Made it possible to register multiple servers
* Added the `Add` and `Remove` buttons to `Preferences` > `Axivion` for
registering multiple servers
([Documentation](https://doc-snapshots.qt.io/qtcreator-14.0/creator-preferences-axivion.html))
### Cppcheck
* Fixed that Cppcheck was not working until selecting `Apply` in the settings
([QTCREATORBUG-28951](https://bugreports.qt.io/browse/QTCREATORBUG-28951),
[QTCREATORBUG-30615](https://bugreports.qt.io/browse/QTCREATORBUG-30615))
Terminal
--------
@@ -249,11 +277,15 @@ Platforms
* Added support for creating `android-desktop` devices
* Added support for `namespace` in `build.gradle`
([QTBUG-106907](https://bugreports.qt.io/browse/QTBUG-106907))
* Fixed that errors when creating AVDs were not visible to the user
([QTCREATORBUG-30852](https://bugreports.qt.io/browse/QTCREATORBUG-30852))
### iOS
* Removed Simulator management from the preferences. Use the
`Devices and Simulators` window in Xcode instead.
* Fixed that starting the debugger could be slow for iOS < 17
([QTCREATORBUG-31044](https://bugreports.qt.io/browse/QTCREATORBUG-31044))
### Remote Linux
@@ -266,6 +298,11 @@ Platforms
* Added support for the `perf` profiler
### Bare Metal
* Fixed issues with Qbs and the IAR toolchain
([QTCREATORBUG-24040](https://bugreports.qt.io/browse/QTCREATORBUG-24040))
Credits for these changes go to:
--------------------------------
Ahmad Samir

View File

@@ -83,6 +83,8 @@ Analyzer
### CTF Visualizer
### Cppcheck
Terminal
--------
@@ -127,5 +129,7 @@ Platforms
### QNX
### Bare Metal
Credits for these changes go to:
--------------------------------

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -15,13 +15,14 @@
\c CMakeUserPresets.json has options for your local builds.
Create the presets files in the format described in
\l{https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html}
\l{https://cmake.org/cmake/help/v3.24/manual/cmake-presets.7.html}
{cmake-presets(7)} and store them in the project's root directory.
You can then see them in the \l {Projects} view.
\QC supports presets up to version 3 (introduced in CMake 3.21), but does not
enforce version checking. It reads and uses all the fields from version 3 if
present. It does not support test presets.
\QC supports \e configure and \e build presets up to version 5
(introduced in CMake 3.24), but does not enforce version checking.
It reads and uses all the fields from version 5 if present.
It does not support \e test presets.
You can import the presets the first time you \l {Open projects}
{open a project}, when no \c CMakeLists.txt.user file exists or you have
@@ -44,7 +45,7 @@
\c NOT_COMMON_VALUE is displayed in \uicontrol {Initial Parameters}
and \c AN_ENVIRONMENT_FLAG in the environment configuration field.
\badcode
\code
{
"version": 1,
"configurePresets": [
@@ -91,7 +92,7 @@
\li GNU gdb 11.2.0 for MinGW 11.2.0 64-bit debugger
\endlist
\badcode
\code
{
"version": 1,
"configurePresets": [
@@ -131,7 +132,7 @@
generator, add \c Debug and \c Release build steps, and specify the path
to \c ninja.exe as a value of the \c CMAKE_MAKE_PROGRAM variable:
\badcode
\code
{
"version": 2,
"configurePresets": [
@@ -180,7 +181,7 @@
For example:
\badcode
\code
"generator": "Ninja Multi-Config",
"toolset": {
"value": "v142,host=x64",
@@ -196,7 +197,7 @@
in the \c PATH, you might also have to specify the compiler to use in
\c cacheVariables or \c environmentVariables:
\badcode
\code
"generator": "Ninja Multi-Config",
"toolset": {
"value": "v142,host=x64",
@@ -226,7 +227,7 @@
\li \c wine emulator
\endlist
\badcode
\code
{
"version": 4,
"configurePresets": [
@@ -262,7 +263,7 @@
if the \c hostSystemName equals \c Linux, the \c linux presets are used and
if it equals \c Windows, the \c windows presets are used.
\badcode
\code
{
"version": 3,
"configurePresets": [

View File

@@ -120,11 +120,14 @@
tooltips
\li Selecting any of the above elements and pressing \key F1 to show
its documentation
\li Switching to the Help mode
\li Switching to the \uicontrol Help mode
\endlist
\sa {Build with CMake}{How To: Build with CMake}, {CMake},
{Read Documentation}{How To: Read Documentation}
To view the documentation online, open it in the \uicontrol Help mode and
select \inlineimage icons/online.png (\uicontrol {Open Online Documentation}).
\sa {Build with CMake}{How To: Build with CMake},
{Read Documentation}{How To: Read Documentation}, {CMake}
*/
/*!

View File

@@ -85,9 +85,8 @@
To enable a language server, select the checkbox next to the language
server name and set server preferences.
To turn on \l{Turn on \QMLLS}{\QMLLS}, go to
\preferences > \uicontrol {Qt Quick} > \uicontrol {QML/JS Editing} and
select \uicontrol {Enable \QMLLS}.
To configure \l{Configure \QMLLS}{\QMLLS}, go to
\preferences > \uicontrol {Qt Quick} > \uicontrol {QML/JS Editing}.
To remove language servers from the list, select \uicontrol Delete.
@@ -237,35 +236,33 @@
\ingroup creator-how-to-lsp
\title Turn on \QMLLS
\title Configure \QMLLS
Since Qt 6.4, \QMLLS offers code completion and
issues warnings for QML. To use it, go to \preferences >
\uicontrol {Qt Quick} > \uicontrol {QML/JS Editing} and select
\uicontrol {Enable \QMLLS}.
Since Qt 6.4, \QMLLS offers code completion and issues warnings for QML.
By default, enabling \QMLLS will only enable warning messages
and code completion, while advanced features such as renaming and finding usages
will be handled by the embedded code model.
To disable the embedded code model and use \QMLLS for everything,
select \uicontrol {Use \QMLLS advanced features}.
To turn off \QMLLS, go to \preferences > \uicontrol {Qt Quick} >
\uicontrol {QML/JS Editing} and clear \uicontrol {Turn on}.
By default, \QMLLS issues warning messages and provides code completion,
while the embedded code model handles advanced features, such as renaming
symbols and finding usages. To disable the embedded code model and use
\QMLLS for everything, select \uicontrol {Use advanced features}.
Also, \QC tries to use \QMLLS shipped with the Qt version in your current
\l{Kits}{kit}. To override that behavior and always use
\QMLLS of the highest registered Qt version, select
\uicontrol {Use \QMLLS from latest Qt version}.
\uicontrol {Use from latest Qt version}.
To use older \QMLLS versions, select
\uicontrol{Allow versions below Qt 6.8}.
\image qtcreator-qml-js-editing.webp {QML/JS Editing preferences}
When using \c qmlls from Qt 6.7 or later, set \l{QT_QML_GENERATE_QMLLS_INI}
to \c{ON} in \uicontrol Projects > \uicontrol {Build Settings}
> \uicontrol {Initial Configuration}.
To automatically configure new CMake projects, select
\uicontrol {Create .qmlls.ini files for new projects}.
\sa {Manage Language Servers}{How To: Manage Language Servers},
{Enabling and Disabling Messages}, {CMake Build Configuration}, {Kits}
{Enabling and Disabling Messages}, {CMake Build Configuration}, {Kits},
{Language Servers}
*/

View File

@@ -23,23 +23,23 @@
\li To view context sensitive help on a Qt class or function as a
tooltip, move the mouse cursor over the class or function. If help
is not available, the tooltip displays type information for the
is not available, the tooltip shows type information for the
symbol.
\li To display tooltips for function signatures regardless of the
\li To show tooltips for function signatures regardless of the
cursor position in the function call, press \key {Ctrl+Shift+D}.
\li To display the full help on a Qt class or function, press \key F1 or
\li To show the full help on a Qt class or function, press \key F1 or
select \uicontrol {Context Help} in the context menu.
The documentation is displayed in a
The documentation is shown in a
view next to the code editor, or, if there is not enough vertical
space, in the fullscreen \uicontrol Help mode.
\li To select and configure how the documentation is displayed in the
\uicontrol Help mode, select \preferences > \uicontrol Help.
\li To change how the documentation is shown in the
\uicontrol Help mode, go to \preferences > \uicontrol Help.
\endlist
The following image displays the context sensitive help in the \uicontrol Edit
The following image shows the context sensitive help in the \uicontrol Edit
mode.
\image qtcreator-context-sensitive-help.png {Context-sensitive help in Edit mode}
@@ -52,43 +52,43 @@
\image qtcreator-preferences-help-general.webp {General tab in Help preferences}
You can set the default zoom level in the \uicontrol Zoom field. When
viewing help pages, you can use the mouse scroll wheel to zoom them. To
disable this feature, deselect the \uicontrol {Enable scroll wheel zooming}
check box.
Set the default zoom level in \uicontrol Zoom. When viewing help pages, use
the mouse scroll wheel to zoom them. To turn off this feature, clear
\uicontrol {Enable scroll wheel zooming}.
To disable antialiasing, deselect the \uicontrol Antialiasing check box.
To turn off antialiasing, clear \uicontrol Antialias.
\section1 Return to the editor
To switch to the editor context when you close the last help page, select
the \uicontrol {Return to editor on closing the last page} check box.
\uicontrol {Return to editor on closing the last page}.
\section1 Select help viewer backend
The help viewer backend determines the style sheet that is used to display
The help viewer backend determines the style sheet that is used to show
the help files. The default help viewer backend that is based on litehtml
is recommended for viewing Qt documentation. You can choose another help
viewer backend in the \uicontrol {Viewer backend} field. To take the new
viewer backend in the \uicontrol {Viewer backend}. To take the new
backend to use, reload the help page.
\section1 View function tooltips
To hide function tooltips by default, select \preferences >
\uicontrol {Text Editor} > \uicontrol Behavior >
\uicontrol {Show help tooltips using the mouse} >
\uicontrol {On Shift+Mouseover}. You can still view the tooltips by pressing
and holding down the \key Shift key.
To hide function tooltips by default:
\list 1
\li Go to \preferences > \uicontrol {Text Editor} > \uicontrol Behavior.
\image qtcreator-preferences-texteditor-behavior.webp {Text Editor Behavior preferences}
\li In \uicontrol {Show help tooltips using the mouse}, select
\uicontrol {On Shift+Mouseover}.
\endlist
You can still view the tooltips by pressing and holding down the \key Shift
key.
To use a keyboard shortcut for viewing help tooltips, select
\uicontrol {Show help tooltips using keyboard shortcut (Alt)}.
\sa {Find information in Qt documentation}, {Filter documentation},
{Search from documentation}
\sa {Add external documentation}, {Detach the help window},
{Filter documentation}, {Find information in Qt documentation},
{Select the help start page}
\sa {Read Documentation}{How To: Read Documentation}
*/
/*!
@@ -103,15 +103,18 @@
\title Find information in Qt documentation
\QC, \QSDK and other Qt deliverables have documentation
as .qch files. All the documentation is accessible in the \uicontrol Help mode.
\QC installer, \QOI, and other Qt deliverables install documentation as .qch
files. View the documentation in the \uicontrol Help mode. To view the
currently open document online, select \inlineimage icons/online.png
(\uicontrol {Open Online Documentation}).
By default, \QC registers only the latest available version of the
documentation for each installed Qt module. To register all installed
documentation, select \preferences > \uicontrol Kits >
\uicontrol {Qt Versions} > \uicontrol {Register documentation}.
documentation, go to \preferences > \uicontrol Kits >
\uicontrol {Qt Versions} and select an option in
\uicontrol {Register documentation}.
\image qtcreator-qt-versions.png {Register documentation field in Qt Versions tab in Kit Preferences}
\image qtcreator-qt-versions.png {Register documentation in Qt Versions preferences}
\section1 Help mode sidebar views
@@ -137,7 +140,7 @@
\endlist
\sa {Add bookmarks to help pages}, {Search from documentation}
\sa {Read Documentation}{How To: Read Documentation}
*/
/*!
@@ -152,8 +155,8 @@
\title Add bookmarks to help pages
You can add bookmarks to useful help pages to easily find them later
in the \uicontrol Bookmarks view. You can either use the page title as the
Add bookmarks to useful help pages to easily find them later
in the \uicontrol Bookmarks view. Either use the page title as the
bookmark or change it to any text. You can organize the bookmarks in
folders in the view.
@@ -163,21 +166,21 @@
\list 1
\li Click the \inlineimage icons/bookmark.png
(\uicontrol {Add Bookmark}) button on the toolbar.
\li Select \inlineimage icons/bookmark.png (\uicontrol {Add Bookmark})
on the toolbar.
\li In the \uicontrol {Add Bookmark} dialog, click \uicontrol OK to save the
\li In the \uicontrol {Add Bookmark} dialog, select \uicontrol OK to save the
page title as a bookmark in the selected folder.
\endlist
\section1 Import and export bookmarks
To import and export bookmarks, select \preferences >
\uicontrol Help > \uicontrol General > \uicontrol {Import Bookmarks} or
To import and export bookmarks, go to \preferences > \uicontrol Help >
\uicontrol General and select \uicontrol {Import Bookmarks} or
\uicontrol {Export Bookmarks}.
\sa {Find information in Qt documentation}
\sa {Read Documentation}{How To: Read Documentation}
*/
/*!
@@ -192,10 +195,10 @@
\title Search from documentation
In the \uicontrol Help mode \uicontrol Search pane, you can use full-text
In the \uicontrol Help mode \uicontrol Search pane, use full-text
search for finding a
particular word in all the installed documents. Enter the term you are
looking for, and select the \uicontrol Search button. All documents that
looking for, and select \uicontrol Search. All documents that
have the specified term are listed. The list is sorted by document
version (if you have installed several Qt versions, for example) and
the number of search hits that the documents have. Select a document in
@@ -225,16 +228,15 @@
time when you open the \uicontrol Search pane. If you add or remove documents,
\QC recreates the index.
If you cannot find words that you know are there, indexing might not have
been completed for some reason. To regenerate the index, click
\inlineimage icons/reload_gray.png
If you cannot find words that you know are there, the index might not be
complete. To recreate it, select \inlineimage icons/reload_gray.png
(\uicontrol {Regenerate Index}).
Punctuation is not included in indexed terms. To find terms that have
punctuation, such as domain names, use the asterisk as a wild card. For
example, to find \c {Pastebin.Com}, enter the search term \c {Pastebin*}.
\sa {Find information in Qt documentation}
\sa {Read Documentation}{How To: Read Documentation}
*/
/*!
@@ -249,7 +251,7 @@
\title Add external documentation
You can display external documentation in the \uicontrol Help mode.
You can view external documentation in the \uicontrol Help mode.
To add documentation or replace the documentation that ships with \QC and Qt:
\list 1
@@ -259,14 +261,14 @@
For information on how to prepare your documentation and create a
.qch file, see \l{The Qt Help Framework}.
\li To add the .qch file to \QC, select \preferences >
\li To add the .qch file to \QC, go to \preferences >
\uicontrol Help > \uicontrol Documentation > \uicontrol Add.
\image qtcreator-preferences-help-documentation.webp {Documentation tab in Help Preferences}
\endlist
\sa {Get help}
\sa {Read Documentation}{How To: Read Documentation}
*/
/*!
@@ -281,26 +283,24 @@
\title Detach the help window
By default, context-sensitive help is opened in a window next to the
By default, context-sensitive help opens in a window next to the
code editor when you press \key F1. If there is not enough vertical
space, the help opens in the full-screen help mode.
\image qtcreator-context-sensitive-help.png {Context-sensitive help in Edit mode}
To specify that the help always opens in full-screen mode or
is detached to an external window, select \preferences > \uicontrol Help >
\uicontrol General.
To specify that the help always opens in full-screen mode or in an external
window, go to \preferences > \uicontrol Help > \uicontrol General.
\image qtcreator-preferences-help-general.webp {General tab in Help preferences}
Set preferences for displaying context-sensitive help
in the \uicontrol {On context help} field. To detach the help window, select
Set preferences for viewing context-sensitive help
in \uicontrol {On context help}. To detach the help window, select
\uicontrol {Always Show in External Window}.
To change this setting in a help view, select the \inlineimage icons/linkicon.png
toolbar button.
To change this setting in a help view, select \inlineimage icons/linkicon.png.
\sa {Get help}
\sa {Read Documentation}{How To: Read Documentation}
*/
/*!
@@ -315,27 +315,27 @@
\title Select the help start page
You can select the page to display when you open the \uicontrol Help mode in the
\preferences > \uicontrol Help > \uicontrol General >
\uicontrol {On help start} field.
To set the page to show when you open the \uicontrol Help mode, go to
\preferences > \uicontrol Help > \uicontrol General and select
\uicontrol {On help start}.
\image qtcreator-preferences-help-general.webp {General tab in Help preferences}
\list
\li To display the page and help views that were open when you exited the mode,
\li To show the page and help views that were open when you exited the mode,
select the \uicontrol {Show My Tabs from Last Session} option. However, Web pages
are not opened because loading them would slow down opening the \uicontrol Help
mode.
\li To display a particular page, select \uicontrol {Show My Home Page}, and specify
the page in the \uicontrol {Home Page} field.
\li To show a particular page, select \uicontrol {Show My Home Page}, and specify
the page in \uicontrol {Home Page}.
\li To display a blank page, select the \uicontrol {Show a Blank Page} option. You can
also select the \uicontrol {Use Blank Page} button to set a blank page as your
\li To show a blank page, select the \uicontrol {Show a Blank Page} option.
Select \uicontrol {Use Blank Page} to set a blank page as your
home page.
\endlist
\sa {Get help}
\sa {Read Documentation}{How To: Read Documentation}
*/
/*!
@@ -350,40 +350,40 @@
\title Filter documentation
You can filter the documents displayed in the \uicontrol Help mode to find
Filter the documents in the \uicontrol Help mode to find
relevant information faster. Select a filter from a list of filters. The
contents of the \uicontrol Index and \uicontrol Contents
view in the sidebar change accordingly.
\image qtcreator-help-filters.png {Filters field on the Help mode toolbar}
\image qtcreator-help-filters.png {Filters on the Help mode toolbar}
\section1 Add filters
You can define your own filters to display documentation for a set of
Define your own filters to show documentation for a set of
Qt modules and versions.
To add filters:
\list 1
\li Select \preferences > \uicontrol Help > \uicontrol Filters.
\li Go to \preferences > \uicontrol Help > \uicontrol Filters.
\image qtcreator-help-filter-attributes.png {Filters tab in Help preferences}
\li Select \inlineimage icons/plus.png
to add a new filter in the \uicontrol {Add Filter} dialog.
\li In the \uicontrol {Filter name} field, enter a name for the filter,
\li In \uicontrol {Filter name}, enter a name for the filter,
and then select \uicontrol {OK} to return to the \uicontrol Filters
tab.
\li In the \uicontrol Components field, select the Qt modules to include
\li In \uicontrol Components, select the Qt modules to include
in the filter.
\li In the \uicontrol Versions field, select the Qt versions to include
\li In \uicontrol Versions, select the Qt versions to include
in the filter.
\li Click \uicontrol OK.
\li Select \uicontrol OK.
\li In the \uicontrol Help mode, select the filter in the list of
filters to see the filtered documentation in the sidebar.
@@ -392,15 +392,14 @@
\section1 Change filters
To modify the selected filter, add and remove Qt modules and versions, and
To modify the selected filter, add and remove Qt modules and versions and
then select \uicontrol Apply.
To rename the selected filter, select \uicontrol Rename.
\section1 Remove filters
To remove the selected filter select \inlineimage icons/minus.png
.
To remove the selected filter, select \inlineimage icons/minus.png.
\sa {Get help}
\sa {Read Documentation}{How To: Read Documentation}
*/

View File

@@ -14,6 +14,7 @@
\ingroup creator-how-to-projects-configure
\ingroup creator-how-to-manage-kits
\ingroup creator-how-to-projects
\title Open projects
@@ -49,6 +50,15 @@
\endlist
\endlist
\section1 Open directories as projects
To open a directory as a project, go to \uicontrol File >
\uicontrol {Open Workspace}.
\QC generates the \e .qtcreator/project.json project file in the directory
for setting a project name and file exclusion filters. You can open either
the JSON file or the workspace to open the project the next time.
\section1 Re-configure projects
\QC stores information that it needs to build projects in a .user file. If

View File

@@ -637,8 +637,8 @@
\li Use Utils::FilePath for any QString that semantically is a file or directory,
see also \l{Passing File Names}.
\li Prefer using Utils::FilePath over any use of QDir and QFileInfo.
\li Prefer using Utils::QtcProcess over QProcess.
\li If Utils::FilePath or Utils::QtcProcess functionality is not sufficient
\li Prefer using Utils::Process over QProcess.
\li If Utils::FilePath or Utils::Process functionality is not sufficient
for your purpose, prefer enhancing them over falling back to QString
or QProcess.
\li Avoid platform #ifdefs unless they are absolutely needed for locally

View File

@@ -187,7 +187,7 @@ class Dumper(DumperBase):
self.nativeTypeEnumDisplay(nativeType, intval, form)
return typeid
def listNativeValueChildren(self, nativeValue: cdbext.Value, include_bases: bool) -> list[DumperBase.Value]:
def listNativeValueChildren(self, nativeValue: cdbext.Value, include_bases: bool):
fields = []
index = 0
nativeMember = nativeValue.childFromIndex(index)
@@ -202,13 +202,13 @@ class Dumper(DumperBase):
nativeMember = nativeValue.childFromIndex(index)
return fields
def listValueChildren(self, value: DumperBase.Value, include_bases=True) -> list[DumperBase.Value]:
def listValueChildren(self, value: DumperBase.Value, include_bases=True):
nativeValue = value.nativeValue
if nativeValue is None:
nativeValue = cdbext.createValue(value.address(), self.lookupNativeType(value.type.name, 0))
return self.listNativeValueChildren(nativeValue, include_bases)
def nativeListMembers(self, value: DumperBase.Value, native_type: cdbext.Type, include_bases: bool) -> list[DumperBase.Value]:
def nativeListMembers(self, value: DumperBase.Value, native_type: cdbext.Type, include_bases: bool):
nativeValue = value.nativeValue
if nativeValue is None:
nativeValue = cdbext.createValue(value.address(), native_type)
@@ -629,100 +629,6 @@ class Dumper(DumperBase):
if self.showQObjectNames:
self.tryPutQObjectGuts(value)
def putFormattedPointerX(self, value: DumperBase.Value):
self.putOriginalAddress(value.address())
pointer = value.pointer()
self.putAddress(pointer)
if pointer == 0:
self.putType(value.type)
self.putValue('0x0')
return
typeName = value.type.name
try:
self.readRawMemory(pointer, 1)
except:
# Failure to dereference a pointer should at least
# show the value of a pointer.
#DumperBase.warn('BAD POINTER: %s' % value)
self.putValue('0x%x' % pointer)
self.putType(typeName)
return
if self.currentIName.endswith('.this'):
self.putDerefedPointer(value)
return
displayFormat = self.currentItemFormat(value.type.name)
if value.type.targetName == 'void':
#DumperBase.warn('VOID POINTER: %s' % displayFormat)
self.putType(typeName)
self.putSymbolValue(pointer)
return
if displayFormat == DisplayFormat.Raw:
# Explicitly requested bald pointer.
#DumperBase.warn('RAW')
self.putType(typeName)
self.putValue('0x%x' % pointer)
self.putExpandable()
if self.currentIName in self.expandedINames:
with Children(self):
with SubItem(self, '*'):
self.putItem(value.dereference())
return
limit = self.displayStringLimit
if displayFormat in (DisplayFormat.SeparateLatin1String, DisplayFormat.SeparateUtf8String):
limit = 1000000
if self.tryPutSimpleFormattedPointer(pointer, typeName,
value.type.targetName, displayFormat, limit):
self.putExpandable()
return
if DisplayFormat.Array10 <= displayFormat and displayFormat <= DisplayFormat.Array10000:
n = (10, 100, 1000, 10000)[displayFormat - DisplayFormat.Array10]
self.putType(typeName)
self.putItemCount(n)
self.putArrayData(value.pointer(), n, value.type.targetName)
return
#DumperBase.warn('AUTODEREF: %s' % self.autoDerefPointers)
#DumperBase.warn('INAME: %s' % self.currentIName)
if self.autoDerefPointers:
# Generic pointer type with AutomaticFormat, but never dereference char types:
if value.type.targetName.strip() not in (
'char',
'signed char',
'int8_t',
'qint8',
'unsigned char',
'uint8_t',
'quint8',
'wchar_t',
'CHAR',
'WCHAR',
'char8_t',
'char16_t',
'char32_t'
):
self.putDerefedPointer(value)
return
#DumperBase.warn('GENERIC PLAIN POINTER: %s' % value.type)
#DumperBase.warn('ADDR PLAIN POINTER: 0x%x' % value.laddress)
self.putType(typeName)
self.putSymbolValue(pointer)
self.putExpandable()
if self.currentIName in self.expandedINames:
with Children(self):
with SubItem(self, '*'):
self.putItem(value.dereference())
def putCStyleArray(self, value: DumperBase.Value):
arrayType = value.type
innerType = arrayType.target()

View File

@@ -1295,8 +1295,6 @@ class DumperBase():
innerType = arrayType.target()
#self.warn("ARRAY TYPE: %s" % arrayType)
#self.warn("INNER TYPE: %s" % innerType)
if innerType is None:
innerType = value.type.target()
address = value.address()
if address:

View File

@@ -680,11 +680,18 @@ int main(int argc, char **argv)
setPixmapCacheLimit();
loadFonts();
// On 100% or 200% scaling we can use the default 'Vista' style on Windows
if (Utils::HostOsInfo::isWindowsHost() && !hasStyleOption) {
// The Windows 11 default style (Qt 6.7) has major issues, therefore
// set the previous default style: "windowsvista"
// FIXME: check newer Qt Versions
QApplication::setStyle(QLatin1String("windowsvista"));
// On scaling different than 100% or 200% use the "fusion" style
qreal tmp;
const bool fractionalDpi = !qFuzzyIsNull(std::modf(qApp->devicePixelRatio(), &tmp));
if (Utils::HostOsInfo::isWindowsHost() && fractionalDpi && !hasStyleOption)
if (fractionalDpi)
QApplication::setStyle(QLatin1String("fusion"));
}
const int threadCount = QThreadPool::globalInstance()->maxThreadCount();
QThreadPool::globalInstance()->setMaxThreadCount(qMax(4, 2 * threadCount));

View File

@@ -89,8 +89,8 @@ static const QIcon &icon(IconIndex icon)
class PluginItem : public TreeItem
{
public:
PluginItem(PluginSpec *spec, PluginView *view)
: m_spec(spec), m_view(view)
PluginItem(PluginSpec *spec, PluginData *data)
: m_spec(spec), m_data(data)
{}
QVariant data(int column, int role) const override
@@ -167,7 +167,7 @@ public:
bool setData(int column, const QVariant &data, int role) override
{
if (column == LoadedColumn && role == Qt::CheckStateRole)
return m_view->setPluginsEnabled({m_spec}, data.toBool());
return m_data->setPluginsEnabled({m_spec}, data.toBool());
return false;
}
@@ -193,19 +193,19 @@ public:
public:
PluginSpec *m_spec; // Not owned.
PluginView *m_view; // Not owned.
PluginData *m_data; // Not owned.
};
class CollectionItem : public TreeItem
{
public:
CollectionItem(const QString &name, const PluginSpecs &plugins, PluginView *view)
CollectionItem(const QString &name, const PluginSpecs &plugins, PluginData *data)
: m_name(name)
, m_plugins(plugins)
, m_view(view)
, m_data(data)
{
for (PluginSpec *spec : plugins)
appendChild(new PluginItem(spec, view));
appendChild(new PluginItem(spec, data));
}
QVariant data(int column, int role) const override
@@ -241,7 +241,7 @@ public:
if (column == LoadedColumn && role == Qt::CheckStateRole) {
const PluginSpecs affectedPlugins
= Utils::filtered(m_plugins, [](PluginSpec *spec) { return !spec->isRequired(); });
if (m_view->setPluginsEnabled(toSet(affectedPlugins), data.toBool())) {
if (m_data->setPluginsEnabled(toSet(affectedPlugins), data.toBool())) {
update();
return true;
}
@@ -260,19 +260,31 @@ public:
public:
QString m_name;
const PluginSpecs m_plugins;
PluginView *m_view; // Not owned.
PluginData *m_data; // Not owned.
};
} // Internal
using namespace ExtensionSystem::Internal;
PluginData::PluginData(QWidget *parent, PluginView *owner)
: m_parent(parent), m_pluginView(owner)
{
m_model = new TreeModel<TreeItem, CollectionItem, PluginItem>(parent);
m_model->setHeader({ Tr::tr("Name"), Tr::tr("Load"), Tr::tr("Version"), Tr::tr("Vendor") });
m_sortModel = new CategorySortFilterModel(parent);
m_sortModel->setSourceModel(m_model);
m_sortModel->setSortRole(SortRole);
m_sortModel->setFilterKeyColumn(-1/*all*/);
}
/*!
Constructs a plugin view with \a parent that displays a list of plugins
from a plugin manager.
*/
PluginView::PluginView(QWidget *parent)
: QWidget(parent)
: QWidget(parent), m_data(this, this)
{
m_categoryView = new TreeView(this);
m_categoryView->setAlternatingRowColors(true);
@@ -282,14 +294,7 @@ PluginView::PluginView(QWidget *parent)
m_categoryView->setSelectionMode(QAbstractItemView::SingleSelection);
m_categoryView->setSelectionBehavior(QAbstractItemView::SelectRows);
m_model = new TreeModel<TreeItem, CollectionItem, PluginItem>(this);
m_model->setHeader({ Tr::tr("Name"), Tr::tr("Load"), Tr::tr("Version"), Tr::tr("Vendor") });
m_sortModel = new CategorySortFilterModel(this);
m_sortModel->setSourceModel(m_model);
m_sortModel->setSortRole(SortRole);
m_sortModel->setFilterKeyColumn(-1/*all*/);
m_categoryView->setModel(m_sortModel);
m_categoryView->setModel(m_data.m_sortModel);
auto *gridLayout = new QGridLayout(this);
gridLayout->setContentsMargins(2, 2, 2, 2);
@@ -329,7 +334,7 @@ PluginSpec *PluginView::currentPlugin() const
*/
void PluginView::setFilter(const QString &filter)
{
m_sortModel->setFilterRegularExpression(
m_data.m_sortModel->setFilterRegularExpression(
QRegularExpression(QRegularExpression::escape(filter),
QRegularExpression::CaseInsensitiveOption));
m_categoryView->expandAll();
@@ -337,15 +342,15 @@ void PluginView::setFilter(const QString &filter)
PluginSpec *PluginView::pluginForIndex(const QModelIndex &index) const
{
const QModelIndex &sourceIndex = m_sortModel->mapToSource(index);
PluginItem *item = m_model->itemForIndexAtLevel<2>(sourceIndex);
const QModelIndex &sourceIndex = m_data.m_sortModel->mapToSource(index);
PluginItem *item = m_data.m_model->itemForIndexAtLevel<2>(sourceIndex);
return item ? item->m_spec: nullptr;
}
void PluginView::updatePlugins()
{
// Model.
m_model->clear();
m_data.m_model->clear();
const QHash<QString, PluginSpecs> pluginCollections
= PluginManager::pluginCollections();
@@ -353,17 +358,22 @@ void PluginView::updatePlugins()
const auto end = pluginCollections.cend();
for (auto it = pluginCollections.cbegin(); it != end; ++it) {
const QString name = it.key().isEmpty() ? Tr::tr("Utilities") : it.key();
collections.push_back(new CollectionItem(name, it.value(), this));
collections.push_back(new CollectionItem(name, it.value(), &m_data));
}
Utils::sort(collections, &CollectionItem::m_name);
for (CollectionItem *collection : std::as_const(collections))
m_model->rootItem()->appendChild(collection);
m_data.m_model->rootItem()->appendChild(collection);
emit m_model->layoutChanged();
emit m_data.m_model->layoutChanged();
m_categoryView->expandAll();
}
PluginData &PluginView::data()
{
return m_data;
}
static QString pluginListString(const QSet<PluginSpec *> &plugins)
{
QStringList names = Utils::transform<QList>(plugins, &PluginSpec::name);
@@ -371,7 +381,7 @@ static QString pluginListString(const QSet<PluginSpec *> &plugins)
return names.join(QLatin1Char('\n'));
}
bool PluginView::setPluginsEnabled(const QSet<PluginSpec *> &plugins, bool enable)
bool PluginData::setPluginsEnabled(const QSet<PluginSpec *> &plugins, bool enable)
{
QSet<PluginSpec *> additionalPlugins;
if (enable) {
@@ -383,7 +393,7 @@ bool PluginView::setPluginsEnabled(const QSet<PluginSpec *> &plugins, bool enabl
}
additionalPlugins.subtract(plugins);
if (!additionalPlugins.isEmpty()) {
if (QMessageBox::question(this, Tr::tr("Enabling Plugins"),
if (QMessageBox::question(m_parent, Tr::tr("Enabling Plugins"),
Tr::tr("Enabling\n%1\nwill also enable the following plugins:\n\n%2")
.arg(pluginListString(plugins), pluginListString(additionalPlugins)),
QMessageBox::Ok | QMessageBox::Cancel,
@@ -400,7 +410,7 @@ bool PluginView::setPluginsEnabled(const QSet<PluginSpec *> &plugins, bool enabl
}
additionalPlugins.subtract(plugins);
if (!additionalPlugins.isEmpty()) {
if (QMessageBox::question(this, Tr::tr("Disabling Plugins"),
if (QMessageBox::question(m_parent, Tr::tr("Disabling Plugins"),
Tr::tr("Disabling\n%1\nwill also disable the following plugins:\n\n%2")
.arg(pluginListString(plugins), pluginListString(additionalPlugins)),
QMessageBox::Ok | QMessageBox::Cancel,
@@ -422,13 +432,16 @@ bool PluginView::setPluginsEnabled(const QSet<PluginSpec *> &plugins, bool enabl
item->updateColumn(LoadedColumn);
item->parent()->updateColumn(LoadedColumn);
}
emit pluginsChanged(affectedPlugins, enable);
if (m_pluginView)
emit m_pluginView->pluginsChanged(affectedPlugins, enable);
return true;
}
void PluginView::cancelChanges()
{
for (auto element : m_affectedPlugins)
for (auto element : m_data.m_affectedPlugins)
element.first->setEnabledBySettings(element.second);
}

View File

@@ -22,12 +22,32 @@ class TreeView;
namespace ExtensionSystem {
class PluginSpec;
class PluginView;
namespace Internal {
class CollectionItem;
class PluginItem;
} // Internal
class EXTENSIONSYSTEM_EXPORT PluginData
{
public:
explicit PluginData(QWidget *parent, PluginView *pluginView = nullptr);
bool setPluginsEnabled(const QSet<PluginSpec *> &plugins, bool enable);
private:
QWidget *m_parent = nullptr;
PluginView *m_pluginView = nullptr;
Utils::TreeModel<Utils::TreeItem, Internal::CollectionItem, Internal::PluginItem> *m_model;
Utils::CategorySortFilterModel *m_sortModel;
std::unordered_map<PluginSpec *, bool> m_affectedPlugins;
friend class Internal::CollectionItem;
friend class Internal::PluginItem;
friend class PluginView;
};
class EXTENSIONSYSTEM_EXPORT PluginView : public QWidget
{
Q_OBJECT
@@ -40,6 +60,8 @@ public:
void setFilter(const QString &filter);
void cancelChanges();
PluginData &data();
signals:
void currentPluginChanged(ExtensionSystem::PluginSpec *spec);
void pluginActivated(ExtensionSystem::PluginSpec *spec);
@@ -48,15 +70,9 @@ signals:
private:
PluginSpec *pluginForIndex(const QModelIndex &index) const;
void updatePlugins();
bool setPluginsEnabled(const QSet<PluginSpec *> &plugins, bool enable);
PluginData m_data;
Utils::TreeView *m_categoryView;
Utils::TreeModel<Utils::TreeItem, Internal::CollectionItem, Internal::PluginItem> *m_model;
Utils::CategorySortFilterModel *m_sortModel;
std::unordered_map<PluginSpec *, bool> m_affectedPlugins;
friend class Internal::CollectionItem;
friend class Internal::PluginItem;
};
} // namespace ExtensionSystem

View File

@@ -18,6 +18,7 @@
#include <QtCore/QTime>
#include <QtCore/QTimer>
using namespace Qt::StringLiterals;
using namespace std::chrono;
QT_BEGIN_NAMESPACE
@@ -1298,11 +1299,11 @@ const void *Loop::valuePtr() const
using StoragePtr = void *;
static QString s_activeStorageWarning =
static constexpr QLatin1StringView s_activeStorageWarning =
"The referenced storage is not reachable in the running tree. "
"A nullptr will be returned which might lead to a crash in the calling code. "
"It is possible that no storage was added to the tree, "
"or the storage is not reachable from where it is referenced.";
"or the storage is not reachable from where it is referenced."_L1;
class StorageThreadData
{

View File

@@ -211,7 +211,7 @@ QIcon Icon::sideBarIcon(const Icon &classic, const Icon &flat)
}
QIcon Icon::modeIcon(const Icon &classic, const Icon &flat,
[[__maybe_unused__]] const Icon &flatActive)
[[maybe_unused]] const Icon &flatActive)
{
QIcon result = sideBarIcon(classic, flat);
return result;

View File

@@ -41,6 +41,11 @@ enum class Channel {
Error
};
enum class DetachedChannelMode {
Forward,
Discard
};
enum class TextChannelMode {
// Keep | Emit | Emit
// raw | text | content

View File

@@ -1317,12 +1317,18 @@ void Process::closeWriteChannel()
d->sendControlSignal(ControlSignal::CloseWriteChannel);
}
bool Process::startDetached(const CommandLine &cmd, const FilePath &workingDirectory, qint64 *pid)
bool Process::startDetached(const CommandLine &cmd, const FilePath &workingDirectory,
DetachedChannelMode channelMode, qint64 *pid)
{
return QProcess::startDetached(cmd.executable().toUserOutput(),
cmd.splitArguments(),
workingDirectory.toUserOutput(),
pid);
QProcess process;
process.setProgram(cmd.executable().toUserOutput());
process.setArguments(cmd.splitArguments());
process.setWorkingDirectory(workingDirectory.toUserOutput());
if (channelMode == DetachedChannelMode::Discard) {
process.setStandardOutputFile(QProcess::nullDevice());
process.setStandardErrorFile(QProcess::nullDevice());
}
return process.startDetached(pid);
}
void Process::setLowPriority()

View File

@@ -137,6 +137,7 @@ public:
// Some of them could be aggregated in another public utils class.
static bool startDetached(const CommandLine &cmd, const FilePath &workingDirectory = {},
DetachedChannelMode channelMode = DetachedChannelMode::Forward,
qint64 *pid = nullptr);
// Starts the command and waits for finish.

View File

@@ -974,8 +974,12 @@ expected_str<void> createAvd(const CreateAvdInfo &info, bool force)
GuardLocker locker(s_instance->m_avdPathGuard);
process.runBlocking();
if (process.result() != ProcessResult::FinishedWithSuccess)
return Utils::make_unexpected(process.exitMessage());
if (process.result() != ProcessResult::FinishedWithSuccess) {
const QString stdErr = process.stdErr();
const QString errorMessage = stdErr.isEmpty() ? process.exitMessage()
: process.exitMessage() + "\n\n" + stdErr;
return Utils::make_unexpected(errorMessage);
}
return {};
}

View File

@@ -55,12 +55,12 @@ static std::optional<QDomElement> documentElement(const FilePath &fileName)
{
QFile file(fileName.toString());
if (!file.open(QIODevice::ReadOnly)) {
MessageManager::writeDisrupting(Tr::tr("Cannot open: %1").arg(fileName.toUserOutput()));
MessageManager::writeDisrupting(Tr::tr("Cannot open \"%1\".").arg(fileName.toUserOutput()));
return {};
}
QDomDocument doc;
if (!doc.setContent(file.readAll())) {
MessageManager::writeDisrupting(Tr::tr("Cannot parse: %1").arg(fileName.toUserOutput()));
MessageManager::writeDisrupting(Tr::tr("Cannot parse \"%1\".").arg(fileName.toUserOutput()));
return {};
}
return doc.documentElement();

View File

@@ -313,7 +313,7 @@ static GroupItem updateRecipe(const Storage<DialogStorage> &dialogStorage)
const QStringList args = {"--update", sdkRootArg()};
QuestionProgressDialog *dialog = dialogStorage->m_dialog.get();
setupSdkProcess(args, &process, dialog, 0, 1);
dialog->appendMessage(Tr::tr("Updating installed packages....") + '\n', NormalMessageFormat);
dialog->appendMessage(Tr::tr("Updating installed packages...") + '\n', NormalMessageFormat);
dialog->setProgress(0);
};
const auto onDone = [dialogStorage](DoneWith result) {

View File

@@ -38,7 +38,8 @@ AppStatisticsMonitorChart::AppStatisticsMonitorChart(
m_chartView->setMinimumHeight(200);
m_chartView->setMinimumWidth(400);
const QBrush brushTitle(creatorColor(Theme::Token_Text_Muted));
const QBrush brush(creatorColor(Theme::Token_Background_Default));
// const QBrush brush(creatorColor(Theme::Token_Background_Default)); left for the future
const QBrush brush(creatorColor(Theme::BackgroundColorNormal));
const QPen penBack(creatorColor(Theme::Token_Text_Muted));
const QPen penAxis(creatorColor(Theme::Token_Text_Muted));
@@ -160,8 +161,8 @@ void Chart::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QPainter painter(this);
painter.fillRect(rect(), creatorColor(Theme::Token_Background_Default));
// painter.fillRect(rect(), creatorColor(Theme::Token_Background_Default)); left for the future
painter.fillRect(rect(), creatorColor(Theme::BackgroundColorNormal));
// add the name of the chart in the middle of the widget width and on the top
painter.drawText(

View File

@@ -69,7 +69,7 @@ ProjectTestSettingsWidget::ProjectTestSettingsWidget(Project *project)
m_pathFilters = new QTreeWidget;
m_pathFilters->setHeaderHidden(true);
m_pathFilters->setRootIsDecorated(false);
QLabel *filterLabel = new QLabel(Tr::tr("Wildcard expressions for filtering"), this);
QLabel *filterLabel = new QLabel(Tr::tr("Wildcard expressions for filtering:"), this);
QPushButton *addFilter = new QPushButton(Tr::tr("Add"), this);
QPushButton *removeFilter = new QPushButton(Tr::tr("Remove"), this);
removeFilter->setEnabled(false);

View File

@@ -35,7 +35,7 @@ TestSettings::TestSettings()
useTimeout.setSettingsKey("UseTimeout");
useTimeout.setDefaultValue(false);
useTimeout.setLabelText(Tr::tr("Timeout"));
useTimeout.setLabelText(Tr::tr("Timeout:"));
useTimeout.setToolTip(Tr::tr("Use a timeout while executing test cases."));
timeout.setSettingsKey("Timeout");

View File

@@ -879,17 +879,17 @@ public:
dashboardUrl.setQuery(search.toUrlQuery(QueryMode::FilterQuery));
QMenu *menu = new QMenu;
auto action = new QAction(Tr::tr("Open issue in Dashboard"), menu);
auto action = new QAction(Tr::tr("Open Issue in Dashboard"), menu);
menu->addAction(action);
QObject::connect(action, &QAction::triggered, menu, [issueBaseUrl] {
QDesktopServices::openUrl(issueBaseUrl);
});
action = new QAction(Tr::tr("Open table in Dashboard"), menu);
action = new QAction(Tr::tr("Open Table in Dashboard"), menu);
QObject::connect(action, &QAction::triggered, menu, [dashboardUrl] {
QDesktopServices::openUrl(dashboardUrl);
});
menu->addAction(action);
action = new QAction(Tr::tr("Copy Dashboard link to clipboard"), menu);
action = new QAction(Tr::tr("Copy Dashboard Link to Clipboard"), menu);
QObject::connect(action, &QAction::triggered, menu, [dashboardUrl] {
if (auto clipboard = QGuiApplication::clipboard())
clipboard->setText(dashboardUrl.toString());

View File

@@ -308,16 +308,17 @@ AxivionSettingsWidget::AxivionSettingsWidget()
auto addButton = new QPushButton(Tr::tr("Add..."), this);
m_edit = new QPushButton(Tr::tr("Edit..."), this);
m_remove = new QPushButton(Tr::tr("Remove"), this);
Column {
Row {
Form {
Tr::tr("Default dashboard server"), m_dashboardServers, br
}, st,
Column { addButton, m_edit, st, m_remove },
Column{
Row{
Form{Tr::tr("Default dashboard server:"), m_dashboardServers, br},
st,
Column{addButton, m_edit, st, m_remove},
},
Space(10), br,
Row { settings().highlightMarks }, st
}.attachTo(this);
Space(10),
br,
Row{settings().highlightMarks},
st}
.attachTo(this);
connect(addButton, &QPushButton::clicked, this, [this] {
// add an empty item unconditionally
@@ -357,9 +358,10 @@ void AxivionSettingsWidget::updateEnabledStates()
void AxivionSettingsWidget::removeCurrentServerConfig()
{
const QString config = m_dashboardServers->currentData().value<AxivionServer>().displayString();
if (QMessageBox::question(ICore::dialogParent(), Tr::tr("Remove Server Configuration"),
Tr::tr("Do you really want to remove the server configuration "
"\"%1\"?").arg(config))
if (QMessageBox::question(
ICore::dialogParent(),
Tr::tr("Remove Server Configuration"),
Tr::tr("Remove the server configuration \"%1\"?").arg(config))
!= QMessageBox::Yes) {
return;
}

View File

@@ -80,7 +80,7 @@ void DeviceDetector::handleDeviceEvent(QdbDeviceTracker::DeviceEventType eventTy
DeviceManager * const dm = DeviceManager::instance();
if (eventType == QdbDeviceTracker::NewDevice) {
const QString name = Tr::tr("Qt Debug Bridge device %1").arg(serial);
const QString name = Tr::tr("Boot to Qt device %1").arg(serial);
QdbDevice::Ptr device = QdbDevice::create();
device->setupId(IDevice::AutoDetected, deviceId);
device->settings()->displayName.setValue(name);

View File

@@ -88,9 +88,9 @@ private:
}
showMessage(errorString, true);
if (!stdOut.isEmpty())
showMessage(Tr::tr("stdout was: \"%1\"").arg(stdOut));
showMessage(Tr::tr("stdout was: \"%1\".").arg(stdOut));
if (!stdErr.isEmpty())
showMessage(Tr::tr("stderr was: \"%1\"").arg(stdErr));
showMessage(Tr::tr("stderr was: \"%1\".").arg(stdErr));
} else {
showMessage(Tr::tr("Commands on device \"%1\" finished successfully.")
.arg(m_deviceName));
@@ -107,7 +107,7 @@ private:
QdbDevice::QdbDevice()
{
setDisplayType(Tr::tr("Boot2Qt Device"));
setDisplayType(Tr::tr("Boot to Qt Device"));
setType(Constants::QdbLinuxOsType);
addDeviceAction({Tr::tr("Reboot Device"), [](const IDevice::Ptr &device, QWidget *) {
@@ -218,7 +218,7 @@ public:
QdbDeviceWizard(QWidget *parent)
: QWizard(parent)
{
setWindowTitle(Tr::tr("Boot2Qt Network Device Setup"));
setWindowTitle(Tr::tr("Boot to Qt Network Device Setup"));
settingsPage.setCommitPage(true);
enum { SettingsPageId };
@@ -253,7 +253,7 @@ public:
QdbLinuxDeviceFactory()
: IDeviceFactory(Constants::QdbLinuxOsType)
{
setDisplayName(Tr::tr("Boot2Qt Device"));
setDisplayName(Tr::tr("Boot to Qt Device"));
setCombinedIcon(":/qdb/images/qdbdevicesmall.png", ":/qdb/images/qdbdevice.png");
setQuickCreationAllowed(true);
setConstructionFunction(&QdbDevice::create);

View File

@@ -106,7 +106,7 @@ public:
{
setConfigBaseId(Constants::QdbDeployConfigurationId);
addSupportedTargetDeviceType(Constants::QdbLinuxOsType);
setDefaultDisplayName(Tr::tr("Deploy to Boot2Qt target"));
setDefaultDisplayName(Tr::tr("Deploy to Boot to Qt target"));
setUseDeploymentDataView();
addInitialStep(RemoteLinux::Constants::MakeInstallStepId, [](Target *target) {

View File

@@ -33,7 +33,7 @@ public:
QdbRunConfiguration(Target *target, Id id)
: RunConfiguration(target, id)
{
setDefaultDisplayName(Tr::tr("Run on Boot2Qt Device"));
setDefaultDisplayName(Tr::tr("Run on Boot to Qt Device"));
executable.setDeviceSelector(target, ExecutableAspect::RunDevice);
executable.setSettingsKey("QdbRunConfig.RemoteExecutable");
@@ -87,7 +87,7 @@ private:
Tasks tasks;
if (executable().isEmpty()) {
tasks << BuildSystemTask(Task::Warning, Tr::tr("The remote executable must be set "
"in order to run on a Boot2Qt device."));
"to run on a Boot to Qt device."));
}
return tasks;
}

View File

@@ -65,7 +65,7 @@ QString overridingEnvironmentVariable(QdbTool tool)
void showMessage(const QString &message, bool important)
{
const QString fullMessage = Tr::tr("Boot2Qt: %1").arg(message);
const QString fullMessage = Tr::tr("Boot to Qt: %1").arg(message);
if (important)
Core::MessageManager::writeFlashing(fullMessage);
else

View File

@@ -358,8 +358,14 @@ void ClangdCompletionItem::apply(TextDocumentManipulator &manipulator,
}
// Avoid inserting characters that are already there
// For include file completions, also consider a possibly pre-existing
// closing quote or angle bracket.
QTextCursor cursor = manipulator.textCursorAt(rangeStart);
cursor.movePosition(QTextCursor::EndOfWord);
if (kind == CompletionItemKind::File && !textToBeInserted.isEmpty()
&& textToBeInserted.right(1) == manipulator.textAt(cursor.position(), 1)) {
cursor.setPosition(cursor.position() + 1);
}
const QString textAfterCursor = manipulator.textAt(currentPos, cursor.position() - currentPos);
if (currentPos < cursor.position()
&& textToBeInserted != textAfterCursor

View File

@@ -110,7 +110,7 @@ ClangFormatGlobalConfigWidget::ClangFormatGlobalConfigWidget(ICodeStylePreferenc
// clang-format off
Group globalSettingsGroupBox {
bindTo(&globalSettingsGroupBoxWidget),
title(Tr::tr("ClangFormat Settings:")),
title(Tr::tr("ClangFormat Settings")),
Column {
m_useGlobalSettings,
Form {

View File

@@ -1585,7 +1585,7 @@ CMakeBuildConfiguration::CMakeBuildConfiguration(Target *target, Id id)
if (info.buildDirectory.isEmpty()) {
setBuildDirectory(shadowBuildDirectory(target->project()->projectFilePath(),
k,
info.typeName,
info.displayName,
info.buildType));
}
@@ -1960,6 +1960,9 @@ CMakeBuildConfigurationFactory::CMakeBuildConfigurationFactory()
k,
info.typeName,
info.buildType);
} else {
info.displayName.clear(); // ask for a name
info.buildDirectory.clear(); // This depends on the displayName
}
result << info;
}

View File

@@ -642,6 +642,25 @@ bool CMakeBuildSystem::addTsFiles(Node *context, const FilePaths &filePaths, Fil
return false;
}
static bool isGlobbingFunction(const cmListFile &cmakeListFile, const cmListFileFunction &func)
{
// Check if the filename is part of globbing variable result
const auto globFunctions = std::get<0>(
Utils::partition(cmakeListFile.Functions, [](const auto &f) {
return f.LowerCaseName() == "file" && f.Arguments().size() > 2
&& (f.Arguments().front().Value == "GLOB"
|| f.Arguments().front().Value == "GLOB_RECURSE");
}));
const auto globVariables = Utils::transform<QSet>(globFunctions, [](const auto &func) {
return std::string("${") + func.Arguments()[1].Value + "}";
});
return Utils::anyOf(func.Arguments(), [globVariables](const auto &arg) {
return globVariables.contains(arg.Value);
});
}
bool CMakeBuildSystem::addSrcFiles(Node *context, const FilePaths &filePaths, FilePaths *notAdded)
{
if (notAdded)
@@ -670,6 +689,11 @@ bool CMakeBuildSystem::addSrcFiles(Node *context, const FilePaths &filePaths, Fi
return false;
}
const bool haveGlobbing = isGlobbingFunction(cmakeListFile.value(), function.value());
n->setVisibleAfterAddFileAction(!haveGlobbing);
if (haveGlobbing && settings(project()).autorunCMake()) {
runCMake();
} else {
const std::string target_name = function->Arguments().front().Value;
auto qtAddModule = [target_name](const auto &func) {
return (func.LowerCaseName() == "qt_add_qml_module"
@@ -691,6 +715,7 @@ bool CMakeBuildSystem::addSrcFiles(Node *context, const FilePaths &filePaths, Fi
qCCritical(cmakeBuildSystemLog) << inserted.error();
return false;
}
}
if (notAdded)
notAdded->removeIf([filePaths](const FilePath &p) { return filePaths.contains(p); });
@@ -770,22 +795,7 @@ CMakeBuildSystem::projectFileArgumentPosition(const QString &targetName, const Q
return ProjectFileArgumentPosition{filePathArgument, targetCMakeFile, fileName};
} else {
// Check if the filename is part of globbing variable result
const auto globFunctions = std::get<0>(
Utils::partition(cmakeListFile->Functions, [](const auto &f) {
return f.LowerCaseName() == "file" && f.Arguments().size() > 2
&& (f.Arguments().front().Value == "GLOB"
|| f.Arguments().front().Value == "GLOB_RECURSE");
}));
const auto globVariables = Utils::transform<QSet>(globFunctions, [](const auto &func) {
return std::string("${") + func.Arguments()[1].Value + "}";
});
const auto haveGlobbing = Utils::anyOf(func->Arguments(),
[globVariables](const auto &arg) {
return globVariables.contains(arg.Value);
});
const auto haveGlobbing = isGlobbingFunction(cmakeListFile.value(), func.value());
if (haveGlobbing) {
return ProjectFileArgumentPosition{filePathArgument,
targetCMakeFile,
@@ -833,6 +843,7 @@ RemovedFilesFromProject CMakeBuildSystem::removeFiles(Node *context,
const FilePath projDir = n->filePath().canonicalPath();
const QString targetName = n->buildKey();
bool haveGlobbing = false;
for (const auto &file : filePaths) {
const QString fileName
= file.canonicalPath().relativePathFrom(projDir).cleanPath().toString();
@@ -847,6 +858,11 @@ RemovedFilesFromProject CMakeBuildSystem::removeFiles(Node *context,
continue;
}
if (filePos.value().fromGlobbing) {
haveGlobbing = true;
continue;
}
BaseTextEditor *editor = qobject_cast<BaseTextEditor *>(
Core::EditorManager::openEditorAt({filePos.value().cmakeFile,
static_cast<int>(filePos.value().argumentPosition.Line),
@@ -869,7 +885,6 @@ RemovedFilesFromProject CMakeBuildSystem::removeFiles(Node *context,
if (filePos->argumentPosition.Delim == cmListFileArgument::Quoted)
extraChars = 2;
if (!filePos.value().fromGlobbing)
editor->replace(filePos.value().relativeFileName.length() + extraChars, "");
editor->editorWidget()->autoIndent();
@@ -889,6 +904,9 @@ RemovedFilesFromProject CMakeBuildSystem::removeFiles(Node *context,
if (notRemoved && !badFiles.isEmpty())
*notRemoved = badFiles;
if (haveGlobbing && settings(project()).autorunCMake())
runCMake();
return badFiles.isEmpty() ? RemovedFilesFromProject::Ok : RemovedFilesFromProject::Error;
}
@@ -936,10 +954,6 @@ bool CMakeBuildSystem::renameFile(Node *context,
const FilePath newRelPath = newFilePath.canonicalPath().relativePathFrom(projDir).cleanPath();
const QString newRelPathName = newRelPath.toString();
// FilePath needs the file to exist on disk, the old file has already been renamed
const QString oldRelPathName
= newRelPath.parentDir().pathAppended(oldFilePath.fileName()).cleanPath().toString();
const QString targetName = n->buildKey();
const QString key
= QStringList{projDir.path(), targetName, oldFilePath.path(), newFilePath.path()}.join(
@@ -953,7 +967,9 @@ bool CMakeBuildSystem::renameFile(Node *context,
return false;
}
bool haveGlobbing = false;
do {
if (!fileToRename->fromGlobbing) {
BaseTextEditor *editor = qobject_cast<BaseTextEditor *>(
Core::EditorManager::openEditorAt(
{fileToRename->cmakeFile,
@@ -973,7 +989,6 @@ bool CMakeBuildSystem::renameFile(Node *context,
if (fileToRename->argumentPosition.Delim == cmListFileArgument::Quoted)
editor->setCursorPosition(editor->position() + 1);
if (!fileToRename->fromGlobbing)
editor->replace(fileToRename->relativeFileName.length(), newRelPathName);
editor->editorWidget()->autoIndent();
@@ -982,11 +997,17 @@ bool CMakeBuildSystem::renameFile(Node *context,
<< "Changes to" << fileToRename->cmakeFile.path() << "could not be saved.";
return false;
}
} else {
haveGlobbing = true;
}
// Try the next occurrence. This can happen if set_source_file_properties is used
fileToRename = projectFileArgumentPosition(targetName, oldRelPathName);
fileToRename = projectFileArgumentPosition(targetName, fileToRename->relativeFileName);
} while (fileToRename && !fileToRename->fromGlobbing);
if (haveGlobbing && settings(project()).autorunCMake())
runCMake();
return true;
}

View File

@@ -330,7 +330,8 @@ void CMakeEditorWidget::findLinkAt(const QTextCursor &cursor,
if (auto project = ProjectTree::currentProject()) {
buffer.replace("${CMAKE_SOURCE_DIR}", project->projectDirectory().path());
if (auto bs = ProjectTree::currentBuildSystem(); bs && bs->buildConfiguration()) {
auto bs = ProjectTree::currentBuildSystem();
if (bs && bs->buildConfiguration()) {
buffer.replace("${CMAKE_BINARY_DIR}", bs->buildConfiguration()->buildDirectory().path());
// Get the path suffix from current source dir to project source dir and apply it

View File

@@ -187,9 +187,16 @@ void CMakeTargetNode::setConfig(const CMakeConfig &config)
m_config = config;
}
void CMakeTargetNode::setVisibleAfterAddFileAction(bool visibleAfterAddFileAction)
{
m_visibleAfterAddFileAction = visibleAfterAddFileAction;
}
std::optional<FilePath> CMakeTargetNode::visibleAfterAddFileAction() const
{
if (m_visibleAfterAddFileAction)
return filePath().pathAppended(Constants::CMAKE_LISTS_TXT);
return std::nullopt;
}
void CMakeTargetNode::build()

View File

@@ -57,11 +57,14 @@ public:
QVariant data(Utils::Id role) const override;
void setConfig(const CMakeConfig &config);
void setVisibleAfterAddFileAction(bool visibleAfterAddFileAction);
private:
QString m_tooltip;
Utils::FilePath m_buildDirectory;
Utils::FilePath m_artifact;
CMakeConfig m_config;
bool m_visibleAfterAddFileAction = true;
};
} // CMakeProjectManager::Internal

View File

@@ -142,8 +142,9 @@ void CMakeSpecificSettings::readSettings()
} else {
Store data = storeFromVariant(project->namedSettings(Constants::Settings::GENERAL_ID));
if (data.isEmpty()) {
CMakeProject *cmakeProject = static_cast<CMakeProject *>(project);
if (cmakeProject->presetsData().havePresets && cmakeProject->presetsData().vendor) {
CMakeProject *cmakeProject = qobject_cast<CMakeProject *>(project);
if (cmakeProject && cmakeProject->presetsData().havePresets
&& cmakeProject->presetsData().vendor) {
useGlobalSettings = false;
data = storeFromMap(cmakeProject->presetsData().vendor.value());
fromMap(data);

View File

@@ -518,7 +518,7 @@ bool PresetsParser::parse(const Utils::FilePath &jsonFile, QString &errorMessage
m_presetsData.configurePresets,
jsonFile.parentDir())) {
errorMessage = ::CMakeProjectManager::Tr::tr(
"Invalid \"configurePresets\" section in %1 file")
"Invalid \"configurePresets\" section in file \"%1\".")
.arg(jsonFile.fileName());
return false;
}
@@ -527,14 +527,15 @@ bool PresetsParser::parse(const Utils::FilePath &jsonFile, QString &errorMessage
if (!parseBuildPresets(root.value("buildPresets"),
m_presetsData.buildPresets,
jsonFile.parentDir())) {
errorMessage = ::CMakeProjectManager::Tr::tr("Invalid \"buildPresets\" section in %1 file")
errorMessage = ::CMakeProjectManager::Tr::tr(
"Invalid \"buildPresets\" section in file \"%1\".")
.arg(jsonFile.fileName());
return false;
}
// optional
if (!parseVendor(root.value("vendor"), m_presetsData.vendor)) {
errorMessage = ::CMakeProjectManager::Tr::tr("Invalid \"vendor\" section in %1 file")
errorMessage = ::CMakeProjectManager::Tr::tr("Invalid \"vendor\" section in file \"%1\".")
.arg(jsonFile.fileName());
}

View File

@@ -4,6 +4,7 @@
#include "ieditorfactory.h"
#include "ieditorfactory_p.h"
#include "editormanager.h"
#include "../coreconstants.h"
#include <utils/algorithm.h>
#include <utils/mimeconstants.h>
@@ -16,15 +17,14 @@ namespace Core {
/* Find the one best matching the mimetype passed in.
* Recurse over the parent classes of the mimetype to find them. */
template<class EditorTypeLike>
static void mimeTypeFactoryLookup(const Utils::MimeType &mimeType,
const QList<EditorTypeLike *> &allFactories,
QList<EditorTypeLike *> *list)
const QList<IEditorFactory *> &allFactories,
QList<IEditorFactory *> *list)
{
QSet<EditorTypeLike *> matches;
QSet<IEditorFactory *> matches;
Utils::visitMimeParents(mimeType, [&](const Utils::MimeType &mt) -> bool {
// check for matching factories
for (EditorTypeLike *factory : allFactories) {
for (IEditorFactory *factory : allFactories) {
if (!matches.contains(factory)) {
const QStringList mimeTypes = factory->mimeTypes();
for (const QString &mimeName : mimeTypes) {
@@ -37,6 +37,14 @@ static void mimeTypeFactoryLookup(const Utils::MimeType &mimeType,
}
return true; // continue
});
// Always offer the plain text editor as a fallback for the case that the mime type
// is not detected correctly.
if (auto plainTextEditorFactory = Utils::findOrDefault(
allFactories,
Utils::equal(&IEditorFactory::id, Utils::Id(Constants::K_DEFAULT_TEXT_EDITOR_ID)))) {
if (!matches.contains(plainTextEditorFactory))
list->append(plainTextEditorFactory);
}
}
/*!

View File

@@ -161,17 +161,39 @@ void OpenEditorsWindow::setVisible(bool visible)
bool OpenEditorsWindow::eventFilter(QObject *obj, QEvent *e)
{
if (obj == m_editorView) {
if (e->type() == QEvent::KeyPress) {
if (e->type() == QEvent::ShortcutOverride) {
auto ke = static_cast<QKeyEvent*>(e);
if (ke->key() == Qt::Key_Escape) {
setVisible(false);
switch (ke->key()) {
case Qt::Key_Up:
case Qt::Key_P:
e->accept();
return true;
case Qt::Key_Down:
case Qt::Key_N:
e->accept();
return true;
}
if (ke->key() == Qt::Key_Return
|| ke->key() == Qt::Key_Enter) {
}
if (e->type() == QEvent::KeyPress) {
auto ke = static_cast<QKeyEvent*>(e);
switch (ke->key()) {
case Qt::Key_Up:
case Qt::Key_P:
selectNextEditor();
return true;
case Qt::Key_Down:
case Qt::Key_N:
selectPreviousEditor();
return true;
case Qt::Key_Escape:
setVisible(false);
return true;
case Qt::Key_Return:
case Qt::Key_Enter:
selectEditor(m_editorView->currentItem());
return true;
}
} else if (e->type() == QEvent::KeyRelease) {
auto ke = static_cast<QKeyEvent*>(e);
if (ke->modifiers() == 0

View File

@@ -74,7 +74,7 @@ public:
QElapsedTimer lastMessage;
QHash<unsigned int, QPair<int, int>> taskPositions;
//: default file name suggested for saving text from output views
QString outputFileNameHint{Tr::tr("output.txt")};
QString outputFileNameHint{::Core::Tr::tr("output.txt")};
};
} // namespace Internal

View File

@@ -47,7 +47,7 @@ class HeaderGuardExpander : public MacroExpander
public:
HeaderGuardExpander(const FilePath &filePath) : m_filePath(filePath)
{
setDisplayName(Tr::tr("Header file variables"));
setDisplayName(Tr::tr("Header File Variables"));
registerFileVariables("Header", Tr::tr("Header file"), [this] {
return m_filePath;
});

View File

@@ -82,8 +82,6 @@ void CppHighlighter::highlightBlock(const QString &text)
return;
}
const int firstNonSpace = tokens.first().utf16charsBegin();
// Keep "semantic parentheses".
Parentheses parentheses;
if (TextBlockUserData *userData = TextDocumentLayout::textUserData(currentBlock())) {
@@ -122,9 +120,16 @@ void CppHighlighter::highlightBlock(const QString &text)
if (tk.is(T_LBRACE)) {
++braceDepth;
// if a folding block opens at the beginning of a line, treat the entire line
// as if it were inside the folding block
if (tk.utf16charsBegin() == firstNonSpace) {
// if a folding block opens at the beginning of a line, treat the line before
// as if it were inside the folding block except if it is a comment or the line does
// end with ;
const int firstNonSpace = tokens.first().utf16charsBegin();
const QString prevBlockText = currentBlock().previous().isValid()
? currentBlock().previous().text().trimmed()
: QString();
if (!prevBlockText.isEmpty() && !prevBlockText.startsWith("//")
&& !prevBlockText.endsWith("*/") && !prevBlockText.endsWith(";")
&& tk.utf16charsBegin() == firstNonSpace) {
++foldingIndent;
TextDocumentLayout::userData(currentBlock())->setFoldingStartIncluded(true);
}
@@ -654,6 +659,32 @@ void CppHighlighterTest::testParentheses()
QCOMPARE(TextDocumentLayout::parentheses(block).count(), expectedParenCount);
}
void CppHighlighterTest::testFoldingIndent_data()
{
QTest::addColumn<int>("line");
QTest::addColumn<int>("expectedFoldingIndent");
QTest::addColumn<int>("expectedFoldingIndentNextLine");
QTest::newRow("braces after one line comment") << 52 << 0 << 1;
QTest::newRow("braces after multiline comment") << 59 << 0 << 1;
QTest::newRow("braces after completed line") << 67 << 1 << 2;
}
void CppHighlighterTest::testFoldingIndent()
{
QFETCH(int, line);
QFETCH(int, expectedFoldingIndent);
QFETCH(int, expectedFoldingIndentNextLine);
QTextBlock block = m_doc.findBlockByNumber(line - 1);
QVERIFY(block.isValid());
QCOMPARE(TextDocumentLayout::foldingIndent(block), expectedFoldingIndent);
QTextBlock nextBlock = m_doc.findBlockByNumber(line);
QVERIFY(nextBlock.isValid());
QCOMPARE(TextDocumentLayout::foldingIndent(nextBlock), expectedFoldingIndentNextLine);
}
} // namespace Internal
#endif // WITH_TESTS

View File

@@ -54,6 +54,8 @@ private slots:
void test();
void testParentheses_data();
void testParentheses();
void testFoldingIndent_data();
void testFoldingIndent();
private:
QTextDocument m_doc;

View File

@@ -553,7 +553,7 @@ using FindUnusedActionsEnabledSwitcherPtr = std::shared_ptr<FindUnusedActionsEna
static void checkNextFunctionForUnused(
const QPointer<SearchResult> &search,
const std::shared_ptr<QFutureInterface<bool>> &findRefsFuture,
const std::shared_ptr<QFutureInterface<void>> &findRefsFuture,
const FindUnusedActionsEnabledSwitcherPtr &actionsSwitcher)
{
if (!search || findRefsFuture->isCanceled())
@@ -650,14 +650,14 @@ void CppModelManager::findUnusedFunctions(const FilePath &folder)
Utils::transform<QVariantList>(links, [](const Link &l) { return QVariant::fromValue(l);
}));
search->setUserData(remainingAndActiveLinks);
const auto findRefsFuture = std::make_shared<QFutureInterface<bool>>();
const auto findRefsFuture = std::make_shared<QFutureInterface<void>>();
FutureProgress *const progress = ProgressManager::addTask(findRefsFuture->future(),
Tr::tr("Finding Unused Functions"),
"CppEditor.FindUnusedFunctions");
connect(progress,
&FutureProgress::canceled,
search,
[search, future = std::weak_ptr<QFutureInterface<bool>>(findRefsFuture)] {
[search, future = std::weak_ptr<QFutureInterface<void>>(findRefsFuture)] {
search->finishSearch(true);
if (const auto f = future.lock()) {
f->cancel();

View File

@@ -47,3 +47,24 @@ static void parenTest()
const char* s7 = R"(
))";
// comment
{
}
/*
*
*/
{
}
static void parenTest2()
{
parenTest();
{
}
}

View File

@@ -115,14 +115,6 @@ LldbDapEngine::LldbDapEngine()
setDebuggerType("DAP");
}
QJsonArray LldbDapEngine::environment() const
{
QJsonArray envArray;
for (const QString &value : runParameters().inferior.environment.toDictionary().toStringList())
envArray.append(value);
return envArray;
}
QJsonArray LldbDapEngine::sourceMap() const
{
QJsonArray sourcePathMapping;
@@ -160,11 +152,12 @@ void LldbDapEngine::handleDapInitialize()
const QJsonArray commands = preRunCommands();
if (!isLocalAttachEngine()) {
const QJsonArray env = environment();
const QJsonArray env = QJsonArray::fromStringList(rp.inferior.environment.toStringList());
const QJsonArray args = QJsonArray::fromStringList(rp.inferior.command.splitArguments());
QJsonObject launchJson{
{"noDebug", false},
{"program", rp.inferior.command.executable().path()},
{"args", rp.inferior.command.arguments()},
{"cwd", rp.inferior.workingDirectory.path()},
{"env", env},
{"__restart", ""},
@@ -173,6 +166,8 @@ void LldbDapEngine::handleDapInitialize()
launchJson.insert("sourceMap", map);
if (!commands.isEmpty())
launchJson.insert("preRunCommands", commands);
if (!args.isEmpty())
launchJson.insert("args", args);
m_dapClient->postRequest("launch", launchJson);

View File

@@ -22,7 +22,6 @@ private:
bool acceptsBreakpoint(const BreakpointParameters &bp) const override;
const QLoggingCategory &logCategory() override;
QJsonArray environment() const;
QJsonArray sourceMap() const;
QJsonArray preRunCommands() const;
};

View File

@@ -30,7 +30,9 @@
#include <coreplugin/messagebox.h>
#include <projectexplorer/devicesupport/idevice.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectmanager.h>
#include <projectexplorer/taskhub.h>
#include <utils/algorithm.h>
@@ -2074,8 +2076,10 @@ QString GdbEngine::breakpointLocation(const BreakpointParameters &data)
return addressSpec(data.address);
BreakpointPathUsage usage = data.pathUsage;
if (usage == BreakpointPathUsageEngineDefault)
usage = BreakpointUseShortPath;
if (usage == BreakpointPathUsageEngineDefault) {
ProjectExplorer::Project *project = ProjectManager::projectForFile(data.fileName);
usage = project ? BreakpointUseFullPath : BreakpointUseShortPath;
}
const QString fileName = usage == BreakpointUseFullPath
? data.fileName.path() : breakLocation(data.fileName);
@@ -2088,8 +2092,10 @@ QString GdbEngine::breakpointLocation(const BreakpointParameters &data)
QString GdbEngine::breakpointLocation2(const BreakpointParameters &data)
{
BreakpointPathUsage usage = data.pathUsage;
if (usage == BreakpointPathUsageEngineDefault)
usage = BreakpointUseShortPath;
if (usage == BreakpointPathUsageEngineDefault) {
ProjectExplorer::Project *project = ProjectManager::projectForFile(data.fileName);
usage = project ? BreakpointUseFullPath : BreakpointUseShortPath;
}
const QString fileName = usage == BreakpointUseFullPath
? data.fileName.path() : breakLocation(data.fileName);

View File

@@ -166,7 +166,7 @@ GdbSettings::GdbSettings()
useDebugInfoD.setSettingsKey("UseDebugInfoD");
useDebugInfoD.setLabelText(Tr::tr("Use debug info daemon"));
useDebugInfoD.setOptionText(TriState::DefaultValue, tr("Use system settings"));
useDebugInfoD.setOptionText(TriState::DefaultValue, Tr::tr("Use system settings"));
useDebugInfoD.setToolTip(Tr::tr("Lets GDB attempt to automatically retrieve "
"debug information for system packages."));

View File

@@ -2,10 +2,14 @@
<qresource prefix="/extensionmanager">
<file>images/download.png</file>
<file>images/download@2x.png</file>
<file>images/extensionbig.png</file>
<file>images/extensionbig@2x.png</file>
<file>images/extensionsmall.png</file>
<file>images/extensionsmall@2x.png</file>
<file>images/mode_extensionmanager_mask.png</file>
<file>images/mode_extensionmanager_mask@2x.png</file>
<file>images/packbig.png</file>
<file>images/packbig@2x.png</file>
<file>images/packsmall.png</file>
<file>images/packsmall@2x.png</file>
</qresource>

View File

@@ -1,6 +1,8 @@
<RCC>
<qresource prefix="/extensionmanager">
<file>testdata/augmentedplugindata.json</file>
<file>testdata/defaultpacks.json</file>
<file>testdata/thirdpartyplugins.json</file>
<file>testdata/varieddata.json</file>
</qresource>
</RCC>

View File

@@ -50,7 +50,7 @@ public:
using namespace Layouting;
auto widget = Column {
new StyledBar,
new ExtensionManagerWidget,
createExtensionManagerWidget(),
noMargin, spacing(0),
}.emerge();

View File

@@ -16,6 +16,7 @@
#include <extensionsystem/pluginmanager.h>
#include <extensionsystem/pluginspec.h>
#include <extensionsystem/pluginview.h>
#include <solutions/tasking/networkquery.h>
#include <solutions/tasking/tasktree.h>
@@ -33,16 +34,56 @@
#include <utils/utilsicons.h>
#include <QAction>
#include <QApplication>
#include <QBuffer>
#include <QCheckBox>
#include <QHBoxLayout>
#include <QImageReader>
#include <QMessageBox>
#include <QTextBrowser>
#include <QMovie>
#include <QPainter>
#include <QProgressDialog>
#include <QScrollArea>
#include <QSignalMapper>
using namespace Core;
using namespace Utils;
using namespace StyleHelper;
using namespace WelcomePageHelpers;
namespace ExtensionManager::Internal {
constexpr TextFormat h5TF
{Theme::Token_Text_Default, UiElement::UiElementH5};
constexpr TextFormat h6TF
{h5TF.themeColor, UiElement::UiElementH6};
constexpr TextFormat h6CapitalTF
{Theme::Token_Text_Muted, UiElement::UiElementH6Capital};
constexpr TextFormat contentTF
{Theme::Token_Text_Default, UiElement::UiElementBody2};
static QLabel *sectionTitle(const TextFormat &tf, const QString &title)
{
QLabel *label = tfLabel(tf, true);
label->setText(title);
return label;
};
static QWidget *toScrollableColumn(QWidget *widget)
{
widget->setContentsMargins(SpacingTokens::ExVPaddingGapXl, SpacingTokens::ExVPaddingGapXl,
SpacingTokens::ExVPaddingGapXl, SpacingTokens::ExVPaddingGapXl);
widget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Minimum);
auto scrollArea = new QScrollArea;
scrollArea->setWidget(widget);
scrollArea->setWidgetResizable(true);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->setFrameStyle(QFrame::NoFrame);
return scrollArea;
};
class CollapsingWidget : public QWidget
{
public:
@@ -68,6 +109,169 @@ private:
int m_width = 100;
};
class HeadingWidget : public QWidget
{
static constexpr QSize iconBgS{68, 68};
static constexpr int dividerH = 16;
Q_OBJECT
public:
explicit HeadingWidget(QWidget *parent = nullptr)
: QWidget(parent)
{
m_icon = new QLabel;
m_icon->setFixedSize(iconBgS);
static const TextFormat titleTF
{Theme::Token_Text_Default, UiElementH4};
static const TextFormat vendorTF
{Theme::Token_Text_Accent, UiElementLabelMedium};
static const TextFormat dlTF
{Theme::Token_Text_Muted, vendorTF.uiElement};
static const TextFormat detailsTF
{Theme::Token_Text_Default, UiElementBody2};
m_title = tfLabel(titleTF);
m_vendor = new Button({}, Button::SmallLink);
m_vendor->setContentsMargins({});
m_divider = new QLabel;
m_divider->setFixedSize(1, dividerH);
WelcomePageHelpers::setBackgroundColor(m_divider, dlTF.themeColor);
m_dlIcon = new QLabel;
const QPixmap dlIcon = Icon({{":/extensionmanager/images/download.png", dlTF.themeColor}},
Icon::Tint).pixmap();
m_dlIcon->setPixmap(dlIcon);
m_dlCount = tfLabel(dlTF);
m_dlCount->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
m_details = tfLabel(detailsTF);
installButton = new Button(Tr::tr("Install..."), Button::MediumPrimary);
installButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
installButton->hide();
using namespace Layouting;
Row {
m_icon,
Column {
m_title,
st,
Row {
m_vendor,
Widget {
bindTo(&m_dlCountItems),
Row {
Space(SpacingTokens::HGapXs),
m_divider,
Space(SpacingTokens::HGapXs),
m_dlIcon,
Space(SpacingTokens::HGapXxs),
m_dlCount,
noMargin, spacing(0),
},
},
},
st,
m_details,
spacing(0),
},
Column {
installButton,
st,
},
noMargin, spacing(SpacingTokens::ExPaddingGapL),
}.attachTo(this);
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
m_dlCountItems->setVisible(false);
connect(installButton, &QAbstractButton::pressed,
this, &HeadingWidget::pluginInstallationRequested);
connect(m_vendor, &QAbstractButton::pressed, this, [this]() {
emit vendorClicked(m_currentVendor);
});
update({});
}
void update(const QModelIndex &current)
{
if (!current.isValid())
return;
m_icon->setPixmap(icon(current));
const QString name = current.data(RoleName).toString();
m_title->setText(name);
m_currentVendor = current.data(RoleVendor).toString();
m_vendor->setText(m_currentVendor);
const int dlCount = current.data(RoleDownloadCount).toInt();
const bool showDlCount = dlCount > 0;
if (showDlCount)
m_dlCount->setText(QString::number(dlCount));
m_dlCountItems->setVisible(showDlCount);
const auto pluginData = current.data(RolePlugins).value<PluginsData>();
if (current.data(RoleItemType).toInt() == ItemTypePack) {
const int pluginsCount = pluginData.count();
const QString details = Tr::tr("Pack contains %n plugins.", nullptr, pluginsCount);
m_details->setText(details);
} else {
m_details->setText({});
}
const ItemType itemType = current.data(RoleItemType).value<ItemType>();
const bool isPack = itemType == ItemTypePack;
const bool isRemotePlugin = !(isPack || pluginSpecForName(name));
installButton->setVisible(isRemotePlugin && !pluginData.empty());
if (installButton->isVisible())
installButton->setToolTip(pluginData.constFirst().second);
}
signals:
void pluginInstallationRequested();
void vendorClicked(const QString &vendor);
private:
static QPixmap icon(const QModelIndex &index)
{
const qreal dpr = qApp->devicePixelRatio();
QPixmap pixmap(iconBgS * dpr);
pixmap.fill(Qt::transparent);
pixmap.setDevicePixelRatio(dpr);
const QRect bgR(QPoint(), pixmap.deviceIndependentSize().toSize());
QPainter p(&pixmap);
QLinearGradient gradient(bgR.topRight(), bgR.bottomLeft());
gradient.setStops(iconGradientStops(index));
constexpr int iconRectRounding = 4;
WelcomePageHelpers::drawCardBackground(&p, bgR, gradient, Qt::NoPen, iconRectRounding);
// Icon
constexpr Theme::Color color = Theme::Token_Basic_White;
static const QIcon pack = Icon({{":/extensionmanager/images/packbig.png", color}},
Icon::Tint).icon();
static const QIcon extension = Icon({{":/extensionmanager/images/extensionbig.png",
color}}, Icon::Tint).icon();
const ItemType itemType = index.data(RoleItemType).value<ItemType>();
(itemType == ItemTypePack ? pack : extension).paint(&p, bgR);
return pixmap;
}
QLabel *m_icon;
QLabel *m_title;
Button *m_vendor;
QLabel *m_divider;
QLabel *m_dlIcon;
QLabel *m_dlCount;
QWidget *m_dlCountItems;
QLabel *m_details;
QAbstractButton *installButton;
QString m_currentVendor;
};
class PluginStatusWidget : public QWidget
{
public:
@@ -75,22 +279,34 @@ public:
: QWidget(parent)
{
m_label = new InfoLabel;
m_checkBox = new QCheckBox(Tr::tr("Load on Start"));
m_checkBox = new QCheckBox(Tr::tr("Load on start"));
m_restartButton = new Button(Tr::tr("Restart Now"), Button::MediumPrimary);
m_restartButton->setVisible(false);
m_pluginView.hide();
using namespace Layouting;
Column {
m_label,
m_checkBox,
m_restartButton,
}.attachTo(this);
connect(m_checkBox, &QCheckBox::clicked, this, [this](bool checked) {
ExtensionSystem::PluginSpec *spec = ExtensionsModel::pluginSpecForName(m_pluginName);
ExtensionSystem::PluginSpec *spec = pluginSpecForName(m_pluginName);
if (spec == nullptr)
return;
spec->setEnabledBySettings(checked);
const bool doIt = m_pluginView.data().setPluginsEnabled({spec}, checked);
if (doIt) {
m_restartButton->show();
ExtensionSystem::PluginManager::writeSettings();
} else {
m_checkBox->setChecked(!checked);
}
});
connect(m_restartButton, &QAbstractButton::clicked,
ICore::instance(), &ICore::restart, Qt::QueuedConnection);
update();
}
@@ -103,7 +319,7 @@ public:
private:
void update()
{
const ExtensionSystem::PluginSpec *spec = ExtensionsModel::pluginSpecForName(m_pluginName);
const ExtensionSystem::PluginSpec *spec = pluginSpecForName(m_pluginName);
setVisible(spec != nullptr);
if (spec == nullptr)
return;
@@ -125,290 +341,322 @@ private:
InfoLabel *m_label;
QCheckBox *m_checkBox;
QAbstractButton *m_restartButton;
QString m_pluginName;
ExtensionSystem::PluginView m_pluginView{this};
};
class ExtensionManagerWidgetPrivate
class TagList : public QWidget
{
Q_OBJECT
public:
QString currentItemName;
ExtensionsBrowser *leftColumn;
CollapsingWidget *secondaryDescriptionWidget;
QTextBrowser *primaryDescription;
QTextBrowser *secondaryDescription;
PluginStatusWidget *pluginStatus;
QAbstractButton *installButton;
PluginsData currentItemPlugins;
Tasking::TaskTreeRunner taskTreeRunner;
};
explicit TagList(QWidget *parent = nullptr)
: QWidget(parent)
{
QHBoxLayout *layout = new QHBoxLayout(this);
setLayout(layout);
layout->setContentsMargins({});
m_signalMapper = new QSignalMapper(this);
connect(m_signalMapper, &QSignalMapper::mappedString, this, &TagList::tagSelected);
}
ExtensionManagerWidget::ExtensionManagerWidget(QWidget *parent)
: ResizeSignallingWidget(parent)
, d(new ExtensionManagerWidgetPrivate)
{
d->leftColumn = new ExtensionsBrowser;
void setTags(const QStringList &tags)
{
if (m_container) {
delete m_container;
m_container = nullptr;
}
auto descriptionColumns = new QWidget;
d->secondaryDescriptionWidget = new CollapsingWidget;
d->primaryDescription = new QTextBrowser;
d->primaryDescription->setOpenExternalLinks(true);
d->primaryDescription->setFrameStyle(QFrame::NoFrame);
d->secondaryDescription = new QTextBrowser;
d->secondaryDescription->setFrameStyle(QFrame::NoFrame);
d->pluginStatus = new PluginStatusWidget;
d->installButton = new Button(Tr::tr("Install..."), Button::MediumPrimary);
d->installButton->hide();
if (!tags.empty()) {
m_container = new QWidget(this);
layout()->addWidget(m_container);
using namespace Layouting;
Flow flow {};
flow.setNoMargins();
flow.setSpacing(SpacingTokens::HGapXs);
for (const QString &tag : tags) {
QAbstractButton *tagButton = new Button(tag, Button::Tag);
connect(tagButton, &QAbstractButton::clicked,
m_signalMapper, qOverload<>(&QSignalMapper::map));
m_signalMapper->setMapping(tagButton, tag);
flow.addItem(tagButton);
}
flow.attachTo(m_container);
}
updateGeometry();
}
signals:
void tagSelected(const QString &tag);
private:
QWidget *m_container = nullptr;
QSignalMapper *m_signalMapper;
};
class ExtensionManagerWidget final : public Core::ResizeSignallingWidget
{
public:
ExtensionManagerWidget();
private:
void updateView(const QModelIndex &current);
void fetchAndInstallPlugin(const QUrl &url);
void fetchAndDisplayImage(const QUrl &url);
QString m_currentItemName;
ExtensionsBrowser *m_extensionBrowser;
CollapsingWidget *m_secondaryDescriptionWidget;
HeadingWidget *m_headingWidget;
QWidget *m_primaryContent;
QWidget *m_secondaryContent;
QLabel *m_description;
QLabel *m_linksTitle;
QLabel *m_links;
QLabel *m_imageTitle;
QLabel *m_image;
QBuffer m_imageDataBuffer;
QMovie m_imageMovie;
QLabel *m_tagsTitle;
TagList *m_tags;
QLabel *m_platformsTitle;
QLabel *m_platforms;
QLabel *m_dependenciesTitle;
QLabel *m_dependencies;
QLabel *m_packExtensionsTitle;
QLabel *m_packExtensions;
PluginStatusWidget *m_pluginStatus;
PluginsData m_currentItemPlugins;
Tasking::TaskTreeRunner m_dlTaskTreeRunner;
Tasking::TaskTreeRunner m_imgTaskTreeRunner;
};
ExtensionManagerWidget::ExtensionManagerWidget()
{
m_extensionBrowser = new ExtensionsBrowser;
auto descriptionColumns = new QWidget;
m_secondaryDescriptionWidget = new CollapsingWidget;
m_headingWidget = new HeadingWidget;
m_description = tfLabel(contentTF, false);
m_description->setWordWrap(true);
m_linksTitle = sectionTitle(h6CapitalTF, Tr::tr("More information"));
m_links = tfLabel(contentTF, false);
m_links->setOpenExternalLinks(true);
m_imageTitle = sectionTitle(h6CapitalTF, {});
m_image = new QLabel;
m_imageMovie.setDevice(&m_imageDataBuffer);
using namespace Layouting;
auto primary = new QWidget;
const auto spL = spacing(SpacingTokens::VPaddingL);
Column {
m_description,
Column { m_linksTitle, m_links, spL },
Column { m_imageTitle, m_image, spL },
st,
noMargin, spacing(SpacingTokens::ExVPaddingGapXl),
}.attachTo(primary);
m_primaryContent = toScrollableColumn(primary);
m_tagsTitle = sectionTitle(h6TF, Tr::tr("Tags"));
m_tags = new TagList;
m_platformsTitle = sectionTitle(h6TF, Tr::tr("Platforms"));
m_platforms = tfLabel(contentTF, false);
m_dependenciesTitle = sectionTitle(h6TF, Tr::tr("Dependencies"));
m_dependencies = tfLabel(contentTF, false);
m_packExtensionsTitle = sectionTitle(h6TF, Tr::tr("Extensions in pack"));
m_packExtensions = tfLabel(contentTF, false);
m_pluginStatus = new PluginStatusWidget;
auto secondary = new QWidget;
const auto spXxs = spacing(SpacingTokens::VPaddingXxs);
Column {
sectionTitle(h6CapitalTF, Tr::tr("Extension details")),
Column {
Column { m_tagsTitle, m_tags, spXxs },
Column { m_platformsTitle, m_platforms, spXxs },
Column { m_dependenciesTitle, m_dependencies, spXxs },
Column { m_packExtensionsTitle, m_packExtensions, spXxs },
spacing(SpacingTokens::VPaddingL),
},
st,
noMargin, spacing(SpacingTokens::ExVPaddingGapXl),
}.attachTo(secondary);
m_secondaryContent = toScrollableColumn(secondary);
Row {
WelcomePageHelpers::createRule(Qt::Vertical),
Column {
d->secondaryDescription,
d->pluginStatus,
d->installButton,
m_secondaryContent,
m_pluginStatus,
},
noMargin, spacing(0),
}.attachTo(d->secondaryDescriptionWidget);
}.attachTo(m_secondaryDescriptionWidget);
Row {
WelcomePageHelpers::createRule(Qt::Vertical),
Row {
d->primaryDescription,
noMargin,
Column {
Column {
m_headingWidget,
customMargins(SpacingTokens::ExVPaddingGapXl, SpacingTokens::ExVPaddingGapXl,
SpacingTokens::ExVPaddingGapXl, SpacingTokens::ExVPaddingGapXl),
},
d->secondaryDescriptionWidget,
m_primaryContent,
},
},
m_secondaryDescriptionWidget,
noMargin, spacing(0),
}.attachTo(descriptionColumns);
Row {
Space(StyleHelper::SpacingTokens::ExVPaddingGapXl),
d->leftColumn,
Space(SpacingTokens::ExVPaddingGapXl),
m_extensionBrowser,
descriptionColumns,
noMargin, spacing(0),
}.attachTo(this);
WelcomePageHelpers::setBackgroundColor(this, Theme::Token_Background_Default);
connect(d->leftColumn, &ExtensionsBrowser::itemSelected,
connect(m_extensionBrowser, &ExtensionsBrowser::itemSelected,
this, &ExtensionManagerWidget::updateView);
connect(this, &ResizeSignallingWidget::resized, this, [this](const QSize &size) {
const int intendedLeftColumnWidth = size.width() - 580;
d->leftColumn->adjustToWidth(intendedLeftColumnWidth);
const int intendedBrowserColumnWidth = size.width() - 580;
m_extensionBrowser->adjustToWidth(intendedBrowserColumnWidth);
const bool secondaryDescriptionVisible = size.width() > 970;
const int secondaryDescriptionWidth = secondaryDescriptionVisible ? 264 : 0;
d->secondaryDescriptionWidget->setWidth(secondaryDescriptionWidth);
m_secondaryDescriptionWidget->setWidth(secondaryDescriptionWidth);
});
connect(d->installButton, &QAbstractButton::pressed, this, [this]() {
fetchAndInstallPlugin(QUrl::fromUserInput(d->currentItemPlugins.constFirst().second));
connect(m_headingWidget, &HeadingWidget::pluginInstallationRequested, this, [this](){
fetchAndInstallPlugin(QUrl::fromUserInput(m_currentItemPlugins.constFirst().second));
});
updateView({});
}
connect(m_tags, &TagList::tagSelected, m_extensionBrowser, &ExtensionsBrowser::setFilter);
connect(m_headingWidget, &HeadingWidget::vendorClicked,
m_extensionBrowser, &ExtensionsBrowser::setFilter);
ExtensionManagerWidget::~ExtensionManagerWidget()
{
delete d;
updateView({});
}
void ExtensionManagerWidget::updateView(const QModelIndex &current)
{
const QString h5Css =
StyleHelper::fontToCssProperties(StyleHelper::uiFont(StyleHelper::UiElementH5))
+ "; margin-top: 0px;";
const QString h6Css =
StyleHelper::fontToCssProperties(StyleHelper::uiFont(StyleHelper::UiElementH6))
+ "; margin-top: 28px;";
const QString h6CapitalCss =
StyleHelper::fontToCssProperties(StyleHelper::uiFont(StyleHelper::UiElementH6Capital))
+ QString::fromLatin1("; margin-top: 0px; color: %1;")
.arg(creatorColor(Theme::Token_Text_Muted).name());
const QString bodyStyle = QString::fromLatin1("color: %1; background-color: %2; "
"margin-left: %3px; margin-right: %3px;")
.arg(creatorColor(Theme::Token_Text_Default).name())
.arg(creatorColor(Theme::Token_Background_Muted).name())
.arg(StyleHelper::SpacingTokens::ExVPaddingGapXl);
const QString htmlStart = QString(R"(
<html>
<body style="%1"><br/>
)").arg(bodyStyle);
const QString htmlEnd = QString(R"(
</body></html>
)");
m_headingWidget->update(current);
if (!current.isValid()) {
const QString emptyHtml = htmlStart + htmlEnd;
d->primaryDescription->setText(emptyHtml);
d->secondaryDescription->setText(emptyHtml);
const bool showContent = current.isValid();
m_primaryContent->setVisible(showContent);
m_secondaryContent->setVisible(showContent);
m_headingWidget->setVisible(showContent);
m_pluginStatus->setVisible(showContent);
if (!showContent)
return;
}
d->currentItemName = current.data().toString();
m_currentItemName = current.data().toString();
const bool isPack = current.data(RoleItemType) == ItemTypePack;
d->pluginStatus->setPluginName(isPack ? QString() : d->currentItemName);
const bool isRemotePlugin = !(isPack || ExtensionsModel::pluginSpecForName(d->currentItemName));
d->currentItemPlugins = current.data(RolePlugins).value<PluginsData>();
d->installButton->setVisible(isRemotePlugin && !d->currentItemPlugins.empty());
if (!d->currentItemPlugins.empty())
d->installButton->setToolTip(d->currentItemPlugins.constFirst().second);
m_pluginStatus->setPluginName(isPack ? QString() : m_currentItemName);
m_currentItemPlugins = current.data(RolePlugins).value<PluginsData>();
{
QString description = htmlStart;
auto toContentParagraph = [](const QString &text) {
const QString pHtml = QString::fromLatin1("<p style=\"margin-top:0;margin-bottom:0;"
"line-height:%1px\">%2</p>")
.arg(contentTF.lineHeight()).arg(text);
return pHtml;
};
QString descriptionHtml;
{
const TextData textData = current.data(RoleDescriptionText).value<TextData>();
const bool hasDescription = !textData.isEmpty();
if (hasDescription) {
const QString headerCssTemplate =
";margin-top:%1;margin-bottom:%2;padding-top:0;padding-bottom:0;";
const QString h4Css = fontToCssProperties(uiFont(UiElementH4))
+ headerCssTemplate.arg(0).arg(SpacingTokens::VGapL);
const QString h5Css = fontToCssProperties(uiFont(UiElementH5))
+ headerCssTemplate.arg(SpacingTokens::ExVPaddingGapXl)
.arg(SpacingTokens::VGapL);
QString descriptionHtml;
for (const TextData::Type &text : textData) {
if (text.second.isEmpty())
continue;
const QString paragraph =
QString::fromLatin1("<div style=\"%1\">%2</div><p>%3</p>")
.arg(descriptionHtml.isEmpty() ? h5Css : h6Css)
QString::fromLatin1("<div style=\"%1\">%2</div>%3")
.arg(descriptionHtml.isEmpty() ? h4Css : h5Css)
.arg(text.first)
.arg(text.second.join("<br/>"));
.arg(toContentParagraph(text.second.join("<br/>")));
descriptionHtml.append(paragraph);
}
descriptionHtml.prepend(QString::fromLatin1("<body style=\"color:%1;\">")
.arg(creatorColor(Theme::Token_Text_Default).name()));
descriptionHtml.append("</body>");
m_description->setText(descriptionHtml);
}
description.append(descriptionHtml);
m_description->setVisible(hasDescription);
description.append(QString::fromLatin1("<div style=\"%1\">%2</div>")
.arg(h6Css)
.arg(Tr::tr("More information")));
const LinksData linksData = current.data(RoleDescriptionLinks).value<LinksData>();
if (!linksData.isEmpty()) {
const bool hasLinks = !linksData.isEmpty();
if (hasLinks) {
QString linksHtml;
const QStringList links = transform(linksData, [](const LinksData::Type &link) {
const QString anchor = link.first.isEmpty() ? link.second : link.first;
return QString::fromLatin1("<a href=\"%1\">%2 &gt;</a>")
.arg(link.second).arg(anchor);
return QString::fromLatin1(R"(<a href="%1" style="color:%2">%3 &gt;</a>)")
.arg(link.second)
.arg(creatorColor(Theme::Token_Text_Accent).name())
.arg(anchor);
});
linksHtml = links.join("<br/>");
description.append(QString::fromLatin1("<p>%1</p>").arg(linksHtml));
m_links->setText(toContentParagraph(linksHtml));
}
m_linksTitle->setVisible(hasLinks);
m_links->setVisible(hasLinks);
m_imgTaskTreeRunner.reset();
m_imageMovie.stop();
m_imageDataBuffer.close();
m_image->clear();
const ImagesData imagesData = current.data(RoleDescriptionImages).value<ImagesData>();
if (!imagesData.isEmpty()) {
const QString examplesBoxCss =
QString::fromLatin1("height: 168px; background-color: %1; ")
.arg(creatorColor(Theme::Token_Background_Default).name());
description.append(QString(R"(
<br/>
<div style="%1">%2</div>
<p style="%3">
<br/><br/><br/><br/><br/>
TODO: Load imagea asynchronously, and show them in a QLabel.
Also Use QMovie for animated images.
<br/><br/><br/><br/><br/>
</p>
)").arg(h6CapitalCss)
.arg(Tr::tr("Examples"))
.arg(examplesBoxCss));
const bool hasImages = !imagesData.isEmpty();
if (hasImages) {
const ImagesData::Type &image = imagesData.constFirst(); // Only show one image
m_imageTitle->setText(image.first);
fetchAndDisplayImage(image.second);
}
// Library details vanished from the Figma designs. The data is available, though.
const bool showDetails = false;
if (showDetails) {
const QString captionStrongCss = StyleHelper::fontToCssProperties(
StyleHelper::uiFont(StyleHelper::UiElementCaptionStrong));
const QLocale locale;
const uint size = current.data(RoleSize).toUInt();
const QString sizeFmt = locale.formattedDataSize(size);
const FilePath location = FilePath::fromVariant(current.data(RoleLocation));
const QString version = current.data(RoleVersion).toString();
description.append(QString(R"(
<div style="%1">%2</div>
<p>
<table>
<tr><td style="%3">%4</td><td>%5</td></tr>
<tr><td style="%3">%6</td><td>%7</td></tr>
)").arg(h6Css)
.arg(Tr::tr("Extension library details"))
.arg(captionStrongCss)
.arg(Tr::tr("Size"))
.arg(sizeFmt)
.arg(Tr::tr("Version"))
.arg(version));
if (!location.isEmpty()) {
const QString locationFmt =
HostOsInfo::isWindowsHost() ? location.toUserOutput()
: location.withTildeHomePath();
description.append(QString(R"(
<tr><td style="%3">%1</td><td>%2</td></tr>
)").arg(Tr::tr("Location"))
.arg(locationFmt));
}
description.append(QString(R"(
</table>
</p>
)"));
}
description.append(htmlEnd);
d->primaryDescription->setText(description);
m_imageTitle->setVisible(hasImages);
m_image->setVisible(hasImages);
}
{
QString description = htmlStart;
description.append(QString(R"(
<p style="%1">%2</p>
)").arg(h6CapitalCss)
.arg(Tr::tr("Extension details")));
const QStringList tags = current.data(RoleTags).toStringList();
if (!tags.isEmpty()) {
const QString tagTemplate = QString(R"(
<td style="border: 1px solid %1; padding: 3px; ">%2</td>
)").arg(creatorColor(Theme::Token_Stroke_Subtle).name());
const QStringList tagsFmt = transform(tags, [&tagTemplate](const QString &tag) {
return tagTemplate.arg(tag);
});
description.append(QString(R"(
<div style="%1">%2</div>
<p>%3</p>
)").arg(h6Css)
.arg(Tr::tr("Related tags"))
.arg(tagsFmt.join("&nbsp;")));
}
m_tags->setTags(tags);
const bool hasTags = !tags.isEmpty();
m_tagsTitle->setVisible(hasTags);
m_tags->setVisible(hasTags);
const QStringList platforms = current.data(RolePlatforms).toStringList();
if (!platforms.isEmpty()) {
description.append(QString(R"(
<div style="%1">%2</div>
<p>%3</p>
)").arg(h6Css)
.arg(Tr::tr("Platforms"))
.arg(platforms.join("<br/>")));
}
const bool hasPlatforms = !platforms.isEmpty();
if (hasPlatforms)
m_platforms->setText(toContentParagraph(platforms.join("<br/>")));
m_platformsTitle->setVisible(hasPlatforms);
m_platforms->setVisible(hasPlatforms);
const QStringList dependencies = current.data(RoleDependencies).toStringList();
if (!dependencies.isEmpty()) {
const QString dependenciesFmt = dependencies.join("<br/>");
description.append(QString(R"(
<div style="%1">%2</div>
<p>%3</p>
)").arg(h6Css)
.arg(Tr::tr("Dependencies"))
.arg(dependenciesFmt));
}
const bool hasDependencies = !dependencies.isEmpty();
if (hasDependencies)
m_dependencies->setText(toContentParagraph(dependencies.join("<br/>")));
m_dependenciesTitle->setVisible(hasDependencies);
m_dependencies->setVisible(hasDependencies);
if (isPack) {
const PluginsData plugins = current.data(RolePlugins).value<PluginsData>();
const bool hasExtensions = isPack && !plugins.isEmpty();
if (hasExtensions) {
const QStringList extensions = transform(plugins, &QPair<QString, QString>::first);
const QString extensionsFmt = extensions.join("<br/>");
description.append(QString(R"(
<div style="%1">%2</div>
<p>%3</p>
)").arg(h6Css)
.arg(Tr::tr("Extensions in pack"))
.arg(extensionsFmt));
m_packExtensions->setText(toContentParagraph(extensions.join("<br/>")));
}
description.append(htmlEnd);
d->secondaryDescription->setText(description);
m_packExtensionsTitle->setVisible(hasExtensions);
m_packExtensions->setVisible(hasExtensions);
}
}
@@ -419,9 +667,9 @@ void ExtensionManagerWidget::fetchAndInstallPlugin(const QUrl &url)
struct StorageStruct
{
StorageStruct() {
progressDialog.reset(new QProgressDialog(Tr::tr("Downloading Plugin..."),
Tr::tr("Cancel"), 0, 0,
ICore::dialogParent()));
progressDialog.reset(new QProgressDialog(
Tr::tr("Downloading..."), Tr::tr("Cancel"), 0, 0, ICore::dialogParent()));
progressDialog->setWindowTitle(Tr::tr("Download Extension"));
progressDialog->setWindowModality(Qt::ApplicationModal);
progressDialog->setFixedSize(progressDialog->sizeHint());
progressDialog->setAutoClose(false);
@@ -446,7 +694,7 @@ void ExtensionManagerWidget::fetchAndInstallPlugin(const QUrl &url)
QMessageBox::warning(
ICore::dialogParent(),
Tr::tr("Download Error"),
Tr::tr("Could not download Plugin") + "\n\n" + storage->url.toString() + "\n\n"
Tr::tr("Cannot download extension") + "\n\n" + storage->url.toString() + "\n\n"
+ Tr::tr("Code: %1.").arg(query.reply()->error()));
}
};
@@ -469,7 +717,61 @@ void ExtensionManagerWidget::fetchAndInstallPlugin(const QUrl &url)
onGroupDone(onPluginInstallation),
};
d->taskTreeRunner.start(group);
m_dlTaskTreeRunner.start(group);
}
void ExtensionManagerWidget::fetchAndDisplayImage(const QUrl &url)
{
using namespace Tasking;
struct StorageStruct
{
QByteArray imageData;
QUrl url;
};
Storage<StorageStruct> storage;
const auto onFetchSetup = [url, storage](NetworkQuery &query) {
storage->url = url;
query.setRequest(QNetworkRequest(url));
query.setNetworkAccessManager(NetworkAccessManager::instance());
};
const auto onFetchDone = [storage](const NetworkQuery &query, DoneWith result) {
if (result == DoneWith::Success)
storage->imageData = query.reply()->readAll();
};
const auto onShowImage = [storage, this]() {
if (storage->imageData.isEmpty())
return;
m_imageDataBuffer.setData(storage->imageData);
if (!m_imageDataBuffer.open(QIODevice::ReadOnly))
return;
QImageReader reader(&m_imageDataBuffer);
const bool animated = reader.supportsAnimation();
if (animated) {
m_image->setMovie(&m_imageMovie);
m_imageMovie.start();
} else {
const QPixmap pixmap = QPixmap::fromImage(reader.read());
m_image->setPixmap(pixmap);
}
};
Group group{
storage,
NetworkQueryTask{onFetchSetup, onFetchDone},
onGroupDone(onShowImage),
};
m_imgTaskTreeRunner.start(group);
}
QWidget *createExtensionManagerWidget()
{
return new ExtensionManagerWidget;
}
} // ExtensionManager::Internal
#include "extensionmanagerwidget.moc"

View File

@@ -1,21 +1,10 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <coreplugin/welcomepagehelper.h>
#include <QWidget>
namespace ExtensionManager::Internal {
class ExtensionManagerWidget final : public Core::ResizeSignallingWidget
{
public:
explicit ExtensionManagerWidget(QWidget *parent = nullptr);
~ExtensionManagerWidget();
private:
void updateView(const QModelIndex &current);
void fetchAndInstallPlugin(const QUrl &url);
class ExtensionManagerWidgetPrivate *d = nullptr;
};
QWidget *createExtensionManagerWidget();
} // ExtensionManager::Internal

View File

@@ -22,10 +22,12 @@
#include <extensionsystem/pluginview.h>
#include <extensionsystem/pluginmanager.h>
#include <solutions/spinner/spinner.h>
#include <solutions/tasking/networkquery.h>
#include <solutions/tasking/tasktree.h>
#include <solutions/tasking/tasktreerunner.h>
#include <utils/elidinglabel.h>
#include <utils/fancylineedit.h>
#include <utils/icon.h>
#include <utils/layoutbuilder.h>
@@ -51,9 +53,9 @@ namespace ExtensionManager::Internal {
Q_LOGGING_CATEGORY(browserLog, "qtc.extensionmanager.browser", QtWarningMsg)
constexpr int gapSize = ExVPaddingGapXl;
constexpr int gapSize = HGapL;
constexpr int itemWidth = 330;
constexpr int cellWidth = itemWidth + HPaddingL;
constexpr int cellWidth = itemWidth + gapSize;
class ExtensionItemDelegate : public QItemDelegate
{
@@ -79,25 +81,25 @@ public:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
const override
{
// +---------------+-------+---------------+----------------------------------------------------------------------+---------------+-----------+
// +---------------+-------+---------------+----------------------------------------------------------------------+---------------+---------+
// | | | | (ExPaddingGapL) | | |
// | | | +-------------------------------------------------------------+--------+ | |
// | | | | <itemName> |<status>| | |
// | | | +-------------------------------------------------------------+--------+ | |
// | | | | (VGapXxs) | | |
// | | | +--------+--------+--------------+--------+--------+---------+---------+ | |
// |(ExPaddingGapL)|<icon> |(ExPaddingGapL)|<vendor>|(HGapXs)|<divider>(h16)|(HGapXs)|<dlIcon>|(HGapXxs)|<dlCount>|(ExPaddingGapL)|(HPaddingL)|
// |(ExPaddingGapL)|<icon> |(ExPaddingGapL)|<vendor>|(HGapXs)|<divider>(h16)|(HGapXs)|<dlIcon>|(HGapXxs)|<dlCount>|(ExPaddingGapL)|(gapSize)|
// | |(50x50)| +--------+--------+--------------+--------+--------+---------+---------+ | |
// | | | | (VGapXxs) | | |
// | | | +----------------------------------------------------------------------+ | |
// | | | | <tags> | | |
// | | | +----------------------------------------------------------------------+ | |
// | | | | (ExPaddingGapL) | | |
// +---------------+-------+---------------+----------------------------------------------------------------------+---------------+-----------+
// | (ExVPaddingGapXl) |
// +------------------------------------------------------------------------------------------------------------------------------------------+
// +---------------+-------+---------------+----------------------------------------------------------------------+---------------+---------+
// | (gapSize) |
// +----------------------------------------------------------------------------------------------------------------------------------------+
const QRect bgRGlobal = option.rect.adjusted(0, 0, -HPaddingL, -gapSize);
const QRect bgRGlobal = option.rect.adjusted(0, 0, -gapSize, -gapSize);
const QRect bgR = bgRGlobal.translated(-option.rect.topLeft());
const int middleColumnW = bgR.width() - ExPaddingGapL - iconBgS.width() - ExPaddingGapL
@@ -141,10 +143,7 @@ public:
}
{
QLinearGradient gradient(iconBgR.topRight(), iconBgR.bottomLeft());
const QColor startColor = creatorColor(Utils::Theme::Token_Gradient01_Start);
const QColor endColor = creatorColor(Utils::Theme::Token_Gradient01_End);
gradient.setColorAt(0, startColor);
gradient.setColorAt(1, endColor);
gradient.setStops(iconGradientStops(index));
constexpr int iconRectRounding = 4;
drawCardBackground(painter, iconBgR, gradient, Qt::NoPen, iconRectRounding);
@@ -248,17 +247,44 @@ public:
}
};
class SortFilterProxyModel : public QSortFilterProxyModel
{
public:
SortFilterProxyModel(QObject *parent = nullptr);
protected:
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
};
SortFilterProxyModel::SortFilterProxyModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
}
bool SortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
const ItemType leftType = left.data(RoleItemType).value<ItemType>();
const ItemType rightType = right.data(RoleItemType).value<ItemType>();
if (leftType != rightType)
return leftType < rightType;
const QString leftName = left.data(RoleName).toString();
const QString rightName = right.data(RoleName).toString();
return leftName < rightName;
}
class ExtensionsBrowserPrivate
{
public:
bool dataFetched = false;
ExtensionsModel *model;
QLineEdit *searchBox;
QAbstractButton *updateButton;
QListView *extensionsView;
QItemSelectionModel *selectionModel = nullptr;
QSortFilterProxyModel *filterProxyModel;
SortFilterProxyModel *filterProxyModel;
int columnsCount = 2;
Tasking::TaskTreeRunner taskTreeRunner;
SpinnerSolution::Spinner *m_spinner;
};
ExtensionsBrowser::ExtensionsBrowser(QWidget *parent)
@@ -267,16 +293,17 @@ ExtensionsBrowser::ExtensionsBrowser(QWidget *parent)
{
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
auto manageLabel = new QLabel(Tr::tr("Manage Extensions"));
manageLabel->setFont(uiFont(UiElementH1));
static const TextFormat titleTF
{Theme::Token_Text_Default, UiElementH2};
QLabel *titleLabel = tfLabel(titleTF);
titleLabel->setText(Tr::tr("Manage Extensions"));
d->searchBox = new SearchBox;
d->searchBox->setFixedWidth(itemWidth);
d->updateButton = new Button(Tr::tr("Install..."), Button::MediumPrimary);
d->searchBox->setPlaceholderText(Tr::tr("Search"));
d->model = new ExtensionsModel(this);
d->filterProxyModel = new QSortFilterProxyModel(this);
d->filterProxyModel = new SortFilterProxyModel(this);
d->filterProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
d->filterProxyModel->setFilterRole(RoleSearchText);
d->filterProxyModel->setSortRole(RoleItemType);
@@ -294,11 +321,16 @@ ExtensionsBrowser::ExtensionsBrowser(QWidget *parent)
using namespace Layouting;
Column {
Space(15),
manageLabel,
Space(15),
Row { d->searchBox, st, d->updateButton, Space(extraListViewWidth() + gapSize) },
Space(gapSize),
Column {
titleLabel,
customMargins(0, VPaddingM, 0, VPaddingM),
},
Row {
d->searchBox,
spacing(gapSize),
customMargins(0, VPaddingM, extraListViewWidth() + gapSize, VPaddingM),
},
Space(ExPaddingGapL),
d->extensionsView,
noMargin, spacing(0),
}.attachTo(this);
@@ -308,6 +340,8 @@ ExtensionsBrowser::ExtensionsBrowser(QWidget *parent)
WelcomePageHelpers::setBackgroundColor(d->extensionsView->viewport(),
Theme::Token_Background_Default);
d->m_spinner = new SpinnerSolution::Spinner(SpinnerSolution::SpinnerSize::Large, this);
auto updateModel = [this] {
d->filterProxyModel->sort(0);
@@ -320,12 +354,7 @@ ExtensionsBrowser::ExtensionsBrowser(QWidget *parent)
}
};
connect(d->updateButton, &QAbstractButton::pressed, this, []() {
executePluginInstallWizard();
});
connect(PluginManager::instance(), &PluginManager::pluginsChanged, this, updateModel);
connect(PluginManager::instance(), &PluginManager::initializationDone,
this, &ExtensionsBrowser::fetchExtensions);
connect(d->searchBox, &QLineEdit::textChanged,
d->filterProxyModel, &QSortFilterProxyModel::setFilterWildcard);
}
@@ -335,11 +364,15 @@ ExtensionsBrowser::~ExtensionsBrowser()
delete d;
}
void ExtensionsBrowser::setFilter(const QString &filter)
{
d->searchBox->setText(filter);
}
void ExtensionsBrowser::adjustToWidth(const int width)
{
const int widthForItems = width - extraListViewWidth();
d->columnsCount = qMax(1, qFloor(widthForItems / cellWidth));
d->updateButton->setVisible(d->columnsCount > 1);
updateGeometry();
}
@@ -352,40 +385,56 @@ QSize ExtensionsBrowser::sizeHint() const
int ExtensionsBrowser::extraListViewWidth() const
{
// TODO: Investigate "transient" scrollbar, just for this list view.
constexpr int extraPadding = qMax(0, ExVPaddingGapXl - gapSize);
return d->extensionsView->style()->pixelMetric(QStyle::PM_ScrollBarExtent)
+ extraPadding
+ 1; // Needed
}
void ExtensionsBrowser::showEvent(QShowEvent *event)
{
if (!d->dataFetched) {
d->dataFetched = true;
fetchExtensions();
}
QWidget::showEvent(event);
}
void ExtensionsBrowser::fetchExtensions()
{
// d->model->setExtensionsJson(testData("thirdpartyplugins")); return;
#ifdef WITH_TESTS
// Uncomment for testing with local json data.
// Available: "augmentedplugindata", "defaultpacks", "varieddata", "thirdpartyplugins"
// d->model->setExtensionsJson(testData("defaultpacks")); return;
#endif // WITH_TESTS
using namespace Tasking;
const auto onQuerySetup = [](NetworkQuery &query) {
const auto onQuerySetup = [this](NetworkQuery &query) {
const QString host = "https://qc-extensions.qt.io";
const QString url = "%1/api/v1/search?request=";
const QString requestTemplate
= R"({"version":"%1","host_os":"%2","host_os_version":"%3","host_architecture":"%4","page_size":200})";
const QString request = url.arg(host)
+ requestTemplate
.arg("2.2") // .arg(QCoreApplication::applicationVersion())
.arg("macOS") // .arg(QSysInfo::productType())
.arg("12") // .arg(QSysInfo::productVersion())
.arg("arm64"); // .arg(QSysInfo::currentCpuArchitecture());
const QString request = url.arg(host) + requestTemplate
.arg(QCoreApplication::applicationVersion())
.arg(QSysInfo::productType())
.arg(QSysInfo::productVersion())
.arg(QSysInfo::currentCpuArchitecture());
query.setRequest(QNetworkRequest(QUrl::fromUserInput(request)));
query.setNetworkAccessManager(NetworkAccessManager::instance());
qCDebug(browserLog).noquote() << "Sending request:" << request;
d->m_spinner->show();
};
const auto onQueryDone = [this](const NetworkQuery &query, DoneWith result) {
if (result != DoneWith::Success) {
#ifdef WITH_TESTS
d->model->setExtensionsJson(testData("defaultpacks"));
#endif // WITH_TESTS
return;
}
const QByteArray response = query.reply()->readAll();
qCDebug(browserLog).noquote() << "Got result" << result;
if (result == DoneWith::Success) {
d->model->setExtensionsJson(response);
} else {
qCDebug(browserLog).noquote() << response;
d->model->setExtensionsJson({});
}
d->m_spinner->hide();
};
Group group {
@@ -395,4 +444,35 @@ void ExtensionsBrowser::fetchExtensions()
d->taskTreeRunner.start(group);
}
QLabel *tfLabel(const TextFormat &tf, bool singleLine)
{
QLabel *label = singleLine ? new Utils::ElidingLabel : new QLabel;
if (singleLine)
label->setFixedHeight(tf.lineHeight());
label->setFont(tf.font());
label->setAlignment(Qt::Alignment(tf.drawTextFlags));
QPalette pal = label->palette();
pal.setColor(QPalette::WindowText, tf.color());
label->setPalette(pal);
return label;
}
QGradientStops iconGradientStops(const QModelIndex &index)
{
const PluginSpec *ps = pluginSpecForName(index.data(RoleName).toString());
const bool greenGradient = ps != nullptr && ps->isEffectivelyEnabled();
const QColor startColor = creatorColor(greenGradient ? Theme::Token_Gradient01_Start
: Theme::Token_Gradient02_Start);
const QColor endColor = creatorColor(greenGradient ? Theme::Token_Gradient01_End
: Theme::Token_Gradient02_End);
const QGradientStops gradient = {
{0, startColor},
{1, endColor},
};
return gradient;
}
} // ExtensionManager::Internal

View File

@@ -5,6 +5,12 @@
#include <QWidget>
QT_FORWARD_DECLARE_CLASS(QLabel)
namespace Core::WelcomePageHelpers {
class TextFormat;
}
namespace ExtensionManager::Internal {
class ExtensionsBrowser final : public QWidget
@@ -15,11 +21,15 @@ public:
ExtensionsBrowser(QWidget *parent = nullptr);
~ExtensionsBrowser();
void setFilter(const QString &filter);
void adjustToWidth(const int width);
QSize sizeHint() const override;
int extraListViewWidth() const; // Space for scrollbar, etc.
void showEvent(QShowEvent *event) override;
signals:
void itemSelected(const QModelIndex &current, const QModelIndex &previous);
@@ -29,4 +39,7 @@ private:
class ExtensionsBrowserPrivate *d = nullptr;
};
QLabel *tfLabel(const Core::WelcomePageHelpers::TextFormat &tf, bool singleLine = true);
QGradientStops iconGradientStops(const QModelIndex &index);
} // ExtensionManager::Internal

View File

@@ -36,8 +36,8 @@ using Dependencies = QList<Dependency>;
struct Plugin
{
Dependencies dependencies;
QString copyright;
Dependencies dependencies;
bool isInternal = false;
QString name;
QString packageUrl;
@@ -69,23 +69,29 @@ struct Extension {
};
using Extensions = QList<Extension>;
static const Dependencies dependenciesFromJson(const QJsonObject &obj)
{
const QJsonArray dependenciesArray = obj.value("Dependencies").toArray();
Dependencies dependencies;
for (const QJsonValueConstRef &dependencyVal : dependenciesArray) {
const QJsonObject dependencyObj = dependencyVal.toObject();
const QJsonObject metaDataObj = dependencyObj.value("meta_data").toObject();
dependencies.append({
.name = metaDataObj.value("Name").toString(),
.version = metaDataObj.value("Version").toString(),
});
}
return dependencies;
}
static Plugin pluginFromJson(const QJsonObject &obj)
{
const QJsonObject metaDataObj = obj.value("meta_data").toObject();
const QJsonArray dependenciesArray = metaDataObj.value("Dependencies").toArray();
Dependencies dependencies;
for (const QJsonValueConstRef &dependencyVal : dependenciesArray) {
const QJsonObject dependencyObj = dependencyVal.toObject();
dependencies.append(Dependency{
.name = dependencyObj.value("Name").toString(),
.version = dependencyObj.value("Version").toString(),
});
}
return {
.dependencies = dependencies,
.copyright = metaDataObj.value("Copyright").toString(),
.dependencies = dependenciesFromJson(metaDataObj),
.isInternal = obj.value("is_internal").toBool(false),
.name = metaDataObj.value("Name").toString(),
.packageUrl = obj.value("url").toString(),
@@ -192,30 +198,80 @@ static Extensions parseExtensionsRepoReply(const QByteArray &jsonData)
return parsedExtensions;
}
static Extension extensionFromPluginSpec(const PluginSpec *pluginSpec)
{
const Dependencies dependencies = transform(pluginSpec->dependencies(),
[](const PluginDependency &pd) -> Dependency {
return {
.name = pd.name,
.version = pd.version,
};
});
const Plugin plugin = {
.copyright = pluginSpec->copyright(),
.dependencies = dependencies,
.name = pluginSpec->name(),
.packageUrl = {},
.vendor = pluginSpec->vendor(),
.version = pluginSpec->version(),
};
const QStringList lines = pluginSpec->description().split('\n', Qt::SkipEmptyParts)
+ pluginSpec->longDescription().split('\n', Qt::SkipEmptyParts);
const TextData text = {{ pluginSpec->name(), lines }};
LinksData links;
if (const QString url = pluginSpec->url(); !url.isEmpty())
links.append({{}, url});
const Description description = {
.images = {},
.links = links,
.text = text,
};
const QString platformsPattern = pluginSpec->platformSpecification().pattern();
const QStringList platforms = platformsPattern.isEmpty()
? QStringList({"macOS", "Windows", "Linux"})
: QStringList(platformsPattern);
const Extension extension = {
.copyright = pluginSpec->copyright(),
.description = description,
.id = {},
.license = pluginSpec->license(),
.name = pluginSpec->name(),
.platforms = platforms,
.plugins = {plugin},
.tags = {},
.type = ItemTypeExtension,
.vendor = pluginSpec->vendor(),
.version = pluginSpec->version(),
};
return extension;
}
class ExtensionsModelPrivate
{
public:
void setExtensions(const Extensions &extensions);
void removeLocalExtensions();
void addUnlistedLocalExtensions();
Extensions allExtensions; // Original, complete extensions entries
Extensions absentExtensions; // All packs + plugin extensions that are not (yet) installed
Extensions extensions;
};
void ExtensionsModelPrivate::setExtensions(const Extensions &extensions)
{
allExtensions = extensions;
removeLocalExtensions();
this->extensions = extensions;
qCDebug(modelLog) << "Number of extensions from json:" << this->extensions.count();
addUnlistedLocalExtensions();
qCDebug(modelLog) << "Number of extensions with added local ones:" << this->extensions.count();
}
void ExtensionsModelPrivate::removeLocalExtensions()
void ExtensionsModelPrivate::addUnlistedLocalExtensions()
{
const QStringList installedPlugins = transform(PluginManager::plugins(), &PluginSpec::name);
absentExtensions.clear();
for (const Extension &extension : allExtensions) {
if (extension.type == ItemTypePack || !installedPlugins.contains(extension.name))
absentExtensions.append(extension);
}
const QStringList listedModelExtensions = transform(extensions, &Extension::name);
for (const PluginSpec *plugin : PluginManager::plugins())
if (!listedModelExtensions.contains(plugin->name()))
extensions.append(extensionFromPluginSpec(plugin));
}
ExtensionsModel::ExtensionsModel(QObject *parent)
@@ -231,66 +287,7 @@ ExtensionsModel::~ExtensionsModel()
int ExtensionsModel::rowCount([[maybe_unused]] const QModelIndex &parent) const
{
const int remoteExtnsionsCount = d->absentExtensions.count();
const int installedPluginsCount = PluginManager::plugins().count();
return remoteExtnsionsCount + installedPluginsCount;
}
static QVariant dataFromPluginSpec(const PluginSpec *pluginSpec, int role)
{
switch (role) {
case Qt::DisplayRole:
case RoleName:
return pluginSpec->name();
case RoleCopyright:
return pluginSpec->copyright();
case RoleDependencies: {
QStringList dependencies = transform(pluginSpec->dependencies(),
&PluginDependency::toString);
dependencies.sort();
return dependencies;
}
case RoleDescriptionImages:
break;
case RoleDescriptionLinks: {
const QString url = pluginSpec->url();
if (!url.isEmpty()) {
const LinksData links = {{{}, url}};
return QVariant::fromValue(links);
}
break;
}
case RoleDescriptionText: {
QStringList lines = pluginSpec->description().split('\n', Qt::SkipEmptyParts);
lines.append(pluginSpec->longDescription().split('\n', Qt::SkipEmptyParts));
const TextData text = {{ pluginSpec->name(), lines }};
return QVariant::fromValue(text);
}
case RoleItemType:
return ItemTypeExtension;
case RoleLicense:
return pluginSpec->license();
case RoleLocation:
return pluginSpec->filePath().toVariant();
case RolePlatforms: {
const QString pattern = pluginSpec->platformSpecification().pattern();
const QStringList platforms = pattern.isEmpty()
? QStringList({"macOS", "Windows", "Linux"})
: QStringList(pattern);
return platforms;
}
case RoleSize:
return pluginSpec->filePath().fileSize();
case RoleTags:
break;
case RoleVendor:
return pluginSpec->vendor();
case RoleVersion:
return pluginSpec->version();
default:
break;
}
return {};
return d->extensions.count();
}
static QStringList dependenciesFromExtension(const Extension &extension)
@@ -371,27 +368,18 @@ QVariant ExtensionsModel::data(const QModelIndex &index, int role) const
if (role == RoleSearchText)
return searchText(index);
const bool itemIsLocalPlugin = index.row() >= d->absentExtensions.count();
if (itemIsLocalPlugin) {
const PluginSpecs &pluginSpecs = PluginManager::plugins();
const int pluginIndex = index.row() - d->absentExtensions.count();
QTC_ASSERT(pluginIndex >= 0 && pluginIndex <= pluginSpecs.size(), return {});
const PluginSpec *plugin = pluginSpecs.at(pluginIndex);
return dataFromPluginSpec(plugin, role);
} else {
const Extension &extension = d->absentExtensions.at(index.row());
const Extension &extension = d->extensions.at(index.row());
const QVariant extensionData = dataFromExtension(extension, role);
// If data is unavailable, retrieve it from the first contained plugin
if (extensionData.isNull() && !extension.plugins.isEmpty()) {
const PluginSpec *pluginSpec = ExtensionsModel::pluginSpecForName(
extension.plugins.constFirst().name);
if (pluginSpec)
return dataFromPluginSpec(pluginSpec, role);
const QString firstPluginName = extension.plugins.constFirst().name;
const Extension firstPluginExtension =
findOrDefault(d->extensions, Utils::equal(&Extension::name, firstPluginName));
if (firstPluginExtension.name.isEmpty())
return {};
return dataFromExtension(firstPluginExtension, role);
}
return extensionData;
}
return {};
}
void ExtensionsModel::setExtensionsJson(const QByteArray &json)
@@ -402,7 +390,7 @@ void ExtensionsModel::setExtensionsJson(const QByteArray &json)
endResetModel();
}
PluginSpec *ExtensionsModel::pluginSpecForName(const QString &pluginName)
PluginSpec *pluginSpecForName(const QString &pluginName)
{
return findOrDefault(PluginManager::plugins(), equal(&PluginSpec::name, pluginName));
}

View File

@@ -54,12 +54,13 @@ public:
QVariant data(const QModelIndex &index, int role) const;
void setExtensionsJson(const QByteArray &json);
static ExtensionSystem::PluginSpec *pluginSpecForName(const QString &pluginName);
private:
class ExtensionsModelPrivate *d = nullptr;
};
ExtensionSystem::PluginSpec *pluginSpecForName(const QString &pluginName);
#ifdef WITH_TESTS
QObject *createExtensionsModelTest();
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

View File

@@ -0,0 +1,71 @@
{
"items": [
{
"name": "ScreenRecorder",
"description": {
"paragraphs": [
{
"header": "Screen Recorder plugin",
"text": [
"With FFmpeg, you can record your screens and save the recordings as animated images or videos.",
"To record screens:",
"",
"- Select Tools > Screen Recording.",
"- Select to select the screen to record from and to set the recorded screen area.",
"- Select to start recording.",
"- Select when you are done recording.",
"- Select Crop and Trim to edit the recording.",
"- Select Export to save the recording as an animated image or a video."
]
},
{
"header": "Set the screen and area to record",
"text": [
"Set the screen and the area to record in the Screen Recording Options dialog.",
"To select a screen and area:",
"",
"- In Display, select the display to record.",
"- In Recorded screen area, drag the guides to set the x and y coordinates of the starting point for the recording area, as well as the width and height of the area.",
"- Select OK to return to the Record Screen dialog."
]
}
],
"images": [
{
"image_label": "Create animated imges like this",
"url": "https://bugreports.qt.io/secure/attachment/156058/156058_DragAndCopyOnLinux.gif"
}
],
"links": [
{
"link_text": "Documentation",
"url": "https://doc.qt.io/qtcreator/creator-how-to-record-screens.html"
},
{
"link_text": "Homepage",
"url": "https://www.qt.io/"
}
]
},
"is_pack": false,
"plugins": [
{
"meta_data": {
"Name": "ScreenRecorder",
"Dependencies": [
{
"meta_data": {
"Name": "Core",
"Version": "13.0.2"
}
}
],
"Version": "13.0.2"
}
}
],
"tags": [ "Utility", "Docs" ],
"vendor": "The Qt Company Ltd"
}
]
}

View File

@@ -121,41 +121,6 @@
"plugins": [
{ "meta_data": { "Name": "Designer" } }
]
},
{
"name": "SpellChecker",
"tags": [ "Editor" ],
"platforms": [ "macOS", "Windows", "Linux" ],
"license": "os",
"is_pack": false,
"description": {
"paragraphs": [
{
"text": [
"Spellcheck comments in source files."
],
"header": "Get started"
}
],
"links": [
{
"url": "https://github.com/CJCombrink/SpellChecker-Plugin",
"link_text": "GitHub page"
}
]
},
"plugins": [
{
"meta_data": {
"Name": "SpellChecker",
"Copyright": "(C) 2015 - 2024 Carel Combrink"
},
"url": "https://github.com/CJCombrink/SpellChecker-Plugin/releases/download/v3.6.0/SpellChecker-Plugin_QtC13.0.0_macos_x64.tar.gz"
}
],
"vendor": "Carel Combrink",
"copyright": "(C) 2015 - 2024 Carel Combrink"
}
]
}

View File

@@ -0,0 +1,76 @@
{
"items": [
{
"name": "Few tags",
"tags": [ "Tag one", "Tag two"]
},
{
"name": "Many tags",
"tags": [ "Tag_01", "Tag_02", "Tag_03", "Tag_04", "Tag_05", "Tag_06", "Tag_07", "Tag_08", "Tag_09", "Tag_10", "Tag_11", "Tag_12", "Tag_13", "Tag_14", "Tag_15", "Tag_16", "Tag_17", "Tag_18", "Tag_19", "Tag_20", "Tag_21", "Tag_22", "Tag_23", "Tag_24", "Tag_25", "Tag_26", "Tag_27", "Tag_28", "Tag_29", "Tag_30", "And_a_very_long_tag_without_spaces", "Ok, a last long tag without spaces, but that sgould be enough"],
"description": {
"paragraphs": [
{
"text": [
"... and a few long ones"
]
}
]
}
},
{
"name": "One static image",
"description": {
"paragraphs": [
{
"text": [
"png"
]
}
],
"images": [
{
"image_label": "Screenshot",
"url": "https://bugreports.qt.io/secure/attachment/147354/VirtualNodesShownAsNotExisting.png"
}
]
}
},
{
"name": "One animated image",
"description": {
"paragraphs": [
{
"text": [
"gif (animated)"
]
}
],
"images": [
{
"image_label": "Screencast",
"url": "https://bugreports.qt.io/secure/attachment/156058/156058_DragAndCopyOnLinux.gif"
}
]
}
},
{
"name": "Vendor, no download count",
"vendor": "Vendor name"
},
{
"name": "No vendor, but download count",
"download_count": 12345
},
{
"name": "Vendor and download count",
"vendor": "Vendor name",
"download_count": 12345
}
]
}

View File

@@ -76,7 +76,12 @@ protected:
}
private:
bool canFetchMore() const override { return m_client && !m_fetchedChildren; }
bool canFetchMore() const override
{
if (m_client && !m_fetchedChildren)
const_cast<HierarchyItem*>(this)->fetchMore();
return false;
}
void fetchMore() override
{
@@ -96,8 +101,6 @@ private:
appendChild(new HierarchyItem(getSourceItem(item), m_client));
}
}
if (!hasChildren())
update();
});
m_client->sendMessage(request);
}
@@ -267,6 +270,7 @@ public:
m_view->setModel(&m_model);
m_view->setActivationMode(SingleClickActivation);
m_view->setItemDelegate(&m_delegate);
m_view->setUniformRowHeights(true);
theWidget->setLayout(new QVBoxLayout);
theWidget->layout()->addWidget(m_view);
@@ -432,7 +436,7 @@ public:
Icons::RELOAD_TOOLBAR.icon();
auto button = new QToolButton;
button->setIcon(Icons::RELOAD_TOOLBAR.icon());
button->setToolTip(LanguageClient::Tr::tr(
button->setToolTip(::LanguageClient::Tr::tr(
"Reloads the call hierarchy for the symbol under cursor position."));
connect(button, &QToolButton::clicked, this, [h] { h->updateHierarchyAtCursorPosition(); });
return {h, {button}};

View File

@@ -1789,6 +1789,11 @@ const DynamicCapabilities &Client::dynamicCapabilities() const
return d->m_dynamicCapabilities;
}
DynamicCapabilities &Client::dynamicCapabilities()
{
return d->m_dynamicCapabilities;
}
DocumentSymbolCache *Client::documentSymbolCache()
{
return &d->m_documentSymbolCache;

View File

@@ -97,6 +97,7 @@ public:
QString serverName() const;
QString serverVersion() const;
const DynamicCapabilities &dynamicCapabilities() const;
DynamicCapabilities &dynamicCapabilities();
void registerCapabilities(const QList<LanguageServerProtocol::Registration> &registrations);
void unregisterCapabilities(const QList<LanguageServerProtocol::Unregistration> &unregistrations);

View File

@@ -3,6 +3,8 @@
#pragma once
#include "languageclient_global.h"
#include <languageserverprotocol/client.h>
namespace LanguageClient {
@@ -21,7 +23,7 @@ public:
void disable()
{
m_enabled = true;
m_enabled = false;
m_id.clear();
m_options = QJsonValue();
}
@@ -37,7 +39,7 @@ private:
};
class DynamicCapabilities
class LANGUAGECLIENT_EXPORT DynamicCapabilities
{
public:
DynamicCapabilities() = default;

View File

@@ -387,6 +387,7 @@ void LanguageClientManager::enableClientSettings(const QString &settingsId, bool
QList<Client *> LanguageClientManager::clientsForSetting(const BaseSettings *setting)
{
QTC_ASSERT(managerInstance, return {});
QTC_ASSERT(setting, return {});
auto instance = managerInstance;
return instance->m_clientsForSetting.value(setting->m_id);
}

View File

@@ -175,7 +175,7 @@ public:
std::optional<sol::protected_function> m_startFailedCallback;
QMap<QString, sol::protected_function> m_messageCallbacks;
QList<Client *> m_clients;
LuaClientSettings *m_settings{nullptr};
public:
static BaseSettings::StartBehavior startBehaviorFromString(const QString &str)
@@ -190,6 +190,8 @@ public:
throw sol::error("Unknown start behavior: " + str.toStdString());
}
void setSettings(LuaClientSettings *settings) { m_settings = settings; }
LuaClientWrapper(const sol::table &options)
{
m_cmdLineCallback = addValue<CommandLine>(
@@ -267,8 +269,6 @@ public:
auto luaClient = qobject_cast<LuaClient *>(c);
if (luaClient && luaClient->m_settingsId == m_settingsTypeId && m_onInstanceStart) {
QTC_CHECK(::Lua::LuaEngine::void_safe_call(*m_onInstanceStart, c));
m_clients.push_back(c);
updateMessageCallbacks();
}
});
@@ -286,22 +286,11 @@ public:
if (!luaClient || luaClient->m_settingsId != m_settingsTypeId)
return;
if (m_clients.contains(c))
m_clients.removeOne(c);
if (unexpected && m_startFailedCallback) {
QTC_CHECK_EXPECTED(::Lua::LuaEngine::void_safe_call(*m_startFailedCallback));
}
}
~LuaClientWrapper()
{
for (auto client : m_clients)
LanguageClientManager::shutdownClient(client);
// TODO: Unregister Client settings from LanguageClientManager
}
TransportType transportType() { return m_transportType; }
void applySettings()
@@ -340,7 +329,9 @@ public:
void updateMessageCallbacks()
{
for (Client *c : m_clients) {
for (Client *c : LanguageClientManager::clientsForSetting(m_settings)) {
if (!c)
continue;
for (const auto &[msg, func] : m_messageCallbacks.asKeyValueRange()) {
c->registerCustomMethod(
msg,
@@ -367,9 +358,11 @@ public:
if (!messageValue.isObject())
throw sol::error("Message is not an object");
const LanguageServerProtocol::JsonRpcMessage jsonrpcmessage(messageValue.toObject());
for (Client *c : m_clients)
for (Client *c : LanguageClientManager::clientsForSetting(m_settings)) {
if (c)
c->sendMessage(jsonrpcmessage);
}
}
void updateOptions()
{
@@ -536,6 +529,7 @@ static void registerLuaApi()
[](const sol::table &options) -> std::shared_ptr<LuaClientWrapper> {
auto luaClient = std::make_shared<LuaClientWrapper>(options);
auto client = new LuaClientSettings(luaClient);
luaClient->setSettings(client);
// The order is important!
// First restore the settings ...

View File

@@ -59,7 +59,7 @@ void addFetchModule()
LuaOptionsPage(Module *module)
{
setId("BB.Lua.Fetch");
setDisplayName(Tr::tr("Network access"));
setDisplayName(Tr::tr("Network Access"));
setCategory("ZY.Lua");
setDisplayCategory("Lua");
setCategoryIconPath(":/lua/images/settingscategory_lua.png");
@@ -176,8 +176,8 @@ void addFetchModule()
// so we have to use a QMessageBox instead of the info bar
auto msgBox = new QMessageBox(
QMessageBox::Question,
Tr::tr("Allow Internet access"),
Tr::tr("The plugin \"%1\" would like to fetch from the following url:\n%2")
Tr::tr("Allow Internet Access"),
Tr::tr("Allow the extension \"%1\" to fetch from the following URL:\n%2")
.arg(pluginName)
.arg(url),
QMessageBox::Yes | QMessageBox::No,
@@ -205,14 +205,13 @@ void addFetchModule()
Utils::InfoBarEntry entry{
Utils::Id::fromString("Fetch" + pluginName),
Tr::tr("The plugin \"%1\" would like to fetch data from the internet. Do "
"you want to allow this?")
Tr::tr("Allow the extension \"%1\" to fetch data from the internet?")
.arg(pluginName)};
entry.setDetailsWidgetCreator([pluginName, url] {
const QString markdown = Tr::tr("The plugin \"**%1**\" would like to fetch "
"from the following url:\n\n")
.arg(pluginName)
+ QString("* [%3](%3)").arg(url);
const QString markdown = Tr::tr("Allow the extension \"%1\" to fetch data"
"from the following URL:\n\n")
.arg("**" + pluginName + "**")
+ QString("* [%1](%1)").arg(url);
QLabel *list = new QLabel();
list->setTextFormat(Qt::TextFormat::MarkdownText);
@@ -225,7 +224,7 @@ void addFetchModule()
Core::ICore::infoBar()->removeInfo(Utils::Id::fromString("Fetch" + pluginName));
fetch();
});
entry.addCustomButton(Tr::tr("Allow once"), [pluginName, fetch]() {
entry.addCustomButton(Tr::tr("Allow Once"), [pluginName, fetch]() {
Core::ICore::infoBar()->removeInfo(Utils::Id::fromString("Fetch" + pluginName));
fetch();
});
@@ -311,8 +310,8 @@ void addFetchModule()
};
checkPermission(url, actualFetch, [callback, pluginName]() {
callback(Tr::tr("Fetching is not allowed for the plugin \"%1\" (You can edit "
"permissions in Preferences => Lua)")
callback(Tr::tr("Fetching is not allowed for the extension \"%1\". (You can edit "
"permissions in Preferences > Lua.)")
.arg(pluginName));
});
};

View File

@@ -50,7 +50,7 @@ expected_str<QJsonDocument> getPackageInfo(const FilePath &appDataPath)
return make_unexpected(error.errorString());
if (!doc.isObject())
return make_unexpected(Tr::tr("Package info is not an object"));
return make_unexpected(Tr::tr("Package info is not an object."));
return doc;
}
@@ -66,7 +66,7 @@ expected_str<QJsonObject> getInstalledPackageInfo(const FilePath &appDataPath, c
if (root.contains(name)) {
QJsonValue v = root[name];
if (!v.isObject())
return make_unexpected(Tr::tr("Installed package info is not an object"));
return make_unexpected(Tr::tr("Installed package info is not an object."));
return v.toObject();
}
@@ -86,12 +86,12 @@ expected_str<QJsonDocument> getOrCreatePackageInfo(const FilePath &appDataPath)
expected_str<void> savePackageInfo(const FilePath &appDataPath, const QJsonDocument &doc)
{
if (!appDataPath.ensureWritableDir())
return make_unexpected(Tr::tr("Could not create app data directory"));
return make_unexpected(Tr::tr("Cannot create app data directory."));
const FilePath packageInfoPath = appDataPath / "package.json";
return packageInfoPath.writeFileContents(doc.toJson())
.transform_error([](const QString &error) {
return Tr::tr("Could not write to package info: %1").arg(error);
return Tr::tr("Cannot write to package info: %1").arg(error);
})
.transform([](qint64) { return; });
}
@@ -147,7 +147,7 @@ static Group installRecipe(
const auto size = reply->size();
const auto written = storage->write(reply->readAll());
if (written != size)
return emitResult(Tr::tr("Could not write to temporary file"));
return emitResult(Tr::tr("Cannot write to temporary file."));
storage->close();
return DoneResult::Success;
};
@@ -169,7 +169,7 @@ static Group installRecipe(
const auto onUnarchiverDone = [appDataPath, installOptionsIt, emitResult](DoneWith result) {
if (result == DoneWith::Error)
return emitResult(Tr::tr("Unarchiving failed"));
return emitResult(Tr::tr("Unarchiving failed."));
if (result == DoneWith::Cancel)
return DoneResult::Error;
@@ -212,7 +212,7 @@ static Group installRecipe(
}
if (!storage->open(QIODevice::WriteOnly)) {
emitResult(Tr::tr("Could not open temporary file"));
emitResult(Tr::tr("Cannot open temporary file."));
return SetupResult::StopWithError;
}
return SetupResult::Continue;
@@ -326,16 +326,17 @@ void addInstallModule()
if (QApplication::activeModalWidget()) {
auto msgBox = new QMessageBox(
QMessageBox::Question,
Tr::tr("Install package"),
Tr::tr("Install Package"),
msg,
QMessageBox::Yes | QMessageBox::No,
Core::ICore::dialogParent());
const QString details
= Tr::tr("The plugin \"%1\" would like to install the following "
= Tr::tr("The extension \"%1\" wants to install the following "
"package(s):\n\n")
.arg(pluginSpec->name)
+ Utils::transform(installOptionsList, [](const InstallOptions &options) {
//: %1 = package name, %2 = version, %3 = URL
return QString("* %1 - %2 (from: %3)")
.arg(options.name, options.version, options.url.toString());
}).join("\n");
@@ -363,11 +364,12 @@ void addInstallModule()
entry.setCancelButtonInfo(denied);
const QString details
= Tr::tr("The plugin \"**%1**\" would like to install the following "
= Tr::tr("The extension \"%1\" wants to install the following "
"package(s):\n\n")
.arg(pluginSpec->name)
.arg("**" + pluginSpec->name + "**") // markdown bold
+ Utils::transform(installOptionsList, [](const InstallOptions &options) {
return QString("* %1 - %2 (from: [%3](%3))")
//: Markdown list item: %1 = package name, %2 = version, %3 = URL
return Tr::tr("* %1 - %2 (from: [%3](%3))")
.arg(options.name, options.version, options.url.toString());
}).join("\n");

View File

@@ -136,7 +136,7 @@ std::unique_ptr<Utils::LuaState> LuaEngine::runScript(
sol::error err = result;
qWarning() << "Failed to run script" << name << ":" << QString::fromUtf8(err.what());
Core::MessageManager::writeFlashing(
tr("Failed to run script %1: %2").arg(name, QString::fromUtf8(err.what())));
Tr::tr("Failed to run script %1: %2").arg(name, QString::fromUtf8(err.what())));
}
return opaque;
@@ -168,7 +168,7 @@ expected_str<void> LuaEngine::connectHooks(
QString hookName = QStringList{path, k.as<QString>()}.join(".");
auto it = d->m_hooks.find(hookName);
if (it == d->m_hooks.end())
return make_unexpected(QString("No hook named '%1' found").arg(hookName));
return make_unexpected(Tr::tr("No hook with the name \"%1\" found.").arg(hookName));
else
it.value()(v.as<sol::function>());
}
@@ -269,7 +269,7 @@ expected_str<sol::protected_function> LuaEngine::prepareSetup(
auto pluginTable = result.get<sol::optional<sol::table>>();
if (!pluginTable)
return make_unexpected(Tr::tr("Script did not return a table"));
return make_unexpected(Tr::tr("Script did not return a table."));
auto hookTable = pluginTable->get<sol::optional<sol::table>>("hooks");
@@ -282,7 +282,7 @@ expected_str<sol::protected_function> LuaEngine::prepareSetup(
auto setupFunction = pluginTable->get_or<sol::function>("setup", {});
if (!setupFunction)
return make_unexpected(Tr::tr("Plugin info table did not contain a setup function"));
return make_unexpected(Tr::tr("Extension info table did not contain a setup function."));
return setupFunction;
}

View File

@@ -108,19 +108,19 @@ bool LuaPluginSpec::initializePlugin()
= LuaEngine::instance().prepareSetup(*activeLuaState, *this);
if (!setupResult) {
setError(Lua::Tr::tr("Failed to prepare plugin setup: %1").arg(setupResult.error()));
setError(Lua::Tr::tr("Cannot prepare extension setup: %1").arg(setupResult.error()));
return false;
}
auto result = setupResult->call();
if (result.get_type() == sol::type::boolean && result.get<bool>() == false) {
setError(Lua::Tr::tr("Plugin setup function returned false"));
setError(Lua::Tr::tr("Extension setup function returned false."));
return false;
} else if (result.get_type() == sol::type::string) {
std::string error = result.get<sol::error>().what();
if (!error.empty()) {
setError(Lua::Tr::tr("Plugin setup function returned error: %1")
setError(Lua::Tr::tr("Extension setup function returned error: %1")
.arg(QString::fromStdString(error)));
return false;
}

View File

@@ -67,12 +67,12 @@ DeployMcuProcessStep::DeployMcuProcessStep(ProjectExplorer::BuildStepList *bc, I
, m_tmpDir()
{
if (!buildSystem()) {
showError(QmlProjectManager::Tr::tr("Failed to find valid build system"));
showError(QmlProjectManager::Tr::tr("Cannot find a valid build system."));
return;
}
if (!m_tmpDir.isValid()) {
showError(QmlProjectManager::Tr::tr("Failed to create valid build directory"));
showError(QmlProjectManager::Tr::tr("Cannot create a valid build directory."));
return;
}
@@ -193,7 +193,7 @@ void MCUBuildStepFactory::updateDeployStep(ProjectExplorer::Target *target, bool
stepList->appendStep(DeployMcuProcessStep::id);
} else {
DeployMcuProcessStep::showError(
QmlProjectManager::Tr::tr("Failed to find valid Qt for MCUs kit"));
QmlProjectManager::Tr::tr("Cannot find a valid Qt for MCUs kit."));
}
} else {
if (!step)

View File

@@ -320,7 +320,7 @@ void McuSupportOptionsWidget::apply()
QMessageBox warningPopup(QMessageBox::Icon::Warning,
Tr::tr("Warning"),
Tr::tr("Unable to apply changes in Devices > MCU."),
Tr::tr("Cannot apply changes in Devices > MCU."),
QMessageBox::Ok,
this);

View File

@@ -448,8 +448,10 @@ void ElementTasks::openLinkedFile(const qmt::MElement *element)
(void) Core::EditorManager::openEditor(filepath);
}
} else {
QMessageBox::critical(Core::ICore::dialogParent(), Tr::tr("Opening File"),
Tr::tr("File %1 does not exist.").arg(filepath.toUserOutput()));
QMessageBox::critical(
Core::ICore::dialogParent(),
Tr::tr("Opening File"),
Tr::tr("File \"%1\" does not exist.").arg(filepath.toUserOutput()));
}
}
}

View File

@@ -257,8 +257,10 @@ void ExtPropertiesMView::onImagePathChanged(const QString &path)
assignModelElement<qmt::DObject, QImage>(m_diagramElements, SelectionSingle, image,
&qmt::DObject::image, &qmt::DObject::setImage);
} else {
QMessageBox::critical(Core::ICore::dialogParent(), Tr::tr("Selecting Image"),
Tr::tr("Unable to read image file %1").arg(path));
QMessageBox::critical(
Core::ICore::dialogParent(),
Tr::tr("Selecting Image"),
Tr::tr("Unable to read image file \"%1\".").arg(path));
}
}
}

View File

@@ -140,7 +140,6 @@ add_qtc_plugin(ProjectExplorer
projectmanager.cpp projectmanager.h
projectmodels.cpp projectmodels.h
projectnodes.cpp projectnodes.h
projectnodeshelper.h
projectpanelfactory.cpp projectpanelfactory.h
projectsettingswidget.cpp projectsettingswidget.h
projecttree.cpp projecttree.h

View File

@@ -57,7 +57,7 @@ BuildPropertiesSettings::BuildPropertiesSettings()
buildDirectoryTemplate.setToolTip(
Tr::tr("Template used to construct the default build directory.<br><br>"
"The default value can be set using the environment variable "
"<tt>%1</tt>")
"<tt>%1</tt>.")
.arg(Constants::QTC_DEFAULT_BUILD_DIRECTORY_TEMPLATE));
buildDirectoryTemplate.setUseResetButton();

View File

@@ -75,12 +75,11 @@ QStringList SshParameters::connectionOptions(const FilePath &binary) const
return args;
}
bool SshParameters::setupSshEnvironment(Process *process)
void SshParameters::setupSshEnvironment(Process *process)
{
Environment env = process->controlEnvironment();
if (!env.hasChanges())
env = Environment::systemEnvironment();
const bool hasDisplay = env.hasKey("DISPLAY") && (env.value("DISPLAY") != QString(":0"));
if (SshSettings::askpassFilePath().exists()) {
env.set("SSH_ASKPASS", SshSettings::askpassFilePath().toUserOutput());
env.set("SSH_ASKPASS_REQUIRE", "force");
@@ -93,7 +92,6 @@ bool SshParameters::setupSshEnvironment(Process *process)
// Otherwise, ssh will ignore SSH_ASKPASS and read from /dev/tty directly.
process->setDisableUnixTerminal();
return hasDisplay;
}
bool operator==(const SshParameters &p1, const SshParameters &p2)

View File

@@ -46,7 +46,7 @@ public:
AuthenticationType authenticationType = AuthenticationTypeAll;
SshHostKeyCheckingMode hostKeyCheckingMode = SshHostKeyCheckingAllowNoMatch;
static bool setupSshEnvironment(Utils::Process *process);
static void setupSshEnvironment(Utils::Process *process);
friend PROJECTEXPLORER_EXPORT bool operator==(const SshParameters &p1, const SshParameters &p2);
friend bool operator!=(const SshParameters &p1, const SshParameters &p2) { return !(p1 == p2); }

View File

@@ -115,7 +115,6 @@ QtcPlugin {
"projectmanager.cpp", "projectmanager.h",
"projectmodels.cpp", "projectmodels.h",
"projectnodes.cpp", "projectnodes.h",
"projectnodeshelper.h",
"projectpanelfactory.cpp", "projectpanelfactory.h",
"projectsettingswidget.cpp", "projectsettingswidget.h",
"projecttree.cpp",

View File

@@ -310,10 +310,11 @@ ProjectExplorerSettingsWidget::ProjectExplorerSettingsWidget()
{
m_reaperTimeoutSpinBox = new QSpinBox;
m_reaperTimeoutSpinBox->setMinimum(1);
//: Suffix for "seconds"
m_reaperTimeoutSpinBox->setSuffix(Tr::tr("s"));
m_reaperTimeoutSpinBox->setToolTip(
Tr::tr("The amount of seconds to wait between a \"soft kill\" and a \"hard kill\" of a "
"running application"));
"running application."));
m_currentDirectoryRadioButton = new QRadioButton(Tr::tr("Current directory"));
m_directoryRadioButton = new QRadioButton(Tr::tr("Directory"));

View File

@@ -284,8 +284,9 @@ bool FlatModel::setData(const QModelIndex &index, const QVariant &value, int rol
QTC_ASSERT(node, return false);
std::vector<std::tuple<Node *, FilePath, FilePath>> toRename;
const Utils::FilePath orgFilePath = node->filePath();
const Utils::FilePath newFilePath = orgFilePath.parentDir().pathAppended(value.toString());
const FilePath orgFilePath = node->filePath();
const FilePath newFilePath = orgFilePath.parentDir().pathAppended(value.toString());
const FilePath valuePath = FilePath::fromString(value.toString());
const QFileInfo orgFileInfo = orgFilePath.toFileInfo();
toRename.emplace_back(std::make_tuple(node, orgFilePath, newFilePath));
@@ -309,12 +310,13 @@ bool FlatModel::setData(const QModelIndex &index, const QVariant &value, int rol
case QMessageBox::Yes:
for (Node * const n : candidateNodes) {
QString targetFilePath = orgFileInfo.absolutePath() + '/'
+ newFilePath.completeBaseName();
+ valuePath.parentDir().path() + '/'
+ valuePath.completeBaseName();
const QString suffix = n->filePath().suffix();
if (!suffix.isEmpty())
targetFilePath.append('.').append(suffix);
toRename.emplace_back(std::make_tuple(n, n->filePath(),
FilePath::fromString(targetFilePath)));
FilePath::fromString(targetFilePath).cleanPath()));
}
break;
case QMessageBox::Cancel:

View File

@@ -1,141 +0,0 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "projectnodes.h"
#include <coreplugin/iversioncontrol.h>
#include <coreplugin/vcsmanager.h>
#include <solutions/tasking/tasktreerunner.h>
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/filepath.h>
#include <utils/hostosinfo.h>
#include <QPromise>
namespace ProjectExplorer {
namespace Internal {
struct DirectoryScanResult
{
QList<FileNode *> nodes;
Utils::FilePaths subDirectories;
};
static DirectoryScanResult scanForFiles(
const QFuture<void> &future,
const Utils::FilePath &directory,
QDir::Filters filter,
const std::function<FileNode *(const Utils::FilePath &)> &factory,
const QList<Core::IVersionControl *> &versionControls)
{
DirectoryScanResult result;
const Utils::FilePaths entries = directory.dirEntries(filter);
for (const Utils::FilePath &entry : entries) {
if (future.isCanceled())
return result;
if (Utils::anyOf(versionControls, [entry](const Core::IVersionControl *vc) {
return vc->isVcsFileOrDirectory(entry);
})) {
continue;
}
if (entry.isDir())
result.subDirectories.append(entry);
else if (FileNode *node = factory(entry))
result.nodes.append(node);
}
return result;
}
template<typename Result>
QList<FileNode *> scanForFilesRecursively(
QPromise<Result> &promise,
int progressRange,
const Utils::FilePath &directory,
QDir::Filters filter,
const std::function<FileNode *(const Utils::FilePath &)> &factory,
const QList<Core::IVersionControl *> &versionControls)
{
const QFuture<void> future(promise.future());
QSet<Utils::FilePath> visited;
const DirectoryScanResult result
= scanForFiles(future, directory, filter, factory, versionControls);
QList<FileNode *> fileNodes = result.nodes;
const int progressIncrement = int(
progressRange / static_cast<double>(fileNodes.count() + result.subDirectories.count()));
promise.setProgressValue(int(fileNodes.count() * progressIncrement));
QList<QPair<Utils::FilePath, int>> subDirectories;
auto addSubDirectories = [&](const Utils::FilePaths &subdirs, int progressIncrement) {
for (const Utils::FilePath &subdir : subdirs) {
if (Utils::insert(visited, subdir.canonicalPath()))
subDirectories.append(qMakePair(subdir, progressIncrement));
else
promise.setProgressValue(promise.future().progressValue() + progressIncrement);
}
};
addSubDirectories(result.subDirectories, progressIncrement);
while (!subDirectories.isEmpty()) {
using namespace Tasking;
const LoopList iterator(subDirectories);
subDirectories.clear();
auto onSetup = [&, iterator](Utils::Async<DirectoryScanResult> &task) {
task.setConcurrentCallData(
scanForFiles, future, iterator->first, filter, factory, versionControls);
};
auto onDone = [&, iterator](const Utils::Async<DirectoryScanResult> &task) {
const int progressRange = iterator->second;
const DirectoryScanResult result = task.result();
fileNodes.append(result.nodes);
const qsizetype subDirCount = result.subDirectories.count();
if (subDirCount == 0) {
promise.setProgressValue(promise.future().progressValue() + progressRange);
} else {
const qsizetype fileCount = result.nodes.count();
const int increment = int(
progressRange / static_cast<double>(fileCount + subDirCount));
promise.setProgressValue(
promise.future().progressValue() + increment * fileCount);
addSubDirectories(result.subDirectories, increment);
}
};
const Group group{
Utils::HostOsInfo::isLinuxHost() ? parallelLimit(2) : parallelIdealThreadCountLimit,
iterator,
Utils::AsyncTask<DirectoryScanResult>(onSetup, onDone)
};
TaskTree::runBlocking(group);
}
return fileNodes;
}
} // namespace Internal
template<typename Result>
QList<FileNode *> scanForFiles(
QPromise<Result> &promise,
const Utils::FilePath &directory,
QDir::Filters filter,
const std::function<FileNode *(const Utils::FilePath &)> &factory)
{
promise.setProgressRange(0, 1000000);
return Internal::scanForFilesRecursively(promise,
1000000,
directory,
filter,
factory,
Core::VcsManager::versionControls());
}
} // namespace ProjectExplorer

View File

@@ -3,15 +3,17 @@
#include "treescanner.h"
#include "projectnodeshelper.h"
#include "projecttree.h"
#include <coreplugin/iversioncontrol.h>
#include <coreplugin/vcsmanager.h>
#include <utils/async.h>
#include <utils/qtcassert.h>
#include <solutions/tasking/tasktreerunner.h>
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/hostosinfo.h>
#include <utils/qtcassert.h>
#include <memory>
@@ -146,14 +148,113 @@ static std::unique_ptr<FolderNode> createFolderNode(const Utils::FilePath &direc
return fileSystemNode;
}
struct DirectoryScanResult
{
QList<FileNode *> nodes;
Utils::FilePaths subDirectories;
};
static DirectoryScanResult scanForFilesImpl(
const QFuture<void> &future,
const Utils::FilePath &directory,
QDir::Filters filter,
const std::function<FileNode *(const Utils::FilePath &)> &factory,
const QList<Core::IVersionControl *> &versionControls)
{
DirectoryScanResult result;
const Utils::FilePaths entries = directory.dirEntries(filter);
for (const Utils::FilePath &entry : entries) {
if (future.isCanceled())
return result;
if (Utils::anyOf(versionControls, [entry](const Core::IVersionControl *vc) {
return vc->isVcsFileOrDirectory(entry);
})) {
continue;
}
if (entry.isDir())
result.subDirectories.append(entry);
else if (FileNode *node = factory(entry))
result.nodes.append(node);
}
return result;
}
static QList<FileNode *> scanForFilesHelper(
TreeScanner::Promise &promise,
const Utils::FilePath &directory,
QDir::Filters filter,
const std::function<FileNode *(const Utils::FilePath &)> &factory)
{
const QFuture<void> future(promise.future());
const int progressRange = 1000000;
const QList<Core::IVersionControl *> &versionControls = Core::VcsManager::versionControls();
promise.setProgressRange(0, progressRange);
QSet<Utils::FilePath> visited;
const DirectoryScanResult result = scanForFilesImpl(future, directory, filter, factory, versionControls);
QList<FileNode *> fileNodes = result.nodes;
const int progressIncrement = int(
progressRange / static_cast<double>(fileNodes.count() + result.subDirectories.count()));
promise.setProgressValue(int(fileNodes.count() * progressIncrement));
QList<QPair<Utils::FilePath, int>> subDirectories;
auto addSubDirectories = [&](const Utils::FilePaths &subdirs, int progressIncrement) {
for (const Utils::FilePath &subdir : subdirs) {
if (Utils::insert(visited, subdir.canonicalPath()))
subDirectories.append(qMakePair(subdir, progressIncrement));
else
promise.setProgressValue(future.progressValue() + progressIncrement);
}
};
addSubDirectories(result.subDirectories, progressIncrement);
while (!subDirectories.isEmpty()) {
using namespace Tasking;
const LoopList iterator(subDirectories);
subDirectories.clear();
auto onSetup = [&, iterator](Utils::Async<DirectoryScanResult> &task) {
task.setConcurrentCallData(
scanForFilesImpl, future, iterator->first, filter, factory, versionControls);
};
auto onDone = [&, iterator](const Utils::Async<DirectoryScanResult> &task) {
const int progressRange = iterator->second;
const DirectoryScanResult result = task.result();
fileNodes.append(result.nodes);
const qsizetype subDirCount = result.subDirectories.count();
if (subDirCount == 0) {
promise.setProgressValue(future.progressValue() + progressRange);
} else {
const qsizetype fileCount = result.nodes.count();
const int increment = int(
progressRange / static_cast<double>(fileCount + subDirCount));
promise.setProgressValue(future.progressValue() + increment * fileCount);
addSubDirectories(result.subDirectories, increment);
}
};
const Group group{
Utils::HostOsInfo::isLinuxHost() ? parallelLimit(2) : parallelIdealThreadCountLimit,
iterator,
Utils::AsyncTask<DirectoryScanResult>(onSetup, onDone)
};
TaskTree::runBlocking(group);
}
return fileNodes;
}
void TreeScanner::scanForFiles(
Promise &promise,
const Utils::FilePath &directory,
const FileFilter &filter,
const QDir::Filters &dirFilter,
QDir::Filters dirFilter,
const FileTypeFactory &factory)
{
QList<FileNode *> nodes = ProjectExplorer::scanForFiles(
QList<FileNode *> nodes = scanForFilesHelper(
promise, directory, dirFilter, [&filter, &factory](const Utils::FilePath &fn) -> FileNode * {
const Utils::MimeType mimeType = Utils::mimeTypesForFileName(fn.path()).value(0);

View File

@@ -75,7 +75,7 @@ private:
static void scanForFiles(Promise &fi,
const Utils::FilePath &directory,
const FileFilter &filter,
const QDir::Filters &dirFilter,
QDir::Filters dirFilter,
const FileTypeFactory &factory);
private:

View File

@@ -66,21 +66,16 @@ void PipInstallTask::run()
emit finished(false);
return;
}
QString operation = Tr::tr("Install");
QString operant;
QStringList arguments = {"-m", "pip", "install"};
if (!m_requirementsFile.isEmpty()) {
operant = Tr::tr("Requirements");
arguments << "-r" << m_requirementsFile.toString();
} else {
for (const PipPackage &package : m_packages) {
QString pipPackage = package.packageName;
if (!package.version.isEmpty())
pipPackage += "==" + package.version;
arguments << pipPackage;
}
operant = m_packages.count() == 1 ? m_packages.first().displayName : Tr::tr("Packages");
}
if (!m_targetPath.isEmpty()) {
@@ -90,17 +85,27 @@ void PipInstallTask::run()
arguments << "--user"; // add --user to global pythons, but skip it for venv pythons
}
if (m_upgrade) {
if (m_upgrade)
arguments << "--upgrade";
operation = Tr::tr("Update");
QString operation;
if (!m_requirementsFile.isEmpty()) {
operation = m_upgrade ? Tr::tr("Update Requirements") : Tr::tr("Install Requirements");
} else if (m_packages.count() == 1) {
//: %1 = package name
operation = m_upgrade ? Tr::tr("Update %1")
//: %1 = package name
: Tr::tr("Install %1");
operation = operation.arg(m_packages.first().displayName);
} else {
operation = m_upgrade ? Tr::tr("Update Packages") : Tr::tr("Install Packages");
}
m_process.setCommand({m_python, arguments});
m_process.setTerminalMode(m_silent ? TerminalMode::Off : TerminalMode::Run);
m_process.start();
const QString taskTitle = Tr::tr("%1 %2").arg(operation).arg(operant);
Core::ProgressManager::addTask(m_future.future(), taskTitle, pipInstallTaskId);
Core::ProgressManager::addTask(m_future.future(), operation, pipInstallTaskId);
Core::MessageManager::writeSilently(
Tr::tr("Running \"%1\" to install %2.")
.arg(m_process.commandLine().toUserOutput(), packagesDisplayName()));

View File

@@ -728,9 +728,10 @@ void QmlJSEditorDocumentPrivate::setSourcesWithCapabilities(
setSemanticWarningSource(QmllsStatus::Source::Qmlls);
else
setSemanticWarningSource(QmllsStatus::Source::EmbeddedCodeModel);
if (cap.semanticTokensProvider())
setSemanticHighlightSource(QmllsStatus::Source::Qmlls);
else
// TODO: uncomment when qmlls semantic tokens reach a stable state
// if (cap.semanticTokensProvider())
// setSemanticHighlightSource(QmllsStatus::Source::Qmlls);
// else
setSemanticHighlightSource(QmllsStatus::Source::EmbeddedCodeModel);
}

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