Merge remote-tracking branch 'origin/13.0'

Change-Id: If752e3383b35873b696e8beca27d8838a4096c8a
This commit is contained in:
Eike Ziller
2024-02-27 09:04:51 +01:00
176 changed files with 3444 additions and 2000 deletions

View File

@@ -24,34 +24,34 @@ function(create_python_xy PythonExe PythonZipFilePath)
cgi.py nntplib.py tarfile.py
cgitb.py nturl2path.py telnetlib.py
chunk.py numbers.py tempfile.py
cmd.py optparse.py textwrap.py
code.py pathlib.py this.py
codeop.py pdb.py timeit.py
colorsys.py pickle.py trace.py
compileall.py pickletools.py tracemalloc.py
configparser.py pipes.py tty.py
contextvars.py plistlib.py turtle.py
cProfile.py poplib.py typing.py
crypt.py pprint.py uu.py
csv.py profile.py uuid.py
dataclasses.py pstats.py wave.py
datetime.py pty.py webbrowser.py
decimal.py pyclbr.py xdrlib.py
difflib.py py_compile.py zipapp.py
doctest.py queue.py zipfile.py
dummy_threading.py quopri.py zipimport.py
filecmp.py random.py _compat_pickle.py
fileinput.py rlcompleter.py _compression.py
formatter.py runpy.py _dummy_thread.py
fractions.py sched.py _markupbase.py
ftplib.py secrets.py _osx_support.py
getopt.py selectors.py _pydecimal.py
getpass.py shelve.py _pyio.py
gettext.py shlex.py _py_abc.py
gzip.py shutil.py _strptime.py
hashlib.py smtpd.py _threading_local.py
hmac.py smtplib.py __future__.py
imaplib.py sndhdr.py __phello__.foo.py
cmd.py optparse.py this.py
code.py pathlib.py timeit.py
codeop.py pdb.py trace.py
colorsys.py pickle.py tracemalloc.py
compileall.py pickletools.py tty.py
configparser.py pipes.py turtle.py
contextvars.py plistlib.py typing.py
cProfile.py poplib.py uu.py
crypt.py pprint.py uuid.py
csv.py profile.py wave.py
dataclasses.py pstats.py webbrowser.py
datetime.py pty.py xdrlib.py
decimal.py pyclbr.py zipapp.py
difflib.py py_compile.py zipfile.py
doctest.py queue.py zipimport.py
dummy_threading.py quopri.py _compat_pickle.py
filecmp.py random.py _compression.py
fileinput.py rlcompleter.py _dummy_thread.py
formatter.py runpy.py _markupbase.py
fractions.py sched.py _osx_support.py
ftplib.py secrets.py _pydecimal.py
getopt.py selectors.py _pyio.py
getpass.py shelve.py _py_abc.py
gettext.py shlex.py _strptime.py
gzip.py shutil.py _threading_local.py
hashlib.py smtpd.py __future__.py
hmac.py smtplib.py __phello__.foo.py
imaplib.py sndhdr.py
)
list(FIND python_lib_files "${python_lib_dir}/${not_needed}" found_not_needed)
if (NOT found_not_needed STREQUAL "-1")

View File

