Merge remote-tracking branch 'origin/13.0'
Change-Id: If752e3383b35873b696e8beca27d8838a4096c8a
@@ -24,34 +24,34 @@ function(create_python_xy PythonExe PythonZipFilePath)
|
||||
cgi.py nntplib.py tarfile.py
|
||||
cgitb.py nturl2path.py telnetlib.py
|
||||
chunk.py numbers.py tempfile.py
|
||||
cmd.py optparse.py textwrap.py
|
||||
code.py pathlib.py this.py
|
||||
codeop.py pdb.py timeit.py
|
||||
colorsys.py pickle.py trace.py
|
||||
compileall.py pickletools.py tracemalloc.py
|
||||
configparser.py pipes.py tty.py
|
||||
contextvars.py plistlib.py turtle.py
|
||||
cProfile.py poplib.py typing.py
|
||||
crypt.py pprint.py uu.py
|
||||
csv.py profile.py uuid.py
|
||||
dataclasses.py pstats.py wave.py
|
||||
datetime.py pty.py webbrowser.py
|
||||
decimal.py pyclbr.py xdrlib.py
|
||||
difflib.py py_compile.py zipapp.py
|
||||
doctest.py queue.py zipfile.py
|
||||
dummy_threading.py quopri.py zipimport.py
|
||||
filecmp.py random.py _compat_pickle.py
|
||||
fileinput.py rlcompleter.py _compression.py
|
||||
formatter.py runpy.py _dummy_thread.py
|
||||
fractions.py sched.py _markupbase.py
|
||||
ftplib.py secrets.py _osx_support.py
|
||||
getopt.py selectors.py _pydecimal.py
|
||||
getpass.py shelve.py _pyio.py
|
||||
gettext.py shlex.py _py_abc.py
|
||||
gzip.py shutil.py _strptime.py
|
||||
hashlib.py smtpd.py _threading_local.py
|
||||
hmac.py smtplib.py __future__.py
|
||||
imaplib.py sndhdr.py __phello__.foo.py
|
||||
cmd.py optparse.py this.py
|
||||
code.py pathlib.py timeit.py
|
||||
codeop.py pdb.py trace.py
|
||||
colorsys.py pickle.py tracemalloc.py
|
||||
compileall.py pickletools.py tty.py
|
||||
configparser.py pipes.py turtle.py
|
||||
contextvars.py plistlib.py typing.py
|
||||
cProfile.py poplib.py uu.py
|
||||
crypt.py pprint.py uuid.py
|
||||
csv.py profile.py wave.py
|
||||
dataclasses.py pstats.py webbrowser.py
|
||||
datetime.py pty.py xdrlib.py
|
||||
decimal.py pyclbr.py zipapp.py
|
||||
difflib.py py_compile.py zipfile.py
|
||||
doctest.py queue.py zipimport.py
|
||||
dummy_threading.py quopri.py _compat_pickle.py
|
||||
filecmp.py random.py _compression.py
|
||||
fileinput.py rlcompleter.py _dummy_thread.py
|
||||
formatter.py runpy.py _markupbase.py
|
||||
fractions.py sched.py _osx_support.py
|
||||
ftplib.py secrets.py _pydecimal.py
|
||||
getopt.py selectors.py _pyio.py
|
||||
getpass.py shelve.py _py_abc.py
|
||||
gettext.py shlex.py _strptime.py
|
||||
gzip.py shutil.py _threading_local.py
|
||||
hashlib.py smtpd.py __future__.py
|
||||
hmac.py smtplib.py __phello__.foo.py
|
||||
imaplib.py sndhdr.py
|
||||
)
|
||||
list(FIND python_lib_files "${python_lib_dir}/${not_needed}" found_not_needed)
|
||||
if (NOT found_not_needed STREQUAL "-1")
|
||||
|
||||
5
dist/changelog/changes-13.0.0.md
vendored
@@ -169,12 +169,13 @@ Projects
|
||||
|
||||
* Added `Generate Kit` to the Python interpreter preferences for generating a
|
||||
Python kit with this interpreter
|
||||
([Documentation](https://doc-snapshots.qt.io/qtcreator-13.0/creator-python-development.html#create-kits-for-python-interpreters))
|
||||
* Added the target setup page when loading unconfigured Python projects
|
||||
* Added the `Kit Selection` page for creating and opening Python projects
|
||||
* Added a `requirements.txt` file to the application wizard
|
||||
* Fixed that the same Python interpreter could be auto-detected multiple times
|
||||
under different names
|
||||
|
||||
([Documentation](https://doc-snapshots.qt.io/qtcreator-13.0/creator-python-development.html))
|
||||
|
||||
Debugging
|
||||
---------
|
||||
|
||||
|
||||
BIN
doc/qtcreator/images/heartgame-start.webp
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
doc/qtcreator/images/qt-app-dev-flow.webp
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
doc/qtcreator/images/qt-tools.webp
Normal file
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 8.2 KiB |
BIN
doc/qtcreator/images/qtcreator-python-run-settings.webp
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
@@ -275,6 +275,7 @@
|
||||
|
||||
The build errors and warnings are parsed and displayed in \l Issues.
|
||||
|
||||
\sa {Activate kits for a project}, {Configure projects for building},
|
||||
{Configure projects for running}, {Open projects}, {CMake}
|
||||
\sa {Activate kits for a project}, {Add custom output parsers},
|
||||
{Configure projects for building}, {Configure projects for running},
|
||||
{Open projects}, {CMake}
|
||||
*/
|
||||
|
||||
@@ -14,18 +14,6 @@
|
||||
|
||||
\title Debugging
|
||||
|
||||
A debugger lets you see what happens \e inside an application while it runs
|
||||
or when it crashes. A debugger can do the following to help you find errors
|
||||
in the application:
|
||||
|
||||
\list
|
||||
\li Start the application with parameters that specify its behavior.
|
||||
\li Stop the application when conditions are met.
|
||||
\li Examine what happens when the application stops.
|
||||
\li Make changes in the application when you fix an error and continue
|
||||
to find the next one.
|
||||
\endlist
|
||||
|
||||
The \QC debugger plugin acts as an interface between the \QC
|
||||
core and external native debuggers that you can use to:
|
||||
|
||||
|
||||
@@ -8,20 +8,6 @@
|
||||
|
||||
\title Refactoring
|
||||
|
||||
\e {Code refactoring} is the process of improving and simplifying code
|
||||
without modifying the existing functionality of an application. You
|
||||
can easily find and rename symbols and apply predefined actions to
|
||||
refactor code.
|
||||
|
||||
Refactor code to:
|
||||
|
||||
\list
|
||||
\li Improve internal quality of your application
|
||||
\li Improve performance and extensibility
|
||||
\li Improve code readability and maintainability
|
||||
\li Simplify code structure
|
||||
\endlist
|
||||
|
||||
To quickly and conveniently apply actions to refactor your
|
||||
code, \l{Apply quick fixes}{select quick fixes in a context menu}.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
\title Edit Markdown files
|
||||
|
||||
Open \l{https://www.markdownguide.org/basic-syntax/}{Markdown} (.md) files
|
||||
or select \uicontrol File > \uicontrol {New File} > \uicontrol {General} >
|
||||
\uicontrol {Markdown File} to create a new file.
|
||||
Open \l{https://www.markdownguide.org/basic-syntax/}{Markdown} (.md) files,
|
||||
or go to \uicontrol File > \uicontrol {New File} and select
|
||||
\uicontrol {General} > \uicontrol {Markdown File} to create a new file.
|
||||
|
||||
\image qtcreator-markdown-editor.webp {Markdown file in Preview and Editor views}
|
||||
|
||||
@@ -26,4 +26,25 @@
|
||||
Use the buttons on the editor toolbar to format text as italic (i), bold (b),
|
||||
or inline code (`), and to create links to web sites
|
||||
(\inlineimage icons/linkicon.png).
|
||||
|
||||
\section1 Move to a line and column
|
||||
|
||||
The line and column indicator shows information about the current cursor
|
||||
position. Select it to activate the locator, and enter a line and column
|
||||
number to move there.
|
||||
|
||||
\section1 Follow links to web sites
|
||||
|
||||
To follow a link to a web site in the editor:
|
||||
|
||||
\list 1
|
||||
\li Place the cursor on the link.
|
||||
\li Then, do one of the following:
|
||||
\list
|
||||
\li Press \key {Ctrl+Click} (or \key {Cmd+Click} on \macos).
|
||||
\li Press \key F2.
|
||||
\li Go to \uicontrol {Follow Symbol Under Cursor} in the context
|
||||
menu.
|
||||
\endlist
|
||||
\endlist
|
||||
*/
|
||||
|
||||
@@ -25,11 +25,11 @@
|
||||
\li \inlineimage front-ui.png
|
||||
\li \inlineimage front-advanced.png
|
||||
\row
|
||||
\li \b {\l{IDE Overview}}
|
||||
\li \b {\l{Overview}}
|
||||
|
||||
If you have not used an integrated development environment (IDE)
|
||||
before, or want to know what kind of IDE \QC is, go to
|
||||
\l{IDE Overview}.
|
||||
\l{Overview}.
|
||||
\li \b {\l{User Interface}}
|
||||
|
||||
If you have not used \QC before, and want to become familiar
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
// **********************************************************************
|
||||
@@ -12,147 +12,243 @@
|
||||
\page creator-overview.html
|
||||
\nextpage creator-quick-tour.html
|
||||
|
||||
\title IDE Overview
|
||||
\title Overview
|
||||
|
||||
\QC is an integrated development environment (IDE) that has tools for
|
||||
designing and developing applications with the Qt application framework.
|
||||
With Qt you can develop applications and user interfaces once and deploy
|
||||
them to several desktop, embedded, and mobile operating systems or
|
||||
web browsers (experimental). \QC has the tools for accomplishing your tasks
|
||||
\QC is a cross-platform, complete integrated development environment
|
||||
(IDE) that you can use to create applications for desktop, embedded,
|
||||
and mobile operating systems, or web browsers.
|
||||
|
||||
With Qt, you can develop applications and user interfaces once and deploy
|
||||
them to many platforms. \QC has the tools for accomplishing your tasks
|
||||
throughout the whole application development life-cycle, from creating a
|
||||
project to deploying the application to the target platforms.
|
||||
project, designing a UI, and writing code to building applications and
|
||||
deploying them to the target platforms for running and debugging.
|
||||
|
||||
\table
|
||||
\row
|
||||
\li \inlineimage front-projects.png
|
||||
\li \inlineimage front-ui.png
|
||||
\li \inlineimage front-coding.png
|
||||
\row
|
||||
\li \b {Managing Projects}
|
||||
\image qt-app-dev-flow.webp {Application development life-cycle}
|
||||
\caption Application development life-cycle
|
||||
|
||||
To be able to build and run applications, \QC needs the same
|
||||
information as a compiler would need. It stores the information
|
||||
in the project settings.
|
||||
\section1 Projects
|
||||
|
||||
You can share projects with other designers and developers across
|
||||
different development platforms with a common tool for design,
|
||||
development, and debugging.
|
||||
First, you need a \e project. \QC relies on a separate build system, such as
|
||||
CMake, qmake, or Qbs for building the project. From the build system, \QC
|
||||
gets most of the information it needs to offer services for writing, editing,
|
||||
and navigating source code, as well as to deploy and run applications. It
|
||||
stores additional information in the project settings.
|
||||
|
||||
\list
|
||||
\li \l{Creating Projects}
|
||||
Share projects with other designers and developers across different
|
||||
development platforms with a common tool for design, development, and
|
||||
debugging.
|
||||
|
||||
To set up a project, you first have to decide what kind
|
||||
of an application you want to develop: do you want a user
|
||||
interface based on \l{User Interfaces}
|
||||
{Qt Quick or Qt Widgets}. Second, you have to choose the
|
||||
programming language to implement the application logic:
|
||||
C++ or Python.
|
||||
\li \l{Version Control Systems}
|
||||
\list
|
||||
\li \l{Creating Projects}
|
||||
|
||||
The recommended way to set up a project is to use a
|
||||
version control system. Store and edit only project
|
||||
source files and configuration files. Do not store
|
||||
generated files.
|
||||
\li \l{Configuring Projects}
|
||||
To set up a project, you first have to decide what kind
|
||||
of an application you want to develop: do you want a user
|
||||
interface based on \l{User Interfaces}
|
||||
{Qt Quick or Qt Widgets}. Second, you have to choose the
|
||||
programming language to implement the application logic:
|
||||
C++ or Python.
|
||||
\li \l{Version Control Systems}
|
||||
|
||||
Installation programs and project wizards create default
|
||||
configurations for \QC and your projects. You can change
|
||||
the configurations in the \uicontrol Projects mode.
|
||||
\endlist
|
||||
For more information, see \l{Manage Projects}
|
||||
{How To: Manage Projects}.
|
||||
\li \b {Designing User Interfaces}
|
||||
The recommended way to set up a project is to use a
|
||||
version control system. Store and edit only project
|
||||
source files and configuration files. Do not store
|
||||
generated files.
|
||||
\li \l{Configuring Projects}
|
||||
|
||||
To create intuitive, modern-looking, fluid user interfaces, you
|
||||
can use \l{Qt Quick} and \l{Qt Design Studio Manual}{\QDS}:
|
||||
Installation programs and project wizards create default
|
||||
configurations for \QC and your projects. Change the
|
||||
configurations in the \uicontrol Projects mode.
|
||||
\endlist
|
||||
|
||||
\list
|
||||
\li \l {\QMLD}
|
||||
For more information, see \l{Manage Projects}{How To: Manage Projects}.
|
||||
|
||||
Or, you can enable the \QMLD plugin to visually edit
|
||||
\l{UI Files}{UI files} (.ui.qml).
|
||||
\li \l {Converting UI Projects to Applications}
|
||||
\section1 User Interfaces
|
||||
|
||||
Qt Quick UI Prototype projects (.qmlproject) are useful
|
||||
for creating user interfaces. To use them for application
|
||||
development, you have to convert them to Qt Quick
|
||||
Application projects that have project configuration
|
||||
files (CMakeLists.txt or .pro), .cpp, and .qrc files.
|
||||
\li \l {UI Files}
|
||||
\image heartgame-start.webp {Heart Rate Game}
|
||||
|
||||
If you switch between \QC and \QDS or cooperate with
|
||||
designers on a project, you might encounter .ui.qml files.
|
||||
They are intended to be edited in \QDS only, so you need
|
||||
to be careful not to break the code. To visually edit the
|
||||
files in \QC, enable the \QMLD plugin.
|
||||
\li \l{Using QML Modules with Plugins}
|
||||
To create intuitive, modern-looking, fluid user interfaces, use \l{Qt Quick}
|
||||
and \l{Qt Design Studio Manual}{\QDS}:
|
||||
|
||||
You can load C++ plugins for QML to simulate data.
|
||||
\endlist
|
||||
\list
|
||||
\li \l {\QMLD}
|
||||
|
||||
If you need a traditional user interface that has a clear
|
||||
structure and enforces a platform look and feel, use
|
||||
\l{Qt Widgets} and the integrated \l{\QD}.
|
||||
Or, enable the \QMLD plugin to visually edit \l{UI Files}{UI files}
|
||||
(.ui.qml).
|
||||
\li \l {Converting UI Projects to Applications}
|
||||
|
||||
For more information, see
|
||||
\l{Design UIs}{How To: Design UIs}.
|
||||
\li \b {\l{Coding}}
|
||||
Qt Quick UI Prototype projects (.qmlproject) are useful
|
||||
for creating user interfaces. To use them for application
|
||||
development, you have to convert them to Qt Quick
|
||||
Application projects that have project configuration
|
||||
files (CMakeLists.txt or .pro), .cpp, and .qrc files.
|
||||
\li \l {UI Files}
|
||||
|
||||
As an IDE, \QC differs from a text editor in that it knows how
|
||||
to build and run applications. It understands the C++ and QML
|
||||
languages as code, not just as plain text. Therefore, it can
|
||||
offer useful features, such as semantic highlighting,
|
||||
checking code syntax, code completion, and refactoring actions.
|
||||
\QC supports some of these services also for other programming
|
||||
languages, such as Python, for which a \e {language server} is
|
||||
available that provides information about the code to IDEs.
|
||||
If you switch between \QC and \QDS or cooperate with
|
||||
designers on a project, you might encounter .ui.qml files.
|
||||
They are intended to be edited in \QDS only, so you need
|
||||
to be careful not to break the code. To visually edit the
|
||||
files in \QC, enable the \QMLD plugin.
|
||||
\li \l{Using QML Modules with Plugins}
|
||||
|
||||
For more information, see \l{Edit Code}{How To: Edit Code}.
|
||||
\row
|
||||
\li \inlineimage front-preview.png
|
||||
\li \inlineimage front-testing.png
|
||||
\li \inlineimage front-publishing.png
|
||||
\row
|
||||
\li \b {\l{Building and Running}}
|
||||
Load C++ plugins for QML to simulate data.
|
||||
\endlist
|
||||
|
||||
\QC integrates cross-platform systems for build
|
||||
automation: qmake, Qbs, CMake, and Autotools. In addition, you
|
||||
can import
|
||||
projects as \e {generic projects} and fully control the steps
|
||||
and commands used to build the project.
|
||||
If you need a traditional user interface that has a clear structure and
|
||||
enforces a platform look and feel, use \l{Qt Widgets} and the integrated
|
||||
\l{\QD}.
|
||||
|
||||
You can build applications for, deploy them to, and run them on
|
||||
the desktop environment or a \l{glossary-device}{device}.
|
||||
\l{glossary-buildandrun-kit}{Kits}, build, run, and deployment
|
||||
settings allow you to quickly switch between different setups and
|
||||
target platforms.
|
||||
For more information, see \l{Design UIs}{How To: Design UIs} and
|
||||
\l{UI Design}.
|
||||
|
||||
For more information, see \l{Build and Run}
|
||||
{How To: Build and Run}.
|
||||
\li \b {\l{Testing}}
|
||||
\section1 Code
|
||||
|
||||
\QC integrates several external native debuggers that you can use
|
||||
to inspect the state of your application while debugging.
|
||||
Writing, editing, and navigating in source code are core tasks in application
|
||||
development. Therefore, the code editor is one of the key components of \QC.
|
||||
Use the code editor in the \l {Edit Mode}{Edit mode}.
|
||||
|
||||
Devices have limited memory and CPU power, so you should use them
|
||||
carefully. \QC integrates code analysis tools for detecting
|
||||
memory leaks, profiling function execution, analyzing CPU use,
|
||||
and eliminating unnecessary complexity of code. Other tools
|
||||
provide code coverage and visualize trace events.
|
||||
As an IDE, \QC differs from a text editor in that it knows how to build and
|
||||
run applications. It understands the C++ and QML languages as code, not just
|
||||
as plain text. Therefore, it can offer useful features, such as semantic
|
||||
highlighting, checking code syntax, code completion, and refactoring actions.
|
||||
|
||||
\QC integrates several testing frameworks for unit testing
|
||||
applications and libraries. You can use \QC to create, build,
|
||||
and run autotests.
|
||||
\QC supports some of these services also for other programming languages,
|
||||
such as Python, for which a \e {language server} is available that provides
|
||||
information about the code to IDEs.
|
||||
|
||||
For more information, see \l{Testing}.
|
||||
\li \b {Publishing}
|
||||
\section2 Find
|
||||
|
||||
\QC enables you to create installation packages for mobile
|
||||
devices that you can publish to application stores
|
||||
and other channels. You must make sure that the package contents
|
||||
meet the requirements for publishing on the channel.
|
||||
Use the incremental and advanced search to search in currently open projects
|
||||
or files on the file system or use the locator to browse through projects,
|
||||
files, classes, functions, documentation, and file systems.
|
||||
|
||||
For more information, see \l{Publishing to Google Play}.
|
||||
\endtable
|
||||
\section2 Refactor
|
||||
|
||||
\e {Code refactoring} is the process of improving and simplifying code
|
||||
without modifying the existing functionality of an application. Find
|
||||
and rename symbols and apply predefined actions to refactor code.
|
||||
|
||||
Refactor code to:
|
||||
|
||||
\list
|
||||
\li Improve internal quality of your application
|
||||
\li Improve performance and extensibility
|
||||
\li Improve code readability and maintainability
|
||||
\li Simplify code structure
|
||||
\endlist
|
||||
|
||||
\section2 Configure the Editor
|
||||
|
||||
Configure the text editor to suit your specific needs. Change the fonts,
|
||||
colors, highlighting, and indentation.
|
||||
|
||||
If you are used to the Vim editor, run the main editor in the
|
||||
\l {FakeVim Modes and Commands}{FakeVim mode}.
|
||||
|
||||
For more information, see \l{Edit Code}{How To: Edit Code} and \l{Editors}.
|
||||
|
||||
\section1 Build, Deploy, and Run
|
||||
|
||||
Run and deploy Qt applications that you build for different target
|
||||
platforms or with different compilers, debuggers, or Qt versions.
|
||||
\l{glossary-buildandrun-kit}{Kits} define the tools, \l{glossary-device}
|
||||
{device} type and other settings to use when building and running your
|
||||
project.
|
||||
|
||||
\QC integrates cross-platform systems for build automation: CMake,
|
||||
qmake, Qbs, and Autotools. In addition, you can import projects as
|
||||
\e {generic projects} and fully control the steps and commands to
|
||||
build the project.
|
||||
|
||||
Build applications for, deploy them to, and run them on the desktop
|
||||
environment or a device. With kits, as well as build, run, and deployment
|
||||
configurations, you can quickly switch between different setups and
|
||||
target platforms.
|
||||
|
||||
For more information, see \l{Build and Run}{How To: Build and Run},
|
||||
\l{Build Systems}, \l{Build Configurations}, and \l{Run Configurations}.
|
||||
|
||||
\section2 On Devices
|
||||
|
||||
When you install tool chains for device types as part of a Qt distribution,
|
||||
the build and run configurations for the devices might be set up
|
||||
automatically. However, you might need to install and configure some
|
||||
additional software on the devices to be able to connect to them
|
||||
from the computer.
|
||||
|
||||
Deployment configurations handle the packaging and copying of the necessary
|
||||
files to a location you want to run the executable at, such as the file
|
||||
system of a device.
|
||||
|
||||
For more information, see \l{Connecting Devices} and \l{Deploying to Devices}.
|
||||
|
||||
\section2 Preview QML
|
||||
|
||||
Use the QML live preview to preview a QML file or an entire Qt Quick
|
||||
application on the desktop, as well as on Android and embedded Linux
|
||||
devices. The changes you make to the UI are instantly visible to you
|
||||
in the preview.
|
||||
|
||||
For more information, see \l{Validating with Target Hardware}.
|
||||
|
||||
\section1 Debug
|
||||
|
||||
A debugger lets you see what happens \e inside an application while it runs
|
||||
or when it crashes. A debugger can do the following to help you find errors
|
||||
in the application:
|
||||
|
||||
\list
|
||||
\li Start the application with parameters that specify its behavior.
|
||||
\li Stop the application when conditions are met.
|
||||
\li Examine what happens when the application stops.
|
||||
\li Make changes in the application when you fix an error and continue
|
||||
to find the next one.
|
||||
\endlist
|
||||
|
||||
\QC integrates several external native debuggers for inspecting the state of
|
||||
your application while debugging. The debugger plugin automatically selects
|
||||
a suitable native debugger for each kit from the ones it finds on the
|
||||
computer. Edit the kits to override this choice.
|
||||
|
||||
Connect devices to your computer to debug processes running on the devices.
|
||||
|
||||
For more information, see \l{Debugging}.
|
||||
|
||||
\section1 Analyze
|
||||
|
||||
Devices have limited memory and CPU power, so you should use them carefully.
|
||||
\QC integrates code analysis tools for detecting memory leaks, profiling
|
||||
function execution, analyzing CPU use, and eliminating unnecessary complexity
|
||||
of code. Other tools provide code coverage and visualize trace events.
|
||||
|
||||
Install and configure the tools on your system to use them from \QC.
|
||||
However, the QML Profiler is installed as part of \QC for profiling
|
||||
Qt Quick applications.
|
||||
|
||||
For more information, see \l{Analyzing Code}.
|
||||
|
||||
\section1 Autotest
|
||||
|
||||
Create, build and run Qt tests, Qt Quick tests, Google tests, and Boost tests
|
||||
to unit test applications and libraries.
|
||||
|
||||
Map AUTs (Application Under Test) to \QC and run Squish test suites
|
||||
and cases from it.
|
||||
|
||||
For more information, see \l{Running Autotests} and \l{Using Squish}.
|
||||
|
||||
\section1 Publish
|
||||
|
||||
Create installation packages for mobile devices that you publish to
|
||||
application stores and other channels. You must make sure that the
|
||||
package contents meet the requirements for publishing on the channel.
|
||||
|
||||
For more information, see \l{Publishing to Google Play}.
|
||||
|
||||
\section1 Qt Tools
|
||||
|
||||
\QC is one of many Qt tools for designing and developing applications.
|
||||
|
||||
\image qt-tools.webp {Tools for Qt application development}
|
||||
\caption Tools for Qt application development
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
\li \l{Debugging}
|
||||
|
||||
If you install \QC as part of \QSDK, the GNU Symbolic Debugger
|
||||
If you install \QC with \QOI, the GNU Symbolic Debugger
|
||||
is installed automatically and you should be ready to start
|
||||
debugging after you create a new project. However, you can
|
||||
change the setup to use debugging tools for Windows, for
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
// **********************************************************************
|
||||
@@ -20,6 +20,8 @@
|
||||
has the same XML structure as a \e {.user} file, but only has the
|
||||
settings to share.
|
||||
|
||||
\note Use \l{CMake Presets} to share CMake project settings.
|
||||
|
||||
\section1 Create a shared settings file
|
||||
|
||||
The easiest way to create a \e {.shared} file is to copy settings from the
|
||||
@@ -70,5 +72,5 @@
|
||||
a permanent sticky setting that was created just because you wanted to try
|
||||
something out.
|
||||
|
||||
\sa {Configuring Projects}
|
||||
\sa {Configuring Projects}, {CMake Presets}
|
||||
*/
|
||||
|
||||
@@ -19,9 +19,8 @@
|
||||
\list
|
||||
\li \l{Set up PySide6}
|
||||
\li \l{Create Qt for Python applications}
|
||||
\li \l{Select the Python interpreter}
|
||||
\li \l{Create a virtual environment}
|
||||
\li \l{Create kits for Python interpreters}
|
||||
\li \l{Select the Python version}
|
||||
\li \l{Create kits for Python}
|
||||
\li \l{Use Python interactive shell}
|
||||
\li \l{Configure Python language servers}
|
||||
\li \l{Run Python applications}
|
||||
@@ -48,9 +47,10 @@
|
||||
|
||||
\section1 Create Qt for Python applications
|
||||
|
||||
You can use wizards to create Qt for Python application projects. The wizards
|
||||
Use wizards to create Qt for Python application projects. The wizards
|
||||
generate a project file, \c {.pyproject}, that lists the files in the Python
|
||||
project. They also generate a \c {.py} file that has some boilerplate code.
|
||||
project. They also generate a \c {.py} file that has some boilerplate code
|
||||
and \c {reguirements.txt} that stores the PySide version of the generated code.
|
||||
In addition, the widget-based UI wizard creates a \c {.ui} file that has a
|
||||
\QD form, and the Qt Quick Application wizard creates a \c {.qml} file that
|
||||
imports Qt Quick controls.
|
||||
@@ -72,50 +72,31 @@
|
||||
use \c {.pyqtc} files, but we recommend that you choose \c{.pyproject} files
|
||||
for new projects.
|
||||
|
||||
\section1 Select the Python interpreter
|
||||
\section1 Select the Python version
|
||||
|
||||
You select the initial Python interpreter when you use the Qt for Python
|
||||
Application wizard templates to create Python projects.
|
||||
The \l{kits-tab}{kits} you select for the project in \uicontrol Projects >
|
||||
\uicontrol {Build & Run} set the Python version to use.
|
||||
|
||||
\image qtcreator-new-qt-for-python-app-widgets-project-details.webp {Define Project Details dialog}
|
||||
The \l {Edit Mode}{Edit mode} toolbar shows the current Python version.
|
||||
|
||||
You can see the current Python interpreter on the \uicontrol Edit mode
|
||||
toolbar.
|
||||
\image qtcreator-python-interpreter-edit-mode.webp {Python version on the Edit mode toolbar}
|
||||
|
||||
\image qtcreator-python-interpreter-edit-mode.webp {Python interpreter on the Edit mode toolbar}
|
||||
To use another Python version, activate another kit for the project.
|
||||
|
||||
You can change the interpreter to use for a particular project in
|
||||
\uicontrol Projects > \uicontrol Run > \uicontrol Interpreter.
|
||||
\section1 Create kits for Python
|
||||
|
||||
\image qtcreator-python-run-settings.png {Python run settings}
|
||||
|
||||
To see the available interpreters and choose another interpreter, select the
|
||||
current interpreter, and then select \uicontrol {Manage Python Interpreters}.
|
||||
Or, select \preferences > \uicontrol Python > \uicontrol Interpreters.
|
||||
\QC automatically adds all Python versions it can find to the list of
|
||||
Python versions in \preferences > \uicontrol Python > \uicontrol Interpreters.
|
||||
It generates kits for the global Python versions that are not inside a
|
||||
virtual environment.
|
||||
|
||||
\image qtcreator-python-interpreters.webp {Python Interpreters in Preferences}
|
||||
|
||||
You can add and remove interpreters and clean up references to interpreters
|
||||
that you uninstalled, but that still appear in the list.
|
||||
You can add and remove Python versions and clean up references to Python
|
||||
versions that you uninstalled, but that still appear in the list.
|
||||
|
||||
To use the selected Python interpreter by default, select
|
||||
\uicontrol {Make Default}.
|
||||
|
||||
\section1 Create a virtual environment
|
||||
|
||||
To use a clean \l{https://docs.python.org/3/library/venv.html}{Python}
|
||||
virtual environment (\c venv) that is independent of your global Python
|
||||
installation for a Qt for Python project, select the
|
||||
\uicontrol {Create new virtual environment} check box in the project wizard.
|
||||
Set the directory where to create the environment in
|
||||
\uicontrol {Path to virtual environment}.
|
||||
|
||||
\section1 Create kits for Python interpreters
|
||||
|
||||
\QC automatically adds all Python interpreters it can find to the list of
|
||||
interpreters in \preferences > \uicontrol Python > \uicontrol Interpreters.
|
||||
It generates \l{kits-tab}{kits} for the global Python interpreters that are
|
||||
not inside a virtual environment.
|
||||
To use the selected Python version when opening \c {.py} files that don't
|
||||
belong to a project, select \uicontrol {Make Default}.
|
||||
|
||||
To use a virtual environment as a kit, select it in \uicontrol Interpreters,
|
||||
and then select \uicontrol {Generate Kit}.
|
||||
@@ -133,5 +114,6 @@
|
||||
the file, select \uicontrol {REPL Import *}.
|
||||
|
||||
\sa {Creating a Qt for Python Application with Qt Widgets},
|
||||
{Creating a Qt for Python Application with Qt Quick}
|
||||
{Creating a Qt for Python Application with Qt Quick},
|
||||
{Activate kits for a project}
|
||||
*/
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
you select for a kit in \uicontrol Projects > \uicontrol {Build & Run} >
|
||||
\uicontrol Run > \uicontrol {Run Settings}.
|
||||
|
||||
\image qtcreator-python-run-settings.png {Python run settings}
|
||||
\image qtcreator-python-run-settings.webp {Python run settings}
|
||||
|
||||
The following table summarizes the settings for running Qt for Python
|
||||
applications.
|
||||
@@ -45,7 +45,7 @@
|
||||
\li Setting
|
||||
\li Value
|
||||
\row
|
||||
\li \uicontrol Interpreter
|
||||
\li \uicontrol Python
|
||||
\li Path to the Python executable.
|
||||
\row
|
||||
\li \uicontrol {Buffered output}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
@@ -28,11 +28,10 @@
|
||||
class:
|
||||
|
||||
\list 1
|
||||
\li Select \uicontrol File > \uicontrol {New Project} >
|
||||
\uicontrol {Application (Qt for Python)} > \uicontrol {Empty Window}
|
||||
> \uicontrol Choose.
|
||||
|
||||
The \uicontrol {Project Location} dialog opens.
|
||||
\li Go to \uicontrol File > \uicontrol {New Project}.
|
||||
\li Select \uicontrol {Application (Qt for Python)} >
|
||||
\uicontrol {Empty Window} > \uicontrol Choose to open the
|
||||
\uicontrol {Project Location} dialog.
|
||||
\image qtcreator-new-qt-for-python-app-widgets-project-location.webp {Project Location dialog}
|
||||
\li In \uicontrol {Name}, enter the project name. For example,
|
||||
\e {hello_world}.
|
||||
@@ -49,16 +48,14 @@
|
||||
\li In \uicontrol {Project file}, enter a name for the project file.
|
||||
\li Select \uicontrol{Next} or \uicontrol Continue to open the
|
||||
\uicontrol {Define Project Details} dialog.
|
||||
\image qtcreator-new-qt-for-python-app-widgets-project-details.webp {Define Project Details dialog}
|
||||
\image qtcreator-new-qt-for-python-app-project-details.webp {Define Project Details dialog}
|
||||
\li In \uicontrol {PySide version}, select the PySide version of the
|
||||
generated code.
|
||||
\li In \uicontrol {Interpreter}, select the Python interpreter to use for
|
||||
the project.
|
||||
\li Select the \uicontrol {Create new virtual environment} check box to
|
||||
use a clean \l{https://docs.python.org/3/library/venv.html}{Python}
|
||||
environment that is independent of your global Python installation.
|
||||
\li In \uicontrol {Path to virtual environment}, specify the directory
|
||||
where to create the environment.
|
||||
\li Select \uicontrol{Next} or \uicontrol Continue to open the
|
||||
\uicontrol {Kit Selection} dialog.
|
||||
\image qtcreator-new-project-qt-for-python-kit-selection.webp {Selecting a kit for a Python project}
|
||||
\li Select Qt for Python kits for building, deploying, and running the
|
||||
project.
|
||||
\li Select \uicontrol{Next} or \uicontrol Continue.
|
||||
\li Review the project settings, and select \uicontrol {Finish} (on
|
||||
Windows and Linux) or \uicontrol Done (on \macos) to create the
|
||||
@@ -71,6 +68,8 @@
|
||||
\li \c {hellow_world.pyproject}, which lists the files in the Python
|
||||
project.
|
||||
\li \c {mywidget.py}, which has some boilerplate code for a class.
|
||||
\li \c {reguirements.txt}, which stores the PySide version of the
|
||||
generated code.
|
||||
\endlist
|
||||
|
||||
\section1 Adding Qt Widgets Imports
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// Copyright (C) 2024 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
@@ -18,16 +18,19 @@
|
||||
|
||||
\image qtcreator-new-qt-for-python-app-qt-quick-empty-project-ready.webp {A small Qt Quick application}
|
||||
|
||||
For more examples of creating Qt for Python applications, see
|
||||
\l {https://doc.qt.io/qtforpython/tutorials/index.html}
|
||||
{Qt for Python Examples and Tutorials}.
|
||||
|
||||
\section1 Creating an Empty Project
|
||||
|
||||
To create a Qt for Python application that has a main QML file:
|
||||
|
||||
\list 1
|
||||
\li Select \uicontrol File > \uicontrol {New Project} >
|
||||
\uicontrol {Application (Qt for Python)} >
|
||||
\uicontrol {Qt Quick Application - Empty} > \uicontrol Choose.
|
||||
|
||||
The \uicontrol {Project Location} dialog opens.
|
||||
\li Go to \uicontrol File > \uicontrol {New Project}.
|
||||
\li Select \uicontrol {Application (Qt for Python)} >
|
||||
\uicontrol {Qt Quick Application - Empty} > \uicontrol Choose to
|
||||
open the \uicontrol {Project Location} dialog.
|
||||
\image qtcreator-new-qt-for-python-app-qt-quick-empty-project-location.webp {Project Location dialog}
|
||||
\li In \uicontrol {Name}, enter the project name. For example,
|
||||
\e {hello_world_quick}.
|
||||
@@ -35,17 +38,14 @@
|
||||
For example, \c {C:\Qt\examples}.
|
||||
\li Select \uicontrol{Next} (on Windows and Linux) or \uicontrol Continue
|
||||
(on \macos) to open the \uicontrol {Define Project Details} dialog.
|
||||
\image qtcreator-new-qt-for-python-app-qt-quick-empty-project-details.webp {Define Project Details dialog}
|
||||
\image qtcreator-new-qt-for-python-app-project-details.webp {Define Project Details dialog}
|
||||
\li In \uicontrol {PySide version}, select the PySide version of
|
||||
the generated code.
|
||||
\li In \uicontrol {Interpreter}, select the Python interpreter to use for
|
||||
the project.
|
||||
\li Select the \uicontrol {Create new virtual environment} check box to
|
||||
use a clean \l{https://docs.python.org/3/library/venv.html}{Python}
|
||||
environment that is independent of your global Python installation.
|
||||
\li In \uicontrol {Path to virtual environment}, specify the directory
|
||||
where to create the environment.
|
||||
\li Select \uicontrol{Next} or \uicontrol Continue.
|
||||
\li Select \uicontrol{Next} or \uicontrol Continue to open the
|
||||
\uicontrol {Kit Selection} dialog.
|
||||
\image qtcreator-new-project-qt-for-python-kit-selection.webp {Selecting a kit for a Python project}
|
||||
\li Select Qt for Python kits for building, deploying, and running the
|
||||
project.
|
||||
\li Review the project settings, and select \uicontrol {Finish} (on
|
||||
Windows and Linux) or \uicontrol Done (on \macos) to create the
|
||||
project.
|
||||
@@ -58,6 +58,8 @@
|
||||
project.
|
||||
\li \c {main.py}, which has some boilerplate code.
|
||||
\li \c {main.qml}, which imports Qt Quick controls.
|
||||
\li \c {reguirements.txt}, which stores the PySide version of the
|
||||
generated code.
|
||||
\endlist
|
||||
|
||||
\section1 Adding Qt Quick Imports
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
\list
|
||||
\li \l{Getting Started}
|
||||
\list
|
||||
\li \l{IDE Overview}
|
||||
\li \l{Overview}
|
||||
\list
|
||||
\li \l{Creating Projects}
|
||||
\li \l{Configuring Projects}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
\row
|
||||
\li \b {\l{Getting Started}}
|
||||
\list
|
||||
\li \l{IDE Overview}
|
||||
\li \l{Overview}
|
||||
\li \l{User Interface}
|
||||
\li \l{Configuring Qt Creator}
|
||||
\li \l{Building and Running an Example}
|
||||
|
||||
@@ -420,7 +420,7 @@ class DumperBase():
|
||||
|
||||
def charType(self):
|
||||
result = self.lookupType('char')
|
||||
self.intType = lambda: result
|
||||
self.charType = lambda: result
|
||||
return result
|
||||
|
||||
def ptrSize(self):
|
||||
@@ -635,7 +635,7 @@ class DumperBase():
|
||||
def putCharArrayValue(self, data, length, charSize,
|
||||
displayFormat=DisplayFormat.Automatic):
|
||||
shown = self.computeLimit(length, self.displayStringLimit)
|
||||
mem = self.readMemory(data, shown)
|
||||
mem = self.readMemory(data, shown * charSize)
|
||||
if charSize == 1:
|
||||
if displayFormat in (DisplayFormat.Latin1String, DisplayFormat.SeparateLatin1String):
|
||||
encodingType = 'latin1'
|
||||
|
||||
@@ -18,7 +18,7 @@ Rectangle {
|
||||
Text {
|
||||
id: text2
|
||||
color: "#ffffff"
|
||||
text: qsTrId("Restart")
|
||||
text: qsTr("Restart")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
font.pixelSize: 12
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
@@ -1717,12 +1717,8 @@ Se Google Test-dokumentation for yderligere information om GTest-filtre.</transl
|
||||
<translation>Perf</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.
|
||||
|
||||
Warning: Plain text misses some information, such as duration.</source>
|
||||
<translation>XML-output anbefales, fordi det forhindre parsing-problemer, mens ren tekst er lettere at læse for mennesker.
|
||||
|
||||
Advarsel: Ren tekst mangle nogle informationer, såsom varighed.</translation>
|
||||
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.<p>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.<p>Advarsel: Ren tekst mangle nogle informationer, såsom varighed.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select Run Configuration</source>
|
||||
@@ -18346,8 +18342,8 @@ Id'er skal begynde med et lille bogstav.</translation>
|
||||
<translation>Simulator start</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot start simulator (%1, %2) in current state: %3</source>
|
||||
<translation>Kan ikke starte simulator (%1, %2) i aktuelle tilstand: %3</translation>
|
||||
<source>Cannot start simulator (%1, %2) in current state: %3.</source>
|
||||
<translation>Kan ikke starte simulator (%1, %2) i aktuelle tilstand: %3.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>simulator start</source>
|
||||
|
||||
@@ -12413,10 +12413,8 @@ Weitere Informationen über GTest-Filter finden Sie in der Dokumenation von Goog
|
||||
<translation>Auf abgeleitete Qt Quick-Tests überprüfen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Search for Qt Quick tests that are derived from TestCase.
|
||||
Warning: Enabling this feature significantly increases scan time.</source>
|
||||
<translation>Sucht nach Qt Quick-Tests, die von TestCase abgeleitet sind.
|
||||
Achtung: Dies erhöht die zum Durchsuchen benötigte Zeit erheblich.</translation>
|
||||
<source>Search for Qt Quick tests that are derived from TestCase.<p>Warning: Enabling this feature significantly increases scan time.</source>
|
||||
<translation>Sucht nach Qt Quick-Tests, die von TestCase abgeleitet sind.<p>Achtung: Dies erhöht die zum Durchsuchen benötigte Zeit erheblich.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Benchmark Metrics</source>
|
||||
@@ -12463,12 +12461,8 @@ Achtung: Dies erhöht die zum Durchsuchen benötigte Zeit erheblich.</translatio
|
||||
<translation>Perf</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.
|
||||
|
||||
Warning: Plain text misses some information, such as duration.</source>
|
||||
<translation>Die XML-Ausgabe ist empfehlenswert, weil sie Probleme beim Einlesen vermeidet. Reiner Text ist hingegen besser lesbar für Menschen.
|
||||
|
||||
Warnung: Reinem Text fehlen manche Informationen, etwa die Dauer.</translation>
|
||||
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.<p>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.<p>Warnung: Reinem Text fehlen manche Informationen, etwa die Dauer.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select Run Configuration</source>
|
||||
@@ -33897,7 +33891,7 @@ Möchten Sie sie überschreiben?</translation>
|
||||
<translation>Simulator starten</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot start simulator (%1, %2) in current state: %3</source>
|
||||
<source>Cannot start simulator (%1, %2) in current state: %3.</source>
|
||||
<translation>Der Simulator (%1, %2) kann im momentanen Zustand (%3) nicht gestartet werden.</translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
@@ -798,7 +798,7 @@ Une valeur positive augmente la réverbération pour les hautes fréquences et
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Axivion</name>
|
||||
<name>QtC::Axivion</name>
|
||||
<message>
|
||||
<source>Project:</source>
|
||||
<translation>Projet :</translation>
|
||||
@@ -12063,12 +12063,8 @@ Voir la documentation de Google Test pour plus d'informations sur les filtr
|
||||
<translation>Utilise la sortie XML</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.
|
||||
|
||||
Warning: Plain text misses some information, such as duration.</source>
|
||||
<translation>La sortie XML est recommandée : elle évite des problèmes d'analyse, alors que le texte brut est plus lisible pour un humain.
|
||||
|
||||
Avertissement : le texte brut ne contient pas toutes les informations, telle que la durée.</translation>
|
||||
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.<p>Warning: Plain text misses some information, such as duration.</source>
|
||||
<translation>La sortie XML est recommandée : elle évite des problèmes d'analyse, alors que le texte brut est plus lisible pour un humain.<p>Avertissement : le texte brut ne contient pas toutes les informations, telle que la durée.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Verbose benchmarks</source>
|
||||
@@ -12099,10 +12095,8 @@ Avertissement : le texte brut ne contient pas toutes les informations, tell
|
||||
<translation>Vérifier la présence de tests dérivés de Qt Quick</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Search for Qt Quick tests that are derived from TestCase.
|
||||
Warning: Enabling this feature significantly increases scan time.</source>
|
||||
<translation>Recherche des tests Qt Quick dérivé de TestCase.
|
||||
Avertissement : l'activation de cette fonctionnalité augmente significativement le temps de recherche.</translation>
|
||||
<source>Search for Qt Quick tests that are derived from TestCase.<p>Warning: Enabling this feature significantly increases scan time.</source>
|
||||
<translation>Recherche des tests Qt Quick dérivé de TestCase.<p>Avertissement : l'activation de cette fonctionnalité augmente significativement le temps de recherche.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Benchmark Metrics</source>
|
||||
@@ -33467,8 +33461,8 @@ Souhaitez-vous les écraser ?</translation>
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot start simulator (%1, %2) in current state: %3</source>
|
||||
<translation>Impossible de démarrer le simulateur (%1, %2) dans l'état actuel : %3</translation>
|
||||
<source>Cannot start simulator (%1, %2) in current state: %3.</source>
|
||||
<translation>Impossible de démarrer le simulateur (%1, %2) dans l'état actuel : %3.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>simulator start</source>
|
||||
|
||||
@@ -1135,12 +1135,8 @@ Dodatne dokumente o GTest filtrima potraži u Google Test dokumentaciji.</transl
|
||||
<translation>Deaktiviraj rukovatelja urušivanja tijekom uklanjanja grešaka</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.
|
||||
|
||||
Warning: Plain text misses some information, such as duration.</source>
|
||||
<translation>Preporuča se XML izlaz, jer izbjegava probleme s raščlanjivanjem, dok je običan tekst čitljiviji za čitanje.
|
||||
|
||||
Upozorenje: Običan tekst propušta neke informacije, kao što je trajanje.</translation>
|
||||
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.<p>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.<p>Upozorenje: Običan tekst propušta neke informacije, kao što je trajanje.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use XML output</source>
|
||||
@@ -4546,8 +4542,8 @@ Dodaj, izmijeni i ukloni filtre dokumenata koji određuju skup dokumentacije pri
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot start simulator (%1, %2) in current state: %3</source>
|
||||
<translation>Nije moguće pokrenuti simulatora (%1, %2) u trenutačnom stanju: %3</translation>
|
||||
<source>Cannot start simulator (%1, %2) in current state: %3.</source>
|
||||
<translation>Nije moguće pokrenuti simulatora (%1, %2) u trenutačnom stanju: %3.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>simulator start</source>
|
||||
|
||||
@@ -12692,8 +12692,7 @@ See also Google Test settings.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Search for Qt Quick tests that are derived from TestCase.
|
||||
Warning: Enabling this feature significantly increases scan time.</source>
|
||||
<source>Search for Qt Quick tests that are derived from TestCase.<p>Warning: Enabling this feature significantly increases scan time.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -12713,9 +12712,7 @@ Warning: Enabling this feature significantly increases scan time.</source>
|
||||
<translation>Używaj XML na wyjściu</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.
|
||||
|
||||
Warning: Plain text misses some information, such as duration.</source>
|
||||
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.<p>Warning: Plain text misses some information, such as duration.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -33930,8 +33927,8 @@ Czy nadpisać je?</translation>
|
||||
<translation>Uruchomienie symulatora</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot start simulator (%1, %2) in current state: %3</source>
|
||||
<translation>Nie można uruchomić symulatora (%1, %2) w bieżącym stanie: %3</translation>
|
||||
<source>Cannot start simulator (%1, %2) in current state: %3.</source>
|
||||
<translation>Nie można uruchomić symulatora (%1, %2) w bieżącym stanie: %3.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>simulator start</source>
|
||||
|
||||
@@ -2745,12 +2745,8 @@ See Google Test documentation for further information on GTest filters.</source>
|
||||
<translation>Логировать сигналы и слоты</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.
|
||||
|
||||
Warning: Plain text misses some information, such as duration.</source>
|
||||
<translation>Рекомендуется вывод в формате XML, так как исключает проблемы при разборе. Простой же текст более удобен для чтения человеком.
|
||||
|
||||
Предупреждение: простой текст не содержит некоторую информацию, например, длительность.</translation>
|
||||
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.<p>Warning: Plain text misses some information, such as duration.</source>
|
||||
<translation>Рекомендуется вывод в формате XML, так как исключает проблемы при разборе. Простой же текст более удобен для чтения человеком.<p>Предупреждение: простой текст не содержит некоторую информацию, например, длительность.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select Run Configuration</source>
|
||||
@@ -22984,8 +22980,8 @@ Ids must begin with a lowercase letter.</source>
|
||||
<translation>Запустить эмулятор</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot start simulator (%1, %2) in current state: %3</source>
|
||||
<translation>Невозможно запустить эмулятор (%1, %2) в текущем состоянии: %3</translation>
|
||||
<source>Cannot start simulator (%1, %2) in current state: %3.</source>
|
||||
<translation>Невозможно запустить эмулятор (%1, %2) в текущем состоянии: %3.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>simulator start</source>
|
||||
|
||||
@@ -2950,12 +2950,8 @@ See Google Test documentation for further information on GTest filters.</source>
|
||||
<translation>使用 XML 输出</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.
|
||||
|
||||
Warning: Plain text misses some information, such as duration.</source>
|
||||
<translation>建议使用 XML 输出,它避免了一些解析问题,虽然纯文本可读性更好。
|
||||
|
||||
警告:纯文本丢失了一些信息,比如持续时间。</translation>
|
||||
<source>XML output is recommended, because it avoids parsing issues, while plain text is more human readable.<p>Warning: Plain text misses some information, such as duration.</source>
|
||||
<translation>建议使用 XML 输出,它避免了一些解析问题,虽然纯文本可读性更好。<p>警告:纯文本丢失了一些信息,比如持续时间。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Verbose benchmarks</source>
|
||||
@@ -23217,7 +23213,7 @@ Id必须以小写字母开头。</translation>
|
||||
</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot start simulator (%1, %2) in current state: %3</source>
|
||||
<source>Cannot start simulator (%1, %2) in current state: %3.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
1
src/libs/3rdparty/qtkeychain/qtkeychain.qbs
vendored
@@ -72,6 +72,7 @@ QtcLibrary {
|
||||
Group {
|
||||
name: "qtkeychain dbus support"
|
||||
cpp.defines: outer.concat(["KEYCHAIN_DBUS=1"])
|
||||
cpp.cxxFlags: outer.concat("-Wno-cast-function-type")
|
||||
files: [
|
||||
"gnomekeyring.cpp",
|
||||
"gnomekeyring_p.h",
|
||||
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
return state.d.data();
|
||||
}
|
||||
|
||||
int size() const
|
||||
std::size_t size() const
|
||||
{
|
||||
return m_contextStack.size();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
|
||||
#include "nanotrace.h"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#include "nanotracehr.h"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
import pandas as pd
|
||||
import plotly.graph_objects as go
|
||||
import plotly.subplots as sp
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
import reader as rd
|
||||
import figures as fgs
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
import os
|
||||
import io
|
||||
import json
|
||||
|
||||
@@ -569,6 +569,9 @@ void PluginDumper::loadQmltypesFile(const FilePaths &qmltypesFilePaths,
|
||||
Utils::onFinished(loadQmlTypeDescription(qmltypesFilePaths), this,
|
||||
[this, qmltypesFilePaths, libraryPath, libraryInfo]
|
||||
(const QFuture<PluginDumper::QmlTypeDescription> &typesFuture) {
|
||||
if (typesFuture.isCanceled() || typesFuture.resultCount() == 0)
|
||||
return;
|
||||
|
||||
PluginDumper::QmlTypeDescription typesResult = typesFuture.result();
|
||||
if (!typesResult.dependencies.isEmpty())
|
||||
{
|
||||
@@ -576,6 +579,9 @@ void PluginDumper::loadQmltypesFile(const FilePaths &qmltypesFilePaths,
|
||||
QSharedPointer<QSet<FilePath>>()), this,
|
||||
[typesResult, libraryInfo, libraryPath, this] (const QFuture<PluginDumper::DependencyInfo> &loadFuture)
|
||||
{
|
||||
if (loadFuture.isCanceled() || loadFuture.resultCount() == 0)
|
||||
return;
|
||||
|
||||
PluginDumper::DependencyInfo loadResult = loadFuture.result();
|
||||
QStringList errors = typesResult.errors;
|
||||
QStringList warnings = typesResult.errors;
|
||||
|
||||
@@ -205,7 +205,7 @@ if (_library_enabled)
|
||||
# Deploy lldb.exe and its Python dependency
|
||||
find_package(Clang QUIET)
|
||||
if (LLVM_TOOLS_BINARY_DIR AND LLVM_LIBRARY_DIRS)
|
||||
foreach(lldb_file lldb.exe lldb-vscode.exe liblldb.dll python311.zip python311.dll)
|
||||
foreach(lldb_file lldb.exe lldb-dap.exe liblldb.dll python311.zip python311.dll)
|
||||
if (EXISTS ${LLVM_TOOLS_BINARY_DIR}/${lldb_file})
|
||||
install(FILES ${LLVM_TOOLS_BINARY_DIR}/${lldb_file}
|
||||
DESTINATION bin/clang/bin
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
#include <QEventLoop>
|
||||
#include <QFutureWatcher>
|
||||
#include <QHash>
|
||||
#include <QMetaEnum>
|
||||
#include <QMutex>
|
||||
#include <QPromise>
|
||||
#include <QPointer>
|
||||
#include <QSet>
|
||||
#include <QTime>
|
||||
#include <QTimer>
|
||||
|
||||
using namespace std::chrono;
|
||||
@@ -1191,6 +1193,17 @@ const GroupItem stopOnSuccessOrError = workflowPolicy(WorkflowPolicy::StopOnSucc
|
||||
const GroupItem finishAllAndSuccess = workflowPolicy(WorkflowPolicy::FinishAllAndSuccess);
|
||||
const GroupItem finishAllAndError = workflowPolicy(WorkflowPolicy::FinishAllAndError);
|
||||
|
||||
// Please note the thread_local keyword below guarantees a separate instance per thread.
|
||||
// The s_activeTaskTrees is currently used internally only and is not exposed in the public API.
|
||||
// It serves for withLog() implementation now. Add a note here when a new usage is introduced.
|
||||
static thread_local QList<TaskTree *> s_activeTaskTrees = {};
|
||||
|
||||
static TaskTree *activeTaskTree()
|
||||
{
|
||||
QT_ASSERT(s_activeTaskTrees.size(), return nullptr);
|
||||
return s_activeTaskTrees.back();
|
||||
}
|
||||
|
||||
DoneResult toDoneResult(bool success)
|
||||
{
|
||||
return success ? DoneResult::Success : DoneResult::Error;
|
||||
@@ -1402,8 +1415,8 @@ void GroupItem::addChildren(const QList<GroupItem> &children)
|
||||
}
|
||||
}
|
||||
|
||||
GroupItem GroupItem::withTimeout(const GroupItem &item, milliseconds timeout,
|
||||
const std::function<void()> &handler)
|
||||
ExecutableItem ExecutableItem::withTimeout(milliseconds timeout,
|
||||
const std::function<void()> &handler) const
|
||||
{
|
||||
const auto onSetup = [timeout](milliseconds &timeoutData) { timeoutData = timeout; };
|
||||
return Group {
|
||||
@@ -1414,7 +1427,41 @@ GroupItem GroupItem::withTimeout(const GroupItem &item, milliseconds timeout,
|
||||
handler ? TimeoutTask(onSetup, [handler] { handler(); }, CallDoneIf::Success)
|
||||
: TimeoutTask(onSetup)
|
||||
},
|
||||
item
|
||||
*this
|
||||
};
|
||||
}
|
||||
|
||||
static QString currentTime() { return QTime::currentTime().toString(Qt::ISODateWithMs); }
|
||||
|
||||
ExecutableItem ExecutableItem::withLog(const QString &logName) const
|
||||
{
|
||||
const auto header = [logName] {
|
||||
return QString("TASK TREE LOG [%1] \"%2\"").arg(currentTime(), logName);
|
||||
};
|
||||
struct LogStorage
|
||||
{
|
||||
time_point<system_clock, nanoseconds> start;
|
||||
int asyncCount = 0;
|
||||
};
|
||||
const Storage<LogStorage> storage;
|
||||
return Group {
|
||||
storage,
|
||||
onGroupSetup([storage, header] {
|
||||
storage->start = system_clock::now();
|
||||
storage->asyncCount = activeTaskTree()->asyncCount();
|
||||
qDebug().noquote() << header() << "started.";
|
||||
}),
|
||||
*this,
|
||||
onGroupDone([storage, header](DoneWith result) {
|
||||
const auto elapsed = duration_cast<milliseconds>(system_clock::now() - storage->start);
|
||||
const int asyncCountDiff = activeTaskTree()->asyncCount() - storage->asyncCount;
|
||||
QT_CHECK(asyncCountDiff >= 0);
|
||||
const QMetaEnum doneWithEnum = QMetaEnum::fromType<DoneWith>();
|
||||
const QString syncType = asyncCountDiff ? QString("asynchronously")
|
||||
: QString("synchronously");
|
||||
qDebug().noquote().nospace() << header() << " finished " << syncType << " with "
|
||||
<< doneWithEnum.valueToKey(int(result)) << " within " << elapsed.count() << "ms.";
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1427,16 +1474,26 @@ class RuntimeTask;
|
||||
class ExecutionContextActivator
|
||||
{
|
||||
public:
|
||||
ExecutionContextActivator(RuntimeIteration *iteration) { activateContext(iteration); }
|
||||
ExecutionContextActivator(RuntimeContainer *container) { activateContext(container); }
|
||||
ExecutionContextActivator(RuntimeIteration *iteration) {
|
||||
activateTaskTree(iteration);
|
||||
activateContext(iteration);
|
||||
}
|
||||
ExecutionContextActivator(RuntimeContainer *container) {
|
||||
activateTaskTree(container);
|
||||
activateContext(container);
|
||||
}
|
||||
~ExecutionContextActivator() {
|
||||
for (int i = m_activeStorages.size() - 1; i >= 0; --i) // iterate in reverse order
|
||||
m_activeStorages[i].m_storageData->threadData().popStorage();
|
||||
for (int i = m_activeLoops.size() - 1; i >= 0; --i) // iterate in reverse order
|
||||
m_activeLoops[i].m_loopData->threadData().popIteration();
|
||||
QT_ASSERT(s_activeTaskTrees.size(), return);
|
||||
s_activeTaskTrees.pop_back();
|
||||
}
|
||||
|
||||
private:
|
||||
void activateTaskTree(RuntimeIteration *iteration);
|
||||
void activateTaskTree(RuntimeContainer *container);
|
||||
void activateContext(RuntimeIteration *iteration);
|
||||
void activateContext(RuntimeContainer *container);
|
||||
QList<Loop> m_activeLoops;
|
||||
@@ -1490,9 +1547,8 @@ public:
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
void bumpAsyncCount();
|
||||
void advanceProgress(int byValue);
|
||||
void emitStartedAndProgress();
|
||||
void emitProgress();
|
||||
void emitDone(DoneWith result);
|
||||
void callSetupHandler(StorageBase storage, StoragePtr storagePtr) {
|
||||
callStorageHandler(storage, storagePtr, &StorageHandler::m_setupHandler);
|
||||
@@ -1552,6 +1608,7 @@ public:
|
||||
TaskTree *q = nullptr;
|
||||
Guard m_guard;
|
||||
int m_progressValue = 0;
|
||||
int m_asyncCount = 0;
|
||||
QSet<StorageBase> m_storages;
|
||||
QHash<StorageBase, StorageHandler> m_storageHandlers;
|
||||
std::optional<TaskNode> m_root;
|
||||
@@ -1666,6 +1723,16 @@ static bool isProgressive(RuntimeContainer *container)
|
||||
return iteration ? iteration->m_isProgressive : true;
|
||||
}
|
||||
|
||||
void ExecutionContextActivator::activateTaskTree(RuntimeIteration *iteration)
|
||||
{
|
||||
activateTaskTree(iteration->m_container);
|
||||
}
|
||||
|
||||
void ExecutionContextActivator::activateTaskTree(RuntimeContainer *container)
|
||||
{
|
||||
s_activeTaskTrees.push_back(container->m_containerNode.m_taskTreePrivate->q);
|
||||
}
|
||||
|
||||
void ExecutionContextActivator::activateContext(RuntimeIteration *iteration)
|
||||
{
|
||||
std::optional<Loop> loop = iteration->loop();
|
||||
@@ -1696,8 +1763,14 @@ void TaskTreePrivate::start()
|
||||
{
|
||||
QT_ASSERT(m_root, return);
|
||||
QT_ASSERT(!m_runtimeRoot, return);
|
||||
m_asyncCount = 0;
|
||||
m_progressValue = 0;
|
||||
emitStartedAndProgress();
|
||||
{
|
||||
GuardLocker locker(m_guard);
|
||||
emit q->started();
|
||||
emit q->asyncCountChanged(m_asyncCount);
|
||||
emit q->progressValueChanged(m_progressValue);
|
||||
}
|
||||
// TODO: check storage handlers for not existing storages in tree
|
||||
for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) {
|
||||
QT_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't "
|
||||
@@ -1705,6 +1778,7 @@ void TaskTreePrivate::start()
|
||||
}
|
||||
m_runtimeRoot.reset(new RuntimeTask{*m_root});
|
||||
start(m_runtimeRoot.get());
|
||||
bumpAsyncCount();
|
||||
}
|
||||
|
||||
void TaskTreePrivate::stop()
|
||||
@@ -1717,6 +1791,15 @@ void TaskTreePrivate::stop()
|
||||
emitDone(DoneWith::Cancel);
|
||||
}
|
||||
|
||||
void TaskTreePrivate::bumpAsyncCount()
|
||||
{
|
||||
if (!m_runtimeRoot)
|
||||
return;
|
||||
++m_asyncCount;
|
||||
GuardLocker locker(m_guard);
|
||||
emit q->asyncCountChanged(m_asyncCount);
|
||||
}
|
||||
|
||||
void TaskTreePrivate::advanceProgress(int byValue)
|
||||
{
|
||||
if (byValue == 0)
|
||||
@@ -1724,18 +1807,6 @@ void TaskTreePrivate::advanceProgress(int byValue)
|
||||
QT_CHECK(byValue > 0);
|
||||
QT_CHECK(m_progressValue + byValue <= m_root->taskCount());
|
||||
m_progressValue += byValue;
|
||||
emitProgress();
|
||||
}
|
||||
|
||||
void TaskTreePrivate::emitStartedAndProgress()
|
||||
{
|
||||
GuardLocker locker(m_guard);
|
||||
emit q->started();
|
||||
emit q->progressValueChanged(m_progressValue);
|
||||
}
|
||||
|
||||
void TaskTreePrivate::emitProgress()
|
||||
{
|
||||
GuardLocker locker(m_guard);
|
||||
emit q->progressValueChanged(m_progressValue);
|
||||
}
|
||||
@@ -2037,10 +2108,12 @@ SetupResult TaskTreePrivate::start(RuntimeTask *node)
|
||||
node->m_task.release()->deleteLater();
|
||||
RuntimeIteration *parentIteration = node->m_parentIteration;
|
||||
parentIteration->deleteChild(node);
|
||||
if (parentIteration->m_container->isStarting())
|
||||
if (parentIteration->m_container->isStarting()) {
|
||||
*unwindAction = toSetupResult(result);
|
||||
else
|
||||
} else {
|
||||
childDone(parentIteration, result);
|
||||
bumpAsyncCount();
|
||||
}
|
||||
});
|
||||
|
||||
node->m_task->start();
|
||||
@@ -2961,6 +3034,38 @@ DoneWith TaskTree::runBlocking(const Group &recipe, const QFuture<void> &future,
|
||||
return taskTree.runBlocking(future);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current real count of asynchronous chains of invocations.
|
||||
|
||||
The returned value indicates how many times the control returns to the caller's
|
||||
event loop while the task tree is running. Initially, this value is 0.
|
||||
If the execution of the task tree finishes fully synchronously, this value remains 0.
|
||||
If the task tree contains any asynchronous tasks that are successfully started during
|
||||
a call to start(), this value is bumped to 1 just before the call to start() finishes.
|
||||
Later, when any asynchronous task finishes and any possible continuations are started,
|
||||
this value is bumped again. The bumping continues until the task tree finishes.
|
||||
When the task tree emits the done() signal, the bumping stops.
|
||||
The asyncCountChanged() signal is emitted on every bump of this value.
|
||||
|
||||
\sa asyncCountChanged()
|
||||
*/
|
||||
int TaskTree::asyncCount() const
|
||||
{
|
||||
return d->m_asyncCount;
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn void TaskTree::asyncCountChanged(int count)
|
||||
|
||||
This signal is emitted when the running task tree is about to return control to the caller's
|
||||
event loop. When the task tree is started, this signal is emitted with \a count value of 0,
|
||||
and emitted later on every asyncCount() value bump with an updated \a count value.
|
||||
Every signal sent (except the initial one with the value of 0) guarantees that the task tree
|
||||
is still running asynchronously after the emission.
|
||||
|
||||
\sa asyncCount()
|
||||
*/
|
||||
|
||||
/*!
|
||||
Returns the number of asynchronous tasks contained in the stored recipe.
|
||||
|
||||
@@ -3006,7 +3111,7 @@ int TaskTree::taskCount() const
|
||||
When the task tree is started, this number is set to \c 0.
|
||||
When the task tree is finished, this number always equals progressMaximum().
|
||||
|
||||
\sa progressMaximum()
|
||||
\sa progressMaximum(), progressValueChanged()
|
||||
*/
|
||||
int TaskTree::progressValue() const
|
||||
{
|
||||
|
||||
@@ -261,8 +261,6 @@ protected:
|
||||
static GroupItem groupHandler(const GroupHandler &handler) { return GroupItem({handler}); }
|
||||
static GroupItem parallelLimit(int limit) { return GroupItem({{}, limit}); }
|
||||
static GroupItem workflowPolicy(WorkflowPolicy policy) { return GroupItem({{}, {}, policy}); }
|
||||
static GroupItem withTimeout(const GroupItem &item, std::chrono::milliseconds timeout,
|
||||
const std::function<void()> &handler = {});
|
||||
|
||||
// Checks if Function may be invoked with Args and if Function's return type is Result.
|
||||
template <typename Result, typename Function, typename ...Args,
|
||||
@@ -286,7 +284,19 @@ private:
|
||||
TaskHandler m_taskHandler;
|
||||
};
|
||||
|
||||
class TASKING_EXPORT Group : public GroupItem
|
||||
class TASKING_EXPORT ExecutableItem : public GroupItem
|
||||
{
|
||||
public:
|
||||
ExecutableItem withTimeout(std::chrono::milliseconds timeout,
|
||||
const std::function<void()> &handler = {}) const;
|
||||
ExecutableItem withLog(const QString &logName) const;
|
||||
|
||||
protected:
|
||||
ExecutableItem() = default;
|
||||
ExecutableItem(const TaskHandler &handler) : GroupItem(handler) {}
|
||||
};
|
||||
|
||||
class TASKING_EXPORT Group : public ExecutableItem
|
||||
{
|
||||
public:
|
||||
Group(const QList<GroupItem> &children) { addChildren(children); }
|
||||
@@ -304,11 +314,6 @@ public:
|
||||
using GroupItem::parallelLimit; // Default: 1 (sequential). 0 means unlimited (parallel).
|
||||
using GroupItem::workflowPolicy; // Default: WorkflowPolicy::StopOnError.
|
||||
|
||||
GroupItem withTimeout(std::chrono::milliseconds timeout,
|
||||
const std::function<void()> &handler = {}) const {
|
||||
return GroupItem::withTimeout(*this, timeout, handler);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Handler>
|
||||
static GroupSetupHandler wrapGroupSetup(Handler &&handler)
|
||||
@@ -387,7 +392,7 @@ public:
|
||||
};
|
||||
|
||||
// Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount()
|
||||
class TASKING_EXPORT Sync final : public GroupItem
|
||||
class TASKING_EXPORT Sync final : public ExecutableItem
|
||||
{
|
||||
public:
|
||||
template <typename Handler>
|
||||
@@ -431,7 +436,7 @@ private:
|
||||
};
|
||||
|
||||
template <typename Adapter>
|
||||
class CustomTask final : public GroupItem
|
||||
class CustomTask final : public ExecutableItem
|
||||
{
|
||||
public:
|
||||
using Task = typename Adapter::TaskType;
|
||||
@@ -445,16 +450,10 @@ public:
|
||||
template <typename SetupHandler = TaskSetupHandler, typename DoneHandler = TaskDoneHandler>
|
||||
CustomTask(SetupHandler &&setup = TaskSetupHandler(), DoneHandler &&done = TaskDoneHandler(),
|
||||
CallDoneIf callDoneIf = CallDoneIf::SuccessOrError)
|
||||
: GroupItem({&createAdapter, wrapSetup(std::forward<SetupHandler>(setup)),
|
||||
wrapDone(std::forward<DoneHandler>(done)), callDoneIf})
|
||||
: ExecutableItem({&createAdapter, wrapSetup(std::forward<SetupHandler>(setup)),
|
||||
wrapDone(std::forward<DoneHandler>(done)), callDoneIf})
|
||||
{}
|
||||
|
||||
GroupItem withTimeout(std::chrono::milliseconds timeout,
|
||||
const std::function<void()> &handler = {}) const
|
||||
{
|
||||
return GroupItem::withTimeout(*this, timeout, handler);
|
||||
}
|
||||
|
||||
private:
|
||||
static Adapter *createAdapter() { return new Adapter; }
|
||||
|
||||
@@ -542,6 +541,7 @@ public:
|
||||
static DoneWith runBlocking(const Group &recipe, const QFuture<void> &future,
|
||||
std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
|
||||
|
||||
int asyncCount() const;
|
||||
int taskCount() const;
|
||||
int progressMaximum() const { return taskCount(); }
|
||||
int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded
|
||||
@@ -566,6 +566,7 @@ public:
|
||||
signals:
|
||||
void started();
|
||||
void done(DoneWith result);
|
||||
void asyncCountChanged(int count);
|
||||
void progressValueChanged(int value); // updated whenever task finished / skipped / stopped
|
||||
|
||||
private:
|
||||
|
||||
@@ -1199,6 +1199,9 @@ void TerminalView::mouseReleaseEvent(QMouseEvent *event)
|
||||
|
||||
void TerminalView::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() != Qt::LeftButton)
|
||||
return;
|
||||
|
||||
if (d->m_allowMouseTracking) {
|
||||
d->m_surface->mouseMove(toGridPos(event), event->modifiers());
|
||||
d->m_surface->mouseButton(event->button(), true, event->modifiers());
|
||||
|
||||
@@ -54,7 +54,7 @@ auto asyncRun(Function &&function, Args &&...args)
|
||||
template <typename R, typename T>
|
||||
const QFuture<T> &onResultReady(const QFuture<T> &future, R *receiver, void(R::*member)(const T &))
|
||||
{
|
||||
auto watcher = new QFutureWatcher<T>();
|
||||
auto watcher = new QFutureWatcher<T>(receiver);
|
||||
QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater);
|
||||
QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, receiver, [=](int index) {
|
||||
(receiver->*member)(watcher->future().resultAt(index));
|
||||
@@ -72,7 +72,7 @@ const QFuture<T> &onResultReady(const QFuture<T> &future, R *receiver, void(R::*
|
||||
template <typename T, typename Function>
|
||||
const QFuture<T> &onResultReady(const QFuture<T> &future, QObject *guard, Function f)
|
||||
{
|
||||
auto watcher = new QFutureWatcher<T>();
|
||||
auto watcher = new QFutureWatcher<T>(guard);
|
||||
QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater);
|
||||
QObject::connect(watcher, &QFutureWatcherBase::resultReadyAt, guard, [f, watcher](int index) {
|
||||
f(watcher->future().resultAt(index));
|
||||
@@ -90,7 +90,7 @@ template<typename R, typename T>
|
||||
const QFuture<T> &onFinished(const QFuture<T> &future,
|
||||
R *receiver, void (R::*member)(const QFuture<T> &))
|
||||
{
|
||||
auto watcher = new QFutureWatcher<T>();
|
||||
auto watcher = new QFutureWatcher<T>(receiver);
|
||||
QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater);
|
||||
QObject::connect(watcher, &QFutureWatcherBase::finished, receiver,
|
||||
[=] { (receiver->*member)(watcher->future()); });
|
||||
@@ -107,7 +107,7 @@ const QFuture<T> &onFinished(const QFuture<T> &future,
|
||||
template<typename T, typename Function>
|
||||
const QFuture<T> &onFinished(const QFuture<T> &future, QObject *guard, Function f)
|
||||
{
|
||||
auto watcher = new QFutureWatcher<T>();
|
||||
auto watcher = new QFutureWatcher<T>(guard);
|
||||
QObject::connect(watcher, &QFutureWatcherBase::finished, watcher, &QObject::deleteLater);
|
||||
QObject::connect(watcher, &QFutureWatcherBase::finished, guard, [f, watcher] {
|
||||
f(watcher->future());
|
||||
|
||||
@@ -566,8 +566,8 @@ static bool checkToRefuseRemoveStandardLocationDirectory(const QString &dirPath,
|
||||
{
|
||||
if (QStandardPaths::standardLocations(location).contains(dirPath)) {
|
||||
if (error) {
|
||||
*error = Tr::tr("Refusing to remove your %1 directory.").arg(
|
||||
QStandardPaths::displayName(location));
|
||||
*error = Tr::tr("Refusing to remove the standard directory \"%1\".")
|
||||
.arg(QStandardPaths::displayName(location));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ void MacroExpander::registerIntVariable(const QByteArray &variable,
|
||||
* Convenience function to register several variables with the same \a prefix, that have a file
|
||||
* as a value. Takes the prefix and registers variables like \c{prefix:FilePath} and
|
||||
* \c{prefix:Path}, with descriptions that start with the given \a heading.
|
||||
* For example \c{registerFileVariables("CurrentDocument", tr("Current Document"))} registers
|
||||
* For example \c{registerFileVariables("CurrentDocument", Tr::tr("Current Document"))} registers
|
||||
* variables such as \c{CurrentDocument:FilePath} with description
|
||||
* "Current Document: Full path including file name."
|
||||
*
|
||||
|
||||
@@ -847,7 +847,9 @@ public:
|
||||
qint64 m_applicationMainThreadId = 0;
|
||||
ProcessResultData m_resultData;
|
||||
|
||||
QTextCodec *m_codec = QTextCodec::codecForLocale();
|
||||
QTextCodec *m_stdOutCodec = QTextCodec::codecForLocale();
|
||||
QTextCodec *m_stdErrCodec = QTextCodec::codecForLocale();
|
||||
|
||||
ProcessResult m_result = ProcessResult::StartFailed;
|
||||
ChannelBuffer m_stdOut;
|
||||
ChannelBuffer m_stdErr;
|
||||
@@ -1102,9 +1104,9 @@ void ProcessPrivate::sendControlSignal(ControlSignal controlSignal)
|
||||
void ProcessPrivate::clearForRun()
|
||||
{
|
||||
m_stdOut.clearForRun();
|
||||
m_stdOut.codec = m_codec;
|
||||
m_stdOut.codec = m_stdOutCodec;
|
||||
m_stdErr.clearForRun();
|
||||
m_stdErr.codec = m_codec;
|
||||
m_stdErr.codec = m_stdErrCodec;
|
||||
m_result = ProcessResult::StartFailed;
|
||||
m_startTimestamp = {};
|
||||
m_doneTimestamp = {};
|
||||
@@ -1663,8 +1665,7 @@ QString Process::exitMessage(const CommandLine &command, ProcessResult result,
|
||||
case ProcessResult::Canceled:
|
||||
// TODO: We might want to format it nicely when bigger than 1 second, e.g. 1,324 s.
|
||||
// Also when it's bigger than 1 minute, 1 hour, etc...
|
||||
return Tr::tr("The command \"%1\" was canceled after (%2 ms).")
|
||||
.arg(cmd).arg(duration.count());
|
||||
return Tr::tr("The command \"%1\" was canceled after %2 ms.").arg(cmd).arg(duration.count());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -1729,13 +1730,13 @@ QByteArray Process::rawStdErr() const
|
||||
QString Process::stdOut() const
|
||||
{
|
||||
QTC_CHECK(d->m_stdOut.keepRawData);
|
||||
return d->m_codec->toUnicode(d->m_stdOut.rawData);
|
||||
return d->m_stdOutCodec->toUnicode(d->m_stdOut.rawData);
|
||||
}
|
||||
|
||||
QString Process::stdErr() const
|
||||
{
|
||||
QTC_CHECK(d->m_stdErr.keepRawData);
|
||||
return d->m_codec->toUnicode(d->m_stdErr.rawData);
|
||||
return d->m_stdErrCodec->toUnicode(d->m_stdErr.rawData);
|
||||
}
|
||||
|
||||
QString Process::cleanedStdOut() const
|
||||
@@ -1850,7 +1851,20 @@ void ChannelBuffer::handleRest()
|
||||
void Process::setCodec(QTextCodec *c)
|
||||
{
|
||||
QTC_ASSERT(c, return);
|
||||
d->m_codec = c;
|
||||
d->m_stdOutCodec = c;
|
||||
d->m_stdErrCodec = c;
|
||||
}
|
||||
|
||||
void Process::setStdOutCodec(QTextCodec *c)
|
||||
{
|
||||
QTC_ASSERT(c, return);
|
||||
d->m_stdOutCodec = c;
|
||||
}
|
||||
|
||||
void Process::setStdErrCodec(QTextCodec *c)
|
||||
{
|
||||
QTC_ASSERT(c, return);
|
||||
d->m_stdErrCodec = c;
|
||||
}
|
||||
|
||||
void Process::setTimeOutMessageBoxEnabled(bool v)
|
||||
|
||||
@@ -148,8 +148,10 @@ public:
|
||||
void runBlocking(std::chrono::seconds timeout = std::chrono::seconds(10),
|
||||
EventLoopMode eventLoopMode = EventLoopMode::Off);
|
||||
|
||||
// TODO: We should specify the purpose of the codec, e.g. setCodecForStandardChannel()
|
||||
void setCodec(QTextCodec *c);
|
||||
void setCodec(QTextCodec *c); // for stdOut and stdErr
|
||||
void setStdOutCodec(QTextCodec *c);
|
||||
void setStdErrCodec(QTextCodec *c);
|
||||
|
||||
void setTimeOutMessageBoxEnabled(bool);
|
||||
|
||||
void setStdOutCallback(const TextChannelCallback &callback);
|
||||
|
||||
@@ -126,6 +126,15 @@ static QString ndkPackageMarker()
|
||||
return QLatin1String(Constants::ndkPackageName) + ";";
|
||||
}
|
||||
|
||||
static QString platformsPackageMarker()
|
||||
{
|
||||
return QLatin1String(Constants::platformsPackageName) + ";";
|
||||
}
|
||||
|
||||
static QString buildToolsPackageMarker()
|
||||
{
|
||||
return QLatin1String(Constants::buildToolsPackageName) + ";";
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// AndroidConfig
|
||||
@@ -951,15 +960,59 @@ bool AndroidConfig::sdkToolsOk() const
|
||||
return exists && writable && sdkToolsExist;
|
||||
}
|
||||
|
||||
static QStringList packagesExcludingBuiltWithDefaults(const QStringList &packages)
|
||||
{
|
||||
return Utils::filtered(packages, [] (const QString &p) {
|
||||
return !p.startsWith(ndkPackageMarker()) && !p.startsWith(platformsPackageMarker())
|
||||
&& !p.startsWith(buildToolsPackageMarker()); });
|
||||
}
|
||||
|
||||
static QString essentialBuiltWithBuildToolsPackage(int builtWithApiVersion)
|
||||
{
|
||||
// For build-tools, to avoid the situation of potentially having the essential packages
|
||||
// invalidated whenever a new minor version is released, check if any version with major
|
||||
// version matching builtWith apiVersion and use it as essential, otherwise use the any
|
||||
// other one that has an minimum major version of builtWith apiVersion.
|
||||
const BuildToolsList buildTools =
|
||||
AndroidConfigurations::sdkManager()->filteredBuildTools(builtWithApiVersion);
|
||||
const BuildToolsList apiBuildTools
|
||||
= Utils::filtered(buildTools, [builtWithApiVersion] (const BuildTools *pkg) {
|
||||
return pkg->revision().majorVersion() == builtWithApiVersion; });
|
||||
const QString installedBuildTool = [apiBuildTools] () -> QString {
|
||||
for (const BuildTools *pkg : apiBuildTools) {
|
||||
if (pkg->state() == AndroidSdkPackage::Installed)
|
||||
return pkg->sdkStylePath();
|
||||
}
|
||||
return {};
|
||||
}();
|
||||
|
||||
if (installedBuildTool.isEmpty()) {
|
||||
if (!apiBuildTools.isEmpty())
|
||||
return apiBuildTools.first()->sdkStylePath();
|
||||
else if (!buildTools.isEmpty())
|
||||
return buildTools.first()->sdkStylePath();
|
||||
// This means there's something wrong with sdkmanager, return a default version anyway
|
||||
else
|
||||
return buildToolsPackageMarker() + QString::number(builtWithApiVersion) + ".0.0";
|
||||
}
|
||||
|
||||
return installedBuildTool;
|
||||
}
|
||||
|
||||
QStringList AndroidConfig::essentialsFromQtVersion(const QtVersion &version) const
|
||||
{
|
||||
if (auto androidQtVersion = dynamic_cast<const AndroidQtVersion *>(&version)) {
|
||||
bool ok;
|
||||
const AndroidQtVersion::BuiltWith bw = androidQtVersion->builtWith(&ok);
|
||||
if (ok) {
|
||||
const QString ndkPackage = ndkPackageMarker() + bw.ndkVersion.toString();
|
||||
return QStringList(ndkPackage)
|
||||
+ packagesWithoutNdks(m_defaultSdkDepends.essentialPackages);
|
||||
QStringList builtWithPackages;
|
||||
builtWithPackages.append(ndkPackageMarker() + bw.ndkVersion.toString());
|
||||
const QString apiVersion = QString::number(bw.apiVersion);
|
||||
builtWithPackages.append(platformsPackageMarker() + "android-" + apiVersion);
|
||||
builtWithPackages.append(essentialBuiltWithBuildToolsPackage(bw.apiVersion));
|
||||
|
||||
return builtWithPackages + packagesExcludingBuiltWithDefaults(
|
||||
m_defaultSdkDepends.essentialPackages);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,8 @@ const Utils::Id AndroidAvdPath = "AndroidAvdPath";
|
||||
// SDK Tools
|
||||
const char cmdlineToolsName[] = "cmdline-tools";
|
||||
const char ndkPackageName[] = "ndk";
|
||||
const char platformsPackageName[] = "platforms";
|
||||
const char buildToolsPackageName[] = "build-tools";
|
||||
|
||||
// For AndroidQtVersion
|
||||
const char ArmToolsDisplayName[] = "arm";
|
||||
|
||||
@@ -405,7 +405,10 @@ void AndroidSdkManagerPrivate::reloadSdkPackages()
|
||||
if (m_packageListingSuccessful) {
|
||||
SdkManagerOutputParser parser(m_allPackages);
|
||||
parser.parsePackageListing(packageListing);
|
||||
} else {
|
||||
qCWarning(sdkManagerLog) << "Failed parsing packages:" << packageListing;
|
||||
}
|
||||
|
||||
emit m_sdkManager.packageReloadFinished();
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ DataTagLocatorFilter::DataTagLocatorFilter()
|
||||
{
|
||||
setId("Locate Qt Test data tags");
|
||||
setDisplayName(Tr::tr("Locate Qt Test data tags"));
|
||||
setDescription(Tr::tr("Locates a Qt Test data tag found inside the active project."));
|
||||
setDescription(Tr::tr("Locates Qt Test data tags found inside the active project."));
|
||||
setDefaultShortcutString("qdt");
|
||||
setPriority(Medium);
|
||||
using namespace ProjectExplorer;
|
||||
|
||||
@@ -73,9 +73,10 @@ QtTestFramework::QtTestFramework()
|
||||
useXMLOutput.setSettingsKey("UseXMLOutput");
|
||||
useXMLOutput.setDefaultValue(true);
|
||||
useXMLOutput.setLabelText(Tr::tr("Use XML output"));
|
||||
useXMLOutput.setToolTip(Tr::tr("XML output is recommended, because it avoids parsing issues, "
|
||||
"while plain text is more human readable.\n\nWarning: "
|
||||
"Plain text misses some information, such as duration."));
|
||||
useXMLOutput.setToolTip("<html>"
|
||||
+ Tr::tr("XML output is recommended, because it avoids parsing issues, "
|
||||
"while plain text is more human readable.<p>Warning: "
|
||||
"Plain text misses some information, such as duration."));
|
||||
|
||||
verboseBench.setSettingsKey("VerboseBench");
|
||||
verboseBench.setLabelText(Tr::tr("Verbose benchmarks"));
|
||||
@@ -98,18 +99,22 @@ QtTestFramework::QtTestFramework()
|
||||
quickCheckForDerivedTests.setDefaultValue(false);
|
||||
quickCheckForDerivedTests.setLabelText(Tr::tr("Check for derived Qt Quick tests"));
|
||||
quickCheckForDerivedTests.setToolTip(
|
||||
Tr::tr("Search for Qt Quick tests that are derived from TestCase.\nWarning: Enabling this "
|
||||
"feature significantly increases scan time."));
|
||||
"<html>"
|
||||
+ Tr::tr(
|
||||
"Search for Qt Quick tests that are derived from TestCase.<p>Warning: Enabling this "
|
||||
"feature significantly increases scan time."));
|
||||
|
||||
parseMessages.setSettingsKey("ParseMessages");
|
||||
parseMessages.setDefaultValue(false);
|
||||
parseMessages.setLabelText(Tr::tr("Find user-defined locations"));
|
||||
parseMessages.setToolTip(
|
||||
Tr::tr("Parse messages for the pattern \"file://filepath:line\", where \":line\" is "
|
||||
"optional, and use this as location information.\n"
|
||||
"Warning: If the patterns are used in code, the location information for debug "
|
||||
"messages and other messages might improve,\n"
|
||||
"at the risk of some incorrect locations and lower performance."));
|
||||
"<html>"
|
||||
+ Tr::tr("Parse messages for the following pattern and use it as location information:"
|
||||
"<pre>file://filepath:line</pre>"
|
||||
"where \":line\" is optional."
|
||||
"<p>Warning: If the patterns are used in code, the location information for debug "
|
||||
"messages and other messages might improve,"
|
||||
"at the risk of some incorrect locations and lower performance."));
|
||||
readSettings();
|
||||
|
||||
maxWarnings.setEnabler(&limitWarnings);
|
||||
|
||||
@@ -6,11 +6,14 @@ add_qtc_plugin(Axivion
|
||||
axivion.qrc
|
||||
axivionoutputpane.cpp axivionoutputpane.h
|
||||
axivionplugin.cpp axivionplugin.h
|
||||
axivionprojectsettings.h axivionprojectsettings.cpp
|
||||
axivionprojectsettings.cpp axivionprojectsettings.h
|
||||
axivionsettings.cpp axivionsettings.h
|
||||
axiviontr.h
|
||||
credentialquery.h credentialquery.cpp
|
||||
dashboard/dto.cpp dashboard/dto.h
|
||||
dashboard/concat.cpp dashboard/concat.h
|
||||
dashboard/error.h dashboard/error.cpp
|
||||
dashboard/error.cpp dashboard/error.h
|
||||
dynamiclistmodel.cpp dynamiclistmodel.h
|
||||
issueheaderview.cpp issueheaderview.h
|
||||
)
|
||||
|
||||
@@ -23,8 +23,12 @@ QtcPlugin {
|
||||
"axivionsettings.cpp",
|
||||
"axivionsettings.h",
|
||||
"axiviontr.h",
|
||||
"credentialquery.h",
|
||||
"credentialquery.cpp",
|
||||
"credentialquery.h",
|
||||
"dynamiclistmodel.cpp",
|
||||
"dynamiclistmodel.h",
|
||||
"issueheaderview.cpp",
|
||||
"issueheaderview.h",
|
||||
]
|
||||
|
||||
cpp.includePaths: base.concat(["."]) // needed for the generated stuff below
|
||||
|
||||
@@ -14,5 +14,9 @@
|
||||
<file>images/button-mv@2x.png</file>
|
||||
<file>images/button-sv.png</file>
|
||||
<file>images/button-sv@2x.png</file>
|
||||
<file>images/sortAsc.png</file>
|
||||
<file>images/sortAsc@2x.png</file>
|
||||
<file>images/sortDesc.png</file>
|
||||
<file>images/sortDesc@2x.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -6,17 +6,21 @@
|
||||
#include "axivionplugin.h"
|
||||
#include "axiviontr.h"
|
||||
#include "dashboard/dto.h"
|
||||
#include "issueheaderview.h"
|
||||
#include "dynamiclistmodel.h"
|
||||
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <coreplugin/ioutputpane.h>
|
||||
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
|
||||
#include <solutions/tasking/tasktreerunner.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/link.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/treemodel.h>
|
||||
#include <utils/basetreeview.h>
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
@@ -28,9 +32,7 @@
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QScrollBar>
|
||||
#include <QStackedWidget>
|
||||
#include <QTextBrowser>
|
||||
#include <QToolButton>
|
||||
|
||||
#include <map>
|
||||
@@ -59,22 +61,24 @@ DashboardWidget::DashboardWidget(QWidget *parent)
|
||||
: QScrollArea(parent)
|
||||
{
|
||||
QWidget *widget = new QWidget(this);
|
||||
QVBoxLayout *layout = new QVBoxLayout(widget);
|
||||
QFormLayout *projectLayout = new QFormLayout;
|
||||
m_project = new QLabel(this);
|
||||
projectLayout->addRow(Tr::tr("Project:"), m_project);
|
||||
m_loc = new QLabel(this);
|
||||
projectLayout->addRow(Tr::tr("Lines of code:"), m_loc);
|
||||
m_timestamp = new QLabel(this);
|
||||
projectLayout->addRow(Tr::tr("Analysis timestamp:"), m_timestamp);
|
||||
layout->addLayout(projectLayout);
|
||||
layout->addSpacing(10);
|
||||
auto row = new QHBoxLayout;
|
||||
|
||||
m_gridLayout = new QGridLayout;
|
||||
row->addLayout(m_gridLayout);
|
||||
row->addStretch(1);
|
||||
layout->addLayout(row);
|
||||
layout->addStretch(1);
|
||||
|
||||
using namespace Layouting;
|
||||
Column {
|
||||
Form {
|
||||
Tr::tr("Project:"), m_project, br,
|
||||
Tr::tr("Lines of code:"), m_loc, br,
|
||||
Tr::tr("Analysis timestamp:"), m_timestamp
|
||||
},
|
||||
Space(10),
|
||||
Row { m_gridLayout, st },
|
||||
st
|
||||
}.attachTo(widget);
|
||||
|
||||
setWidget(widget);
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
setWidgetResizable(true);
|
||||
@@ -187,32 +191,59 @@ void DashboardWidget::updateUi()
|
||||
addValuesWidgets(Tr::tr("Total:"), allTotal, allAdded, allRemoved, row);
|
||||
}
|
||||
|
||||
class IssueTreeItem final : public StaticTreeItem
|
||||
struct LinkWithColumns
|
||||
{
|
||||
Link link;
|
||||
QList<int> columns;
|
||||
};
|
||||
|
||||
class IssueListItem final : public ListItem
|
||||
{
|
||||
public:
|
||||
IssueTreeItem(const QStringList &data, const QStringList &toolTips)
|
||||
: StaticTreeItem(data, toolTips)
|
||||
IssueListItem(int row, const QString &id, const QStringList &data, const QStringList &toolTips)
|
||||
: ListItem(row)
|
||||
, m_id(id)
|
||||
, m_data(data)
|
||||
, m_toolTips(toolTips)
|
||||
{}
|
||||
|
||||
void setLinks(const Links &links) { m_links = links; }
|
||||
void setLinks(const QList<LinkWithColumns> &links) { m_links = links; }
|
||||
|
||||
QVariant data(int column, int role) const
|
||||
{
|
||||
if (role == Qt::DisplayRole && column >= 0 && column < m_data.size())
|
||||
return m_data.at(column);
|
||||
if (role == Qt::ToolTipRole && column >= 0 && column < m_toolTips.size())
|
||||
return m_toolTips.at(column);
|
||||
return {};
|
||||
}
|
||||
|
||||
bool setData(int column, const QVariant &value, int role) final
|
||||
{
|
||||
if (role == BaseTreeView::ItemActivatedRole && !m_links.isEmpty()) {
|
||||
// TODO for now only simple - just the first..
|
||||
Link link = m_links.first();
|
||||
Project *project = ProjectManager::startupProject();
|
||||
FilePath baseDir = project ? project->projectDirectory() : FilePath{};
|
||||
link.targetFilePath = baseDir.resolvePath(link.targetFilePath);
|
||||
if (link.targetFilePath.exists())
|
||||
EditorManager::openEditorAt(link);
|
||||
if (role == BaseTreeView::ItemActivatedRole) {
|
||||
if (!m_links.isEmpty()) {
|
||||
Link link
|
||||
= Utils::findOr(m_links, m_links.first(), [column](const LinkWithColumns &link) {
|
||||
return link.columns.contains(column);
|
||||
}).link;
|
||||
Project *project = ProjectManager::startupProject();
|
||||
FilePath baseDir = project ? project->projectDirectory() : FilePath{};
|
||||
link.targetFilePath = baseDir.resolvePath(link.targetFilePath);
|
||||
if (link.targetFilePath.exists())
|
||||
EditorManager::openEditorAt(link);
|
||||
}
|
||||
if (!m_id.isEmpty())
|
||||
fetchIssueInfo(m_id);
|
||||
return true;
|
||||
}
|
||||
return StaticTreeItem::setData(column, value, role);
|
||||
return ListItem::setData(column, value, role);
|
||||
}
|
||||
|
||||
private:
|
||||
Links m_links;
|
||||
const QString m_id;
|
||||
QStringList m_data;
|
||||
QStringList m_toolTips;
|
||||
QList<LinkWithColumns> m_links;
|
||||
};
|
||||
|
||||
class IssuesWidget : public QScrollArea
|
||||
@@ -220,24 +251,23 @@ class IssuesWidget : public QScrollArea
|
||||
public:
|
||||
explicit IssuesWidget(QWidget *parent = nullptr);
|
||||
void updateUi();
|
||||
void setTableDto(const Dto::TableInfoDto &dto);
|
||||
void addIssues(const Dto::IssueTableDto &dto);
|
||||
|
||||
private:
|
||||
void updateTable();
|
||||
void addIssues(const Dto::IssueTableDto &dto, int startRow);
|
||||
void onSearchParameterChanged();
|
||||
void updateBasicProjectInfo(std::optional<Dto::ProjectInfoDto> info);
|
||||
void updateTableView();
|
||||
void setFiltersEnabled(bool enabled);
|
||||
IssueListSearch searchFromUi() const;
|
||||
void fetchTable();
|
||||
void fetchIssues(const IssueListSearch &search);
|
||||
void fetchMoreIssues();
|
||||
void onFetchRequested(int startRow, int limit);
|
||||
|
||||
QString m_currentPrefix;
|
||||
QString m_currentProject;
|
||||
std::optional<Dto::TableInfoDto> m_currentTableInfo;
|
||||
QHBoxLayout *m_typesLayout = nullptr;
|
||||
QButtonGroup *m_typesButtonGroup = nullptr;
|
||||
QHBoxLayout *m_filtersLayout = nullptr;
|
||||
QPushButton *m_addedFilter = nullptr;
|
||||
QPushButton *m_removedFilter = nullptr;
|
||||
QComboBox *m_ownerFilter = nullptr;
|
||||
@@ -246,9 +276,9 @@ private:
|
||||
QLineEdit *m_pathGlobFilter = nullptr; // FancyLineEdit instead?
|
||||
QLabel *m_totalRows = nullptr;
|
||||
BaseTreeView *m_issuesView = nullptr;
|
||||
TreeModel<> *m_issuesModel = nullptr;
|
||||
IssueHeaderView *m_headerView = nullptr;
|
||||
DynamicListModel *m_issuesModel = nullptr;
|
||||
int m_totalRowCount = 0;
|
||||
int m_lastRequestedOffset = 0;
|
||||
QStringList m_userNames;
|
||||
QStringList m_versionDates;
|
||||
TaskTreeRunner m_taskTreeRunner;
|
||||
@@ -258,79 +288,71 @@ IssuesWidget::IssuesWidget(QWidget *parent)
|
||||
: QScrollArea(parent)
|
||||
{
|
||||
QWidget *widget = new QWidget(this);
|
||||
QVBoxLayout *layout = new QVBoxLayout(widget);
|
||||
// row with issue types (-> depending on choice, tables below change)
|
||||
// and a selectable range (start version, end version)
|
||||
// row with added/removed and some filters (assignee, path glob, (named filter))
|
||||
// table, columns depend on chosen issue type
|
||||
QHBoxLayout *top = new QHBoxLayout;
|
||||
layout->addLayout(top);
|
||||
m_typesButtonGroup = new QButtonGroup(this);
|
||||
m_typesButtonGroup->setExclusive(true);
|
||||
m_typesLayout = new QHBoxLayout;
|
||||
top->addLayout(m_typesLayout);
|
||||
top->addStretch(1);
|
||||
|
||||
m_versionStart = new QComboBox(this);
|
||||
m_versionStart->setMinimumContentsLength(25);
|
||||
top->addWidget(m_versionStart);
|
||||
connect(m_versionStart, &QComboBox::activated, this, &IssuesWidget::onSearchParameterChanged);
|
||||
|
||||
m_versionEnd = new QComboBox(this);
|
||||
m_versionEnd->setMinimumContentsLength(25);
|
||||
connect(m_versionStart, &QComboBox::activated, this, &IssuesWidget::onSearchParameterChanged);
|
||||
connect(m_versionEnd, &QComboBox::activated, this, &IssuesWidget::onSearchParameterChanged);
|
||||
top->addWidget(m_versionEnd);
|
||||
top->addStretch(1);
|
||||
m_filtersLayout = new QHBoxLayout;
|
||||
|
||||
m_addedFilter = new QPushButton(this);
|
||||
m_addedFilter->setIcon(trendIcon(1, 0));
|
||||
m_addedFilter->setText("0");
|
||||
m_addedFilter->setCheckable(true);
|
||||
m_filtersLayout->addWidget(m_addedFilter);
|
||||
m_removedFilter = new QPushButton(this);
|
||||
m_removedFilter->setIcon(trendIcon(0, 1));
|
||||
m_removedFilter->setText("0");
|
||||
m_removedFilter->setCheckable(true);
|
||||
m_filtersLayout->addWidget(m_removedFilter);
|
||||
connect(m_addedFilter, &QPushButton::clicked, this, [this](bool checked) {
|
||||
if (checked && m_removedFilter->isChecked())
|
||||
m_removedFilter->setChecked(false);
|
||||
onSearchParameterChanged();
|
||||
});
|
||||
|
||||
m_removedFilter = new QPushButton(this);
|
||||
m_removedFilter->setIcon(trendIcon(0, 1));
|
||||
m_removedFilter->setText("0");
|
||||
m_removedFilter->setCheckable(true);
|
||||
connect(m_removedFilter, &QPushButton::clicked, this, [this](bool checked) {
|
||||
if (checked && m_addedFilter->isChecked())
|
||||
m_addedFilter->setChecked(false);
|
||||
onSearchParameterChanged();
|
||||
});
|
||||
m_filtersLayout->addSpacing(1);
|
||||
|
||||
m_ownerFilter = new QComboBox(this);
|
||||
m_ownerFilter->setToolTip(Tr::tr("Owner"));
|
||||
m_ownerFilter->setMinimumContentsLength(25);
|
||||
connect(m_ownerFilter, &QComboBox::activated, this, &IssuesWidget::onSearchParameterChanged);
|
||||
m_filtersLayout->addWidget(m_ownerFilter);
|
||||
|
||||
m_pathGlobFilter = new QLineEdit(this);
|
||||
m_pathGlobFilter->setPlaceholderText(Tr::tr("Path globbing"));
|
||||
connect(m_pathGlobFilter, &QLineEdit::textEdited, this, &IssuesWidget::onSearchParameterChanged);
|
||||
m_filtersLayout->addWidget(m_pathGlobFilter);
|
||||
layout->addLayout(m_filtersLayout);
|
||||
|
||||
m_issuesView = new BaseTreeView(this);
|
||||
m_headerView = new IssueHeaderView(this);
|
||||
connect(m_headerView, &IssueHeaderView::sortTriggered,
|
||||
this, &IssuesWidget::onSearchParameterChanged);
|
||||
m_issuesView->setHeader(m_headerView);
|
||||
m_issuesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
m_issuesView->enableColumnHiding();
|
||||
m_issuesModel = new TreeModel(this);
|
||||
m_issuesModel = new DynamicListModel(this);
|
||||
m_issuesView->setModel(m_issuesModel);
|
||||
auto sb = m_issuesView->verticalScrollBar();
|
||||
if (QTC_GUARD(sb)) {
|
||||
connect(sb, &QAbstractSlider::valueChanged, sb, [this, sb](int value) {
|
||||
if (value >= sb->maximum() - 50) {
|
||||
if (m_issuesModel->rowCount() < m_totalRowCount)
|
||||
fetchMoreIssues();
|
||||
}
|
||||
});
|
||||
}
|
||||
layout->addWidget(m_issuesView);
|
||||
connect(m_issuesModel, &DynamicListModel::fetchRequested, this, &IssuesWidget::onFetchRequested);
|
||||
m_totalRows = new QLabel(Tr::tr("Total rows:"), this);
|
||||
QHBoxLayout *bottom = new QHBoxLayout;
|
||||
layout->addLayout(bottom);
|
||||
bottom->addStretch(1);
|
||||
bottom->addWidget(m_totalRows);
|
||||
|
||||
using namespace Layouting;
|
||||
Column {
|
||||
Row { m_typesLayout, st, m_versionStart, m_versionEnd, st },
|
||||
Row { m_addedFilter, m_removedFilter, Space(1), m_ownerFilter, m_pathGlobFilter },
|
||||
m_issuesView,
|
||||
Row { st, m_totalRows }
|
||||
}.attachTo(widget);
|
||||
|
||||
setWidget(widget);
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
setWidgetResizable(true);
|
||||
@@ -355,50 +377,81 @@ void IssuesWidget::updateUi()
|
||||
|
||||
if (info.issueKinds.size())
|
||||
m_currentPrefix = info.issueKinds.front().prefix;
|
||||
updateTableView();
|
||||
fetchTable();
|
||||
}
|
||||
|
||||
void IssuesWidget::setTableDto(const Dto::TableInfoDto &dto)
|
||||
static Qt::Alignment alignmentFromString(const QString &str)
|
||||
{
|
||||
m_currentTableInfo.emplace(dto);
|
||||
if (str == "left")
|
||||
return Qt::AlignLeft;
|
||||
if (str == "right")
|
||||
return Qt::AlignRight;
|
||||
if (str == "center")
|
||||
return Qt::AlignHCenter;
|
||||
return Qt::AlignLeft;
|
||||
}
|
||||
|
||||
void IssuesWidget::updateTable()
|
||||
{
|
||||
if (!m_currentTableInfo)
|
||||
return;
|
||||
|
||||
// update issues table layout - for now just simple approach
|
||||
TreeModel<> *issuesModel = new TreeModel(this);
|
||||
QStringList columnHeaders;
|
||||
QStringList hiddenColumns;
|
||||
for (const Dto::ColumnInfoDto &column : dto.columns) {
|
||||
QList<bool> sortableColumns;
|
||||
QList<int> columnWidths;
|
||||
QList<Qt::Alignment> alignments;
|
||||
for (const Dto::ColumnInfoDto &column : m_currentTableInfo->columns) {
|
||||
columnHeaders << column.header.value_or(column.key);
|
||||
if (!column.showByDefault)
|
||||
hiddenColumns << column.key;
|
||||
sortableColumns << column.canSort;
|
||||
columnWidths << column.width;
|
||||
alignments << alignmentFromString(column.alignment);
|
||||
}
|
||||
m_addedFilter->setText("0");
|
||||
m_removedFilter->setText("0");
|
||||
m_totalRows->setText(Tr::tr("Total rows:"));
|
||||
|
||||
issuesModel->setHeader(columnHeaders);
|
||||
|
||||
auto oldModel = m_issuesModel;
|
||||
m_issuesModel = issuesModel;
|
||||
m_issuesView->setModel(issuesModel);
|
||||
delete oldModel;
|
||||
m_issuesModel->clear();
|
||||
m_issuesModel->setHeader(columnHeaders);
|
||||
m_issuesModel->setAlignments(alignments);
|
||||
m_headerView->setSortableColumns(sortableColumns);
|
||||
m_headerView->setColumnWidths(columnWidths);
|
||||
int counter = 0;
|
||||
for (const QString &header : std::as_const(columnHeaders))
|
||||
m_issuesView->setColumnHidden(counter++, hiddenColumns.contains(header));
|
||||
m_headerView->resizeSections(QHeaderView::ResizeToContents);
|
||||
}
|
||||
|
||||
static Links linksForIssue(const std::map<QString, Dto::Any> &issueRow)
|
||||
static QList<LinkWithColumns> linksForIssue(const std::map<QString, Dto::Any> &issueRow,
|
||||
const std::vector<Dto::ColumnInfoDto> &columnInfos)
|
||||
{
|
||||
Links links;
|
||||
QList<LinkWithColumns> links;
|
||||
|
||||
auto end = issueRow.end();
|
||||
auto findAndAppend = [&links, &issueRow, &end](const QString &path, const QString &line) {
|
||||
auto findColumn = [columnInfos](const QString &columnKey) {
|
||||
int col = 0;
|
||||
for (auto it = columnInfos.cbegin(), end = columnInfos.cend(); it != end; ++it) {
|
||||
if (it->key == columnKey)
|
||||
return col;
|
||||
++col;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
auto findAndAppend = [&links, &issueRow, &findColumn, &end](const QString &path,
|
||||
const QString &line) {
|
||||
QList<int> columns;
|
||||
auto it = issueRow.find(path);
|
||||
if (it != end) {
|
||||
Link link{ FilePath::fromUserInput(it->second.getString()) };
|
||||
columns.append(findColumn(it->first));
|
||||
it = issueRow.find(line);
|
||||
if (it != end)
|
||||
if (it != end) {
|
||||
link.targetLine = it->second.getDouble();
|
||||
links.append(link);
|
||||
columns.append(findColumn(it->first));
|
||||
}
|
||||
links.append({link, columns});
|
||||
}
|
||||
};
|
||||
// do these always? or just for their "expected" kind
|
||||
@@ -411,11 +464,12 @@ static Links linksForIssue(const std::map<QString, Dto::Any> &issueRow)
|
||||
return links;
|
||||
}
|
||||
|
||||
void IssuesWidget::addIssues(const Dto::IssueTableDto &dto)
|
||||
void IssuesWidget::addIssues(const Dto::IssueTableDto &dto, int startRow)
|
||||
{
|
||||
QTC_ASSERT(m_currentTableInfo.has_value(), return);
|
||||
if (dto.totalRowCount.has_value()) {
|
||||
m_totalRowCount = dto.totalRowCount.value();
|
||||
m_issuesModel->setExpectedRowCount(m_totalRowCount);
|
||||
m_totalRows->setText(Tr::tr("Total rows:") + ' ' + QString::number(m_totalRowCount));
|
||||
}
|
||||
if (dto.totalAddedCount.has_value())
|
||||
@@ -425,21 +479,32 @@ void IssuesWidget::addIssues(const Dto::IssueTableDto &dto)
|
||||
|
||||
const std::vector<Dto::ColumnInfoDto> &tableColumns = m_currentTableInfo->columns;
|
||||
const std::vector<std::map<QString, Dto::Any>> &rows = dto.rows;
|
||||
QList<ListItem *> items;
|
||||
for (const auto &row : rows) {
|
||||
QString id;
|
||||
QStringList data;
|
||||
QStringList toolTips;
|
||||
for (const auto &column : tableColumns) {
|
||||
const auto it = row.find(column.key);
|
||||
if (it != row.end()) {
|
||||
QString value = anyToSimpleString(it->second);
|
||||
if (column.key == "id")
|
||||
if (column.key == "id") {
|
||||
value.prepend(m_currentPrefix);
|
||||
id = value;
|
||||
}
|
||||
toolTips << value;
|
||||
if (column.key.toLower().endsWith("path")) {
|
||||
const FilePath fp = FilePath::fromUserInput(value);
|
||||
value = QString("%1 [%2]").arg(fp.fileName(), fp.path());
|
||||
}
|
||||
data << value;
|
||||
}
|
||||
}
|
||||
IssueTreeItem *it = new IssueTreeItem(data, data);
|
||||
it->setLinks(linksForIssue(row));
|
||||
m_issuesModel->rootItem()->appendChild(it);
|
||||
IssueListItem *it = new IssueListItem(startRow++, id, data, toolTips);
|
||||
it->setLinks(linksForIssue(row, tableColumns));
|
||||
items.append(it);
|
||||
}
|
||||
m_issuesModel->setItems(items);
|
||||
}
|
||||
|
||||
void IssuesWidget::onSearchParameterChanged()
|
||||
@@ -448,10 +513,9 @@ void IssuesWidget::onSearchParameterChanged()
|
||||
m_removedFilter->setText("0");
|
||||
m_totalRows->setText(Tr::tr("Total rows:"));
|
||||
|
||||
m_issuesModel->rootItem()->removeChildren();
|
||||
m_issuesModel->clear();
|
||||
// new "first" time lookup
|
||||
m_totalRowCount = 0;
|
||||
m_lastRequestedOffset = 0;
|
||||
IssueListSearch search = searchFromUi();
|
||||
search.computeTotalRowCount = true;
|
||||
fetchIssues(search);
|
||||
@@ -503,7 +567,7 @@ void IssuesWidget::updateBasicProjectInfo(std::optional<Dto::ProjectInfoDto> inf
|
||||
button->setCheckable(true);
|
||||
connect(button, &QToolButton::clicked, this, [this, prefix = kind.prefix]{
|
||||
m_currentPrefix = prefix;
|
||||
updateTableView();
|
||||
fetchTable();
|
||||
});
|
||||
m_typesButtonGroup->addButton(button, ++buttonId);
|
||||
m_typesLayout->addWidget(button);
|
||||
@@ -535,27 +599,6 @@ void IssuesWidget::updateBasicProjectInfo(std::optional<Dto::ProjectInfoDto> inf
|
||||
m_versionStart->setCurrentIndex(m_versionDates.count() - 1);
|
||||
}
|
||||
|
||||
void IssuesWidget::updateTableView()
|
||||
{
|
||||
QTC_ASSERT(!m_currentPrefix.isEmpty(), return);
|
||||
// fetch table dto and apply, on done fetch first data for the selected issues
|
||||
const auto tableHandler = [this](const Dto::TableInfoDto &dto) { setTableDto(dto); };
|
||||
const auto setupHandler = [this](TaskTree *) { m_issuesView->showProgressIndicator(); };
|
||||
const auto doneHandler = [this](DoneWith result) {
|
||||
if (result == DoneWith::Error) {
|
||||
m_issuesView->hideProgressIndicator();
|
||||
return;
|
||||
}
|
||||
// first time lookup... should we cache and maybe represent old data?
|
||||
m_totalRowCount = 0;
|
||||
m_lastRequestedOffset = 0;
|
||||
IssueListSearch search = searchFromUi();
|
||||
search.computeTotalRowCount = true;
|
||||
fetchIssues(search);
|
||||
};
|
||||
m_taskTreeRunner.start(tableInfoRecipe(m_currentPrefix, tableHandler), setupHandler, doneHandler);
|
||||
}
|
||||
|
||||
void IssuesWidget::setFiltersEnabled(bool enabled)
|
||||
{
|
||||
m_addedFilter->setEnabled(enabled);
|
||||
@@ -581,143 +624,168 @@ IssueListSearch IssuesWidget::searchFromUi() const
|
||||
search.state = "added";
|
||||
else if (m_removedFilter->isChecked())
|
||||
search.state = "removed";
|
||||
if (int column = m_headerView->currentSortColumn() != -1) {
|
||||
QTC_ASSERT(m_currentTableInfo, return search);
|
||||
QTC_ASSERT((ulong)column < m_currentTableInfo->columns.size(), return search);
|
||||
search.sort = m_currentTableInfo->columns.at(m_headerView->currentSortColumn()).key
|
||||
+ (m_headerView->currentSortOrder() == SortOrder::Ascending ? " asc" : " desc");
|
||||
}
|
||||
|
||||
return search;
|
||||
}
|
||||
|
||||
void IssuesWidget::fetchTable()
|
||||
{
|
||||
QTC_ASSERT(!m_currentPrefix.isEmpty(), return);
|
||||
// fetch table dto and apply, on done fetch first data for the selected issues
|
||||
const auto tableHandler = [this](const Dto::TableInfoDto &dto) {
|
||||
m_currentTableInfo.emplace(dto);
|
||||
};
|
||||
const auto setupHandler = [this](TaskTree *) {
|
||||
m_totalRowCount = 0;
|
||||
m_currentTableInfo.reset();
|
||||
m_issuesView->showProgressIndicator();
|
||||
};
|
||||
const auto doneHandler = [this](DoneWith result) {
|
||||
if (result == DoneWith::Error) {
|
||||
m_issuesView->hideProgressIndicator();
|
||||
return;
|
||||
}
|
||||
// first time lookup... should we cache and maybe represent old data?
|
||||
updateTable();
|
||||
IssueListSearch search = searchFromUi();
|
||||
search.computeTotalRowCount = true;
|
||||
fetchIssues(search);
|
||||
};
|
||||
m_taskTreeRunner.start(tableInfoRecipe(m_currentPrefix, tableHandler), setupHandler, doneHandler);
|
||||
}
|
||||
|
||||
void IssuesWidget::fetchIssues(const IssueListSearch &search)
|
||||
{
|
||||
const auto issuesHandler = [this](const Dto::IssueTableDto &dto) { addIssues(dto); };
|
||||
const auto issuesHandler = [this, startRow = search.offset](const Dto::IssueTableDto &dto) {
|
||||
addIssues(dto, startRow);
|
||||
};
|
||||
const auto setupHandler = [this](TaskTree *) { m_issuesView->showProgressIndicator(); };
|
||||
const auto doneHandler = [this](DoneWith) { m_issuesView->hideProgressIndicator(); };
|
||||
m_taskTreeRunner.start(issueTableRecipe(search, issuesHandler), setupHandler, doneHandler);
|
||||
}
|
||||
|
||||
void IssuesWidget::fetchMoreIssues()
|
||||
void IssuesWidget::onFetchRequested(int startRow, int limit)
|
||||
{
|
||||
if (m_lastRequestedOffset == m_issuesModel->rowCount())
|
||||
if (m_taskTreeRunner.isRunning())
|
||||
return;
|
||||
|
||||
IssueListSearch search = searchFromUi();
|
||||
m_lastRequestedOffset = m_issuesModel->rowCount();
|
||||
search.offset = m_lastRequestedOffset;
|
||||
search.offset = startRow;
|
||||
search.limit = limit;
|
||||
fetchIssues(search);
|
||||
}
|
||||
|
||||
AxivionOutputPane::AxivionOutputPane(QObject *parent)
|
||||
: IOutputPane(parent)
|
||||
class AxivionOutputPane final : public IOutputPane
|
||||
{
|
||||
setId("Axivion");
|
||||
setDisplayName(Tr::tr("Axivion"));
|
||||
setPriorityInStatusBar(-50);
|
||||
public:
|
||||
explicit AxivionOutputPane(QObject *parent)
|
||||
: IOutputPane(parent)
|
||||
{
|
||||
setId("Axivion");
|
||||
setDisplayName(Tr::tr("Axivion"));
|
||||
setPriorityInStatusBar(-50);
|
||||
|
||||
m_outputWidget = new QStackedWidget;
|
||||
DashboardWidget *dashboardWidget = new DashboardWidget(m_outputWidget);
|
||||
m_outputWidget->addWidget(dashboardWidget);
|
||||
IssuesWidget *issuesWidget = new IssuesWidget(m_outputWidget);
|
||||
m_outputWidget->addWidget(issuesWidget);
|
||||
QTextBrowser *browser = new QTextBrowser(m_outputWidget);
|
||||
m_outputWidget->addWidget(browser);
|
||||
}
|
||||
m_outputWidget = new QStackedWidget;
|
||||
DashboardWidget *dashboardWidget = new DashboardWidget(m_outputWidget);
|
||||
m_outputWidget->addWidget(dashboardWidget);
|
||||
IssuesWidget *issuesWidget = new IssuesWidget(m_outputWidget);
|
||||
m_outputWidget->addWidget(issuesWidget);
|
||||
|
||||
AxivionOutputPane::~AxivionOutputPane()
|
||||
{
|
||||
if (!m_outputWidget->parent())
|
||||
delete m_outputWidget;
|
||||
}
|
||||
QPalette pal = m_outputWidget->palette();
|
||||
pal.setColor(QPalette::Window, creatorTheme()->color(Theme::Color::BackgroundColorNormal));
|
||||
m_outputWidget->setPalette(pal);
|
||||
|
||||
QWidget *AxivionOutputPane::outputWidget(QWidget *parent)
|
||||
{
|
||||
if (m_outputWidget)
|
||||
m_outputWidget->setParent(parent);
|
||||
else
|
||||
QTC_CHECK(false);
|
||||
return m_outputWidget;
|
||||
}
|
||||
m_showDashboard = new QToolButton(m_outputWidget);
|
||||
m_showDashboard->setIcon(Icons::HOME_TOOLBAR.icon());
|
||||
m_showDashboard->setToolTip(Tr::tr("Show dashboard"));
|
||||
m_showDashboard->setCheckable(true);
|
||||
m_showDashboard->setChecked(true);
|
||||
connect(m_showDashboard, &QToolButton::clicked, this, [this] {
|
||||
QTC_ASSERT(m_outputWidget, return);
|
||||
m_outputWidget->setCurrentIndex(0);
|
||||
});
|
||||
|
||||
QList<QWidget *> AxivionOutputPane::toolBarWidgets() const
|
||||
{
|
||||
QList<QWidget *> buttons;
|
||||
auto showDashboard = new QToolButton(m_outputWidget);
|
||||
showDashboard->setIcon(Icons::HOME_TOOLBAR.icon());
|
||||
showDashboard->setToolTip(Tr::tr("Show dashboard"));
|
||||
connect(showDashboard, &QToolButton::clicked, this, [this]{
|
||||
QTC_ASSERT(m_outputWidget, return);
|
||||
m_outputWidget->setCurrentIndex(0);
|
||||
});
|
||||
buttons.append(showDashboard);
|
||||
auto showIssues = new QToolButton(m_outputWidget);
|
||||
showIssues->setIcon(Icons::ZOOM_TOOLBAR.icon());
|
||||
showIssues->setToolTip(Tr::tr("Search for issues"));
|
||||
connect(showIssues, &QToolButton::clicked, this, [this]{
|
||||
QTC_ASSERT(m_outputWidget, return);
|
||||
m_outputWidget->setCurrentIndex(1);
|
||||
if (auto issues = static_cast<IssuesWidget *>(m_outputWidget->widget(1)))
|
||||
issues->updateUi();
|
||||
});
|
||||
buttons.append(showIssues);
|
||||
return buttons;
|
||||
}
|
||||
m_showIssues = new QToolButton(m_outputWidget);
|
||||
m_showIssues->setIcon(Icons::ZOOM_TOOLBAR.icon());
|
||||
m_showIssues->setToolTip(Tr::tr("Search for issues"));
|
||||
m_showIssues->setCheckable(true);
|
||||
connect(m_showIssues, &QToolButton::clicked, this, [this] {
|
||||
QTC_ASSERT(m_outputWidget, return);
|
||||
m_outputWidget->setCurrentIndex(1);
|
||||
if (auto issues = static_cast<IssuesWidget *>(m_outputWidget->widget(1)))
|
||||
issues->updateUi();
|
||||
});
|
||||
|
||||
void AxivionOutputPane::clearContents()
|
||||
{
|
||||
}
|
||||
|
||||
void AxivionOutputPane::setFocus()
|
||||
{
|
||||
}
|
||||
|
||||
bool AxivionOutputPane::hasFocus() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AxivionOutputPane::canFocus() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AxivionOutputPane::canNavigate() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AxivionOutputPane::canNext() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AxivionOutputPane::canPrevious() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void AxivionOutputPane::goToNext()
|
||||
{
|
||||
}
|
||||
|
||||
void AxivionOutputPane::goToPrev()
|
||||
{
|
||||
}
|
||||
|
||||
void AxivionOutputPane::updateDashboard()
|
||||
{
|
||||
if (auto dashboard = static_cast<DashboardWidget *>(m_outputWidget->widget(0))) {
|
||||
dashboard->updateUi();
|
||||
m_outputWidget->setCurrentIndex(0);
|
||||
if (dashboard->hasProject())
|
||||
flash();
|
||||
connect(m_outputWidget, &QStackedWidget::currentChanged, this, [this](int idx) {
|
||||
m_showDashboard->setChecked(idx == 0);
|
||||
m_showIssues->setChecked(idx == 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void AxivionOutputPane::updateAndShowRule(const QString &ruleHtml)
|
||||
{
|
||||
if (auto browser = static_cast<QTextBrowser *>(m_outputWidget->widget(2))) {
|
||||
browser->setText(ruleHtml);
|
||||
if (!ruleHtml.isEmpty()) {
|
||||
m_outputWidget->setCurrentIndex(2);
|
||||
popup(IOutputPane::NoModeSwitch);
|
||||
~AxivionOutputPane()
|
||||
{
|
||||
if (!m_outputWidget->parent())
|
||||
delete m_outputWidget;
|
||||
}
|
||||
|
||||
QWidget *outputWidget(QWidget *parent) final
|
||||
{
|
||||
if (m_outputWidget)
|
||||
m_outputWidget->setParent(parent);
|
||||
else
|
||||
QTC_CHECK(false);
|
||||
return m_outputWidget;
|
||||
}
|
||||
|
||||
QList<QWidget *> toolBarWidgets() const final
|
||||
{
|
||||
return {m_showDashboard, m_showIssues};
|
||||
}
|
||||
|
||||
void clearContents() final {}
|
||||
void setFocus() final {}
|
||||
bool hasFocus() const final { return false; }
|
||||
bool canFocus() const final { return true; }
|
||||
bool canNavigate() const final { return true; }
|
||||
bool canNext() const final { return false; }
|
||||
bool canPrevious() const final { return false; }
|
||||
void goToNext() final {}
|
||||
void goToPrev() final {}
|
||||
|
||||
void updateDashboard()
|
||||
{
|
||||
if (auto dashboard = static_cast<DashboardWidget *>(m_outputWidget->widget(0))) {
|
||||
dashboard->updateUi();
|
||||
m_outputWidget->setCurrentIndex(0);
|
||||
if (dashboard->hasProject())
|
||||
flash();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QStackedWidget *m_outputWidget = nullptr;
|
||||
QToolButton *m_showDashboard = nullptr;
|
||||
QToolButton *m_showIssues = nullptr;
|
||||
};
|
||||
|
||||
|
||||
static QPointer<AxivionOutputPane> theAxivionOutputPane;
|
||||
|
||||
void setupAxivionOutputPane(QObject *guard)
|
||||
{
|
||||
theAxivionOutputPane = new AxivionOutputPane(guard);
|
||||
}
|
||||
|
||||
void updateDashboard()
|
||||
{
|
||||
QTC_ASSERT(theAxivionOutputPane, return);
|
||||
theAxivionOutputPane->updateDashboard();
|
||||
}
|
||||
|
||||
} // Axivion::Internal
|
||||
|
||||
@@ -3,38 +3,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <coreplugin/ioutputpane.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QStackedWidget;
|
||||
QT_END_NAMESPACE
|
||||
#include <QObject>
|
||||
|
||||
namespace Axivion::Internal {
|
||||
|
||||
class AxivionOutputPane : public Core::IOutputPane
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AxivionOutputPane(QObject *parent = nullptr);
|
||||
~AxivionOutputPane();
|
||||
|
||||
// IOutputPane interface
|
||||
QWidget *outputWidget(QWidget *parent) override;
|
||||
QList<QWidget *> toolBarWidgets() const override;
|
||||
void clearContents() override;
|
||||
void setFocus() override;
|
||||
bool hasFocus() const override;
|
||||
bool canFocus() const override;
|
||||
bool canNavigate() const override;
|
||||
bool canNext() const override;
|
||||
bool canPrevious() const override;
|
||||
void goToNext() override;
|
||||
void goToPrev() override;
|
||||
|
||||
void updateDashboard();
|
||||
void updateAndShowRule(const QString &ruleHtml);
|
||||
private:
|
||||
QStackedWidget *m_outputWidget = nullptr;
|
||||
};
|
||||
void setupAxivionOutputPane(QObject *guard);
|
||||
void updateDashboard();
|
||||
|
||||
} // Axivion::Internal
|
||||
|
||||
@@ -7,13 +7,16 @@
|
||||
#include "axivionprojectsettings.h"
|
||||
#include "axivionsettings.h"
|
||||
#include "axiviontr.h"
|
||||
#include "credentialquery.h"
|
||||
#include "dashboard/dto.h"
|
||||
#include "dashboard/error.h"
|
||||
|
||||
#include <coreplugin/editormanager/documentmodel.h>
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/inavigationwidgetfactory.h>
|
||||
#include <coreplugin/messagemanager.h>
|
||||
#include <coreplugin/navigationwidget.h>
|
||||
|
||||
#include <extensionsystem/iplugin.h>
|
||||
#include <extensionsystem/pluginmanager.h>
|
||||
@@ -31,19 +34,26 @@
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/async.h>
|
||||
#include <utils/checkablemessagebox.h>
|
||||
#include <utils/environment.h>
|
||||
#include <utils/networkaccessmanager.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
#include <QAction>
|
||||
#include <QDesktopServices>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QTextBrowser>
|
||||
#include <QTimer>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include <memory>
|
||||
|
||||
constexpr char AxivionTextMarkId[] = "AxivionTextMark";
|
||||
constexpr char s_axivionTextMarkId[] = "AxivionTextMark";
|
||||
constexpr char s_axivionKeychainService[] = "keychain.axivion.qtcreator";
|
||||
|
||||
using namespace Core;
|
||||
using namespace ProjectExplorer;
|
||||
@@ -96,6 +106,42 @@ QString anyToSimpleString(const Dto::Any &any)
|
||||
return {};
|
||||
}
|
||||
|
||||
static QString apiTokenDescription()
|
||||
{
|
||||
const QString ua = "Axivion" + QCoreApplication::applicationName() + "Plugin/"
|
||||
+ QCoreApplication::applicationVersion();
|
||||
QString user = Utils::qtcEnvironmentVariable("USERNAME");
|
||||
if (user.isEmpty())
|
||||
user = Utils::qtcEnvironmentVariable("USER");
|
||||
return "Automatically created by " + ua + " on " + user + "@" + QSysInfo::machineHostName();
|
||||
}
|
||||
|
||||
static QString credentialKey()
|
||||
{
|
||||
const auto escape = [](const QString &string) {
|
||||
QString escaped = string;
|
||||
return escaped.replace('\\', "\\\\").replace('@', "\\@");
|
||||
};
|
||||
return escape(settings().server.dashboard) + '@' + escape(settings().server.username);
|
||||
}
|
||||
|
||||
static DashboardInfo toDashboardInfo(const QUrl &source, const Dto::DashboardInfoDto &infoDto)
|
||||
{
|
||||
const QVersionNumber versionNumber = infoDto.dashboardVersionNumber
|
||||
? QVersionNumber::fromString(*infoDto.dashboardVersionNumber) : QVersionNumber();
|
||||
|
||||
QStringList projects;
|
||||
QHash<QString, QUrl> projectUrls;
|
||||
|
||||
if (infoDto.projects) {
|
||||
for (const Dto::ProjectReferenceDto &project : *infoDto.projects) {
|
||||
projects.push_back(project.name);
|
||||
projectUrls.insert(project.name, project.url);
|
||||
}
|
||||
}
|
||||
return {source, versionNumber, projects, projectUrls, infoDto.checkCredentialsUrl};
|
||||
}
|
||||
|
||||
QString IssueListSearch::toQuery() const
|
||||
{
|
||||
if (kind.isEmpty())
|
||||
@@ -118,18 +164,24 @@ QString IssueListSearch::toQuery() const
|
||||
QString::fromUtf8((QUrl::toPercentEncoding(owner)))));
|
||||
}
|
||||
if (!filter_path.isEmpty()) {
|
||||
result.append(QString("&filter_path=%1").arg(
|
||||
result.append(QString("&filter_any path=%1").arg(
|
||||
QString::fromUtf8(QUrl::toPercentEncoding(filter_path))));
|
||||
}
|
||||
if (!state.isEmpty())
|
||||
result.append(QString("&state=%1").arg(state));
|
||||
if (computeTotalRowCount)
|
||||
result.append("&computeTotalRowCount=true");
|
||||
if (!sort.isEmpty())
|
||||
result.append(QString("&sort=%1").arg(
|
||||
QString::fromUtf8(QUrl::toPercentEncoding(sort))));
|
||||
return result;
|
||||
}
|
||||
|
||||
enum class ServerAccess { Unknown, NoAuthorization, WithAuthorization };
|
||||
|
||||
class AxivionPluginPrivate : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AxivionPluginPrivate();
|
||||
void handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
|
||||
@@ -141,9 +193,18 @@ public:
|
||||
void clearAllMarks();
|
||||
void handleIssuesForFile(const Dto::FileViewDto &fileView);
|
||||
void fetchIssueInfo(const QString &id);
|
||||
void setIssueDetails(const QString &issueDetailsHtml);
|
||||
void handleAnchorClicked(const QUrl &url);
|
||||
|
||||
signals:
|
||||
void issueDetailsChanged(const QString &issueDetailsHtml);
|
||||
|
||||
public:
|
||||
// TODO: Should be set to Unknown on server address change in settings.
|
||||
ServerAccess m_serverAccess = ServerAccess::Unknown;
|
||||
// TODO: Should be cleared on username change in settings.
|
||||
std::optional<QByteArray> m_apiToken;
|
||||
NetworkAccessManager m_networkAccessManager;
|
||||
AxivionOutputPane m_axivionOutputPane;
|
||||
std::optional<DashboardInfo> m_dashboardInfo;
|
||||
std::optional<Dto::ProjectInfoDto> m_currentProjectInfo;
|
||||
Project *m_project = nullptr;
|
||||
@@ -158,13 +219,16 @@ static AxivionPluginPrivate *dd = nullptr;
|
||||
class AxivionTextMark : public TextMark
|
||||
{
|
||||
public:
|
||||
AxivionTextMark(const FilePath &filePath, const Dto::LineMarkerDto &issue)
|
||||
: TextMark(filePath, issue.startLine, {Tr::tr("Axivion"), AxivionTextMarkId})
|
||||
AxivionTextMark(const FilePath &filePath, const Dto::LineMarkerDto &issue,
|
||||
std::optional<Theme::Color> color)
|
||||
: TextMark(filePath, issue.startLine, {"Axivion", s_axivionTextMarkId})
|
||||
{
|
||||
const QString markText = issue.description;
|
||||
const QString id = issue.kind + QString::number(issue.id.value_or(-1));
|
||||
setToolTip(id + markText);
|
||||
setToolTip(id + '\n' + markText);
|
||||
setIcon(iconForIssue(issue.kind));
|
||||
if (color)
|
||||
setColor(*color);
|
||||
setPriority(TextMark::NormalPriority);
|
||||
setLineAnnotation(markText);
|
||||
setActionsProvider([id] {
|
||||
@@ -247,7 +311,7 @@ void AxivionPluginPrivate::onStartupProjectChanged(Project *project)
|
||||
m_project = project;
|
||||
clearAllMarks();
|
||||
m_currentProjectInfo = {};
|
||||
m_axivionOutputPane.updateDashboard();
|
||||
updateDashboard();
|
||||
|
||||
if (!m_project)
|
||||
return;
|
||||
@@ -259,10 +323,7 @@ void AxivionPluginPrivate::onStartupProjectChanged(Project *project)
|
||||
|
||||
static QUrl urlForProject(const QString &projectName)
|
||||
{
|
||||
QString dashboard = settings().server.dashboard;
|
||||
if (!dashboard.endsWith(QLatin1Char('/')))
|
||||
dashboard += QLatin1Char('/');
|
||||
return QUrl(dashboard).resolved(QStringLiteral("api/projects/")).resolved(projectName);
|
||||
return QUrl(settings().server.dashboard).resolved(QString("api/projects/")).resolved(projectName);
|
||||
}
|
||||
|
||||
static constexpr int httpStatusCodeOk = 200;
|
||||
@@ -271,28 +332,22 @@ constexpr char s_jsonContentType[] = "application/json";
|
||||
|
||||
static Group fetchHtmlRecipe(const QUrl &url, const std::function<void(const QByteArray &)> &handler)
|
||||
{
|
||||
struct StorageData
|
||||
{
|
||||
QByteArray credentials;
|
||||
};
|
||||
// TODO: Refactor so that it's a common code with fetchDataRecipe().
|
||||
const auto onQuerySetup = [url](NetworkQuery &query) {
|
||||
if (dd->m_serverAccess == ServerAccess::Unknown)
|
||||
return SetupResult::StopWithError; // TODO: start authorizationRecipe()?
|
||||
|
||||
const Storage<StorageData> storage;
|
||||
|
||||
const auto onCredentialSetup = [storage] {
|
||||
storage->credentials = QByteArrayLiteral("AxToken ") + settings().server.token.toUtf8();
|
||||
};
|
||||
|
||||
const auto onQuerySetup = [storage, url](NetworkQuery &query) {
|
||||
QNetworkRequest request(url);
|
||||
request.setRawHeader("Accept", s_htmlContentType);
|
||||
request.setRawHeader("Authorization", storage->credentials);
|
||||
if (dd->m_serverAccess == ServerAccess::WithAuthorization && dd->m_apiToken)
|
||||
request.setRawHeader("Authorization", "AxToken " + *dd->m_apiToken);
|
||||
const QByteArray ua = "Axivion" + QCoreApplication::applicationName().toUtf8() +
|
||||
"Plugin/" + QCoreApplication::applicationVersion().toUtf8();
|
||||
request.setRawHeader("X-Axivion-User-Agent", ua);
|
||||
query.setRequest(request);
|
||||
query.setNetworkAccessManager(&dd->m_networkAccessManager);
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
|
||||
const auto onQueryDone = [url, handler](const NetworkQuery &query, DoneWith doneWith) {
|
||||
QNetworkReply *reply = query.reply();
|
||||
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
@@ -309,125 +364,49 @@ static Group fetchHtmlRecipe(const QUrl &url, const std::function<void(const QBy
|
||||
}
|
||||
return DoneResult::Error;
|
||||
};
|
||||
|
||||
const Group recipe {
|
||||
storage,
|
||||
Sync(onCredentialSetup),
|
||||
NetworkQueryTask(onQuerySetup, onQueryDone),
|
||||
};
|
||||
|
||||
return recipe;
|
||||
return {NetworkQueryTask(onQuerySetup, onQueryDone)};
|
||||
}
|
||||
|
||||
template <typename DtoType>
|
||||
struct GetDtoStorage
|
||||
{
|
||||
QByteArray credential;
|
||||
QUrl url;
|
||||
std::optional<QByteArray> credential;
|
||||
std::optional<DtoType> dtoData;
|
||||
};
|
||||
|
||||
template <typename DtoType>
|
||||
static Group getDtoRecipe(const Storage<GetDtoStorage<DtoType>> &dtoStorage)
|
||||
{
|
||||
const Storage<QByteArray> storage;
|
||||
|
||||
const auto onNetworkQuerySetup = [dtoStorage](NetworkQuery &query) {
|
||||
QNetworkRequest request(dtoStorage->url);
|
||||
request.setRawHeader("Accept", s_jsonContentType);
|
||||
request.setRawHeader("Authorization", dtoStorage->credential);
|
||||
const QByteArray ua = "Axivion" + QCoreApplication::applicationName().toUtf8() +
|
||||
"Plugin/" + QCoreApplication::applicationVersion().toUtf8();
|
||||
request.setRawHeader("X-Axivion-User-Agent", ua);
|
||||
query.setRequest(request);
|
||||
query.setNetworkAccessManager(&dd->m_networkAccessManager);
|
||||
};
|
||||
|
||||
const auto onNetworkQueryDone = [storage](const NetworkQuery &query, DoneWith doneWith) {
|
||||
QNetworkReply *reply = query.reply();
|
||||
const QNetworkReply::NetworkError error = reply->error();
|
||||
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
const QString contentType = reply->header(QNetworkRequest::ContentTypeHeader)
|
||||
.toString()
|
||||
.split(';')
|
||||
.constFirst()
|
||||
.trimmed()
|
||||
.toLower();
|
||||
if (doneWith == DoneWith::Success && statusCode == httpStatusCodeOk
|
||||
&& contentType == s_jsonContentType) {
|
||||
*storage = reply->readAll();
|
||||
return DoneResult::Success;
|
||||
}
|
||||
|
||||
const auto getError = [&]() -> Error {
|
||||
if (contentType == s_jsonContentType) {
|
||||
try {
|
||||
return DashboardError(reply->url(), statusCode,
|
||||
reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(),
|
||||
Dto::ErrorDto::deserialize(reply->readAll()));
|
||||
} catch (const Dto::invalid_dto_exception &) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
if (statusCode != 0) {
|
||||
return HttpError(reply->url(), statusCode,
|
||||
reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(),
|
||||
QString::fromUtf8(reply->readAll())); // encoding?
|
||||
}
|
||||
return NetworkError(reply->url(), error, reply->errorString());
|
||||
};
|
||||
|
||||
MessageManager::writeFlashing(QStringLiteral("Axivion: %1").arg(getError().message()));
|
||||
return DoneResult::Error;
|
||||
};
|
||||
|
||||
const auto onDeserializeSetup = [storage](Async<DtoType> &task) {
|
||||
const auto deserialize = [](QPromise<DtoType> &promise, const QByteArray &input) {
|
||||
promise.addResult(DtoType::deserialize(input));
|
||||
};
|
||||
task.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
|
||||
task.setConcurrentCallData(deserialize, *storage);
|
||||
};
|
||||
|
||||
const auto onDeserializeDone = [dtoStorage](const Async<DtoType> &task, DoneWith doneWith) {
|
||||
if (doneWith == DoneWith::Success)
|
||||
dtoStorage->dtoData = task.future().result();
|
||||
};
|
||||
|
||||
const Group recipe {
|
||||
storage,
|
||||
NetworkQueryTask(onNetworkQuerySetup, onNetworkQueryDone),
|
||||
AsyncTask<DtoType>(onDeserializeSetup, onDeserializeDone)
|
||||
};
|
||||
return recipe;
|
||||
};
|
||||
|
||||
template <typename DtoType>
|
||||
struct PostDtoStorage
|
||||
{
|
||||
QByteArray credential;
|
||||
QUrl url;
|
||||
std::optional<QByteArray> credential;
|
||||
QByteArray csrfToken;
|
||||
QByteArray writeData;
|
||||
std::optional<DtoType> dtoData;
|
||||
};
|
||||
|
||||
template <typename DtoType>
|
||||
static Group postDtoRecipe(const Storage<PostDtoStorage<DtoType>> &dtoStorage)
|
||||
template <typename DtoType, template <typename> typename DtoStorageType>
|
||||
static Group dtoRecipe(const Storage<DtoStorageType<DtoType>> &dtoStorage)
|
||||
{
|
||||
const Storage<QByteArray> storage;
|
||||
|
||||
const auto onNetworkQuerySetup = [dtoStorage](NetworkQuery &query) {
|
||||
QNetworkRequest request(dtoStorage->url);
|
||||
request.setRawHeader("Accept", s_jsonContentType);
|
||||
request.setRawHeader("Authorization", dtoStorage->credential);
|
||||
if (dtoStorage->credential) // Unauthorized access otherwise
|
||||
request.setRawHeader("Authorization", *dtoStorage->credential);
|
||||
const QByteArray ua = "Axivion" + QCoreApplication::applicationName().toUtf8() +
|
||||
"Plugin/" + QCoreApplication::applicationVersion().toUtf8();
|
||||
request.setRawHeader("X-Axivion-User-Agent", ua);
|
||||
request.setRawHeader("AX-CSRF-Token", dtoStorage->csrfToken);
|
||||
|
||||
if constexpr (std::is_same_v<DtoStorageType<DtoType>, PostDtoStorage<DtoType>>) {
|
||||
request.setRawHeader("Content-Type", "application/json");
|
||||
request.setRawHeader("AX-CSRF-Token", dtoStorage->csrfToken);
|
||||
query.setWriteData(dtoStorage->writeData);
|
||||
query.setOperation(NetworkOperation::Post);
|
||||
}
|
||||
|
||||
query.setRequest(request);
|
||||
query.setWriteData(dtoStorage->writeData);
|
||||
query.setOperation(NetworkOperation::Post);
|
||||
query.setNetworkAccessManager(&dd->m_networkAccessManager);
|
||||
};
|
||||
|
||||
@@ -464,76 +443,220 @@ static Group postDtoRecipe(const Storage<PostDtoStorage<DtoType>> &dtoStorage)
|
||||
}
|
||||
return NetworkError(reply->url(), error, reply->errorString());
|
||||
};
|
||||
|
||||
MessageManager::writeFlashing(QStringLiteral("Axivion: %1").arg(getError().message()));
|
||||
MessageManager::writeDisrupting(QString("Axivion: %1").arg(getError().message()));
|
||||
return DoneResult::Error;
|
||||
};
|
||||
|
||||
const auto onDeserializeSetup = [storage](Async<DtoType> &task) {
|
||||
const auto deserialize = [](QPromise<DtoType> &promise, const QByteArray &input) {
|
||||
promise.addResult(DtoType::deserialize(input));
|
||||
const auto onDeserializeSetup = [storage](Async<expected_str<DtoType>> &task) {
|
||||
const auto deserialize = [](QPromise<expected_str<DtoType>> &promise, const QByteArray &input) {
|
||||
promise.addResult(DtoType::deserializeExpected(input));
|
||||
};
|
||||
task.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
|
||||
task.setConcurrentCallData(deserialize, *storage);
|
||||
};
|
||||
|
||||
const auto onDeserializeDone = [dtoStorage](const Async<DtoType> &task, DoneWith doneWith) {
|
||||
if (doneWith == DoneWith::Success)
|
||||
dtoStorage->dtoData = task.future().result();
|
||||
const auto onDeserializeDone = [dtoStorage](const Async<expected_str<DtoType>> &task,
|
||||
DoneWith doneWith) {
|
||||
if (doneWith == DoneWith::Success && task.isResultAvailable()) {
|
||||
const auto result = task.result();
|
||||
if (result) {
|
||||
dtoStorage->dtoData = *result;
|
||||
return DoneResult::Success;
|
||||
}
|
||||
MessageManager::writeFlashing(QString("Axivion: %1").arg(result.error()));
|
||||
} else {
|
||||
MessageManager::writeFlashing(QString("Axivion: %1")
|
||||
.arg(Tr::tr("Unknown Dto structure deserialization error.")));
|
||||
}
|
||||
return DoneResult::Error;
|
||||
};
|
||||
|
||||
const Group recipe {
|
||||
return {
|
||||
storage,
|
||||
NetworkQueryTask(onNetworkQuerySetup, onNetworkQueryDone),
|
||||
AsyncTask<DtoType>(onDeserializeSetup, onDeserializeDone)
|
||||
AsyncTask<expected_str<DtoType>>(onDeserializeSetup, onDeserializeDone)
|
||||
};
|
||||
return recipe;
|
||||
};
|
||||
}
|
||||
|
||||
static QString credentialOperationMessage(CredentialOperation operation)
|
||||
{
|
||||
switch (operation) {
|
||||
case CredentialOperation::Get:
|
||||
return Tr::tr("The ApiToken cannot be read in a secure way.");
|
||||
case CredentialOperation::Set:
|
||||
return Tr::tr("The ApiToken cannot be stored in a secure way.");
|
||||
case CredentialOperation::Delete:
|
||||
return Tr::tr("The ApiToken cannot be deleted in a secure way.");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static void handleCredentialError(const CredentialQuery &credential)
|
||||
{
|
||||
const QString keyChainMessage = credential.errorString().isEmpty() ? QString()
|
||||
: QString(" %1").arg(Tr::tr("Key chain message: \"%1\".").arg(credential.errorString()));
|
||||
MessageManager::writeFlashing(QString("Axivion: %1")
|
||||
.arg(credentialOperationMessage(credential.operation()) + keyChainMessage));
|
||||
}
|
||||
|
||||
static Group authorizationRecipe()
|
||||
{
|
||||
const Storage<GetDtoStorage<Dto::DashboardInfoDto>> unauthorizedDashboardStorage;
|
||||
const auto onUnauthorizedGroupSetup = [unauthorizedDashboardStorage] {
|
||||
if (dd->m_serverAccess != ServerAccess::NoAuthorization)
|
||||
return SetupResult::StopWithSuccess;
|
||||
|
||||
unauthorizedDashboardStorage->url = QUrl(settings().server.dashboard);
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
const auto onUnauthorizedGroupDone = [unauthorizedDashboardStorage] {
|
||||
if (unauthorizedDashboardStorage->dtoData) {
|
||||
dd->m_serverAccess = ServerAccess::NoAuthorization;
|
||||
dd->m_dashboardInfo = toDashboardInfo(settings().server.dashboard,
|
||||
*unauthorizedDashboardStorage->dtoData);
|
||||
} else {
|
||||
dd->m_serverAccess = ServerAccess::WithAuthorization;
|
||||
}
|
||||
return DoneResult::Success;
|
||||
};
|
||||
|
||||
const auto onCredentialLoopCondition = [](int) {
|
||||
return dd->m_serverAccess == ServerAccess::WithAuthorization && !dd->m_apiToken;
|
||||
};
|
||||
const auto onGetCredentialSetup = [](CredentialQuery &credential) {
|
||||
credential.setOperation(CredentialOperation::Get);
|
||||
credential.setService(s_axivionKeychainService);
|
||||
credential.setKey(credentialKey());
|
||||
};
|
||||
const auto onGetCredentialDone = [](const CredentialQuery &credential, DoneWith result) {
|
||||
if (result == DoneWith::Success)
|
||||
dd->m_apiToken = credential.data();
|
||||
else
|
||||
handleCredentialError(credential);
|
||||
// TODO: In case of an error we are multiplying the ApiTokens on Axivion server for each
|
||||
// Creator run, but at least things should continue to work OK in the current session.
|
||||
return DoneResult::Success;
|
||||
};
|
||||
|
||||
const Storage<QString> passwordStorage;
|
||||
const Storage<GetDtoStorage<Dto::DashboardInfoDto>> dashboardStorage;
|
||||
const auto onDashboardGroupSetup = [passwordStorage, dashboardStorage] {
|
||||
if (dd->m_apiToken)
|
||||
return SetupResult::StopWithSuccess;
|
||||
|
||||
bool ok = false;
|
||||
const QString text(Tr::tr("Enter the password for:\nDashboard: %1\nUser: %2")
|
||||
.arg(settings().server.dashboard, settings().server.username));
|
||||
*passwordStorage = QInputDialog::getText(ICore::mainWindow(),
|
||||
Tr::tr("Axivion Server Password"), text, QLineEdit::Password, {}, &ok);
|
||||
if (!ok)
|
||||
return SetupResult::StopWithError;
|
||||
|
||||
const QString credential = settings().server.username + ':' + *passwordStorage;
|
||||
dashboardStorage->credential = "Basic " + credential.toUtf8().toBase64();
|
||||
dashboardStorage->url = QUrl(settings().server.dashboard);
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
|
||||
const Storage<PostDtoStorage<Dto::ApiTokenInfoDto>> apiTokenStorage;
|
||||
const auto onApiTokenGroupSetup = [passwordStorage, dashboardStorage, apiTokenStorage] {
|
||||
if (!dashboardStorage->dtoData)
|
||||
return SetupResult::StopWithSuccess;
|
||||
|
||||
dd->m_dashboardInfo = toDashboardInfo(settings().server.dashboard,
|
||||
*dashboardStorage->dtoData);
|
||||
|
||||
const Dto::DashboardInfoDto &dashboardDto = *dashboardStorage->dtoData;
|
||||
if (!dashboardDto.userApiTokenUrl)
|
||||
return SetupResult::StopWithError;
|
||||
|
||||
apiTokenStorage->credential = dashboardStorage->credential;
|
||||
apiTokenStorage->url
|
||||
= QUrl(settings().server.dashboard).resolved(*dashboardDto.userApiTokenUrl);
|
||||
apiTokenStorage->csrfToken = dashboardDto.csrfToken.toUtf8();
|
||||
const Dto::ApiTokenCreationRequestDto requestDto{*passwordStorage, "IdePlugin",
|
||||
apiTokenDescription(), 0};
|
||||
apiTokenStorage->writeData = requestDto.serialize();
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
|
||||
const auto onSetCredentialSetup = [apiTokenStorage](CredentialQuery &credential) {
|
||||
if (!apiTokenStorage->dtoData || !apiTokenStorage->dtoData->token)
|
||||
return SetupResult::StopWithSuccess;
|
||||
|
||||
dd->m_apiToken = apiTokenStorage->dtoData->token->toUtf8();
|
||||
credential.setOperation(CredentialOperation::Set);
|
||||
credential.setService(s_axivionKeychainService);
|
||||
credential.setKey(credentialKey());
|
||||
credential.setData(*dd->m_apiToken);
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
const auto onSetCredentialDone = [](const CredentialQuery &credential) {
|
||||
handleCredentialError(credential);
|
||||
// TODO: In case of an error we are multiplying the ApiTokens on Axivion server for each
|
||||
// Creator run, but at least things should continue to work OK in the current session.
|
||||
return DoneResult::Success;
|
||||
};
|
||||
|
||||
return {
|
||||
Group {
|
||||
unauthorizedDashboardStorage,
|
||||
onGroupSetup(onUnauthorizedGroupSetup),
|
||||
dtoRecipe(unauthorizedDashboardStorage),
|
||||
onGroupDone(onUnauthorizedGroupDone)
|
||||
},
|
||||
Group {
|
||||
LoopUntil(onCredentialLoopCondition),
|
||||
CredentialQueryTask(onGetCredentialSetup, onGetCredentialDone),
|
||||
Group {
|
||||
passwordStorage,
|
||||
dashboardStorage,
|
||||
onGroupSetup(onDashboardGroupSetup),
|
||||
Group { // GET DashboardInfoDto
|
||||
finishAllAndSuccess,
|
||||
dtoRecipe(dashboardStorage)
|
||||
},
|
||||
Group { // POST ApiTokenCreationRequestDto, GET ApiTokenInfoDto.
|
||||
apiTokenStorage,
|
||||
onGroupSetup(onApiTokenGroupSetup),
|
||||
dtoRecipe(apiTokenStorage),
|
||||
CredentialQueryTask(onSetCredentialSetup, onSetCredentialDone, CallDoneIf::Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template<typename DtoType>
|
||||
static Group fetchDataRecipe(const QUrl &url, const std::function<void(const DtoType &)> &handler)
|
||||
{
|
||||
const Storage<GetDtoStorage<DtoType>> dtoStorage;
|
||||
|
||||
const auto onCredentialSetup = [dtoStorage, url] {
|
||||
dtoStorage->credential = QByteArrayLiteral("AxToken ") + settings().server.token.toUtf8();
|
||||
dtoStorage->url = url;
|
||||
};
|
||||
const auto onDtoSetup = [dtoStorage, url] {
|
||||
if (!dd->m_apiToken)
|
||||
return SetupResult::StopWithError;
|
||||
|
||||
dtoStorage->credential = "AxToken " + *dd->m_apiToken;
|
||||
dtoStorage->url = url;
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
const auto onDtoDone = [dtoStorage, handler] {
|
||||
if (dtoStorage->dtoData)
|
||||
handler(*dtoStorage->dtoData);
|
||||
};
|
||||
|
||||
const Group recipe {
|
||||
dtoStorage,
|
||||
Sync(onCredentialSetup),
|
||||
authorizationRecipe(),
|
||||
Group {
|
||||
getDtoRecipe(dtoStorage),
|
||||
dtoStorage,
|
||||
onGroupSetup(onDtoSetup),
|
||||
dtoRecipe(dtoStorage),
|
||||
onGroupDone(onDtoDone)
|
||||
}
|
||||
};
|
||||
|
||||
return recipe;
|
||||
}
|
||||
|
||||
static DashboardInfo toDashboardInfo(const QUrl &source, const Dto::DashboardInfoDto &infoDto)
|
||||
{
|
||||
const QVersionNumber versionNumber = infoDto.dashboardVersionNumber
|
||||
? QVersionNumber::fromString(*infoDto.dashboardVersionNumber) : QVersionNumber();
|
||||
|
||||
QStringList projects;
|
||||
QHash<QString, QUrl> projectUrls;
|
||||
|
||||
if (infoDto.projects) {
|
||||
for (const Dto::ProjectReferenceDto &project : *infoDto.projects) {
|
||||
projects.push_back(project.name);
|
||||
projectUrls.insert(project.name, project.url);
|
||||
}
|
||||
}
|
||||
return {source, versionNumber, projects, projectUrls, infoDto.checkCredentialsUrl};
|
||||
}
|
||||
|
||||
Group dashboardInfoRecipe(const DashboardInfoHandler &handler)
|
||||
{
|
||||
const auto onSetup = [handler] {
|
||||
@@ -549,17 +672,15 @@ Group dashboardInfoRecipe(const DashboardInfoHandler &handler)
|
||||
handler(make_unexpected(QString("Error"))); // TODO: Collect error message in the storage.
|
||||
};
|
||||
|
||||
const QUrl url(settings().server.dashboard);
|
||||
|
||||
const auto resultHandler = [handler, url](const Dto::DashboardInfoDto &data) {
|
||||
dd->m_dashboardInfo = toDashboardInfo(url, data);
|
||||
const auto resultHandler = [handler](const Dto::DashboardInfoDto &data) {
|
||||
dd->m_dashboardInfo = toDashboardInfo(settings().server.dashboard, data);
|
||||
if (handler)
|
||||
handler(*dd->m_dashboardInfo);
|
||||
};
|
||||
|
||||
const Group root {
|
||||
onGroupSetup(onSetup), // Stops if cache exists.
|
||||
fetchDataRecipe<Dto::DashboardInfoDto>(url, resultHandler),
|
||||
fetchDataRecipe<Dto::DashboardInfoDto>(settings().server.dashboard, resultHandler),
|
||||
onGroupDone(onDone, CallDoneIf::Error)
|
||||
};
|
||||
return root;
|
||||
@@ -575,7 +696,6 @@ Group issueTableRecipe(const IssueListSearch &search, const IssueTableHandler &h
|
||||
|
||||
const QUrl url = urlForProject(dd->m_currentProjectInfo.value().name + '/')
|
||||
.resolved(QString("issues" + query));
|
||||
|
||||
return fetchDataRecipe<Dto::IssueTableDto>(url, handler);
|
||||
}
|
||||
|
||||
@@ -594,10 +714,6 @@ Group issueHtmlRecipe(const QString &issueId, const HtmlHandler &handler)
|
||||
{
|
||||
QTC_ASSERT(dd->m_currentProjectInfo, return {}); // TODO: Call handler with unexpected?
|
||||
|
||||
QString dashboard = settings().server.dashboard;
|
||||
if (!dashboard.endsWith(QLatin1Char('/')))
|
||||
dashboard += QLatin1Char('/');
|
||||
|
||||
const QUrl url = urlForProject(dd->m_currentProjectInfo.value().name + '/')
|
||||
.resolved(QString("issues/"))
|
||||
.resolved(QString(issueId + '/'))
|
||||
@@ -614,21 +730,27 @@ void AxivionPluginPrivate::fetchProjectInfo(const QString &projectName)
|
||||
clearAllMarks();
|
||||
if (projectName.isEmpty()) {
|
||||
m_currentProjectInfo = {};
|
||||
m_axivionOutputPane.updateDashboard();
|
||||
updateDashboard();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto onTaskTreeSetup = [this, projectName](TaskTree &taskTree) {
|
||||
if (!m_dashboardInfo)
|
||||
if (!m_dashboardInfo) {
|
||||
MessageManager::writeDisrupting(QString("Axivion: %1")
|
||||
.arg(Tr::tr("Fetching DashboardInfo error.")));
|
||||
return SetupResult::StopWithError;
|
||||
}
|
||||
|
||||
const auto it = m_dashboardInfo->projectUrls.constFind(projectName);
|
||||
if (it == m_dashboardInfo->projectUrls.constEnd())
|
||||
if (it == m_dashboardInfo->projectUrls.constEnd()) {
|
||||
MessageManager::writeDisrupting(QString("Axivion: %1")
|
||||
.arg(Tr::tr("The DashboardInfo doesn't contain project \"%1\".").arg(projectName)));
|
||||
return SetupResult::StopWithError;
|
||||
}
|
||||
|
||||
const auto handler = [this](const Dto::ProjectInfoDto &data) {
|
||||
m_currentProjectInfo = data;
|
||||
m_axivionOutputPane.updateDashboard();
|
||||
updateDashboard();
|
||||
handleOpenedDocs();
|
||||
};
|
||||
|
||||
@@ -661,12 +783,19 @@ void AxivionPluginPrivate::fetchIssueInfo(const QString &id)
|
||||
const int idx = htmlText.indexOf("<div class=\"ax-issuedetails-table-container\">");
|
||||
if (idx >= 0)
|
||||
fixedHtml = "<html><body>" + htmlText.mid(idx);
|
||||
dd->m_axivionOutputPane.updateAndShowRule(QString::fromUtf8(fixedHtml));
|
||||
|
||||
NavigationWidget::activateSubWidget("Axivion.Issue", Side::Right);
|
||||
dd->setIssueDetails(QString::fromUtf8(fixedHtml));
|
||||
};
|
||||
|
||||
m_issueInfoRunner.start(issueHtmlRecipe(id, ruleHandler));
|
||||
}
|
||||
|
||||
void AxivionPluginPrivate::setIssueDetails(const QString &issueDetailsHtml)
|
||||
{
|
||||
emit issueDetailsChanged(issueDetailsHtml);
|
||||
}
|
||||
|
||||
void AxivionPluginPrivate::handleOpenedDocs()
|
||||
{
|
||||
const QList<IDocument *> openDocuments = DocumentModel::openedDocuments();
|
||||
@@ -687,7 +816,8 @@ void AxivionPluginPrivate::onDocumentOpened(IDocument *doc)
|
||||
return;
|
||||
|
||||
const FilePath filePath = doc->filePath().relativeChildPath(m_project->projectDirectory());
|
||||
QTC_ASSERT(!filePath.isEmpty(), return);
|
||||
if (filePath.isEmpty())
|
||||
return; // Empty is fine
|
||||
|
||||
const auto handler = [this](const Dto::FileViewDto &data) {
|
||||
if (data.lineMarkers.empty())
|
||||
@@ -718,7 +848,7 @@ void AxivionPluginPrivate::onDocumentClosed(IDocument *doc)
|
||||
|
||||
const TextMarks &marks = document->marks();
|
||||
for (TextMark *mark : marks) {
|
||||
if (mark->category().id == AxivionTextMarkId)
|
||||
if (mark->category().id == s_axivionTextMarkId)
|
||||
delete mark;
|
||||
}
|
||||
}
|
||||
@@ -733,15 +863,76 @@ void AxivionPluginPrivate::handleIssuesForFile(const Dto::FileViewDto &fileView)
|
||||
return;
|
||||
|
||||
const FilePath filePath = project->projectDirectory().pathAppended(fileView.fileName);
|
||||
|
||||
std::optional<Theme::Color> color = std::nullopt;
|
||||
if (settings().highlightMarks())
|
||||
color.emplace(Theme::Color(Theme::Bookmarks_TextMarkColor)); // FIXME!
|
||||
for (const Dto::LineMarkerDto &marker : std::as_const(fileView.lineMarkers)) {
|
||||
// FIXME the line location can be wrong (even the whole issue could be wrong)
|
||||
// depending on whether this line has been changed since the last axivion run and the
|
||||
// current state of the file - some magic has to happen here
|
||||
new AxivionTextMark(filePath, marker);
|
||||
new AxivionTextMark(filePath, marker, color);
|
||||
}
|
||||
}
|
||||
|
||||
void AxivionPluginPrivate::handleAnchorClicked(const QUrl &url)
|
||||
{
|
||||
QTC_ASSERT(dd, return);
|
||||
QTC_ASSERT(dd->m_project, return);
|
||||
if (!url.scheme().isEmpty()) {
|
||||
const QString detail = Tr::tr("The activated link appears to be external.\n"
|
||||
"Do you want to open \"%1\" with its default application?")
|
||||
.arg(url.toString());
|
||||
const QMessageBox::StandardButton pressed
|
||||
= CheckableMessageBox::question(Core::ICore::dialogParent(),
|
||||
Tr::tr("Open External Links"),
|
||||
detail,
|
||||
Key("AxivionOpenExternalLinks"));
|
||||
if (pressed == QMessageBox::Yes)
|
||||
QDesktopServices::openUrl(url);
|
||||
return;
|
||||
}
|
||||
const QUrlQuery query(url);
|
||||
if (query.isEmpty())
|
||||
return;
|
||||
Link link;
|
||||
if (const QString path = query.queryItemValue("filename", QUrl::FullyDecoded); !path.isEmpty())
|
||||
link.targetFilePath = m_project->projectDirectory().pathAppended(path);
|
||||
if (const QString line = query.queryItemValue("line"); !line.isEmpty())
|
||||
link.targetLine = line.toInt();
|
||||
// column entry is wrong - so, ignore it
|
||||
if (link.hasValidTarget() && link.targetFilePath.exists())
|
||||
EditorManager::openEditorAt(link);
|
||||
}
|
||||
|
||||
class AxivionIssueWidgetFactory final : public INavigationWidgetFactory
|
||||
{
|
||||
public:
|
||||
AxivionIssueWidgetFactory()
|
||||
{
|
||||
setDisplayName(Tr::tr("Axivion"));
|
||||
setId("Axivion.Issue");
|
||||
setPriority(555);
|
||||
}
|
||||
|
||||
NavigationView createWidget() final
|
||||
{
|
||||
QTC_ASSERT(dd, return {});
|
||||
QTextBrowser *browser = new QTextBrowser;
|
||||
browser->setOpenLinks(false);
|
||||
NavigationView view;
|
||||
view.widget = browser;
|
||||
connect(dd, &AxivionPluginPrivate::issueDetailsChanged, browser, &QTextBrowser::setHtml);
|
||||
connect(browser, &QTextBrowser::anchorClicked,
|
||||
dd, &AxivionPluginPrivate::handleAnchorClicked);
|
||||
return view;
|
||||
}
|
||||
};
|
||||
|
||||
void setupAxivionIssueWidgetFactory()
|
||||
{
|
||||
static AxivionIssueWidgetFactory issueWidgetFactory;
|
||||
}
|
||||
|
||||
class AxivionPlugin final : public ExtensionSystem::IPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -756,9 +947,12 @@ class AxivionPlugin final : public ExtensionSystem::IPlugin
|
||||
|
||||
void initialize() final
|
||||
{
|
||||
setupAxivionOutputPane(this);
|
||||
|
||||
dd = new AxivionPluginPrivate;
|
||||
|
||||
AxivionProjectSettings::setupProjectPanel();
|
||||
setupAxivionIssueWidgetFactory();
|
||||
|
||||
connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
|
||||
dd, &AxivionPluginPrivate::onStartupProjectChanged);
|
||||
@@ -769,6 +963,12 @@ class AxivionPlugin final : public ExtensionSystem::IPlugin
|
||||
}
|
||||
};
|
||||
|
||||
void fetchIssueInfo(const QString &id)
|
||||
{
|
||||
QTC_ASSERT(dd, return);
|
||||
dd->fetchIssueInfo(id);
|
||||
}
|
||||
|
||||
} // Axivion::Internal
|
||||
|
||||
#include "axivionplugin.moc"
|
||||
|
||||
@@ -31,6 +31,7 @@ struct IssueListSearch
|
||||
QString versionEnd;
|
||||
QString owner;
|
||||
QString filter_path;
|
||||
QString sort;
|
||||
int offset = 0;
|
||||
int limit = 150;
|
||||
bool computeTotalRowCount = false;
|
||||
@@ -72,6 +73,7 @@ bool handleCertificateIssue();
|
||||
|
||||
QIcon iconForIssue(const QString &prefix);
|
||||
QString anyToSimpleString(const Dto::Any &any);
|
||||
void fetchIssueInfo(const QString &id);
|
||||
|
||||
} // Axivion::Internal
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <solutions/tasking/tasktreerunner.h>
|
||||
|
||||
#include <utils/infolabel.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <QPushButton>
|
||||
@@ -117,34 +118,33 @@ AxivionProjectSettingsWidget::AxivionProjectSettingsWidget(Project *project)
|
||||
setUseGlobalSettingsCheckBoxVisible(false);
|
||||
setUseGlobalSettingsLabelVisible(true);
|
||||
setGlobalSettingsId("Axivion.Settings.General"); // FIXME move id to constants
|
||||
// setup ui
|
||||
auto verticalLayout = new QVBoxLayout(this);
|
||||
verticalLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_linkedProject = new QLabel(this);
|
||||
verticalLayout->addWidget(m_linkedProject);
|
||||
|
||||
m_dashboardProjects = new QTreeWidget(this);
|
||||
m_dashboardProjects->setHeaderHidden(true);
|
||||
m_dashboardProjects->setRootIsDecorated(false);
|
||||
verticalLayout->addWidget(new QLabel(Tr::tr("Dashboard projects:")));
|
||||
verticalLayout->addWidget(m_dashboardProjects);
|
||||
|
||||
m_infoLabel = new InfoLabel(this);
|
||||
m_infoLabel->setVisible(false);
|
||||
verticalLayout->addWidget(m_infoLabel);
|
||||
|
||||
auto horizontalLayout = new QHBoxLayout;
|
||||
horizontalLayout->setContentsMargins(0, 0, 0, 0);
|
||||
m_fetchProjects = new QPushButton(Tr::tr("Fetch Projects"));
|
||||
horizontalLayout->addWidget(m_fetchProjects);
|
||||
|
||||
m_link = new QPushButton(Tr::tr("Link Project"));
|
||||
m_link->setEnabled(false);
|
||||
horizontalLayout->addWidget(m_link);
|
||||
|
||||
m_unlink = new QPushButton(Tr::tr("Unlink Project"));
|
||||
m_unlink->setEnabled(false);
|
||||
horizontalLayout->addWidget(m_unlink);
|
||||
verticalLayout->addLayout(horizontalLayout);
|
||||
|
||||
using namespace Layouting;
|
||||
Column {
|
||||
noMargin,
|
||||
m_linkedProject,
|
||||
Tr::tr("Dashboard projects:"),
|
||||
m_dashboardProjects,
|
||||
m_infoLabel,
|
||||
Row { m_fetchProjects, m_link, m_unlink, st }
|
||||
}.attachTo(this);
|
||||
|
||||
connect(m_dashboardProjects, &QTreeWidget::itemSelectionChanged,
|
||||
this, &AxivionProjectSettingsWidget::updateEnabledStates);
|
||||
@@ -220,8 +220,7 @@ void AxivionProjectSettingsWidget::updateUi()
|
||||
|
||||
void AxivionProjectSettingsWidget::updateEnabledStates()
|
||||
{
|
||||
const bool hasDashboardSettings = !settings().server.dashboard.isEmpty()
|
||||
&& !settings().server.token.isEmpty();
|
||||
const bool hasDashboardSettings = !settings().server.dashboard.isEmpty();
|
||||
const bool linked = !m_projectSettings->dashboardProjectName().isEmpty();
|
||||
const bool linkable = m_dashboardProjects->topLevelItemCount()
|
||||
&& !m_dashboardProjects->selectedItems().isEmpty();
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <utils/id.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/stringutils.h>
|
||||
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
@@ -27,8 +28,7 @@ namespace Axivion::Internal {
|
||||
|
||||
bool AxivionServer::operator==(const AxivionServer &other) const
|
||||
{
|
||||
return id == other.id && dashboard == other.dashboard && username == other.username
|
||||
&& description == other.description && token == other.token;
|
||||
return id == other.id && dashboard == other.dashboard && username == other.username;
|
||||
}
|
||||
|
||||
bool AxivionServer::operator!=(const AxivionServer &other) const
|
||||
@@ -42,11 +42,15 @@ QJsonObject AxivionServer::toJson() const
|
||||
result.insert("id", id.toString());
|
||||
result.insert("dashboard", dashboard);
|
||||
result.insert("username", username);
|
||||
result.insert("description", description);
|
||||
result.insert("token", token);
|
||||
return result;
|
||||
}
|
||||
|
||||
static QString fixUrl(const QString &url)
|
||||
{
|
||||
const QString trimmed = Utils::trimBack(url, ' ');
|
||||
return trimmed.endsWith('/') ? trimmed : trimmed + '/';
|
||||
}
|
||||
|
||||
AxivionServer AxivionServer::fromJson(const QJsonObject &json)
|
||||
{
|
||||
const AxivionServer invalidServer;
|
||||
@@ -59,14 +63,7 @@ AxivionServer AxivionServer::fromJson(const QJsonObject &json)
|
||||
const QJsonValue username = json.value("username");
|
||||
if (username == QJsonValue::Undefined)
|
||||
return invalidServer;
|
||||
const QJsonValue description = json.value("description");
|
||||
if (description == QJsonValue::Undefined)
|
||||
return invalidServer;
|
||||
const QJsonValue token = json.value("token");
|
||||
if (token == QJsonValue::Undefined)
|
||||
return invalidServer;
|
||||
return {Id::fromString(id.toString()), dashboard.toString(), username.toString(),
|
||||
description.toString(), token.toString()};
|
||||
return {Id::fromString(id.toString()), fixUrl(dashboard.toString()), username.toString()};
|
||||
}
|
||||
|
||||
static FilePath tokensFilePath()
|
||||
@@ -109,6 +106,10 @@ AxivionSettings::AxivionSettings()
|
||||
{
|
||||
setSettingsGroup("Axivion");
|
||||
|
||||
highlightMarks.setSettingsKey("HighlightMarks");
|
||||
highlightMarks.setLabelText(Tr::tr("Highlight marks"));
|
||||
highlightMarks.setToolTip(Tr::tr("Marks issues on the scroll bar."));
|
||||
highlightMarks.setDefaultValue(false);
|
||||
AspectContainer::readSettings();
|
||||
|
||||
server = readTokenFile(tokensFilePath());
|
||||
@@ -161,8 +162,6 @@ private:
|
||||
Id m_id;
|
||||
StringAspect m_dashboardUrl;
|
||||
StringAspect m_username;
|
||||
StringAspect m_description;
|
||||
StringAspect m_token;
|
||||
BoolAspect m_valid;
|
||||
};
|
||||
|
||||
@@ -181,23 +180,12 @@ DashboardSettingsWidget::DashboardSettingsWidget(Mode mode, QWidget *parent, QPu
|
||||
m_username.setDisplayStyle(labelStyle);
|
||||
m_username.setPlaceHolderText(Tr::tr("User name"));
|
||||
|
||||
m_description.setLabelText(Tr::tr("Description:"));
|
||||
m_description.setDisplayStyle(labelStyle);
|
||||
m_description.setPlaceHolderText(Tr::tr("Non-empty description"));
|
||||
|
||||
m_token.setLabelText(Tr::tr("Access token:"));
|
||||
m_token.setDisplayStyle(labelStyle);
|
||||
m_token.setPlaceHolderText(Tr::tr("IDE Access Token"));
|
||||
m_token.setVisible(mode == Edit);
|
||||
|
||||
using namespace Layouting;
|
||||
|
||||
Form {
|
||||
m_dashboardUrl, br,
|
||||
m_username, br,
|
||||
m_description, br,
|
||||
m_token, br,
|
||||
mode == Edit ? normalMargin : noMargin
|
||||
noMargin
|
||||
}.attachTo(this);
|
||||
|
||||
if (mode == Edit) {
|
||||
@@ -208,8 +196,6 @@ DashboardSettingsWidget::DashboardSettingsWidget(Mode mode, QWidget *parent, QPu
|
||||
};
|
||||
connect(&m_dashboardUrl, &BaseAspect::changed, this, checkValidity);
|
||||
connect(&m_username, &BaseAspect::changed, this, checkValidity);
|
||||
connect(&m_description, &BaseAspect::changed, this, checkValidity);
|
||||
connect(&m_token, &BaseAspect::changed, this, checkValidity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,10 +206,8 @@ AxivionServer DashboardSettingsWidget::dashboardServer() const
|
||||
result.id = m_id;
|
||||
else
|
||||
result.id = m_mode == Edit ? Id::fromName(QUuid::createUuid().toByteArray()) : m_id;
|
||||
result.dashboard = m_dashboardUrl();
|
||||
result.dashboard = fixUrl(m_dashboardUrl());
|
||||
result.username = m_username();
|
||||
result.description = m_description();
|
||||
result.token = m_token();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -232,13 +216,11 @@ void DashboardSettingsWidget::setDashboardServer(const AxivionServer &server)
|
||||
m_id = server.id;
|
||||
m_dashboardUrl.setValue(server.dashboard);
|
||||
m_username.setValue(server.username);
|
||||
m_description.setValue(server.description);
|
||||
m_token.setValue(server.token);
|
||||
}
|
||||
|
||||
bool DashboardSettingsWidget::isValid() const
|
||||
{
|
||||
return !m_token().isEmpty() && !m_description().isEmpty() && isUrlValid(m_dashboardUrl());
|
||||
return isUrlValid(m_dashboardUrl());
|
||||
}
|
||||
|
||||
class AxivionSettingsWidget : public IOptionsPageWidget
|
||||
@@ -262,10 +244,15 @@ AxivionSettingsWidget::AxivionSettingsWidget()
|
||||
m_dashboardDisplay = new DashboardSettingsWidget(DashboardSettingsWidget::Display, this);
|
||||
m_dashboardDisplay->setDashboardServer(settings().server);
|
||||
m_edit = new QPushButton(Tr::tr("Edit..."), this);
|
||||
Row {
|
||||
Form {
|
||||
m_dashboardDisplay, br,
|
||||
}, Column { m_edit, st }
|
||||
Column {
|
||||
Row {
|
||||
Form {
|
||||
m_dashboardDisplay, br
|
||||
}, st,
|
||||
Column { m_edit },
|
||||
},
|
||||
Space(10), br,
|
||||
Row { settings().highlightMarks }, st
|
||||
}.attachTo(this);
|
||||
|
||||
connect(m_edit, &QPushButton::clicked, this, &AxivionSettingsWidget::showEditServerDialog);
|
||||
|
||||
@@ -26,8 +26,6 @@ public:
|
||||
Utils::Id id;
|
||||
QString dashboard;
|
||||
QString username;
|
||||
QString description;
|
||||
QString token;
|
||||
|
||||
bool validateCert = true;
|
||||
};
|
||||
@@ -40,6 +38,7 @@ public:
|
||||
void toSettings() const;
|
||||
|
||||
AxivionServer server; // shall we have more than one?
|
||||
Utils::BoolAspect highlightMarks{this};
|
||||
};
|
||||
|
||||
AxivionSettings &settings();
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Axivion {
|
||||
|
||||
struct Tr
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(Axivion)
|
||||
Q_DECLARE_TR_FUNCTIONS(QtC::Axivion)
|
||||
};
|
||||
|
||||
} // Axivion
|
||||
|
||||
@@ -24,7 +24,8 @@ void CredentialQueryTaskAdapter::start()
|
||||
}
|
||||
case CredentialOperation::Set: {
|
||||
WritePasswordJob *writer = new WritePasswordJob(task()->m_service);
|
||||
writer->setBinaryData(task()->m_data);
|
||||
if (task()->m_data)
|
||||
writer->setBinaryData(*task()->m_data);
|
||||
job = writer;
|
||||
break;
|
||||
}
|
||||
@@ -38,11 +39,12 @@ void CredentialQueryTaskAdapter::start()
|
||||
m_guard.reset(job);
|
||||
|
||||
connect(job, &Job::finished, this, [this, reader](Job *job) {
|
||||
if (job->error() != NoError)
|
||||
const bool success = job->error() == NoError || job->error() == EntryNotFound;
|
||||
if (!success)
|
||||
task()->m_errorString = job->errorString();
|
||||
else if (reader)
|
||||
else if (reader && job->error() == NoError)
|
||||
task()->m_data = reader->binaryData();
|
||||
emit done(toDoneResult(job->error() == NoError));
|
||||
emit done(toDoneResult(success));
|
||||
m_guard.release()->deleteLater();
|
||||
});
|
||||
job->start();
|
||||
|
||||
@@ -17,14 +17,15 @@ public:
|
||||
void setKey(const QString &key) { m_key = key; }
|
||||
void setData(const QByteArray &data) { m_data = data; }
|
||||
|
||||
QByteArray data() const { return m_data; }
|
||||
CredentialOperation operation() const { return m_operation; }
|
||||
std::optional<QByteArray> data() const { return m_data; }
|
||||
QString errorString() const { return m_errorString; }
|
||||
|
||||
private:
|
||||
CredentialOperation m_operation = CredentialOperation::Get;
|
||||
QString m_service;
|
||||
QString m_key;
|
||||
QByteArray m_data; // Used for input when Set and for output when Get.
|
||||
std::optional<QByteArray> m_data; // Used for input when Set and for output when Get.
|
||||
QString m_errorString;
|
||||
friend class CredentialQueryTaskAdapter;
|
||||
};
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* <AxivionSuiteRepo>/projects/libs/dashboard_cpp_api/generator/generate_dashboard_cpp_api.py
|
||||
*/
|
||||
|
||||
#include <utils/expected.h>
|
||||
|
||||
#include <QAnyStringView>
|
||||
#include <QByteArray>
|
||||
#include <QHashFunctions>
|
||||
@@ -70,6 +72,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static Any deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<Any> deserializeExpected(const QByteArray &json);
|
||||
|
||||
Any();
|
||||
|
||||
Any(QString value);
|
||||
@@ -171,6 +175,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static AnalyzedFileDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<AnalyzedFileDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
AnalyzedFileDto(
|
||||
QString path,
|
||||
std::optional<bool> isSystemHeader,
|
||||
@@ -219,6 +225,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws std::range_error
|
||||
static ApiTokenType strToEnum(QAnyStringView str);
|
||||
|
||||
static std::optional<ApiTokenType> strToOptionalEnum(QAnyStringView str);
|
||||
|
||||
static QLatin1String enumToStr(ApiTokenType e);
|
||||
|
||||
ApiTokenTypeMeta() = delete;
|
||||
@@ -245,6 +253,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static ChangePasswordFormDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<ChangePasswordFormDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
ChangePasswordFormDto(
|
||||
QString currentPassword,
|
||||
QString newPassword
|
||||
@@ -295,6 +305,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws std::range_error
|
||||
static ColumnType strToEnum(QAnyStringView str);
|
||||
|
||||
static std::optional<ColumnType> strToOptionalEnum(QAnyStringView str);
|
||||
|
||||
static QLatin1String enumToStr(ColumnType e);
|
||||
|
||||
ColumnTypeMeta() = delete;
|
||||
@@ -332,6 +344,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static ColumnTypeOptionDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<ColumnTypeOptionDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
ColumnTypeOptionDto(
|
||||
QString key,
|
||||
std::optional<QString> displayName,
|
||||
@@ -356,6 +370,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static CommentRequestDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<CommentRequestDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
CommentRequestDto(
|
||||
QString text
|
||||
);
|
||||
@@ -384,6 +400,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static CsrfTokenDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<CsrfTokenDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
CsrfTokenDto(
|
||||
QString csrfToken
|
||||
);
|
||||
@@ -427,6 +445,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static EntityDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<EntityDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
EntityDto(
|
||||
QString id,
|
||||
QString name,
|
||||
@@ -544,6 +564,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static ErrorDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<ErrorDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
ErrorDto(
|
||||
std::optional<QString> dashboardVersionNumber,
|
||||
QString type,
|
||||
@@ -613,6 +635,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static IssueCommentDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<IssueCommentDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
IssueCommentDto(
|
||||
QString username,
|
||||
QString userDisplayName,
|
||||
@@ -654,6 +678,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws std::range_error
|
||||
static IssueKind strToEnum(QAnyStringView str);
|
||||
|
||||
static std::optional<IssueKind> strToOptionalEnum(QAnyStringView str);
|
||||
|
||||
static QLatin1String enumToStr(IssueKind e);
|
||||
|
||||
IssueKindMeta() = delete;
|
||||
@@ -690,6 +716,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws std::range_error
|
||||
static IssueKindForNamedFilterCreation strToEnum(QAnyStringView str);
|
||||
|
||||
static std::optional<IssueKindForNamedFilterCreation> strToOptionalEnum(QAnyStringView str);
|
||||
|
||||
static QLatin1String enumToStr(IssueKindForNamedFilterCreation e);
|
||||
|
||||
IssueKindForNamedFilterCreationMeta() = delete;
|
||||
@@ -759,6 +787,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static IssueSourceLocationDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<IssueSourceLocationDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
IssueSourceLocationDto(
|
||||
QString fileName,
|
||||
std::optional<QString> role,
|
||||
@@ -795,6 +825,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static IssueTagDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<IssueTagDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
IssueTagDto(
|
||||
QString tag,
|
||||
QString color
|
||||
@@ -851,6 +883,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static IssueTagTypeDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<IssueTagTypeDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
IssueTagTypeDto(
|
||||
QString id,
|
||||
std::optional<QString> text,
|
||||
@@ -893,6 +927,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws std::range_error
|
||||
static MessageSeverity strToEnum(QAnyStringView str);
|
||||
|
||||
static std::optional<MessageSeverity> strToOptionalEnum(QAnyStringView str);
|
||||
|
||||
static QLatin1String enumToStr(MessageSeverity e);
|
||||
|
||||
MessageSeverityMeta() = delete;
|
||||
@@ -937,6 +973,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static MetricDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<MetricDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
MetricDto(
|
||||
QString name,
|
||||
QString displayName,
|
||||
@@ -996,6 +1034,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static MetricValueTableRowDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<MetricValueTableRowDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
MetricValueTableRowDto(
|
||||
QString metric,
|
||||
std::optional<QString> path,
|
||||
@@ -1033,6 +1073,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws std::range_error
|
||||
static NamedFilterType strToEnum(QAnyStringView str);
|
||||
|
||||
static std::optional<NamedFilterType> strToOptionalEnum(QAnyStringView str);
|
||||
|
||||
static QLatin1String enumToStr(NamedFilterType e);
|
||||
|
||||
NamedFilterTypeMeta() = delete;
|
||||
@@ -1062,6 +1104,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static NamedFilterVisibilityDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<NamedFilterVisibilityDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
NamedFilterVisibilityDto(
|
||||
std::optional<std::vector<QString>> groups
|
||||
);
|
||||
@@ -1089,6 +1133,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static ProjectReferenceDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<ProjectReferenceDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
ProjectReferenceDto(
|
||||
QString name,
|
||||
QString url
|
||||
@@ -1127,6 +1173,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static RuleDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<RuleDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
RuleDto(
|
||||
QString name,
|
||||
QString original_name,
|
||||
@@ -1157,6 +1205,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws std::range_error
|
||||
static SortDirection strToEnum(QAnyStringView str);
|
||||
|
||||
static std::optional<SortDirection> strToOptionalEnum(QAnyStringView str);
|
||||
|
||||
static QLatin1String enumToStr(SortDirection e);
|
||||
|
||||
SortDirectionMeta() = delete;
|
||||
@@ -1187,6 +1237,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws std::range_error
|
||||
static TableCellAlignment strToEnum(QAnyStringView str);
|
||||
|
||||
static std::optional<TableCellAlignment> strToOptionalEnum(QAnyStringView str);
|
||||
|
||||
static QLatin1String enumToStr(TableCellAlignment e);
|
||||
|
||||
TableCellAlignmentMeta() = delete;
|
||||
@@ -1218,6 +1270,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static ToolsVersionDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<ToolsVersionDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
ToolsVersionDto(
|
||||
QString name,
|
||||
QString number,
|
||||
@@ -1253,6 +1307,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws std::range_error
|
||||
static UserRefType strToEnum(QAnyStringView str);
|
||||
|
||||
static std::optional<UserRefType> strToOptionalEnum(QAnyStringView str);
|
||||
|
||||
static QLatin1String enumToStr(UserRefType e);
|
||||
|
||||
UserRefTypeMeta() = delete;
|
||||
@@ -1284,6 +1340,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static VersionKindCountDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<VersionKindCountDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
VersionKindCountDto(
|
||||
qint32 Total,
|
||||
qint32 Added,
|
||||
@@ -1392,6 +1450,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static AnalysisVersionDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<AnalysisVersionDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
AnalysisVersionDto(
|
||||
QString date,
|
||||
std::optional<QString> label,
|
||||
@@ -1450,6 +1510,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static ApiTokenCreationRequestDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<ApiTokenCreationRequestDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
ApiTokenCreationRequestDto(
|
||||
QString password,
|
||||
QString type,
|
||||
@@ -1568,6 +1630,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static ApiTokenInfoDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<ApiTokenInfoDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
ApiTokenInfoDto(
|
||||
QString id,
|
||||
QString url,
|
||||
@@ -1693,6 +1757,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static ColumnInfoDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<ColumnInfoDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
ColumnInfoDto(
|
||||
QString key,
|
||||
std::optional<QString> header,
|
||||
@@ -1726,7 +1792,7 @@ namespace Axivion::Internal::Dto
|
||||
|
||||
void setAlignmentEnum(TableCellAlignment newValue);
|
||||
|
||||
// Throws std::range_error
|
||||
// Throws std::range_error
|
||||
ColumnType getTypeEnum() const;
|
||||
|
||||
std::optional<ColumnType> getOptionalTypeEnum() const;
|
||||
@@ -1859,6 +1925,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static DashboardInfoDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<DashboardInfoDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
DashboardInfoDto(
|
||||
std::optional<QString> mainUrl,
|
||||
QString dashboardVersion,
|
||||
@@ -1895,6 +1963,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static IssueCommentListDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<IssueCommentListDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
IssueCommentListDto(
|
||||
std::vector<IssueCommentDto> comments
|
||||
);
|
||||
@@ -1929,6 +1999,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static IssueKindInfoDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<IssueKindInfoDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
IssueKindInfoDto(
|
||||
QString prefix,
|
||||
QString niceSingularName,
|
||||
@@ -1966,6 +2038,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static IssueTagTypeListDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<IssueTagTypeListDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
IssueTagTypeListDto(
|
||||
std::vector<IssueTagTypeDto> tags
|
||||
);
|
||||
@@ -2056,6 +2130,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static LineMarkerDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<LineMarkerDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
LineMarkerDto(
|
||||
QString kind,
|
||||
std::optional<qint64> id,
|
||||
@@ -2115,6 +2191,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static RepositoryUpdateMessageDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<RepositoryUpdateMessageDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
RepositoryUpdateMessageDto(
|
||||
QString severity,
|
||||
QString message
|
||||
@@ -2150,6 +2228,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static RuleListDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<RuleListDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
RuleListDto(
|
||||
std::vector<RuleDto> rules
|
||||
);
|
||||
@@ -2179,6 +2259,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static SortInfoDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<SortInfoDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
SortInfoDto(
|
||||
QString key,
|
||||
QString direction
|
||||
@@ -2235,6 +2317,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static UserRefDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<UserRefDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
UserRefDto(
|
||||
QString name,
|
||||
QString displayName,
|
||||
@@ -2279,6 +2363,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static AnalyzedFileListDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<AnalyzedFileListDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
AnalyzedFileListDto(
|
||||
AnalysisVersionDto version,
|
||||
std::vector<AnalyzedFileDto> rows
|
||||
@@ -2307,6 +2393,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static EntityListDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<EntityListDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
EntityListDto(
|
||||
std::optional<AnalysisVersionDto> version,
|
||||
std::vector<EntityDto> entities
|
||||
@@ -2355,6 +2443,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static FileViewDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<FileViewDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
FileViewDto(
|
||||
QString fileName,
|
||||
std::optional<QString> version,
|
||||
@@ -2418,6 +2508,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static IssueDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<IssueDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
IssueDto(
|
||||
QString kind,
|
||||
qint64 id,
|
||||
@@ -2532,6 +2624,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static IssueTableDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<IssueTableDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
IssueTableDto(
|
||||
std::optional<AnalysisVersionDto> startVersion,
|
||||
AnalysisVersionDto endVersion,
|
||||
@@ -2566,6 +2660,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static MetricListDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<MetricListDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
MetricListDto(
|
||||
std::optional<AnalysisVersionDto> version,
|
||||
std::vector<MetricDto> metrics
|
||||
@@ -2617,6 +2713,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static MetricValueRangeDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<MetricValueRangeDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
MetricValueRangeDto(
|
||||
AnalysisVersionDto startVersion,
|
||||
AnalysisVersionDto endVersion,
|
||||
@@ -2650,6 +2748,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static MetricValueTableDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<MetricValueTableDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
MetricValueTableDto(
|
||||
std::vector<ColumnInfoDto> columns,
|
||||
std::vector<MetricValueTableRowDto> rows
|
||||
@@ -2712,6 +2812,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static NamedFilterCreateDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<NamedFilterCreateDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
NamedFilterCreateDto(
|
||||
QString displayName,
|
||||
QString kind,
|
||||
@@ -2834,6 +2936,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static NamedFilterInfoDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<NamedFilterInfoDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
NamedFilterInfoDto(
|
||||
QString key,
|
||||
QString displayName,
|
||||
@@ -2924,6 +3028,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static NamedFilterUpdateDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<NamedFilterUpdateDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
NamedFilterUpdateDto(
|
||||
std::optional<QString> name,
|
||||
std::optional<std::map<QString, QString>> filters,
|
||||
@@ -2983,6 +3089,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static ProjectInfoDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<ProjectInfoDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
ProjectInfoDto(
|
||||
QString name,
|
||||
std::optional<QString> issueFilterHelp,
|
||||
@@ -3024,6 +3132,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static RepositoryUpdateResponseDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<RepositoryUpdateResponseDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
RepositoryUpdateResponseDto(
|
||||
std::vector<RepositoryUpdateMessageDto> messages,
|
||||
bool hasErrors,
|
||||
@@ -3091,6 +3201,8 @@ namespace Axivion::Internal::Dto
|
||||
// Throws Axivion::Internal::Dto::invalid_dto_exception
|
||||
static TableInfoDto deserialize(const QByteArray &json);
|
||||
|
||||
static Utils::expected_str<TableInfoDto> deserializeExpected(const QByteArray &json);
|
||||
|
||||
TableInfoDto(
|
||||
QString tableDataUri,
|
||||
std::optional<QString> issueBaseViewUri,
|
||||
|
||||
194
src/plugins/axivion/dynamiclistmodel.cpp
Normal 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
|
||||
69
src/plugins/axivion/dynamiclistmodel.h
Normal 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
|
||||
BIN
src/plugins/axivion/images/sortAsc.png
Normal file
|
After Width: | Height: | Size: 113 B |
BIN
src/plugins/axivion/images/sortAsc@2x.png
Normal file
|
After Width: | Height: | Size: 137 B |
BIN
src/plugins/axivion/images/sortDesc.png
Normal file
|
After Width: | Height: | Size: 118 B |
BIN
src/plugins/axivion/images/sortDesc@2x.png
Normal file
|
After Width: | Height: | Size: 142 B |
141
src/plugins/axivion/issueheaderview.cpp
Normal 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
|
||||
44
src/plugins/axivion/issueheaderview.h
Normal 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
|
||||
@@ -294,10 +294,7 @@ ClangModelManagerSupport::ClangModelManagerSupport()
|
||||
new ClangdQuickFixFactory(); // memory managed by CppEditor::g_cppQuickFixFactories
|
||||
}
|
||||
|
||||
ClangModelManagerSupport::~ClangModelManagerSupport()
|
||||
{
|
||||
m_generatorSynchronizer.waitForFinished();
|
||||
}
|
||||
ClangModelManagerSupport::~ClangModelManagerSupport() = default;
|
||||
|
||||
void ClangModelManagerSupport::followSymbol(const CursorInEditor &data,
|
||||
const LinkHandler &processLinkCallback,
|
||||
|
||||
@@ -94,10 +94,10 @@ private:
|
||||
void scheduleClientRestart(ClangdClient *client);
|
||||
static ClangdClient *clientWithProject(const ProjectExplorer::Project *project);
|
||||
|
||||
Utils::FutureSynchronizer m_generatorSynchronizer;
|
||||
QList<QPointer<ClangdClient>> m_clientsToRestart;
|
||||
QTimer * const m_clientRestartTimer;
|
||||
QHash<Utils::FilePath, QString> m_potentialShadowDocuments;
|
||||
Utils::FutureSynchronizer m_generatorSynchronizer; // Keep me last
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "cmakeprojectconstants.h"
|
||||
#include "cmakeprojectimporter.h"
|
||||
#include "cmakeprojectmanagertr.h"
|
||||
#include "presetsmacros.h"
|
||||
|
||||
#include <coreplugin/icontext.h>
|
||||
#include <projectexplorer/buildconfiguration.h>
|
||||
@@ -301,6 +302,18 @@ void CMakeProject::readPresets()
|
||||
|
||||
m_presetsData = combinePresets(cmakePresetsData, cmakeUserPresetsData);
|
||||
setupBuildPresets(m_presetsData);
|
||||
|
||||
for (const auto &configPreset : m_presetsData.configurePresets) {
|
||||
if (configPreset.hidden.value())
|
||||
continue;
|
||||
|
||||
if (configPreset.condition) {
|
||||
if (!CMakePresets::Macros::evaluatePresetCondition(configPreset, projectFilePath()))
|
||||
continue;
|
||||
}
|
||||
m_presetsData.havePresets = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool CMakeProject::setupTarget(Target *t)
|
||||
|
||||
@@ -201,8 +201,6 @@ FilePaths CMakeProjectImporter::presetCandidates()
|
||||
}
|
||||
}
|
||||
|
||||
m_hasCMakePresets = !candidates.isEmpty();
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
@@ -223,7 +221,7 @@ Target *CMakeProjectImporter::preferredTarget(const QList<Target *> &possibleTar
|
||||
|
||||
bool CMakeProjectImporter::filter(ProjectExplorer::Kit *k) const
|
||||
{
|
||||
if (!m_hasCMakePresets)
|
||||
if (!m_project->presetsData().havePresets)
|
||||
return true;
|
||||
|
||||
const auto presetConfigItem = CMakeConfigurationKitAspect::cmakePresetConfigItem(k);
|
||||
|
||||
@@ -49,7 +49,6 @@ private:
|
||||
|
||||
const CMakeProject *m_project;
|
||||
Utils::TemporaryDirectory m_presetsTempDir;
|
||||
bool m_hasCMakePresets = false;
|
||||
};
|
||||
|
||||
#ifdef WITH_TESTS
|
||||
|
||||
@@ -93,16 +93,16 @@ CMakeSpecificSettings::CMakeSpecificSettings()
|
||||
"UseJunctionsForSourceAndBuildDirectories");
|
||||
useJunctionsForSourceAndBuildDirectories.setDefaultValue(false);
|
||||
useJunctionsForSourceAndBuildDirectories.setLabelText(::CMakeProjectManager::Tr::tr(
|
||||
"Use Junctions for CMake configuration and build operations"));
|
||||
"Use junctions for CMake configuration and build operations"));
|
||||
useJunctionsForSourceAndBuildDirectories.setVisible(Utils::HostOsInfo().isWindowsHost());
|
||||
useJunctionsForSourceAndBuildDirectories.setToolTip(::CMakeProjectManager::Tr::tr(
|
||||
"Create and use junctions for the source and build directories. This helps to overcome "
|
||||
"Create and use junctions for the source and build directories to overcome "
|
||||
"issues with long paths on Windows.<br><br>"
|
||||
"They are stored under <tt>C:\\ProgramData\\QtCreator\\Links</tt> (overridable via "
|
||||
"<tt>QTC_CMAKE_JUNCTIONS_DIR</tt> environment variable).<br><br>"
|
||||
"With <tt>QTC_CMAKE_JUNCTIONS_HASH_LENGTH</tt> the MD5 hash key length can be shortened "
|
||||
"Junctions are stored under <tt>C:\\ProgramData\\QtCreator\\Links</tt> (overridable via "
|
||||
"the <tt>QTC_CMAKE_JUNCTIONS_DIR</tt> environment variable).<br><br>"
|
||||
"With <tt>QTC_CMAKE_JUNCTIONS_HASH_LENGTH</tt>, you can shorten the MD5 hash key length "
|
||||
"to a value smaller than the default length value of 32.<br><br>"
|
||||
"They are used for CMake configure, build and install operations."));
|
||||
"Junctions are used for CMake configure, build and install operations."));
|
||||
|
||||
readSettings();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <coreplugin/icore.h>
|
||||
|
||||
#include <projectexplorer/buildsystem.h>
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
#include <projectexplorer/projecttree.h>
|
||||
#include <projectexplorer/target.h>
|
||||
|
||||
@@ -69,6 +70,7 @@ typedef struct _REPARSE_DATA_BUFFER {
|
||||
#endif
|
||||
|
||||
using namespace Core;
|
||||
using namespace ProjectExplorer;
|
||||
using namespace Utils;
|
||||
|
||||
namespace CMakeProjectManager {
|
||||
@@ -451,8 +453,19 @@ FilePath CMakeToolManager::mappedFilePath(const FilePath &path)
|
||||
if (path.needsDevice())
|
||||
return path;
|
||||
|
||||
Internal::settings();
|
||||
if (!Internal::settings().useJunctionsForSourceAndBuildDirectories())
|
||||
auto project = ProjectManager::startupProject();
|
||||
auto environment = Environment::systemEnvironment();
|
||||
if (project)
|
||||
environment.modify(project->additionalEnvironment());
|
||||
const bool enableJunctions
|
||||
= QVariant(
|
||||
environment.value_or("QTC_CMAKE_USE_JUNCTIONS",
|
||||
Internal::settings().useJunctionsForSourceAndBuildDirectories()
|
||||
? "1"
|
||||
: "0"))
|
||||
.toBool();
|
||||
|
||||
if (!enableJunctions)
|
||||
return path;
|
||||
|
||||
if (!d->m_junctionsDir.isDir())
|
||||
@@ -583,14 +596,17 @@ CMakeToolManagerPrivate::CMakeToolManagerPrivate()
|
||||
m_junctionsDir = FilePath::fromString(*std::min_element(locations.begin(), locations.end()))
|
||||
.pathAppended("QtCreator/Links");
|
||||
|
||||
if (Utils::qtcEnvironmentVariableIsSet("QTC_CMAKE_JUNCTIONS_DIR")) {
|
||||
m_junctionsDir = FilePath::fromUserInput(
|
||||
Utils::qtcEnvironmentVariable("QTC_CMAKE_JUNCTIONS_DIR"));
|
||||
}
|
||||
if (Utils::qtcEnvironmentVariableIsSet("QTC_CMAKE_JUNCTIONS_HASH_LENGTH")) {
|
||||
auto project = ProjectManager::startupProject();
|
||||
auto environment = Environment::systemEnvironment();
|
||||
if (project)
|
||||
environment.modify(project->additionalEnvironment());
|
||||
|
||||
if (environment.hasKey("QTC_CMAKE_JUNCTIONS_DIR"))
|
||||
m_junctionsDir = FilePath::fromUserInput(environment.value("QTC_CMAKE_JUNCTIONS_DIR"));
|
||||
|
||||
if (environment.hasKey("QTC_CMAKE_JUNCTIONS_HASH_LENGTH")) {
|
||||
bool ok = false;
|
||||
const int hashLength
|
||||
= Utils::qtcEnvironmentVariableIntValue("QTC_CMAKE_JUNCTIONS_HASH_LENGTH", &ok);
|
||||
const int hashLength = environment.value("QTC_CMAKE_JUNCTIONS_HASH_LENGTH").toInt(&ok);
|
||||
if (ok && hashLength >= 4 && hashLength < 32)
|
||||
m_junctionsHashLength = hashLength;
|
||||
}
|
||||
|
||||
@@ -140,6 +140,7 @@ class PresetsData
|
||||
{
|
||||
public:
|
||||
int version = 0;
|
||||
bool havePresets = false;
|
||||
QVersionNumber cmakeMinimimRequired;
|
||||
QHash<QString, QString> vendor;
|
||||
std::optional<QStringList> include;
|
||||
|
||||
@@ -332,11 +332,7 @@ CompilationDatabaseBuildSystem::CompilationDatabaseBuildSystem(Target *target)
|
||||
this, &CompilationDatabaseBuildSystem::updateDeploymentData);
|
||||
}
|
||||
|
||||
CompilationDatabaseBuildSystem::~CompilationDatabaseBuildSystem()
|
||||
{
|
||||
m_parserWatcher.cancel();
|
||||
m_parserWatcher.waitForFinished();
|
||||
}
|
||||
CompilationDatabaseBuildSystem::~CompilationDatabaseBuildSystem() = default;
|
||||
|
||||
void CompilationDatabaseBuildSystem::triggerParsing()
|
||||
{
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
|
||||
#include <utils/filesystemwatcher.h>
|
||||
|
||||
#include <QFutureWatcher>
|
||||
|
||||
namespace ProjectExplorer {
|
||||
class Kit;
|
||||
class ProjectUpdater;
|
||||
@@ -51,7 +49,6 @@ public:
|
||||
void updateDeploymentData();
|
||||
void buildTreeAndProjectParts();
|
||||
|
||||
QFutureWatcher<void> m_parserWatcher;
|
||||
std::unique_ptr<ProjectExplorer::ProjectUpdater> m_cppCodeModelUpdater;
|
||||
MimeBinaryCache m_mimeBinaryCache;
|
||||
QByteArray m_projectFileHash;
|
||||
|
||||
@@ -8,9 +8,12 @@
|
||||
|
||||
#include <coreplugin/progressmanager/progressmanager.h>
|
||||
|
||||
#include <extensionsystem/pluginmanager.h>
|
||||
|
||||
#include <projectexplorer/treescanner.h>
|
||||
|
||||
#include <utils/async.h>
|
||||
#include <utils/futuresynchronizer.h>
|
||||
#include <utils/mimeutils.h>
|
||||
|
||||
#include <QCryptographicHash>
|
||||
@@ -187,6 +190,7 @@ void CompilationDbParser::start()
|
||||
"CompilationDatabase.Parse");
|
||||
++m_runningParserJobs;
|
||||
m_parserWatcher.setFuture(future);
|
||||
ExtensionSystem::PluginManager::futureSynchronizer()->addFuture(future);
|
||||
}
|
||||
|
||||
void CompilationDbParser::stop()
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
#include <utils/infobar.h>
|
||||
#include <utils/macroexpander.h>
|
||||
#include <utils/mimeutils.h>
|
||||
#include <utils/networkaccessmanager.h>
|
||||
#include <utils/passworddialog.h>
|
||||
#include <utils/pathchooser.h>
|
||||
#include <utils/savefile.h>
|
||||
#include <utils/store.h>
|
||||
@@ -41,6 +43,7 @@
|
||||
#include <utils/theme/theme.h>
|
||||
#include <utils/theme/theme_p.h>
|
||||
|
||||
#include <QAuthenticator>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
@@ -136,6 +139,30 @@ void CorePlugin::loadMimeFromPlugin(const ExtensionSystem::PluginSpec *plugin)
|
||||
Utils::addMimeTypes(plugin->name() + ".mimetypes", mimetypeString.trimmed().toUtf8());
|
||||
}
|
||||
|
||||
static void initProxyAuthDialog()
|
||||
{
|
||||
QObject::connect(Utils::NetworkAccessManager::instance(),
|
||||
&QNetworkAccessManager::proxyAuthenticationRequired,
|
||||
Utils::NetworkAccessManager::instance(),
|
||||
[](const QNetworkProxy &, QAuthenticator *authenticator) {
|
||||
static bool doNotAskAgain = false;
|
||||
|
||||
std::optional<QPair<QString, QString>> answer
|
||||
= Utils::PasswordDialog::getUserAndPassword(
|
||||
Tr::tr("Proxy Authentication Required"),
|
||||
authenticator->realm(),
|
||||
Tr::tr("Do not ask again."),
|
||||
{},
|
||||
&doNotAskAgain,
|
||||
Core::ICore::dialogParent());
|
||||
|
||||
if (answer) {
|
||||
authenticator->setUser(answer->first);
|
||||
authenticator->setPassword(answer->second);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage)
|
||||
{
|
||||
// register all mime types from all plugins
|
||||
@@ -145,6 +172,8 @@ bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage)
|
||||
loadMimeFromPlugin(plugin);
|
||||
}
|
||||
|
||||
initProxyAuthDialog();
|
||||
|
||||
if (ThemeEntry::availableThemes().isEmpty()) {
|
||||
*errorMessage = Tr::tr("No themes found in installation.");
|
||||
return false;
|
||||
@@ -238,9 +267,9 @@ bool CorePlugin::initialize(const QStringList &arguments, QString *errorMessage)
|
||||
[] { return QUuid::createUuid().toString(); });
|
||||
|
||||
expander->registerPrefix("#:", Tr::tr("A comment."), [](const QString &) { return QString(); });
|
||||
expander->registerPrefix("Asciify:", Tr::tr("Convert string into pure ascii."),
|
||||
[expander] (const QString &s) {
|
||||
return asciify(expander->expand(s)); });
|
||||
expander->registerPrefix("Asciify:",
|
||||
Tr::tr("Convert string to pure ASCII."),
|
||||
[expander](const QString &s) { return asciify(expander->expand(s)); });
|
||||
|
||||
Utils::PathChooser::setAboutToShowContextMenuHandler(&CorePlugin::addToPathChooserContextMenu);
|
||||
|
||||
|
||||
@@ -579,7 +579,7 @@ void EditorManagerPrivate::init()
|
||||
// Go back in navigation history
|
||||
ActionBuilder goBack(this, Constants::GO_BACK);
|
||||
goBack.setIcon(Utils::Icons::PREV.icon());
|
||||
goBack.setText(Core::Tr::tr("Go Back"));
|
||||
goBack.setText(::Core::Tr::tr("Go Back"));
|
||||
goBack.bindContextAction(&m_goBackAction);
|
||||
goBack.setContext(editDesignContext);
|
||||
goBack.setDefaultKeySequence(::Core::Tr::tr("Ctrl+Alt+Left"), ::Core::Tr::tr("Alt+Left"));
|
||||
@@ -589,7 +589,7 @@ void EditorManagerPrivate::init()
|
||||
// Go forward in navigation history
|
||||
ActionBuilder goForward(this, Constants::GO_FORWARD);
|
||||
goForward.setIcon(Utils::Icons::NEXT.icon());
|
||||
goForward.setText(Core::Tr::tr("Go Forward"));
|
||||
goForward.setText(::Core::Tr::tr("Go Forward"));
|
||||
goForward.bindContextAction(&m_goForwardAction);
|
||||
goForward.setContext(editDesignContext);
|
||||
goForward.setDefaultKeySequence(::Core::Tr::tr("Ctrl+Alt+Right"), ::Core::Tr::tr("Alt+Right"));
|
||||
@@ -618,7 +618,7 @@ void EditorManagerPrivate::init()
|
||||
splitSideBySide.setText(::Core::Tr::tr("Split Side by Side"));
|
||||
splitSideBySide.bindContextAction(&m_splitSideBySideAction);
|
||||
splitSideBySide.setContext(editManagerContext);
|
||||
splitSideBySide.setDefaultKeySequence(::Core::Tr::tr("Meta+E,3"), Core::Tr::tr("Ctrl+E,3"));
|
||||
splitSideBySide.setDefaultKeySequence(::Core::Tr::tr("Meta+E,3"), ::Core::Tr::tr("Ctrl+E,3"));
|
||||
splitSideBySide.addToContainer(Constants::M_WINDOW, Constants::G_WINDOW_SPLIT);
|
||||
splitSideBySide.addOnTriggered(this, &EditorManager::splitSideBySide);
|
||||
|
||||
|
||||
@@ -47,9 +47,11 @@ EditorWindow::EditorWindow(QWidget *parent) :
|
||||
|
||||
static int windowId = 0;
|
||||
|
||||
const Utils::Id windowContext
|
||||
= Utils::Id("EditorManager.ExternalWindow.").withSuffix(++windowId);
|
||||
ICore::registerWindow(this,
|
||||
Context(Utils::Id("EditorManager.ExternalWindow.").withSuffix(++windowId),
|
||||
Constants::C_EDITORMANAGER));
|
||||
Context(windowContext, Constants::C_EDITORMANAGER),
|
||||
Context(windowContext));
|
||||
|
||||
connect(m_area, &EditorArea::windowTitleNeedsUpdate,
|
||||
this, &EditorWindow::updateWindowTitle);
|
||||
|
||||
@@ -92,7 +92,7 @@ using namespace Utils;
|
||||
Returns the name of the find filter or scope as presented to the user.
|
||||
|
||||
This is the name that appears in the scope selection combo box, for example.
|
||||
Always return a translatable string. That is, use \c tr() for the return
|
||||
Always return a translatable string. That is, use \c {Tr::tr()} for the return
|
||||
value.
|
||||
*/
|
||||
|
||||
|
||||
@@ -1011,14 +1011,15 @@ void ICore::removeAdditionalContext(const Context &context)
|
||||
Registers a \a window with the specified \a context. Registered windows are
|
||||
shown in the \uicontrol Window menu and get registered for the various
|
||||
window related actions, like the minimize, zoom, fullscreen and close
|
||||
actions.
|
||||
actions. The context for the actions is \a context by default, but can be
|
||||
overridden with \a actionContext.
|
||||
|
||||
Whenever the application focus is in \a window, its \a context is made
|
||||
active.
|
||||
*/
|
||||
void ICore::registerWindow(QWidget *window, const Context &context)
|
||||
void ICore::registerWindow(QWidget *window, const Context &context, const Context &actionContext)
|
||||
{
|
||||
new WindowSupport(window, context); // deletes itself when widget is destroyed
|
||||
new WindowSupport(window, context, actionContext); // deletes itself when widget is destroyed
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1970,14 +1971,32 @@ void ICorePrivate::registerDefaultActions()
|
||||
toggleMenubarAction.addToContainer(Constants::M_VIEW, Constants::G_VIEW_VIEWS);
|
||||
toggleMenubarAction.addOnToggled(this, [](bool visible) {
|
||||
if (!visible) {
|
||||
const QString keys = ActionManager::command(Constants::TOGGLE_MENUBAR)
|
||||
->keySequence().toString(QKeySequence::NativeText);
|
||||
CheckableMessageBox::information(Core::ICore::dialogParent(),
|
||||
Tr::tr("Hide Menu Bar"),
|
||||
Tr::tr("This will hide the menu bar completely. "
|
||||
"You can show it again by typing %1.")
|
||||
.arg(keys),
|
||||
Key("ToogleMenuBarHint"));
|
||||
auto keySequenceAndText = [](const Utils::Id &actionName) {
|
||||
const auto command = ActionManager::command(actionName);
|
||||
|
||||
const QString keySequence = command->keySequence().toString(
|
||||
QKeySequence::NativeText);
|
||||
const QString text = command->action()->text();
|
||||
|
||||
return QPair<QString, QString>(keySequence, text);
|
||||
};
|
||||
|
||||
auto [menuBarKeys, menuBarText] = keySequenceAndText(Constants::TOGGLE_MENUBAR);
|
||||
auto [actionsFromMenuKeys, actionsFromMenuText] = keySequenceAndText(
|
||||
"Locator.Actions from the menu");
|
||||
|
||||
CheckableMessageBox::information(
|
||||
Core::ICore::dialogParent(),
|
||||
Tr::tr("Hide Menu Bar"),
|
||||
Tr::tr("This will hide the menu bar completely. "
|
||||
"You can show it again by typing %1."
|
||||
"<br><br>"
|
||||
"Or, trigger the \"%2\" action from the \"%3\" locator filter (%4).")
|
||||
.arg(menuBarKeys)
|
||||
.arg(menuBarText)
|
||||
.arg(actionsFromMenuText)
|
||||
.arg(actionsFromMenuKeys),
|
||||
Key("ToogleMenuBarHint"));
|
||||
}
|
||||
globalMenuBar()->setVisible(visible);
|
||||
});
|
||||
|
||||