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 cgi.py nntplib.py tarfile.py
cgitb.py nturl2path.py telnetlib.py cgitb.py nturl2path.py telnetlib.py
chunk.py numbers.py tempfile.py chunk.py numbers.py tempfile.py
cmd.py optparse.py textwrap.py cmd.py optparse.py this.py
code.py pathlib.py this.py code.py pathlib.py timeit.py
codeop.py pdb.py timeit.py codeop.py pdb.py trace.py
colorsys.py pickle.py trace.py colorsys.py pickle.py tracemalloc.py
compileall.py pickletools.py tracemalloc.py compileall.py pickletools.py tty.py
configparser.py pipes.py tty.py configparser.py pipes.py turtle.py
contextvars.py plistlib.py turtle.py contextvars.py plistlib.py typing.py
cProfile.py poplib.py typing.py cProfile.py poplib.py uu.py
crypt.py pprint.py uu.py crypt.py pprint.py uuid.py
csv.py profile.py uuid.py csv.py profile.py wave.py
dataclasses.py pstats.py wave.py dataclasses.py pstats.py webbrowser.py
datetime.py pty.py webbrowser.py datetime.py pty.py xdrlib.py
decimal.py pyclbr.py xdrlib.py decimal.py pyclbr.py zipapp.py
difflib.py py_compile.py zipapp.py difflib.py py_compile.py zipfile.py
doctest.py queue.py zipfile.py doctest.py queue.py zipimport.py
dummy_threading.py quopri.py zipimport.py dummy_threading.py quopri.py _compat_pickle.py
filecmp.py random.py _compat_pickle.py filecmp.py random.py _compression.py
fileinput.py rlcompleter.py _compression.py fileinput.py rlcompleter.py _dummy_thread.py
formatter.py runpy.py _dummy_thread.py formatter.py runpy.py _markupbase.py
fractions.py sched.py _markupbase.py fractions.py sched.py _osx_support.py
ftplib.py secrets.py _osx_support.py ftplib.py secrets.py _pydecimal.py
getopt.py selectors.py _pydecimal.py getopt.py selectors.py _pyio.py
getpass.py shelve.py _pyio.py getpass.py shelve.py _py_abc.py
gettext.py shlex.py _py_abc.py gettext.py shlex.py _strptime.py
gzip.py shutil.py _strptime.py gzip.py shutil.py _threading_local.py
hashlib.py smtpd.py _threading_local.py hashlib.py smtpd.py __future__.py
hmac.py smtplib.py __future__.py hmac.py smtplib.py __phello__.foo.py
imaplib.py sndhdr.py __phello__.foo.py imaplib.py sndhdr.py
) )
list(FIND python_lib_files "${python_lib_dir}/${not_needed}" found_not_needed) list(FIND python_lib_files "${python_lib_dir}/${not_needed}" found_not_needed)
if (NOT found_not_needed STREQUAL "-1") 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 * Added `Generate Kit` to the Python interpreter preferences for generating a
Python kit with this interpreter 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 `Kit Selection` page for creating and opening Python projects
* Added the target setup page when loading unconfigured Python projects
* Added a `requirements.txt` file to the application wizard * Added a `requirements.txt` file to the application wizard
* Fixed that the same Python interpreter could be auto-detected multiple times * Fixed that the same Python interpreter could be auto-detected multiple times
under different names under different names
([Documentation](https://doc-snapshots.qt.io/qtcreator-13.0/creator-python-development.html))
Debugging 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. The build errors and warnings are parsed and displayed in \l Issues.
\sa {Activate kits for a project}, {Configure projects for building}, \sa {Activate kits for a project}, {Add custom output parsers},
{Configure projects for running}, {Open projects}, {CMake} {Configure projects for building}, {Configure projects for running},
{Open projects}, {CMake}
*/ */

View File

@@ -14,18 +14,6 @@
\title Debugging \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 The \QC debugger plugin acts as an interface between the \QC
core and external native debuggers that you can use to: core and external native debuggers that you can use to:

View File

@@ -8,20 +8,6 @@
\title Refactoring \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 To quickly and conveniently apply actions to refactor your
code, \l{Apply quick fixes}{select quick fixes in a context menu}. 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 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*! /*!
@@ -9,9 +9,9 @@
\title Edit Markdown files \title Edit Markdown files
Open \l{https://www.markdownguide.org/basic-syntax/}{Markdown} (.md) files Open \l{https://www.markdownguide.org/basic-syntax/}{Markdown} (.md) files,
or select \uicontrol File > \uicontrol {New File} > \uicontrol {General} > or go to \uicontrol File > \uicontrol {New File} and select
\uicontrol {Markdown File} to create a new file. \uicontrol {General} > \uicontrol {Markdown File} to create a new file.
\image qtcreator-markdown-editor.webp {Markdown file in Preview and Editor views} \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), 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 or inline code (`), and to create links to web sites
(\inlineimage icons/linkicon.png). (\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-ui.png
\li \inlineimage front-advanced.png \li \inlineimage front-advanced.png
\row \row
\li \b {\l{IDE Overview}} \li \b {\l{Overview}}
If you have not used an integrated development environment (IDE) If you have not used an integrated development environment (IDE)
before, or want to know what kind of IDE \QC is, go to before, or want to know what kind of IDE \QC is, go to
\l{IDE Overview}. \l{Overview}.
\li \b {\l{User Interface}} \li \b {\l{User Interface}}
If you have not used \QC before, and want to become familiar 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 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
// ********************************************************************** // **********************************************************************
@@ -12,147 +12,243 @@
\page creator-overview.html \page creator-overview.html
\nextpage creator-quick-tour.html \nextpage creator-quick-tour.html
\title IDE Overview \title Overview
\QC is an integrated development environment (IDE) that has tools for \QC is a cross-platform, complete integrated development environment
designing and developing applications with the Qt application framework. (IDE) that you can use to create applications for desktop, embedded,
With Qt you can develop applications and user interfaces once and deploy and mobile operating systems, or web browsers.
them to several desktop, embedded, and mobile operating systems or
web browsers (experimental). \QC has the tools for accomplishing your tasks 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 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 \image qt-app-dev-flow.webp {Application development life-cycle}
\row \caption Application development life-cycle
\li \inlineimage front-projects.png
\li \inlineimage front-ui.png
\li \inlineimage front-coding.png
\row
\li \b {Managing Projects}
To be able to build and run applications, \QC needs the same \section1 Projects
information as a compiler would need. It stores the information
in the project settings.
You can share projects with other designers and developers across First, you need a \e project. \QC relies on a separate build system, such as
different development platforms with a common tool for design, CMake, qmake, or Qbs for building the project. From the build system, \QC
development, and debugging. 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 Share projects with other designers and developers across different
\li \l{Creating Projects} development platforms with a common tool for design, development, and
debugging.
To set up a project, you first have to decide what kind \list
of an application you want to develop: do you want a user \li \l{Creating Projects}
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}
The recommended way to set up a project is to use a To set up a project, you first have to decide what kind
version control system. Store and edit only project of an application you want to develop: do you want a user
source files and configuration files. Do not store interface based on \l{User Interfaces}
generated files. {Qt Quick or Qt Widgets}. Second, you have to choose the
\li \l{Configuring Projects} programming language to implement the application logic:
C++ or Python.
\li \l{Version Control Systems}
Installation programs and project wizards create default The recommended way to set up a project is to use a
configurations for \QC and your projects. You can change version control system. Store and edit only project
the configurations in the \uicontrol Projects mode. source files and configuration files. Do not store
\endlist generated files.
For more information, see \l{Manage Projects} \li \l{Configuring Projects}
{How To: Manage Projects}.
\li \b {Designing User Interfaces}
To create intuitive, modern-looking, fluid user interfaces, you Installation programs and project wizards create default
can use \l{Qt Quick} and \l{Qt Design Studio Manual}{\QDS}: configurations for \QC and your projects. Change the
configurations in the \uicontrol Projects mode.
\endlist
\list For more information, see \l{Manage Projects}{How To: Manage Projects}.
\li \l {\QMLD}
Or, you can enable the \QMLD plugin to visually edit \section1 User Interfaces
\l{UI Files}{UI files} (.ui.qml).
\li \l {Converting UI Projects to Applications}
Qt Quick UI Prototype projects (.qmlproject) are useful \image heartgame-start.webp {Heart Rate Game}
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}
If you switch between \QC and \QDS or cooperate with To create intuitive, modern-looking, fluid user interfaces, use \l{Qt Quick}
designers on a project, you might encounter .ui.qml files. and \l{Qt Design Studio Manual}{\QDS}:
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}
You can load C++ plugins for QML to simulate data. \list
\endlist \li \l {\QMLD}
If you need a traditional user interface that has a clear Or, enable the \QMLD plugin to visually edit \l{UI Files}{UI files}
structure and enforces a platform look and feel, use (.ui.qml).
\l{Qt Widgets} and the integrated \l{\QD}. \li \l {Converting UI Projects to Applications}
For more information, see Qt Quick UI Prototype projects (.qmlproject) are useful
\l{Design UIs}{How To: Design UIs}. for creating user interfaces. To use them for application
\li \b {\l{Coding}} 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 If you switch between \QC and \QDS or cooperate with
to build and run applications. It understands the C++ and QML designers on a project, you might encounter .ui.qml files.
languages as code, not just as plain text. Therefore, it can They are intended to be edited in \QDS only, so you need
offer useful features, such as semantic highlighting, to be careful not to break the code. To visually edit the
checking code syntax, code completion, and refactoring actions. files in \QC, enable the \QMLD plugin.
\QC supports some of these services also for other programming \li \l{Using QML Modules with Plugins}
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{Edit Code}{How To: Edit Code}. Load C++ plugins for QML to simulate data.
\row \endlist
\li \inlineimage front-preview.png
\li \inlineimage front-testing.png
\li \inlineimage front-publishing.png
\row
\li \b {\l{Building and Running}}
\QC integrates cross-platform systems for build If you need a traditional user interface that has a clear structure and
automation: qmake, Qbs, CMake, and Autotools. In addition, you enforces a platform look and feel, use \l{Qt Widgets} and the integrated
can import \l{\QD}.
projects as \e {generic projects} and fully control the steps
and commands used to build the project.
You can build applications for, deploy them to, and run them on For more information, see \l{Design UIs}{How To: Design UIs} and
the desktop environment or a \l{glossary-device}{device}. \l{UI Design}.
\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{Build and Run} \section1 Code
{How To: Build and Run}.
\li \b {\l{Testing}}
\QC integrates several external native debuggers that you can use Writing, editing, and navigating in source code are core tasks in application
to inspect the state of your application while debugging. 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 As an IDE, \QC differs from a text editor in that it knows how to build and
carefully. \QC integrates code analysis tools for detecting run applications. It understands the C++ and QML languages as code, not just
memory leaks, profiling function execution, analyzing CPU use, as plain text. Therefore, it can offer useful features, such as semantic
and eliminating unnecessary complexity of code. Other tools highlighting, checking code syntax, code completion, and refactoring actions.
provide code coverage and visualize trace events.
\QC integrates several testing frameworks for unit testing \QC supports some of these services also for other programming languages,
applications and libraries. You can use \QC to create, build, such as Python, for which a \e {language server} is available that provides
and run autotests. information about the code to IDEs.
For more information, see \l{Testing}. \section2 Find
\li \b {Publishing}
\QC enables you to create installation packages for mobile Use the incremental and advanced search to search in currently open projects
devices that you can publish to application stores or files on the file system or use the locator to browse through projects,
and other channels. You must make sure that the package contents files, classes, functions, documentation, and file systems.
meet the requirements for publishing on the channel.
For more information, see \l{Publishing to Google Play}. \section2 Refactor
\endtable
\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} \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 is installed automatically and you should be ready to start
debugging after you create a new project. However, you can debugging after you create a new project. However, you can
change the setup to use debugging tools for Windows, for 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 // 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 has the same XML structure as a \e {.user} file, but only has the
settings to share. settings to share.
\note Use \l{CMake Presets} to share CMake project settings.
\section1 Create a shared settings file \section1 Create a shared settings file
The easiest way to create a \e {.shared} file is to copy settings from the 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 a permanent sticky setting that was created just because you wanted to try
something out. something out.
\sa {Configuring Projects} \sa {Configuring Projects}, {CMake Presets}
*/ */

View File

@@ -19,9 +19,8 @@
\list \list
\li \l{Set up PySide6} \li \l{Set up PySide6}
\li \l{Create Qt for Python applications} \li \l{Create Qt for Python applications}
\li \l{Select the Python interpreter} \li \l{Select the Python version}
\li \l{Create a virtual environment} \li \l{Create kits for Python}
\li \l{Create kits for Python interpreters}
\li \l{Use Python interactive shell} \li \l{Use Python interactive shell}
\li \l{Configure Python language servers} \li \l{Configure Python language servers}
\li \l{Run Python applications} \li \l{Run Python applications}
@@ -48,9 +47,10 @@
\section1 Create Qt for Python applications \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 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 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 \QD form, and the Qt Quick Application wizard creates a \c {.qml} file that
imports Qt Quick controls. imports Qt Quick controls.
@@ -72,50 +72,31 @@
use \c {.pyqtc} files, but we recommend that you choose \c{.pyproject} files use \c {.pyqtc} files, but we recommend that you choose \c{.pyproject} files
for new projects. 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 The \l{kits-tab}{kits} you select for the project in \uicontrol Projects >
Application wizard templates to create Python 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 \image qtcreator-python-interpreter-edit-mode.webp {Python version on the Edit mode toolbar}
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 \section1 Create kits for Python
\uicontrol Projects > \uicontrol Run > \uicontrol Interpreter.
\image qtcreator-python-run-settings.png {Python run settings} \QC automatically adds all Python versions it can find to the list of
Python versions in \preferences > \uicontrol Python > \uicontrol Interpreters.
To see the available interpreters and choose another interpreter, select the It generates kits for the global Python versions that are not inside a
current interpreter, and then select \uicontrol {Manage Python Interpreters}. virtual environment.
Or, select \preferences > \uicontrol Python > \uicontrol Interpreters.
\image qtcreator-python-interpreters.webp {Python Interpreters in Preferences} \image qtcreator-python-interpreters.webp {Python Interpreters in Preferences}
You can add and remove interpreters and clean up references to interpreters You can add and remove Python versions and clean up references to Python
that you uninstalled, but that still appear in the list. versions that you uninstalled, but that still appear in the list.
To use the selected Python interpreter by default, select To use the selected Python version when opening \c {.py} files that don't
\uicontrol {Make Default}. belong to a project, 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 a virtual environment as a kit, select it in \uicontrol Interpreters, To use a virtual environment as a kit, select it in \uicontrol Interpreters,
and then select \uicontrol {Generate Kit}. and then select \uicontrol {Generate Kit}.
@@ -133,5 +114,6 @@
the file, select \uicontrol {REPL Import *}. the file, select \uicontrol {REPL Import *}.
\sa {Creating a Qt for Python Application with Qt Widgets}, \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} > you select for a kit in \uicontrol Projects > \uicontrol {Build & Run} >
\uicontrol Run > \uicontrol {Run Settings}. \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 The following table summarizes the settings for running Qt for Python
applications. applications.
@@ -45,7 +45,7 @@
\li Setting \li Setting
\li Value \li Value
\row \row
\li \uicontrol Interpreter \li \uicontrol Python
\li Path to the Python executable. \li Path to the Python executable.
\row \row
\li \uicontrol {Buffered output} \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 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*! /*!
@@ -28,11 +28,10 @@
class: class:
\list 1 \list 1
\li Select \uicontrol File > \uicontrol {New Project} > \li Go to \uicontrol File > \uicontrol {New Project}.
\uicontrol {Application (Qt for Python)} > \uicontrol {Empty Window} \li Select \uicontrol {Application (Qt for Python)} >
> \uicontrol Choose. \uicontrol {Empty Window} > \uicontrol Choose to open the
\uicontrol {Project Location} dialog.
The \uicontrol {Project Location} dialog opens.
\image qtcreator-new-qt-for-python-app-widgets-project-location.webp {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, \li In \uicontrol {Name}, enter the project name. For example,
\e {hello_world}. \e {hello_world}.
@@ -49,16 +48,14 @@
\li In \uicontrol {Project file}, enter a name for the project file. \li In \uicontrol {Project file}, enter a name for the project file.
\li Select \uicontrol{Next} or \uicontrol Continue to open the \li Select \uicontrol{Next} or \uicontrol Continue to open the
\uicontrol {Define Project Details} dialog. \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 \li In \uicontrol {PySide version}, select the PySide version of the
generated code. generated code.
\li In \uicontrol {Interpreter}, select the Python interpreter to use for \li Select \uicontrol{Next} or \uicontrol Continue to open the
the project. \uicontrol {Kit Selection} dialog.
\li Select the \uicontrol {Create new virtual environment} check box to \image qtcreator-new-project-qt-for-python-kit-selection.webp {Selecting a kit for a Python project}
use a clean \l{https://docs.python.org/3/library/venv.html}{Python} \li Select Qt for Python kits for building, deploying, and running the
environment that is independent of your global Python installation. project.
\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.
\li Review the project settings, and select \uicontrol {Finish} (on \li Review the project settings, and select \uicontrol {Finish} (on
Windows and Linux) or \uicontrol Done (on \macos) to create the 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 \li \c {hellow_world.pyproject}, which lists the files in the Python
project. project.
\li \c {mywidget.py}, which has some boilerplate code for a class. \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 \endlist
\section1 Adding Qt Widgets Imports \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 // 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} \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 \section1 Creating an Empty Project
To create a Qt for Python application that has a main QML file: To create a Qt for Python application that has a main QML file:
\list 1 \list 1
\li Select \uicontrol File > \uicontrol {New Project} > \li Go to \uicontrol File > \uicontrol {New Project}.
\uicontrol {Application (Qt for Python)} > \li Select \uicontrol {Application (Qt for Python)} >
\uicontrol {Qt Quick Application - Empty} > \uicontrol Choose. \uicontrol {Qt Quick Application - Empty} > \uicontrol Choose to
open the \uicontrol {Project Location} dialog.
The \uicontrol {Project Location} dialog opens.
\image qtcreator-new-qt-for-python-app-qt-quick-empty-project-location.webp {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, \li In \uicontrol {Name}, enter the project name. For example,
\e {hello_world_quick}. \e {hello_world_quick}.
@@ -35,17 +38,14 @@
For example, \c {C:\Qt\examples}. For example, \c {C:\Qt\examples}.
\li Select \uicontrol{Next} (on Windows and Linux) or \uicontrol Continue \li Select \uicontrol{Next} (on Windows and Linux) or \uicontrol Continue
(on \macos) to open the \uicontrol {Define Project Details} dialog. (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 \li In \uicontrol {PySide version}, select the PySide version of
the generated code. the generated code.
\li In \uicontrol {Interpreter}, select the Python interpreter to use for \li Select \uicontrol{Next} or \uicontrol Continue to open the
the project. \uicontrol {Kit Selection} dialog.
\li Select the \uicontrol {Create new virtual environment} check box to \image qtcreator-new-project-qt-for-python-kit-selection.webp {Selecting a kit for a Python project}
use a clean \l{https://docs.python.org/3/library/venv.html}{Python} \li Select Qt for Python kits for building, deploying, and running the
environment that is independent of your global Python installation. project.
\li In \uicontrol {Path to virtual environment}, specify the directory
where to create the environment.
\li Select \uicontrol{Next} or \uicontrol Continue.
\li Review the project settings, and select \uicontrol {Finish} (on \li Review the project settings, and select \uicontrol {Finish} (on
Windows and Linux) or \uicontrol Done (on \macos) to create the Windows and Linux) or \uicontrol Done (on \macos) to create the
project. project.
@@ -58,6 +58,8 @@
project. project.
\li \c {main.py}, which has some boilerplate code. \li \c {main.py}, which has some boilerplate code.
\li \c {main.qml}, which imports Qt Quick controls. \li \c {main.qml}, which imports Qt Quick controls.
\li \c {reguirements.txt}, which stores the PySide version of the
generated code.
\endlist \endlist
\section1 Adding Qt Quick Imports \section1 Adding Qt Quick Imports

View File

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

View File

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

View File

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

View File

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

View File

@@ -1717,12 +1717,8 @@ Se Google Test-dokumentation for yderligere information om GTest-filtre.</transl
<translation>Perf</translation> <translation>Perf</translation>
</message> </message>
<message> <message>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable. <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>
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>
</message> </message>
<message> <message>
<source>Select Run Configuration</source> <source>Select Run Configuration</source>
@@ -18346,8 +18342,8 @@ Id&apos;er skal begynde med et lille bogstav.</translation>
<translation>Simulator start</translation> <translation>Simulator start</translation>
</message> </message>
<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>Kan ikke starte simulator (%1, %2) i aktuelle tilstand: %3</translation> <translation>Kan ikke starte simulator (%1, %2) i aktuelle tilstand: %3.</translation>
</message> </message>
<message> <message>
<source>simulator start</source> <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> <translation>Auf abgeleitete Qt Quick-Tests überprüfen</translation>
</message> </message>
<message> <message>
<source>Search for Qt Quick tests that are derived from TestCase. <source>Search for Qt Quick tests that are derived from TestCase.&lt;p&gt;Warning: Enabling this feature significantly increases scan time.</source>
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>
<translation>Sucht nach Qt Quick-Tests, die von TestCase abgeleitet sind.
Achtung: Dies erhöht die zum Durchsuchen benötigte Zeit erheblich.</translation>
</message> </message>
<message> <message>
<source>Benchmark Metrics</source> <source>Benchmark Metrics</source>
@@ -12463,12 +12461,8 @@ Achtung: Dies erhöht die zum Durchsuchen benötigte Zeit erheblich.</translatio
<translation>Perf</translation> <translation>Perf</translation>
</message> </message>
<message> <message>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable. <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>
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>
</message> </message>
<message> <message>
<source>Select Run Configuration</source> <source>Select Run Configuration</source>
@@ -33897,7 +33891,7 @@ Möchten Sie sie überschreiben?</translation>
<translation>Simulator starten</translation> <translation>Simulator starten</translation>
</message> </message>
<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> <translation>Der Simulator (%1, %2) kann im momentanen Zustand (%3) nicht gestartet werden.</translation>
</message> </message>
<message> <message>

View File

@@ -798,7 +798,7 @@ Une valeur positive augmente la réverbération pour les hautes fréquences et
</message> </message>
</context> </context>
<context> <context>
<name>Axivion</name> <name>QtC::Axivion</name>
<message> <message>
<source>Project:</source> <source>Project:</source>
<translation>Projet&#xa0;:</translation> <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> <translation>Utilise la sortie XML</translation>
</message> </message>
<message> <message>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable. <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>
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>
</message> </message>
<message> <message>
<source>Verbose benchmarks</source> <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> <translation>Vérifier la présence de tests dérivés de Qt Quick</translation>
</message> </message>
<message> <message>
<source>Search for Qt Quick tests that are derived from TestCase. <source>Search for Qt Quick tests that are derived from TestCase.&lt;p&gt;Warning: Enabling this feature significantly increases scan time.</source>
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>
<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>
</message> </message>
<message> <message>
<source>Benchmark Metrics</source> <source>Benchmark Metrics</source>
@@ -33467,8 +33461,8 @@ Souhaitez-vous les écraser&#xa0;?</translation>
</translation> </translation>
</message> </message>
<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>Impossible de démarrer le simulateur (%1, %2) dans l&apos;état actuel&#xa0;: %3</translation> <translation>Impossible de démarrer le simulateur (%1, %2) dans l&apos;état actuel&#xa0;: %3.</translation>
</message> </message>
<message> <message>
<source>simulator start</source> <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> <translation>Deaktiviraj rukovatelja urušivanja tijekom uklanjanja grešaka</translation>
</message> </message>
<message> <message>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable. <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>
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>
</message> </message>
<message> <message>
<source>Use XML output</source> <source>Use XML output</source>
@@ -4546,8 +4542,8 @@ Dodaj, izmijeni i ukloni filtre dokumenata koji određuju skup dokumentacije pri
</translation> </translation>
</message> </message>
<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>Nije moguće pokrenuti simulatora (%1, %2) u trenutačnom stanju: %3</translation> <translation>Nije moguće pokrenuti simulatora (%1, %2) u trenutačnom stanju: %3.</translation>
</message> </message>
<message> <message>
<source>simulator start</source> <source>simulator start</source>

View File

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

View File

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

View File

@@ -2950,12 +2950,8 @@ See Google Test documentation for further information on GTest filters.</source>
<translation>使 XML </translation> <translation>使 XML </translation>
</message> </message>
<message> <message>
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable. <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>
Warning: Plain text misses some information, such as duration.</source>
<translation>使 XML
</translation>
</message> </message>
<message> <message>
<source>Verbose benchmarks</source> <source>Verbose benchmarks</source>
@@ -23217,7 +23213,7 @@ Id必须以小写字母开头。</translation>
</translation> </translation>
</message> </message>
<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> <translation type="unfinished"></translation>
</message> </message>
<message> <message>

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
// Copyright (C) 2022 The Qt Company Ltd. // 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" #include "nanotrace.h"

View File

@@ -1,5 +1,5 @@
// Copyright (C) 2022 The Qt Company Ltd. // 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 #pragma once

View File

@@ -1,5 +1,5 @@
// Copyright (C) 2023 The Qt Company Ltd. // 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 #pragma once

View File

@@ -1,5 +1,5 @@
// Copyright (C) 2023 The Qt Company Ltd. // 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" #include "nanotracehr.h"

View File

@@ -1,5 +1,5 @@
// Copyright (C) 2022 The Qt Company Ltd. // 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 #pragma once

View File

@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd. # 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 pandas as pd
import plotly.graph_objects as go import plotly.graph_objects as go
import plotly.subplots as sp import plotly.subplots as sp

View File

@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd. # 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 reader as rd
import figures as fgs import figures as fgs

View File

@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd. # 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 os
import io import io
import json import json

View File

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

View File

@@ -205,7 +205,7 @@ if (_library_enabled)
# Deploy lldb.exe and its Python dependency # Deploy lldb.exe and its Python dependency
find_package(Clang QUIET) find_package(Clang QUIET)
if (LLVM_TOOLS_BINARY_DIR AND LLVM_LIBRARY_DIRS) 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}) if (EXISTS ${LLVM_TOOLS_BINARY_DIR}/${lldb_file})
install(FILES ${LLVM_TOOLS_BINARY_DIR}/${lldb_file} install(FILES ${LLVM_TOOLS_BINARY_DIR}/${lldb_file}
DESTINATION bin/clang/bin DESTINATION bin/clang/bin

View File

@@ -7,10 +7,12 @@
#include <QEventLoop> #include <QEventLoop>
#include <QFutureWatcher> #include <QFutureWatcher>
#include <QHash> #include <QHash>
#include <QMetaEnum>
#include <QMutex> #include <QMutex>
#include <QPromise> #include <QPromise>
#include <QPointer> #include <QPointer>
#include <QSet> #include <QSet>
#include <QTime>
#include <QTimer> #include <QTimer>
using namespace std::chrono; using namespace std::chrono;
@@ -1191,6 +1193,17 @@ const GroupItem stopOnSuccessOrError = workflowPolicy(WorkflowPolicy::StopOnSucc
const GroupItem finishAllAndSuccess = workflowPolicy(WorkflowPolicy::FinishAllAndSuccess); const GroupItem finishAllAndSuccess = workflowPolicy(WorkflowPolicy::FinishAllAndSuccess);
const GroupItem finishAllAndError = workflowPolicy(WorkflowPolicy::FinishAllAndError); 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) DoneResult toDoneResult(bool success)
{ {
return success ? DoneResult::Success : DoneResult::Error; 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, ExecutableItem ExecutableItem::withTimeout(milliseconds timeout,
const std::function<void()> &handler) const std::function<void()> &handler) const
{ {
const auto onSetup = [timeout](milliseconds &timeoutData) { timeoutData = timeout; }; const auto onSetup = [timeout](milliseconds &timeoutData) { timeoutData = timeout; };
return Group { return Group {
@@ -1414,7 +1427,41 @@ GroupItem GroupItem::withTimeout(const GroupItem &item, milliseconds timeout,
handler ? TimeoutTask(onSetup, [handler] { handler(); }, CallDoneIf::Success) handler ? TimeoutTask(onSetup, [handler] { handler(); }, CallDoneIf::Success)
: TimeoutTask(onSetup) : 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 class ExecutionContextActivator
{ {
public: public:
ExecutionContextActivator(RuntimeIteration *iteration) { activateContext(iteration); } ExecutionContextActivator(RuntimeIteration *iteration) {
ExecutionContextActivator(RuntimeContainer *container) { activateContext(container); } activateTaskTree(iteration);
activateContext(iteration);
}
ExecutionContextActivator(RuntimeContainer *container) {
activateTaskTree(container);
activateContext(container);
}
~ExecutionContextActivator() { ~ExecutionContextActivator() {
for (int i = m_activeStorages.size() - 1; i >= 0; --i) // iterate in reverse order for (int i = m_activeStorages.size() - 1; i >= 0; --i) // iterate in reverse order
m_activeStorages[i].m_storageData->threadData().popStorage(); m_activeStorages[i].m_storageData->threadData().popStorage();
for (int i = m_activeLoops.size() - 1; i >= 0; --i) // iterate in reverse order for (int i = m_activeLoops.size() - 1; i >= 0; --i) // iterate in reverse order
m_activeLoops[i].m_loopData->threadData().popIteration(); m_activeLoops[i].m_loopData->threadData().popIteration();
QT_ASSERT(s_activeTaskTrees.size(), return);
s_activeTaskTrees.pop_back();
} }
private: private:
void activateTaskTree(RuntimeIteration *iteration);
void activateTaskTree(RuntimeContainer *container);
void activateContext(RuntimeIteration *iteration); void activateContext(RuntimeIteration *iteration);
void activateContext(RuntimeContainer *container); void activateContext(RuntimeContainer *container);
QList<Loop> m_activeLoops; QList<Loop> m_activeLoops;
@@ -1490,9 +1547,8 @@ public:
void start(); void start();
void stop(); void stop();
void bumpAsyncCount();
void advanceProgress(int byValue); void advanceProgress(int byValue);
void emitStartedAndProgress();
void emitProgress();
void emitDone(DoneWith result); void emitDone(DoneWith result);
void callSetupHandler(StorageBase storage, StoragePtr storagePtr) { void callSetupHandler(StorageBase storage, StoragePtr storagePtr) {
callStorageHandler(storage, storagePtr, &StorageHandler::m_setupHandler); callStorageHandler(storage, storagePtr, &StorageHandler::m_setupHandler);
@@ -1552,6 +1608,7 @@ public:
TaskTree *q = nullptr; TaskTree *q = nullptr;
Guard m_guard; Guard m_guard;
int m_progressValue = 0; int m_progressValue = 0;
int m_asyncCount = 0;
QSet<StorageBase> m_storages; QSet<StorageBase> m_storages;
QHash<StorageBase, StorageHandler> m_storageHandlers; QHash<StorageBase, StorageHandler> m_storageHandlers;
std::optional<TaskNode> m_root; std::optional<TaskNode> m_root;
@@ -1666,6 +1723,16 @@ static bool isProgressive(RuntimeContainer *container)
return iteration ? iteration->m_isProgressive : true; 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) void ExecutionContextActivator::activateContext(RuntimeIteration *iteration)
{ {
std::optional<Loop> loop = iteration->loop(); std::optional<Loop> loop = iteration->loop();
@@ -1696,8 +1763,14 @@ void TaskTreePrivate::start()
{ {
QT_ASSERT(m_root, return); QT_ASSERT(m_root, return);
QT_ASSERT(!m_runtimeRoot, return); QT_ASSERT(!m_runtimeRoot, return);
m_asyncCount = 0;
m_progressValue = 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 // TODO: check storage handlers for not existing storages in tree
for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) { for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) {
QT_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't " 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}); m_runtimeRoot.reset(new RuntimeTask{*m_root});
start(m_runtimeRoot.get()); start(m_runtimeRoot.get());
bumpAsyncCount();
} }
void TaskTreePrivate::stop() void TaskTreePrivate::stop()
@@ -1717,6 +1791,15 @@ void TaskTreePrivate::stop()
emitDone(DoneWith::Cancel); 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) void TaskTreePrivate::advanceProgress(int byValue)
{ {
if (byValue == 0) if (byValue == 0)
@@ -1724,18 +1807,6 @@ void TaskTreePrivate::advanceProgress(int byValue)
QT_CHECK(byValue > 0); QT_CHECK(byValue > 0);
QT_CHECK(m_progressValue + byValue <= m_root->taskCount()); QT_CHECK(m_progressValue + byValue <= m_root->taskCount());
m_progressValue += byValue; 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); GuardLocker locker(m_guard);
emit q->progressValueChanged(m_progressValue); emit q->progressValueChanged(m_progressValue);
} }
@@ -2037,10 +2108,12 @@ SetupResult TaskTreePrivate::start(RuntimeTask *node)
node->m_task.release()->deleteLater(); node->m_task.release()->deleteLater();
RuntimeIteration *parentIteration = node->m_parentIteration; RuntimeIteration *parentIteration = node->m_parentIteration;
parentIteration->deleteChild(node); parentIteration->deleteChild(node);
if (parentIteration->m_container->isStarting()) if (parentIteration->m_container->isStarting()) {
*unwindAction = toSetupResult(result); *unwindAction = toSetupResult(result);
else } else {
childDone(parentIteration, result); childDone(parentIteration, result);
bumpAsyncCount();
}
}); });
node->m_task->start(); node->m_task->start();
@@ -2961,6 +3034,38 @@ DoneWith TaskTree::runBlocking(const Group &recipe, const QFuture<void> &future,
return taskTree.runBlocking(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. 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 started, this number is set to \c 0.
When the task tree is finished, this number always equals progressMaximum(). When the task tree is finished, this number always equals progressMaximum().
\sa progressMaximum() \sa progressMaximum(), progressValueChanged()
*/ */
int TaskTree::progressValue() const int TaskTree::progressValue() const
{ {

View File

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

View File

@@ -1199,6 +1199,9 @@ void TerminalView::mouseReleaseEvent(QMouseEvent *event)
void TerminalView::mouseDoubleClickEvent(QMouseEvent *event) void TerminalView::mouseDoubleClickEvent(QMouseEvent *event)
{ {
if (event->button() != Qt::LeftButton)
return;
if (d->m_allowMouseTracking) { if (d->m_allowMouseTracking) {
d->m_surface->mouseMove(toGridPos(event), event->modifiers()); d->m_surface->mouseMove(toGridPos(event), event->modifiers());
d->m_surface->mouseButton(event->button(), true, 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> template <typename R, typename T>
const QFuture<T> &onResultReady(const QFuture<T> &future, R *receiver, void(R::*member)(const 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::finished, watcher, &QObject::deleteLater);
QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, receiver, [=](int index) { QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, receiver, [=](int index) {
(receiver->*member)(watcher->future().resultAt(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> template <typename T, typename Function>
const QFuture<T> &onResultReady(const QFuture<T> &future, QObject *guard, Function f) 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::finished, watcher, &QObject::deleteLater);
QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, guard, [f, watcher](int index) { QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, guard, [f, watcher](int index) {
f(watcher->future().resultAt(index)); f(watcher->future().resultAt(index));
@@ -90,7 +90,7 @@ template<typename R, typename T>
const QFuture<T> &onFinished(const QFuture<T> &future, const QFuture<T> &onFinished(const QFuture<T> &future,
R *receiver, void (R::*member)(const QFuture<T> &)) 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, watcher, &QObject::deleteLater);
QObject::connect(watcher, &QFutureWatcherBase::finished, receiver, QObject::connect(watcher, &QFutureWatcherBase::finished, receiver,
[=] { (receiver->*member)(watcher->future()); }); [=] { (receiver->*member)(watcher->future()); });
@@ -107,7 +107,7 @@ const QFuture<T> &onFinished(const QFuture<T> &future,
template<typename T, typename Function> template<typename T, typename Function>
const QFuture<T> &onFinished(const QFuture<T> &future, QObject *guard, Function f) 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, watcher, &QObject::deleteLater);
QObject::connect(watcher, &QFutureWatcherBase::finished, guard, [f, watcher] { QObject::connect(watcher, &QFutureWatcherBase::finished, guard, [f, watcher] {
f(watcher->future()); f(watcher->future());

View File

@@ -566,8 +566,8 @@ static bool checkToRefuseRemoveStandardLocationDirectory(const QString &dirPath,
{ {
if (QStandardPaths::standardLocations(location).contains(dirPath)) { if (QStandardPaths::standardLocations(location).contains(dirPath)) {
if (error) { if (error) {
*error = Tr::tr("Refusing to remove your %1 directory.").arg( *error = Tr::tr("Refusing to remove the standard directory \"%1\".")
QStandardPaths::displayName(location)); .arg(QStandardPaths::displayName(location));
} }
return false; 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 * 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 * 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. * \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 * variables such as \c{CurrentDocument:FilePath} with description
* "Current Document: Full path including file name." * "Current Document: Full path including file name."
* *

View File

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

View File

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

View File

@@ -126,6 +126,15 @@ static QString ndkPackageMarker()
return QLatin1String(Constants::ndkPackageName) + ";"; return QLatin1String(Constants::ndkPackageName) + ";";
} }
static QString platformsPackageMarker()
{
return QLatin1String(Constants::platformsPackageName) + ";";
}
static QString buildToolsPackageMarker()
{
return QLatin1String(Constants::buildToolsPackageName) + ";";
}
////////////////////////////////// //////////////////////////////////
// AndroidConfig // AndroidConfig
@@ -951,15 +960,59 @@ bool AndroidConfig::sdkToolsOk() const
return exists && writable && sdkToolsExist; 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 QStringList AndroidConfig::essentialsFromQtVersion(const QtVersion &version) const
{ {
if (auto androidQtVersion = dynamic_cast<const AndroidQtVersion *>(&version)) { if (auto androidQtVersion = dynamic_cast<const AndroidQtVersion *>(&version)) {
bool ok; bool ok;
const AndroidQtVersion::BuiltWith bw = androidQtVersion->builtWith(&ok); const AndroidQtVersion::BuiltWith bw = androidQtVersion->builtWith(&ok);
if (ok) { if (ok) {
const QString ndkPackage = ndkPackageMarker() + bw.ndkVersion.toString(); QStringList builtWithPackages;
return QStringList(ndkPackage) builtWithPackages.append(ndkPackageMarker() + bw.ndkVersion.toString());
+ packagesWithoutNdks(m_defaultSdkDepends.essentialPackages); 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 // SDK Tools
const char cmdlineToolsName[] = "cmdline-tools"; const char cmdlineToolsName[] = "cmdline-tools";
const char ndkPackageName[] = "ndk"; const char ndkPackageName[] = "ndk";
const char platformsPackageName[] = "platforms";
const char buildToolsPackageName[] = "build-tools";
// For AndroidQtVersion // For AndroidQtVersion
const char ArmToolsDisplayName[] = "arm"; const char ArmToolsDisplayName[] = "arm";

View File

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

View File

@@ -70,7 +70,7 @@ DataTagLocatorFilter::DataTagLocatorFilter()
{ {
setId("Locate Qt Test data tags"); setId("Locate Qt Test data tags");
setDisplayName(Tr::tr("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"); setDefaultShortcutString("qdt");
setPriority(Medium); setPriority(Medium);
using namespace ProjectExplorer; using namespace ProjectExplorer;

View File

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

View File

@@ -6,11 +6,14 @@ add_qtc_plugin(Axivion
axivion.qrc axivion.qrc
axivionoutputpane.cpp axivionoutputpane.h axivionoutputpane.cpp axivionoutputpane.h
axivionplugin.cpp axivionplugin.h axivionplugin.cpp axivionplugin.h
axivionprojectsettings.h axivionprojectsettings.cpp axivionprojectsettings.cpp axivionprojectsettings.h
axivionsettings.cpp axivionsettings.h axivionsettings.cpp axivionsettings.h
axiviontr.h axiviontr.h
credentialquery.h credentialquery.cpp credentialquery.h credentialquery.cpp
dashboard/dto.cpp dashboard/dto.h dashboard/dto.cpp dashboard/dto.h
dashboard/concat.cpp dashboard/concat.h dashboard/concat.cpp dashboard/concat.h
dashboard/error.h dashboard/error.cpp 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.cpp",
"axivionsettings.h", "axivionsettings.h",
"axiviontr.h", "axiviontr.h",
"credentialquery.h",
"credentialquery.cpp", "credentialquery.cpp",
"credentialquery.h",
"dynamiclistmodel.cpp",
"dynamiclistmodel.h",
"issueheaderview.cpp",
"issueheaderview.h",
] ]
cpp.includePaths: base.concat(["."]) // needed for the generated stuff below 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-mv@2x.png</file>
<file>images/button-sv.png</file> <file>images/button-sv.png</file>
<file>images/button-sv@2x.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> </qresource>
</RCC> </RCC>

View File

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

View File

@@ -3,38 +3,11 @@
#pragma once #pragma once
#include <coreplugin/ioutputpane.h> #include <QObject>
QT_BEGIN_NAMESPACE
class QStackedWidget;
QT_END_NAMESPACE
namespace Axivion::Internal { namespace Axivion::Internal {
class AxivionOutputPane : public Core::IOutputPane void setupAxivionOutputPane(QObject *guard);
{ void updateDashboard();
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;
};
} // Axivion::Internal } // Axivion::Internal

View File

@@ -7,13 +7,16 @@
#include "axivionprojectsettings.h" #include "axivionprojectsettings.h"
#include "axivionsettings.h" #include "axivionsettings.h"
#include "axiviontr.h" #include "axiviontr.h"
#include "credentialquery.h"
#include "dashboard/dto.h" #include "dashboard/dto.h"
#include "dashboard/error.h" #include "dashboard/error.h"
#include <coreplugin/editormanager/documentmodel.h> #include <coreplugin/editormanager/documentmodel.h>
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/inavigationwidgetfactory.h>
#include <coreplugin/messagemanager.h> #include <coreplugin/messagemanager.h>
#include <coreplugin/navigationwidget.h>
#include <extensionsystem/iplugin.h> #include <extensionsystem/iplugin.h>
#include <extensionsystem/pluginmanager.h> #include <extensionsystem/pluginmanager.h>
@@ -31,19 +34,26 @@
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/async.h> #include <utils/async.h>
#include <utils/checkablemessagebox.h>
#include <utils/environment.h>
#include <utils/networkaccessmanager.h> #include <utils/networkaccessmanager.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/utilsicons.h> #include <utils/utilsicons.h>
#include <QAction> #include <QAction>
#include <QDesktopServices>
#include <QInputDialog>
#include <QMessageBox> #include <QMessageBox>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkReply> #include <QNetworkReply>
#include <QTextBrowser>
#include <QTimer> #include <QTimer>
#include <QUrlQuery>
#include <memory> #include <memory>
constexpr char AxivionTextMarkId[] = "AxivionTextMark"; constexpr char s_axivionTextMarkId[] = "AxivionTextMark";
constexpr char s_axivionKeychainService[] = "keychain.axivion.qtcreator";
using namespace Core; using namespace Core;
using namespace ProjectExplorer; using namespace ProjectExplorer;
@@ -96,6 +106,42 @@ QString anyToSimpleString(const Dto::Any &any)
return {}; 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 QString IssueListSearch::toQuery() const
{ {
if (kind.isEmpty()) if (kind.isEmpty())
@@ -118,18 +164,24 @@ QString IssueListSearch::toQuery() const
QString::fromUtf8((QUrl::toPercentEncoding(owner))))); QString::fromUtf8((QUrl::toPercentEncoding(owner)))));
} }
if (!filter_path.isEmpty()) { 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)))); QString::fromUtf8(QUrl::toPercentEncoding(filter_path))));
} }
if (!state.isEmpty()) if (!state.isEmpty())
result.append(QString("&state=%1").arg(state)); result.append(QString("&state=%1").arg(state));
if (computeTotalRowCount) if (computeTotalRowCount)
result.append("&computeTotalRowCount=true"); result.append("&computeTotalRowCount=true");
if (!sort.isEmpty())
result.append(QString("&sort=%1").arg(
QString::fromUtf8(QUrl::toPercentEncoding(sort))));
return result; return result;
} }
enum class ServerAccess { Unknown, NoAuthorization, WithAuthorization };
class AxivionPluginPrivate : public QObject class AxivionPluginPrivate : public QObject
{ {
Q_OBJECT
public: public:
AxivionPluginPrivate(); AxivionPluginPrivate();
void handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors); void handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
@@ -141,9 +193,18 @@ public:
void clearAllMarks(); void clearAllMarks();
void handleIssuesForFile(const Dto::FileViewDto &fileView); void handleIssuesForFile(const Dto::FileViewDto &fileView);
void fetchIssueInfo(const QString &id); 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; NetworkAccessManager m_networkAccessManager;
AxivionOutputPane m_axivionOutputPane;
std::optional<DashboardInfo> m_dashboardInfo; std::optional<DashboardInfo> m_dashboardInfo;
std::optional<Dto::ProjectInfoDto> m_currentProjectInfo; std::optional<Dto::ProjectInfoDto> m_currentProjectInfo;
Project *m_project = nullptr; Project *m_project = nullptr;
@@ -158,13 +219,16 @@ static AxivionPluginPrivate *dd = nullptr;
class AxivionTextMark : public TextMark class AxivionTextMark : public TextMark
{ {
public: public:
AxivionTextMark(const FilePath &filePath, const Dto::LineMarkerDto &issue) AxivionTextMark(const FilePath &filePath, const Dto::LineMarkerDto &issue,
: TextMark(filePath, issue.startLine, {Tr::tr("Axivion"), AxivionTextMarkId}) std::optional<Theme::Color> color)
: TextMark(filePath, issue.startLine, {"Axivion", s_axivionTextMarkId})
{ {
const QString markText = issue.description; const QString markText = issue.description;
const QString id = issue.kind + QString::number(issue.id.value_or(-1)); const QString id = issue.kind + QString::number(issue.id.value_or(-1));
setToolTip(id + markText); setToolTip(id + '\n' + markText);
setIcon(iconForIssue(issue.kind)); setIcon(iconForIssue(issue.kind));
if (color)
setColor(*color);
setPriority(TextMark::NormalPriority); setPriority(TextMark::NormalPriority);
setLineAnnotation(markText); setLineAnnotation(markText);
setActionsProvider([id] { setActionsProvider([id] {
@@ -247,7 +311,7 @@ void AxivionPluginPrivate::onStartupProjectChanged(Project *project)
m_project = project; m_project = project;
clearAllMarks(); clearAllMarks();
m_currentProjectInfo = {}; m_currentProjectInfo = {};
m_axivionOutputPane.updateDashboard(); updateDashboard();
if (!m_project) if (!m_project)
return; return;
@@ -259,10 +323,7 @@ void AxivionPluginPrivate::onStartupProjectChanged(Project *project)
static QUrl urlForProject(const QString &projectName) static QUrl urlForProject(const QString &projectName)
{ {
QString dashboard = settings().server.dashboard; return QUrl(settings().server.dashboard).resolved(QString("api/projects/")).resolved(projectName);
if (!dashboard.endsWith(QLatin1Char('/')))
dashboard += QLatin1Char('/');
return QUrl(dashboard).resolved(QStringLiteral("api/projects/")).resolved(projectName);
} }
static constexpr int httpStatusCodeOk = 200; 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) static Group fetchHtmlRecipe(const QUrl &url, const std::function<void(const QByteArray &)> &handler)
{ {
struct StorageData // TODO: Refactor so that it's a common code with fetchDataRecipe().
{ const auto onQuerySetup = [url](NetworkQuery &query) {
QByteArray credentials; 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); QNetworkRequest request(url);
request.setRawHeader("Accept", s_htmlContentType); 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() + const QByteArray ua = "Axivion" + QCoreApplication::applicationName().toUtf8() +
"Plugin/" + QCoreApplication::applicationVersion().toUtf8(); "Plugin/" + QCoreApplication::applicationVersion().toUtf8();
request.setRawHeader("X-Axivion-User-Agent", ua); request.setRawHeader("X-Axivion-User-Agent", ua);
query.setRequest(request); query.setRequest(request);
query.setNetworkAccessManager(&dd->m_networkAccessManager); query.setNetworkAccessManager(&dd->m_networkAccessManager);
return SetupResult::Continue;
}; };
const auto onQueryDone = [url, handler](const NetworkQuery &query, DoneWith doneWith) { const auto onQueryDone = [url, handler](const NetworkQuery &query, DoneWith doneWith) {
QNetworkReply *reply = query.reply(); QNetworkReply *reply = query.reply();
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 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; return DoneResult::Error;
}; };
return {NetworkQueryTask(onQuerySetup, onQueryDone)};
const Group recipe {
storage,
Sync(onCredentialSetup),
NetworkQueryTask(onQuerySetup, onQueryDone),
};
return recipe;
} }
template <typename DtoType> template <typename DtoType>
struct GetDtoStorage struct GetDtoStorage
{ {
QByteArray credential;
QUrl url; QUrl url;
std::optional<QByteArray> credential;
std::optional<DtoType> dtoData; 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> template <typename DtoType>
struct PostDtoStorage struct PostDtoStorage
{ {
QByteArray credential;
QUrl url; QUrl url;
std::optional<QByteArray> credential;
QByteArray csrfToken; QByteArray csrfToken;
QByteArray writeData; QByteArray writeData;
std::optional<DtoType> dtoData; std::optional<DtoType> dtoData;
}; };
template <typename DtoType> template <typename DtoType, template <typename> typename DtoStorageType>
static Group postDtoRecipe(const Storage<PostDtoStorage<DtoType>> &dtoStorage) static Group dtoRecipe(const Storage<DtoStorageType<DtoType>> &dtoStorage)
{ {
const Storage<QByteArray> storage; const Storage<QByteArray> storage;
const auto onNetworkQuerySetup = [dtoStorage](NetworkQuery &query) { const auto onNetworkQuerySetup = [dtoStorage](NetworkQuery &query) {
QNetworkRequest request(dtoStorage->url); QNetworkRequest request(dtoStorage->url);
request.setRawHeader("Accept", s_jsonContentType); 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() + const QByteArray ua = "Axivion" + QCoreApplication::applicationName().toUtf8() +
"Plugin/" + QCoreApplication::applicationVersion().toUtf8(); "Plugin/" + QCoreApplication::applicationVersion().toUtf8();
request.setRawHeader("X-Axivion-User-Agent", ua); 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.setRequest(request);
query.setWriteData(dtoStorage->writeData);
query.setOperation(NetworkOperation::Post);
query.setNetworkAccessManager(&dd->m_networkAccessManager); query.setNetworkAccessManager(&dd->m_networkAccessManager);
}; };
@@ -464,76 +443,220 @@ static Group postDtoRecipe(const Storage<PostDtoStorage<DtoType>> &dtoStorage)
} }
return NetworkError(reply->url(), error, reply->errorString()); return NetworkError(reply->url(), error, reply->errorString());
}; };
MessageManager::writeDisrupting(QString("Axivion: %1").arg(getError().message()));
MessageManager::writeFlashing(QStringLiteral("Axivion: %1").arg(getError().message()));
return DoneResult::Error; return DoneResult::Error;
}; };
const auto onDeserializeSetup = [storage](Async<DtoType> &task) { const auto onDeserializeSetup = [storage](Async<expected_str<DtoType>> &task) {
const auto deserialize = [](QPromise<DtoType> &promise, const QByteArray &input) { const auto deserialize = [](QPromise<expected_str<DtoType>> &promise, const QByteArray &input) {
promise.addResult(DtoType::deserialize(input)); promise.addResult(DtoType::deserializeExpected(input));
}; };
task.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); task.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
task.setConcurrentCallData(deserialize, *storage); task.setConcurrentCallData(deserialize, *storage);
}; };
const auto onDeserializeDone = [dtoStorage](const Async<DtoType> &task, DoneWith doneWith) { const auto onDeserializeDone = [dtoStorage](const Async<expected_str<DtoType>> &task,
if (doneWith == DoneWith::Success) DoneWith doneWith) {
dtoStorage->dtoData = task.future().result(); 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, storage,
NetworkQueryTask(onNetworkQuerySetup, onNetworkQueryDone), 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> template<typename DtoType>
static Group fetchDataRecipe(const QUrl &url, const std::function<void(const DtoType &)> &handler) static Group fetchDataRecipe(const QUrl &url, const std::function<void(const DtoType &)> &handler)
{ {
const Storage<GetDtoStorage<DtoType>> dtoStorage; const Storage<GetDtoStorage<DtoType>> dtoStorage;
const auto onCredentialSetup = [dtoStorage, url] { const auto onDtoSetup = [dtoStorage, url] {
dtoStorage->credential = QByteArrayLiteral("AxToken ") + settings().server.token.toUtf8(); if (!dd->m_apiToken)
dtoStorage->url = url; return SetupResult::StopWithError;
};
dtoStorage->credential = "AxToken " + *dd->m_apiToken;
dtoStorage->url = url;
return SetupResult::Continue;
};
const auto onDtoDone = [dtoStorage, handler] { const auto onDtoDone = [dtoStorage, handler] {
if (dtoStorage->dtoData) if (dtoStorage->dtoData)
handler(*dtoStorage->dtoData); handler(*dtoStorage->dtoData);
}; };
const Group recipe { const Group recipe {
dtoStorage, authorizationRecipe(),
Sync(onCredentialSetup),
Group { Group {
getDtoRecipe(dtoStorage), dtoStorage,
onGroupSetup(onDtoSetup),
dtoRecipe(dtoStorage),
onGroupDone(onDtoDone) onGroupDone(onDtoDone)
} }
}; };
return recipe; 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) Group dashboardInfoRecipe(const DashboardInfoHandler &handler)
{ {
const auto onSetup = [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. handler(make_unexpected(QString("Error"))); // TODO: Collect error message in the storage.
}; };
const QUrl url(settings().server.dashboard); const auto resultHandler = [handler](const Dto::DashboardInfoDto &data) {
dd->m_dashboardInfo = toDashboardInfo(settings().server.dashboard, data);
const auto resultHandler = [handler, url](const Dto::DashboardInfoDto &data) {
dd->m_dashboardInfo = toDashboardInfo(url, data);
if (handler) if (handler)
handler(*dd->m_dashboardInfo); handler(*dd->m_dashboardInfo);
}; };
const Group root { const Group root {
onGroupSetup(onSetup), // Stops if cache exists. onGroupSetup(onSetup), // Stops if cache exists.
fetchDataRecipe<Dto::DashboardInfoDto>(url, resultHandler), fetchDataRecipe<Dto::DashboardInfoDto>(settings().server.dashboard, resultHandler),
onGroupDone(onDone, CallDoneIf::Error) onGroupDone(onDone, CallDoneIf::Error)
}; };
return root; return root;
@@ -575,7 +696,6 @@ Group issueTableRecipe(const IssueListSearch &search, const IssueTableHandler &h
const QUrl url = urlForProject(dd->m_currentProjectInfo.value().name + '/') const QUrl url = urlForProject(dd->m_currentProjectInfo.value().name + '/')
.resolved(QString("issues" + query)); .resolved(QString("issues" + query));
return fetchDataRecipe<Dto::IssueTableDto>(url, handler); 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? 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 + '/') const QUrl url = urlForProject(dd->m_currentProjectInfo.value().name + '/')
.resolved(QString("issues/")) .resolved(QString("issues/"))
.resolved(QString(issueId + '/')) .resolved(QString(issueId + '/'))
@@ -614,21 +730,27 @@ void AxivionPluginPrivate::fetchProjectInfo(const QString &projectName)
clearAllMarks(); clearAllMarks();
if (projectName.isEmpty()) { if (projectName.isEmpty()) {
m_currentProjectInfo = {}; m_currentProjectInfo = {};
m_axivionOutputPane.updateDashboard(); updateDashboard();
return; return;
} }
const auto onTaskTreeSetup = [this, projectName](TaskTree &taskTree) { 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; return SetupResult::StopWithError;
}
const auto it = m_dashboardInfo->projectUrls.constFind(projectName); 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; return SetupResult::StopWithError;
}
const auto handler = [this](const Dto::ProjectInfoDto &data) { const auto handler = [this](const Dto::ProjectInfoDto &data) {
m_currentProjectInfo = data; m_currentProjectInfo = data;
m_axivionOutputPane.updateDashboard(); updateDashboard();
handleOpenedDocs(); handleOpenedDocs();
}; };
@@ -661,12 +783,19 @@ void AxivionPluginPrivate::fetchIssueInfo(const QString &id)
const int idx = htmlText.indexOf("<div class=\"ax-issuedetails-table-container\">"); const int idx = htmlText.indexOf("<div class=\"ax-issuedetails-table-container\">");
if (idx >= 0) if (idx >= 0)
fixedHtml = "<html><body>" + htmlText.mid(idx); 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)); m_issueInfoRunner.start(issueHtmlRecipe(id, ruleHandler));
} }
void AxivionPluginPrivate::setIssueDetails(const QString &issueDetailsHtml)
{
emit issueDetailsChanged(issueDetailsHtml);
}
void AxivionPluginPrivate::handleOpenedDocs() void AxivionPluginPrivate::handleOpenedDocs()
{ {
const QList<IDocument *> openDocuments = DocumentModel::openedDocuments(); const QList<IDocument *> openDocuments = DocumentModel::openedDocuments();
@@ -687,7 +816,8 @@ void AxivionPluginPrivate::onDocumentOpened(IDocument *doc)
return; return;
const FilePath filePath = doc->filePath().relativeChildPath(m_project->projectDirectory()); 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) { const auto handler = [this](const Dto::FileViewDto &data) {
if (data.lineMarkers.empty()) if (data.lineMarkers.empty())
@@ -718,7 +848,7 @@ void AxivionPluginPrivate::onDocumentClosed(IDocument *doc)
const TextMarks &marks = document->marks(); const TextMarks &marks = document->marks();
for (TextMark *mark : marks) { for (TextMark *mark : marks) {
if (mark->category().id == AxivionTextMarkId) if (mark->category().id == s_axivionTextMarkId)
delete mark; delete mark;
} }
} }
@@ -733,15 +863,76 @@ void AxivionPluginPrivate::handleIssuesForFile(const Dto::FileViewDto &fileView)
return; return;
const FilePath filePath = project->projectDirectory().pathAppended(fileView.fileName); 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)) { for (const Dto::LineMarkerDto &marker : std::as_const(fileView.lineMarkers)) {
// FIXME the line location can be wrong (even the whole issue could be wrong) // 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 // 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 // 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 class AxivionPlugin final : public ExtensionSystem::IPlugin
{ {
Q_OBJECT Q_OBJECT
@@ -756,9 +947,12 @@ class AxivionPlugin final : public ExtensionSystem::IPlugin
void initialize() final void initialize() final
{ {
setupAxivionOutputPane(this);
dd = new AxivionPluginPrivate; dd = new AxivionPluginPrivate;
AxivionProjectSettings::setupProjectPanel(); AxivionProjectSettings::setupProjectPanel();
setupAxivionIssueWidgetFactory();
connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
dd, &AxivionPluginPrivate::onStartupProjectChanged); 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 } // Axivion::Internal
#include "axivionplugin.moc" #include "axivionplugin.moc"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,7 @@
#include "cmakeprojectconstants.h" #include "cmakeprojectconstants.h"
#include "cmakeprojectimporter.h" #include "cmakeprojectimporter.h"
#include "cmakeprojectmanagertr.h" #include "cmakeprojectmanagertr.h"
#include "presetsmacros.h"
#include <coreplugin/icontext.h> #include <coreplugin/icontext.h>
#include <projectexplorer/buildconfiguration.h> #include <projectexplorer/buildconfiguration.h>
@@ -301,6 +302,18 @@ void CMakeProject::readPresets()
m_presetsData = combinePresets(cmakePresetsData, cmakeUserPresetsData); m_presetsData = combinePresets(cmakePresetsData, cmakeUserPresetsData);
setupBuildPresets(m_presetsData); 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) bool CMakeProject::setupTarget(Target *t)

View File

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

View File

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

View File

@@ -93,16 +93,16 @@ CMakeSpecificSettings::CMakeSpecificSettings()
"UseJunctionsForSourceAndBuildDirectories"); "UseJunctionsForSourceAndBuildDirectories");
useJunctionsForSourceAndBuildDirectories.setDefaultValue(false); useJunctionsForSourceAndBuildDirectories.setDefaultValue(false);
useJunctionsForSourceAndBuildDirectories.setLabelText(::CMakeProjectManager::Tr::tr( 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.setVisible(Utils::HostOsInfo().isWindowsHost());
useJunctionsForSourceAndBuildDirectories.setToolTip(::CMakeProjectManager::Tr::tr( 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>" "issues with long paths on Windows.<br><br>"
"They are stored under <tt>C:\\ProgramData\\QtCreator\\Links</tt> (overridable via " "Junctions are stored under <tt>C:\\ProgramData\\QtCreator\\Links</tt> (overridable via "
"<tt>QTC_CMAKE_JUNCTIONS_DIR</tt> environment variable).<br><br>" "the <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 " "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>" "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(); readSettings();
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -33,6 +33,8 @@
#include <utils/infobar.h> #include <utils/infobar.h>
#include <utils/macroexpander.h> #include <utils/macroexpander.h>
#include <utils/mimeutils.h> #include <utils/mimeutils.h>
#include <utils/networkaccessmanager.h>
#include <utils/passworddialog.h>
#include <utils/pathchooser.h> #include <utils/pathchooser.h>
#include <utils/savefile.h> #include <utils/savefile.h>
#include <utils/store.h> #include <utils/store.h>
@@ -41,6 +43,7 @@
#include <utils/theme/theme.h> #include <utils/theme/theme.h>
#include <utils/theme/theme_p.h> #include <utils/theme/theme_p.h>
#include <QAuthenticator>
#include <QDateTime> #include <QDateTime>
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
@@ -136,6 +139,30 @@ void CorePlugin::loadMimeFromPlugin(const ExtensionSystem::PluginSpec *plugin)
Utils::addMimeTypes(plugin->name() + ".mimetypes", mimetypeString.trimmed().toUtf8()); 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) bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage)
{ {
// register all mime types from all plugins // register all mime types from all plugins
@@ -145,6 +172,8 @@ bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage)
loadMimeFromPlugin(plugin); loadMimeFromPlugin(plugin);
} }
initProxyAuthDialog();
if (ThemeEntry::availableThemes().isEmpty()) { if (ThemeEntry::availableThemes().isEmpty()) {
*errorMessage = Tr::tr("No themes found in installation."); *errorMessage = Tr::tr("No themes found in installation.");
return false; return false;
@@ -238,9 +267,9 @@ bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage)
[] { return QUuid::createUuid().toString(); }); [] { return QUuid::createUuid().toString(); });
expander->registerPrefix("#:", Tr::tr("A comment."), [](const QString &) { return QString(); }); expander->registerPrefix("#:", Tr::tr("A comment."), [](const QString &) { return QString(); });
expander->registerPrefix("Asciify:", Tr::tr("Convert string into pure ascii."), expander->registerPrefix("Asciify:",
[expander] (const QString &s) { Tr::tr("Convert string to pure ASCII."),
return asciify(expander->expand(s)); }); [expander](const QString &s) { return asciify(expander->expand(s)); });
Utils::PathChooser::setAboutToShowContextMenuHandler(&CorePlugin::addToPathChooserContextMenu); Utils::PathChooser::setAboutToShowContextMenuHandler(&CorePlugin::addToPathChooserContextMenu);

View File

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

View File

@@ -47,9 +47,11 @@ EditorWindow::EditorWindow(QWidget *parent) :
static int windowId = 0; static int windowId = 0;
const Utils::Id windowContext
= Utils::Id("EditorManager.ExternalWindow.").withSuffix(++windowId);
ICore::registerWindow(this, ICore::registerWindow(this,
Context(Utils::Id("EditorManager.ExternalWindow.").withSuffix(++windowId), Context(windowContext, Constants::C_EDITORMANAGER),
Constants::C_EDITORMANAGER)); Context(windowContext));
connect(m_area, &EditorArea::windowTitleNeedsUpdate, connect(m_area, &EditorArea::windowTitleNeedsUpdate,
this, &EditorWindow::updateWindowTitle); 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. 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. 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. 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 Registers a \a window with the specified \a context. Registered windows are
shown in the \uicontrol Window menu and get registered for the various shown in the \uicontrol Window menu and get registered for the various
window related actions, like the minimize, zoom, fullscreen and close 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 Whenever the application focus is in \a window, its \a context is made
active. 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.addToContainer(Constants::M_VIEW, Constants::G_VIEW_VIEWS);
toggleMenubarAction.addOnToggled(this, [](bool visible) { toggleMenubarAction.addOnToggled(this, [](bool visible) {
if (!visible) { if (!visible) {
const QString keys = ActionManager::command(Constants::TOGGLE_MENUBAR) auto keySequenceAndText = [](const Utils::Id &actionName) {
->keySequence().toString(QKeySequence::NativeText); const auto command = ActionManager::command(actionName);
CheckableMessageBox::information(Core::ICore::dialogParent(),
Tr::tr("Hide Menu Bar"), const QString keySequence = command->keySequence().toString(
Tr::tr("This will hide the menu bar completely. " QKeySequence::NativeText);
"You can show it again by typing %1.") const QString text = command->action()->text();
.arg(keys),
Key("ToogleMenuBarHint")); 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); globalMenuBar()->setVisible(visible);
}); });

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