@@ -169,12 +169,13 @@ Projects
* Added `Generate Kit` to the Python interpreter preferences for generating a
Python kit with this interpreter
([Documentation](https://doc-snapshots.qt.io/qtcreator-13.0/creator-python-development.html#create-kits-for-python-interpreters))
* Added the target setup page when loading unconfigured Python projects
* Added the `Kit Selection` page for creating and opening Python projects
* Added a `requirements.txt` file to the application wizard
* Fixed that the same Python interpreter could be auto-detected multiple times
under different names
([Documentation](https://doc-snapshots.qt.io/qtcreator-13.0/creator-python-development.html))
Debugging
---------

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -275,6 +275,7 @@
The build errors and warnings are parsed and displayed in \l Issues.
\sa {Activate kits for a project}, {Configure projects for building},
{Configure projects for running}, {Open projects}, {CMake}
\sa {Activate kits for a project}, {Add custom output parsers},
{Configure projects for building}, {Configure projects for running},
{Open projects}, {CMake}
*/

View File

@@ -14,18 +14,6 @@
\title Debugging
A debugger lets you see what happens \e inside an application while it runs
or when it crashes. A debugger can do the following to help you find errors
in the application:
\list
\li Start the application with parameters that specify its behavior.
\li Stop the application when conditions are met.
\li Examine what happens when the application stops.
\li Make changes in the application when you fix an error and continue
to find the next one.
\endlist
The \QC debugger plugin acts as an interface between the \QC
core and external native debuggers that you can use to:

View File

@@ -8,20 +8,6 @@
\title Refactoring
\e {Code refactoring} is the process of improving and simplifying code
without modifying the existing functionality of an application. You
can easily find and rename symbols and apply predefined actions to
refactor code.
Refactor code to:
\list
\li Improve internal quality of your application
\li Improve performance and extensibility
\li Improve code readability and maintainability
\li Simplify code structure
\endlist
To quickly and conveniently apply actions to refactor your
code, \l{Apply quick fixes}{select quick fixes in a context menu}.

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2023 The Qt Company Ltd.
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
@@ -9,9 +9,9 @@
\title Edit Markdown files
Open \l{https://www.markdownguide.org/basic-syntax/}{Markdown} (.md) files
or select \uicontrol File > \uicontrol {New File} > \uicontrol {General} >
\uicontrol {Markdown File} to create a new file.
Open \l{https://www.markdownguide.org/basic-syntax/}{Markdown} (.md) files,
or go to \uicontrol File > \uicontrol {New File} and select
\uicontrol {General} > \uicontrol {Markdown File} to create a new file.
\image qtcreator-markdown-editor.webp {Markdown file in Preview and Editor views}
@@ -26,4 +26,25 @@
Use the buttons on the editor toolbar to format text as italic (i), bold (b),
or inline code (`), and to create links to web sites
(\inlineimage icons/linkicon.png).
\section1 Move to a line and column
The line and column indicator shows information about the current cursor
position. Select it to activate the locator, and enter a line and column
number to move there.
\section1 Follow links to web sites
To follow a link to a web site in the editor:
\list 1
\li Place the cursor on the link.
\li Then, do one of the following:
\list
\li Press \key {Ctrl+Click} (or \key {Cmd+Click} on \macos).
\li Press \key F2.
\li Go to \uicontrol {Follow Symbol Under Cursor} in the context
menu.
\endlist
\endlist
*/

View File

@@ -25,11 +25,11 @@
\li \inlineimage front-ui.png
\li \inlineimage front-advanced.png
\row
\li \b {\l{IDE Overview}}
\li \b {\l{Overview}}
If you have not used an integrated development environment (IDE)
before, or want to know what kind of IDE \QC is, go to
\l{IDE Overview}.
\l{Overview}.
\li \b {\l{User Interface}}
If you have not used \QC before, and want to become familiar

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2023 The Qt Company Ltd.
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
// **********************************************************************
@@ -12,147 +12,243 @@
\page creator-overview.html
\nextpage creator-quick-tour.html
\title IDE Overview
\title Overview
\QC is an integrated development environment (IDE) that has tools for
designing and developing applications with the Qt application framework.
With Qt you can develop applications and user interfaces once and deploy
them to several desktop, embedded, and mobile operating systems or
web browsers (experimental). \QC has the tools for accomplishing your tasks
\QC is a cross-platform, complete integrated development environment
(IDE) that you can use to create applications for desktop, embedded,
and mobile operating systems, or web browsers.
With Qt, you can develop applications and user interfaces once and deploy
them to many platforms. \QC has the tools for accomplishing your tasks
throughout the whole application development life-cycle, from creating a
project to deploying the application to the target platforms.
project, designing a UI, and writing code to building applications and
deploying them to the target platforms for running and debugging.
\table
\row
\li \inlineimage front-projects.png
\li \inlineimage front-ui.png
\li \inlineimage front-coding.png
\row
\li \b {Managing Projects}
\image qt-app-dev-flow.webp {Application development life-cycle}
\caption Application development life-cycle
To be able to build and run applications, \QC needs the same
information as a compiler would need. It stores the information
in the project settings.
\section1 Projects
You can share projects with other designers and developers across
different development platforms with a common tool for design,
development, and debugging.
First, you need a \e project. \QC relies on a separate build system, such as
CMake, qmake, or Qbs for building the project. From the build system, \QC
gets most of the information it needs to offer services for writing, editing,
and navigating source code, as well as to deploy and run applications. It
stores additional information in the project settings.
\list
\li \l{Creating Projects}
Share projects with other designers and developers across different
development platforms with a common tool for design, development, and
debugging.
To set up a project, you first have to decide what kind
of an application you want to develop: do you want a user
interface based on \l{User Interfaces}
{Qt Quick or Qt Widgets}. Second, you have to choose the
programming language to implement the application logic:
C++ or Python.
\li \l{Version Control Systems}
\list
\li \l{Creating Projects}
The recommended way to set up a project is to use a
version control system. Store and edit only project
source files and configuration files. Do not store
generated files.
\li \l{Configuring Projects}
To set up a project, you first have to decide what kind
of an application you want to develop: do you want a user
interface based on \l{User Interfaces}
{Qt Quick or Qt Widgets}. Second, you have to choose the
programming language to implement the application logic:
C++ or Python.
\li \l{Version Control Systems}
Installation programs and project wizards create default
configurations for \QC and your projects. You can change
the configurations in the \uicontrol Projects mode.
\endlist
For more information, see \l{Manage Projects}
{How To: Manage Projects}.
\li \b {Designing User Interfaces}
The recommended way to set up a project is to use a
version control system. Store and edit only project
source files and configuration files. Do not store
generated files.
\li \l{Configuring Projects}
To create intuitive, modern-looking, fluid user interfaces, you
can use \l{Qt Quick} and \l{Qt Design Studio Manual}{\QDS}:
Installation programs and project wizards create default
configurations for \QC and your projects. Change the
configurations in the \uicontrol Projects mode.
\endlist
\list
\li \l {\QMLD}
For more information, see \l{Manage Projects}{How To: Manage Projects}.
Or, you can enable the \QMLD plugin to visually edit
\l{UI Files}{UI files} (.ui.qml).
\li \l {Converting UI Projects to Applications}
\section1 User Interfaces
Qt Quick UI Prototype projects (.qmlproject) are useful
for creating user interfaces. To use them for application
development, you have to convert them to Qt Quick
Application projects that have project configuration
files (CMakeLists.txt or .pro), .cpp, and .qrc files.
\li \l {UI Files}
\image heartgame-start.webp {Heart Rate Game}
If you switch between \QC and \QDS or cooperate with
designers on a project, you might encounter .ui.qml files.
They are intended to be edited in \QDS only, so you need
to be careful not to break the code. To visually edit the
files in \QC, enable the \QMLD plugin.
\li \l{Using QML Modules with Plugins}
To create intuitive, modern-looking, fluid user interfaces, use \l{Qt Quick}
and \l{Qt Design Studio Manual}{\QDS}:
You can load C++ plugins for QML to simulate data.
\endlist
\list
\li \l {\QMLD}
If you need a traditional user interface that has a clear
structure and enforces a platform look and feel, use
\l{Qt Widgets} and the integrated \l{\QD}.
Or, enable the \QMLD plugin to visually edit \l{UI Files}{UI files}
(.ui.qml).
\li \l {Converting UI Projects to Applications}
For more information, see
\l{Design UIs}{How To: Design UIs}.
\li \b {\l{Coding}}
Qt Quick UI Prototype projects (.qmlproject) are useful
for creating user interfaces. To use them for application
development, you have to convert them to Qt Quick
Application projects that have project configuration
files (CMakeLists.txt or .pro), .cpp, and .qrc files.
\li \l {UI Files}
As an IDE, \QC differs from a text editor in that it knows how
to build and run applications. It understands the C++ and QML
languages as code, not just as plain text. Therefore, it can
offer useful features, such as semantic highlighting,
checking code syntax, code completion, and refactoring actions.
\QC supports some of these services also for other programming
languages, such as Python, for which a \e {language server} is
available that provides information about the code to IDEs.
If you switch between \QC and \QDS or cooperate with
designers on a project, you might encounter .ui.qml files.
They are intended to be edited in \QDS only, so you need
to be careful not to break the code. To visually edit the
files in \QC, enable the \QMLD plugin.
\li \l{Using QML Modules with Plugins}
For more information, see \l{Edit Code}{How To: Edit Code}.
\row
\li \inlineimage front-preview.png
\li \inlineimage front-testing.png
\li \inlineimage front-publishing.png
\row
\li \b {\l{Building and Running}}
Load C++ plugins for QML to simulate data.
\endlist
\QC integrates cross-platform systems for build
automation: qmake, Qbs, CMake, and Autotools. In addition, you
can import
projects as \e {generic projects} and fully control the steps
and commands used to build the project.
If you need a traditional user interface that has a clear structure and
enforces a platform look and feel, use \l{Qt Widgets} and the integrated
\l{\QD}.
You can build applications for, deploy them to, and run them on
the desktop environment or a \l{glossary-device}{device}.
\l{glossary-buildandrun-kit}{Kits}, build, run, and deployment
settings allow you to quickly switch between different setups and
target platforms.
For more information, see \l{Design UIs}{How To: Design UIs} and
\l{UI Design}.
For more information, see \l{Build and Run}
{How To: Build and Run}.
\li \b {\l{Testing}}
\section1 Code
\QC integrates several external native debuggers that you can use
to inspect the state of your application while debugging.
Writing, editing, and navigating in source code are core tasks in application
development. Therefore, the code editor is one of the key components of \QC.
Use the code editor in the \l {Edit Mode}{Edit mode}.
Devices have limited memory and CPU power, so you should use them
carefully. \QC integrates code analysis tools for detecting
memory leaks, profiling function execution, analyzing CPU use,
and eliminating unnecessary complexity of code. Other tools
provide code coverage and visualize trace events.
As an IDE, \QC differs from a text editor in that it knows how to build and
run applications. It understands the C++ and QML languages as code, not just
as plain text. Therefore, it can offer useful features, such as semantic
highlighting, checking code syntax, code completion, and refactoring actions.
\QC integrates several testing frameworks for unit testing
applications and libraries. You can use \QC to create, build,
and run autotests.
\QC supports some of these services also for other programming languages,
such as Python, for which a \e {language server} is available that provides
information about the code to IDEs.
For more information, see \l{Testing}.
\li \b {Publishing}
\section2 Find
\QC enables you to create installation packages for mobile
devices that you can publish to application stores
and other channels. You must make sure that the package contents
meet the requirements for publishing on the channel.
Use the incremental and advanced search to search in currently open projects
or files on the file system or use the locator to browse through projects,
files, classes, functions, documentation, and file systems.
For more information, see \l{Publishing to Google Play}.
\endtable
\section2 Refactor
\e {Code refactoring} is the process of improving and simplifying code
without modifying the existing functionality of an application. Find
and rename symbols and apply predefined actions to refactor code.
Refactor code to:
\list
\li Improve internal quality of your application
\li Improve performance and extensibility
\li Improve code readability and maintainability
\li Simplify code structure
\endlist
\section2 Configure the Editor
Configure the text editor to suit your specific needs. Change the fonts,
colors, highlighting, and indentation.
If you are used to the Vim editor, run the main editor in the
\l {FakeVim Modes and Commands}{FakeVim mode}.
For more information, see \l{Edit Code}{How To: Edit Code} and \l{Editors}.
\section1 Build, Deploy, and Run
Run and deploy Qt applications that you build for different target
platforms or with different compilers, debuggers, or Qt versions.
\l{glossary-buildandrun-kit}{Kits} define the tools, \l{glossary-device}
{device} type and other settings to use when building and running your
project.
\QC integrates cross-platform systems for build automation: CMake,
qmake, Qbs, and Autotools. In addition, you can import projects as
\e {generic projects} and fully control the steps and commands to
build the project.
Build applications for, deploy them to, and run them on the desktop
environment or a device. With kits, as well as build, run, and deployment
configurations, you can quickly switch between different setups and
target platforms.
For more information, see \l{Build and Run}{How To: Build and Run},
\l{Build Systems}, \l{Build Configurations}, and \l{Run Configurations}.
\section2 On Devices
When you install tool chains for device types as part of a Qt distribution,
the build and run configurations for the devices might be set up
automatically. However, you might need to install and configure some
additional software on the devices to be able to connect to them
from the computer.
Deployment configurations handle the packaging and copying of the necessary
files to a location you want to run the executable at, such as the file
system of a device.
For more information, see \l{Connecting Devices} and \l{Deploying to Devices}.
\section2 Preview QML
Use the QML live preview to preview a QML file or an entire Qt Quick
application on the desktop, as well as on Android and embedded Linux
devices. The changes you make to the UI are instantly visible to you
in the preview.
For more information, see \l{Validating with Target Hardware}.
\section1 Debug
A debugger lets you see what happens \e inside an application while it runs
or when it crashes. A debugger can do the following to help you find errors
in the application:
\list
\li Start the application with parameters that specify its behavior.
\li Stop the application when conditions are met.
\li Examine what happens when the application stops.
\li Make changes in the application when you fix an error and continue
to find the next one.
\endlist
\QC integrates several external native debuggers for inspecting the state of
your application while debugging. The debugger plugin automatically selects
a suitable native debugger for each kit from the ones it finds on the
computer. Edit the kits to override this choice.
Connect devices to your computer to debug processes running on the devices.
For more information, see \l{Debugging}.
\section1 Analyze
Devices have limited memory and CPU power, so you should use them carefully.
\QC integrates code analysis tools for detecting memory leaks, profiling
function execution, analyzing CPU use, and eliminating unnecessary complexity
of code. Other tools provide code coverage and visualize trace events.
Install and configure the tools on your system to use them from \QC.
However, the QML Profiler is installed as part of \QC for profiling
Qt Quick applications.
For more information, see \l{Analyzing Code}.
\section1 Autotest
Create, build and run Qt tests, Qt Quick tests, Google tests, and Boost tests
to unit test applications and libraries.
Map AUTs (Application Under Test) to \QC and run Squish test suites
and cases from it.
For more information, see \l{Running Autotests} and \l{Using Squish}.
\section1 Publish
Create installation packages for mobile devices that you publish to
application stores and other channels. You must make sure that the
package contents meet the requirements for publishing on the channel.
For more information, see \l{Publishing to Google Play}.
\section1 Qt Tools
\QC is one of many Qt tools for designing and developing applications.
\image qt-tools.webp {Tools for Qt application development}
\caption Tools for Qt application development
*/

View File

@@ -20,7 +20,7 @@
\li \l{Debugging}
If you install \QC as part of \QSDK, the GNU Symbolic Debugger
If you install \QC with \QOI, the GNU Symbolic Debugger
is installed automatically and you should be ready to start
debugging after you create a new project. However, you can
change the setup to use debugging tools for Windows, for

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2023 The Qt Company Ltd.
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
// **********************************************************************
@@ -20,6 +20,8 @@
has the same XML structure as a \e {.user} file, but only has the
settings to share.
\note Use \l{CMake Presets} to share CMake project settings.
\section1 Create a shared settings file
The easiest way to create a \e {.shared} file is to copy settings from the
@@ -70,5 +72,5 @@
a permanent sticky setting that was created just because you wanted to try
something out.
\sa {Configuring Projects}
\sa {Configuring Projects}, {CMake Presets}
*/

View File

@@ -19,9 +19,8 @@
\list
\li \l{Set up PySide6}
\li \l{Create Qt for Python applications}
\li \l{Select the Python interpreter}
\li \l{Create a virtual environment}
\li \l{Create kits for Python interpreters}
\li \l{Select the Python version}
\li \l{Create kits for Python}
\li \l{Use Python interactive shell}
\li \l{Configure Python language servers}
\li \l{Run Python applications}
@@ -48,9 +47,10 @@
\section1 Create Qt for Python applications
You can use wizards to create Qt for Python application projects. The wizards
Use wizards to create Qt for Python application projects. The wizards
generate a project file, \c {.pyproject}, that lists the files in the Python
project. They also generate a \c {.py} file that has some boilerplate code.
project. They also generate a \c {.py} file that has some boilerplate code
and \c {reguirements.txt} that stores the PySide version of the generated code.
In addition, the widget-based UI wizard creates a \c {.ui} file that has a
\QD form, and the Qt Quick Application wizard creates a \c {.qml} file that
imports Qt Quick controls.
@@ -72,50 +72,31 @@
use \c {.pyqtc} files, but we recommend that you choose \c{.pyproject} files
for new projects.
\section1 Select the Python interpreter
\section1 Select the Python version
You select the initial Python interpreter when you use the Qt for Python
Application wizard templates to create Python projects.
The \l{kits-tab}{kits} you select for the project in \uicontrol Projects >
\uicontrol {Build & Run} set the Python version to use.
\image qtcreator-new-qt-for-python-app-widgets-project-details.webp {Define Project Details dialog}
The \l {Edit Mode}{Edit mode} toolbar shows the current Python version.
You can see the current Python interpreter on the \uicontrol Edit mode
toolbar.
\image qtcreator-python-interpreter-edit-mode.webp {Python version on the Edit mode toolbar}
\image qtcreator-python-interpreter-edit-mode.webp {Python interpreter on the Edit mode toolbar}
To use another Python version, activate another kit for the project.
You can change the interpreter to use for a particular project in
\uicontrol Projects > \uicontrol Run > \uicontrol Interpreter.
\section1 Create kits for Python
\image qtcreator-python-run-settings.png {Python run settings}
To see the available interpreters and choose another interpreter, select the
current interpreter, and then select \uicontrol {Manage Python Interpreters}.
Or, select \preferences > \uicontrol Python > \uicontrol Interpreters.
\QC automatically adds all Python versions it can find to the list of
Python versions in \preferences > \uicontrol Python > \uicontrol Interpreters.
It generates kits for the global Python versions that are not inside a
virtual environment.
\image qtcreator-python-interpreters.webp {Python Interpreters in Preferences}
You can add and remove interpreters and clean up references to interpreters
that you uninstalled, but that still appear in the list.
You can add and remove Python versions and clean up references to Python
versions that you uninstalled, but that still appear in the list.
To use the selected Python interpreter by default, select
\uicontrol {Make Default}.
\section1 Create a virtual environment
To use a clean \l{https://docs.python.org/3/library/venv.html}{Python}
virtual environment (\c venv) that is independent of your global Python
installation for a Qt for Python project, select the
\uicontrol {Create new virtual environment} check box in the project wizard.
Set the directory where to create the environment in
\uicontrol {Path to virtual environment}.
\section1 Create kits for Python interpreters
\QC automatically adds all Python interpreters it can find to the list of
interpreters in \preferences > \uicontrol Python > \uicontrol Interpreters.
It generates \l{kits-tab}{kits} for the global Python interpreters that are
not inside a virtual environment.
To use the selected Python version when opening \c {.py} files that don't
belong to a project, select \uicontrol {Make Default}.
To use a virtual environment as a kit, select it in \uicontrol Interpreters,
and then select \uicontrol {Generate Kit}.
@@ -133,5 +114,6 @@
the file, select \uicontrol {REPL Import *}.
\sa {Creating a Qt for Python Application with Qt Widgets},
{Creating a Qt for Python Application with Qt Quick}
{Creating a Qt for Python Application with Qt Quick},
{Activate kits for a project}
*/

View File

@@ -35,7 +35,7 @@
you select for a kit in \uicontrol Projects > \uicontrol {Build & Run} >
\uicontrol Run > \uicontrol {Run Settings}.
\image qtcreator-python-run-settings.png {Python run settings}
\image qtcreator-python-run-settings.webp {Python run settings}
The following table summarizes the settings for running Qt for Python
applications.
@@ -45,7 +45,7 @@
\li Setting
\li Value
\row
\li \uicontrol Interpreter
\li \uicontrol Python
\li Path to the Python executable.
\row
\li \uicontrol {Buffered output}

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2023 The Qt Company Ltd.
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
@@ -28,11 +28,10 @@
class:
\list 1
\li Select \uicontrol File > \uicontrol {New Project} >
\uicontrol {Application (Qt for Python)} > \uicontrol {Empty Window}
> \uicontrol Choose.
The \uicontrol {Project Location} dialog opens.
\li Go to \uicontrol File > \uicontrol {New Project}.
\li Select \uicontrol {Application (Qt for Python)} >
\uicontrol {Empty Window} > \uicontrol Choose to open the
\uicontrol {Project Location} dialog.
\image qtcreator-new-qt-for-python-app-widgets-project-location.webp {Project Location dialog}
\li In \uicontrol {Name}, enter the project name. For example,
\e {hello_world}.
@@ -49,16 +48,14 @@
\li In \uicontrol {Project file}, enter a name for the project file.
\li Select \uicontrol{Next} or \uicontrol Continue to open the
\uicontrol {Define Project Details} dialog.
\image qtcreator-new-qt-for-python-app-widgets-project-details.webp {Define Project Details dialog}
\image qtcreator-new-qt-for-python-app-project-details.webp {Define Project Details dialog}
\li In \uicontrol {PySide version}, select the PySide version of the
generated code.
\li In \uicontrol {Interpreter}, select the Python interpreter to use for
the project.
\li Select the \uicontrol {Create new virtual environment} check box to
use a clean \l{https://docs.python.org/3/library/venv.html}{Python}
environment that is independent of your global Python installation.
\li In \uicontrol {Path to virtual environment}, specify the directory
where to create the environment.
\li Select \uicontrol{Next} or \uicontrol Continue to open the
\uicontrol {Kit Selection} dialog.
\image qtcreator-new-project-qt-for-python-kit-selection.webp {Selecting a kit for a Python project}
\li Select Qt for Python kits for building, deploying, and running the
project.
\li Select \uicontrol{Next} or \uicontrol Continue.
\li Review the project settings, and select \uicontrol {Finish} (on
Windows and Linux) or \uicontrol Done (on \macos) to create the
@@ -71,6 +68,8 @@
\li \c {hellow_world.pyproject}, which lists the files in the Python
project.
\li \c {mywidget.py}, which has some boilerplate code for a class.
\li \c {reguirements.txt}, which stores the PySide version of the
generated code.
\endlist
\section1 Adding Qt Widgets Imports

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2023 The Qt Company Ltd.
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
@@ -18,16 +18,19 @@
\image qtcreator-new-qt-for-python-app-qt-quick-empty-project-ready.webp {A small Qt Quick application}
For more examples of creating Qt for Python applications, see
\l {https://doc.qt.io/qtforpython/tutorials/index.html}
{Qt for Python Examples and Tutorials}.
\section1 Creating an Empty Project
To create a Qt for Python application that has a main QML file:
\list 1
\li Select \uicontrol File > \uicontrol {New Project} >
\uicontrol {Application (Qt for Python)} >
\uicontrol {Qt Quick Application - Empty} > \uicontrol Choose.
The \uicontrol {Project Location} dialog opens.
\li Go to \uicontrol File > \uicontrol {New Project}.
\li Select \uicontrol {Application (Qt for Python)} >
\uicontrol {Qt Quick Application - Empty} > \uicontrol Choose to
open the \uicontrol {Project Location} dialog.
\image qtcreator-new-qt-for-python-app-qt-quick-empty-project-location.webp {Project Location dialog}
\li In \uicontrol {Name}, enter the project name. For example,
\e {hello_world_quick}.
@@ -35,17 +38,14 @@
For example, \c {C:\Qt\examples}.
\li Select \uicontrol{Next} (on Windows and Linux) or \uicontrol Continue
(on \macos) to open the \uicontrol {Define Project Details} dialog.
\image qtcreator-new-qt-for-python-app-qt-quick-empty-project-details.webp {Define Project Details dialog}
\image qtcreator-new-qt-for-python-app-project-details.webp {Define Project Details dialog}
\li In \uicontrol {PySide version}, select the PySide version of
the generated code.
\li In \uicontrol {Interpreter}, select the Python interpreter to use for
the project.
\li Select the \uicontrol {Create new virtual environment} check box to
use a clean \l{https://docs.python.org/3/library/venv.html}{Python}
environment that is independent of your global Python installation.
\li In \uicontrol {Path to virtual environment}, specify the directory
where to create the environment.
\li Select \uicontrol{Next} or \uicontrol Continue.
\li Select \uicontrol{Next} or \uicontrol Continue to open the
\uicontrol {Kit Selection} dialog.
\image qtcreator-new-project-qt-for-python-kit-selection.webp {Selecting a kit for a Python project}
\li Select Qt for Python kits for building, deploying, and running the
project.
\li Review the project settings, and select \uicontrol {Finish} (on
Windows and Linux) or \uicontrol Done (on \macos) to create the
project.
@@ -58,6 +58,8 @@
project.
\li \c {main.py}, which has some boilerplate code.
\li \c {main.qml}, which imports Qt Quick controls.
\li \c {reguirements.txt}, which stores the PySide version of the
generated code.
\endlist
\section1 Adding Qt Quick Imports

View File

@@ -15,7 +15,7 @@
\list
\li \l{Getting Started}
\list
\li \l{IDE Overview}
\li \l{Overview}
\list
\li \l{Creating Projects}
\li \l{Configuring Projects}

View File

@@ -41,7 +41,7 @@
\row
\li \b {\l{Getting Started}}
\list
\li \l{IDE Overview}
\li \l{Overview}
\li \l{User Interface}
\li \l{Configuring Qt Creator}
\li \l{Building and Running an Example}

View File

@@ -420,7 +420,7 @@ class DumperBase():
def charType(self):
result = self.lookupType('char')
self.intType = lambda: result
self.charType = lambda: result
return result
def ptrSize(self):
@@ -635,7 +635,7 @@ class DumperBase():
def putCharArrayValue(self, data, length, charSize,
displayFormat=DisplayFormat.Automatic):
shown = self.computeLimit(length, self.displayStringLimit)
mem = self.readMemory(data, shown)
mem = self.readMemory(data, shown * charSize)
if charSize == 1:
if displayFormat in (DisplayFormat.Latin1String, DisplayFormat.SeparateLatin1String):
encodingType = 'latin1'

View File

@@ -18,7 +18,7 @@ Rectangle {
Text {
id: text2
color: "#ffffff"
text: qsTrId("Restart")
text: qsTr("Restart")
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 12
anchors.horizontalCenter: parent.horizontalCenter

View File

@@ -1717,12 +1717,8 @@ Se Google Test-dokumentation for yderligere information om GTest-filtre.</transl
<translation>Perf</translation>
</message>
<message>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.
Warning: Plain text misses some information, such as duration.</source>
<translation>XML-output anbefales, fordi det forhindre parsing-problemer, mens ren tekst er lettere at læse for mennesker.
Advarsel: Ren tekst mangle nogle informationer, såsom varighed.</translation>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.&lt;p&gt;Warning: Plain text misses some information, such as duration.</source>
<translation>XML-output anbefales, fordi det forhindre parsing-problemer, mens ren tekst er lettere at læse for mennesker.&lt;p&gt;Advarsel: Ren tekst mangle nogle informationer, såsom varighed.</translation>
</message>
<message>
<source>Select Run Configuration</source>
@@ -18346,8 +18342,8 @@ Id&apos;er skal begynde med et lille bogstav.</translation>
<translation>Simulator start</translation>
</message>
<message>
<source>Cannot start simulator (%1, %2) in current state: %3</source>
<translation>Kan ikke starte simulator (%1, %2) i aktuelle tilstand: %3</translation>
<source>Cannot start simulator (%1, %2) in current state: %3.</source>
<translation>Kan ikke starte simulator (%1, %2) i aktuelle tilstand: %3.</translation>
</message>
<message>
<source>simulator start</source>

View File

@@ -12413,10 +12413,8 @@ Weitere Informationen über GTest-Filter finden Sie in der Dokumenation von Goog
<translation>Auf abgeleitete Qt Quick-Tests überprüfen</translation>
</message>
<message>
<source>Search for Qt Quick tests that are derived from TestCase.
Warning: Enabling this feature significantly increases scan time.</source>
<translation>Sucht nach Qt Quick-Tests, die von TestCase abgeleitet sind.
Achtung: Dies erhöht die zum Durchsuchen benötigte Zeit erheblich.</translation>
<source>Search for Qt Quick tests that are derived from TestCase.&lt;p&gt;Warning: Enabling this feature significantly increases scan time.</source>
<translation>Sucht nach Qt Quick-Tests, die von TestCase abgeleitet sind.&lt;p&gt;Achtung: Dies erhöht die zum Durchsuchen benötigte Zeit erheblich.</translation>
</message>
<message>
<source>Benchmark Metrics</source>
@@ -12463,12 +12461,8 @@ Achtung: Dies erhöht die zum Durchsuchen benötigte Zeit erheblich.</translatio
<translation>Perf</translation>
</message>
<message>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.
Warning: Plain text misses some information, such as duration.</source>
<translation>Die XML-Ausgabe ist empfehlenswert, weil sie Probleme beim Einlesen vermeidet. Reiner Text ist hingegen besser lesbar für Menschen.
Warnung: Reinem Text fehlen manche Informationen, etwa die Dauer.</translation>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.&lt;p&gt;Warning: Plain text misses some information, such as duration.</source>
<translation>Die XML-Ausgabe ist empfehlenswert, weil sie Probleme beim Einlesen vermeidet. Reiner Text ist hingegen besser lesbar für Menschen.&lt;p&gt;Warnung: Reinem Text fehlen manche Informationen, etwa die Dauer.</translation>
</message>
<message>
<source>Select Run Configuration</source>
@@ -33897,7 +33891,7 @@ Möchten Sie sie überschreiben?</translation>
<translation>Simulator starten</translation>
</message>
<message>
<source>Cannot start simulator (%1, %2) in current state: %3</source>
<source>Cannot start simulator (%1, %2) in current state: %3.</source>
<translation>Der Simulator (%1, %2) kann im momentanen Zustand (%3) nicht gestartet werden.</translation>
</message>
<message>

View File

@@ -798,7 +798,7 @@ Une valeur positive augmente la réverbération pour les hautes fréquences et
</message>
</context>
<context>
<name>Axivion</name>
<name>QtC::Axivion</name>
<message>
<source>Project:</source>
<translation>Projet&#xa0;:</translation>
@@ -12063,12 +12063,8 @@ Voir la documentation de Google Test pour plus d&apos;informations sur les filtr
<translation>Utilise la sortie XML</translation>
</message>
<message>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.
Warning: Plain text misses some information, such as duration.</source>
<translation>La sortie XML est recommandée&#xa0;: elle évite des problèmes d&apos;analyse, alors que le texte brut est plus lisible pour un humain.
Avertissement&#xa0;: le texte brut ne contient pas toutes les informations, telle que la durée.</translation>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.&lt;p&gt;Warning: Plain text misses some information, such as duration.</source>
<translation>La sortie XML est recommandée&#xa0;: elle évite des problèmes d&apos;analyse, alors que le texte brut est plus lisible pour un humain.&lt;p&gt;Avertissement&#xa0;: le texte brut ne contient pas toutes les informations, telle que la durée.</translation>
</message>
<message>
<source>Verbose benchmarks</source>
@@ -12099,10 +12095,8 @@ Avertissement&#xa0;: le texte brut ne contient pas toutes les informations, tell
<translation>Vérifier la présence de tests dérivés de Qt Quick</translation>
</message>
<message>
<source>Search for Qt Quick tests that are derived from TestCase.
Warning: Enabling this feature significantly increases scan time.</source>
<translation>Recherche des tests Qt Quick dérivé de TestCase.
Avertissement&#xa0;: l&apos;activation de cette fonctionnalité augmente significativement le temps de recherche.</translation>
<source>Search for Qt Quick tests that are derived from TestCase.&lt;p&gt;Warning: Enabling this feature significantly increases scan time.</source>
<translation>Recherche des tests Qt Quick dérivé de TestCase.&lt;p&gt;Avertissement&#xa0;: l&apos;activation de cette fonctionnalité augmente significativement le temps de recherche.</translation>
</message>
<message>
<source>Benchmark Metrics</source>
@@ -33467,8 +33461,8 @@ Souhaitez-vous les écraser&#xa0;?</translation>
</translation>
</message>
<message>
<source>Cannot start simulator (%1, %2) in current state: %3</source>
<translation>Impossible de démarrer le simulateur (%1, %2) dans l&apos;état actuel&#xa0;: %3</translation>
<source>Cannot start simulator (%1, %2) in current state: %3.</source>
<translation>Impossible de démarrer le simulateur (%1, %2) dans l&apos;état actuel&#xa0;: %3.</translation>
</message>
<message>
<source>simulator start</source>

View File

@@ -1135,12 +1135,8 @@ Dodatne dokumente o GTest filtrima potraži u Google Test dokumentaciji.</transl
<translation>Deaktiviraj rukovatelja urušivanja tijekom uklanjanja grešaka</translation>
</message>
<message>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.
Warning: Plain text misses some information, such as duration.</source>
<translation>Preporuča se XML izlaz, jer izbjegava probleme s raščlanjivanjem, dok je običan tekst čitljiviji za čitanje.
Upozorenje: Običan tekst propušta neke informacije, kao što je trajanje.</translation>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.&lt;p&gt;Warning: Plain text misses some information, such as duration.</source>
<translation>Preporuča se XML izlaz, jer izbjegava probleme s raščlanjivanjem, dok je običan tekst čitljiviji za čitanje.&lt;p&gt;Upozorenje: Običan tekst propušta neke informacije, kao što je trajanje.</translation>
</message>
<message>
<source>Use XML output</source>
@@ -4546,8 +4542,8 @@ Dodaj, izmijeni i ukloni filtre dokumenata koji određuju skup dokumentacije pri
</translation>
</message>
<message>
<source>Cannot start simulator (%1, %2) in current state: %3</source>
<translation>Nije moguće pokrenuti simulatora (%1, %2) u trenutačnom stanju: %3</translation>
<source>Cannot start simulator (%1, %2) in current state: %3.</source>
<translation>Nije moguće pokrenuti simulatora (%1, %2) u trenutačnom stanju: %3.</translation>
</message>
<message>
<source>simulator start</source>

View File

@@ -12692,8 +12692,7 @@ See also Google Test settings.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Search for Qt Quick tests that are derived from TestCase.
Warning: Enabling this feature significantly increases scan time.</source>
<source>Search for Qt Quick tests that are derived from TestCase.&lt;p&gt;Warning: Enabling this feature significantly increases scan time.</source>
<translation type="unfinished"></translation>
</message>
<message>
@@ -12713,9 +12712,7 @@ Warning: Enabling this feature significantly increases scan time.</source>
<translation>Używaj XML na wyjściu</translation>
</message>
<message>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.
Warning: Plain text misses some information, such as duration.</source>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.&lt;p&gt;Warning: Plain text misses some information, such as duration.</source>
<translation type="unfinished"></translation>
</message>
<message>
@@ -33930,8 +33927,8 @@ Czy nadpisać je?</translation>
<translation>Uruchomienie symulatora</translation>
</message>
<message>
<source>Cannot start simulator (%1, %2) in current state: %3</source>
<translation>Nie można uruchomić symulatora (%1, %2) w bieżącym stanie: %3</translation>
<source>Cannot start simulator (%1, %2) in current state: %3.</source>
<translation>Nie można uruchomić symulatora (%1, %2) w bieżącym stanie: %3.</translation>
</message>
<message>
<source>simulator start</source>

View File

@@ -2745,12 +2745,8 @@ See Google Test documentation for further information on GTest filters.</source>
<translation>Логировать сигналы и слоты</translation>
</message>
<message>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.
Warning: Plain text misses some information, such as duration.</source>
<translation>Рекомендуется вывод в формате XML, так как исключает проблемы при разборе. Простой же текст более удобен для чтения человеком.
Предупреждение: простой текст не содержит некоторую информацию, например, длительность.</translation>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.&lt;p&gt;Warning: Plain text misses some information, such as duration.</source>
<translation>Рекомендуется вывод в формате XML, так как исключает проблемы при разборе. Простой же текст более удобен для чтения человеком.&lt;p&gt;Предупреждение: простой текст не содержит некоторую информацию, например, длительность.</translation>
</message>
<message>
<source>Select Run Configuration</source>
@@ -22984,8 +22980,8 @@ Ids must begin with a lowercase letter.</source>
<translation>Запустить эмулятор</translation>
</message>
<message>
<source>Cannot start simulator (%1, %2) in current state: %3</source>
<translation>Невозможно запустить эмулятор (%1, %2) в текущем состоянии: %3</translation>
<source>Cannot start simulator (%1, %2) in current state: %3.</source>
<translation>Невозможно запустить эмулятор (%1, %2) в текущем состоянии: %3.</translation>
</message>
<message>
<source>simulator start</source>

View File

@@ -2950,12 +2950,8 @@ See Google Test documentation for further information on GTest filters.</source>
<translation>使 XML </translation>
</message>
<message>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.
Warning: Plain text misses some information, such as duration.</source>
<translation>使 XML
</translation>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.&lt;p&gt;Warning: Plain text misses some information, such as duration.</source>
<translation>使 XML &lt;p&gt;</translation>
</message>
<message>
<source>Verbose benchmarks</source>
@@ -23217,7 +23213,7 @@ Id必须以小写字母开头。</translation>
</translation>
</message>
<message>
<source>Cannot start simulator (%1, %2) in current state: %3</source>
<source>Cannot start simulator (%1, %2) in current state: %3.</source>
<translation type="unfinished"></translation>
</message>
<message>

View File

@@ -72,6 +72,7 @@ QtcLibrary {
Group {
name: "qtkeychain dbus support"
cpp.defines: outer.concat(["KEYCHAIN_DBUS=1"])
cpp.cxxFlags: outer.concat("-Wno-cast-function-type")
files: [
"gnomekeyring.cpp",
"gnomekeyring_p.h",

View File

@@ -36,7 +36,7 @@ public:
return state.d.data();
}
int size() const
std::size_t size() const
{
return m_contextStack.size();
}

View File

@@ -1,5 +1,5 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "nanotrace.h"

View File

@@ -1,5 +1,5 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#pragma once

View File

@@ -1,5 +1,5 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#pragma once

View File

@@ -1,5 +1,5 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "nanotracehr.h"

View File

@@ -1,5 +1,5 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#pragma once

View File

@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
import pandas as pd
import plotly.graph_objects as go
import plotly.subplots as sp

View File

@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
import reader as rd
import figures as fgs

View File

@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
import os
import io
import json

View File

@@ -569,6 +569,9 @@ void PluginDumper::loadQmltypesFile(const FilePaths &qmltypesFilePaths,
Utils::onFinished(loadQmlTypeDescription(qmltypesFilePaths), this,
[this, qmltypesFilePaths, libraryPath, libraryInfo]
(const QFuture<PluginDumper::QmlTypeDescription> &typesFuture) {
if (typesFuture.isCanceled() || typesFuture.resultCount() == 0)
return;
PluginDumper::QmlTypeDescription typesResult = typesFuture.result();
if (!typesResult.dependencies.isEmpty())
{
@@ -576,6 +579,9 @@ void PluginDumper::loadQmltypesFile(const FilePaths &qmltypesFilePaths,
QSharedPointer<QSet<FilePath>>()), this,
[typesResult, libraryInfo, libraryPath, this] (const QFuture<PluginDumper::DependencyInfo> &loadFuture)
{
if (loadFuture.isCanceled() || loadFuture.resultCount() == 0)
return;
PluginDumper::DependencyInfo loadResult = loadFuture.result();
QStringList errors = typesResult.errors;
QStringList warnings = typesResult.errors;

View File

@@ -205,7 +205,7 @@ if (_library_enabled)
# Deploy lldb.exe and its Python dependency
find_package(Clang QUIET)
if (LLVM_TOOLS_BINARY_DIR AND LLVM_LIBRARY_DIRS)
foreach(lldb_file lldb.exe lldb-vscode.exe liblldb.dll python311.zip python311.dll)
foreach(lldb_file lldb.exe lldb-dap.exe liblldb.dll python311.zip python311.dll)
if (EXISTS ${LLVM_TOOLS_BINARY_DIR}/${lldb_file})
install(FILES ${LLVM_TOOLS_BINARY_DIR}/${lldb_file}
DESTINATION bin/clang/bin

View File

@@ -7,10 +7,12 @@
#include <QEventLoop>
#include <QFutureWatcher>
#include <QHash>
#include <QMetaEnum>
#include <QMutex>
#include <QPromise>
#include <QPointer>
#include <QSet>
#include <QTime>
#include <QTimer>
using namespace std::chrono;
@@ -1191,6 +1193,17 @@ const GroupItem stopOnSuccessOrError = workflowPolicy(WorkflowPolicy::StopOnSucc
const GroupItem finishAllAndSuccess = workflowPolicy(WorkflowPolicy::FinishAllAndSuccess);
const GroupItem finishAllAndError = workflowPolicy(WorkflowPolicy::FinishAllAndError);
// Please note the thread_local keyword below guarantees a separate instance per thread.
// The s_activeTaskTrees is currently used internally only and is not exposed in the public API.
// It serves for withLog() implementation now. Add a note here when a new usage is introduced.
static thread_local QList<TaskTree *> s_activeTaskTrees = {};
static TaskTree *activeTaskTree()
{
QT_ASSERT(s_activeTaskTrees.size(), return nullptr);
return s_activeTaskTrees.back();
}
DoneResult toDoneResult(bool success)
{
return success ? DoneResult::Success : DoneResult::Error;
@@ -1402,8 +1415,8 @@ void GroupItem::addChildren(const QList<GroupItem> &children)
}
}
GroupItem GroupItem::withTimeout(const GroupItem &item, milliseconds timeout,
const std::function<void()> &handler)
ExecutableItem ExecutableItem::withTimeout(milliseconds timeout,
const std::function<void()> &handler) const
{
const auto onSetup = [timeout](milliseconds &timeoutData) { timeoutData = timeout; };
return Group {
@@ -1414,7 +1427,41 @@ GroupItem GroupItem::withTimeout(const GroupItem &item, milliseconds timeout,
handler ? TimeoutTask(onSetup, [handler] { handler(); }, CallDoneIf::Success)
: TimeoutTask(onSetup)
},
item
*this
};
}
static QString currentTime() { return QTime::currentTime().toString(Qt::ISODateWithMs); }
ExecutableItem ExecutableItem::withLog(const QString &logName) const
{
const auto header = [logName] {
return QString("TASK TREE LOG [%1] \"%2\"").arg(currentTime(), logName);
};
struct LogStorage
{
time_point<system_clock, nanoseconds> start;
int asyncCount = 0;
};
const Storage<LogStorage> storage;
return Group {
storage,
onGroupSetup([storage, header] {
storage->start = system_clock::now();
storage->asyncCount = activeTaskTree()->asyncCount();
qDebug().noquote() << header() << "started.";
}),
*this,
onGroupDone([storage, header](DoneWith result) {
const auto elapsed = duration_cast<milliseconds>(system_clock::now() - storage->start);
const int asyncCountDiff = activeTaskTree()->asyncCount() - storage->asyncCount;
QT_CHECK(asyncCountDiff >= 0);
const QMetaEnum doneWithEnum = QMetaEnum::fromType<DoneWith>();
const QString syncType = asyncCountDiff ? QString("asynchronously")
: QString("synchronously");
qDebug().noquote().nospace() << header() << " finished " << syncType << " with "
<< doneWithEnum.valueToKey(int(result)) << " within " << elapsed.count() << "ms.";
})
};
}
@@ -1427,16 +1474,26 @@ class RuntimeTask;
class ExecutionContextActivator
{
public:
ExecutionContextActivator(RuntimeIteration *iteration) { activateContext(iteration); }
ExecutionContextActivator(RuntimeContainer *container) { activateContext(container); }
ExecutionContextActivator(RuntimeIteration *iteration) {
activateTaskTree(iteration);
activateContext(iteration);
}
ExecutionContextActivator(RuntimeContainer *container) {
activateTaskTree(container);
activateContext(container);
}
~ExecutionContextActivator() {
for (int i = m_activeStorages.size() - 1; i >= 0; --i) // iterate in reverse order
m_activeStorages[i].m_storageData->threadData().popStorage();
for (int i = m_activeLoops.size() - 1; i >= 0; --i) // iterate in reverse order
m_activeLoops[i].m_loopData->threadData().popIteration();
QT_ASSERT(s_activeTaskTrees.size(), return);
s_activeTaskTrees.pop_back();
}
private:
void activateTaskTree(RuntimeIteration *iteration);
void activateTaskTree(RuntimeContainer *container);
void activateContext(RuntimeIteration *iteration);
void activateContext(RuntimeContainer *container);
QList<Loop> m_activeLoops;
@@ -1490,9 +1547,8 @@ public:
void start();
void stop();
void bumpAsyncCount();
void advanceProgress(int byValue);
void emitStartedAndProgress();
void emitProgress();
void emitDone(DoneWith result);
void callSetupHandler(StorageBase storage, StoragePtr storagePtr) {
callStorageHandler(storage, storagePtr, &StorageHandler::m_setupHandler);
@@ -1552,6 +1608,7 @@ public:
TaskTree *q = nullptr;
Guard m_guard;
int m_progressValue = 0;
int m_asyncCount = 0;
QSet<StorageBase> m_storages;
QHash<StorageBase, StorageHandler> m_storageHandlers;
std::optional<TaskNode> m_root;
@@ -1666,6 +1723,16 @@ static bool isProgressive(RuntimeContainer *container)
return iteration ? iteration->m_isProgressive : true;
}
void ExecutionContextActivator::activateTaskTree(RuntimeIteration *iteration)
{
activateTaskTree(iteration->m_container);
}
void ExecutionContextActivator::activateTaskTree(RuntimeContainer *container)
{
s_activeTaskTrees.push_back(container->m_containerNode.m_taskTreePrivate->q);
}
void ExecutionContextActivator::activateContext(RuntimeIteration *iteration)
{
std::optional<Loop> loop = iteration->loop();
@@ -1696,8 +1763,14 @@ void TaskTreePrivate::start()
{
QT_ASSERT(m_root, return);
QT_ASSERT(!m_runtimeRoot, return);
m_asyncCount = 0;
m_progressValue = 0;
emitStartedAndProgress();
{
GuardLocker locker(m_guard);
emit q->started();
emit q->asyncCountChanged(m_asyncCount);
emit q->progressValueChanged(m_progressValue);
}
// TODO: check storage handlers for not existing storages in tree
for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) {
QT_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't "
@@ -1705,6 +1778,7 @@ void TaskTreePrivate::start()
}
m_runtimeRoot.reset(new RuntimeTask{*m_root});
start(m_runtimeRoot.get());
bumpAsyncCount();
}
void TaskTreePrivate::stop()
@@ -1717,6 +1791,15 @@ void TaskTreePrivate::stop()
emitDone(DoneWith::Cancel);
}
void TaskTreePrivate::bumpAsyncCount()
{
if (!m_runtimeRoot)
return;
++m_asyncCount;
GuardLocker locker(m_guard);
emit q->asyncCountChanged(m_asyncCount);
}
void TaskTreePrivate::advanceProgress(int byValue)
{
if (byValue == 0)
@@ -1724,18 +1807,6 @@ void TaskTreePrivate::advanceProgress(int byValue)
QT_CHECK(byValue > 0);
QT_CHECK(m_progressValue + byValue <= m_root->taskCount());
m_progressValue += byValue;
emitProgress();
}
void TaskTreePrivate::emitStartedAndProgress()
{
GuardLocker locker(m_guard);
emit q->started();
emit q->progressValueChanged(m_progressValue);
}
void TaskTreePrivate::emitProgress()
{
GuardLocker locker(m_guard);
emit q->progressValueChanged(m_progressValue);
}
@@ -2037,10 +2108,12 @@ SetupResult TaskTreePrivate::start(RuntimeTask *node)
node->m_task.release()->deleteLater();
RuntimeIteration *parentIteration = node->m_parentIteration;
parentIteration->deleteChild(node);
if (parentIteration->m_container->isStarting())
if (parentIteration->m_container->isStarting()) {
*unwindAction = toSetupResult(result);
else
} else {
childDone(parentIteration, result);
bumpAsyncCount();
}
});
node->m_task->start();
@@ -2961,6 +3034,38 @@ DoneWith TaskTree::runBlocking(const Group &recipe, const QFuture<void> &future,
return taskTree.runBlocking(future);
}
/*!
Returns the current real count of asynchronous chains of invocations.
The returned value indicates how many times the control returns to the caller's
event loop while the task tree is running. Initially, this value is 0.
If the execution of the task tree finishes fully synchronously, this value remains 0.
If the task tree contains any asynchronous tasks that are successfully started during
a call to start(), this value is bumped to 1 just before the call to start() finishes.
Later, when any asynchronous task finishes and any possible continuations are started,
this value is bumped again. The bumping continues until the task tree finishes.
When the task tree emits the done() signal, the bumping stops.
The asyncCountChanged() signal is emitted on every bump of this value.
\sa asyncCountChanged()
*/
int TaskTree::asyncCount() const
{
return d->m_asyncCount;
}
/*!
\fn void TaskTree::asyncCountChanged(int count)
This signal is emitted when the running task tree is about to return control to the caller's
event loop. When the task tree is started, this signal is emitted with \a count value of 0,
and emitted later on every asyncCount() value bump with an updated \a count value.
Every signal sent (except the initial one with the value of 0) guarantees that the task tree
is still running asynchronously after the emission.
\sa asyncCount()
*/
/*!
Returns the number of asynchronous tasks contained in the stored recipe.
@@ -3006,7 +3111,7 @@ int TaskTree::taskCount() const
When the task tree is started, this number is set to \c 0.
When the task tree is finished, this number always equals progressMaximum().
\sa progressMaximum()
\sa progressMaximum(), progressValueChanged()
*/
int TaskTree::progressValue() const
{

View File

@@ -261,8 +261,6 @@ protected:
static GroupItem groupHandler(const GroupHandler &handler) { return GroupItem({handler}); }
static GroupItem parallelLimit(int limit) { return GroupItem({{}, limit}); }
static GroupItem workflowPolicy(WorkflowPolicy policy) { return GroupItem({{}, {}, policy}); }
static GroupItem withTimeout(const GroupItem &item, std::chrono::milliseconds timeout,
const std::function<void()> &handler = {});
// Checks if Function may be invoked with Args and if Function's return type is Result.
template <typename Result, typename Function, typename ...Args,
@@ -286,7 +284,19 @@ private:
TaskHandler m_taskHandler;
};
class TASKING_EXPORT Group : public GroupItem
class TASKING_EXPORT ExecutableItem : public GroupItem
{
public:
ExecutableItem withTimeout(std::chrono::milliseconds timeout,
const std::function<void()> &handler = {}) const;
ExecutableItem withLog(const QString &logName) const;
protected:
ExecutableItem() = default;
ExecutableItem(const TaskHandler &handler) : GroupItem(handler) {}
};
class TASKING_EXPORT Group : public ExecutableItem
{
public:
Group(const QList<GroupItem> &children) { addChildren(children); }
@@ -304,11 +314,6 @@ public:
using GroupItem::parallelLimit; // Default: 1 (sequential). 0 means unlimited (parallel).
using GroupItem::workflowPolicy; // Default: WorkflowPolicy::StopOnError.
GroupItem withTimeout(std::chrono::milliseconds timeout,
const std::function<void()> &handler = {}) const {
return GroupItem::withTimeout(*this, timeout, handler);
}
private:
template <typename Handler>
static GroupSetupHandler wrapGroupSetup(Handler &&handler)
@@ -387,7 +392,7 @@ public:
};
// Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount()
class TASKING_EXPORT Sync final : public GroupItem
class TASKING_EXPORT Sync final : public ExecutableItem
{
public:
template <typename Handler>
@@ -431,7 +436,7 @@ private:
};
template <typename Adapter>
class CustomTask final : public GroupItem
class CustomTask final : public ExecutableItem
{
public:
using Task = typename Adapter::TaskType;
@@ -445,16 +450,10 @@ public:
template <typename SetupHandler = TaskSetupHandler, typename DoneHandler = TaskDoneHandler>
CustomTask(SetupHandler &&setup = TaskSetupHandler(), DoneHandler &&done = TaskDoneHandler(),
CallDoneIf callDoneIf = CallDoneIf::SuccessOrError)
: GroupItem({&createAdapter, wrapSetup(std::forward<SetupHandler>(setup)),
wrapDone(std::forward<DoneHandler>(done)), callDoneIf})
: ExecutableItem({&createAdapter, wrapSetup(std::forward<SetupHandler>(setup)),
wrapDone(std::forward<DoneHandler>(done)), callDoneIf})
{}
GroupItem withTimeout(std::chrono::milliseconds timeout,
const std::function<void()> &handler = {}) const
{
return GroupItem::withTimeout(*this, timeout, handler);
}
private:
static Adapter *createAdapter() { return new Adapter; }
@@ -542,6 +541,7 @@ public:
static DoneWith runBlocking(const Group &recipe, const QFuture<void> &future,
std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
int asyncCount() const;
int taskCount() const;
int progressMaximum() const { return taskCount(); }
int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded
@@ -566,6 +566,7 @@ public:
signals:
void started();
void done(DoneWith result);
void asyncCountChanged(int count);
void progressValueChanged(int value); // updated whenever task finished / skipped / stopped
private:

View File

@@ -1199,6 +1199,9 @@ void TerminalView::mouseReleaseEvent(QMouseEvent *event)
void TerminalView::mouseDoubleClickEvent(QMouseEvent *event)
{
if (event->button() != Qt::LeftButton)
return;
if (d->m_allowMouseTracking) {
d->m_surface->mouseMove(toGridPos(event), event->modifiers());
d->m_surface->mouseButton(event->button(), true, event->modifiers());

View File

@@ -54,7 +54,7 @@ auto asyncRun(Function &&function, Args &&...args)
template <typename R, typename T>
const QFuture<T> &onResultReady(const QFuture<T> &future, R *receiver, void(R::*member)(const T &))
{
auto watcher = new QFutureWatcher<T>();
auto watcher = new QFutureWatcher<T>(receiver);
QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater);
QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, receiver, [=](int index) {
(receiver->*member)(watcher->future().resultAt(index));
@@ -72,7 +72,7 @@ const QFuture<T> &onResultReady(const QFuture<T> &future, R *receiver, void(R::*
template <typename T, typename Function>
const QFuture<T> &onResultReady(const QFuture<T> &future, QObject *guard, Function f)
{
auto watcher = new QFutureWatcher<T>();
auto watcher = new QFutureWatcher<T>(guard);
QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater);
QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, guard, [f, watcher](int index) {
f(watcher->future().resultAt(index));
@@ -90,7 +90,7 @@ template<typename R, typename T>
const QFuture<T> &onFinished(const QFuture<T> &future,
R *receiver, void (R::*member)(const QFuture<T> &))
{
auto watcher = new QFutureWatcher<T>();
auto watcher = new QFutureWatcher<T>(receiver);
QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater);
QObject::connect(watcher, &QFutureWatcherBase::finished, receiver,
[=] { (receiver->*member)(watcher->future()); });
@@ -107,7 +107,7 @@ const QFuture<T> &onFinished(const QFuture<T> &future,
template<typename T, typename Function>
const QFuture<T> &onFinished(const QFuture<T> &future, QObject *guard, Function f)
{
auto watcher = new QFutureWatcher<T>();
auto watcher = new QFutureWatcher<T>(guard);
QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater);
QObject::connect(watcher, &QFutureWatcherBase::finished, guard, [f, watcher] {
f(watcher->future());

View File

@@ -566,8 +566,8 @@ static bool checkToRefuseRemoveStandardLocationDirectory(const QString &dirPath,
{
if (QStandardPaths::standardLocations(location).contains(dirPath)) {
if (error) {
*error = Tr::tr("Refusing to remove your %1 directory.").arg(
QStandardPaths::displayName(location));
*error = Tr::tr("Refusing to remove the standard directory \"%1\".")
.arg(QStandardPaths::displayName(location));
}
return false;
}

View File

@@ -375,7 +375,7 @@ void MacroExpander::registerIntVariable(const QByteArray &variable,
* Convenience function to register several variables with the same \a prefix, that have a file
* as a value. Takes the prefix and registers variables like \c{prefix:FilePath} and
* \c{prefix:Path}, with descriptions that start with the given \a heading.
* For example \c{registerFileVariables("CurrentDocument", tr("Current Document"))} registers
* For example \c{registerFileVariables("CurrentDocument", Tr::tr("Current Document"))} registers
* variables such as \c{CurrentDocument:FilePath} with description
* "Current Document: Full path including file name."
*

View File

@@ -847,7 +847,9 @@ public:
qint64 m_applicationMainThreadId = 0;
ProcessResultData m_resultData;
QTextCodec *m_codec = QTextCodec::codecForLocale();
QTextCodec *m_stdOutCodec = QTextCodec::codecForLocale();
QTextCodec *m_stdErrCodec = QTextCodec::codecForLocale();
ProcessResult m_result = ProcessResult::StartFailed;
ChannelBuffer m_stdOut;
ChannelBuffer m_stdErr;
@@ -1102,9 +1104,9 @@ void ProcessPrivate::sendControlSignal(ControlSignal controlSignal)
void ProcessPrivate::clearForRun()
{
m_stdOut.clearForRun();
m_stdOut.codec = m_codec;
m_stdOut.codec = m_stdOutCodec;
m_stdErr.clearForRun();
m_stdErr.codec = m_codec;
m_stdErr.codec = m_stdErrCodec;
m_result = ProcessResult::StartFailed;
m_startTimestamp = {};
m_doneTimestamp = {};
@@ -1663,8 +1665,7 @@ QString Process::exitMessage(const CommandLine &command, ProcessResult result,
case ProcessResult::Canceled:
// TODO: We might want to format it nicely when bigger than 1 second, e.g. 1,324 s.
// Also when it's bigger than 1 minute, 1 hour, etc...
return Tr::tr("The command \"%1\" was canceled after (%2 ms).")
.arg(cmd).arg(duration.count());
return Tr::tr("The command \"%1\" was canceled after %2 ms.").arg(cmd).arg(duration.count());
}
return {};
}
@@ -1729,13 +1730,13 @@ QByteArray Process::rawStdErr() const
QString Process::stdOut() const
{
QTC_CHECK(d->m_stdOut.keepRawData);
return d->m_codec->toUnicode(d->m_stdOut.rawData);
return d->m_stdOutCodec->toUnicode(d->m_stdOut.rawData);
}
QString Process::stdErr() const
{
QTC_CHECK(d->m_stdErr.keepRawData);
return d->m_codec->toUnicode(d->m_stdErr.rawData);
return d->m_stdErrCodec->toUnicode(d->m_stdErr.rawData);
}
QString Process::cleanedStdOut() const
@@ -1850,7 +1851,20 @@ void ChannelBuffer::handleRest()
void Process::setCodec(QTextCodec *c)
{
QTC_ASSERT(c, return);
d->m_codec = c;
d->m_stdOutCodec = c;
d->m_stdErrCodec = c;
}
void Process::setStdOutCodec(QTextCodec *c)
{
QTC_ASSERT(c, return);
d->m_stdOutCodec = c;
}
void Process::setStdErrCodec(QTextCodec *c)
{
QTC_ASSERT(c, return);
d->m_stdErrCodec = c;
}
void Process::setTimeOutMessageBoxEnabled(bool v)

View File

@@ -148,8 +148,10 @@ public:
void runBlocking(std::chrono::seconds timeout = std::chrono::seconds(10),
EventLoopMode eventLoopMode = EventLoopMode::Off);
// TODO: We should specify the purpose of the codec, e.g. setCodecForStandardChannel()
void setCodec(QTextCodec *c);
void setCodec(QTextCodec *c); // for stdOut and stdErr
void setStdOutCodec(QTextCodec *c);
void setStdErrCodec(QTextCodec *c);
void setTimeOutMessageBoxEnabled(bool);
void setStdOutCallback(const TextChannelCallback &callback);

View File

@@ -126,6 +126,15 @@ static QString ndkPackageMarker()
return QLatin1String(Constants::ndkPackageName) + ";";
}
static QString platformsPackageMarker()
{
return QLatin1String(Constants::platformsPackageName) + ";";
}
static QString buildToolsPackageMarker()
{
return QLatin1String(Constants::buildToolsPackageName) + ";";
}
//////////////////////////////////
// AndroidConfig
@@ -951,15 +960,59 @@ bool AndroidConfig::sdkToolsOk() const
return exists && writable && sdkToolsExist;
}
static QStringList packagesExcludingBuiltWithDefaults(const QStringList &packages)
{
return Utils::filtered(packages, [] (const QString &p) {
return !p.startsWith(ndkPackageMarker()) && !p.startsWith(platformsPackageMarker())
&& !p.startsWith(buildToolsPackageMarker()); });
}
static QString essentialBuiltWithBuildToolsPackage(int builtWithApiVersion)
{
// For build-tools, to avoid the situation of potentially having the essential packages
// invalidated whenever a new minor version is released, check if any version with major
// version matching builtWith apiVersion and use it as essential, otherwise use the any
// other one that has an minimum major version of builtWith apiVersion.
const BuildToolsList buildTools =
AndroidConfigurations::sdkManager()->filteredBuildTools(builtWithApiVersion);
const BuildToolsList apiBuildTools
= Utils::filtered(buildTools, [builtWithApiVersion] (const BuildTools *pkg) {
return pkg->revision().majorVersion() == builtWithApiVersion; });
const QString installedBuildTool = [apiBuildTools] () -> QString {
for (const BuildTools *pkg : apiBuildTools) {
if (pkg->state() == AndroidSdkPackage::Installed)
return pkg->sdkStylePath();
}
return {};
}();
if (installedBuildTool.isEmpty()) {
if (!apiBuildTools.isEmpty())
return apiBuildTools.first()->sdkStylePath();
else if (!buildTools.isEmpty())
return buildTools.first()->sdkStylePath();
// This means there's something wrong with sdkmanager, return a default version anyway
else
return buildToolsPackageMarker() + QString::number(builtWithApiVersion) + ".0.0";
}
return installedBuildTool;
}
QStringList AndroidConfig::essentialsFromQtVersion(const QtVersion &version) const
{
if (auto androidQtVersion = dynamic_cast<const AndroidQtVersion *>(&version)) {
bool ok;
const AndroidQtVersion::BuiltWith bw = androidQtVersion->builtWith(&ok);
if (ok) {
const QString ndkPackage = ndkPackageMarker() + bw.ndkVersion.toString();
return QStringList(ndkPackage)
+ packagesWithoutNdks(m_defaultSdkDepends.essentialPackages);
QStringList builtWithPackages;
builtWithPackages.append(ndkPackageMarker() + bw.ndkVersion.toString());
const QString apiVersion = QString::number(bw.apiVersion);
builtWithPackages.append(platformsPackageMarker() + "android-" + apiVersion);
builtWithPackages.append(essentialBuiltWithBuildToolsPackage(bw.apiVersion));
return builtWithPackages + packagesExcludingBuiltWithDefaults(
m_defaultSdkDepends.essentialPackages);
}
}

View File

@@ -78,6 +78,8 @@ const Utils::Id AndroidAvdPath = "AndroidAvdPath";
// SDK Tools
const char cmdlineToolsName[] = "cmdline-tools";
const char ndkPackageName[] = "ndk";
const char platformsPackageName[] = "platforms";
const char buildToolsPackageName[] = "build-tools";
// For AndroidQtVersion
const char ArmToolsDisplayName[] = "arm";

View File

@@ -405,7 +405,10 @@ void AndroidSdkManagerPrivate::reloadSdkPackages()
if (m_packageListingSuccessful) {
SdkManagerOutputParser parser(m_allPackages);
parser.parsePackageListing(packageListing);
} else {
qCWarning(sdkManagerLog) << "Failed parsing packages:" << packageListing;
}
emit m_sdkManager.packageReloadFinished();
}

View File

@@ -70,7 +70,7 @@ DataTagLocatorFilter::DataTagLocatorFilter()
{
setId("Locate Qt Test data tags");
setDisplayName(Tr::tr("Locate Qt Test data tags"));
setDescription(Tr::tr("Locates a Qt Test data tag found inside the active project."));
setDescription(Tr::tr("Locates Qt Test data tags found inside the active project."));
setDefaultShortcutString("qdt");
setPriority(Medium);
using namespace ProjectExplorer;

View File

@@ -73,9 +73,10 @@ QtTestFramework::QtTestFramework()
useXMLOutput.setSettingsKey("UseXMLOutput");
useXMLOutput.setDefaultValue(true);
useXMLOutput.setLabelText(Tr::tr("Use XML output"));
useXMLOutput.setToolTip(Tr::tr("XML output is recommended, because it avoids parsing issues, "
"while plain text is more human readable.\n\nWarning: "
"Plain text misses some information, such as duration."));
useXMLOutput.setToolTip("<html>"
+ Tr::tr("XML output is recommended, because it avoids parsing issues, "
"while plain text is more human readable.<p>Warning: "
"Plain text misses some information, such as duration."));
verboseBench.setSettingsKey("VerboseBench");
verboseBench.setLabelText(Tr::tr("Verbose benchmarks"));
@@ -98,18 +99,22 @@ QtTestFramework::QtTestFramework()
quickCheckForDerivedTests.setDefaultValue(false);
quickCheckForDerivedTests.setLabelText(Tr::tr("Check for derived Qt Quick tests"));
quickCheckForDerivedTests.setToolTip(
Tr::tr("Search for Qt Quick tests that are derived from TestCase.\nWarning: Enabling this "
"feature significantly increases scan time."));
"<html>"
+ Tr::tr(
"Search for Qt Quick tests that are derived from TestCase.<p>Warning: Enabling this "
"feature significantly increases scan time."));
parseMessages.setSettingsKey("ParseMessages");
parseMessages.setDefaultValue(false);
parseMessages.setLabelText(Tr::tr("Find user-defined locations"));
parseMessages.setToolTip(
Tr::tr("Parse messages for the pattern \"file://filepath:line\", where \":line\" is "
"optional, and use this as location information.\n"
"Warning: If the patterns are used in code, the location information for debug "
"messages and other messages might improve,\n"
"at the risk of some incorrect locations and lower performance."));
"<html>"
+ Tr::tr("Parse messages for the following pattern and use it as location information:"
"<pre>file://filepath:line</pre>"
"where \":line\" is optional."
"<p>Warning: If the patterns are used in code, the location information for debug "
"messages and other messages might improve,"
"at the risk of some incorrect locations and lower performance."));
readSettings();
maxWarnings.setEnabler(&limitWarnings);

View File

@@ -6,11 +6,14 @@ add_qtc_plugin(Axivion
axivion.qrc
axivionoutputpane.cpp axivionoutputpane.h
axivionplugin.cpp axivionplugin.h
axivionprojectsettings.h axivionprojectsettings.cpp
axivionprojectsettings.cpp axivionprojectsettings.h
axivionsettings.cpp axivionsettings.h
axiviontr.h
credentialquery.h credentialquery.cpp
dashboard/dto.cpp dashboard/dto.h
dashboard/concat.cpp dashboard/concat.h
dashboard/error.h dashboard/error.cpp
dashboard/error.cpp dashboard/error.h
dynamiclistmodel.cpp dynamiclistmodel.h
issueheaderview.cpp issueheaderview.h
)

View File

@@ -23,8 +23,12 @@ QtcPlugin {
"axivionsettings.cpp",
"axivionsettings.h",
"axiviontr.h",
"credentialquery.h",
"credentialquery.cpp",
"credentialquery.h",
"dynamiclistmodel.cpp",
"dynamiclistmodel.h",
"issueheaderview.cpp",
"issueheaderview.h",
]
cpp.includePaths: base.concat(["."]) // needed for the generated stuff below

View File

@@ -14,5 +14,9 @@
<file>images/button-mv@2x.png</file>
<file>images/button-sv.png</file>
<file>images/button-sv@2x.png</file>
<file>images/sortAsc.png</file>
<file>images/sortAsc@2x.png</file>
<file>images/sortDesc.png</file>
<file>images/sortDesc@2x.png</file>
</qresource>
</RCC>

View File

@@ -6,17 +6,21 @@
#include "axivionplugin.h"
#include "axiviontr.h"
#include "dashboard/dto.h"
#include "issueheaderview.h"
#include "dynamiclistmodel.h"
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/ioutputpane.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectmanager.h>
#include <solutions/tasking/tasktreerunner.h>
#include <utils/algorithm.h>
#include <utils/layoutbuilder.h>
#include <utils/link.h>
#include <utils/qtcassert.h>
#include <utils/treemodel.h>
#include <utils/basetreeview.h>
#include <utils/utilsicons.h>
@@ -28,9 +32,7 @@
#include <QLabel>
#include <QPushButton>
#include <QScrollArea>
#include <QScrollBar>
#include <QStackedWidget>
#include <QTextBrowser>
#include <QToolButton>
#include <map>
@@ -59,22 +61,24 @@ DashboardWidget::DashboardWidget(QWidget *parent)
: QScrollArea(parent)
{
QWidget *widget = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(widget);
QFormLayout *projectLayout = new QFormLayout;
m_project = new QLabel(this);
projectLayout->addRow(Tr::tr("Project:"), m_project);
m_loc = new QLabel(this);
projectLayout->addRow(Tr::tr("Lines of code:"), m_loc);
m_timestamp = new QLabel(this);
projectLayout->addRow(Tr::tr("Analysis timestamp:"), m_timestamp);
layout->addLayout(projectLayout);
layout->addSpacing(10);
auto row = new QHBoxLayout;
m_gridLayout = new QGridLayout;
row->addLayout(m_gridLayout);
row->addStretch(1);
layout->addLayout(row);
layout->addStretch(1);
using namespace Layouting;
Column {
Form {
Tr::tr("Project:"), m_project, br,
Tr::tr("Lines of code:"), m_loc, br,
Tr::tr("Analysis timestamp:"), m_timestamp
},
Space(10),
Row { m_gridLayout, st },
st
}.attachTo(widget);
setWidget(widget);
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
setWidgetResizable(true);
@@ -187,32 +191,59 @@ void DashboardWidget::updateUi()
addValuesWidgets(Tr::tr("Total:"), allTotal, allAdded, allRemoved, row);
}
class IssueTreeItem final : public StaticTreeItem
struct LinkWithColumns
{
Link link;
QList<int> columns;
};
class IssueListItem final : public ListItem
{
public:
IssueTreeItem(const QStringList &data, const QStringList &toolTips)
: StaticTreeItem(data, toolTips)
IssueListItem(int row, const QString &id, const QStringList &data, const QStringList &toolTips)
: ListItem(row)
, m_id(id)
, m_data(data)
, m_toolTips(toolTips)
{}
void setLinks(const Links &links) { m_links = links; }
void setLinks(const QList<LinkWithColumns> &links) { m_links = links; }
QVariant data(int column, int role) const
{
if (role == Qt::DisplayRole && column >= 0 && column < m_data.size())
return m_data.at(column);
if (role == Qt::ToolTipRole && column >= 0 && column < m_toolTips.size())
return m_toolTips.at(column);
return {};
}
bool setData(int column, const QVariant &value, int role) final
{
if (role == BaseTreeView::ItemActivatedRole && !m_links.isEmpty()) {
// TODO for now only simple - just the first..
Link link = m_links.first();
Project *project = ProjectManager::startupProject();
FilePath baseDir = project ? project->projectDirectory() : FilePath{};
link.targetFilePath = baseDir.resolvePath(link.targetFilePath);
if (link.targetFilePath.exists())
EditorManager::openEditorAt(link);
if (role == BaseTreeView::ItemActivatedRole) {
if (!m_links.isEmpty()) {
Link link
= Utils::findOr(m_links, m_links.first(), [column](const LinkWithColumns &link) {
return link.columns.contains(column);
}).link;
Project *project = ProjectManager::startupProject();
FilePath baseDir = project ? project->projectDirectory() : FilePath{};
link.targetFilePath = baseDir.resolvePath(link.targetFilePath);
if (link.targetFilePath.exists())
EditorManager::openEditorAt(link);
}
if (!m_id.isEmpty())
fetchIssueInfo(m_id);
return true;
}
return StaticTreeItem::setData(column, value, role);
return ListItem::setData(column, value, role);
}
private:
Links m_links;
const QString m_id;
QStringList m_data;
QStringList m_toolTips;
QList<LinkWithColumns> m_links;
};
class IssuesWidget : public QScrollArea
@@ -220,24 +251,23 @@ class IssuesWidget : public QScrollArea
public:
explicit IssuesWidget(QWidget *parent = nullptr);
void updateUi();
void setTableDto(const Dto::TableInfoDto &dto);
void addIssues(const Dto::IssueTableDto &dto);
private:
void updateTable();
void addIssues(const Dto::IssueTableDto &dto, int startRow);
void onSearchParameterChanged();
void updateBasicProjectInfo(std::optional<Dto::ProjectInfoDto> info);
void updateTableView();
void setFiltersEnabled(bool enabled);
IssueListSearch searchFromUi() const;
void fetchTable();
void fetchIssues(const IssueListSearch &search);
void fetchMoreIssues();
void onFetchRequested(int startRow, int limit);
QString m_currentPrefix;
QString m_currentProject;
std::optional<Dto::TableInfoDto> m_currentTableInfo;
QHBoxLayout *m_typesLayout = nullptr;
QButtonGroup *m_typesButtonGroup = nullptr;
QHBoxLayout *m_filtersLayout = nullptr;
QPushButton *m_addedFilter = nullptr;
QPushButton *m_removedFilter = nullptr;
QComboBox *m_ownerFilter = nullptr;
@@ -246,9 +276,9 @@ private:
QLineEdit *m_pathGlobFilter = nullptr; // FancyLineEdit instead?
QLabel *m_totalRows = nullptr;
BaseTreeView *m_issuesView = nullptr;
TreeModel<> *m_issuesModel = nullptr;
IssueHeaderView *m_headerView = nullptr;
DynamicListModel *m_issuesModel = nullptr;
int m_totalRowCount = 0;
int m_lastRequestedOffset = 0;
QStringList m_userNames;
QStringList m_versionDates;
TaskTreeRunner m_taskTreeRunner;
@@ -258,79 +288,71 @@ IssuesWidget::IssuesWidget(QWidget *parent)
: QScrollArea(parent)
{
QWidget *widget = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(widget);
// row with issue types (-> depending on choice, tables below change)
// and a selectable range (start version, end version)
// row with added/removed and some filters (assignee, path glob, (named filter))
// table, columns depend on chosen issue type
QHBoxLayout *top = new QHBoxLayout;
layout->addLayout(top);
m_typesButtonGroup = new QButtonGroup(this);
m_typesButtonGroup->setExclusive(true);
m_typesLayout = new QHBoxLayout;
top->addLayout(m_typesLayout);
top->addStretch(1);
m_versionStart = new QComboBox(this);
m_versionStart->setMinimumContentsLength(25);
top->addWidget(m_versionStart);
connect(m_versionStart, &QComboBox::activated, this, &IssuesWidget::onSearchParameterChanged);
m_versionEnd = new QComboBox(this);
m_versionEnd->setMinimumContentsLength(25);
connect(m_versionStart, &QComboBox::activated, this, &IssuesWidget::onSearchParameterChanged);
connect(m_versionEnd, &QComboBox::activated, this, &IssuesWidget::onSearchParameterChanged);
top->addWidget(m_versionEnd);
top->addStretch(1);
m_filtersLayout = new QHBoxLayout;
m_addedFilter = new QPushButton(this);
m_addedFilter->setIcon(trendIcon(1, 0));
m_addedFilter->setText("0");
m_addedFilter->setCheckable(true);
m_filtersLayout->addWidget(m_addedFilter);
m_removedFilter = new QPushButton(this);
m_removedFilter->setIcon(trendIcon(0, 1));
m_removedFilter->setText("0");
m_removedFilter->setCheckable(true);
m_filtersLayout->addWidget(m_removedFilter);
connect(m_addedFilter, &QPushButton::clicked, this, [this](bool checked) {
if (checked && m_removedFilter->isChecked())
m_removedFilter->setChecked(false);
onSearchParameterChanged();
});
m_removedFilter = new QPushButton(this);
m_removedFilter->setIcon(trendIcon(0, 1));
m_removedFilter->setText("0");
m_removedFilter->setCheckable(true);
connect(m_removedFilter, &QPushButton::clicked, this, [this](bool checked) {
if (checked && m_addedFilter->isChecked())
m_addedFilter->setChecked(false);
onSearchParameterChanged();
});
m_filtersLayout->addSpacing(1);
m_ownerFilter = new QComboBox(this);
m_ownerFilter->setToolTip(Tr::tr("Owner"));
m_ownerFilter->setMinimumContentsLength(25);
connect(m_ownerFilter, &QComboBox::activated, this, &IssuesWidget::onSearchParameterChanged);
m_filtersLayout->addWidget(m_ownerFilter);
m_pathGlobFilter = new QLineEdit(this);
m_pathGlobFilter->setPlaceholderText(Tr::tr("Path globbing"));
connect(m_pathGlobFilter, &QLineEdit::textEdited, this, &IssuesWidget::onSearchParameterChanged);
m_filtersLayout->addWidget(m_pathGlobFilter);
layout->addLayout(m_filtersLayout);
m_issuesView = new BaseTreeView(this);
m_headerView = new IssueHeaderView(this);
connect(m_headerView, &IssueHeaderView::sortTriggered,
this, &IssuesWidget::onSearchParameterChanged);
m_issuesView->setHeader(m_headerView);
m_issuesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_issuesView->enableColumnHiding();
m_issuesModel = new TreeModel(this);
m_issuesModel = new DynamicListModel(this);
m_issuesView->setModel(m_issuesModel);
auto sb = m_issuesView->verticalScrollBar();
if (QTC_GUARD(sb)) {
connect(sb, &QAbstractSlider::valueChanged, sb, [this, sb](int value) {
if (value >= sb->maximum() - 50) {
if (m_issuesModel->rowCount() < m_totalRowCount)
fetchMoreIssues();
}
});
}
layout->addWidget(m_issuesView);
connect(m_issuesModel, &DynamicListModel::fetchRequested, this, &IssuesWidget::onFetchRequested);
m_totalRows = new QLabel(Tr::tr("Total rows:"), this);
QHBoxLayout *bottom = new QHBoxLayout;
layout->addLayout(bottom);
bottom->addStretch(1);
bottom->addWidget(m_totalRows);
using namespace Layouting;
Column {
Row { m_typesLayout, st, m_versionStart, m_versionEnd, st },
Row { m_addedFilter, m_removedFilter, Space(1), m_ownerFilter, m_pathGlobFilter },
m_issuesView,
Row { st, m_totalRows }
}.attachTo(widget);
setWidget(widget);
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
setWidgetResizable(true);
@@ -355,50 +377,81 @@ void IssuesWidget::updateUi()
if (info.issueKinds.size())
m_currentPrefix = info.issueKinds.front().prefix;
updateTableView();
fetchTable();
}
void IssuesWidget::setTableDto(const Dto::TableInfoDto &dto)
static Qt::Alignment alignmentFromString(const QString &str)
{
m_currentTableInfo.emplace(dto);
if (str == "left")
return Qt::AlignLeft;
if (str == "right")
return Qt::AlignRight;
if (str == "center")
return Qt::AlignHCenter;
return Qt::AlignLeft;
}
void IssuesWidget::updateTable()
{
if (!m_currentTableInfo)
return;
// update issues table layout - for now just simple approach
TreeModel<> *issuesModel = new TreeModel(this);
QStringList columnHeaders;
QStringList hiddenColumns;
for (const Dto::ColumnInfoDto &column : dto.columns) {
QList<bool> sortableColumns;
QList<int> columnWidths;
QList<Qt::Alignment> alignments;
for (const Dto::ColumnInfoDto &column : m_currentTableInfo->columns) {
columnHeaders << column.header.value_or(column.key);
if (!column.showByDefault)
hiddenColumns << column.key;
sortableColumns << column.canSort;
columnWidths << column.width;
alignments << alignmentFromString(column.alignment);
}
m_addedFilter->setText("0");
m_removedFilter->setText("0");
m_totalRows->setText(Tr::tr("Total rows:"));
issuesModel->setHeader(columnHeaders);
auto oldModel = m_issuesModel;
m_issuesModel = issuesModel;
m_issuesView->setModel(issuesModel);
delete oldModel;
m_issuesModel->clear();
m_issuesModel->setHeader(columnHeaders);
m_issuesModel->setAlignments(alignments);
m_headerView->setSortableColumns(sortableColumns);
m_headerView->setColumnWidths(columnWidths);
int counter = 0;
for (const QString &header : std::as_const(columnHeaders))
m_issuesView->setColumnHidden(counter++, hiddenColumns.contains(header));
m_headerView->resizeSections(QHeaderView::ResizeToContents);
}
static Links linksForIssue(const std::map<QString, Dto::Any> &issueRow)
static QList<LinkWithColumns> linksForIssue(const std::map<QString, Dto::Any> &issueRow,
const std::vector<Dto::ColumnInfoDto> &columnInfos)
{
Links links;
QList<LinkWithColumns> links;
auto end = issueRow.end();
auto findAndAppend = [&links, &issueRow, &end](const QString &path, const QString &line) {
auto findColumn = [columnInfos](const QString &columnKey) {
int col = 0;
for (auto it = columnInfos.cbegin(), end = columnInfos.cend(); it != end; ++it) {
if (it->key == columnKey)
return col;
++col;
}
return -1;
};
auto findAndAppend = [&links, &issueRow, &findColumn, &end](const QString &path,
const QString &line) {
QList<int> columns;
auto it = issueRow.find(path);
if (it != end) {
Link link{ FilePath::fromUserInput(it->second.getString()) };
columns.append(findColumn(it->first));
it = issueRow.find(line);
if (it != end)
if (it != end) {
link.targetLine = it->second.getDouble();
links.append(link);
columns.append(findColumn(it->first));
}
links.append({link, columns});
}
};
// do these always? or just for their "expected" kind
@@ -411,11 +464,12 @@ static Links linksForIssue(const std::map<QString, Dto::Any> &issueRow)
return links;
}
void IssuesWidget::addIssues(const Dto::IssueTableDto &dto)
void IssuesWidget::addIssues(const Dto::IssueTableDto &dto, int startRow)
{
QTC_ASSERT(m_currentTableInfo.has_value(), return);
if (dto.totalRowCount.has_value()) {
m_totalRowCount = dto.totalRowCount.value();
m_issuesModel->setExpectedRowCount(m_totalRowCount);
m_totalRows->setText(Tr::tr("Total rows:") + ' ' + QString::number(m_totalRowCount));
}
if (dto.totalAddedCount.has_value())
@@ -425,21 +479,32 @@ void IssuesWidget::addIssues(const Dto::IssueTableDto &dto)
const std::vector<Dto::ColumnInfoDto> &tableColumns = m_currentTableInfo->columns;
const std::vector<std::map<QString, Dto::Any>> &rows = dto.rows;
QList<ListItem *> items;
for (const auto &row : rows) {
QString id;
QStringList data;
QStringList toolTips;
for (const auto &column : tableColumns) {
const auto it = row.find(column.key);
if (it != row.end()) {
QString value = anyToSimpleString(it->second);
if (column.key == "id")
if (column.key == "id") {
value.prepend(m_currentPrefix);
id = value;
}
toolTips << value;
if (column.key.toLower().endsWith("path")) {
const FilePath fp = FilePath::fromUserInput(value);
value = QString("%1 [%2]").arg(fp.fileName(), fp.path());
}
data << value;
}
}
IssueTreeItem *it = new IssueTreeItem(data, data);
it->setLinks(linksForIssue(row));
m_issuesModel->rootItem()->appendChild(it);
IssueListItem *it = new IssueListItem(startRow++, id, data, toolTips);
it->setLinks(linksForIssue(row, tableColumns));
items.append(it);
}
m_issuesModel->setItems(items);
}
void IssuesWidget::onSearchParameterChanged()
@@ -448,10 +513,9 @@ void IssuesWidget::onSearchParameterChanged()
m_removedFilter->setText("0");
m_totalRows->setText(Tr::tr("Total rows:"));
m_issuesModel->rootItem()->removeChildren();
m_issuesModel->clear();
// new "first" time lookup
m_totalRowCount = 0;
m_lastRequestedOffset = 0;
IssueListSearch search = searchFromUi();
search.computeTotalRowCount = true;
fetchIssues(search);
@@ -503,7 +567,7 @@ void IssuesWidget::updateBasicProjectInfo(std::optional<Dto::ProjectInfoDto> inf
button->setCheckable(true);
connect(button, &QToolButton::clicked, this, [this, prefix = kind.prefix]{
m_currentPrefix = prefix;
updateTableView();
fetchTable();
});
m_typesButtonGroup->addButton(button, ++buttonId);
m_typesLayout->addWidget(button);
@@ -535,27 +599,6 @@ void IssuesWidget::updateBasicProjectInfo(std::optional<Dto::ProjectInfoDto> inf
m_versionStart->setCurrentIndex(m_versionDates.count() - 1);
}
void IssuesWidget::updateTableView()
{
QTC_ASSERT(!m_currentPrefix.isEmpty(), return);
// fetch table dto and apply, on done fetch first data for the selected issues
const auto tableHandler = [this](const Dto::TableInfoDto &dto) { setTableDto(dto); };
const auto setupHandler = [this](TaskTree *) { m_issuesView->showProgressIndicator(); };
const auto doneHandler = [this](DoneWith result) {
if (result == DoneWith::Error) {
m_issuesView->hideProgressIndicator();
return;
}
// first time lookup... should we cache and maybe represent old data?
m_totalRowCount = 0;
m_lastRequestedOffset = 0;
IssueListSearch search = searchFromUi();
search.computeTotalRowCount = true;
fetchIssues(search);
};
m_taskTreeRunner.start(tableInfoRecipe(m_currentPrefix, tableHandler), setupHandler, doneHandler);
}
void IssuesWidget::setFiltersEnabled(bool enabled)
{
m_addedFilter->setEnabled(enabled);
@@ -581,143 +624,168 @@ IssueListSearch IssuesWidget::searchFromUi() const
search.state = "added";
else if (m_removedFilter->isChecked())
search.state = "removed";
if (int column = m_headerView->currentSortColumn() != -1) {
QTC_ASSERT(m_currentTableInfo, return search);
QTC_ASSERT((ulong)column < m_currentTableInfo->columns.size(), return search);
search.sort = m_currentTableInfo->columns.at(m_headerView->currentSortColumn()).key
+ (m_headerView->currentSortOrder() == SortOrder::Ascending ? " asc" : " desc");
}
return search;
}
void IssuesWidget::fetchTable()
{
QTC_ASSERT(!m_currentPrefix.isEmpty(), return);
// fetch table dto and apply, on done fetch first data for the selected issues
const auto tableHandler = [this](const Dto::TableInfoDto &dto) {
m_currentTableInfo.emplace(dto);
};
const auto setupHandler = [this](TaskTree *) {
m_totalRowCount = 0;
m_currentTableInfo.reset();
m_issuesView->showProgressIndicator();
};
const auto doneHandler = [this](DoneWith result) {
if (result == DoneWith::Error) {
m_issuesView->hideProgressIndicator();
return;
}
// first time lookup... should we cache and maybe represent old data?
updateTable();
IssueListSearch search = searchFromUi();
search.computeTotalRowCount = true;
fetchIssues(search);
};
m_taskTreeRunner.start(tableInfoRecipe(m_currentPrefix, tableHandler), setupHandler, doneHandler);
}
void IssuesWidget::fetchIssues(const IssueListSearch &search)
{
const auto issuesHandler = [this](const Dto::IssueTableDto &dto) { addIssues(dto); };
const auto issuesHandler = [this, startRow = search.offset](const Dto::IssueTableDto &dto) {
addIssues(dto, startRow);
};
const auto setupHandler = [this](TaskTree *) { m_issuesView->showProgressIndicator(); };
const auto doneHandler = [this](DoneWith) { m_issuesView->hideProgressIndicator(); };
m_taskTreeRunner.start(issueTableRecipe(search, issuesHandler), setupHandler, doneHandler);
}
void IssuesWidget::fetchMoreIssues()
void IssuesWidget::onFetchRequested(int startRow, int limit)
{
if (m_lastRequestedOffset == m_issuesModel->rowCount())
if (m_taskTreeRunner.isRunning())
return;
IssueListSearch search = searchFromUi();
m_lastRequestedOffset = m_issuesModel->rowCount();
search.offset = m_lastRequestedOffset;
search.offset = startRow;
search.limit = limit;
fetchIssues(search);
}
AxivionOutputPane::AxivionOutputPane(QObject *parent)
: IOutputPane(parent)
class AxivionOutputPane final : public IOutputPane
{
setId("Axivion");
setDisplayName(Tr::tr("Axivion"));
setPriorityInStatusBar(-50);
public:
explicit AxivionOutputPane(QObject *parent)
: IOutputPane(parent)
{
setId("Axivion");
setDisplayName(Tr::tr("Axivion"));
setPriorityInStatusBar(-50);
m_outputWidget = new QStackedWidget;
DashboardWidget *dashboardWidget = new DashboardWidget(m_outputWidget);
m_outputWidget->addWidget(dashboardWidget);
IssuesWidget *issuesWidget = new IssuesWidget(m_outputWidget);
m_outputWidget->addWidget(issuesWidget);
QTextBrowser *browser = new QTextBrowser(m_outputWidget);
m_outputWidget->addWidget(browser);
}
m_outputWidget = new QStackedWidget;
DashboardWidget *dashboardWidget = new DashboardWidget(m_outputWidget);
m_outputWidget->addWidget(dashboardWidget);
IssuesWidget *issuesWidget = new IssuesWidget(m_outputWidget);
m_outputWidget->addWidget(issuesWidget);
AxivionOutputPane::~AxivionOutputPane()
{
if (!m_outputWidget->parent())
delete m_outputWidget;
}
QPalette pal = m_outputWidget->palette();
pal.setColor(QPalette::Window, creatorTheme()->color(Theme::Color::BackgroundColorNormal));
m_outputWidget->setPalette(pal);
QWidget *AxivionOutputPane::outputWidget(QWidget *parent)
{
if (m_outputWidget)
m_outputWidget->setParent(parent);
else
QTC_CHECK(false);
return m_outputWidget;
}
m_showDashboard = new QToolButton(m_outputWidget);
m_showDashboard->setIcon(Icons::HOME_TOOLBAR.icon());
m_showDashboard->setToolTip(Tr::tr("Show dashboard"));
m_showDashboard->setCheckable(true);
m_showDashboard->setChecked(true);
connect(m_showDashboard, &QToolButton::clicked, this, [this] {
QTC_ASSERT(m_outputWidget, return);
m_outputWidget->setCurrentIndex(0);
});
QList<QWidget *> AxivionOutputPane::toolBarWidgets() const
{
QList<QWidget *> buttons;
auto showDashboard = new QToolButton(m_outputWidget);
showDashboard->setIcon(Icons::HOME_TOOLBAR.icon());
showDashboard->setToolTip(Tr::tr("Show dashboard"));
connect(showDashboard, &QToolButton::clicked, this, [this]{
QTC_ASSERT(m_outputWidget, return);
m_outputWidget->setCurrentIndex(0);
});
buttons.append(showDashboard);
auto showIssues = new QToolButton(m_outputWidget);
showIssues->setIcon(Icons::ZOOM_TOOLBAR.icon());
showIssues->setToolTip(Tr::tr("Search for issues"));
connect(showIssues, &QToolButton::clicked, this, [this]{
QTC_ASSERT(m_outputWidget, return);
m_outputWidget->setCurrentIndex(1);
if (auto issues = static_cast<IssuesWidget *>(m_outputWidget->widget(1)))
issues->updateUi();
});
buttons.append(showIssues);
return buttons;
}
m_showIssues = new QToolButton(m_outputWidget);
m_showIssues->setIcon(Icons::ZOOM_TOOLBAR.icon());
m_showIssues->setToolTip(Tr::tr("Search for issues"));
m_showIssues->setCheckable(true);
connect(m_showIssues, &QToolButton::clicked, this, [this] {
QTC_ASSERT(m_outputWidget, return);
m_outputWidget->setCurrentIndex(1);
if (auto issues = static_cast<IssuesWidget *>(m_outputWidget->widget(1)))
issues->updateUi();
});
void AxivionOutputPane::clearContents()
{
}
void AxivionOutputPane::setFocus()
{
}
bool AxivionOutputPane::hasFocus() const
{
return false;
}
bool AxivionOutputPane::canFocus() const
{
return true;
}
bool AxivionOutputPane::canNavigate() const
{
return true;
}
bool AxivionOutputPane::canNext() const
{
return false;
}
bool AxivionOutputPane::canPrevious() const
{
return false;
}
void AxivionOutputPane::goToNext()
{
}
void AxivionOutputPane::goToPrev()
{
}
void AxivionOutputPane::updateDashboard()
{
if (auto dashboard = static_cast<DashboardWidget *>(m_outputWidget->widget(0))) {
dashboard->updateUi();
m_outputWidget->setCurrentIndex(0);
if (dashboard->hasProject())
flash();
connect(m_outputWidget, &QStackedWidget::currentChanged, this, [this](int idx) {
m_showDashboard->setChecked(idx == 0);
m_showIssues->setChecked(idx == 1);
});
}
}
void AxivionOutputPane::updateAndShowRule(const QString &ruleHtml)
{
if (auto browser = static_cast<QTextBrowser *>(m_outputWidget->widget(2))) {
browser->setText(ruleHtml);
if (!ruleHtml.isEmpty()) {
m_outputWidget->setCurrentIndex(2);
popup(IOutputPane::NoModeSwitch);
~AxivionOutputPane()
{
if (!m_outputWidget->parent())
delete m_outputWidget;
}
QWidget *outputWidget(QWidget *parent) final
{
if (m_outputWidget)
m_outputWidget->setParent(parent);
else
QTC_CHECK(false);
return m_outputWidget;
}
QList<QWidget *> toolBarWidgets() const final
{
return {m_showDashboard, m_showIssues};
}
void clearContents() final {}
void setFocus() final {}
bool hasFocus() const final { return false; }
bool canFocus() const final { return true; }
bool canNavigate() const final { return true; }
bool canNext() const final { return false; }
bool canPrevious() const final { return false; }
void goToNext() final {}
void goToPrev() final {}
void updateDashboard()
{
if (auto dashboard = static_cast<DashboardWidget *>(m_outputWidget->widget(0))) {
dashboard->updateUi();
m_outputWidget->setCurrentIndex(0);
if (dashboard->hasProject())
flash();
}
}
private:
QStackedWidget *m_outputWidget = nullptr;
QToolButton *m_showDashboard = nullptr;
QToolButton *m_showIssues = nullptr;
};
static QPointer<AxivionOutputPane> theAxivionOutputPane;
void setupAxivionOutputPane(QObject *guard)
{
theAxivionOutputPane = new AxivionOutputPane(guard);
}
void updateDashboard()
{
QTC_ASSERT(theAxivionOutputPane, return);
theAxivionOutputPane->updateDashboard();
}
} // Axivion::Internal

View File

@@ -3,38 +3,11 @@
#pragma once
#include <coreplugin/ioutputpane.h>
QT_BEGIN_NAMESPACE
class QStackedWidget;
QT_END_NAMESPACE
#include <QObject>
namespace Axivion::Internal {
class AxivionOutputPane : public Core::IOutputPane
{
Q_OBJECT
public:
explicit AxivionOutputPane(QObject *parent = nullptr);
~AxivionOutputPane();
// IOutputPane interface
QWidget *outputWidget(QWidget *parent) override;
QList<QWidget *> toolBarWidgets() const override;
void clearContents() override;
void setFocus() override;
bool hasFocus() const override;
bool canFocus() const override;
bool canNavigate() const override;
bool canNext() const override;
bool canPrevious() const override;
void goToNext() override;
void goToPrev() override;
void updateDashboard();
void updateAndShowRule(const QString &ruleHtml);
private:
QStackedWidget *m_outputWidget = nullptr;
};
void setupAxivionOutputPane(QObject *guard);
void updateDashboard();
} // Axivion::Internal

View File

@@ -7,13 +7,16 @@
#include "axivionprojectsettings.h"
#include "axivionsettings.h"
#include "axiviontr.h"
#include "credentialquery.h"
#include "dashboard/dto.h"
#include "dashboard/error.h"
#include <coreplugin/editormanager/documentmodel.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/inavigationwidgetfactory.h>
#include <coreplugin/messagemanager.h>
#include <coreplugin/navigationwidget.h>
#include <extensionsystem/iplugin.h>
#include <extensionsystem/pluginmanager.h>
@@ -31,19 +34,26 @@
#include <utils/algorithm.h>
#include <utils/async.h>
#include <utils/checkablemessagebox.h>
#include <utils/environment.h>
#include <utils/networkaccessmanager.h>
#include <utils/qtcassert.h>
#include <utils/utilsicons.h>
#include <QAction>
#include <QDesktopServices>
#include <QInputDialog>
#include <QMessageBox>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QTextBrowser>
#include <QTimer>
#include <QUrlQuery>
#include <memory>
constexpr char AxivionTextMarkId[] = "AxivionTextMark";
constexpr char s_axivionTextMarkId[] = "AxivionTextMark";
constexpr char s_axivionKeychainService[] = "keychain.axivion.qtcreator";
using namespace Core;
using namespace ProjectExplorer;
@@ -96,6 +106,42 @@ QString anyToSimpleString(const Dto::Any &any)
return {};
}
static QString apiTokenDescription()
{
const QString ua = "Axivion" + QCoreApplication::applicationName() + "Plugin/"
+ QCoreApplication::applicationVersion();
QString user = Utils::qtcEnvironmentVariable("USERNAME");
if (user.isEmpty())
user = Utils::qtcEnvironmentVariable("USER");
return "Automatically created by " + ua + " on " + user + "@" + QSysInfo::machineHostName();
}
static QString credentialKey()
{
const auto escape = [](const QString &string) {
QString escaped = string;
return escaped.replace('\\', "\\\\").replace('@', "\\@");
};
return escape(settings().server.dashboard) + '@' + escape(settings().server.username);
}
static DashboardInfo toDashboardInfo(const QUrl &source, const Dto::DashboardInfoDto &infoDto)
{
const QVersionNumber versionNumber = infoDto.dashboardVersionNumber
? QVersionNumber::fromString(*infoDto.dashboardVersionNumber) : QVersionNumber();
QStringList projects;
QHash<QString, QUrl> projectUrls;
if (infoDto.projects) {
for (const Dto::ProjectReferenceDto &project : *infoDto.projects) {
projects.push_back(project.name);
projectUrls.insert(project.name, project.url);
}
}
return {source, versionNumber, projects, projectUrls, infoDto.checkCredentialsUrl};
}
QString IssueListSearch::toQuery() const
{
if (kind.isEmpty())
@@ -118,18 +164,24 @@ QString IssueListSearch::toQuery() const
QString::fromUtf8((QUrl::toPercentEncoding(owner)))));
}
if (!filter_path.isEmpty()) {
result.append(QString("&filter_path=%1").arg(
result.append(QString("&filter_any path=%1").arg(
QString::fromUtf8(QUrl::toPercentEncoding(filter_path))));
}
if (!state.isEmpty())
result.append(QString("&state=%1").arg(state));
if (computeTotalRowCount)
result.append("&computeTotalRowCount=true");
if (!sort.isEmpty())
result.append(QString("&sort=%1").arg(
QString::fromUtf8(QUrl::toPercentEncoding(sort))));
return result;
}
enum class ServerAccess { Unknown, NoAuthorization, WithAuthorization };
class AxivionPluginPrivate : public QObject
{
Q_OBJECT
public:
AxivionPluginPrivate();
void handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
@@ -141,9 +193,18 @@ public:
void clearAllMarks();
void handleIssuesForFile(const Dto::FileViewDto &fileView);
void fetchIssueInfo(const QString &id);
void setIssueDetails(const QString &issueDetailsHtml);
void handleAnchorClicked(const QUrl &url);
signals:
void issueDetailsChanged(const QString &issueDetailsHtml);
public:
// TODO: Should be set to Unknown on server address change in settings.
ServerAccess m_serverAccess = ServerAccess::Unknown;
// TODO: Should be cleared on username change in settings.
std::optional<QByteArray> m_apiToken;
NetworkAccessManager m_networkAccessManager;
AxivionOutputPane m_axivionOutputPane;
std::optional<DashboardInfo> m_dashboardInfo;
std::optional<Dto::ProjectInfoDto> m_currentProjectInfo;
Project *m_project = nullptr;
@@ -158,13 +219,16 @@ static AxivionPluginPrivate *dd = nullptr;
class AxivionTextMark : public TextMark
{
public:
AxivionTextMark(const FilePath &filePath, const Dto::LineMarkerDto &issue)
: TextMark(filePath, issue.startLine, {Tr::tr("Axivion"), AxivionTextMarkId})
AxivionTextMark(const FilePath &filePath, const Dto::LineMarkerDto &issue,
std::optional<Theme::Color> color)
: TextMark(filePath, issue.startLine, {"Axivion", s_axivionTextMarkId})
{
const QString markText = issue.description;
const QString id = issue.kind + QString::number(issue.id.value_or(-1));
setToolTip(id + markText);
setToolTip(id + '\n' + markText);
setIcon(iconForIssue(issue.kind));
if (color)
setColor(*color);
setPriority(TextMark::NormalPriority);
setLineAnnotation(markText);
setActionsProvider([id] {
@@ -247,7 +311,7 @@ void AxivionPluginPrivate::onStartupProjectChanged(Project *project)
m_project = project;
clearAllMarks();
m_currentProjectInfo = {};
m_axivionOutputPane.updateDashboard();
updateDashboard();
if (!m_project)
return;
@@ -259,10 +323,7 @@ void AxivionPluginPrivate::onStartupProjectChanged(Project *project)
static QUrl urlForProject(const QString &projectName)
{
QString dashboard = settings().server.dashboard;
if (!dashboard.endsWith(QLatin1Char('/')))
dashboard += QLatin1Char('/');
return QUrl(dashboard).resolved(QStringLiteral("api/projects/")).resolved(projectName);
return QUrl(settings().server.dashboard).resolved(QString("api/projects/")).resolved(projectName);
}
static constexpr int httpStatusCodeOk = 200;
@@ -271,28 +332,22 @@ constexpr char s_jsonContentType[] = "application/json";
static Group fetchHtmlRecipe(const QUrl &url, const std::function<void(const QByteArray &)> &handler)
{
struct StorageData
{
QByteArray credentials;
};
// TODO: Refactor so that it's a common code with fetchDataRecipe().
const auto onQuerySetup = [url](NetworkQuery &query) {
if (dd->m_serverAccess == ServerAccess::Unknown)
return SetupResult::StopWithError; // TODO: start authorizationRecipe()?
const Storage<StorageData> storage;
const auto onCredentialSetup = [storage] {
storage->credentials = QByteArrayLiteral("AxToken ") + settings().server.token.toUtf8();
};
const auto onQuerySetup = [storage, url](NetworkQuery &query) {
QNetworkRequest request(url);
request.setRawHeader("Accept", s_htmlContentType);
request.setRawHeader("Authorization", storage->credentials);
if (dd->m_serverAccess == ServerAccess::WithAuthorization && dd->m_apiToken)
request.setRawHeader("Authorization", "AxToken " + *dd->m_apiToken);
const QByteArray ua = "Axivion" + QCoreApplication::applicationName().toUtf8() +
"Plugin/" + QCoreApplication::applicationVersion().toUtf8();
request.setRawHeader("X-Axivion-User-Agent", ua);
query.setRequest(request);
query.setNetworkAccessManager(&dd->m_networkAccessManager);
return SetupResult::Continue;
};
const auto onQueryDone = [url, handler](const NetworkQuery &query, DoneWith doneWith) {
QNetworkReply *reply = query.reply();
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
@@ -309,125 +364,49 @@ static Group fetchHtmlRecipe(const QUrl &url, const std::function<void(const QBy
}
return DoneResult::Error;
};
const Group recipe {
storage,
Sync(onCredentialSetup),
NetworkQueryTask(onQuerySetup, onQueryDone),
};
return recipe;
return {NetworkQueryTask(onQuerySetup, onQueryDone)};
}
template <typename DtoType>
struct GetDtoStorage
{
QByteArray credential;
QUrl url;
std::optional<QByteArray> credential;
std::optional<DtoType> dtoData;
};
template <typename DtoType>
static Group getDtoRecipe(const Storage<GetDtoStorage<DtoType>> &dtoStorage)
{
const Storage<QByteArray> storage;
const auto onNetworkQuerySetup = [dtoStorage](NetworkQuery &query) {
QNetworkRequest request(dtoStorage->url);
request.setRawHeader("Accept", s_jsonContentType);
request.setRawHeader("Authorization", dtoStorage->credential);
const QByteArray ua = "Axivion" + QCoreApplication::applicationName().toUtf8() +
"Plugin/" + QCoreApplication::applicationVersion().toUtf8();
request.setRawHeader("X-Axivion-User-Agent", ua);
query.setRequest(request);
query.setNetworkAccessManager(&dd->m_networkAccessManager);
};
const auto onNetworkQueryDone = [storage](const NetworkQuery &query, DoneWith doneWith) {
QNetworkReply *reply = query.reply();
const QNetworkReply::NetworkError error = reply->error();
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader)
.toString()
.split(';')
.constFirst()
.trimmed()
.toLower();
if (doneWith == DoneWith::Success && statusCode == httpStatusCodeOk
&& contentType == s_jsonContentType) {
*storage = reply->readAll();
return DoneResult::Success;
}
const auto getError = [&]() -> Error {
if (contentType == s_jsonContentType) {
try {
return DashboardError(reply->url(), statusCode,
reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(),
Dto::ErrorDto::deserialize(reply->readAll()));
} catch (const Dto::invalid_dto_exception &) {
// ignore
}
}
if (statusCode != 0) {
return HttpError(reply->url(), statusCode,
reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(),
QString::fromUtf8(reply->readAll())); // encoding?
}
return NetworkError(reply->url(), error, reply->errorString());
};
MessageManager::writeFlashing(QStringLiteral("Axivion: %1").arg(getError().message()));
return DoneResult::Error;
};
const auto onDeserializeSetup = [storage](Async<DtoType> &task) {
const auto deserialize = [](QPromise<DtoType> &promise, const QByteArray &input) {
promise.addResult(DtoType::deserialize(input));
};
task.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
task.setConcurrentCallData(deserialize, *storage);
};
const auto onDeserializeDone = [dtoStorage](const Async<DtoType> &task, DoneWith doneWith) {
if (doneWith == DoneWith::Success)
dtoStorage->dtoData = task.future().result();
};
const Group recipe {
storage,
NetworkQueryTask(onNetworkQuerySetup, onNetworkQueryDone),
AsyncTask<DtoType>(onDeserializeSetup, onDeserializeDone)
};
return recipe;
};
template <typename DtoType>
struct PostDtoStorage
{
QByteArray credential;
QUrl url;
std::optional<QByteArray> credential;
QByteArray csrfToken;
QByteArray writeData;
std::optional<DtoType> dtoData;
};
template <typename DtoType>
static Group postDtoRecipe(const Storage<PostDtoStorage<DtoType>> &dtoStorage)
template <typename DtoType, template <typename> typename DtoStorageType>
static Group dtoRecipe(const Storage<DtoStorageType<DtoType>> &dtoStorage)
{
const Storage<QByteArray> storage;
const auto onNetworkQuerySetup = [dtoStorage](NetworkQuery &query) {
QNetworkRequest request(dtoStorage->url);
request.setRawHeader("Accept", s_jsonContentType);
request.setRawHeader("Authorization", dtoStorage->credential);
if (dtoStorage->credential) // Unauthorized access otherwise
request.setRawHeader("Authorization", *dtoStorage->credential);
const QByteArray ua = "Axivion" + QCoreApplication::applicationName().toUtf8() +
"Plugin/" + QCoreApplication::applicationVersion().toUtf8();
request.setRawHeader("X-Axivion-User-Agent", ua);
request.setRawHeader("AX-CSRF-Token", dtoStorage->csrfToken);
if constexpr (std::is_same_v<DtoStorageType<DtoType>, PostDtoStorage<DtoType>>) {
request.setRawHeader("Content-Type", "application/json");
request.setRawHeader("AX-CSRF-Token", dtoStorage->csrfToken);
query.setWriteData(dtoStorage->writeData);
query.setOperation(NetworkOperation::Post);
}
query.setRequest(request);
query.setWriteData(dtoStorage->writeData);
query.setOperation(NetworkOperation::Post);
query.setNetworkAccessManager(&dd->m_networkAccessManager);
};
@@ -464,76 +443,220 @@ static Group postDtoRecipe(const Storage<PostDtoStorage<DtoType>> &dtoStorage)
}
return NetworkError(reply->url(), error, reply->errorString());
};
MessageManager::writeFlashing(QStringLiteral("Axivion: %1").arg(getError().message()));
MessageManager::writeDisrupting(QString("Axivion: %1").arg(getError().message()));
return DoneResult::Error;
};
const auto onDeserializeSetup = [storage](Async<DtoType> &task) {
const auto deserialize = [](QPromise<DtoType> &promise, const QByteArray &input) {
promise.addResult(DtoType::deserialize(input));
const auto onDeserializeSetup = [storage](Async<expected_str<DtoType>> &task) {
const auto deserialize = [](QPromise<expected_str<DtoType>> &promise, const QByteArray &input) {
promise.addResult(DtoType::deserializeExpected(input));
};
task.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
task.setConcurrentCallData(deserialize, *storage);
};
const auto onDeserializeDone = [dtoStorage](const Async<DtoType> &task, DoneWith doneWith) {
if (doneWith == DoneWith::Success)
dtoStorage->dtoData = task.future().result();
const auto onDeserializeDone = [dtoStorage](const Async<expected_str<DtoType>> &task,
DoneWith doneWith) {
if (doneWith == DoneWith::Success && task.isResultAvailable()) {
const auto result = task.result();
if (result) {
dtoStorage->dtoData = *result;
return DoneResult::Success;
}
MessageManager::writeFlashing(QString("Axivion: %1").arg(result.error()));
} else {
MessageManager::writeFlashing(QString("Axivion: %1")
.arg(Tr::tr("Unknown Dto structure deserialization error.")));
}
return DoneResult::Error;
};
const Group recipe {
return {
storage,
NetworkQueryTask(onNetworkQuerySetup, onNetworkQueryDone),
AsyncTask<DtoType>(onDeserializeSetup, onDeserializeDone)
AsyncTask<expected_str<DtoType>>(onDeserializeSetup, onDeserializeDone)
};
return recipe;
};
}
static QString credentialOperationMessage(CredentialOperation operation)
{
switch (operation) {
case CredentialOperation::Get:
return Tr::tr("The ApiToken cannot be read in a secure way.");
case CredentialOperation::Set:
return Tr::tr("The ApiToken cannot be stored in a secure way.");
case CredentialOperation::Delete:
return Tr::tr("The ApiToken cannot be deleted in a secure way.");
}
return {};
}
static void handleCredentialError(const CredentialQuery &credential)
{
const QString keyChainMessage = credential.errorString().isEmpty() ? QString()
: QString(" %1").arg(Tr::tr("Key chain message: \"%1\".").arg(credential.errorString()));
MessageManager::writeFlashing(QString("Axivion: %1")
.arg(credentialOperationMessage(credential.operation()) + keyChainMessage));
}
static Group authorizationRecipe()
{
const Storage<GetDtoStorage<Dto::DashboardInfoDto>> unauthorizedDashboardStorage;
const auto onUnauthorizedGroupSetup = [unauthorizedDashboardStorage] {
if (dd->m_serverAccess != ServerAccess::NoAuthorization)
return SetupResult::StopWithSuccess;
unauthorizedDashboardStorage->url = QUrl(settings().server.dashboard);
return SetupResult::Continue;
};
const auto onUnauthorizedGroupDone = [unauthorizedDashboardStorage] {
if (unauthorizedDashboardStorage->dtoData) {
dd->m_serverAccess = ServerAccess::NoAuthorization;
dd->m_dashboardInfo = toDashboardInfo(settings().server.dashboard,
*unauthorizedDashboardStorage->dtoData);
} else {
dd->m_serverAccess = ServerAccess::WithAuthorization;
}
return DoneResult::Success;
};
const auto onCredentialLoopCondition = [](int) {
return dd->m_serverAccess == ServerAccess::WithAuthorization && !dd->m_apiToken;
};
const auto onGetCredentialSetup = [](CredentialQuery &credential) {
credential.setOperation(CredentialOperation::Get);
credential.setService(s_axivionKeychainService);
credential.setKey(credentialKey());
};
const auto onGetCredentialDone = [](const CredentialQuery &credential, DoneWith result) {
if (result == DoneWith::Success)
dd->m_apiToken = credential.data();
else
handleCredentialError(credential);
// TODO: In case of an error we are multiplying the ApiTokens on Axivion server for each
// Creator run, but at least things should continue to work OK in the current session.
return DoneResult::Success;
};
const Storage<QString> passwordStorage;
const Storage<GetDtoStorage<Dto::DashboardInfoDto>> dashboardStorage;
const auto onDashboardGroupSetup = [passwordStorage, dashboardStorage] {
if (dd->m_apiToken)
return SetupResult::StopWithSuccess;
bool ok = false;
const QString text(Tr::tr("Enter the password for:\nDashboard: %1\nUser: %2")
.arg(settings().server.dashboard, settings().server.username));
*passwordStorage = QInputDialog::getText(ICore::mainWindow(),
Tr::tr("Axivion Server Password"), text, QLineEdit::Password, {}, &ok);
if (!ok)
return SetupResult::StopWithError;
const QString credential = settings().server.username + ':' + *passwordStorage;
dashboardStorage->credential = "Basic " + credential.toUtf8().toBase64();
dashboardStorage->url = QUrl(settings().server.dashboard);
return SetupResult::Continue;
};
const Storage<PostDtoStorage<Dto::ApiTokenInfoDto>> apiTokenStorage;
const auto onApiTokenGroupSetup = [passwordStorage, dashboardStorage, apiTokenStorage] {
if (!dashboardStorage->dtoData)
return SetupResult::StopWithSuccess;
dd->m_dashboardInfo = toDashboardInfo(settings().server.dashboard,
*dashboardStorage->dtoData);
const Dto::DashboardInfoDto &dashboardDto = *dashboardStorage->dtoData;
if (!dashboardDto.userApiTokenUrl)
return SetupResult::StopWithError;
apiTokenStorage->credential = dashboardStorage->credential;
apiTokenStorage->url
= QUrl(settings().server.dashboard).resolved(*dashboardDto.userApiTokenUrl);
apiTokenStorage->csrfToken = dashboardDto.csrfToken.toUtf8();
const Dto::ApiTokenCreationRequestDto requestDto{*passwordStorage, "IdePlugin",
apiTokenDescription(), 0};
apiTokenStorage->writeData = requestDto.serialize();
return SetupResult::Continue;
};
const auto onSetCredentialSetup = [apiTokenStorage](CredentialQuery &credential) {
if (!apiTokenStorage->dtoData || !apiTokenStorage->dtoData->token)
return SetupResult::StopWithSuccess;
dd->m_apiToken = apiTokenStorage->dtoData->token->toUtf8();
credential.setOperation(CredentialOperation::Set);
credential.setService(s_axivionKeychainService);
credential.setKey(credentialKey());
credential.setData(*dd->m_apiToken);
return SetupResult::Continue;
};
const auto onSetCredentialDone = [](const CredentialQuery &credential) {
handleCredentialError(credential);
// TODO: In case of an error we are multiplying the ApiTokens on Axivion server for each
// Creator run, but at least things should continue to work OK in the current session.
return DoneResult::Success;
};
return {
Group {
unauthorizedDashboardStorage,
onGroupSetup(onUnauthorizedGroupSetup),
dtoRecipe(unauthorizedDashboardStorage),
onGroupDone(onUnauthorizedGroupDone)
},
Group {
LoopUntil(onCredentialLoopCondition),
CredentialQueryTask(onGetCredentialSetup, onGetCredentialDone),
Group {
passwordStorage,
dashboardStorage,
onGroupSetup(onDashboardGroupSetup),
Group { // GET DashboardInfoDto
finishAllAndSuccess,
dtoRecipe(dashboardStorage)
},
Group { // POST ApiTokenCreationRequestDto, GET ApiTokenInfoDto.
apiTokenStorage,
onGroupSetup(onApiTokenGroupSetup),
dtoRecipe(apiTokenStorage),
CredentialQueryTask(onSetCredentialSetup, onSetCredentialDone, CallDoneIf::Error)
}
}
}
};
}
template<typename DtoType>
static Group fetchDataRecipe(const QUrl &url, const std::function<void(const DtoType &)> &handler)
{
const Storage<GetDtoStorage<DtoType>> dtoStorage;
const auto onCredentialSetup = [dtoStorage, url] {
dtoStorage->credential = QByteArrayLiteral("AxToken ") + settings().server.token.toUtf8();
dtoStorage->url = url;
};
const auto onDtoSetup = [dtoStorage, url] {
if (!dd->m_apiToken)
return SetupResult::StopWithError;
dtoStorage->credential = "AxToken " + *dd->m_apiToken;
dtoStorage->url = url;
return SetupResult::Continue;
};
const auto onDtoDone = [dtoStorage, handler] {
if (dtoStorage->dtoData)
handler(*dtoStorage->dtoData);
};
const Group recipe {
dtoStorage,
Sync(onCredentialSetup),
authorizationRecipe(),
Group {
getDtoRecipe(dtoStorage),
dtoStorage,
onGroupSetup(onDtoSetup),
dtoRecipe(dtoStorage),
onGroupDone(onDtoDone)
}
};
return recipe;
}
static DashboardInfo toDashboardInfo(const QUrl &source, const Dto::DashboardInfoDto &infoDto)
{
const QVersionNumber versionNumber = infoDto.dashboardVersionNumber
? QVersionNumber::fromString(*infoDto.dashboardVersionNumber) : QVersionNumber();
QStringList projects;
QHash<QString, QUrl> projectUrls;
if (infoDto.projects) {
for (const Dto::ProjectReferenceDto &project : *infoDto.projects) {
projects.push_back(project.name);
projectUrls.insert(project.name, project.url);
}
}
return {source, versionNumber, projects, projectUrls, infoDto.checkCredentialsUrl};
}
Group dashboardInfoRecipe(const DashboardInfoHandler &handler)
{
const auto onSetup = [handler] {
@@ -549,17 +672,15 @@ Group dashboardInfoRecipe(const DashboardInfoHandler &handler)
handler(make_unexpected(QString("Error"))); // TODO: Collect error message in the storage.
};
const QUrl url(settings().server.dashboard);
const auto resultHandler = [handler, url](const Dto::DashboardInfoDto &data) {
dd->m_dashboardInfo = toDashboardInfo(url, data);
const auto resultHandler = [handler](const Dto::DashboardInfoDto &data) {
dd->m_dashboardInfo = toDashboardInfo(settings().server.dashboard, data);
if (handler)
handler(*dd->m_dashboardInfo);
};
const Group root {
onGroupSetup(onSetup), // Stops if cache exists.
fetchDataRecipe<Dto::DashboardInfoDto>(url, resultHandler),
fetchDataRecipe<Dto::DashboardInfoDto>(settings().server.dashboard, resultHandler),
onGroupDone(onDone, CallDoneIf::Error)
};
return root;
@@ -575,7 +696,6 @@ Group issueTableRecipe(const IssueListSearch &search, const IssueTableHandler &h
const QUrl url = urlForProject(dd->m_currentProjectInfo.value().name + '/')
.resolved(QString("issues" + query));
return fetchDataRecipe<Dto::IssueTableDto>(url, handler);
}
@@ -594,10 +714,6 @@ Group issueHtmlRecipe(const QString &issueId, const HtmlHandler &handler)
{
QTC_ASSERT(dd->m_currentProjectInfo, return {}); // TODO: Call handler with unexpected?
QString dashboard = settings().server.dashboard;
if (!dashboard.endsWith(QLatin1Char('/')))
dashboard += QLatin1Char('/');
const QUrl url = urlForProject(dd->m_currentProjectInfo.value().name + '/')
.resolved(QString("issues/"))
.resolved(QString(issueId + '/'))
@@ -614,21 +730,27 @@ void AxivionPluginPrivate::fetchProjectInfo(const QString &projectName)
clearAllMarks();
if (projectName.isEmpty()) {
m_currentProjectInfo = {};
m_axivionOutputPane.updateDashboard();
updateDashboard();
return;
}
const auto onTaskTreeSetup = [this, projectName](TaskTree &taskTree) {
if (!m_dashboardInfo)
if (!m_dashboardInfo) {
MessageManager::writeDisrupting(QString("Axivion: %1")
.arg(Tr::tr("Fetching DashboardInfo error.")));
return SetupResult::StopWithError;
}
const auto it = m_dashboardInfo->projectUrls.constFind(projectName);
if (it == m_dashboardInfo->projectUrls.constEnd())
if (it == m_dashboardInfo->projectUrls.constEnd()) {
MessageManager::writeDisrupting(QString("Axivion: %1")
.arg(Tr::tr("The DashboardInfo doesn't contain project \"%1\".").arg(projectName)));
return SetupResult::StopWithError;
}
const auto handler = [this](const Dto::ProjectInfoDto &data) {
m_currentProjectInfo = data;
m_axivionOutputPane.updateDashboard();
updateDashboard();
handleOpenedDocs();
};
@@ -661,12 +783,19 @@ void AxivionPluginPrivate::fetchIssueInfo(const QString &id)
const int idx = htmlText.indexOf("<div class=\"ax-issuedetails-table-container\">");
if (idx >= 0)
fixedHtml = "<html><body>" + htmlText.mid(idx);
dd->m_axivionOutputPane.updateAndShowRule(QString::fromUtf8(fixedHtml));
NavigationWidget::activateSubWidget("Axivion.Issue", Side::Right);
dd->setIssueDetails(QString::fromUtf8(fixedHtml));
};
m_issueInfoRunner.start(issueHtmlRecipe(id, ruleHandler));
}
void AxivionPluginPrivate::setIssueDetails(const QString &issueDetailsHtml)
{
emit issueDetailsChanged(issueDetailsHtml);
}
void AxivionPluginPrivate::handleOpenedDocs()
{
const QList<IDocument *> openDocuments = DocumentModel::openedDocuments();
@@ -687,7 +816,8 @@ void AxivionPluginPrivate::onDocumentOpened(IDocument *doc)
return;
const FilePath filePath = doc->filePath().relativeChildPath(m_project->projectDirectory());
QTC_ASSERT(!filePath.isEmpty(), return);
if (filePath.isEmpty())
return; // Empty is fine
const auto handler = [this](const Dto::FileViewDto &data) {
if (data.lineMarkers.empty())
@@ -718,7 +848,7 @@ void AxivionPluginPrivate::onDocumentClosed(IDocument *doc)
const TextMarks &marks = document->marks();
for (TextMark *mark : marks) {
if (mark->category().id == AxivionTextMarkId)
if (mark->category().id == s_axivionTextMarkId)
delete mark;
}
}
@@ -733,15 +863,76 @@ void AxivionPluginPrivate::handleIssuesForFile(const Dto::FileViewDto &fileView)
return;
const FilePath filePath = project->projectDirectory().pathAppended(fileView.fileName);
std::optional<Theme::Color> color = std::nullopt;
if (settings().highlightMarks())
color.emplace(Theme::Color(Theme::Bookmarks_TextMarkColor)); // FIXME!
for (const Dto::LineMarkerDto &marker : std::as_const(fileView.lineMarkers)) {
// FIXME the line location can be wrong (even the whole issue could be wrong)
// depending on whether this line has been changed since the last axivion run and the
// current state of the file - some magic has to happen here
new AxivionTextMark(filePath, marker);
new AxivionTextMark(filePath, marker, color);
}
}
void AxivionPluginPrivate::handleAnchorClicked(const QUrl &url)
{
QTC_ASSERT(dd, return);
QTC_ASSERT(dd->m_project, return);
if (!url.scheme().isEmpty()) {
const QString detail = Tr::tr("The activated link appears to be external.\n"
"Do you want to open \"%1\" with its default application?")
.arg(url.toString());
const QMessageBox::StandardButton pressed
= CheckableMessageBox::question(Core::ICore::dialogParent(),
Tr::tr("Open External Links"),
detail,
Key("AxivionOpenExternalLinks"));
if (pressed == QMessageBox::Yes)
QDesktopServices::openUrl(url);
return;
}
const QUrlQuery query(url);
if (query.isEmpty())
return;
Link link;
if (const QString path = query.queryItemValue("filename", QUrl::FullyDecoded); !path.isEmpty())
link.targetFilePath = m_project->projectDirectory().pathAppended(path);
if (const QString line = query.queryItemValue("line"); !line.isEmpty())
link.targetLine = line.toInt();
// column entry is wrong - so, ignore it
if (link.hasValidTarget() && link.targetFilePath.exists())
EditorManager::openEditorAt(link);
}
class AxivionIssueWidgetFactory final : public INavigationWidgetFactory
{
public:
AxivionIssueWidgetFactory()
{
setDisplayName(Tr::tr("Axivion"));
setId("Axivion.Issue");
setPriority(555);
}
NavigationView createWidget() final
{
QTC_ASSERT(dd, return {});
QTextBrowser *browser = new QTextBrowser;
browser->setOpenLinks(false);
NavigationView view;
view.widget = browser;
connect(dd, &AxivionPluginPrivate::issueDetailsChanged, browser, &QTextBrowser::setHtml);
connect(browser, &QTextBrowser::anchorClicked,
dd, &AxivionPluginPrivate::handleAnchorClicked);
return view;
}
};
void setupAxivionIssueWidgetFactory()
{
static AxivionIssueWidgetFactory issueWidgetFactory;
}
class AxivionPlugin final : public ExtensionSystem::IPlugin
{
Q_OBJECT
@@ -756,9 +947,12 @@ class AxivionPlugin final : public ExtensionSystem::IPlugin
void initialize() final
{
setupAxivionOutputPane(this);
dd = new AxivionPluginPrivate;
AxivionProjectSettings::setupProjectPanel();
setupAxivionIssueWidgetFactory();
connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
dd, &AxivionPluginPrivate::onStartupProjectChanged);
@@ -769,6 +963,12 @@ class AxivionPlugin final : public ExtensionSystem::IPlugin
}
};
void fetchIssueInfo(const QString &id)
{
QTC_ASSERT(dd, return);
dd->fetchIssueInfo(id);
}
} // Axivion::Internal
#include "axivionplugin.moc"

View File

@@ -31,6 +31,7 @@ struct IssueListSearch
QString versionEnd;
QString owner;
QString filter_path;
QString sort;
int offset = 0;
int limit = 150;
bool computeTotalRowCount = false;
@@ -72,6 +73,7 @@ bool handleCertificateIssue();
QIcon iconForIssue(const QString &prefix);
QString anyToSimpleString(const Dto::Any &any);
void fetchIssueInfo(const QString &id);
} // Axivion::Internal

View File

@@ -14,6 +14,7 @@
#include <solutions/tasking/tasktreerunner.h>
#include <utils/infolabel.h>
#include <utils/layoutbuilder.h>
#include <utils/qtcassert.h>
#include <QPushButton>
@@ -117,34 +118,33 @@ AxivionProjectSettingsWidget::AxivionProjectSettingsWidget(Project *project)
setUseGlobalSettingsCheckBoxVisible(false);
setUseGlobalSettingsLabelVisible(true);
setGlobalSettingsId("Axivion.Settings.General"); // FIXME move id to constants
// setup ui
auto verticalLayout = new QVBoxLayout(this);
verticalLayout->setContentsMargins(0, 0, 0, 0);
m_linkedProject = new QLabel(this);
verticalLayout->addWidget(m_linkedProject);
m_dashboardProjects = new QTreeWidget(this);
m_dashboardProjects->setHeaderHidden(true);
m_dashboardProjects->setRootIsDecorated(false);
verticalLayout->addWidget(new QLabel(Tr::tr("Dashboard projects:")));
verticalLayout->addWidget(m_dashboardProjects);
m_infoLabel = new InfoLabel(this);
m_infoLabel->setVisible(false);
verticalLayout->addWidget(m_infoLabel);
auto horizontalLayout = new QHBoxLayout;
horizontalLayout->setContentsMargins(0, 0, 0, 0);
m_fetchProjects = new QPushButton(Tr::tr("Fetch Projects"));
horizontalLayout->addWidget(m_fetchProjects);
m_link = new QPushButton(Tr::tr("Link Project"));
m_link->setEnabled(false);
horizontalLayout->addWidget(m_link);
m_unlink = new QPushButton(Tr::tr("Unlink Project"));
m_unlink->setEnabled(false);
horizontalLayout->addWidget(m_unlink);
verticalLayout->addLayout(horizontalLayout);
using namespace Layouting;
Column {
noMargin,
m_linkedProject,
Tr::tr("Dashboard projects:"),
m_dashboardProjects,
m_infoLabel,
Row { m_fetchProjects, m_link, m_unlink, st }
}.attachTo(this);
connect(m_dashboardProjects, &QTreeWidget::itemSelectionChanged,
this, &AxivionProjectSettingsWidget::updateEnabledStates);
@@ -220,8 +220,7 @@ void AxivionProjectSettingsWidget::updateUi()
void AxivionProjectSettingsWidget::updateEnabledStates()
{
const bool hasDashboardSettings = !settings().server.dashboard.isEmpty()
&& !settings().server.token.isEmpty();
const bool hasDashboardSettings = !settings().server.dashboard.isEmpty();
const bool linked = !m_projectSettings->dashboardProjectName().isEmpty();
const bool linkable = m_dashboardProjects->topLevelItemCount()
&& !m_dashboardProjects->selectedItems().isEmpty();

View File

@@ -10,6 +10,7 @@
#include <utils/id.h>
#include <utils/layoutbuilder.h>
#include <utils/stringutils.h>
#include <QDialog>
#include <QDialogButtonBox>
@@ -27,8 +28,7 @@ namespace Axivion::Internal {
bool AxivionServer::operator==(const AxivionServer &other) const
{
return id == other.id && dashboard == other.dashboard && username == other.username
&& description == other.description && token == other.token;
return id == other.id && dashboard == other.dashboard && username == other.username;
}
bool AxivionServer::operator!=(const AxivionServer &other) const
@@ -42,11 +42,15 @@ QJsonObject AxivionServer::toJson() const
result.insert("id", id.toString());
result.insert("dashboard", dashboard);
result.insert("username", username);
result.insert("description", description);
result.insert("token", token);
return result;
}
static QString fixUrl(const QString &url)
{
const QString trimmed = Utils::trimBack(url, ' ');
return trimmed.endsWith('/') ? trimmed : trimmed + '/';
}
AxivionServer AxivionServer::fromJson(const QJsonObject &json)
{
const AxivionServer invalidServer;
@@ -59,14 +63,7 @@ AxivionServer AxivionServer::fromJson(const QJsonObject &json)
const QJsonValue username = json.value("username");
if (username == QJsonValue::Undefined)
return invalidServer;
const QJsonValue description = json.value("description");
if (description == QJsonValue::Undefined)
return invalidServer;
const QJsonValue token = json.value("token");
if (token == QJsonValue::Undefined)
return invalidServer;
return {Id::fromString(id.toString()), dashboard.toString(), username.toString(),
description.toString(), token.toString()};
return {Id::fromString(id.toString()), fixUrl(dashboard.toString()), username.toString()};
}
static FilePath tokensFilePath()
@@ -109,6 +106,10 @@ AxivionSettings::AxivionSettings()
{
setSettingsGroup("Axivion");
highlightMarks.setSettingsKey("HighlightMarks");
highlightMarks.setLabelText(Tr::tr("Highlight marks"));
highlightMarks.setToolTip(Tr::tr("Marks issues on the scroll bar."));
highlightMarks.setDefaultValue(false);
AspectContainer::readSettings();
server = readTokenFile(tokensFilePath());
@@ -161,8 +162,6 @@ private:
Id m_id;
StringAspect m_dashboardUrl;
StringAspect m_username;
StringAspect m_description;
StringAspect m_token;
BoolAspect m_valid;
};
@@ -181,23 +180,12 @@ DashboardSettingsWidget::DashboardSettingsWidget(Mode mode, QWidget *parent, QPu
m_username.setDisplayStyle(labelStyle);
m_username.setPlaceHolderText(Tr::tr("User name"));
m_description.setLabelText(Tr::tr("Description:"));
m_description.setDisplayStyle(labelStyle);
m_description.setPlaceHolderText(Tr::tr("Non-empty description"));
m_token.setLabelText(Tr::tr("Access token:"));
m_token.setDisplayStyle(labelStyle);
m_token.setPlaceHolderText(Tr::tr("IDE Access Token"));
m_token.setVisible(mode == Edit);
using namespace Layouting;
Form {
m_dashboardUrl, br,
m_username, br,
m_description, br,
m_token, br,
mode == Edit ? normalMargin : noMargin
noMargin
}.attachTo(this);
if (mode == Edit) {
@@ -208,8 +196,6 @@ DashboardSettingsWidget::DashboardSettingsWidget(Mode mode, QWidget *parent, QPu
};
connect(&m_dashboardUrl, &BaseAspect::changed, this, checkValidity);
connect(&m_username, &BaseAspect::changed, this, checkValidity);
connect(&m_description, &BaseAspect::changed, this, checkValidity);
connect(&m_token, &BaseAspect::changed, this, checkValidity);
}
}
@@ -220,10 +206,8 @@ AxivionServer DashboardSettingsWidget::dashboardServer() const
result.id = m_id;
else
result.id = m_mode == Edit ? Id::fromName(QUuid::createUuid().toByteArray()) : m_id;
result.dashboard = m_dashboardUrl();
result.dashboard = fixUrl(m_dashboardUrl());
result.username = m_username();
result.description = m_description();
result.token = m_token();
return result;
}
@@ -232,13 +216,11 @@ void DashboardSettingsWidget::setDashboardServer(const AxivionServer &server)
m_id = server.id;
m_dashboardUrl.setValue(server.dashboard);
m_username.setValue(server.username);
m_description.setValue(server.description);
m_token.setValue(server.token);
}
bool DashboardSettingsWidget::isValid() const
{
return !m_token().isEmpty() && !m_description().isEmpty() && isUrlValid(m_dashboardUrl());
return isUrlValid(m_dashboardUrl());
}
class AxivionSettingsWidget : public IOptionsPageWidget
@@ -262,10 +244,15 @@ AxivionSettingsWidget::AxivionSettingsWidget()
m_dashboardDisplay = new DashboardSettingsWidget(DashboardSettingsWidget::Display, this);
m_dashboardDisplay->setDashboardServer(settings().server);
m_edit = new QPushButton(Tr::tr("Edit..."), this);
Row {
Form {
m_dashboardDisplay, br,
}, Column { m_edit, st }
Column {
Row {
Form {
m_dashboardDisplay, br
}, st,
Column { m_edit },
},
Space(10), br,
Row { settings().highlightMarks }, st
}.attachTo(this);
connect(m_edit, &QPushButton::clicked, this, &AxivionSettingsWidget::showEditServerDialog);

View File

@@ -26,8 +26,6 @@ public:
Utils::Id id;
QString dashboard;
QString username;
QString description;
QString token;
bool validateCert = true;
};
@@ -40,6 +38,7 @@ public:
void toSettings() const;
AxivionServer server; // shall we have more than one?
Utils::BoolAspect highlightMarks{this};
};
AxivionSettings &settings();

View File

@@ -9,7 +9,7 @@ namespace Axivion {
struct Tr
{
Q_DECLARE_TR_FUNCTIONS(Axivion)
Q_DECLARE_TR_FUNCTIONS(QtC::Axivion)
};
} // Axivion

View File

@@ -24,7 +24,8 @@ void CredentialQueryTaskAdapter::start()
}
case CredentialOperation::Set: {
WritePasswordJob *writer = new WritePasswordJob(task()->m_service);
writer->setBinaryData(task()->m_data);
if (task()->m_data)
writer->setBinaryData(*task()->m_data);
job = writer;
break;
}
@@ -38,11 +39,12 @@ void CredentialQueryTaskAdapter::start()
m_guard.reset(job);
connect(job, &Job::finished, this, [this, reader](Job *job) {
if (job->error() != NoError)
const bool success = job->error() == NoError || job->error() == EntryNotFound;
if (!success)
task()->m_errorString = job->errorString();
else if (reader)
else if (reader && job->error() == NoError)
task()->m_data = reader->binaryData();
emit done(toDoneResult(job->error() == NoError));
emit done(toDoneResult(success));
m_guard.release()->deleteLater();
});
job->start();

View File

@@ -17,14 +17,15 @@ public:
void setKey(const QString &key) { m_key = key; }
void setData(const QByteArray &data) { m_data = data; }
QByteArray data() const { return m_data; }
CredentialOperation operation() const { return m_operation; }
std::optional<QByteArray> data() const { return m_data; }
QString errorString() const { return m_errorString; }
private:
CredentialOperation m_operation = CredentialOperation::Get;
QString m_service;
QString m_key;
QByteArray m_data; // Used for input when Set and for output when Get.
std::optional<QByteArray> m_data; // Used for input when Set and for output when Get.
QString m_errorString;
friend class CredentialQueryTaskAdapter;
};

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,8 @@
* <AxivionSuiteRepo>/projects/libs/dashboard_cpp_api/generator/generate_dashboard_cpp_api.py
*/
#include <utils/expected.h>
#include <QAnyStringView>
#include <QByteArray>
#include <QHashFunctions>
@@ -70,6 +72,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static Any deserialize(const QByteArray &json);
static Utils::expected_str<Any> deserializeExpected(const QByteArray &json);
Any();
Any(QString value);
@@ -171,6 +175,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static AnalyzedFileDto deserialize(const QByteArray &json);
static Utils::expected_str<AnalyzedFileDto> deserializeExpected(const QByteArray &json);
AnalyzedFileDto(
QString path,
std::optional<bool> isSystemHeader,
@@ -219,6 +225,8 @@ namespace Axivion::Internal::Dto
// Throws std::range_error
static ApiTokenType strToEnum(QAnyStringView str);
static std::optional<ApiTokenType> strToOptionalEnum(QAnyStringView str);
static QLatin1String enumToStr(ApiTokenType e);
ApiTokenTypeMeta() = delete;
@@ -245,6 +253,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static ChangePasswordFormDto deserialize(const QByteArray &json);
static Utils::expected_str<ChangePasswordFormDto> deserializeExpected(const QByteArray &json);
ChangePasswordFormDto(
QString currentPassword,
QString newPassword
@@ -295,6 +305,8 @@ namespace Axivion::Internal::Dto
// Throws std::range_error
static ColumnType strToEnum(QAnyStringView str);
static std::optional<ColumnType> strToOptionalEnum(QAnyStringView str);
static QLatin1String enumToStr(ColumnType e);
ColumnTypeMeta() = delete;
@@ -332,6 +344,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static ColumnTypeOptionDto deserialize(const QByteArray &json);
static Utils::expected_str<ColumnTypeOptionDto> deserializeExpected(const QByteArray &json);
ColumnTypeOptionDto(
QString key,
std::optional<QString> displayName,
@@ -356,6 +370,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static CommentRequestDto deserialize(const QByteArray &json);
static Utils::expected_str<CommentRequestDto> deserializeExpected(const QByteArray &json);
CommentRequestDto(
QString text
);
@@ -384,6 +400,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static CsrfTokenDto deserialize(const QByteArray &json);
static Utils::expected_str<CsrfTokenDto> deserializeExpected(const QByteArray &json);
CsrfTokenDto(
QString csrfToken
);
@@ -427,6 +445,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static EntityDto deserialize(const QByteArray &json);
static Utils::expected_str<EntityDto> deserializeExpected(const QByteArray &json);
EntityDto(
QString id,
QString name,
@@ -544,6 +564,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static ErrorDto deserialize(const QByteArray &json);
static Utils::expected_str<ErrorDto> deserializeExpected(const QByteArray &json);
ErrorDto(
std::optional<QString> dashboardVersionNumber,
QString type,
@@ -613,6 +635,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static IssueCommentDto deserialize(const QByteArray &json);
static Utils::expected_str<IssueCommentDto> deserializeExpected(const QByteArray &json);
IssueCommentDto(
QString username,
QString userDisplayName,
@@ -654,6 +678,8 @@ namespace Axivion::Internal::Dto
// Throws std::range_error
static IssueKind strToEnum(QAnyStringView str);
static std::optional<IssueKind> strToOptionalEnum(QAnyStringView str);
static QLatin1String enumToStr(IssueKind e);
IssueKindMeta() = delete;
@@ -690,6 +716,8 @@ namespace Axivion::Internal::Dto
// Throws std::range_error
static IssueKindForNamedFilterCreation strToEnum(QAnyStringView str);
static std::optional<IssueKindForNamedFilterCreation> strToOptionalEnum(QAnyStringView str);
static QLatin1String enumToStr(IssueKindForNamedFilterCreation e);
IssueKindForNamedFilterCreationMeta() = delete;
@@ -759,6 +787,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static IssueSourceLocationDto deserialize(const QByteArray &json);
static Utils::expected_str<IssueSourceLocationDto> deserializeExpected(const QByteArray &json);
IssueSourceLocationDto(
QString fileName,
std::optional<QString> role,
@@ -795,6 +825,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static IssueTagDto deserialize(const QByteArray &json);
static Utils::expected_str<IssueTagDto> deserializeExpected(const QByteArray &json);
IssueTagDto(
QString tag,
QString color
@@ -851,6 +883,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static IssueTagTypeDto deserialize(const QByteArray &json);
static Utils::expected_str<IssueTagTypeDto> deserializeExpected(const QByteArray &json);
IssueTagTypeDto(
QString id,
std::optional<QString> text,
@@ -893,6 +927,8 @@ namespace Axivion::Internal::Dto
// Throws std::range_error
static MessageSeverity strToEnum(QAnyStringView str);
static std::optional<MessageSeverity> strToOptionalEnum(QAnyStringView str);
static QLatin1String enumToStr(MessageSeverity e);
MessageSeverityMeta() = delete;
@@ -937,6 +973,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static MetricDto deserialize(const QByteArray &json);
static Utils::expected_str<MetricDto> deserializeExpected(const QByteArray &json);
MetricDto(
QString name,
QString displayName,
@@ -996,6 +1034,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static MetricValueTableRowDto deserialize(const QByteArray &json);
static Utils::expected_str<MetricValueTableRowDto> deserializeExpected(const QByteArray &json);
MetricValueTableRowDto(
QString metric,
std::optional<QString> path,
@@ -1033,6 +1073,8 @@ namespace Axivion::Internal::Dto
// Throws std::range_error
static NamedFilterType strToEnum(QAnyStringView str);
static std::optional<NamedFilterType> strToOptionalEnum(QAnyStringView str);
static QLatin1String enumToStr(NamedFilterType e);
NamedFilterTypeMeta() = delete;
@@ -1062,6 +1104,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static NamedFilterVisibilityDto deserialize(const QByteArray &json);
static Utils::expected_str<NamedFilterVisibilityDto> deserializeExpected(const QByteArray &json);
NamedFilterVisibilityDto(
std::optional<std::vector<QString>> groups
);
@@ -1089,6 +1133,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static ProjectReferenceDto deserialize(const QByteArray &json);
static Utils::expected_str<ProjectReferenceDto> deserializeExpected(const QByteArray &json);
ProjectReferenceDto(
QString name,
QString url
@@ -1127,6 +1173,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static RuleDto deserialize(const QByteArray &json);
static Utils::expected_str<RuleDto> deserializeExpected(const QByteArray &json);
RuleDto(
QString name,
QString original_name,
@@ -1157,6 +1205,8 @@ namespace Axivion::Internal::Dto
// Throws std::range_error
static SortDirection strToEnum(QAnyStringView str);
static std::optional<SortDirection> strToOptionalEnum(QAnyStringView str);
static QLatin1String enumToStr(SortDirection e);
SortDirectionMeta() = delete;
@@ -1187,6 +1237,8 @@ namespace Axivion::Internal::Dto
// Throws std::range_error
static TableCellAlignment strToEnum(QAnyStringView str);
static std::optional<TableCellAlignment> strToOptionalEnum(QAnyStringView str);
static QLatin1String enumToStr(TableCellAlignment e);
TableCellAlignmentMeta() = delete;
@@ -1218,6 +1270,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static ToolsVersionDto deserialize(const QByteArray &json);
static Utils::expected_str<ToolsVersionDto> deserializeExpected(const QByteArray &json);
ToolsVersionDto(
QString name,
QString number,
@@ -1253,6 +1307,8 @@ namespace Axivion::Internal::Dto
// Throws std::range_error
static UserRefType strToEnum(QAnyStringView str);
static std::optional<UserRefType> strToOptionalEnum(QAnyStringView str);
static QLatin1String enumToStr(UserRefType e);
UserRefTypeMeta() = delete;
@@ -1284,6 +1340,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static VersionKindCountDto deserialize(const QByteArray &json);
static Utils::expected_str<VersionKindCountDto> deserializeExpected(const QByteArray &json);
VersionKindCountDto(
qint32 Total,
qint32 Added,
@@ -1392,6 +1450,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static AnalysisVersionDto deserialize(const QByteArray &json);
static Utils::expected_str<AnalysisVersionDto> deserializeExpected(const QByteArray &json);
AnalysisVersionDto(
QString date,
std::optional<QString> label,
@@ -1450,6 +1510,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static ApiTokenCreationRequestDto deserialize(const QByteArray &json);
static Utils::expected_str<ApiTokenCreationRequestDto> deserializeExpected(const QByteArray &json);
ApiTokenCreationRequestDto(
QString password,
QString type,
@@ -1568,6 +1630,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static ApiTokenInfoDto deserialize(const QByteArray &json);
static Utils::expected_str<ApiTokenInfoDto> deserializeExpected(const QByteArray &json);
ApiTokenInfoDto(
QString id,
QString url,
@@ -1693,6 +1757,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static ColumnInfoDto deserialize(const QByteArray &json);
static Utils::expected_str<ColumnInfoDto> deserializeExpected(const QByteArray &json);
ColumnInfoDto(
QString key,
std::optional<QString> header,
@@ -1726,7 +1792,7 @@ namespace Axivion::Internal::Dto
void setAlignmentEnum(TableCellAlignment newValue);
// Throws std::range_error
// Throws std::range_error
ColumnType getTypeEnum() const;
std::optional<ColumnType> getOptionalTypeEnum() const;
@@ -1859,6 +1925,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static DashboardInfoDto deserialize(const QByteArray &json);
static Utils::expected_str<DashboardInfoDto> deserializeExpected(const QByteArray &json);
DashboardInfoDto(
std::optional<QString> mainUrl,
QString dashboardVersion,
@@ -1895,6 +1963,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static IssueCommentListDto deserialize(const QByteArray &json);
static Utils::expected_str<IssueCommentListDto> deserializeExpected(const QByteArray &json);
IssueCommentListDto(
std::vector<IssueCommentDto> comments
);
@@ -1929,6 +1999,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static IssueKindInfoDto deserialize(const QByteArray &json);
static Utils::expected_str<IssueKindInfoDto> deserializeExpected(const QByteArray &json);
IssueKindInfoDto(
QString prefix,
QString niceSingularName,
@@ -1966,6 +2038,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static IssueTagTypeListDto deserialize(const QByteArray &json);
static Utils::expected_str<IssueTagTypeListDto> deserializeExpected(const QByteArray &json);
IssueTagTypeListDto(
std::vector<IssueTagTypeDto> tags
);
@@ -2056,6 +2130,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static LineMarkerDto deserialize(const QByteArray &json);
static Utils::expected_str<LineMarkerDto> deserializeExpected(const QByteArray &json);
LineMarkerDto(
QString kind,
std::optional<qint64> id,
@@ -2115,6 +2191,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static RepositoryUpdateMessageDto deserialize(const QByteArray &json);
static Utils::expected_str<RepositoryUpdateMessageDto> deserializeExpected(const QByteArray &json);
RepositoryUpdateMessageDto(
QString severity,
QString message
@@ -2150,6 +2228,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static RuleListDto deserialize(const QByteArray &json);
static Utils::expected_str<RuleListDto> deserializeExpected(const QByteArray &json);
RuleListDto(
std::vector<RuleDto> rules
);
@@ -2179,6 +2259,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static SortInfoDto deserialize(const QByteArray &json);
static Utils::expected_str<SortInfoDto> deserializeExpected(const QByteArray &json);
SortInfoDto(
QString key,
QString direction
@@ -2235,6 +2317,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static UserRefDto deserialize(const QByteArray &json);
static Utils::expected_str<UserRefDto> deserializeExpected(const QByteArray &json);
UserRefDto(
QString name,
QString displayName,
@@ -2279,6 +2363,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static AnalyzedFileListDto deserialize(const QByteArray &json);
static Utils::expected_str<AnalyzedFileListDto> deserializeExpected(const QByteArray &json);
AnalyzedFileListDto(
AnalysisVersionDto version,
std::vector<AnalyzedFileDto> rows
@@ -2307,6 +2393,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static EntityListDto deserialize(const QByteArray &json);
static Utils::expected_str<EntityListDto> deserializeExpected(const QByteArray &json);
EntityListDto(
std::optional<AnalysisVersionDto> version,
std::vector<EntityDto> entities
@@ -2355,6 +2443,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static FileViewDto deserialize(const QByteArray &json);
static Utils::expected_str<FileViewDto> deserializeExpected(const QByteArray &json);
FileViewDto(
QString fileName,
std::optional<QString> version,
@@ -2418,6 +2508,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static IssueDto deserialize(const QByteArray &json);
static Utils::expected_str<IssueDto> deserializeExpected(const QByteArray &json);
IssueDto(
QString kind,
qint64 id,
@@ -2532,6 +2624,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static IssueTableDto deserialize(const QByteArray &json);
static Utils::expected_str<IssueTableDto> deserializeExpected(const QByteArray &json);
IssueTableDto(
std::optional<AnalysisVersionDto> startVersion,
AnalysisVersionDto endVersion,
@@ -2566,6 +2660,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static MetricListDto deserialize(const QByteArray &json);
static Utils::expected_str<MetricListDto> deserializeExpected(const QByteArray &json);
MetricListDto(
std::optional<AnalysisVersionDto> version,
std::vector<MetricDto> metrics
@@ -2617,6 +2713,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static MetricValueRangeDto deserialize(const QByteArray &json);
static Utils::expected_str<MetricValueRangeDto> deserializeExpected(const QByteArray &json);
MetricValueRangeDto(
AnalysisVersionDto startVersion,
AnalysisVersionDto endVersion,
@@ -2650,6 +2748,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static MetricValueTableDto deserialize(const QByteArray &json);
static Utils::expected_str<MetricValueTableDto> deserializeExpected(const QByteArray &json);
MetricValueTableDto(
std::vector<ColumnInfoDto> columns,
std::vector<MetricValueTableRowDto> rows
@@ -2712,6 +2812,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static NamedFilterCreateDto deserialize(const QByteArray &json);
static Utils::expected_str<NamedFilterCreateDto> deserializeExpected(const QByteArray &json);
NamedFilterCreateDto(
QString displayName,
QString kind,
@@ -2834,6 +2936,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static NamedFilterInfoDto deserialize(const QByteArray &json);
static Utils::expected_str<NamedFilterInfoDto> deserializeExpected(const QByteArray &json);
NamedFilterInfoDto(
QString key,
QString displayName,
@@ -2924,6 +3028,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static NamedFilterUpdateDto deserialize(const QByteArray &json);
static Utils::expected_str<NamedFilterUpdateDto> deserializeExpected(const QByteArray &json);
NamedFilterUpdateDto(
std::optional<QString> name,
std::optional<std::map<QString, QString>> filters,
@@ -2983,6 +3089,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static ProjectInfoDto deserialize(const QByteArray &json);
static Utils::expected_str<ProjectInfoDto> deserializeExpected(const QByteArray &json);
ProjectInfoDto(
QString name,
std::optional<QString> issueFilterHelp,
@@ -3024,6 +3132,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static RepositoryUpdateResponseDto deserialize(const QByteArray &json);
static Utils::expected_str<RepositoryUpdateResponseDto> deserializeExpected(const QByteArray &json);
RepositoryUpdateResponseDto(
std::vector<RepositoryUpdateMessageDto> messages,
bool hasErrors,
@@ -3091,6 +3201,8 @@ namespace Axivion::Internal::Dto
// Throws Axivion::Internal::Dto::invalid_dto_exception
static TableInfoDto deserialize(const QByteArray &json);
static Utils::expected_str<TableInfoDto> deserializeExpected(const QByteArray &json);
TableInfoDto(
QString tableDataUri,
std::optional<QString> issueBaseViewUri,

View File

@@ -0,0 +1,194 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "dynamiclistmodel.h"
#include "axiviontr.h"
#include <utils/qtcassert.h>
#include <utils/theme/theme.h>
namespace Axivion::Internal {
constexpr int pageSize = 150;
DynamicListModel::DynamicListModel(QObject *parent)
: QAbstractItemModel(parent)
{
m_fetchMoreTimer.setSingleShot(true);
m_fetchMoreTimer.setInterval(50);
connect(&m_fetchMoreTimer, &QTimer::timeout, this, &DynamicListModel::fetchNow);
}
DynamicListModel::~DynamicListModel()
{
clear();
}
QModelIndex DynamicListModel::index(int row, int column, const QModelIndex &parent) const
{
if (parent.isValid())
return {};
if (row < m_expectedRowCount.value_or(m_children.size())) {
auto it = m_children.constFind(row);
return createIndex(row, column, it != m_children.constEnd() ? it.value() : nullptr);
}
return {};
}
QModelIndex DynamicListModel::parent(const QModelIndex &/*child*/) const
{
return {};
}
int DynamicListModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) // for simplicity only single level
return 0;
return m_expectedRowCount.value_or(m_children.size());
}
int DynamicListModel::columnCount(const QModelIndex &/*parent*/) const
{
return m_columnCount;
}
QVariant DynamicListModel::data(const QModelIndex &index, int role) const
{
const int row = index.row();
if (!index.isValid() || row < 0 || row > m_expectedRowCount.value_or(m_children.size()))
return {};
auto item = m_children.constFind(row);
if (item != m_children.cend()) {
if (role == Qt::TextAlignmentRole) {
if (!m_alignments.isEmpty() && index.column() < m_alignments.size())
return QVariant::fromValue(m_alignments.at(index.column()));
}
return item.value()->data(index.column(), role);
}
if ((row < m_lastFetch || row > m_lastFetchEnd) && (row < m_fetchStart || row > m_fetchEnd))
const_cast<DynamicListModel *>(this)->onNeedFetch(row);
if (role == Qt::DisplayRole && index.column() == 0)
return Tr::tr("Fetching..."); // TODO improve/customize?
if (role == Qt::ForegroundRole && index.column() == 0)
return Utils::creatorTheme()->color(Utils::Theme::TextColorDisabled);
return {};
}
bool DynamicListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
auto found = m_children.constFind(index.row());
if (found == m_children.constEnd())
return false;
return found.value()->setData(index.column(), value, role);
}
QVariant DynamicListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section < m_header.size())
return m_header.at(section);
return {};
}
void DynamicListModel::setItems(const QList<ListItem *> &items)
{
m_lastFetchEnd = -1; // FIXME wrong.. better a callback? - should happen for failed requests too
// for simplicity we assume an ordered list and no non-existing items between first and last
if (items.isEmpty())
return;
// to keep it simple here, we expect the expectedRowCount to be set *before* adding items
QTC_ASSERT(m_expectedRowCount, setExpectedRowCount(items.size()));
if (int lastRow = items.last()->row; lastRow > *m_expectedRowCount)
m_expectedRowCount.emplace(lastRow);
emit layoutAboutToBeChanged();
auto end = m_children.end();
for (ListItem *it : items) {
auto found = m_children.find(it->row); // check for old data to be removed
ListItem *old = nullptr;
if (found != end)
old = found.value();
m_children.insert(it->row, it);
delete old;
}
emit dataChanged(indexForItem(items.first()), indexForItem(items.last()));
emit layoutChanged();
}
void DynamicListModel::clear()
{
beginResetModel();
qDeleteAll(m_children);
m_children.clear();
m_expectedRowCount.reset();
endResetModel();
}
void DynamicListModel::setExpectedRowCount(int expected)
{
QTC_ASSERT(expected >= m_children.size(), return);
if (expected == m_children.size())
return;
beginInsertRows({}, m_children.size(), expected);
m_expectedRowCount.emplace(expected);
endInsertRows();
}
void DynamicListModel::setHeader(const QStringList &header)
{
m_header = header;
m_columnCount = m_header.size();
}
void DynamicListModel::setAlignments(const QList<Qt::Alignment> &alignments)
{
m_alignments = alignments;
}
QModelIndex DynamicListModel::indexForItem(const ListItem *item) const
{
QTC_ASSERT(item, return {});
auto found = m_children.constFind(item->row);
if (found == m_children.cend())
return {};
QTC_ASSERT(found.value() == item, return {});
return createIndex(item->row, 0, item);
}
void DynamicListModel::onNeedFetch(int row)
{
m_fetchStart = row;
m_fetchEnd = row + pageSize;
if (m_fetchStart < 0)
return;
m_fetchMoreTimer.start();
}
void DynamicListModel::fetchNow()
{
const int old = m_lastFetch;
m_lastFetch = m_fetchStart; // we need the "original" fetch request to avoid endless loop
m_lastFetchEnd = m_fetchStart + pageSize;
if (old != -1) {
const int diff = old - m_fetchStart;
if (0 < diff && diff < pageSize) {
m_fetchStart = qMax(old - pageSize, 0);
} else if (0 > diff && diff > -pageSize) {
m_fetchStart = old + pageSize;
if (m_expectedRowCount && m_fetchStart > *m_expectedRowCount)
m_fetchStart = *m_expectedRowCount;
}
}
QTC_CHECK(m_expectedRowCount ? m_fetchStart <= *m_expectedRowCount
: m_fetchStart >= m_children.size());
emit fetchRequested(m_fetchStart, pageSize);
m_fetchStart = -1;
m_fetchEnd = -1;
}
} // namespace Axivion::Internal

View File

@@ -0,0 +1,69 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QAbstractItemModel>
#include <QHash>
#include <QTimer>
#include <QVariant>
#include <optional>
namespace Axivion::Internal {
class ListItem
{
public:
explicit ListItem(int row) : row(row) {}
virtual ~ListItem() = default;
virtual bool setData(int /*column*/, const QVariant &/*value*/, int /*role*/) { return false; }
virtual QVariant data(int /*column*/, int /*role*/) const { return {}; }
const int row;
};
class DynamicListModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit DynamicListModel(QObject *parent = nullptr);
~DynamicListModel();
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
QModelIndex parent(const QModelIndex &child) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
bool setData(const QModelIndex&, const QVariant &value, int role) override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
void setItems(const QList<ListItem *> &items);
void clear();
void setExpectedRowCount(int expected);
void setHeader(const QStringList &header);
void setAlignments(const QList<Qt::Alignment> &alignments);
QModelIndex indexForItem(const ListItem *item) const;
signals:
void fetchRequested(int startRow, int limit);
private:
void onNeedFetch(int row);
void fetchNow();
QHash<int, ListItem *> m_children;
QStringList m_header;
QList<Qt::Alignment> m_alignments;
QTimer m_fetchMoreTimer;
std::optional<int> m_expectedRowCount = {};
int m_fetchStart = -1;
int m_fetchEnd = -1;
int m_lastFetch = -1;
int m_lastFetchEnd = -1;
int m_columnCount = 0;
};
} // namespace Axivion::Internal

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

View File

@@ -0,0 +1,141 @@
// 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 "issueheaderview.h"
#include <utils/icon.h>
#include <QMouseEvent>
#include <QPainter>
namespace Axivion::Internal {
constexpr int ICON_SIZE = 16;
static QIcon iconForSorted(SortOrder order)
{
const Utils::Icon UNSORTED(
{{":/axivion/images/sortAsc.png", Utils::Theme::IconsDisabledColor},
{":/axivion/images/sortDesc.png", Utils::Theme::IconsDisabledColor}});
const Utils::Icon SORT_ASC(
{{":/axivion/images/sortAsc.png", Utils::Theme::PaletteText},
{":/axivion/images/sortDesc.png", Utils::Theme::IconsDisabledColor}});
const Utils::Icon SORT_DESC(
{{":/axivion/images/sortAsc.png", Utils::Theme::IconsDisabledColor},
{":/axivion/images/sortDesc.png", Utils::Theme::PaletteText}});
static const QIcon unsorted = UNSORTED.icon();
static const QIcon sortedAsc = SORT_ASC.icon();
static const QIcon sortedDesc = SORT_DESC.icon();
switch (order) {
case SortOrder::None:
return unsorted;
case SortOrder::Ascending:
return sortedAsc;
case SortOrder::Descending:
return sortedDesc;
}
return {};
}
void IssueHeaderView::setSortableColumns(const QList<bool> &sortable)
{
m_sortableColumns = sortable;
int oldIndex = m_currentSortIndex;
m_currentSortIndex = -1;
m_currentSortOrder = SortOrder::None;
if (oldIndex != -1)
headerDataChanged(Qt::Horizontal, oldIndex, oldIndex);
}
int IssueHeaderView::currentSortColumn() const
{
return m_currentSortOrder == SortOrder::None ? -1 : m_currentSortIndex;
}
void IssueHeaderView::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
const QPoint position = event->position().toPoint();
const int y = position.y();
if (y > 1 && y < height() - 2) { // TODO improve
const int pos = position.x();
const int logical = logicalIndexAt(pos);
const int end = sectionViewportPosition(logical) + sectionSize(logical);
const int start = end - ICON_SIZE - 2;
m_maybeToggleSort = start < pos && end > pos;
}
}
QHeaderView::mousePressEvent(event);
}
void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event)
{
bool dontSkip = !m_dragging && m_maybeToggleSort;
m_dragging = false;
m_maybeToggleSort = false;
if (dontSkip) {
const QPoint position = event->position().toPoint();
const int y = position.y();
const int logical = logicalIndexAt(position.x());
if (logical > -1 && logical < m_sortableColumns.size()) {
if (m_sortableColumns.at(logical)) { // ignore non-sortable
if (y < height() / 2) // TODO improve
onToggleSort(logical, SortOrder::Ascending);
else
onToggleSort(logical, SortOrder::Descending);
}
}
}
QHeaderView::mouseReleaseEvent(event);
}
void IssueHeaderView::mouseMoveEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
m_dragging = true;
QHeaderView::mouseMoveEvent(event);
}
void IssueHeaderView::onToggleSort(int index, SortOrder order)
{
if (m_currentSortIndex == index)
m_currentSortOrder = (order == m_currentSortOrder) ? SortOrder::None : order;
else
m_currentSortOrder = order;
int oldIndex = m_currentSortIndex;
m_currentSortIndex = index;
if (oldIndex != -1)
headerDataChanged(Qt::Horizontal, oldIndex, oldIndex);
headerDataChanged(Qt::Horizontal, index, index);
emit sortTriggered();
}
QSize IssueHeaderView::sectionSizeFromContents(int logicalIndex) const
{
const QSize oldSize = QHeaderView::sectionSizeFromContents(logicalIndex);
const QSize newSize = logicalIndex < m_columnWidths.size()
? QSize(qMax(m_columnWidths.at(logicalIndex), oldSize.width()), oldSize.height()) : oldSize;
// add icon size and margin (2)
return QSize{newSize.width() + ICON_SIZE + 2, qMax(newSize.height(), ICON_SIZE)};
}
void IssueHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
{
painter->save();
QHeaderView::paintSection(painter, rect, logicalIndex);
painter->restore();
if (logicalIndex < 0 || logicalIndex >= m_sortableColumns.size())
return;
if (!m_sortableColumns.at(logicalIndex))
return;
const QIcon icon = iconForSorted(logicalIndex == m_currentSortIndex ? m_currentSortOrder : SortOrder::None);
const int offset = qMax((rect.height() - ICON_SIZE), 0) / 2;
const QRect iconRect(rect.left() + rect.width() - ICON_SIZE - 2, offset, ICON_SIZE, ICON_SIZE);
icon.paint(painter, iconRect);
}
} // namespace Axivion::Internal

View File

@@ -0,0 +1,44 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QHeaderView>
#include <QList>
namespace Axivion::Internal {
enum class SortOrder { None, Ascending, Descending };
class IssueHeaderView : public QHeaderView
{
Q_OBJECT
public:
explicit IssueHeaderView(QWidget *parent = nullptr) : QHeaderView(Qt::Horizontal, parent) {}
void setSortableColumns(const QList<bool> &sortable);
void setColumnWidths(const QList<int> &widths) { m_columnWidths = widths; }
SortOrder currentSortOrder() const { return m_currentSortOrder; }
int currentSortColumn() const;
signals:
void sortTriggered();
protected:
void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override;
QSize sectionSizeFromContents(int logicalIndex) const override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
private:
void onToggleSort(int index, SortOrder order);
bool m_dragging = false;
bool m_maybeToggleSort = false;
int m_currentSortIndex = -1;
SortOrder m_currentSortOrder = SortOrder::None;
QList<bool> m_sortableColumns;
QList<int> m_columnWidths;
};
} // namespace Axivion::Internal

View File

@@ -294,10 +294,7 @@ ClangModelManagerSupport::ClangModelManagerSupport()
new ClangdQuickFixFactory(); // memory managed by CppEditor::g_cppQuickFixFactories
}
ClangModelManagerSupport::~ClangModelManagerSupport()
{
m_generatorSynchronizer.waitForFinished();
}
ClangModelManagerSupport::~ClangModelManagerSupport() = default;
void ClangModelManagerSupport::followSymbol(const CursorInEditor &data,
const LinkHandler &processLinkCallback,

View File

@@ -94,10 +94,10 @@ private:
void scheduleClientRestart(ClangdClient *client);
static ClangdClient *clientWithProject(const ProjectExplorer::Project *project);
Utils::FutureSynchronizer m_generatorSynchronizer;
QList<QPointer<ClangdClient>> m_clientsToRestart;
QTimer * const m_clientRestartTimer;
QHash<Utils::FilePath, QString> m_potentialShadowDocuments;
Utils::FutureSynchronizer m_generatorSynchronizer; // Keep me last
};
} // namespace Internal

View File

@@ -7,6 +7,7 @@
#include "cmakeprojectconstants.h"
#include "cmakeprojectimporter.h"
#include "cmakeprojectmanagertr.h"
#include "presetsmacros.h"
#include <coreplugin/icontext.h>
#include <projectexplorer/buildconfiguration.h>
@@ -301,6 +302,18 @@ void CMakeProject::readPresets()
m_presetsData = combinePresets(cmakePresetsData, cmakeUserPresetsData);
setupBuildPresets(m_presetsData);
for (const auto &configPreset : m_presetsData.configurePresets) {
if (configPreset.hidden.value())
continue;
if (configPreset.condition) {
if (!CMakePresets::Macros::evaluatePresetCondition(configPreset, projectFilePath()))
continue;
}
m_presetsData.havePresets = true;
break;
}
}
bool CMakeProject::setupTarget(Target *t)

View File

@@ -201,8 +201,6 @@ FilePaths CMakeProjectImporter::presetCandidates()
}
}
m_hasCMakePresets = !candidates.isEmpty();
return candidates;
}
@@ -223,7 +221,7 @@ Target *CMakeProjectImporter::preferredTarget(const QList<Target *> &possibleTar
bool CMakeProjectImporter::filter(ProjectExplorer::Kit *k) const
{
if (!m_hasCMakePresets)
if (!m_project->presetsData().havePresets)
return true;
const auto presetConfigItem = CMakeConfigurationKitAspect::cmakePresetConfigItem(k);

View File

@@ -49,7 +49,6 @@ private:
const CMakeProject *m_project;
Utils::TemporaryDirectory m_presetsTempDir;
bool m_hasCMakePresets = false;
};
#ifdef WITH_TESTS

View File

@@ -93,16 +93,16 @@ CMakeSpecificSettings::CMakeSpecificSettings()
"UseJunctionsForSourceAndBuildDirectories");
useJunctionsForSourceAndBuildDirectories.setDefaultValue(false);
useJunctionsForSourceAndBuildDirectories.setLabelText(::CMakeProjectManager::Tr::tr(
"Use Junctions for CMake configuration and build operations"));
"Use junctions for CMake configuration and build operations"));
useJunctionsForSourceAndBuildDirectories.setVisible(Utils::HostOsInfo().isWindowsHost());
useJunctionsForSourceAndBuildDirectories.setToolTip(::CMakeProjectManager::Tr::tr(
"Create and use junctions for the source and build directories. This helps to overcome "
"Create and use junctions for the source and build directories to overcome "
"issues with long paths on Windows.<br><br>"
"They are stored under <tt>C:\\ProgramData\\QtCreator\\Links</tt> (overridable via "
"<tt>QTC_CMAKE_JUNCTIONS_DIR</tt> environment variable).<br><br>"
"With <tt>QTC_CMAKE_JUNCTIONS_HASH_LENGTH</tt> the MD5 hash key length can be shortened "
"Junctions are stored under <tt>C:\\ProgramData\\QtCreator\\Links</tt> (overridable via "
"the <tt>QTC_CMAKE_JUNCTIONS_DIR</tt> environment variable).<br><br>"
"With <tt>QTC_CMAKE_JUNCTIONS_HASH_LENGTH</tt>, you can shorten the MD5 hash key length "
"to a value smaller than the default length value of 32.<br><br>"
"They are used for CMake configure, build and install operations."));
"Junctions are used for CMake configure, build and install operations."));
readSettings();
}

View File

@@ -16,6 +16,7 @@
#include <coreplugin/icore.h>
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projecttree.h>
#include <projectexplorer/target.h>
@@ -69,6 +70,7 @@ typedef struct _REPARSE_DATA_BUFFER {
#endif
using namespace Core;
using namespace ProjectExplorer;
using namespace Utils;
namespace CMakeProjectManager {
@@ -451,8 +453,19 @@ FilePath CMakeToolManager::mappedFilePath(const FilePath &path)
if (path.needsDevice())
return path;
Internal::settings();
if (!Internal::settings().useJunctionsForSourceAndBuildDirectories())
auto project = ProjectManager::startupProject();
auto environment = Environment::systemEnvironment();
if (project)
environment.modify(project->additionalEnvironment());
const bool enableJunctions
= QVariant(
environment.value_or("QTC_CMAKE_USE_JUNCTIONS",
Internal::settings().useJunctionsForSourceAndBuildDirectories()
? "1"
: "0"))
.toBool();
if (!enableJunctions)
return path;
if (!d->m_junctionsDir.isDir())
@@ -583,14 +596,17 @@ CMakeToolManagerPrivate::CMakeToolManagerPrivate()
m_junctionsDir = FilePath::fromString(*std::min_element(locations.begin(), locations.end()))
.pathAppended("QtCreator/Links");
if (Utils::qtcEnvironmentVariableIsSet("QTC_CMAKE_JUNCTIONS_DIR")) {
m_junctionsDir = FilePath::fromUserInput(
Utils::qtcEnvironmentVariable("QTC_CMAKE_JUNCTIONS_DIR"));
}
if (Utils::qtcEnvironmentVariableIsSet("QTC_CMAKE_JUNCTIONS_HASH_LENGTH")) {
auto project = ProjectManager::startupProject();
auto environment = Environment::systemEnvironment();
if (project)
environment.modify(project->additionalEnvironment());
if (environment.hasKey("QTC_CMAKE_JUNCTIONS_DIR"))
m_junctionsDir = FilePath::fromUserInput(environment.value("QTC_CMAKE_JUNCTIONS_DIR"));
if (environment.hasKey("QTC_CMAKE_JUNCTIONS_HASH_LENGTH")) {
bool ok = false;
const int hashLength
= Utils::qtcEnvironmentVariableIntValue("QTC_CMAKE_JUNCTIONS_HASH_LENGTH", &ok);
const int hashLength = environment.value("QTC_CMAKE_JUNCTIONS_HASH_LENGTH").toInt(&ok);
if (ok && hashLength >= 4 && hashLength < 32)
m_junctionsHashLength = hashLength;
}

View File

@@ -140,6 +140,7 @@ class PresetsData
{
public:
int version = 0;
bool havePresets = false;
QVersionNumber cmakeMinimimRequired;
QHash<QString, QString> vendor;
std::optional<QStringList> include;

View File

@@ -332,11 +332,7 @@ CompilationDatabaseBuildSystem::CompilationDatabaseBuildSystem(Target *target)
this, &CompilationDatabaseBuildSystem::updateDeploymentData);
}
CompilationDatabaseBuildSystem::~CompilationDatabaseBuildSystem()
{
m_parserWatcher.cancel();
m_parserWatcher.waitForFinished();
}
CompilationDatabaseBuildSystem::~CompilationDatabaseBuildSystem() = default;
void CompilationDatabaseBuildSystem::triggerParsing()
{

View File

@@ -13,8 +13,6 @@
#include <utils/filesystemwatcher.h>
#include <QFutureWatcher>
namespace ProjectExplorer {
class Kit;
class ProjectUpdater;
@@ -51,7 +49,6 @@ public:
void updateDeploymentData();
void buildTreeAndProjectParts();
QFutureWatcher<void> m_parserWatcher;
std::unique_ptr<ProjectExplorer::ProjectUpdater> m_cppCodeModelUpdater;
MimeBinaryCache m_mimeBinaryCache;
QByteArray m_projectFileHash;

View File

@@ -8,9 +8,12 @@
#include <coreplugin/progressmanager/progressmanager.h>
#include <extensionsystem/pluginmanager.h>
#include <projectexplorer/treescanner.h>
#include <utils/async.h>
#include <utils/futuresynchronizer.h>
#include <utils/mimeutils.h>
#include <QCryptographicHash>
@@ -187,6 +190,7 @@ void CompilationDbParser::start()
"CompilationDatabase.Parse");
++m_runningParserJobs;
m_parserWatcher.setFuture(future);
ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(future);
}
void CompilationDbParser::stop()

View File

@@ -33,6 +33,8 @@
#include <utils/infobar.h>
#include <utils/macroexpander.h>
#include <utils/mimeutils.h>
#include <utils/networkaccessmanager.h>
#include <utils/passworddialog.h>
#include <utils/pathchooser.h>
#include <utils/savefile.h>
#include <utils/store.h>
@@ -41,6 +43,7 @@
#include <utils/theme/theme.h>
#include <utils/theme/theme_p.h>
#include <QAuthenticator>
#include <QDateTime>
#include <QDebug>
#include <QDir>
@@ -136,6 +139,30 @@ void CorePlugin::loadMimeFromPlugin(const ExtensionSystem::PluginSpec *plugin)
Utils::addMimeTypes(plugin->name() + ".mimetypes", mimetypeString.trimmed().toUtf8());
}
static void initProxyAuthDialog()
{
QObject::connect(Utils::NetworkAccessManager::instance(),
&QNetworkAccessManager::proxyAuthenticationRequired,
Utils::NetworkAccessManager::instance(),
[](const QNetworkProxy &, QAuthenticator *authenticator) {
static bool doNotAskAgain = false;
std::optional<QPair<QString, QString>> answer
= Utils::PasswordDialog::getUserAndPassword(
Tr::tr("Proxy Authentication Required"),
authenticator->realm(),
Tr::tr("Do not ask again."),
{},
&doNotAskAgain,
Core::ICore::dialogParent());
if (answer) {
authenticator->setUser(answer->first);
authenticator->setPassword(answer->second);
}
});
}
bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage)
{
// register all mime types from all plugins
@@ -145,6 +172,8 @@ bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage)
loadMimeFromPlugin(plugin);
}
initProxyAuthDialog();
if (ThemeEntry::availableThemes().isEmpty()) {
*errorMessage = Tr::tr("No themes found in installation.");
return false;
@@ -238,9 +267,9 @@ bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage)
[] { return QUuid::createUuid().toString(); });
expander->registerPrefix("#:", Tr::tr("A comment."), [](const QString &) { return QString(); });
expander->registerPrefix("Asciify:", Tr::tr("Convert string into pure ascii."),
[expander] (const QString &s) {
return asciify(expander->expand(s)); });
expander->registerPrefix("Asciify:",
Tr::tr("Convert string to pure ASCII."),
[expander](const QString &s) { return asciify(expander->expand(s)); });
Utils::PathChooser::setAboutToShowContextMenuHandler(&CorePlugin::addToPathChooserContextMenu);

View File

@@ -579,7 +579,7 @@ void EditorManagerPrivate::init()
// Go back in navigation history
ActionBuilder goBack(this, Constants::GO_BACK);
goBack.setIcon(Utils::Icons::PREV.icon());
goBack.setText(Core::Tr::tr("Go Back"));
goBack.setText(::Core::Tr::tr("Go Back"));
goBack.bindContextAction(&m_goBackAction);
goBack.setContext(editDesignContext);
goBack.setDefaultKeySequence(::Core::Tr::tr("Ctrl+Alt+Left"), ::Core::Tr::tr("Alt+Left"));
@@ -589,7 +589,7 @@ void EditorManagerPrivate::init()
// Go forward in navigation history
ActionBuilder goForward(this, Constants::GO_FORWARD);
goForward.setIcon(Utils::Icons::NEXT.icon());
goForward.setText(Core::Tr::tr("Go Forward"));
goForward.setText(::Core::Tr::tr("Go Forward"));
goForward.bindContextAction(&m_goForwardAction);
goForward.setContext(editDesignContext);
goForward.setDefaultKeySequence(::Core::Tr::tr("Ctrl+Alt+Right"), ::Core::Tr::tr("Alt+Right"));
@@ -618,7 +618,7 @@ void EditorManagerPrivate::init()
splitSideBySide.setText(::Core::Tr::tr("Split Side by Side"));
splitSideBySide.bindContextAction(&m_splitSideBySideAction);
splitSideBySide.setContext(editManagerContext);
splitSideBySide.setDefaultKeySequence(::Core::Tr::tr("Meta+E,3"), Core::Tr::tr("Ctrl+E,3"));
splitSideBySide.setDefaultKeySequence(::Core::Tr::tr("Meta+E,3"), ::Core::Tr::tr("Ctrl+E,3"));
splitSideBySide.addToContainer(Constants::M_WINDOW, Constants::G_WINDOW_SPLIT);
splitSideBySide.addOnTriggered(this, &EditorManager::splitSideBySide);

View File

@@ -47,9 +47,11 @@ EditorWindow::EditorWindow(QWidget *parent) :
static int windowId = 0;
const Utils::Id windowContext
= Utils::Id("EditorManager.ExternalWindow.").withSuffix(++windowId);
ICore::registerWindow(this,
Context(Utils::Id("EditorManager.ExternalWindow.").withSuffix(++windowId),
Constants::C_EDITORMANAGER));
Context(windowContext, Constants::C_EDITORMANAGER),
Context(windowContext));
connect(m_area, &EditorArea::windowTitleNeedsUpdate,
this, &EditorWindow::updateWindowTitle);

View File

@@ -92,7 +92,7 @@ using namespace Utils;
Returns the name of the find filter or scope as presented to the user.
This is the name that appears in the scope selection combo box, for example.
Always return a translatable string. That is, use \c tr() for the return
Always return a translatable string. That is, use \c {Tr::tr()} for the return
value.
*/

View File

@@ -1011,14 +1011,15 @@ void ICore::removeAdditionalContext(const Context &context)
Registers a \a window with the specified \a context. Registered windows are
shown in the \uicontrol Window menu and get registered for the various
window related actions, like the minimize, zoom, fullscreen and close
actions.
actions. The context for the actions is \a context by default, but can be
overridden with \a actionContext.
Whenever the application focus is in \a window, its \a context is made
active.
*/
void ICore::registerWindow(QWidget *window, const Context &context)
void ICore::registerWindow(QWidget *window, const Context &context, const Context &actionContext)
{
new WindowSupport(window, context); // deletes itself when widget is destroyed
new WindowSupport(window, context, actionContext); // deletes itself when widget is destroyed
}
/*!
@@ -1970,14 +1971,32 @@ void ICorePrivate::registerDefaultActions()
toggleMenubarAction.addToContainer(Constants::M_VIEW, Constants::G_VIEW_VIEWS);
toggleMenubarAction.addOnToggled(this, [](bool visible) {
if (!visible) {
const QString keys = ActionManager::command(Constants::TOGGLE_MENUBAR)
->keySequence().toString(QKeySequence::NativeText);
CheckableMessageBox::information(Core::ICore::dialogParent(),
Tr::tr("Hide Menu Bar"),
Tr::tr("This will hide the menu bar completely. "
"You can show it again by typing %1.")
.arg(keys),
Key("ToogleMenuBarHint"));
auto keySequenceAndText = [](const Utils::Id &actionName) {
const auto command = ActionManager::command(actionName);
const QString keySequence = command->keySequence().toString(
QKeySequence::NativeText);
const QString text = command->action()->text();
return QPair<QString, QString>(keySequence, text);
};
auto [menuBarKeys, menuBarText] = keySequenceAndText(Constants::TOGGLE_MENUBAR);
auto [actionsFromMenuKeys, actionsFromMenuText] = keySequenceAndText(
"Locator.Actions from the menu");
CheckableMessageBox::information(
Core::ICore::dialogParent(),
Tr::tr("Hide Menu Bar"),
Tr::tr("This will hide the menu bar completely. "
"You can show it again by typing %1."
"<br><br>"
"Or, trigger the \"%2\" action from the \"%3\" locator filter (%4).")
.arg(menuBarKeys)
.arg(menuBarText)
.arg(actionsFromMenuText)
.arg(actionsFromMenuKeys),
Key("ToogleMenuBarHint"));
}
globalMenuBar()->setVisible(visible);
});

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