Merge remote-tracking branch 'origin/11.0'

Change-Id: Icb3ed8a1aaf31e8201a61d04221bfcb23a78562a
This commit is contained in:
Eike Ziller
2023-06-22 12:42:43 +02:00
78 changed files with 1255 additions and 328 deletions

View File

@@ -42,7 +42,7 @@ To use an external terminal, deselect the `Use internal terminal` check box in
### Copilot ### Copilot
The experimental Copilot plugin integrates The Copilot plugin (disabled by default) integrates
[GitHub Copilot](https://github.com/features/copilot), which uses OpenAI to [GitHub Copilot](https://github.com/features/copilot), which uses OpenAI to
suggest code in the `Edit` mode. suggest code in the `Edit` mode.
@@ -87,6 +87,8 @@ General
([QTCREATORBUG-26128](https://bugreports.qt.io/browse/QTCREATORBUG-26128), ([QTCREATORBUG-26128](https://bugreports.qt.io/browse/QTCREATORBUG-26128),
[QTCREATORBUG-27006](https://bugreports.qt.io/browse/QTCREATORBUG-27006), [QTCREATORBUG-27006](https://bugreports.qt.io/browse/QTCREATORBUG-27006),
[QTCREATORBUG-27506](https://bugreports.qt.io/browse/QTCREATORBUG-27506)) [QTCREATORBUG-27506](https://bugreports.qt.io/browse/QTCREATORBUG-27506))
* Fixed a crash with a large number of search hits from Silver Searcher
([QTCREATORBUG-29130](https://bugreports.qt.io/browse/QTCREATORBUG-29130))
* Locator * Locator
* Improved performance * Improved performance
* Added the creation of directories to the `Files in File System` filter * Added the creation of directories to the `Files in File System` filter
@@ -101,6 +103,8 @@ Editing
([QTCREATORBUG-19651](https://bugreports.qt.io/browse/QTCREATORBUG-19651)) ([QTCREATORBUG-19651](https://bugreports.qt.io/browse/QTCREATORBUG-19651))
* Fixed an issue of copy and paste with multiple cursors * Fixed an issue of copy and paste with multiple cursors
([QTCREATORBUG-29117](https://bugreports.qt.io/browse/QTCREATORBUG-29117)) ([QTCREATORBUG-29117](https://bugreports.qt.io/browse/QTCREATORBUG-29117))
* Fixed the handling of pre-edit text for input methods
([QTCREATORBUG-29134](https://bugreports.qt.io/browse/QTCREATORBUG-29134))
### C++ ### C++
@@ -111,12 +115,22 @@ Editing
* Extended the `Add Class Member` refactoring action to create class * Extended the `Add Class Member` refactoring action to create class
members from assignments members from assignments
([QTCREATORBUG-1918](https://bugreports.qt.io/browse/QTCREATORBUG-1918)) ([QTCREATORBUG-1918](https://bugreports.qt.io/browse/QTCREATORBUG-1918))
* Fixed that generated functions did not have a `const` qualifier when
required
([QTCREATORBUG-29274](https://bugreports.qt.io/browse/QTCREATORBUG-29274))
* Fixed that locator showed both the declaration and the definition of symbols * Fixed that locator showed both the declaration and the definition of symbols
([QTCREATORBUG-13894](https://bugreports.qt.io/browse/QTCREATORBUG-13894)) ([QTCREATORBUG-13894](https://bugreports.qt.io/browse/QTCREATORBUG-13894))
* Fixed the handling of C++20 keywords and concepts * Fixed the handling of C++20 keywords and concepts
* Clangd
* Fixed that the index could be outdated after VCS operations
* Fixed the highlighting of labels
([QTCREATORBUG-27338](https://bugreports.qt.io/browse/QTCREATORBUG-27338))
* Built-in * Built-in
* Fixed support for `if`-statements with initializer * Fixed support for `if`-statements with initializer
([QTCREATORBUG-29182](https://bugreports.qt.io/browse/QTCREATORBUG-29182)) ([QTCREATORBUG-29182](https://bugreports.qt.io/browse/QTCREATORBUG-29182))
* Clang Format
* Fixed the conversion of tab indentation settings to Clang Format
([QTCREATORBUG-29185](https://bugreports.qt.io/browse/QTCREATORBUG-29185))
### Language Server Protocol ### Language Server Protocol
@@ -135,12 +149,16 @@ Editing
([QTCREATORBUG-29123](https://bugreports.qt.io/browse/QTCREATORBUG-29123)) ([QTCREATORBUG-29123](https://bugreports.qt.io/browse/QTCREATORBUG-29123))
* Fixed the completion for Qt Quick Controls * Fixed the completion for Qt Quick Controls
([QTCREATORBUG-28648](https://bugreports.qt.io/browse/QTCREATORBUG-28648)) ([QTCREATORBUG-28648](https://bugreports.qt.io/browse/QTCREATORBUG-28648))
* Fixed that `qmllint` issues were not shown in the `Issues` view
([QTCREATORBUG-28720](https://bugreports.qt.io/browse/QTCREATORBUG-28720))
### Python ### Python
* Added the option to create a virtual environment (`venv`) to the Python * Added the option to create a virtual environment (`venv`) to the Python
interpreter selector and the wizard interpreter selector and the wizard
([PYSIDE-2152](https://bugreports.qt.io/browse/PYSIDE-2152)) ([PYSIDE-2152](https://bugreports.qt.io/browse/PYSIDE-2152))
* Fixed that too many progress indicators could be created
([QTCREATORBUG-29224](https://bugreports.qt.io/browse/QTCREATORBUG-29224))
([Documentation](https://doc-snapshots.qt.io/qtcreator-11.0/creator-python-development.html)) ([Documentation](https://doc-snapshots.qt.io/qtcreator-11.0/creator-python-development.html))
@@ -168,6 +186,7 @@ Projects
[QTCREATORBUG-29006](https://bugreports.qt.io/browse/QTCREATORBUG-29006)) [QTCREATORBUG-29006](https://bugreports.qt.io/browse/QTCREATORBUG-29006))
* Added `Build > Reload CMake Presets` to reload CMake presets after making * Added `Build > Reload CMake Presets` to reload CMake presets after making
changes to them changes to them
* Added support for `block()` and `endblock()`
* Fixed that CMake Presets were not visible in `Projects` view * Fixed that CMake Presets were not visible in `Projects` view
([QTCREATORBUG-28966](https://bugreports.qt.io/browse/QTCREATORBUG-28966)) ([QTCREATORBUG-28966](https://bugreports.qt.io/browse/QTCREATORBUG-28966))
* Fixed issues with detecting a configured Qt version when importing a build * Fixed issues with detecting a configured Qt version when importing a build
@@ -184,11 +203,17 @@ Debugging
* Improved the UI for enabling and disabling debuggers in `Projects > Run > * Improved the UI for enabling and disabling debuggers in `Projects > Run >
Debugger settings` Debugger settings`
([QTCREATORBUG-28627](https://bugreports.qt.io/browse/QTCREATORBUG-28627)) ([QTCREATORBUG-28627](https://bugreports.qt.io/browse/QTCREATORBUG-28627))
* Fixed the automatic source mapping for Qt versions from an installer
([QTCREATORBUG-28950](https://bugreports.qt.io/browse/QTCREATORBUG-28950))
* Fixed pretty printer for `std::string` for recent `libc++`
([QTCREATORBUG-29230](https://bugreports.qt.io/browse/QTCREATORBUG-29230))
### C++ ### C++
* Added an option for the default number of array elements to show * Added an option for the default number of array elements to show
(`Preferences > Debugger > Locals & Expressions > Default array size`) (`Preferences > Debugger > Locals & Expressions > Default array size`)
* Fixed debugging in a terminal as the root user
([QTCREATORBUG-27519](https://bugreports.qt.io/browse/QTCREATORBUG-27519))
* CDB * CDB
* Added automatic source file mapping for Qt packages * Added automatic source file mapping for Qt packages
* Fixed the variables view on remote Windows devices * Fixed the variables view on remote Windows devices

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -42,9 +42,11 @@
\section2 Debugging Qt Quick UI Projects \section2 Debugging Qt Quick UI Projects
\endif \endif
To debug Qt Quick UI projects (.qmlproject), select the To debug Qt Quick UI projects (.qmlproject), select \uicontrol Automatic
\uicontrol {Enable QML} check box in \uicontrol {Debugger settings} or \uicontrol Enabled in \uicontrol{Run Settings} >
in \uicontrol Projects mode \uicontrol {Run Settings}. \uicontrol {Debugger Settings} > \uicontrol {QML debugger}.
\image qtquick-debugger-settings.webp {Debugger settings section in Run Settings}
\if defined(qtcreator) \if defined(qtcreator)
\section2 Debugging Qt Quick Applications \section2 Debugging Qt Quick Applications
@@ -65,9 +67,13 @@
functions. Therefore, you must make sure that the port is properly functions. Therefore, you must make sure that the port is properly
protected by a firewall. protected by a firewall.
\li In \uicontrol {Run Settings} > \uicontrol {Debugger settings}, select \li In \uicontrol {Run Settings} > \uicontrol {Debugger settings} >
the \uicontrol {Enable QML} check box to enable QML debugging for \uicontrol {QML debugger}, select \uicontrol Automatic or
running applications. \uicontrol Enabled to enable QML debugging for running applications.
To debug both the C++ and QML parts of your application at the same
time, also select \uicontrol Automatic or \uicontrol Enabled in
\uicontrol {C++ debugger}.
\li Select \uicontrol Build > \uicontrol {Rebuild Project} to clean and \li Select \uicontrol Build > \uicontrol {Rebuild Project} to clean and
rebuild the project. rebuild the project.
@@ -119,17 +125,8 @@
For example, for qmake the global setting only affects build configurations For example, for qmake the global setting only affects build configurations
that are automatically created when enabling a kit. Also, CMake ignores the that are automatically created when enabling a kit. Also, CMake ignores the
global setting. global setting.
\section1 Mixed C++/QML Debugging
To debug both the C++ and QML parts of your application at the same time,
select the \uicontrol {Enable C++} and \uicontrol {Enable QML} checkboxes for both
languages in the \uicontrol {Debugger Settings} section in the project
\uicontrol{Run Settings}.
\endif \endif
\image qtquick-debugging-settings.png {Debugger settings section in Run Settings}
\section1 Starting QML Debugging \section1 Starting QML Debugging
To start the application, choose \uicontrol Debug > \uicontrol {Start Debugging} To start the application, choose \uicontrol Debug > \uicontrol {Start Debugging}

View File

@@ -81,7 +81,7 @@
\li \l {Using GitHub Copilot} \li \l {Using GitHub Copilot}
The experimental Copilot plugin integrates The Copilot plugin (disabled by default) integrates
\l{https://github.com/features/copilot}{GitHub Copilot} into \QC. \l{https://github.com/features/copilot}{GitHub Copilot} into \QC.
You can view suggestions from Copilot in the code editor. You can view suggestions from Copilot in the code editor.

View File

@@ -8,7 +8,7 @@
\title Using GitHub Copilot \title Using GitHub Copilot
The experimental Copilot plugin integrates The Copilot plugin (disabled by default) integrates
\l{https://github.com/features/copilot}{GitHub Copilot} into \QC. \l{https://github.com/features/copilot}{GitHub Copilot} into \QC.
You can view suggestions from Copilot in the \uicontrol Edit mode. You can view suggestions from Copilot in the \uicontrol Edit mode.

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2022 The Qt Company Ltd. // Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*! /*!
@@ -6,10 +6,11 @@
\section1 Enabling Debugging \section1 Enabling Debugging
\image qtquick-debugging-settings.png "Debugger Settings" \image qtquick-debugger-settings.webp "Debugger Settings"
To select the languages to debug, select the \uicontrol {Enable C++} and To select the languages to debug, select \uicontrol Automatic
\uicontrol {Enable QML} check boxes. or \uicontrol Enabled in \uicontrol {Debugger Settings} >
\uicontrol {C++ debugger} and \uicontrol {QML debugger}.
\note Opening a socket at a well-known port presents a security risk. Anyone \note Opening a socket at a well-known port presents a security risk. Anyone
on the Internet could connect to the application that you are debugging and on the Internet could connect to the application that you are debugging and
@@ -28,6 +29,11 @@
However, you can usually leave this field empty. However, you can usually leave this field empty.
\note To create a build configuration that supports debugging for a
Qt Quick application project, you also need to \l {Using Default Values}
{enable QML debugging} either globally or in the \uicontrol {Build Settings}
of the project.
For more information about debugging, see \l{Debugging}. For more information about debugging, see \l{Debugging}.
//! [run settings debugger] //! [run settings debugger]

View File

@@ -16,7 +16,7 @@ headerdirs = . \
../src \ ../src \
../../../src/libs/aggregation \ ../../../src/libs/aggregation \
../../../src/libs/extensionsystem \ ../../../src/libs/extensionsystem \
../../../src/libs/solutions/tasking \ ../../../src/libs/solutions \
../../../src/libs/utils \ ../../../src/libs/utils \
../../../src/plugins/coreplugin ../../../src/plugins/coreplugin
@@ -24,7 +24,7 @@ sourcedirs = . \
../src \ ../src \
../../../src/libs/aggregation \ ../../../src/libs/aggregation \
../../../src/libs/extensionsystem \ ../../../src/libs/extensionsystem \
../../../src/libs/solutions/tasking \ ../../../src/libs/solutions \
../../../src/libs/utils \ ../../../src/libs/utils \
../../../src/plugins/coreplugin ../../../src/plugins/coreplugin
@@ -42,7 +42,8 @@ sources.fileextensions = "*.cpp *.qdoc"
imagedirs = ../images \ imagedirs = ../images \
../../config/images \ ../../config/images \
../../qtcreator/images ../../qtcreator/images \
../../../src/libs/solutions
exampledirs = ../examples exampledirs = ../examples
depends += qtwidgets \ depends += qtwidgets \

View File

@@ -119,6 +119,11 @@
\li Solution Name \li Solution Name
\li Description \li Description
\row
\li \l{Spinner Solution}{Spinner}
\li Renders a circular, endlessly animated progress indicator,
which may be attached to any widget as an overlay.
\row \row
\li \l{Tasking Solution}{Tasking} \li \l{Tasking Solution}{Tasking}
\li Enables you to build extensible, declarative task tree structures \li Enables you to build extensible, declarative task tree structures

View File

@@ -1,6 +1,7 @@
# Copyright (C) 2016 The Qt Company Ltd. # Copyright (C) 2016 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 GPL-3.0-only WITH Qt-GPL-exception-1.0
import argparse
import os import os
import locale import locale
import shutil import shutil
@@ -196,13 +197,13 @@ def is_not_debug(path, filenames):
files = [fn for fn in filenames if os.path.isfile(os.path.join(path, fn))] files = [fn for fn in filenames if os.path.isfile(os.path.join(path, fn))]
return [fn for fn in files if not is_debug_file(os.path.join(path, fn))] return [fn for fn in files if not is_debug_file(os.path.join(path, fn))]
def codesign_call(): def codesign_call(identity=None, flags=None):
signing_identity = os.environ.get('SIGNING_IDENTITY') signing_identity = identity or os.environ.get('SIGNING_IDENTITY')
if not signing_identity: if not signing_identity:
return None return None
codesign_call = ['codesign', '-o', 'runtime', '--force', '-s', signing_identity, codesign_call = ['codesign', '-o', 'runtime', '--force', '-s', signing_identity,
'-v'] '-v']
signing_flags = os.environ.get('SIGNING_FLAGS') signing_flags = flags or os.environ.get('SIGNING_FLAGS')
if signing_flags: if signing_flags:
codesign_call.extend(signing_flags.split()) codesign_call.extend(signing_flags.split())
return codesign_call return codesign_call
@@ -228,8 +229,8 @@ def conditional_sign_recursive(path, filter):
if is_mac_platform(): if is_mac_platform():
os_walk(path, filter, lambda fp: codesign_executable(fp)) os_walk(path, filter, lambda fp: codesign_executable(fp))
def codesign(app_path): def codesign(app_path, identity=None, flags=None):
codesign = codesign_call() codesign = codesign_call(identity, flags)
if not codesign or not is_mac_platform(): if not codesign or not is_mac_platform():
return return
# sign all executables in Resources # sign all executables in Resources
@@ -243,3 +244,20 @@ def codesign(app_path):
entitlements_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'dist', entitlements_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'dist',
'installer', 'mac', 'entitlements.plist') 'installer', 'mac', 'entitlements.plist')
subprocess.check_call(codesign + ['--deep', app_path, '--entitlements', entitlements_path]) subprocess.check_call(codesign + ['--deep', app_path, '--entitlements', entitlements_path])
def codesign_main(args):
codesign(args.app_bundle, args.identity, args.flags)
def main():
parser = argparse.ArgumentParser(description='Qt Creator build tools')
subparsers = parser.add_subparsers(title='subcommands', required=True)
parser_codesign = subparsers.add_parser('codesign', description='Codesign macOS app bundle')
parser_codesign.add_argument('app_bundle')
parser_codesign.add_argument('-s', '--identity', help='Codesign identity')
parser_codesign.add_argument('--flags', help='Additional flags')
parser_codesign.set_defaults(func=codesign_main)
args = parser.parse_args()
args.func(args)
if __name__ == '__main__':
main()

View File

@@ -1509,9 +1509,10 @@ class CliDumper(Dumper):
self.setupDumpers({}) self.setupDumpers({})
def put(self, line): def put(self, line):
if self.output.endswith('\n'): if self.output:
self.output = self.output[0:-1] if self.output[-1].endswith('\n'):
self.output += line self.output[-1] = self.output[-1][0:-1]
self.output.append(line)
def putNumChild(self, numchild): def putNumChild(self, numchild):
pass pass

View File

@@ -504,6 +504,21 @@ int main(int argc, char **argv)
{{"LD_PRELOAD", "", Utils::EnvironmentItem::Unset}}); {{"LD_PRELOAD", "", Utils::EnvironmentItem::Unset}});
} }
auto restoreEnvVarFromSquish = [](const QByteArray &squishVar, const QString &var) {
if (qEnvironmentVariableIsSet(squishVar)) {
Utils::Environment::modifySystemEnvironment(
{{var, "", Utils::EnvironmentItem::Unset}});
const QString content = qEnvironmentVariable(squishVar);
if (!content.isEmpty()) {
Utils::Environment::modifySystemEnvironment(
{{var, content, Utils::EnvironmentItem::Prepend}});
}
}
};
restoreEnvVarFromSquish("SQUISH_SHELL_ORIG_DYLD_LIBRARY_PATH", "DYLD_LIBRARY_PATH");
restoreEnvVarFromSquish("SQUISH_ORIG_DYLD_FRAMEWORK_PATH", "DYLD_FRAMEWORK_PATH");
if (options.userLibraryPath) { if (options.userLibraryPath) {
if ((*options.userLibraryPath).isEmpty()) { if ((*options.userLibraryPath).isEmpty()) {
Utils::Environment::modifySystemEnvironment( Utils::Environment::modifySystemEnvironment(

View File

@@ -370,7 +370,7 @@ QByteArray LibraryInfo::calculateFingerprint() const
{ {
QCryptographicHash hash(QCryptographicHash::Sha1); QCryptographicHash hash(QCryptographicHash::Sha1);
auto addData = [&hash](auto p, size_t len) { auto addData = [&hash](auto p, size_t len) {
hash.addData(QByteArrayView(reinterpret_cast<const char *>(p), len)); hash.addData(reinterpret_cast<const char *>(p), len);
}; };
addData(&_status, sizeof(_status)); addData(&_status, sizeof(_status));

View File

@@ -1 +1,2 @@
add_subdirectory(spinner)
add_subdirectory(tasking) add_subdirectory(tasking)

View File

@@ -2,6 +2,7 @@ Project {
name: "Solutions" name: "Solutions"
references: [ references: [
"spinner/spinner.qbs",
"tasking/tasking.qbs", "tasking/tasking.qbs",
].concat(project.additionalLibs) ].concat(project.additionalLibs)
} }

View File

@@ -0,0 +1,9 @@
add_qtc_library(Spinner OBJECT
# Never add dependencies to non-Qt libraries for this library
DEPENDS Qt::Core Qt::Widgets
PUBLIC_DEFINES SPINNER_LIBRARY
SOURCES
spinner.cpp spinner.h
spinner.qrc
spinner_global.h
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

View File

@@ -0,0 +1,249 @@
// 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 "spinner.h"
#include <QEvent>
#include <QPainter>
#include <QTimer>
#include <QWidget>
namespace SpinnerSolution {
class OverlayWidget : public QWidget
{
public:
using PaintFunction = std::function<void(QWidget *, QPainter &, QPaintEvent *)>;
explicit OverlayWidget(QWidget *parent = nullptr)
{
setAttribute(Qt::WA_TransparentForMouseEvents);
if (parent)
attachToWidget(parent);
}
void attachToWidget(QWidget *parent)
{
if (parentWidget())
parentWidget()->removeEventFilter(this);
setParent(parent);
if (parent) {
parent->installEventFilter(this);
resizeToParent();
raise();
}
}
void setPaintFunction(const PaintFunction &paint) { m_paint = paint; }
protected:
bool eventFilter(QObject *obj, QEvent *ev) override
{
if (obj == parent() && ev->type() == QEvent::Resize)
resizeToParent();
return QWidget::eventFilter(obj, ev);
}
void paintEvent(QPaintEvent *ev) override
{
if (m_paint) {
QPainter p(this);
m_paint(this, p, ev);
}
}
private:
void resizeToParent() { setGeometry(QRect({}, parentWidget()->size())); }
PaintFunction m_paint;
};
class SpinnerPainter
{
public:
using UpdateCallback = std::function<void()>;
SpinnerPainter(SpinnerSize size);
void setSize(SpinnerSize size);
void setUpdateCallback(const UpdateCallback &cb) { m_callback = cb; }
QSize size() const { return m_pixmap.size() / m_pixmap.devicePixelRatio(); }
void paint(QPainter &painter, const QRect &rect) const;
void startAnimation() { m_timer.start(); }
void stopAnimation() { m_timer.stop(); }
protected:
void nextAnimationStep() { m_rotation = (m_rotation + m_rotationStep + 360) % 360; }
private:
SpinnerSize m_size = SpinnerSize::Small;
int m_rotationStep = 45;
int m_rotation = 0;
QTimer m_timer;
QPixmap m_pixmap;
UpdateCallback m_callback;
};
static QString imageFileNameForSpinnerSize(SpinnerSize size)
{
switch (size) {
case SpinnerSize::Large:
return ":/icons/spinner_large.png";
case SpinnerSize::Medium:
return ":/icons/spinner_medium.png";
case SpinnerSize::Small:
return ":/icons/spinner_small.png";
}
return {};
}
SpinnerPainter::SpinnerPainter(SpinnerSize size)
{
m_timer.setSingleShot(false);
QObject::connect(&m_timer, &QTimer::timeout, &m_timer, [this] {
nextAnimationStep();
if (m_callback)
m_callback();
});
setSize(size);
}
void SpinnerPainter::setSize(SpinnerSize size)
{
m_size = size;
m_rotationStep = size == SpinnerSize::Small ? 45 : 30;
m_timer.setInterval(size == SpinnerSize::Small ? 100 : 80);
m_pixmap = QPixmap(imageFileNameForSpinnerSize(size));
}
void SpinnerPainter::paint(QPainter &painter, const QRect &rect) const
{
painter.save();
painter.setRenderHint(QPainter::SmoothPixmapTransform);
QPoint translate(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2);
QTransform t;
t.translate(translate.x(), translate.y());
t.rotate(m_rotation);
t.translate(-translate.x(), -translate.y());
painter.setTransform(t);
QSize pixmapUserSize(m_pixmap.size() / m_pixmap.devicePixelRatio());
painter.drawPixmap(QPoint(rect.x() + ((rect.width() - pixmapUserSize.width()) / 2),
rect.y() + ((rect.height() - pixmapUserSize.height()) / 2)),
m_pixmap);
painter.restore();
}
class SpinnerWidget : public OverlayWidget
{
public:
explicit SpinnerWidget(SpinnerSize size, QWidget *parent = nullptr)
: OverlayWidget(parent)
, m_paint(size)
{
setPaintFunction(
[this](QWidget *w, QPainter &p, QPaintEvent *) { m_paint.paint(p, w->rect()); });
m_paint.setUpdateCallback([this] { update(); });
updateGeometry();
}
void setSize(SpinnerSize size)
{
m_paint.setSize(size);
updateGeometry();
}
QSize sizeHint() const final { return m_paint.size(); }
protected:
void showEvent(QShowEvent *) final { m_paint.startAnimation(); }
void hideEvent(QHideEvent *) final { m_paint.stopAnimation(); }
private:
SpinnerPainter m_paint;
};
/*!
\module SpinnerSolution
\title Spinner Solution
\ingroup solutions-modules
\brief Contains a Spinner solution.
The Spinner solution depends on Qt only, and doesn't depend on any \QC specific code.
*/
/*!
\namespace SpinnerSolution
\inmodule SpinnerSolution
\brief The SpinnerSolution namespace encloses the Spinner class.
*/
/*!
\enum SpinnerSolution::SpinnerSize
This enum describes the possible spinner sizes.
\value Small \inlineimage spinner/icons/spinner_small.png
\value Medium \inlineimage spinner/icons/spinner_medium.png
\value Large \inlineimage spinner/icons/spinner_large.png
*/
/*!
\class SpinnerSolution::Spinner
\inheaderfile solutions/spinner/spinner.h
\inmodule SpinnerSolution
\brief The Spinner class renders a circular, endlessly animated progress indicator,
that may be attached to any widget as an overlay.
*/
/*!
Creates a spinner overlay with a given \a size for the passed \a parent widget.
The \a parent widget takes the ownership of the created spinner.
*/
Spinner::Spinner(SpinnerSize size, QWidget *parent)
: QObject(parent)
, m_widget(new SpinnerWidget(size, parent)) {}
/*!
Sets the size of the spinner to the given \a size.
*/
void Spinner::setSize(SpinnerSize size)
{
m_widget->setSize(size);
}
/*!
Shows the animated spinner as an overlay for the parent widget
set previously in the constructor.
*/
void Spinner::show()
{
m_widget->show();
}
/*!
Hides the spinner.
*/
void Spinner::hide()
{
m_widget->hide();
}
/*!
Returns \c true if the spinner is visible; otherwise, returns \c false.
*/
bool Spinner::isVisible() const
{
return m_widget->isVisible();
}
/*!
Shows or hides the spinner depending on the value of \a visible.
By default, the spinner is visible.
*/
void Spinner::setVisible(bool visible)
{
m_widget->setVisible(visible);
}
} // namespace SpinnerSolution

View File

@@ -0,0 +1,37 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#ifndef SPINNER_H
#define SPINNER_H
#include "spinner_global.h"
#include <QObject>
namespace SpinnerSolution {
Q_NAMESPACE_EXPORT(SPINNER_EXPORT)
enum class SpinnerSize { Small, Medium, Large };
Q_ENUM_NS(SpinnerSize);
// TODO: SpinnerOverlay and SpinnerWidget?
class SPINNER_EXPORT Spinner : public QObject
{
Q_OBJECT
public:
explicit Spinner(SpinnerSize size, QWidget *parent = nullptr);
void setSize(SpinnerSize size);
void show();
void hide();
bool isVisible() const;
void setVisible(bool visible);
private:
class SpinnerWidget *m_widget = nullptr;
};
} // namespace SpinnerSolution
#endif // SPINNER_H

View File

@@ -0,0 +1,13 @@
QtcLibrary {
name: "Spinner"
Depends { name: "Qt"; submodules: ["core", "widgets"] }
cpp.defines: base.concat("SPINNER_LIBRARY")
files: [
"spinner.cpp",
"spinner.h",
"spinner.qrc",
"spinner_global.h",
]
}

View File

@@ -0,0 +1,7 @@
<RCC>
<qresource prefix="/" >
<file>icons/spinner_large.png</file>
<file>icons/spinner_medium.png</file>
<file>icons/spinner_small.png</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,14 @@
// 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 <qglobal.h>
#if defined(SPINNER_LIBRARY)
# define SPINNER_EXPORT Q_DECL_EXPORT
#elif defined(SPINNER_STATIC_LIBRARY)
# define SPINNER_EXPORT
#else
# define SPINNER_EXPORT Q_DECL_IMPORT
#endif

View File

@@ -83,14 +83,14 @@ public:
"It is possible that no barrier was added to the tree, " "It is possible that no barrier was added to the tree, "
"or the storage is not reachable from where it is referenced. " "or the storage is not reachable from where it is referenced. "
"The WaitForBarrier task will finish with error. "); "The WaitForBarrier task will finish with error. ");
return TaskAction::StopWithError; return SetupResult::StopWithError;
} }
Barrier *activeSharedBarrier = activeBarrier->barrier(); Barrier *activeSharedBarrier = activeBarrier->barrier();
const std::optional<bool> result = activeSharedBarrier->result(); const std::optional<bool> result = activeSharedBarrier->result();
if (result.has_value()) if (result.has_value())
return result.value() ? TaskAction::StopWithDone : TaskAction::StopWithError; return result.value() ? SetupResult::StopWithDone : SetupResult::StopWithError;
QObject::connect(activeSharedBarrier, &Barrier::done, &barrier, &Barrier::stopWithResult); QObject::connect(activeSharedBarrier, &Barrier::done, &barrier, &Barrier::stopWithResult);
return TaskAction::Continue; return SetupResult::Continue;
}) {} }) {}
}; };

View File

@@ -57,6 +57,107 @@ private:
\brief The Tasking namespace encloses all classes and global functions of the Tasking solution. \brief The Tasking namespace encloses all classes and global functions of the Tasking solution.
*/ */
/*!
\class Tasking::TaskInterface
\inheaderfile solutions/tasking/tasktree.h
\inmodule TaskingSolution
\brief TaskInterface is the abstract base class for implementing custom task adapters.
To implement a custom task adapter, derive your adapter from the
\c TaskAdapter<Task> class template. TaskAdapter automatically creates and destroys
the custom task instance and associates the adapter with a given \c Task type.
*/
/*!
\fn virtual void TaskInterface::start()
This method is called by the running TaskTree for starting the \c Task instance.
Reimplement this method in \c TaskAdapter<Task>'s subclass in order to start the
associated task.
Use TaskAdapter::task() to access the associated \c Task instance.
\sa done(), TaskAdapter::task()
*/
/*!
\fn void TaskInterface::done(bool success)
Emit this signal from the \c TaskAdapter<Task>'s subclass, when the \c Task is finished.
Pass \c true as a \a success argument when the task finishes with success;
otherwise, when an error occurs, pass \c false.
*/
/*!
\class Tasking::TaskAdapter
\inheaderfile solutions/tasking/tasktree.h
\inmodule TaskingSolution
\brief A class template for implementing custom task adapters.
The TaskAdapter class template is responsible for creating a task of the \c Task type,
starting it, and reporting success or an error when the task is finished.
It also associates the adapter with a given \c Task type.
Reimplement this class with the actual \c Task type to adapt the task's interface
into the general TaskTree's interface for managing the \c Task instances.
Each subclass needs to provide a public default constructor,
implement the start() method, and emit the done() signal when the task is finished.
Use task() to access the associated \c Task instance.
For more information on implementing the custom task adapters, refer to \l {Task Adapters}.
\sa start(), done(), task()
*/
/*!
\typealias TaskAdapter::Type
Type alias for the \c Task type.
*/
/*!
\fn template <typename Task> TaskAdapter<Task>::TaskAdapter<Task>()
Creates a task adapter for the given \c Task type. Internally, it creates
an instance of \c Task, which is accessible via the task() method.
\sa task()
*/
/*!
\fn template <typename Task> Task *TaskAdapter<Task>::task()
Returns the pointer to the associated \c Task instance.
*/
/*!
\fn template <typename Task> Task *TaskAdapter<Task>::task() const
\overload
Returns the const pointer to the associated \c Task instance.
*/
/*!
\macro TASKING_DECLARE_TASK(CustomTaskName, TaskAdapterClass)
\relates Tasking
Registers the new custom task type under a \a CustomTaskName name inside the
Tasking namespace for the passed \a TaskAdapterClass adapter class.
For more information on implementing the custom task adapters, refer to \l {Task Adapters}.
*/
/*!
\macro TASKING_DECLARE_TEMPLATE_TASK(CustomTaskName, TaskAdapterClass)
\relates Tasking
Registers the new custom task template type under a \a CustomTaskName name inside the
Tasking namespace for the passed \a TaskAdapterClass adapter class template.
For more information on implementing the custom task adapters, refer to \l {Task Adapters}.
*/
/*! /*!
\class Tasking::GroupItem \class Tasking::GroupItem
\inheaderfile solutions/tasking/tasktree.h \inheaderfile solutions/tasking/tasktree.h
@@ -236,25 +337,27 @@ private:
*/ */
/*! /*!
\enum Tasking::TaskAction \enum Tasking::SetupResult
This enum is optionally returned from the group's or task's setup handler function. This enum is optionally returned from the group's or task's setup handler function.
It instructs the running task tree on how to proceed after the setup handler's execution It instructs the running task tree on how to proceed after the setup handler's execution
finished. finished.
\value Continue \value Continue
Default. The group's or task's execution continues nomally. Default. The group's or task's execution continues normally.
When a group's or task's setup handler returns void, it's assumed that When a group's or task's setup handler returns void, it's assumed that
it returned Continue. it returned Continue.
\value StopWithDone \value StopWithDone
The group's or task's execution stops immediately with success. The group's or task's execution stops immediately with success.
When returned from the group's setup handler, all child tasks are skipped, When returned from the group's setup handler, all child tasks are skipped,
and the group's onGroupDone() handler is invoked (if provided). and the group's onGroupDone() handler is invoked (if provided).
The group reports success to its parent. The group's workflow policy is ignored.
When returned from the task's setup handler, the task isn't started, When returned from the task's setup handler, the task isn't started,
its done handler isn't invoked, and the task reports success to its parent. its done handler isn't invoked, and the task reports success to its parent.
\value StopWithError \value StopWithError
The group's or task's execution stops immediately with an error. The group's or task's execution stops immediately with an error.
When returned from the group's setup handler, all child tasks are skipped, When returned from the group's setup handler, all child tasks are skipped,
and the group's onGroupError() handler is invoked (if provided). and the group's onGroupError() handler is invoked (if provided).
The group reports an error to its parent. The group's workflow policy is ignored.
When returned from the task's setup handler, the task isn't started, When returned from the task's setup handler, the task isn't started,
its error handler isn't invoked, and the task reports an error to its parent. its error handler isn't invoked, and the task reports an error to its parent.
*/ */
@@ -262,21 +365,21 @@ private:
/*! /*!
\typealias GroupItem::GroupSetupHandler \typealias GroupItem::GroupSetupHandler
Type alias for \c std::function<TaskAction()>. Type alias for \c std::function<SetupResult()>.
The GroupSetupHandler is used when constructing the onGroupSetup() element. The GroupSetupHandler is used when constructing the onGroupSetup() element.
Any function with the above signature, when passed as a group setup handler, Any function with the above signature, when passed as a group setup handler,
will be called by the running task tree when the group execution starts. will be called by the running task tree when the group execution starts.
The return value of the handler instructs the running group on how to proceed The return value of the handler instructs the running group on how to proceed
after the handler's invocation is finished. The default return value of TaskAction::Continue after the handler's invocation is finished. The default return value of SetupResult::Continue
instructs the group to continue running, i.e. to start executing its child tasks. instructs the group to continue running, i.e. to start executing its child tasks.
The return value of TaskAction::StopWithDone or TaskAction::StopWithError The return value of SetupResult::StopWithDone or SetupResult::StopWithError
instructs the group to skip the child tasks' execution and finish immediately with instructs the group to skip the child tasks' execution and finish immediately with
success or an error, respectively. success or an error, respectively.
When the return type is either TaskAction::StopWithDone When the return type is either SetupResult::StopWithDone
of TaskAction::StopWithError, the group's done or error handler (if provided) of SetupResult::StopWithError, the group's done or error handler (if provided)
is called synchronously immediately afterwards. is called synchronously immediately afterwards.
\note Even if the group setup handler returns StopWithDone or StopWithError, \note Even if the group setup handler returns StopWithDone or StopWithError,
@@ -285,7 +388,7 @@ private:
The onGroupSetup() accepts also functions in the shortened form of \c std::function<void()>, The onGroupSetup() accepts also functions in the shortened form of \c std::function<void()>,
i.e. the return value is void. In this case it's assumed that the return value i.e. the return value is void. In this case it's assumed that the return value
is TaskAction::Continue by default. is SetupResult::Continue by default.
\sa onGroupSetup() \sa onGroupSetup()
*/ */
@@ -293,7 +396,7 @@ private:
/*! /*!
\typealias GroupItem::GroupEndHandler \typealias GroupItem::GroupEndHandler
Type alias for \c std::function\<void()\>. Type alias for \c std::function<void()>.
The GroupEndHandler is used when constructing the onGroupDone() and onGroupError() elements. The GroupEndHandler is used when constructing the onGroupDone() and onGroupError() elements.
Any function with the above signature, when passed as a group done or error handler, Any function with the above signature, when passed as a group done or error handler,
@@ -309,7 +412,7 @@ private:
Constructs a group's element holding the group setup handler. Constructs a group's element holding the group setup handler.
The \a handler is invoked whenever the group starts. The \a handler is invoked whenever the group starts.
The passed \a handler is either of \c std::function<TaskAction()> or \c std::function<void()> The passed \a handler is either of \c std::function<SetupResult()> or \c std::function<void()>
type. For more information on possible argument type, refer to type. For more information on possible argument type, refer to
\l {GroupItem::GroupSetupHandler}. \l {GroupItem::GroupSetupHandler}.
@@ -430,9 +533,9 @@ const GroupItem stopOnFinished = workflowPolicy(WorkflowPolicy::StopOnFinished);
const GroupItem finishAllAndDone = workflowPolicy(WorkflowPolicy::FinishAllAndDone); const GroupItem finishAllAndDone = workflowPolicy(WorkflowPolicy::FinishAllAndDone);
const GroupItem finishAllAndError = workflowPolicy(WorkflowPolicy::FinishAllAndError); const GroupItem finishAllAndError = workflowPolicy(WorkflowPolicy::FinishAllAndError);
static TaskAction toTaskAction(bool success) static SetupResult toSetupResult(bool success)
{ {
return success ? TaskAction::StopWithDone : TaskAction::StopWithError; return success ? SetupResult::StopWithDone : SetupResult::StopWithError;
} }
bool TreeStorageBase::isValid() const bool TreeStorageBase::isValid() const
@@ -658,10 +761,10 @@ public:
TaskContainer(TaskTreePrivate *taskTreePrivate, const GroupItem &task, TaskContainer(TaskTreePrivate *taskTreePrivate, const GroupItem &task,
TaskNode *parentNode, TaskContainer *parentContainer) TaskNode *parentNode, TaskContainer *parentContainer)
: m_constData(taskTreePrivate, task, parentNode, parentContainer, this) {} : m_constData(taskTreePrivate, task, parentNode, parentContainer, this) {}
TaskAction start(); SetupResult start();
TaskAction continueStart(TaskAction startAction, int nextChild); SetupResult continueStart(SetupResult startAction, int nextChild);
TaskAction startChildren(int nextChild); SetupResult startChildren(int nextChild);
TaskAction childDone(bool success); SetupResult childDone(bool success);
void stop(); void stop();
void invokeEndHandler(); void invokeEndHandler();
bool isRunning() const { return m_runtimeData.has_value(); } bool isRunning() const { return m_runtimeData.has_value(); }
@@ -716,7 +819,7 @@ public:
// If returned value != Continue, childDone() needs to be called in parent container (in caller) // If returned value != Continue, childDone() needs to be called in parent container (in caller)
// in order to unwind properly. // in order to unwind properly.
TaskAction start(); SetupResult start();
void stop(); void stop();
void invokeEndHandler(bool success); void invokeEndHandler(bool success);
bool isRunning() const { return m_task || m_container.isRunning(); } bool isRunning() const { return m_task || m_container.isRunning(); }
@@ -945,31 +1048,34 @@ int TaskContainer::RuntimeData::currentLimit() const
? qMin(m_doneCount + m_constData.m_parallelLimit, childCount) : childCount; ? qMin(m_doneCount + m_constData.m_parallelLimit, childCount) : childCount;
} }
TaskAction TaskContainer::start() SetupResult TaskContainer::start()
{ {
QTC_CHECK(!isRunning()); QTC_CHECK(!isRunning());
m_runtimeData.emplace(m_constData); m_runtimeData.emplace(m_constData);
TaskAction startAction = TaskAction::Continue; SetupResult startAction = SetupResult::Continue;
if (m_constData.m_groupHandler.m_setupHandler) { if (m_constData.m_groupHandler.m_setupHandler) {
startAction = invokeHandler(this, m_constData.m_groupHandler.m_setupHandler); startAction = invokeHandler(this, m_constData.m_groupHandler.m_setupHandler);
if (startAction != TaskAction::Continue) if (startAction != SetupResult::Continue) {
m_constData.m_taskTreePrivate->advanceProgress(m_constData.m_taskCount); m_constData.m_taskTreePrivate->advanceProgress(m_constData.m_taskCount);
// Non-Continue SetupResult takes precedence over the workflow policy.
m_runtimeData->m_successBit = startAction == SetupResult::StopWithDone;
} }
if (startAction == TaskAction::Continue) { }
if (startAction == SetupResult::Continue) {
if (m_constData.m_children.isEmpty()) if (m_constData.m_children.isEmpty())
startAction = toTaskAction(m_runtimeData->m_successBit); startAction = toSetupResult(m_runtimeData->m_successBit);
} }
return continueStart(startAction, 0); return continueStart(startAction, 0);
} }
TaskAction TaskContainer::continueStart(TaskAction startAction, int nextChild) SetupResult TaskContainer::continueStart(SetupResult startAction, int nextChild)
{ {
const TaskAction groupAction = startAction == TaskAction::Continue ? startChildren(nextChild) const SetupResult groupAction = startAction == SetupResult::Continue ? startChildren(nextChild)
: startAction; : startAction;
QTC_CHECK(isRunning()); // TODO: superfluous QTC_CHECK(isRunning()); // TODO: superfluous
if (groupAction != TaskAction::Continue) { if (groupAction != SetupResult::Continue) {
const bool success = m_runtimeData->updateSuccessBit(groupAction == TaskAction::StopWithDone); const bool success = m_runtimeData->updateSuccessBit(groupAction == SetupResult::StopWithDone);
invokeEndHandler(); invokeEndHandler();
if (TaskContainer *parentContainer = m_constData.m_parentContainer) { if (TaskContainer *parentContainer = m_constData.m_parentContainer) {
QTC_CHECK(parentContainer->isRunning()); QTC_CHECK(parentContainer->isRunning());
@@ -984,7 +1090,7 @@ TaskAction TaskContainer::continueStart(TaskAction startAction, int nextChild)
return groupAction; return groupAction;
} }
TaskAction TaskContainer::startChildren(int nextChild) SetupResult TaskContainer::startChildren(int nextChild)
{ {
QTC_CHECK(isRunning()); QTC_CHECK(isRunning());
GuardLocker locker(m_runtimeData->m_startGuard); GuardLocker locker(m_runtimeData->m_startGuard);
@@ -993,12 +1099,12 @@ TaskAction TaskContainer::startChildren(int nextChild)
if (i >= limit) if (i >= limit)
break; break;
const TaskAction startAction = m_constData.m_children.at(i)->start(); const SetupResult startAction = m_constData.m_children.at(i)->start();
if (startAction == TaskAction::Continue) if (startAction == SetupResult::Continue)
continue; continue;
const TaskAction finalizeAction = childDone(startAction == TaskAction::StopWithDone); const SetupResult finalizeAction = childDone(startAction == SetupResult::StopWithDone);
if (finalizeAction == TaskAction::Continue) if (finalizeAction == SetupResult::Continue)
continue; continue;
int skippedTaskCount = 0; int skippedTaskCount = 0;
@@ -1008,10 +1114,10 @@ TaskAction TaskContainer::startChildren(int nextChild)
m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount); m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount);
return finalizeAction; return finalizeAction;
} }
return TaskAction::Continue; return SetupResult::Continue;
} }
TaskAction TaskContainer::childDone(bool success) SetupResult TaskContainer::childDone(bool success)
{ {
QTC_CHECK(isRunning()); QTC_CHECK(isRunning());
const int limit = m_runtimeData->currentLimit(); // Read before bumping m_doneCount and stop() const int limit = m_runtimeData->currentLimit(); // Read before bumping m_doneCount and stop()
@@ -1023,9 +1129,9 @@ TaskAction TaskContainer::childDone(bool success)
++m_runtimeData->m_doneCount; ++m_runtimeData->m_doneCount;
const bool updatedSuccess = m_runtimeData->updateSuccessBit(success); const bool updatedSuccess = m_runtimeData->updateSuccessBit(success);
const TaskAction startAction const SetupResult startAction
= (shouldStop || m_runtimeData->m_doneCount == m_constData.m_children.size()) = (shouldStop || m_runtimeData->m_doneCount == m_constData.m_children.size())
? toTaskAction(updatedSuccess) : TaskAction::Continue; ? toSetupResult(updatedSuccess) : SetupResult::Continue;
if (isStarting()) if (isStarting())
return startAction; return startAction;
@@ -1059,30 +1165,30 @@ void TaskContainer::invokeEndHandler()
m_runtimeData.reset(); m_runtimeData.reset();
} }
TaskAction TaskNode::start() SetupResult TaskNode::start()
{ {
QTC_CHECK(!isRunning()); QTC_CHECK(!isRunning());
if (!isTask()) if (!isTask())
return m_container.start(); return m_container.start();
m_task.reset(m_taskHandler.m_createHandler()); m_task.reset(m_taskHandler.m_createHandler());
const TaskAction startAction = m_taskHandler.m_setupHandler const SetupResult startAction = m_taskHandler.m_setupHandler
? invokeHandler(parentContainer(), m_taskHandler.m_setupHandler, *m_task.get()) ? invokeHandler(parentContainer(), m_taskHandler.m_setupHandler, *m_task.get())
: TaskAction::Continue; : SetupResult::Continue;
if (startAction != TaskAction::Continue) { if (startAction != SetupResult::Continue) {
m_container.m_constData.m_taskTreePrivate->advanceProgress(1); m_container.m_constData.m_taskTreePrivate->advanceProgress(1);
m_task.reset(); m_task.reset();
return startAction; return startAction;
} }
const std::shared_ptr<TaskAction> unwindAction const std::shared_ptr<SetupResult> unwindAction
= std::make_shared<TaskAction>(TaskAction::Continue); = std::make_shared<SetupResult>(SetupResult::Continue);
QObject::connect(m_task.get(), &TaskInterface::done, taskTree(), [=](bool success) { QObject::connect(m_task.get(), &TaskInterface::done, taskTree(), [=](bool success) {
invokeEndHandler(success); invokeEndHandler(success);
QObject::disconnect(m_task.get(), &TaskInterface::done, taskTree(), nullptr); QObject::disconnect(m_task.get(), &TaskInterface::done, taskTree(), nullptr);
m_task.release()->deleteLater(); m_task.release()->deleteLater();
QTC_ASSERT(parentContainer() && parentContainer()->isRunning(), return); QTC_ASSERT(parentContainer() && parentContainer()->isRunning(), return);
if (parentContainer()->isStarting()) if (parentContainer()->isStarting())
*unwindAction = toTaskAction(success); *unwindAction = toSetupResult(success);
else else
parentContainer()->childDone(success); parentContainer()->childDone(success);
}); });
@@ -1321,18 +1427,18 @@ void TaskNode::invokeEndHandler(bool success)
as the task tree calls it when needed. The setup handler is optional. When used, as the task tree calls it when needed. The setup handler is optional. When used,
it must be the first argument of the task's constructor. it must be the first argument of the task's constructor.
Optionally, the setup handler may return a TaskAction. The returned Optionally, the setup handler may return a SetupResult. The returned
TaskAction influences the further start behavior of a given task. The SetupResult influences the further start behavior of a given task. The
possible values are: possible values are:
\table \table
\header \header
\li TaskAction Value \li SetupResult Value
\li Brief Description \li Brief Description
\row \row
\li Continue \li Continue
\li The task will be started normally. This is the default behavior when the \li The task will be started normally. This is the default behavior when the
setup handler doesn't return TaskAction (that is, its return type is setup handler doesn't return SetupResult (that is, its return type is
void). void).
\row \row
\li StopWithDone \li StopWithDone
@@ -1404,12 +1510,12 @@ void TaskNode::invokeEndHandler(bool success)
handler. If you add more than one onGroupSetup() element to a group, an assert handler. If you add more than one onGroupSetup() element to a group, an assert
is triggered at runtime that includes an error message. is triggered at runtime that includes an error message.
Like the task's start handler, the group start handler may return TaskAction. Like the task's start handler, the group start handler may return SetupResult.
The returned TaskAction value affects the start behavior of the The returned SetupResult value affects the start behavior of the
whole group. If you do not specify a group start handler or its return type whole group. If you do not specify a group start handler or its return type
is void, the default group's action is TaskAction::Continue, so that all is void, the default group's action is SetupResult::Continue, so that all
tasks are started normally. Otherwise, when the start handler returns tasks are started normally. Otherwise, when the start handler returns
TaskAction::StopWithDone or TaskAction::StopWithError, the tasks are not SetupResult::StopWithDone or SetupResult::StopWithError, the tasks are not
started (they are skipped) and the group itself reports success or failure, started (they are skipped) and the group itself reports success or failure,
depending on the returned value, respectively. depending on the returned value, respectively.
@@ -1417,15 +1523,15 @@ void TaskNode::invokeEndHandler(bool success)
const Group root { const Group root {
onGroupSetup([] { qDebug() << "Root setup"; }), onGroupSetup([] { qDebug() << "Root setup"; }),
Group { Group {
onGroupSetup([] { qDebug() << "Group 1 setup"; return TaskAction::Continue; }), onGroupSetup([] { qDebug() << "Group 1 setup"; return SetupResult::Continue; }),
ProcessTask(...) // Process 1 ProcessTask(...) // Process 1
}, },
Group { Group {
onGroupSetup([] { qDebug() << "Group 2 setup"; return TaskAction::StopWithDone; }), onGroupSetup([] { qDebug() << "Group 2 setup"; return SetupResult::StopWithDone; }),
ProcessTask(...) // Process 2 ProcessTask(...) // Process 2
}, },
Group { Group {
onGroupSetup([] { qDebug() << "Group 3 setup"; return TaskAction::StopWithError; }), onGroupSetup([] { qDebug() << "Group 3 setup"; return SetupResult::StopWithError; }),
ProcessTask(...) // Process 3 ProcessTask(...) // Process 3
}, },
ProcessTask(...) // Process 4 ProcessTask(...) // Process 4
@@ -1441,7 +1547,7 @@ void TaskNode::invokeEndHandler(bool success)
\li Comment \li Comment
\row \row
\li Root Group starts \li Root Group starts
\li Doesn't return TaskAction, so its tasks are executed. \li Doesn't return SetupResult, so its tasks are executed.
\row \row
\li Group 1 starts \li Group 1 starts
\li Returns Continue, so its tasks are executed. \li Returns Continue, so its tasks are executed.
@@ -1833,7 +1939,7 @@ void TaskTree::setRecipe(const Group &recipe)
Otherwise, the task tree is started. Otherwise, the task tree is started.
The started task tree may finish synchronously, The started task tree may finish synchronously,
for example when the main group's start handler returns TaskAction::StopWithError. for example when the main group's start handler returns SetupResult::StopWithError.
For this reason, the connections to the done and errorOccurred signals should be For this reason, the connections to the done and errorOccurred signals should be
established before calling start. Use isRunning() in order to detect whether established before calling start. Use isRunning() in order to detect whether
the task tree is still running after a call to start(). the task tree is still running after a call to start().

View File

@@ -26,12 +26,14 @@ class TASKING_EXPORT TaskInterface : public QObject
{ {
Q_OBJECT Q_OBJECT
public:
TaskInterface() = default;
virtual void start() = 0;
signals: signals:
void done(bool success); void done(bool success);
private:
template <typename Task> friend class TaskAdapter;
friend class TaskNode;
TaskInterface() = default;
virtual void start() = 0;
}; };
class TASKING_EXPORT TreeStorageBase class TASKING_EXPORT TreeStorageBase
@@ -115,13 +117,13 @@ enum class WorkflowPolicy {
}; };
Q_ENUM_NS(WorkflowPolicy); Q_ENUM_NS(WorkflowPolicy);
enum class TaskAction enum class SetupResult
{ {
Continue, Continue,
StopWithDone, StopWithDone,
StopWithError StopWithError
}; };
Q_ENUM_NS(TaskAction); Q_ENUM_NS(SetupResult);
class TASKING_EXPORT GroupItem class TASKING_EXPORT GroupItem
{ {
@@ -129,11 +131,11 @@ public:
// Internal, provided by QTC_DECLARE_CUSTOM_TASK // Internal, provided by QTC_DECLARE_CUSTOM_TASK
using TaskCreateHandler = std::function<TaskInterface *(void)>; using TaskCreateHandler = std::function<TaskInterface *(void)>;
// Called prior to task start, just after createHandler // Called prior to task start, just after createHandler
using TaskSetupHandler = std::function<TaskAction(TaskInterface &)>; using TaskSetupHandler = std::function<SetupResult(TaskInterface &)>;
// Called on task done / error // Called on task done / error
using TaskEndHandler = std::function<void(const TaskInterface &)>; using TaskEndHandler = std::function<void(const TaskInterface &)>;
// Called when group entered // Called when group entered
using GroupSetupHandler = std::function<TaskAction()>; using GroupSetupHandler = std::function<SetupResult()>;
// Called when group done / error // Called when group done / error
using GroupEndHandler = std::function<void()>; using GroupEndHandler = std::function<void()>;
@@ -228,17 +230,17 @@ private:
static GroupSetupHandler wrapGroupSetup(SetupHandler &&handler) static GroupSetupHandler wrapGroupSetup(SetupHandler &&handler)
{ {
static constexpr bool isDynamic static constexpr bool isDynamic
= std::is_same_v<TaskAction, std::invoke_result_t<std::decay_t<SetupHandler>>>; = std::is_same_v<SetupResult, std::invoke_result_t<std::decay_t<SetupHandler>>>;
constexpr bool isVoid constexpr bool isVoid
= std::is_same_v<void, std::invoke_result_t<std::decay_t<SetupHandler>>>; = std::is_same_v<void, std::invoke_result_t<std::decay_t<SetupHandler>>>;
static_assert(isDynamic || isVoid, static_assert(isDynamic || isVoid,
"Group setup handler needs to take no arguments and has to return " "Group setup handler needs to take no arguments and has to return "
"void or TaskAction. The passed handler doesn't fulfill these requirements."); "void or SetupResult. The passed handler doesn't fulfill these requirements.");
return [=] { return [=] {
if constexpr (isDynamic) if constexpr (isDynamic)
return std::invoke(handler); return std::invoke(handler);
std::invoke(handler); std::invoke(handler);
return TaskAction::Continue; return SetupResult::Continue;
}; };
}; };
}; };
@@ -290,22 +292,24 @@ private:
static_assert(isBool || isVoid, static_assert(isBool || isVoid,
"Sync element: The synchronous function has to return void or bool."); "Sync element: The synchronous function has to return void or bool.");
if constexpr (isBool) { if constexpr (isBool) {
return {onGroupSetup([function] { return function() ? TaskAction::StopWithDone return {onGroupSetup([function] { return function() ? SetupResult::StopWithDone
: TaskAction::StopWithError; })}; : SetupResult::StopWithError; })};
} }
return {onGroupSetup([function] { function(); return TaskAction::StopWithDone; })}; return {onGroupSetup([function] { function(); return SetupResult::StopWithDone; })};
}; };
}; };
template <typename Task> template <typename Task>
class TaskAdapter : public TaskInterface class TaskAdapter : public TaskInterface
{ {
public: protected:
using Type = Task; using Type = Task;
TaskAdapter() = default; TaskAdapter() = default;
Task *task() { return &m_task; } Task *task() { return &m_task; }
const Task *task() const { return &m_task; } const Task *task() const { return &m_task; }
private: private:
template <typename Adapter> friend class CustomTask;
Task m_task; Task m_task;
}; };
@@ -344,19 +348,19 @@ public:
private: private:
template<typename SetupFunction> template<typename SetupFunction>
static GroupItem::TaskSetupHandler wrapSetup(SetupFunction &&function) { static GroupItem::TaskSetupHandler wrapSetup(SetupFunction &&function) {
static constexpr bool isDynamic = std::is_same_v<TaskAction, static constexpr bool isDynamic = std::is_same_v<SetupResult,
std::invoke_result_t<std::decay_t<SetupFunction>, typename Adapter::Type &>>; std::invoke_result_t<std::decay_t<SetupFunction>, typename Adapter::Type &>>;
constexpr bool isVoid = std::is_same_v<void, constexpr bool isVoid = std::is_same_v<void,
std::invoke_result_t<std::decay_t<SetupFunction>, typename Adapter::Type &>>; std::invoke_result_t<std::decay_t<SetupFunction>, typename Adapter::Type &>>;
static_assert(isDynamic || isVoid, static_assert(isDynamic || isVoid,
"Task setup handler needs to take (Task &) as an argument and has to return " "Task setup handler needs to take (Task &) as an argument and has to return "
"void or TaskAction. The passed handler doesn't fulfill these requirements."); "void or SetupResult. The passed handler doesn't fulfill these requirements.");
return [=](TaskInterface &taskInterface) { return [=](TaskInterface &taskInterface) {
Adapter &adapter = static_cast<Adapter &>(taskInterface); Adapter &adapter = static_cast<Adapter &>(taskInterface);
if constexpr (isDynamic) if constexpr (isDynamic)
return std::invoke(function, *adapter.task()); return std::invoke(function, *adapter.task());
std::invoke(function, *adapter.task()); std::invoke(function, *adapter.task());
return TaskAction::Continue; return SetupResult::Continue;
}; };
}; };

View File

@@ -1474,8 +1474,13 @@ void BoolAspect::addToLayout(Layouting::LayoutItem &parent)
d->m_button = createSubWidget<QCheckBox>(); d->m_button = createSubWidget<QCheckBox>();
} }
switch (d->m_labelPlacement) { switch (d->m_labelPlacement) {
case LabelPlacement::AtCheckBoxWithoutDummyLabel:
d->m_button->setText(labelText());
parent.addItem(d->m_button.data());
break;
case LabelPlacement::AtCheckBox: case LabelPlacement::AtCheckBox:
d->m_button->setText(labelText()); d->m_button->setText(labelText());
parent.addItem(empty());
parent.addItem(d->m_button.data()); parent.addItem(d->m_button.data());
break; break;
case LabelPlacement::InExtraLabel: case LabelPlacement::InExtraLabel:

View File

@@ -237,7 +237,7 @@ public:
bool defaultValue() const; bool defaultValue() const;
void setDefaultValue(bool val); void setDefaultValue(bool val);
enum class LabelPlacement { AtCheckBox, InExtraLabel }; enum class LabelPlacement { AtCheckBox, AtCheckBoxWithoutDummyLabel, InExtraLabel };
void setLabel(const QString &labelText, void setLabel(const QString &labelText,
LabelPlacement labelPlacement = LabelPlacement::InExtraLabel); LabelPlacement labelPlacement = LabelPlacement::InExtraLabel);
void setLabelPlacement(LabelPlacement labelPlacement); void setLabelPlacement(LabelPlacement labelPlacement);

View File

@@ -1444,16 +1444,15 @@ CommandLine CommandLine::fromUserInput(const QString &cmdline, MacroExpander *ex
QString input = cmdline.trimmed(); QString input = cmdline.trimmed();
QStringList result = ProcessArgs::splitArgs(cmdline, HostOsInfo::hostOs()); if (expander)
input = expander->expand(input);
const QStringList result = ProcessArgs::splitArgs(input, HostOsInfo::hostOs());
if (result.isEmpty()) if (result.isEmpty())
return {}; return {};
auto cmd = CommandLine(FilePath::fromUserInput(result.value(0)), result.mid(1)); return {FilePath::fromUserInput(result.value(0)), result.mid(1)};
if (expander)
cmd.m_arguments = expander->expand(cmd.m_arguments);
return cmd;
} }
void CommandLine::addArg(const QString &arg) void CommandLine::addArg(const QString &arg)

View File

@@ -229,6 +229,7 @@ struct ResultItem
int space = -1; int space = -1;
int stretch = -1; int stretch = -1;
int span = 1; int span = 1;
bool empty = false;
}; };
struct Slice struct Slice
@@ -287,6 +288,8 @@ static void addItemToBoxLayout(QBoxLayout *layout, const ResultItem &item)
layout->addSpacing(item.space); layout->addSpacing(item.space);
} else if (!item.text.isEmpty()) { } else if (!item.text.isEmpty()) {
layout->addWidget(createLabel(item.text)); layout->addWidget(createLabel(item.text));
} else if (item.empty) {
// Nothing to do, but no reason to warn, either.
} else { } else {
QTC_CHECK(false); QTC_CHECK(false);
} }
@@ -654,8 +657,8 @@ LayoutItem empty()
LayoutItem item; LayoutItem item;
item.onAdd = [](LayoutBuilder &builder) { item.onAdd = [](LayoutBuilder &builder) {
ResultItem ri; ResultItem ri;
ri.span = 1; ri.empty = true;
builder.stack.last().pendingItems.append(ResultItem()); builder.stack.last().pendingItems.append(ri);
}; };
return item; return item;
} }

View File

@@ -91,7 +91,7 @@ static CreateAvdInfo createAvdCommand(const AndroidConfig &config, const CreateA
proc.setCommand(avdManager); proc.setCommand(avdManager);
proc.start(); proc.start();
if (!proc.waitForStarted()) { if (!proc.waitForStarted()) {
result.error = Tr::tr("Could not start process \"%1\"").arg(avdManager.toUserOutput()); result.error = Tr::tr("Could not start process \"%1\".").arg(avdManager.toUserOutput());
return result; return result;
} }
QTC_CHECK(proc.isRunning()); QTC_CHECK(proc.isRunning());

View File

@@ -389,7 +389,7 @@ FilePath AndroidQmlPreviewWorker::createQmlrcFile(const FilePath &workFolder,
rccProcess.setCommand({rccBinary, args}); rccProcess.setCommand({rccBinary, args});
rccProcess.start(); rccProcess.start();
if (!rccProcess.waitForStarted()) { if (!rccProcess.waitForStarted()) {
appendMessage(Tr::tr("Could not create file for %1 \"%2\""). appendMessage(Tr::tr("Could not create file for %1 \"%2\".").
arg(apkInfo()->name, rccProcess.commandLine().toUserOutput()), arg(apkInfo()->name, rccProcess.commandLine().toUserOutput()),
StdErrFormat); StdErrFormat);
qrcPath.removeFile(); qrcPath.removeFile();
@@ -400,7 +400,7 @@ FilePath AndroidQmlPreviewWorker::createQmlrcFile(const FilePath &workFolder,
if (!rccProcess.readDataFromProcess(&stdOut, &stdErr)) { if (!rccProcess.readDataFromProcess(&stdOut, &stdErr)) {
rccProcess.stop(); rccProcess.stop();
rccProcess.waitForFinished(); rccProcess.waitForFinished();
appendMessage(Tr::tr("A timeout occurred running \"%1\""). appendMessage(Tr::tr("A timeout occurred running \"%1\".").
arg(rccProcess.commandLine().toUserOutput()), StdErrFormat); arg(rccProcess.commandLine().toUserOutput()), StdErrFormat);
qrcPath.removeFile(); qrcPath.removeFile();
return {}; return {};
@@ -412,7 +412,7 @@ FilePath AndroidQmlPreviewWorker::createQmlrcFile(const FilePath &workFolder,
appendMessage(QString::fromLocal8Bit(stdErr), StdErrFormat); appendMessage(QString::fromLocal8Bit(stdErr), StdErrFormat);
if (rccProcess.exitStatus() != QProcess::NormalExit) { if (rccProcess.exitStatus() != QProcess::NormalExit) {
appendMessage(Tr::tr("Crash while creating file for %1 \"%2\""). appendMessage(Tr::tr("Crash while creating file for %1 \"%2\".").
arg(apkInfo()->name, rccProcess.commandLine().toUserOutput()), arg(apkInfo()->name, rccProcess.commandLine().toUserOutput()),
StdErrFormat); StdErrFormat);
qrcPath.removeFile(); qrcPath.removeFile();

View File

@@ -158,20 +158,20 @@ void AndroidSdkDownloader::downloadAndExtractSdk()
m_progressDialog->setRange(0, 0); m_progressDialog->setRange(0, 0);
m_progressDialog->setLabelText(Tr::tr("Unarchiving SDK Tools package...")); m_progressDialog->setLabelText(Tr::tr("Unarchiving SDK Tools package..."));
if (!*storage) if (!*storage)
return TaskAction::StopWithError; return SetupResult::StopWithError;
const FilePath sdkFileName = **storage; const FilePath sdkFileName = **storage;
if (!verifyFileIntegrity(sdkFileName, m_androidConfig.getSdkToolsSha256())) { if (!verifyFileIntegrity(sdkFileName, m_androidConfig.getSdkToolsSha256())) {
logError(Tr::tr("Verifying the integrity of the downloaded file has failed.")); logError(Tr::tr("Verifying the integrity of the downloaded file has failed."));
return TaskAction::StopWithError; return SetupResult::StopWithError;
} }
const auto sourceAndCommand = Unarchiver::sourceAndCommand(sdkFileName); const auto sourceAndCommand = Unarchiver::sourceAndCommand(sdkFileName);
if (!sourceAndCommand) { if (!sourceAndCommand) {
logError(sourceAndCommand.error()); logError(sourceAndCommand.error());
return TaskAction::StopWithError; return SetupResult::StopWithError;
} }
unarchiver.setSourceAndCommand(*sourceAndCommand); unarchiver.setSourceAndCommand(*sourceAndCommand);
unarchiver.setDestDir(sdkFileName.parentDir()); unarchiver.setDestDir(sdkFileName.parentDir());
return TaskAction::Continue; return SetupResult::Continue;
}; };
const auto onUnarchiverDone = [this, storage](const Unarchiver &) { const auto onUnarchiverDone = [this, storage](const Unarchiver &) {
m_androidConfig.setTemporarySdkToolsPath( m_androidConfig.setTemporarySdkToolsPath(

View File

@@ -102,8 +102,13 @@ QStringList BoostTestConfiguration::argumentsForTestRunner(QStringList *omitted)
arguments << "--detect_memory_leaks=0"; arguments << "--detect_memory_leaks=0";
// TODO improve the test case gathering and arguments building to avoid too long command lines // TODO improve the test case gathering and arguments building to avoid too long command lines
if (isDebugRunMode()) { // debugger has its own quoting
for (const QString &test : testCases()) for (const QString &test : testCases())
arguments << "-t" << test; arguments << "-t" << test;
} else {
for (const QString &test : testCases())
arguments << "-t" << "\"" + test + "\"";
}
if (TestSettings::instance()->processArgs()) { if (TestSettings::instance()->processArgs()) {
arguments << filterInterfering(runnable().command.arguments().split( arguments << filterInterfering(runnable().command.arguments().split(

View File

@@ -31,12 +31,6 @@ BoostTestOutputReader::BoostTestOutputReader(Process *testApplication,
, m_logLevel(log) , m_logLevel(log)
, m_reportLevel(report) , m_reportLevel(report)
{ {
if (!testApplication)
return;
connect(testApplication, &Process::done, this, [this, testApplication] {
onDone(testApplication->exitCode());
});
} }
// content of "error:..." / "info:..." / ... messages // content of "error:..." / "info:..." / ... messages
@@ -147,7 +141,7 @@ void BoostTestOutputReader::handleMessageMatch(const QRegularExpressionMatch &ma
if (m_currentTest != match.captured(11) && m_currentTest.isEmpty()) if (m_currentTest != match.captured(11) && m_currentTest.isEmpty())
m_currentTest = match.captured(11); m_currentTest = match.captured(11);
m_result = ResultType::TestEnd; m_result = ResultType::TestEnd;
m_description = Tr::tr("Test execution took %1").arg(match.captured(12)); m_description = Tr::tr("Test execution took %1.").arg(match.captured(12));
} else if (type == "suite") { } else if (type == "suite") {
if (!m_currentSuite.isEmpty()) { if (!m_currentSuite.isEmpty()) {
int index = m_currentSuite.lastIndexOf('/'); int index = m_currentSuite.lastIndexOf('/');
@@ -163,7 +157,7 @@ void BoostTestOutputReader::handleMessageMatch(const QRegularExpressionMatch &ma
} }
m_currentTest.clear(); m_currentTest.clear();
m_result = ResultType::TestEnd; m_result = ResultType::TestEnd;
m_description = Tr::tr("Test suite execution took %1").arg(match.captured(12)); m_description = Tr::tr("Test suite execution took %1.").arg(match.captured(12));
} }
} else if (content.startsWith("Test case ")) { } else if (content.startsWith("Test case ")) {
m_currentTest = match.captured(4); m_currentTest = match.captured(4);
@@ -247,7 +241,7 @@ void BoostTestOutputReader::processOutputLine(const QByteArray &outputLine)
} else { } else {
QTC_CHECK(m_currentModule == match.captured(3)); QTC_CHECK(m_currentModule == match.captured(3));
BoostTestResult result(id(), m_currentModule, m_projectFile); BoostTestResult result(id(), m_currentModule, m_projectFile);
result.setDescription(Tr::tr("Test module execution took %1").arg(match.captured(4))); result.setDescription(Tr::tr("Test module execution took %1.").arg(match.captured(4)));
result.setResult(ResultType::TestEnd); result.setResult(ResultType::TestEnd);
reportResult(result); reportResult(result);
@@ -394,17 +388,17 @@ void BoostTestOutputReader::onDone(int exitCode)
if (m_logLevel == LogLevel::Nothing && m_reportLevel == ReportLevel::No) { if (m_logLevel == LogLevel::Nothing && m_reportLevel == ReportLevel::No) {
switch (exitCode) { switch (exitCode) {
case 0: case 0:
reportNoOutputFinish(Tr::tr("Running tests exited with %1").arg("boost::exit_success."), reportNoOutputFinish(Tr::tr("Running tests exited with %1.").arg("boost::exit_success"),
ResultType::Pass); ResultType::Pass);
break; break;
case 200: case 200:
reportNoOutputFinish( reportNoOutputFinish(
Tr::tr("Running tests exited with %1").arg("boost::exit_test_exception."), Tr::tr("Running tests exited with %1.").arg("boost::exit_test_exception"),
ResultType::MessageFatal); ResultType::MessageFatal);
break; break;
case 201: case 201:
reportNoOutputFinish(Tr::tr("Running tests exited with %1") reportNoOutputFinish(Tr::tr("Running tests exited with %1.")
.arg("boost::exit_test_failure."), ResultType::Fail); .arg("boost::exit_test_failure"), ResultType::Fail);
break; break;
} }
} else if (exitCode != 0 && exitCode != 201 && !m_description.isEmpty()) { } else if (exitCode != 0 && exitCode != 201 && !m_description.isEmpty()) {

View File

@@ -23,7 +23,7 @@ protected:
TestResult createDefaultResult() const override; TestResult createDefaultResult() const override;
private: private:
void onDone(int exitCode); void onDone(int exitCode) override;
void sendCompleteInformation(); void sendCompleteInformation();
void handleMessageMatch(const QRegularExpressionMatch &match); void handleMessageMatch(const QRegularExpressionMatch &match);
void reportNoOutputFinish(const QString &description, ResultType type); void reportNoOutputFinish(const QString &description, ResultType type);

View File

@@ -90,7 +90,7 @@ void CTestOutputReader::processOutputLine(const QByteArray &outputLine)
m_project = match.captured(1); m_project = match.captured(1);
TestResult testResult = createDefaultResult(); TestResult testResult = createDefaultResult();
testResult.setResult(ResultType::TestStart); testResult.setResult(ResultType::TestStart);
testResult.setDescription(Tr::tr("Running tests for %1").arg(m_project)); testResult.setDescription(Tr::tr("Running tests for \"%1\".").arg(m_project));
reportResult(testResult); reportResult(testResult);
} else if (ExactMatch match = testCase1.match(line)) { } else if (ExactMatch match = testCase1.match(line)) {
int current = match.captured("current").toInt(); int current = match.captured("current").toInt();

View File

@@ -23,18 +23,6 @@ GTestOutputReader::GTestOutputReader(Process *testApplication,
: TestOutputReader(testApplication, buildDirectory) : TestOutputReader(testApplication, buildDirectory)
, m_projectFile(projectFile) , m_projectFile(projectFile)
{ {
if (testApplication) {
connect(testApplication, &Process::done, this, [this, testApplication] {
const int exitCode = testApplication->exitCode();
if (exitCode == 1 && !m_description.isEmpty()) {
createAndReportResult(Tr::tr("Running tests failed.\n %1\nExecutable: %2")
.arg(m_description).arg(id()), ResultType::MessageFatal);
}
// on Windows abort() will result in normal termination, but exit code will be set to 3
if (HostOsInfo::isWindowsHost() && exitCode == 3)
reportCrash();
});
}
} }
void GTestOutputReader::processOutputLine(const QByteArray &outputLine) void GTestOutputReader::processOutputLine(const QByteArray &outputLine)
@@ -81,7 +69,7 @@ void GTestOutputReader::processOutputLine(const QByteArray &outputLine)
if (ExactMatch match = testEnds.match(line)) { if (ExactMatch match = testEnds.match(line)) {
TestResult testResult = createDefaultResult(); TestResult testResult = createDefaultResult();
testResult.setResult(ResultType::TestEnd); testResult.setResult(ResultType::TestEnd);
testResult.setDescription(Tr::tr("Test execution took %1").arg(match.captured(2))); testResult.setDescription(Tr::tr("Test execution took %1.").arg(match.captured(2)));
reportResult(testResult); reportResult(testResult);
m_currentTestSuite.clear(); m_currentTestSuite.clear();
m_currentTestCase.clear(); m_currentTestCase.clear();
@@ -182,6 +170,17 @@ TestResult GTestOutputReader::createDefaultResult() const
return result; return result;
} }
void GTestOutputReader::onDone(int exitCode)
{
if (exitCode == 1 && !m_description.isEmpty()) {
createAndReportResult(Tr::tr("Running tests failed.\n %1\nExecutable: %2")
.arg(m_description).arg(id()), ResultType::MessageFatal);
}
// on Windows abort() will result in normal termination, but exit code will be set to 3
if (HostOsInfo::isWindowsHost() && exitCode == 3)
reportCrash();
}
void GTestOutputReader::setCurrentTestCase(const QString &testCase) void GTestOutputReader::setCurrentTestCase(const QString &testCase)
{ {
m_currentTestCase = testCase; m_currentTestCase = testCase;

View File

@@ -19,6 +19,7 @@ protected:
TestResult createDefaultResult() const override; TestResult createDefaultResult() const override;
private: private:
void onDone(int exitCode) override;
void setCurrentTestCase(const QString &testCase); void setCurrentTestCase(const QString &testCase);
void setCurrentTestSuite(const QString &testSuite); void setCurrentTestSuite(const QString &testSuite);
void handleDescriptionAndReportResult(const TestResult &testResult); void handleDescriptionAndReportResult(const TestResult &testResult);

View File

@@ -28,6 +28,8 @@ public:
void setId(const QString &id) { m_id = id; } void setId(const QString &id) { m_id = id; }
QString id() const { return m_id; } QString id() const { return m_id; }
virtual void onDone(int exitCode) { Q_UNUSED(exitCode) }
void resetCommandlineColor(); void resetCommandlineColor();
signals: signals:
void newResult(const TestResult &result); void newResult(const TestResult &result);

View File

@@ -355,13 +355,13 @@ void TestRunner::runTestsHelper()
const auto onSetup = [this, config] { const auto onSetup = [this, config] {
if (!config->project()) if (!config->project())
return TaskAction::StopWithDone; return SetupResult::StopWithDone;
if (config->testExecutable().isEmpty()) { if (config->testExecutable().isEmpty()) {
reportResult(ResultType::MessageFatal, reportResult(ResultType::MessageFatal,
Tr::tr("Executable path is empty. (%1)").arg(config->displayName())); Tr::tr("Executable path is empty. (%1)").arg(config->displayName()));
return TaskAction::StopWithDone; return SetupResult::StopWithDone;
} }
return TaskAction::Continue; return SetupResult::Continue;
}; };
const auto onProcessSetup = [this, config, storage](Process &process) { const auto onProcessSetup = [this, config, storage](Process &process) {
TestStorage *testStorage = storage.activeStorage(); TestStorage *testStorage = storage.activeStorage();
@@ -419,6 +419,9 @@ void TestRunner::runTestsHelper()
+ processInformation(&process) + rcInfo(config)); + processInformation(&process) + rcInfo(config));
} }
if (testStorage->m_outputReader)
testStorage->m_outputReader->onDone(process.exitCode());
if (process.exitStatus() == QProcess::CrashExit) { if (process.exitStatus() == QProcess::CrashExit) {
if (testStorage->m_outputReader) if (testStorage->m_outputReader)
testStorage->m_outputReader->reportCrash(); testStorage->m_outputReader->reportCrash();

View File

@@ -11,6 +11,7 @@
#include <utils/utilsicons.h> #include <utils/utilsicons.h>
#include <QFormLayout> #include <QFormLayout>
#include <QGridLayout>
#include <QLabel> #include <QLabel>
#include <QScrollArea> #include <QScrollArea>
#include <QStackedWidget> #include <QStackedWidget>
@@ -28,7 +29,8 @@ public:
private: private:
QLabel *m_project = nullptr; QLabel *m_project = nullptr;
QLabel *m_loc = nullptr; QLabel *m_loc = nullptr;
QFormLayout *m_formLayout = nullptr; QLabel *m_timestamp = nullptr;
QGridLayout *m_gridLayout = nullptr;
}; };
DashboardWidget::DashboardWidget(QWidget *parent) DashboardWidget::DashboardWidget(QWidget *parent)
@@ -41,58 +43,98 @@ DashboardWidget::DashboardWidget(QWidget *parent)
projectLayout->addRow(Tr::tr("Project:"), m_project); projectLayout->addRow(Tr::tr("Project:"), m_project);
m_loc = new QLabel(this); m_loc = new QLabel(this);
projectLayout->addRow(Tr::tr("Lines of code:"), m_loc); 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->addLayout(projectLayout);
m_formLayout = new QFormLayout; layout->addSpacing(10);
layout->addLayout(m_formLayout); auto row = new QHBoxLayout;
m_gridLayout = new QGridLayout;
row->addLayout(m_gridLayout);
row->addStretch(1);
layout->addLayout(row);
layout->addStretch(1);
setWidget(widget); setWidget(widget);
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
setWidgetResizable(true); setWidgetResizable(true);
} }
static QPixmap trendIcon(int added, int removed)
{
static const QPixmap unchanged = Utils::Icons::NEXT.pixmap();
static const QPixmap increased = Utils::Icon(
{ {":/utils/images/arrowup.png", Utils::Theme::IconsErrorColor} }).pixmap();
static const QPixmap decreased = Utils::Icon(
{ {":/utils/images/arrowdown.png", Utils::Theme::IconsRunColor} }).pixmap();
if (added == removed)
return unchanged;
return added < removed ? decreased : increased;
}
void DashboardWidget::updateUi() void DashboardWidget::updateUi()
{ {
const ProjectInfo &info = AxivionPlugin::projectInfo(); const ProjectInfo &info = AxivionPlugin::projectInfo();
m_project->setText(info.name); m_project->setText(info.name);
m_loc->setText({}); m_loc->setText({});
while (m_formLayout->rowCount()) m_timestamp->setText({});
m_formLayout->removeRow(0); QLayoutItem *child;
while ((child = m_gridLayout->takeAt(0)) != nullptr) {
delete child->widget();
delete child;
}
if (info.versions.isEmpty()) if (info.versions.isEmpty())
return; return;
const ResultVersion &last = info.versions.last(); const ResultVersion &last = info.versions.last();
m_loc->setText(QString::number(last.linesOfCode)); m_loc->setText(QString::number(last.linesOfCode));
const QDateTime timeStamp = QDateTime::fromString(last.timeStamp, Qt::ISODate);
m_timestamp->setText(timeStamp.isValid() ? timeStamp.toString("yyyy-MM-dd HH::mm::ss")
: Tr::tr("unknown"));
const QString tmpl("%1 %2 +%3 / -%4");
auto apply = [&tmpl](int t, int a, int r){
QChar tr = (a == r ? '=' : (a < r ? '^' : 'v'));
return tmpl.arg(t, 10, 10, QLatin1Char(' ')).arg(tr).arg(a, 5, 10, QLatin1Char(' '))
.arg(r, 5, 10, QLatin1Char(' '));
};
const QList<IssueKind> &issueKinds = info.issueKinds; const QList<IssueKind> &issueKinds = info.issueKinds;
auto toolTip = [issueKinds](const QString &prefix){ auto toolTip = [issueKinds](const QString &prefix){
for (const IssueKind &kind : issueKinds) { for (const IssueKind &kind : issueKinds) {
if (kind.prefix == prefix) if (kind.prefix == prefix)
return kind.nicePlural; return kind.nicePlural;
} }
return QString(); return prefix;
}; };
int allTotal = 0, allAdded = 0, allRemoved = 0; auto addValuesWidgets = [this, &toolTip](const IssueCount &issueCount, int row){
const QString currentToolTip = toolTip(issueCount.issueKind);
QLabel *label = new QLabel(issueCount.issueKind, this);
label->setToolTip(currentToolTip);
m_gridLayout->addWidget(label, row, 0);
label = new QLabel(QString::number(issueCount.total), this);
label->setToolTip(currentToolTip);
label->setAlignment(Qt::AlignRight);
m_gridLayout->addWidget(label, row, 1);
label = new QLabel(this);
label->setPixmap(trendIcon(issueCount.added, issueCount.removed));
label->setToolTip(currentToolTip);
m_gridLayout->addWidget(label, row, 2);
label = new QLabel('+' + QString::number(issueCount.added));
label->setAlignment(Qt::AlignRight);
label->setToolTip(currentToolTip);
m_gridLayout->addWidget(label, row, 3);
label = new QLabel("/");
label->setToolTip(currentToolTip);
m_gridLayout->addWidget(label, row, 4);
label = new QLabel('-' + QString::number(issueCount.removed));
label->setAlignment(Qt::AlignRight);
label->setToolTip(currentToolTip);
m_gridLayout->addWidget(label, row, 5);
};
int allTotal = 0, allAdded = 0, allRemoved = 0, row = 0;
for (auto issueCount : std::as_const(last.issueCounts)) { for (auto issueCount : std::as_const(last.issueCounts)) {
allTotal += issueCount.total; allTotal += issueCount.total;
allAdded += issueCount.added; allAdded += issueCount.added;
allRemoved += issueCount.removed; allRemoved += issueCount.removed;
const QString txt = apply(issueCount.total, issueCount.added, issueCount.removed); addValuesWidgets(issueCount, row);
const QString currentToolTip = toolTip(issueCount.issueKind); ++row;
QLabel *label = new QLabel(issueCount.issueKind, this);
label->setToolTip(currentToolTip);
QLabel *values = new QLabel(txt, this);
values->setToolTip(currentToolTip);
m_formLayout->addRow(label, values);
} }
QLabel *label = new QLabel(apply(allTotal, allAdded, allRemoved), this); const IssueCount total{{}, Tr::tr("Total:"), allTotal, allAdded, allRemoved};
m_formLayout->addRow(Tr::tr("Total:"), label); addValuesWidgets(total, row);
} }
AxivionOutputPane::AxivionOutputPane(QObject *parent) AxivionOutputPane::AxivionOutputPane(QObject *parent)

View File

@@ -43,7 +43,7 @@ Group QdbStopApplicationStep::deployRecipe()
const auto device = DeviceKitAspect::device(target()->kit()); const auto device = DeviceKitAspect::device(target()->kit());
if (!device) { if (!device) {
addErrorMessage(Tr::tr("No device to stop the application on.")); addErrorMessage(Tr::tr("No device to stop the application on."));
return TaskAction::StopWithError; return SetupResult::StopWithError;
} }
QTC_CHECK(device); QTC_CHECK(device);
process.setCommand({device->filePath(Constants::AppcontrollerFilepath), {"--stop"}}); process.setCommand({device->filePath(Constants::AppcontrollerFilepath), {"--stop"}});
@@ -52,7 +52,7 @@ Group QdbStopApplicationStep::deployRecipe()
connect(proc, &Process::readyReadStandardOutput, this, [this, proc] { connect(proc, &Process::readyReadStandardOutput, this, [this, proc] {
handleStdOutData(proc->readAllStandardOutput()); handleStdOutData(proc->readAllStandardOutput());
}); });
return TaskAction::Continue; return SetupResult::Continue;
}; };
const auto doneHandler = [this](const Process &) { const auto doneHandler = [this](const Process &) {
addProgressMessage(Tr::tr("Stopped the running application.")); addProgressMessage(Tr::tr("Stopped the running application."));

View File

@@ -128,23 +128,23 @@ GroupItem clangToolTask(const AnalyzeInputData &input,
const auto onSetup = [=] { const auto onSetup = [=] {
if (setupHandler && !setupHandler()) if (setupHandler && !setupHandler())
return TaskAction::StopWithError; return SetupResult::StopWithError;
ClangToolStorage *data = storage.activeStorage(); ClangToolStorage *data = storage.activeStorage();
data->name = clangToolName(input.tool); data->name = clangToolName(input.tool);
data->executable = toolExecutable(input.tool); data->executable = toolExecutable(input.tool);
if (!data->executable.isExecutableFile()) { if (!data->executable.isExecutableFile()) {
qWarning() << "Can't start:" << data->executable << "as" << data->name; qWarning() << "Can't start:" << data->executable << "as" << data->name;
return TaskAction::StopWithError; return SetupResult::StopWithError;
} }
QTC_CHECK(!input.unit.arguments.contains(QLatin1String("-o"))); QTC_CHECK(!input.unit.arguments.contains(QLatin1String("-o")));
QTC_CHECK(!input.unit.arguments.contains(input.unit.file.nativePath())); QTC_CHECK(!input.unit.arguments.contains(input.unit.file.nativePath()));
QTC_ASSERT(input.unit.file.exists(), return TaskAction::StopWithError); QTC_ASSERT(input.unit.file.exists(), return SetupResult::StopWithError);
data->outputFilePath = createOutputFilePath(input.outputDirPath, input.unit.file); data->outputFilePath = createOutputFilePath(input.outputDirPath, input.unit.file);
QTC_ASSERT(!data->outputFilePath.isEmpty(), return TaskAction::StopWithError); QTC_ASSERT(!data->outputFilePath.isEmpty(), return SetupResult::StopWithError);
return TaskAction::Continue; return SetupResult::Continue;
}; };
const auto onProcessSetup = [=](Process &process) { const auto onProcessSetup = [=](Process &process) {
process.setEnvironment(input.environment); process.setEnvironment(input.environment);

View File

@@ -12,6 +12,10 @@
#include "cmakeprojectmanagertr.h" #include "cmakeprojectmanagertr.h"
#include "cmaketool.h" #include "cmaketool.h"
#include <android/androidconstants.h>
#include <ios/iosconstants.h>
#include <coreplugin/find/itemviewfind.h> #include <coreplugin/find/itemviewfind.h>
#include <projectexplorer/buildsteplist.h> #include <projectexplorer/buildsteplist.h>
#include <projectexplorer/devicesupport/idevice.h> #include <projectexplorer/devicesupport/idevice.h>
@@ -173,13 +177,15 @@ static QString initialStagingDir(Kit *kit)
return QString::fromUtf8("/tmp/Qt-Creator-staging-" + ba); return QString::fromUtf8("/tmp/Qt-Creator-staging-" + ba);
} }
static bool buildAndRunOnSameDevice(Kit *kit) static bool supportsStageForInstallation(const Kit *kit)
{ {
IDeviceConstPtr runDevice = DeviceKitAspect::device(kit); IDeviceConstPtr runDevice = DeviceKitAspect::device(kit);
IDeviceConstPtr buildDevice = BuildDeviceKitAspect::device(kit); IDeviceConstPtr buildDevice = BuildDeviceKitAspect::device(kit);
QTC_ASSERT(runDevice, return false); QTC_ASSERT(runDevice, return false);
QTC_ASSERT(buildDevice, return false); QTC_ASSERT(buildDevice, return false);
return runDevice->id() == buildDevice->id(); return runDevice->id() != buildDevice->id()
&& runDevice->type() != Android::Constants::ANDROID_DEVICE_TYPE
&& runDevice->type() != Ios::Constants::IOS_DEVICE_TYPE;
} }
CMakeBuildStep::CMakeBuildStep(BuildStepList *bsl, Id id) : CMakeBuildStep::CMakeBuildStep(BuildStepList *bsl, Id id) :
@@ -198,7 +204,7 @@ CMakeBuildStep::CMakeBuildStep(BuildStepList *bsl, Id id) :
m_useStaging = addAspect<BoolAspect>(); m_useStaging = addAspect<BoolAspect>();
m_useStaging->setSettingsKey(USE_STAGING_KEY); m_useStaging->setSettingsKey(USE_STAGING_KEY);
m_useStaging->setLabel(Tr::tr("Stage for installation"), BoolAspect::LabelPlacement::AtCheckBox); m_useStaging->setLabel(Tr::tr("Stage for installation"), BoolAspect::LabelPlacement::AtCheckBox);
m_useStaging->setDefaultValue(!buildAndRunOnSameDevice(kit())); m_useStaging->setDefaultValue(supportsStageForInstallation(kit()));
m_stagingDir = addAspect<FilePathAspect>(); m_stagingDir = addAspect<FilePathAspect>();
m_stagingDir->setSettingsKey(STAGING_DIR_KEY); m_stagingDir->setSettingsKey(STAGING_DIR_KEY);

View File

@@ -30,6 +30,8 @@ AuthWidget::AuthWidget(QWidget *parent)
m_progressIndicator->setVisible(false); m_progressIndicator->setVisible(false);
m_statusLabel = new QLabel(); m_statusLabel = new QLabel();
m_statusLabel->setVisible(false); m_statusLabel->setVisible(false);
m_statusLabel->setTextInteractionFlags(Qt::TextInteractionFlag::TextSelectableByMouse
| Qt::TextInteractionFlag::TextSelectableByKeyboard);
// clang-format off // clang-format off
Column { Column {

View File

@@ -12,6 +12,8 @@ const char COPILOT_TOGGLE[] = "Copilot.Toggle";
const char COPILOT_ENABLE[] = "Copilot.Enable"; const char COPILOT_ENABLE[] = "Copilot.Enable";
const char COPILOT_DISABLE[] = "Copilot.Disable"; const char COPILOT_DISABLE[] = "Copilot.Disable";
const char COPILOT_REQUEST_SUGGESTION[] = "Copilot.RequestSuggestion"; const char COPILOT_REQUEST_SUGGESTION[] = "Copilot.RequestSuggestion";
const char COPILOT_NEXT_SUGGESTION[] = "Copilot.NextSuggestion";
const char COPILOT_PREVIOUS_SUGGESTION[] = "Copilot.PreviousSuggestion";
const char COPILOT_GENERAL_OPTIONS_ID[] = "Copilot.General"; const char COPILOT_GENERAL_OPTIONS_ID[] = "Copilot.General";
const char COPILOT_GENERAL_OPTIONS_CATEGORY[] = "ZY.Copilot"; const char COPILOT_GENERAL_OPTIONS_CATEGORY[] = "ZY.Copilot";

View File

@@ -41,16 +41,15 @@ public:
}); });
// clang-format off // clang-format off
helpLabel->setText(Tr::tr(R"( helpLabel->setText(Tr::tr(
The Copilot plugin requires node.js and the Copilot neovim plugin. "The Copilot plugin requires node.js and the Copilot neovim plugin. "
If you install the neovim plugin as described in the "If you install the neovim plugin as described in %1, "
[README.md](https://github.com/github/copilot.vim), "the plugin will find the agent.js file automatically.\n\n"
the plugin will find the agent.js file automatically. "Otherwise you need to specify the path to the %2 "
"file from the Copilot neovim plugin.",
Otherwise you need to specify the path to the "Markdown text for the copilot instruction label").arg(
[agent.js](https://github.com/github/copilot.vim/tree/release/copilot/dist) "[README.md](https://github.com/github/copilot.vim)",
file from the Copilot neovim plugin. "[agent.js](https://github.com/github/copilot.vim/tree/release/copilot/dist)"));
)", "Markdown text for the copilot instruction label"));
Column { Column {
authWidget, br, authWidget, br,

View File

@@ -9,6 +9,7 @@
#include "copilotoptionspage.h" #include "copilotoptionspage.h"
#include "copilotprojectpanel.h" #include "copilotprojectpanel.h"
#include "copilotsettings.h" #include "copilotsettings.h"
#include "copilotsuggestion.h"
#include "copilottr.h" #include "copilottr.h"
#include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/actionmanager/actionmanager.h>
@@ -20,6 +21,7 @@
#include <projectexplorer/projectpanelfactory.h> #include <projectexplorer/projectpanelfactory.h>
#include <texteditor/textdocumentlayout.h>
#include <texteditor/texteditor.h> #include <texteditor/texteditor.h>
#include <QAction> #include <QAction>
@@ -32,6 +34,28 @@ using namespace ProjectExplorer;
namespace Copilot { namespace Copilot {
namespace Internal { namespace Internal {
enum Direction { Previous, Next };
void cycleSuggestion(TextEditor::TextEditorWidget *editor, Direction direction)
{
QTextBlock block = editor->textCursor().block();
if (auto *suggestion = dynamic_cast<CopilotSuggestion *>(
TextEditor::TextDocumentLayout::suggestion(block))) {
int index = suggestion->currentCompletion();
if (direction == Previous)
--index;
else
++index;
if (index < 0)
index = suggestion->completions().count() - 1;
else if (index >= suggestion->completions().count())
index = 0;
suggestion->reset();
editor->insertSuggestion(std::make_unique<CopilotSuggestion>(suggestion->completions(),
editor->document(),
index));
}
}
void CopilotPlugin::initialize() void CopilotPlugin::initialize()
{ {
CopilotSettings::instance().readSettings(ICore::settings()); CopilotSettings::instance().readSettings(ICore::settings());
@@ -57,6 +81,30 @@ void CopilotPlugin::initialize()
ActionManager::registerAction(requestAction, Constants::COPILOT_REQUEST_SUGGESTION); ActionManager::registerAction(requestAction, Constants::COPILOT_REQUEST_SUGGESTION);
QAction *nextSuggestionAction = new QAction(this);
nextSuggestionAction->setText(Tr::tr("Show next Copilot Suggestion"));
nextSuggestionAction->setToolTip(Tr::tr(
"Cycles through the received Copilot Suggestions showing the next available Suggestion."));
connect(nextSuggestionAction, &QAction::triggered, this, [] {
if (auto editor = TextEditor::TextEditorWidget::currentTextEditorWidget())
cycleSuggestion(editor, Next);
});
ActionManager::registerAction(nextSuggestionAction, Constants::COPILOT_NEXT_SUGGESTION);
QAction *previousSuggestionAction = new QAction(this);
previousSuggestionAction->setText(Tr::tr("Show previos Copilot Suggestion"));
previousSuggestionAction->setToolTip(Tr::tr("Cycles through the received Copilot Suggestions "
"showing the previous available Suggestion."));
connect(previousSuggestionAction, &QAction::triggered, this, [] {
if (auto editor = TextEditor::TextEditorWidget::currentTextEditorWidget())
cycleSuggestion(editor, Previous);
});
ActionManager::registerAction(previousSuggestionAction, Constants::COPILOT_PREVIOUS_SUGGESTION);
QAction *disableAction = new QAction(this); QAction *disableAction = new QAction(this);
disableAction->setText(Tr::tr("Disable Copilot")); disableAction->setText(Tr::tr("Disable Copilot"));
disableAction->setToolTip(Tr::tr("Disable Copilot.")); disableAction->setToolTip(Tr::tr("Disable Copilot."));

View File

@@ -190,11 +190,11 @@ LocatorMatcherTasks ActionsFilter::matchers()
collectEntriesForCommands(); collectEntriesForCommands();
if (storage->input().simplified().isEmpty()) { if (storage->input().simplified().isEmpty()) {
storage->reportOutput(m_entries); storage->reportOutput(m_entries);
return TaskAction::StopWithDone; return SetupResult::StopWithDone;
} }
async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(matches, *storage, m_entries); async.setConcurrentCallData(matches, *storage, m_entries);
return TaskAction::Continue; return SetupResult::Continue;
}; };
return {{AsyncTask<void>(onSetup), storage}}; return {{AsyncTask<void>(onSetup), storage}};

View File

@@ -82,9 +82,9 @@ DirectoryFilter::DirectoryFilter(Id id)
using namespace Tasking; using namespace Tasking;
const auto groupSetup = [this] { const auto groupSetup = [this] {
if (!m_directories.isEmpty()) if (!m_directories.isEmpty())
return TaskAction::Continue; // Async task will run return SetupResult::Continue; // Async task will run
m_cache.setFilePaths({}); m_cache.setFilePaths({});
return TaskAction::StopWithDone; // Group stops, skips async task return SetupResult::StopWithDone; // Group stops, skips async task
}; };
const auto asyncSetup = [this](Async<FilePaths> &async) { const auto asyncSetup = [this](Async<FilePaths> &async) {
async.setConcurrentCallData(&refresh, m_directories, m_filters, m_exclusionFilters, async.setConcurrentCallData(&refresh, m_directories, m_filters, m_exclusionFilters,

View File

@@ -1503,16 +1503,16 @@ LocatorMatcherTask LocatorFileCache::matcher() const
const auto onSetup = [storage, weak](Async<LocatorFileCachePrivate> &async) { const auto onSetup = [storage, weak](Async<LocatorFileCachePrivate> &async) {
auto that = weak.lock(); auto that = weak.lock();
if (!that) // LocatorMatcher is running after *this LocatorFileCache was destructed. if (!that) // LocatorMatcher is running after *this LocatorFileCache was destructed.
return TaskAction::StopWithDone; return SetupResult::StopWithDone;
if (!that->ensureValidated()) if (!that->ensureValidated())
return TaskAction::StopWithDone; // The cache is invalid and return SetupResult::StopWithDone; // The cache is invalid and
// no provider is set or it returned empty generator // no provider is set or it returned empty generator
that->bumpExecutionId(); that->bumpExecutionId();
async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(&filter, *storage, *that); async.setConcurrentCallData(&filter, *storage, *that);
return TaskAction::Continue; return SetupResult::Continue;
}; };
const auto onDone = [weak](const Async<LocatorFileCachePrivate> &async) { const auto onDone = [weak](const Async<LocatorFileCachePrivate> &async) {
auto that = weak.lock(); auto that = weak.lock();

View File

@@ -372,7 +372,7 @@ LocatorMatcherTasks JavaScriptFilter::matchers()
const auto onSetup = [storage, engine] { const auto onSetup = [storage, engine] {
if (!engine) if (!engine)
return TaskAction::StopWithError; return SetupResult::StopWithError;
if (storage->input().trimmed().isEmpty()) { if (storage->input().trimmed().isEmpty()) {
LocatorFilterEntry entry; LocatorFilterEntry entry;
entry.displayName = Tr::tr("Reset Engine"); entry.displayName = Tr::tr("Reset Engine");
@@ -385,9 +385,9 @@ LocatorMatcherTasks JavaScriptFilter::matchers()
return AcceptResult(); return AcceptResult();
}; };
storage->reportOutput({entry}); storage->reportOutput({entry});
return TaskAction::StopWithDone; return SetupResult::StopWithDone;
} }
return TaskAction::Continue; return SetupResult::Continue;
}; };
const auto onJavaScriptSetup = [storage, engine](JavaScriptRequest &request) { const auto onJavaScriptSetup = [storage, engine](JavaScriptRequest &request) {

View File

@@ -179,7 +179,7 @@ LocatorMatcherTasks SpotlightLocatorFilter::matchers()
const Link link = Link::fromString(storage->input(), true); const Link link = Link::fromString(storage->input(), true);
const FilePath input = link.targetFilePath; const FilePath input = link.targetFilePath;
if (input.isEmpty()) if (input.isEmpty())
return TaskAction::StopWithDone; return SetupResult::StopWithDone;
// only pass the file name part to allow searches like "somepath/*foo" // only pass the file name part to allow searches like "somepath/*foo"
const std::unique_ptr<MacroExpander> expander(createMacroExpander(input.fileName())); const std::unique_ptr<MacroExpander> expander(createMacroExpander(input.fileName()));
@@ -189,7 +189,7 @@ LocatorMatcherTasks SpotlightLocatorFilter::matchers()
CommandLine::Raw); CommandLine::Raw);
async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(matches, *storage, cmd); async.setConcurrentCallData(matches, *storage, cmd);
return TaskAction::Continue; return SetupResult::Continue;
}; };
return {{AsyncTask<void>(onSetup), storage}}; return {{AsyncTask<void>(onSetup), storage}};

View File

@@ -236,11 +236,11 @@ public:
const auto onCheckerSetup = [this](Async<ArchiveIssue> &async) { const auto onCheckerSetup = [this](Async<ArchiveIssue> &async) {
if (!m_tempDir) if (!m_tempDir)
return TaskAction::StopWithError; return SetupResult::StopWithError;
async.setConcurrentCallData(checkContents, m_tempDir->path()); async.setConcurrentCallData(checkContents, m_tempDir->path());
async.setFutureSynchronizer(PluginManager::futureSynchronizer()); async.setFutureSynchronizer(PluginManager::futureSynchronizer());
return TaskAction::Continue; return SetupResult::Continue;
}; };
const auto onCheckerDone = [this](const Async<ArchiveIssue> &async) { const auto onCheckerDone = [this](const Async<ArchiveIssue> &async) {
m_isComplete = !async.isResultAvailable(); m_isComplete = !async.isResultAvailable();

View File

@@ -310,12 +310,12 @@ FileListDiffController::FileListDiffController(IDocument *document, const QStrin
const auto setupStaged = [this, stagedFiles](Process &process) { const auto setupStaged = [this, stagedFiles](Process &process) {
if (stagedFiles.isEmpty()) if (stagedFiles.isEmpty())
return TaskAction::StopWithError; return SetupResult::StopWithError;
process.setCodec(VcsBaseEditor::getCodec(workingDirectory(), stagedFiles)); process.setCodec(VcsBaseEditor::getCodec(workingDirectory(), stagedFiles));
setupCommand(process, addConfigurationArguments( setupCommand(process, addConfigurationArguments(
QStringList({"diff", "--cached", "--"}) + stagedFiles)); QStringList({"diff", "--cached", "--"}) + stagedFiles));
VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine()); VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine());
return TaskAction::Continue; return SetupResult::Continue;
}; };
const auto onStagedDone = [storage](const Process &process) { const auto onStagedDone = [storage](const Process &process) {
storage->m_stagedOutput = process.cleanedStdOut(); storage->m_stagedOutput = process.cleanedStdOut();
@@ -323,12 +323,12 @@ FileListDiffController::FileListDiffController(IDocument *document, const QStrin
const auto setupUnstaged = [this, unstagedFiles](Process &process) { const auto setupUnstaged = [this, unstagedFiles](Process &process) {
if (unstagedFiles.isEmpty()) if (unstagedFiles.isEmpty())
return TaskAction::StopWithError; return SetupResult::StopWithError;
process.setCodec(VcsBaseEditor::getCodec(workingDirectory(), unstagedFiles)); process.setCodec(VcsBaseEditor::getCodec(workingDirectory(), unstagedFiles));
setupCommand(process, addConfigurationArguments( setupCommand(process, addConfigurationArguments(
QStringList({"diff", "--"}) + unstagedFiles)); QStringList({"diff", "--"}) + unstagedFiles));
VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine()); VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine());
return TaskAction::Continue; return SetupResult::Continue;
}; };
const auto onUnstagedDone = [storage](const Process &process) { const auto onUnstagedDone = [storage](const Process &process) {
storage->m_unstagedOutput = process.cleanedStdOut(); storage->m_unstagedOutput = process.cleanedStdOut();
@@ -421,8 +421,8 @@ ShowController::ShowController(IDocument *document, const QString &id)
const auto desciptionDetailsSetup = [storage] { const auto desciptionDetailsSetup = [storage] {
if (!storage->m_postProcessDescription) if (!storage->m_postProcessDescription)
return TaskAction::StopWithDone; return SetupResult::StopWithDone;
return TaskAction::Continue; return SetupResult::Continue;
}; };
const auto setupBranches = [this, storage](Process &process) { const auto setupBranches = [this, storage](Process &process) {

View File

@@ -1698,27 +1698,35 @@ void ClientPrivate::log(const ShowMessageParams &message)
LanguageClientValue<MessageActionItem> ClientPrivate::showMessageBox( LanguageClientValue<MessageActionItem> ClientPrivate::showMessageBox(
const ShowMessageRequestParams &message) const ShowMessageRequestParams &message)
{ {
auto box = new QMessageBox(); QMessageBox box;
box->setText(message.toString()); box.setText(message.toString());
box->setAttribute(Qt::WA_DeleteOnClose);
switch (message.type()) { switch (message.type()) {
case Error: box->setIcon(QMessageBox::Critical); break; case Error:
case Warning: box->setIcon(QMessageBox::Warning); break; box.setIcon(QMessageBox::Critical);
case Info: box->setIcon(QMessageBox::Information); break; break;
case Log: box->setIcon(QMessageBox::NoIcon); break; case Warning:
box.setIcon(QMessageBox::Warning);
break;
case Info:
box.setIcon(QMessageBox::Information);
break;
case Log:
box.setIcon(QMessageBox::NoIcon);
break;
} }
QHash<QAbstractButton *, MessageActionItem> itemForButton; QHash<QAbstractButton *, MessageActionItem> itemForButton;
if (const std::optional<QList<MessageActionItem>> actions = message.actions()) { if (const std::optional<QList<MessageActionItem>> actions = message.actions()) {
auto button = box->addButton(QMessageBox::Close);
connect(button, &QPushButton::clicked, box, &QMessageBox::reject);
for (const MessageActionItem &action : *actions) { for (const MessageActionItem &action : *actions) {
connect(button, &QPushButton::clicked, box, &QMessageBox::accept); auto button = box.addButton(action.title(), QMessageBox::ActionRole);
connect(button, &QPushButton::clicked, &box, &QMessageBox::accept);
itemForButton.insert(button, action); itemForButton.insert(button, action);
} }
} }
if (box->exec() == QDialog::Rejected)
if (box.exec() == QDialog::Rejected || itemForButton.isEmpty())
return {}; return {};
const MessageActionItem &item = itemForButton.value(box->clickedButton()); const MessageActionItem &item = itemForButton.value(box.clickedButton());
return item.isValid() ? LanguageClientValue<MessageActionItem>(item) return item.isValid() ? LanguageClientValue<MessageActionItem>(item)
: LanguageClientValue<MessageActionItem>(); : LanguageClientValue<MessageActionItem>();
} }

View File

@@ -69,10 +69,10 @@ LocatorMatcherTask locatorMatcher(Client *client, int maxResultCount,
const auto onFilterSetup = [storage, resultStorage, client, filter](Async<void> &async) { const auto onFilterSetup = [storage, resultStorage, client, filter](Async<void> &async) {
const QList<SymbolInformation> results = *resultStorage; const QList<SymbolInformation> results = *resultStorage;
if (results.isEmpty()) if (results.isEmpty())
return TaskAction::StopWithDone; return SetupResult::StopWithDone;
async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer()); async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(filterResults, *storage, client, results, filter); async.setConcurrentCallData(filterResults, *storage, client, results, filter);
return TaskAction::Continue; return SetupResult::Continue;
}; };
const Group root { const Group root {

View File

@@ -1,5 +1,5 @@
add_qtc_plugin(McuSupport add_qtc_plugin(McuSupport
DEPENDS Qt::Core DEPENDS Qt::Core QmlJS
PLUGIN_DEPENDS Core BareMetal ProjectExplorer Debugger CMakeProjectManager QtSupport PLUGIN_DEPENDS Core BareMetal ProjectExplorer Debugger CMakeProjectManager QtSupport
SOURCES SOURCES
mcukitinformation.cpp mcukitinformation.h mcukitinformation.cpp mcukitinformation.h

View File

@@ -18,6 +18,7 @@
#include "test/unittest.h" #include "test/unittest.h"
#endif #endif
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/coreconstants.h> #include <coreplugin/coreconstants.h>
#include <coreplugin/icontext.h> #include <coreplugin/icontext.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
@@ -34,9 +35,14 @@
#include <cmakeprojectmanager/cmakeprojectconstants.h> #include <cmakeprojectmanager/cmakeprojectconstants.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <qmljstools/qmljstoolsconstants.h>
#include <utils/filepath.h> #include <utils/filepath.h>
#include <utils/infobar.h> #include <utils/infobar.h>
#include <QAction>
#include <QDateTime>
#include <QTimer> #include <QTimer>
using namespace Core; using namespace Core;
@@ -119,6 +125,41 @@ void McuSupportPlugin::initialize()
&ProjectManager::projectFinishedParsing, &ProjectManager::projectFinishedParsing,
updateMCUProjectTree); updateMCUProjectTree);
// Temporary fix for CodeModel/Checker race condition
// Remove after https://bugreports.qt.io/browse/QTCREATORBUG-29269 is closed
connect(QmlJS::ModelManagerInterface::instance(),
&QmlJS::ModelManagerInterface::documentUpdated,
[lasttime = QTime::currentTime()](QmlJS::Document::Ptr doc) mutable {
// Prevent inifinite recall loop
auto currenttime = QTime::currentTime();
if (lasttime.msecsTo(currenttime) < 1000) {
lasttime = currenttime;
return;
}
lasttime = currenttime;
if (!doc)
return;
//Reset code model only for QtMCUs documents
const Project *project = ProjectManager::projectForFile(doc->path());
if (!project)
return;
const QList<Target *> targets = project->targets();
bool isMcuDocument
= std::any_of(std::begin(targets), std::end(targets), [](const Target *target) {
if (!target || !target->kit()
|| !target->kit()->hasValue(Constants::KIT_MCUTARGET_KITVERSION_KEY))
return false;
return true;
});
if (!isMcuDocument)
return;
Core::ActionManager::command(QmlJSTools::Constants::RESET_CODEMODEL)
->action()
->trigger();
});
dd->m_options.registerQchFiles(); dd->m_options.registerQchFiles();
dd->m_options.registerExamples(); dd->m_options.registerExamples();
ProjectExplorer::JsonWizardFactory::addWizardPath(":/mcusupport/wizards/"); ProjectExplorer::JsonWizardFactory::addWizardPath(":/mcusupport/wizards/");

View File

@@ -70,6 +70,7 @@ public:
{ {
if (m_widget) if (m_widget)
return m_widget->displayIcon(); return m_widget->displayIcon();
QTC_ASSERT(m_kit, return {});
return m_kit->displayIcon(); return m_kit->displayIcon();
} }
@@ -77,6 +78,7 @@ public:
{ {
if (m_widget) if (m_widget)
return m_widget->displayName(); return m_widget->displayName();
QTC_ASSERT(m_kit, return {});
return m_kit->displayName(); return m_kit->displayName();
} }

View File

@@ -68,7 +68,7 @@ void TerminalAspect::addToLayout(LayoutItem &parent)
m_checkBox = createSubWidget<QCheckBox>(Tr::tr("Run in terminal")); m_checkBox = createSubWidget<QCheckBox>(Tr::tr("Run in terminal"));
m_checkBox->setChecked(m_useTerminal); m_checkBox->setChecked(m_useTerminal);
m_checkBox->setEnabled(isEnabled()); m_checkBox->setEnabled(isEnabled());
parent.addItems({{}, m_checkBox.data()}); parent.addItems({empty(), m_checkBox.data()});
connect(m_checkBox.data(), &QAbstractButton::clicked, this, [this] { connect(m_checkBox.data(), &QAbstractButton::clicked, this, [this] {
m_userSet = true; m_userSet = true;
m_useTerminal = m_checkBox->isChecked(); m_useTerminal = m_checkBox->isChecked();

View File

@@ -671,7 +671,6 @@ TaskView::TaskView()
void TaskView::resizeColumns() void TaskView::resizeColumns()
{ {
setColumnWidth(0, width() * 0.85); setColumnWidth(0, width() * 0.85);
setColumnWidth(1, width() * 0.15);
} }
void TaskView::resizeEvent(QResizeEvent *e) void TaskView::resizeEvent(QResizeEvent *e)

View File

@@ -149,10 +149,10 @@ GroupItem QnxDeployQtLibrariesDialogPrivate::removeDirTask()
{ {
const auto setupHandler = [this](Process &process) { const auto setupHandler = [this](Process &process) {
if (m_checkResult != CheckResult::RemoveDir) if (m_checkResult != CheckResult::RemoveDir)
return TaskAction::StopWithDone; return SetupResult::StopWithDone;
m_deployLogWindow->appendPlainText(Tr::tr("Removing \"%1\"").arg(fullRemoteDirectory())); m_deployLogWindow->appendPlainText(Tr::tr("Removing \"%1\"").arg(fullRemoteDirectory()));
process.setCommand({m_device->filePath("rm"), {"-rf", fullRemoteDirectory()}}); process.setCommand({m_device->filePath("rm"), {"-rf", fullRemoteDirectory()}});
return TaskAction::Continue; return SetupResult::Continue;
}; };
const auto errorHandler = [this](const Process &process) { const auto errorHandler = [this](const Process &process) {
QTC_ASSERT(process.exitCode() == 0, return); QTC_ASSERT(process.exitCode() == 0, return);
@@ -167,7 +167,7 @@ GroupItem QnxDeployQtLibrariesDialogPrivate::uploadTask()
const auto setupHandler = [this](FileTransfer &transfer) { const auto setupHandler = [this](FileTransfer &transfer) {
if (m_deployableFiles.isEmpty()) { if (m_deployableFiles.isEmpty()) {
emitProgressMessage(Tr::tr("No files need to be uploaded.")); emitProgressMessage(Tr::tr("No files need to be uploaded."));
return TaskAction::StopWithDone; return SetupResult::StopWithDone;
} }
emitProgressMessage(Tr::tr("%n file(s) need to be uploaded.", "", emitProgressMessage(Tr::tr("%n file(s) need to be uploaded.", "",
m_deployableFiles.size())); m_deployableFiles.size()));
@@ -177,18 +177,18 @@ GroupItem QnxDeployQtLibrariesDialogPrivate::uploadTask()
const QString message = Tr::tr("Local file \"%1\" does not exist.") const QString message = Tr::tr("Local file \"%1\" does not exist.")
.arg(file.localFilePath().toUserOutput()); .arg(file.localFilePath().toUserOutput());
emitErrorMessage(message); emitErrorMessage(message);
return TaskAction::StopWithError; return SetupResult::StopWithError;
} }
files.append({file.localFilePath(), m_device->filePath(file.remoteFilePath())}); files.append({file.localFilePath(), m_device->filePath(file.remoteFilePath())});
} }
if (files.isEmpty()) { if (files.isEmpty()) {
emitProgressMessage(Tr::tr("No files need to be uploaded.")); emitProgressMessage(Tr::tr("No files need to be uploaded."));
return TaskAction::StopWithDone; return SetupResult::StopWithDone;
} }
transfer.setFilesToTransfer(files); transfer.setFilesToTransfer(files);
QObject::connect(&transfer, &FileTransfer::progress, QObject::connect(&transfer, &FileTransfer::progress,
this, &QnxDeployQtLibrariesDialogPrivate::emitProgressMessage); this, &QnxDeployQtLibrariesDialogPrivate::emitProgressMessage);
return TaskAction::Continue; return SetupResult::Continue;
}; };
const auto errorHandler = [this](const FileTransfer &transfer) { const auto errorHandler = [this](const FileTransfer &transfer) {
emitErrorMessage(transfer.resultData().m_errorString); emitErrorMessage(transfer.resultData().m_errorString);
@@ -238,7 +238,7 @@ Group QnxDeployQtLibrariesDialogPrivate::deployRecipe()
const auto setupHandler = [this] { const auto setupHandler = [this] {
if (!m_device) { if (!m_device) {
emitErrorMessage(Tr::tr("No device configuration set.")); emitErrorMessage(Tr::tr("No device configuration set."));
return TaskAction::StopWithError; return SetupResult::StopWithError;
} }
QList<DeployableFile> collected; QList<DeployableFile> collected;
for (int i = 0; i < m_deployableFiles.count(); ++i) for (int i = 0; i < m_deployableFiles.count(); ++i)
@@ -247,18 +247,18 @@ Group QnxDeployQtLibrariesDialogPrivate::deployRecipe()
QTC_CHECK(collected.size() >= m_deployableFiles.size()); QTC_CHECK(collected.size() >= m_deployableFiles.size());
m_deployableFiles = collected; m_deployableFiles = collected;
if (!m_deployableFiles.isEmpty()) if (!m_deployableFiles.isEmpty())
return TaskAction::Continue; return SetupResult::Continue;
emitProgressMessage(Tr::tr("No deployment action necessary. Skipping.")); emitProgressMessage(Tr::tr("No deployment action necessary. Skipping."));
return TaskAction::StopWithDone; return SetupResult::StopWithDone;
}; };
const auto doneHandler = [this] { const auto doneHandler = [this] {
emitProgressMessage(Tr::tr("All files successfully deployed.")); emitProgressMessage(Tr::tr("All files successfully deployed."));
}; };
const auto subGroupSetupHandler = [this] { const auto subGroupSetupHandler = [this] {
if (m_checkResult == CheckResult::Abort) if (m_checkResult == CheckResult::Abort)
return TaskAction::StopWithError; return SetupResult::StopWithError;
return TaskAction::Continue; return SetupResult::Continue;
}; };
const Group root { const Group root {
onGroupSetup(setupHandler), onGroupSetup(setupHandler),

View File

@@ -19,12 +19,13 @@ using namespace ProjectExplorer;
namespace QtSupport { namespace QtSupport {
// opt. drive letter + filename: (2 brackets) // opt. drive letter + filename: (2 brackets)
#define FILE_PATTERN R"(^(?<file>(?:[A-Za-z]:)?[^:\(]+\.[^:\(]+))" #define FILE_PATTERN R"((?<file>(?:[A-Za-z]:)?[^:\(]+\.[^:\(]+))"
QtParser::QtParser() : QtParser::QtParser() :
m_mocRegExp(FILE_PATTERN R"([:\(](?<line>\d+)?(?::(?<column>\d+))?\)?:\s(?<level>[Ww]arning|[Ee]rror|[Nn]ote):\s(?<description>.+?)$)"), m_mocRegExp("^" FILE_PATTERN R"([:\(](?<line>\d+)?(?::(?<column>\d+))?\)?:\s(?<level>[Ww]arning|[Ee]rror|[Nn]ote):\s(?<description>.+?)$)"),
m_uicRegExp(FILE_PATTERN R"(: Warning:\s(?<msg>.+?)$)"), m_uicRegExp("^" FILE_PATTERN R"(: Warning:\s(?<msg>.+?)$)"),
m_translationRegExp(R"(^(?<level>[Ww]arning|[Ee]rror):\s+(?<description>.*?) in '(?<file>.*?)'$)") m_translationRegExp(R"(^(?<level>[Ww]arning|[Ee]rror):\s+(?<description>.*?) in '(?<file>.*?)'$)"),
m_qmlToolsRegExp(R"(^(?<level>Warning|Error):\s*)" FILE_PATTERN R"([:\(](?<line>\d+)?(?::(?<column>\d+))?\)?:\s(?<description>.+?)$)")
{ {
setObjectName(QLatin1String("QtParser")); setObjectName(QLatin1String("QtParser"));
} }
@@ -89,19 +90,22 @@ Utils::OutputLineParser::Result QtParser::handleLine(const QString &line, Utils:
scheduleTask(task, 1); scheduleTask(task, 1);
return {Status::Done, linkSpecs}; return {Status::Done, linkSpecs};
} }
match = m_qmlToolsRegExp.match(line);
if (lne.startsWith(QLatin1String("Error:"))) { if (match.hasMatch()) {
constexpr int matchLength = 6; const Task::TaskType type = match.captured("level") == "Error" ? Task::Error
CompileTask task(Task::TaskType::Error, line.mid(matchLength).trimmed()); : Task::Warning;
const Utils::FilePath file
= absoluteFilePath(Utils::FilePath::fromUserInput(match.captured("file")));
bool ok;
int lineno = match.captured("line").toInt(&ok);
if (!ok)
lineno = -1;
LinkSpecs linkSpecs;
addLinkSpecForAbsoluteFilePath(linkSpecs, file, lineno, match, "file");
CompileTask task(type, match.captured("description"), file, lineno,
match.captured("column").toInt());
scheduleTask(task, 1); scheduleTask(task, 1);
return Status::Done; return {Status::Done, linkSpecs};
}
if (lne.startsWith(QLatin1String("Warning:"))) {
constexpr int matchLength = 8;
CompileTask task(Task::TaskType::Warning, line.mid(matchLength).trimmed());
scheduleTask(task, 1);
return Status::Done;
} }
return Status::NotHandled; return Status::NotHandled;
@@ -220,14 +224,21 @@ void QtSupportPlugin::testQtOutputParser_data()
QLatin1String("dropping duplicate messages"), QLatin1String("dropping duplicate messages"),
Utils::FilePath::fromUserInput(QLatin1String("/some/place/qtcreator_fr.qm")), -1)) Utils::FilePath::fromUserInput(QLatin1String("/some/place/qtcreator_fr.qm")), -1))
<< QString(); << QString();
QTest::newRow("qmlsc warning") // QTCREATORBUG-28720 QTest::newRow("qmlsc/qmllint warning") // QTCREATORBUG-28720
<< QString::fromUtf8("Warning: Main.qml:4:1: Warnings occurred while importing module " << QString::fromLatin1("Warning: Main.qml:4:1: Warnings occurred while importing module "
"\"QtQuick.Controls\": [import]\"") "\"QtQuick.Controls\": [import]\"")
<< OutputParserTester::STDERR << QString() << QString() << OutputParserTester::STDERR << QString() << QString()
<< (Tasks() << CompileTask(Task::Warning, << (Tasks() << CompileTask(Task::Warning,
QString::fromUtf8( "Warnings occurred while importing module \"QtQuick.Controls\": [import]\"",
"Main.qml:4:1: Warnings occurred while importing module " Utils::FilePath::fromUserInput("Main.qml"), 4, 1))
"\"QtQuick.Controls\": [import]\""))) << QString();
QTest::newRow("qmlsc/qmllint error") // QTCREATORBUG-28720
<< QString::fromLatin1("Error: E:/foo/PerfProfilerFlameGraphView.qml:10:5: "
"Could not compile binding for model: Cannot resolve property type for binding on model")
<< OutputParserTester::STDERR << QString() << QString()
<< (Tasks() << CompileTask(Task::Error,
"Could not compile binding for model: Cannot resolve property type for binding on model",
Utils::FilePath::fromUserInput("E:/foo/PerfProfilerFlameGraphView.qml"), 10, 5))
<< QString(); << QString();
} }

View File

@@ -25,6 +25,7 @@ private:
QRegularExpression m_mocRegExp; QRegularExpression m_mocRegExp;
QRegularExpression m_uicRegExp; QRegularExpression m_uicRegExp;
QRegularExpression m_translationRegExp; QRegularExpression m_translationRegExp;
const QRegularExpression m_qmlToolsRegExp;
}; };
} // namespace QtSupport } // namespace QtSupport

View File

@@ -194,7 +194,7 @@ GroupItem GenericDirectUploadStep::uploadTask(const TreeStorage<UploadStorage> &
const auto setupHandler = [this, storage](FileTransfer &transfer) { const auto setupHandler = [this, storage](FileTransfer &transfer) {
if (storage->filesToUpload.isEmpty()) { if (storage->filesToUpload.isEmpty()) {
addProgressMessage(Tr::tr("No files need to be uploaded.")); addProgressMessage(Tr::tr("No files need to be uploaded."));
return TaskAction::StopWithDone; return SetupResult::StopWithDone;
} }
addProgressMessage(Tr::tr("%n file(s) need to be uploaded.", "", addProgressMessage(Tr::tr("%n file(s) need to be uploaded.", "",
storage->filesToUpload.size())); storage->filesToUpload.size()));
@@ -208,19 +208,19 @@ GroupItem GenericDirectUploadStep::uploadTask(const TreeStorage<UploadStorage> &
continue; continue;
} }
addErrorMessage(message); addErrorMessage(message);
return TaskAction::StopWithError; return SetupResult::StopWithError;
} }
files.append({file.localFilePath(), files.append({file.localFilePath(),
deviceConfiguration()->filePath(file.remoteFilePath())}); deviceConfiguration()->filePath(file.remoteFilePath())});
} }
if (files.isEmpty()) { if (files.isEmpty()) {
addProgressMessage(Tr::tr("No files need to be uploaded.")); addProgressMessage(Tr::tr("No files need to be uploaded."));
return TaskAction::StopWithDone; return SetupResult::StopWithDone;
} }
transfer.setFilesToTransfer(files); transfer.setFilesToTransfer(files);
QObject::connect(&transfer, &FileTransfer::progress, QObject::connect(&transfer, &FileTransfer::progress,
this, &GenericDirectUploadStep::addProgressMessage); this, &GenericDirectUploadStep::addProgressMessage);
return TaskAction::Continue; return SetupResult::Continue;
}; };
const auto errorHandler = [this](const FileTransfer &transfer) { const auto errorHandler = [this](const FileTransfer &transfer) {
addErrorMessage(transfer.resultData().m_errorString); addErrorMessage(transfer.resultData().m_errorString);

View File

@@ -167,13 +167,13 @@ SubversionDiffEditorController::SubversionDiffEditorController(IDocument *docume
const auto setupDescription = [this](Process &process) { const auto setupDescription = [this](Process &process) {
if (m_changeNumber == 0) if (m_changeNumber == 0)
return TaskAction::StopWithDone; return SetupResult::StopWithDone;
setupCommand(process, {"log", "-r", QString::number(m_changeNumber)}); setupCommand(process, {"log", "-r", QString::number(m_changeNumber)});
CommandLine command = process.commandLine(); CommandLine command = process.commandLine();
command << SubversionClient::AddAuthOptions(); command << SubversionClient::AddAuthOptions();
process.setCommand(command); process.setCommand(command);
setDescription(Tr::tr("Waiting for data...")); setDescription(Tr::tr("Waiting for data..."));
return TaskAction::Continue; return SetupResult::Continue;
}; };
const auto onDescriptionDone = [this](const Process &process) { const auto onDescriptionDone = [this](const Process &process) {
setDescription(process.cleanedStdOut()); setDescription(process.cleanedStdOut());

View File

@@ -227,7 +227,7 @@ void tst_Tasking::testTree_data()
}; };
}; };
const auto setupDynamicTask = [storage](int taskId, TaskAction action) { const auto setupDynamicTask = [storage](int taskId, SetupResult action) {
return [storage, taskId, action](TaskObject &) { return [storage, taskId, action](TaskObject &) {
storage->m_log.append({taskId, Handler::Setup}); storage->m_log.append({taskId, Handler::Setup});
return action; return action;
@@ -274,7 +274,7 @@ void tst_Tasking::testTree_data()
}; };
const auto createDynamicTask = [storage, setupDynamicTask, setupDone, setupError]( const auto createDynamicTask = [storage, setupDynamicTask, setupDone, setupError](
int taskId, TaskAction action) { int taskId, SetupResult action) {
return TestTask(setupDynamicTask(taskId, action), setupDone(taskId), setupError(taskId)); return TestTask(setupDynamicTask(taskId, action), setupDone(taskId), setupError(taskId));
}; };
@@ -302,35 +302,74 @@ void tst_Tasking::testTree_data()
}; };
const Group root2 { const Group root2 {
Storage(storage), Storage(storage),
onGroupSetup([] { return TaskAction::Continue; }), onGroupSetup([] { return SetupResult::Continue; }),
groupDone(0), groupDone(0),
groupError(0) groupError(0)
}; };
const Group root3 { const Group root3 {
Storage(storage), Storage(storage),
onGroupSetup([] { return TaskAction::StopWithDone; }), onGroupSetup([] { return SetupResult::StopWithDone; }),
groupDone(0), groupDone(0),
groupError(0) groupError(0)
}; };
const Group root4 { const Group root4 {
Storage(storage), Storage(storage),
onGroupSetup([] { return TaskAction::StopWithError; }), onGroupSetup([] { return SetupResult::StopWithError; }),
groupDone(0), groupDone(0),
groupError(0) groupError(0)
}; };
const Log logDone {{0, Handler::GroupDone}}; const Log logDone {{0, Handler::GroupDone}};
const Log logError {{0, Handler::GroupError}}; const Log logError {{0, Handler::GroupError}};
QTest::newRow("Empty") << TestData{storage, root1, logDone, 0, OnDone::Success}; QTest::newRow("Empty") << TestData{storage, root1, logDone, 0, OnDone::Success};
QTest::newRow("EmptyContinue") << TestData{storage, root2, logDone, 0, OnDone::Success}; QTest::newRow("EmptyContinue") << TestData{storage, root2, logDone, 0, OnDone::Success};
QTest::newRow("EmptyDone") << TestData{storage, root3, logDone, 0, OnDone::Success}; QTest::newRow("EmptyDone") << TestData{storage, root3, logDone, 0, OnDone::Success};
QTest::newRow("EmptyError") << TestData{storage, root4, logError, 0, OnDone::Failure}; QTest::newRow("EmptyError") << TestData{storage, root4, logError, 0, OnDone::Failure};
} }
{
const auto setupGroup = [=](SetupResult setupResult, WorkflowPolicy policy) {
return Group {
Storage(storage),
workflowPolicy(policy),
onGroupSetup([setupResult] { return setupResult; }),
groupDone(0),
groupError(0)
};
};
const auto doneData = [storage, setupGroup](WorkflowPolicy policy) {
return TestData{storage, setupGroup(SetupResult::StopWithDone, policy),
Log{{0, Handler::GroupDone}}, 0, OnDone::Success};
};
const auto errorData = [storage, setupGroup](WorkflowPolicy policy) {
return TestData{storage, setupGroup(SetupResult::StopWithError, policy),
Log{{0, Handler::GroupError}}, 0, OnDone::Failure};
};
QTest::newRow("DoneAndStopOnError") << doneData(WorkflowPolicy::StopOnError);
QTest::newRow("DoneAndContinueOnError") << doneData(WorkflowPolicy::ContinueOnError);
QTest::newRow("DoneAndStopOnDone") << doneData(WorkflowPolicy::StopOnDone);
QTest::newRow("DoneAndContinueOnDone") << doneData(WorkflowPolicy::ContinueOnDone);
QTest::newRow("DoneAndStopOnFinished") << doneData(WorkflowPolicy::StopOnFinished);
QTest::newRow("DoneAndFinishAllAndDone") << doneData(WorkflowPolicy::FinishAllAndDone);
QTest::newRow("DoneAndFinishAllAndError") << doneData(WorkflowPolicy::FinishAllAndError);
QTest::newRow("ErrorAndStopOnError") << errorData(WorkflowPolicy::StopOnError);
QTest::newRow("ErrorAndContinueOnError") << errorData(WorkflowPolicy::ContinueOnError);
QTest::newRow("ErrorAndStopOnDone") << errorData(WorkflowPolicy::StopOnDone);
QTest::newRow("ErrorAndContinueOnDone") << errorData(WorkflowPolicy::ContinueOnDone);
QTest::newRow("ErrorAndStopOnFinished") << errorData(WorkflowPolicy::StopOnFinished);
QTest::newRow("ErrorAndFinishAllAndDone") << errorData(WorkflowPolicy::FinishAllAndDone);
QTest::newRow("ErrorAndFinishAllAndError") << errorData(WorkflowPolicy::FinishAllAndError);
}
{ {
const Group root { const Group root {
Storage(storage), Storage(storage),
createDynamicTask(1, TaskAction::StopWithDone), createDynamicTask(1, SetupResult::StopWithDone),
createDynamicTask(2, TaskAction::StopWithDone) createDynamicTask(2, SetupResult::StopWithDone)
}; };
const Log log {{1, Handler::Setup}, {2, Handler::Setup}}; const Log log {{1, Handler::Setup}, {2, Handler::Setup}};
QTest::newRow("DynamicTaskDone") << TestData{storage, root, log, 2, OnDone::Success}; QTest::newRow("DynamicTaskDone") << TestData{storage, root, log, 2, OnDone::Success};
@@ -339,8 +378,8 @@ void tst_Tasking::testTree_data()
{ {
const Group root { const Group root {
Storage(storage), Storage(storage),
createDynamicTask(1, TaskAction::StopWithError), createDynamicTask(1, SetupResult::StopWithError),
createDynamicTask(2, TaskAction::StopWithError) createDynamicTask(2, SetupResult::StopWithError)
}; };
const Log log {{1, Handler::Setup}}; const Log log {{1, Handler::Setup}};
QTest::newRow("DynamicTaskError") << TestData{storage, root, log, 2, OnDone::Failure}; QTest::newRow("DynamicTaskError") << TestData{storage, root, log, 2, OnDone::Failure};
@@ -349,10 +388,10 @@ void tst_Tasking::testTree_data()
{ {
const Group root { const Group root {
Storage(storage), Storage(storage),
createDynamicTask(1, TaskAction::Continue), createDynamicTask(1, SetupResult::Continue),
createDynamicTask(2, TaskAction::Continue), createDynamicTask(2, SetupResult::Continue),
createDynamicTask(3, TaskAction::StopWithError), createDynamicTask(3, SetupResult::StopWithError),
createDynamicTask(4, TaskAction::Continue) createDynamicTask(4, SetupResult::Continue)
}; };
const Log log { const Log log {
{1, Handler::Setup}, {1, Handler::Setup},
@@ -368,10 +407,10 @@ void tst_Tasking::testTree_data()
const Group root { const Group root {
parallel, parallel,
Storage(storage), Storage(storage),
createDynamicTask(1, TaskAction::Continue), createDynamicTask(1, SetupResult::Continue),
createDynamicTask(2, TaskAction::Continue), createDynamicTask(2, SetupResult::Continue),
createDynamicTask(3, TaskAction::StopWithError), createDynamicTask(3, SetupResult::StopWithError),
createDynamicTask(4, TaskAction::Continue) createDynamicTask(4, SetupResult::Continue)
}; };
const Log log { const Log log {
{1, Handler::Setup}, {1, Handler::Setup},
@@ -387,12 +426,12 @@ void tst_Tasking::testTree_data()
const Group root { const Group root {
parallel, parallel,
Storage(storage), Storage(storage),
createDynamicTask(1, TaskAction::Continue), createDynamicTask(1, SetupResult::Continue),
createDynamicTask(2, TaskAction::Continue), createDynamicTask(2, SetupResult::Continue),
Group { Group {
createDynamicTask(3, TaskAction::StopWithError) createDynamicTask(3, SetupResult::StopWithError)
}, },
createDynamicTask(4, TaskAction::Continue) createDynamicTask(4, SetupResult::Continue)
}; };
const Log log { const Log log {
{1, Handler::Setup}, {1, Handler::Setup},
@@ -408,16 +447,16 @@ void tst_Tasking::testTree_data()
const Group root { const Group root {
parallel, parallel,
Storage(storage), Storage(storage),
createDynamicTask(1, TaskAction::Continue), createDynamicTask(1, SetupResult::Continue),
createDynamicTask(2, TaskAction::Continue), createDynamicTask(2, SetupResult::Continue),
Group { Group {
onGroupSetup([storage] { onGroupSetup([storage] {
storage->m_log.append({0, Handler::GroupSetup}); storage->m_log.append({0, Handler::GroupSetup});
return TaskAction::StopWithError; return SetupResult::StopWithError;
}), }),
createDynamicTask(3, TaskAction::Continue) createDynamicTask(3, SetupResult::Continue)
}, },
createDynamicTask(4, TaskAction::Continue) createDynamicTask(4, SetupResult::Continue)
}; };
const Log log { const Log log {
{1, Handler::Setup}, {1, Handler::Setup},
@@ -1280,14 +1319,14 @@ void tst_Tasking::testTree_data()
{ {
const auto createRoot = [storage, createSuccessTask, groupDone, groupError]( const auto createRoot = [storage, createSuccessTask, groupDone, groupError](
TaskAction taskAction) { SetupResult setupResult) {
return Group { return Group {
Storage(storage), Storage(storage),
Group { Group {
createSuccessTask(1) createSuccessTask(1)
}, },
Group { Group {
onGroupSetup([=] { return taskAction; }), onGroupSetup([=] { return setupResult; }),
createSuccessTask(2), createSuccessTask(2),
createSuccessTask(3), createSuccessTask(3),
createSuccessTask(4) createSuccessTask(4)
@@ -1297,7 +1336,7 @@ void tst_Tasking::testTree_data()
}; };
}; };
const Group root1 = createRoot(TaskAction::StopWithDone); const Group root1 = createRoot(SetupResult::StopWithDone);
const Log log1 { const Log log1 {
{1, Handler::Setup}, {1, Handler::Setup},
{1, Handler::Done}, {1, Handler::Done},
@@ -1305,7 +1344,7 @@ void tst_Tasking::testTree_data()
}; };
QTest::newRow("DynamicSetupDone") << TestData{storage, root1, log1, 4, OnDone::Success}; QTest::newRow("DynamicSetupDone") << TestData{storage, root1, log1, 4, OnDone::Success};
const Group root2 = createRoot(TaskAction::StopWithError); const Group root2 = createRoot(SetupResult::StopWithError);
const Log log2 { const Log log2 {
{1, Handler::Setup}, {1, Handler::Setup},
{1, Handler::Done}, {1, Handler::Done},
@@ -1313,7 +1352,7 @@ void tst_Tasking::testTree_data()
}; };
QTest::newRow("DynamicSetupError") << TestData{storage, root2, log2, 4, OnDone::Failure}; QTest::newRow("DynamicSetupError") << TestData{storage, root2, log2, 4, OnDone::Failure};
const Group root3 = createRoot(TaskAction::Continue); const Group root3 = createRoot(SetupResult::Continue);
const Log log3 { const Log log3 {
{1, Handler::Setup}, {1, Handler::Setup},
{1, Handler::Done}, {1, Handler::Done},
@@ -1380,7 +1419,7 @@ void tst_Tasking::testTree_data()
}, },
Group { Group {
groupSetup(3), groupSetup(3),
createDynamicTask(3, TaskAction::StopWithDone) createDynamicTask(3, SetupResult::StopWithDone)
}, },
Group { Group {
groupSetup(4), groupSetup(4),
@@ -1424,7 +1463,7 @@ void tst_Tasking::testTree_data()
}, },
Group { Group {
groupSetup(3), groupSetup(3),
createDynamicTask(3, TaskAction::StopWithError) createDynamicTask(3, SetupResult::StopWithError)
}, },
Group { Group {
groupSetup(4), groupSetup(4),
@@ -1463,7 +1502,7 @@ void tst_Tasking::testTree_data()
}, },
Group { Group {
groupSetup(3), groupSetup(3),
createDynamicTask(3, TaskAction::StopWithError) createDynamicTask(3, SetupResult::StopWithError)
}, },
Group { Group {
groupSetup(4), groupSetup(4),
@@ -1508,7 +1547,7 @@ void tst_Tasking::testTree_data()
}, },
Group { Group {
groupSetup(3), groupSetup(3),
createDynamicTask(3, TaskAction::StopWithError) createDynamicTask(3, SetupResult::StopWithError)
}, },
Group { Group {
groupSetup(4), groupSetup(4),
@@ -1605,7 +1644,7 @@ void tst_Tasking::testTree_data()
}, },
Group { Group {
groupSetup(3), groupSetup(3),
Group { createDynamicTask(3, TaskAction::StopWithDone) } Group { createDynamicTask(3, SetupResult::StopWithDone) }
}, },
Group { Group {
groupSetup(4), groupSetup(4),
@@ -1650,7 +1689,7 @@ void tst_Tasking::testTree_data()
}, },
Group { Group {
groupSetup(3), groupSetup(3),
Group { createDynamicTask(3, TaskAction::StopWithError) } Group { createDynamicTask(3, SetupResult::StopWithError) }
}, },
Group { Group {
groupSetup(4), groupSetup(4),

View File

@@ -196,8 +196,7 @@ private slots:
QTest::newRow("simple") << "command %{hello}" QTest::newRow("simple") << "command %{hello}"
<< "command" << "command"
<< (HostOsInfo::isWindowsHost() ? "\"hello world\"" << "hello world";
: "'hello world'");
QTest::newRow("simple-quoted") QTest::newRow("simple-quoted")
<< "command \"%{hello}\"" << "command \"%{hello}\""
@@ -226,15 +225,11 @@ private slots:
CommandLine cmd = CommandLine::fromUserInput(input, &expander); CommandLine cmd = CommandLine::fromUserInput(input, &expander);
QCOMPARE(cmd.executable().toUserOutput(), expectedExecutable); QCOMPARE(cmd.executable().toUserOutput(), expectedExecutable);
// TODO: Fix (macro) escaping on windows if (HostOsInfo::isWindowsHost()) {
if (HostOsInfo::isWindowsHost())
QEXPECT_FAIL("simple", "Windows does not correctly quote macro arguments", Continue);
if (HostOsInfo::isWindowsHost())
QEXPECT_FAIL("simple-quoted", "Windows removes quotes from macro arguments", Continue);
if (HostOsInfo::isWindowsHost())
QEXPECT_FAIL("convert-to-quote-win", QEXPECT_FAIL("convert-to-quote-win",
"Windows should convert single to double quotes", "Windows should convert single to double quotes",
Continue); Continue);
}
QCOMPARE(cmd.arguments(), expectedArguments); QCOMPARE(cmd.arguments(), expectedArguments);
} }

View File

@@ -16,6 +16,7 @@ add_subdirectory(proparser)
# add_subdirectory(qt4projectmanager) # add_subdirectory(qt4projectmanager)
# add_subdirectory(search) # add_subdirectory(search)
add_subdirectory(shootout) add_subdirectory(shootout)
add_subdirectory(spinner)
add_subdirectory(subdirfileiterator) add_subdirectory(subdirfileiterator)
add_subdirectory(tasking) add_subdirectory(tasking)
add_subdirectory(widgets) add_subdirectory(widgets)

View File

@@ -0,0 +1,23 @@
FROM fedora:37
RUN yum update
RUN yum -y install \
yum-utils \
qt-creator \
gdb \
git \
vim \
cmake \
qt \
qt-devel \
qt6-qtbase-devel \
qt6-qtdeclarative-devel \
qt6-qtquicktimeline-devel \
qt6-qtquick3d-devel \
ninja-build \
valgrind \
xclock
# && rm -rf /var/lib/apt/lists/*

View File

@@ -3,3 +3,4 @@
docker build -t qt-5-ubuntu-20.04-build -f Dockerfile-qt-5-ubuntu-20.04-build . docker build -t qt-5-ubuntu-20.04-build -f Dockerfile-qt-5-ubuntu-20.04-build .
docker build -t qt-5-ubuntu-20.04-run -f Dockerfile-qt-5-ubuntu-20.04-run . docker build -t qt-5-ubuntu-20.04-run -f Dockerfile-qt-5-ubuntu-20.04-run .
docker build -t qt-5-ubuntu-20.04-clang-lldb-build -f Dockerfile-qt-5-ubuntu-20.04-clang-lldb-build . docker build -t qt-5-ubuntu-20.04-clang-lldb-build -f Dockerfile-qt-5-ubuntu-20.04-clang-lldb-build .
docker build -t qt-6-fedora-37-build -f Dockerfile-qt-6-fedora-37-build .

View File

@@ -13,6 +13,7 @@ Project {
"pluginview/pluginview.qbs", "pluginview/pluginview.qbs",
"proparser/testreader.qbs", "proparser/testreader.qbs",
"shootout/shootout.qbs", "shootout/shootout.qbs",
"spinner/spinner.qbs",
"subdirfileiterator/subdirfileiterator.qbs", "subdirfileiterator/subdirfileiterator.qbs",
"tasking/demo/demo.qbs", "tasking/demo/demo.qbs",
"tasking/imagescaling/imagescaling.qbs", "tasking/imagescaling/imagescaling.qbs",

View File

@@ -0,0 +1,6 @@
add_qtc_test(tst_spinner
MANUALTEST
DEPENDS Spinner Qt::Widgets
SOURCES
main.cpp
)

View File

@@ -0,0 +1,167 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <spinner/spinner.h>
#include <QApplication>
#include <QBoxLayout>
#include <QCalendarWidget>
#include <QGroupBox>
#include <QLabel>
#include <QMetaEnum>
#include <QTextEdit>
#include <QToolButton>
using namespace SpinnerSolution;
enum class State {
NotRunning,
Running
};
static QString colorButtonStyleSheet(const QColor &bgColor)
{
QString rc("border-width: 2px; border-radius: 2px; border-color: black; ");
rc += bgColor.isValid() ? "border-style: solid; background:" + bgColor.name() + ";"
: QString("border-style: dotted;");
return rc;
}
static QColor stateToColor(State state)
{
switch (state) {
case State::NotRunning: return Qt::gray;
case State::Running: return Qt::yellow;
}
return {};
}
class StateIndicator : public QLabel
{
public:
StateIndicator(QWidget *parent = nullptr)
: QLabel(parent)
{
setFixedSize(30, 30);
m_spinner = new Spinner(SpinnerSize::Small, this);
m_spinner->hide();
updateState();
}
void setState(State state)
{
if (m_state == state)
return;
m_state = state;
updateState();
}
private:
void updateState()
{
setStyleSheet(colorButtonStyleSheet(stateToColor(m_state)));
if (m_state == State::Running)
m_spinner->show();
else
m_spinner->hide();
}
State m_state = State::NotRunning;
Spinner *m_spinner = nullptr;
};
class StateWidget : public QWidget
{
public:
StateWidget() : m_stateIndicator(new StateIndicator(this)) {
QBoxLayout *layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(m_stateIndicator);
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
}
void setState(State state) { m_stateIndicator->setState(state); }
protected:
StateIndicator *m_stateIndicator = nullptr;
};
QGroupBox *createGroupBox(SpinnerSize size, QWidget *widget)
{
const QMetaEnum spinnerSize = QMetaEnum::fromType<SpinnerSize>();
QGroupBox *groupBox = new QGroupBox(spinnerSize.valueToKey(int(size)));
StateWidget *stateWidget = new StateWidget;
QToolButton *startButton = new QToolButton;
startButton->setText("Start");
QToolButton *stopButton = new QToolButton;
stopButton->setText("Stop");
QVBoxLayout *mainLayout = new QVBoxLayout(groupBox);
QHBoxLayout *topLayout = new QHBoxLayout();
topLayout->addWidget(stateWidget);
topLayout->addWidget(startButton);
topLayout->addWidget(stopButton);
topLayout->addStretch();
mainLayout->addLayout(topLayout);
mainLayout->addWidget(widget);
Spinner *spinner = new Spinner(size, widget);
spinner->hide(); // TODO: make the default hidden?
QObject::connect(startButton, &QAbstractButton::clicked, groupBox, [=] {
stateWidget->setState(State::Running);
spinner->show();
widget->setEnabled(false);
});
QObject::connect(stopButton, &QAbstractButton::clicked, groupBox, [=] {
stateWidget->setState(State::NotRunning);
spinner->hide();
widget->setEnabled(true);
});
return groupBox;
}
static QWidget *hr()
{
auto frame = new QFrame;
frame->setFrameShape(QFrame::HLine);
frame->setFrameShadow(QFrame::Sunken);
return frame;
}
static QString pangram(int count)
{
return QStringList(count, "The quick brown fox jumps over the lazy dog.").join(' ');
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget mainWidget;
mainWidget.setWindowTitle("Spinner Example");
QVBoxLayout *mainLayout = new QVBoxLayout(&mainWidget);
QLabel *smallWidget = new QLabel;
smallWidget->setFixedWidth(30);
QGroupBox *smallGroupBox = createGroupBox(SpinnerSize::Small, smallWidget);
mainLayout->addWidget(smallGroupBox);
mainLayout->addWidget(hr());
QCalendarWidget *mediumWidget = new QCalendarWidget;
mediumWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
QGroupBox *mediumGroupBox = createGroupBox(SpinnerSize::Medium, mediumWidget);
mainLayout->addWidget(mediumGroupBox);
mainLayout->addWidget(hr());
QTextEdit *largeWidget = new QTextEdit;
largeWidget->setText(pangram(25));
QGroupBox *largeGroupBox = createGroupBox(SpinnerSize::Large, largeWidget);
mainLayout->addWidget(largeGroupBox);
mainWidget.show();
return app.exec();
}

View File

@@ -0,0 +1,13 @@
import qbs.FileInfo
QtcManualtest {
name: "Spinner example"
type: ["application"]
Depends { name: "Qt"; submodules: ["widgets"] }
Depends { name: "Spinner" }
files: [
"main.cpp",
]
}