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
The experimental Copilot plugin integrates
The Copilot plugin (disabled by default) integrates
[GitHub Copilot](https://github.com/features/copilot), which uses OpenAI to
suggest code in the `Edit` mode.
@@ -87,6 +87,8 @@ General
([QTCREATORBUG-26128](https://bugreports.qt.io/browse/QTCREATORBUG-26128),
[QTCREATORBUG-27006](https://bugreports.qt.io/browse/QTCREATORBUG-27006),
[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
* Improved performance
* 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))
* Fixed an issue of copy and paste with multiple cursors
([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++
@@ -111,12 +115,22 @@ Editing
* Extended the `Add Class Member` refactoring action to create class
members from assignments
([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
([QTCREATORBUG-13894](https://bugreports.qt.io/browse/QTCREATORBUG-13894))
* 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
* Fixed support for `if`-statements with initializer
([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
@@ -135,12 +149,16 @@ Editing
([QTCREATORBUG-29123](https://bugreports.qt.io/browse/QTCREATORBUG-29123))
* Fixed the completion for Qt Quick Controls
([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
* Added the option to create a virtual environment (`venv`) to the Python
interpreter selector and the wizard
([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))
@@ -168,6 +186,7 @@ Projects
[QTCREATORBUG-29006](https://bugreports.qt.io/browse/QTCREATORBUG-29006))
* Added `Build > Reload CMake Presets` to reload CMake presets after making
changes to them
* Added support for `block()` and `endblock()`
* Fixed that CMake Presets were not visible in `Projects` view
([QTCREATORBUG-28966](https://bugreports.qt.io/browse/QTCREATORBUG-28966))
* 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 >
Debugger settings`
([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++
* Added an option for the default number of array elements to show
(`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
* Added automatic source file mapping for Qt packages
* 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
\endif
To debug Qt Quick UI projects (.qmlproject), select the
\uicontrol {Enable QML} check box in \uicontrol {Debugger settings}
in \uicontrol Projects mode \uicontrol {Run Settings}.
To debug Qt Quick UI projects (.qmlproject), select \uicontrol Automatic
or \uicontrol Enabled in \uicontrol{Run Settings} >
\uicontrol {Debugger Settings} > \uicontrol {QML debugger}.
\image qtquick-debugger-settings.webp {Debugger settings section in Run Settings}
\if defined(qtcreator)
\section2 Debugging Qt Quick Applications
@@ -65,9 +67,13 @@
functions. Therefore, you must make sure that the port is properly
protected by a firewall.
\li In \uicontrol {Run Settings} > \uicontrol {Debugger settings}, select
the \uicontrol {Enable QML} check box to enable QML debugging for
running applications.
\li In \uicontrol {Run Settings} > \uicontrol {Debugger settings} >
\uicontrol {QML debugger}, select \uicontrol Automatic or
\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
rebuild the project.
@@ -119,17 +125,8 @@
For example, for qmake the global setting only affects build configurations
that are automatically created when enabling a kit. Also, CMake ignores the
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
\image qtquick-debugging-settings.png {Debugger settings section in Run Settings}
\section1 Starting QML Debugging
To start the application, choose \uicontrol Debug > \uicontrol {Start Debugging}

View File

@@ -81,7 +81,7 @@
\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.
You can view suggestions from Copilot in the code editor.

View File

@@ -8,7 +8,7 @@
\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.
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
/*!
@@ -6,10 +6,11 @@
\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
\uicontrol {Enable QML} check boxes.
To select the languages to debug, select \uicontrol Automatic
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
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.
\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}.
//! [run settings debugger]

View File

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

View File

@@ -119,6 +119,11 @@
\li Solution Name
\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
\li \l{Tasking Solution}{Tasking}
\li Enables you to build extensible, declarative task tree structures

View File

@@ -1,6 +1,7 @@
# Copyright (C) 2016 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import argparse
import os
import locale
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))]
return [fn for fn in files if not is_debug_file(os.path.join(path, fn))]
def codesign_call():
signing_identity = os.environ.get('SIGNING_IDENTITY')
def codesign_call(identity=None, flags=None):
signing_identity = identity or os.environ.get('SIGNING_IDENTITY')
if not signing_identity:
return None
codesign_call = ['codesign', '-o', 'runtime', '--force', '-s', signing_identity,
'-v']
signing_flags = os.environ.get('SIGNING_FLAGS')
signing_flags = flags or os.environ.get('SIGNING_FLAGS')
if signing_flags:
codesign_call.extend(signing_flags.split())
return codesign_call
@@ -228,8 +229,8 @@ def conditional_sign_recursive(path, filter):
if is_mac_platform():
os_walk(path, filter, lambda fp: codesign_executable(fp))
def codesign(app_path):
codesign = codesign_call()
def codesign(app_path, identity=None, flags=None):
codesign = codesign_call(identity, flags)
if not codesign or not is_mac_platform():
return
# 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',
'installer', 'mac', 'entitlements.plist')
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({})
def put(self, line):
if self.output.endswith('\n'):
self.output = self.output[0:-1]
self.output += line
if self.output:
if self.output[-1].endswith('\n'):
self.output[-1] = self.output[-1][0:-1]
self.output.append(line)
def putNumChild(self, numchild):
pass

View File

@@ -504,6 +504,21 @@ int main(int argc, char **argv)
{{"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).isEmpty()) {
Utils::Environment::modifySystemEnvironment(

View File

@@ -370,7 +370,7 @@ QByteArray LibraryInfo::calculateFingerprint() const
{
QCryptographicHash hash(QCryptographicHash::Sha1);
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));

View File

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

View File

@@ -2,6 +2,7 @@ Project {
name: "Solutions"
references: [
"spinner/spinner.qbs",
"tasking/tasking.qbs",
].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, "
"or the storage is not reachable from where it is referenced. "
"The WaitForBarrier task will finish with error. ");
return TaskAction::StopWithError;
return SetupResult::StopWithError;
}
Barrier *activeSharedBarrier = activeBarrier->barrier();
const std::optional<bool> result = activeSharedBarrier->result();
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);
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.
*/
/*!
\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
\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.
It instructs the running task tree on how to proceed after the setup handler's execution
finished.
\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
it returned Continue.
\value StopWithDone
The group's or task's execution stops immediately with success.
When returned from the group's setup handler, all child tasks are skipped,
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,
its done handler isn't invoked, and the task reports success to its parent.
\value StopWithError
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,
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,
its error handler isn't invoked, and the task reports an error to its parent.
*/
@@ -262,21 +365,21 @@ private:
/*!
\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.
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.
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.
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
success or an error, respectively.
When the return type is either TaskAction::StopWithDone
of TaskAction::StopWithError, the group's done or error handler (if provided)
When the return type is either SetupResult::StopWithDone
of SetupResult::StopWithError, the group's done or error handler (if provided)
is called synchronously immediately afterwards.
\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()>,
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()
*/
@@ -293,7 +396,7 @@ private:
/*!
\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.
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.
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
\l {GroupItem::GroupSetupHandler}.
@@ -430,9 +533,9 @@ const GroupItem stopOnFinished = workflowPolicy(WorkflowPolicy::StopOnFinished);
const GroupItem finishAllAndDone = workflowPolicy(WorkflowPolicy::FinishAllAndDone);
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
@@ -658,10 +761,10 @@ public:
TaskContainer(TaskTreePrivate *taskTreePrivate, const GroupItem &task,
TaskNode *parentNode, TaskContainer *parentContainer)
: m_constData(taskTreePrivate, task, parentNode, parentContainer, this) {}
TaskAction start();
TaskAction continueStart(TaskAction startAction, int nextChild);
TaskAction startChildren(int nextChild);
TaskAction childDone(bool success);
SetupResult start();
SetupResult continueStart(SetupResult startAction, int nextChild);
SetupResult startChildren(int nextChild);
SetupResult childDone(bool success);
void stop();
void invokeEndHandler();
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)
// in order to unwind properly.
TaskAction start();
SetupResult start();
void stop();
void invokeEndHandler(bool success);
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;
}
TaskAction TaskContainer::start()
SetupResult TaskContainer::start()
{
QTC_CHECK(!isRunning());
m_runtimeData.emplace(m_constData);
TaskAction startAction = TaskAction::Continue;
SetupResult startAction = SetupResult::Continue;
if (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);
// 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())
startAction = toTaskAction(m_runtimeData->m_successBit);
startAction = toSetupResult(m_runtimeData->m_successBit);
}
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;
QTC_CHECK(isRunning()); // TODO: superfluous
if (groupAction != TaskAction::Continue) {
const bool success = m_runtimeData->updateSuccessBit(groupAction == TaskAction::StopWithDone);
if (groupAction != SetupResult::Continue) {
const bool success = m_runtimeData->updateSuccessBit(groupAction == SetupResult::StopWithDone);
invokeEndHandler();
if (TaskContainer *parentContainer = m_constData.m_parentContainer) {
QTC_CHECK(parentContainer->isRunning());
@@ -984,7 +1090,7 @@ TaskAction TaskContainer::continueStart(TaskAction startAction, int nextChild)
return groupAction;
}
TaskAction TaskContainer::startChildren(int nextChild)
SetupResult TaskContainer::startChildren(int nextChild)
{
QTC_CHECK(isRunning());
GuardLocker locker(m_runtimeData->m_startGuard);
@@ -993,12 +1099,12 @@ TaskAction TaskContainer::startChildren(int nextChild)
if (i >= limit)
break;
const TaskAction startAction = m_constData.m_children.at(i)->start();
if (startAction == TaskAction::Continue)
const SetupResult startAction = m_constData.m_children.at(i)->start();
if (startAction == SetupResult::Continue)
continue;
const TaskAction finalizeAction = childDone(startAction == TaskAction::StopWithDone);
if (finalizeAction == TaskAction::Continue)
const SetupResult finalizeAction = childDone(startAction == SetupResult::StopWithDone);
if (finalizeAction == SetupResult::Continue)
continue;
int skippedTaskCount = 0;
@@ -1008,10 +1114,10 @@ TaskAction TaskContainer::startChildren(int nextChild)
m_constData.m_taskTreePrivate->advanceProgress(skippedTaskCount);
return finalizeAction;
}
return TaskAction::Continue;
return SetupResult::Continue;
}
TaskAction TaskContainer::childDone(bool success)
SetupResult TaskContainer::childDone(bool success)
{
QTC_CHECK(isRunning());
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;
const bool updatedSuccess = m_runtimeData->updateSuccessBit(success);
const TaskAction startAction
const SetupResult startAction
= (shouldStop || m_runtimeData->m_doneCount == m_constData.m_children.size())
? toTaskAction(updatedSuccess) : TaskAction::Continue;
? toSetupResult(updatedSuccess) : SetupResult::Continue;
if (isStarting())
return startAction;
@@ -1059,30 +1165,30 @@ void TaskContainer::invokeEndHandler()
m_runtimeData.reset();
}
TaskAction TaskNode::start()
SetupResult TaskNode::start()
{
QTC_CHECK(!isRunning());
if (!isTask())
return m_container.start();
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())
: TaskAction::Continue;
if (startAction != TaskAction::Continue) {
: SetupResult::Continue;
if (startAction != SetupResult::Continue) {
m_container.m_constData.m_taskTreePrivate->advanceProgress(1);
m_task.reset();
return startAction;
}
const std::shared_ptr<TaskAction> unwindAction
= std::make_shared<TaskAction>(TaskAction::Continue);
const std::shared_ptr<SetupResult> unwindAction
= std::make_shared<SetupResult>(SetupResult::Continue);
QObject::connect(m_task.get(), &TaskInterface::done, taskTree(), [=](bool success) {
invokeEndHandler(success);
QObject::disconnect(m_task.get(), &TaskInterface::done, taskTree(), nullptr);
m_task.release()->deleteLater();
QTC_ASSERT(parentContainer() && parentContainer()->isRunning(), return);
if (parentContainer()->isStarting())
*unwindAction = toTaskAction(success);
*unwindAction = toSetupResult(success);
else
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,
it must be the first argument of the task's constructor.
Optionally, the setup handler may return a TaskAction. The returned
TaskAction influences the further start behavior of a given task. The
Optionally, the setup handler may return a SetupResult. The returned
SetupResult influences the further start behavior of a given task. The
possible values are:
\table
\header
\li TaskAction Value
\li SetupResult Value
\li Brief Description
\row
\li Continue
\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).
\row
\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
is triggered at runtime that includes an error message.
Like the task's start handler, the group start handler may return TaskAction.
The returned TaskAction value affects the start behavior of the
Like the task's start handler, the group start handler may return SetupResult.
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
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
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,
depending on the returned value, respectively.
@@ -1417,15 +1523,15 @@ void TaskNode::invokeEndHandler(bool success)
const Group root {
onGroupSetup([] { qDebug() << "Root setup"; }),
Group {
onGroupSetup([] { qDebug() << "Group 1 setup"; return TaskAction::Continue; }),
onGroupSetup([] { qDebug() << "Group 1 setup"; return SetupResult::Continue; }),
ProcessTask(...) // Process 1
},
Group {
onGroupSetup([] { qDebug() << "Group 2 setup"; return TaskAction::StopWithDone; }),
onGroupSetup([] { qDebug() << "Group 2 setup"; return SetupResult::StopWithDone; }),
ProcessTask(...) // Process 2
},
Group {
onGroupSetup([] { qDebug() << "Group 3 setup"; return TaskAction::StopWithError; }),
onGroupSetup([] { qDebug() << "Group 3 setup"; return SetupResult::StopWithError; }),
ProcessTask(...) // Process 3
},
ProcessTask(...) // Process 4
@@ -1441,7 +1547,7 @@ void TaskNode::invokeEndHandler(bool success)
\li Comment
\row
\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
\li Group 1 starts
\li Returns Continue, so its tasks are executed.
@@ -1833,7 +1939,7 @@ void TaskTree::setRecipe(const Group &recipe)
Otherwise, the task tree is started.
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
established before calling start. Use isRunning() in order to detect whether
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
public:
TaskInterface() = default;
virtual void start() = 0;
signals:
void done(bool success);
private:
template <typename Task> friend class TaskAdapter;
friend class TaskNode;
TaskInterface() = default;
virtual void start() = 0;
};
class TASKING_EXPORT TreeStorageBase
@@ -115,13 +117,13 @@ enum class WorkflowPolicy {
};
Q_ENUM_NS(WorkflowPolicy);
enum class TaskAction
enum class SetupResult
{
Continue,
StopWithDone,
StopWithError
};
Q_ENUM_NS(TaskAction);
Q_ENUM_NS(SetupResult);
class TASKING_EXPORT GroupItem
{
@@ -129,11 +131,11 @@ public:
// Internal, provided by QTC_DECLARE_CUSTOM_TASK
using TaskCreateHandler = std::function<TaskInterface *(void)>;
// 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
using TaskEndHandler = std::function<void(const TaskInterface &)>;
// Called when group entered
using GroupSetupHandler = std::function<TaskAction()>;
using GroupSetupHandler = std::function<SetupResult()>;
// Called when group done / error
using GroupEndHandler = std::function<void()>;
@@ -228,17 +230,17 @@ private:
static GroupSetupHandler wrapGroupSetup(SetupHandler &&handler)
{
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
= std::is_same_v<void, std::invoke_result_t<std::decay_t<SetupHandler>>>;
static_assert(isDynamic || isVoid,
"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 [=] {
if constexpr (isDynamic)
return std::invoke(handler);
std::invoke(handler);
return TaskAction::Continue;
return SetupResult::Continue;
};
};
};
@@ -290,22 +292,24 @@ private:
static_assert(isBool || isVoid,
"Sync element: The synchronous function has to return void or bool.");
if constexpr (isBool) {
return {onGroupSetup([function] { return function() ? TaskAction::StopWithDone
: TaskAction::StopWithError; })};
return {onGroupSetup([function] { return function() ? SetupResult::StopWithDone
: SetupResult::StopWithError; })};
}
return {onGroupSetup([function] { function(); return TaskAction::StopWithDone; })};
return {onGroupSetup([function] { function(); return SetupResult::StopWithDone; })};
};
};
template <typename Task>
class TaskAdapter : public TaskInterface
{
public:
protected:
using Type = Task;
TaskAdapter() = default;
Task *task() { return &m_task; }
const Task *task() const { return &m_task; }
private:
template <typename Adapter> friend class CustomTask;
Task m_task;
};
@@ -344,19 +348,19 @@ public:
private:
template<typename SetupFunction>
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 &>>;
constexpr bool isVoid = std::is_same_v<void,
std::invoke_result_t<std::decay_t<SetupFunction>, typename Adapter::Type &>>;
static_assert(isDynamic || isVoid,
"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) {
Adapter &adapter = static_cast<Adapter &>(taskInterface);
if constexpr (isDynamic)
return 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>();
}
switch (d->m_labelPlacement) {
case LabelPlacement::AtCheckBoxWithoutDummyLabel:
d->m_button->setText(labelText());
parent.addItem(d->m_button.data());
break;
case LabelPlacement::AtCheckBox:
d->m_button->setText(labelText());
parent.addItem(empty());
parent.addItem(d->m_button.data());
break;
case LabelPlacement::InExtraLabel:

View File

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

View File

@@ -1444,16 +1444,15 @@ CommandLine CommandLine::fromUserInput(const QString &cmdline, MacroExpander *ex
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())
return {};
auto cmd = CommandLine(FilePath::fromUserInput(result.value(0)), result.mid(1));
if (expander)
cmd.m_arguments = expander->expand(cmd.m_arguments);
return cmd;
return {FilePath::fromUserInput(result.value(0)), result.mid(1)};
}
void CommandLine::addArg(const QString &arg)

View File

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

View File

@@ -91,7 +91,7 @@ static CreateAvdInfo createAvdCommand(const AndroidConfig &config, const CreateA
proc.setCommand(avdManager);
proc.start();
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;
}
QTC_CHECK(proc.isRunning());

View File

@@ -389,7 +389,7 @@ FilePath AndroidQmlPreviewWorker::createQmlrcFile(const FilePath &workFolder,
rccProcess.setCommand({rccBinary, args});
rccProcess.start();
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()),
StdErrFormat);
qrcPath.removeFile();
@@ -400,7 +400,7 @@ FilePath AndroidQmlPreviewWorker::createQmlrcFile(const FilePath &workFolder,
if (!rccProcess.readDataFromProcess(&stdOut, &stdErr)) {
rccProcess.stop();
rccProcess.waitForFinished();
appendMessage(Tr::tr("A timeout occurred running \"%1\"").
appendMessage(Tr::tr("A timeout occurred running \"%1\".").
arg(rccProcess.commandLine().toUserOutput()), StdErrFormat);
qrcPath.removeFile();
return {};
@@ -412,7 +412,7 @@ FilePath AndroidQmlPreviewWorker::createQmlrcFile(const FilePath &workFolder,
appendMessage(QString::fromLocal8Bit(stdErr), StdErrFormat);
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()),
StdErrFormat);
qrcPath.removeFile();

View File

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

View File

@@ -102,8 +102,13 @@ QStringList BoostTestConfiguration::argumentsForTestRunner(QStringList *omitted)
arguments << "--detect_memory_leaks=0";
// 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())
arguments << "-t" << test;
} else {
for (const QString &test : testCases())
arguments << "-t" << "\"" + test + "\"";
}
if (TestSettings::instance()->processArgs()) {
arguments << filterInterfering(runnable().command.arguments().split(

View File

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

View File

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

View File

@@ -90,7 +90,7 @@ void CTestOutputReader::processOutputLine(const QByteArray &outputLine)
m_project = match.captured(1);
TestResult testResult = createDefaultResult();
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);
} else if (ExactMatch match = testCase1.match(line)) {
int current = match.captured("current").toInt();

View File

@@ -23,18 +23,6 @@ GTestOutputReader::GTestOutputReader(Process *testApplication,
: TestOutputReader(testApplication, buildDirectory)
, 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)
@@ -81,7 +69,7 @@ void GTestOutputReader::processOutputLine(const QByteArray &outputLine)
if (ExactMatch match = testEnds.match(line)) {
TestResult testResult = createDefaultResult();
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);
m_currentTestSuite.clear();
m_currentTestCase.clear();
@@ -182,6 +170,17 @@ TestResult GTestOutputReader::createDefaultResult() const
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)
{
m_currentTestCase = testCase;

View File

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

View File

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

View File

@@ -355,13 +355,13 @@ void TestRunner::runTestsHelper()
const auto onSetup = [this, config] {
if (!config->project())
return TaskAction::StopWithDone;
return SetupResult::StopWithDone;
if (config->testExecutable().isEmpty()) {
reportResult(ResultType::MessageFatal,
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) {
TestStorage *testStorage = storage.activeStorage();
@@ -419,6 +419,9 @@ void TestRunner::runTestsHelper()
+ processInformation(&process) + rcInfo(config));
}
if (testStorage->m_outputReader)
testStorage->m_outputReader->onDone(process.exitCode());
if (process.exitStatus() == QProcess::CrashExit) {
if (testStorage->m_outputReader)
testStorage->m_outputReader->reportCrash();

View File

@@ -11,6 +11,7 @@
#include <utils/utilsicons.h>
#include <QFormLayout>
#include <QGridLayout>
#include <QLabel>
#include <QScrollArea>
#include <QStackedWidget>
@@ -28,7 +29,8 @@ public:
private:
QLabel *m_project = nullptr;
QLabel *m_loc = nullptr;
QFormLayout *m_formLayout = nullptr;
QLabel *m_timestamp = nullptr;
QGridLayout *m_gridLayout = nullptr;
};
DashboardWidget::DashboardWidget(QWidget *parent)
@@ -41,58 +43,98 @@ DashboardWidget::DashboardWidget(QWidget *parent)
projectLayout->addRow(Tr::tr("Project:"), m_project);
m_loc = new QLabel(this);
projectLayout->addRow(Tr::tr("Lines of code:"), m_loc);
m_timestamp = new QLabel(this);
projectLayout->addRow(Tr::tr("Analysis timestamp:"), m_timestamp);
layout->addLayout(projectLayout);
m_formLayout = new QFormLayout;
layout->addLayout(m_formLayout);
layout->addSpacing(10);
auto row = new QHBoxLayout;
m_gridLayout = new QGridLayout;
row->addLayout(m_gridLayout);
row->addStretch(1);
layout->addLayout(row);
layout->addStretch(1);
setWidget(widget);
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
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()
{
const ProjectInfo &info = AxivionPlugin::projectInfo();
m_project->setText(info.name);
m_loc->setText({});
while (m_formLayout->rowCount())
m_formLayout->removeRow(0);
m_timestamp->setText({});
QLayoutItem *child;
while ((child = m_gridLayout->takeAt(0)) != nullptr) {
delete child->widget();
delete child;
}
if (info.versions.isEmpty())
return;
const ResultVersion &last = info.versions.last();
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;
auto toolTip = [issueKinds](const QString &prefix){
for (const IssueKind &kind : issueKinds) {
if (kind.prefix == prefix)
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)) {
allTotal += issueCount.total;
allAdded += issueCount.added;
allRemoved += issueCount.removed;
const QString txt = apply(issueCount.total, issueCount.added, issueCount.removed);
const QString currentToolTip = toolTip(issueCount.issueKind);
QLabel *label = new QLabel(issueCount.issueKind, this);
label->setToolTip(currentToolTip);
QLabel *values = new QLabel(txt, this);
values->setToolTip(currentToolTip);
m_formLayout->addRow(label, values);
addValuesWidgets(issueCount, row);
++row;
}
QLabel *label = new QLabel(apply(allTotal, allAdded, allRemoved), this);
m_formLayout->addRow(Tr::tr("Total:"), label);
const IssueCount total{{}, Tr::tr("Total:"), allTotal, allAdded, allRemoved};
addValuesWidgets(total, row);
}
AxivionOutputPane::AxivionOutputPane(QObject *parent)

View File

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

View File

@@ -128,23 +128,23 @@ GroupItem clangToolTask(const AnalyzeInputData &input,
const auto onSetup = [=] {
if (setupHandler && !setupHandler())
return TaskAction::StopWithError;
return SetupResult::StopWithError;
ClangToolStorage *data = storage.activeStorage();
data->name = clangToolName(input.tool);
data->executable = toolExecutable(input.tool);
if (!data->executable.isExecutableFile()) {
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(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);
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) {
process.setEnvironment(input.environment);

View File

@@ -12,6 +12,10 @@
#include "cmakeprojectmanagertr.h"
#include "cmaketool.h"
#include <android/androidconstants.h>
#include <ios/iosconstants.h>
#include <coreplugin/find/itemviewfind.h>
#include <projectexplorer/buildsteplist.h>
#include <projectexplorer/devicesupport/idevice.h>
@@ -173,13 +177,15 @@ static QString initialStagingDir(Kit *kit)
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 buildDevice = BuildDeviceKitAspect::device(kit);
QTC_ASSERT(runDevice, 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) :
@@ -198,7 +204,7 @@ CMakeBuildStep::CMakeBuildStep(BuildStepList *bsl, Id id) :
m_useStaging = addAspect<BoolAspect>();
m_useStaging->setSettingsKey(USE_STAGING_KEY);
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->setSettingsKey(STAGING_DIR_KEY);

View File

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

View File

@@ -12,6 +12,8 @@ const char COPILOT_TOGGLE[] = "Copilot.Toggle";
const char COPILOT_ENABLE[] = "Copilot.Enable";
const char COPILOT_DISABLE[] = "Copilot.Disable";
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_CATEGORY[] = "ZY.Copilot";

View File

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

View File

@@ -9,6 +9,7 @@
#include "copilotoptionspage.h"
#include "copilotprojectpanel.h"
#include "copilotsettings.h"
#include "copilotsuggestion.h"
#include "copilottr.h"
#include <coreplugin/actionmanager/actionmanager.h>
@@ -20,6 +21,7 @@
#include <projectexplorer/projectpanelfactory.h>
#include <texteditor/textdocumentlayout.h>
#include <texteditor/texteditor.h>
#include <QAction>
@@ -32,6 +34,28 @@ using namespace ProjectExplorer;
namespace Copilot {
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()
{
CopilotSettings::instance().readSettings(ICore::settings());
@@ -57,6 +81,30 @@ void CopilotPlugin::initialize()
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);
disableAction->setText(Tr::tr("Disable Copilot"));
disableAction->setToolTip(Tr::tr("Disable Copilot."));

View File

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

View File

@@ -82,9 +82,9 @@ DirectoryFilter::DirectoryFilter(Id id)
using namespace Tasking;
const auto groupSetup = [this] {
if (!m_directories.isEmpty())
return TaskAction::Continue; // Async task will run
return SetupResult::Continue; // Async task will run
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) {
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) {
auto that = weak.lock();
if (!that) // LocatorMatcher is running after *this LocatorFileCache was destructed.
return TaskAction::StopWithDone;
return SetupResult::StopWithDone;
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
that->bumpExecutionId();
async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(&filter, *storage, *that);
return TaskAction::Continue;
return SetupResult::Continue;
};
const auto onDone = [weak](const Async<LocatorFileCachePrivate> &async) {
auto that = weak.lock();

View File

@@ -372,7 +372,7 @@ LocatorMatcherTasks JavaScriptFilter::matchers()
const auto onSetup = [storage, engine] {
if (!engine)
return TaskAction::StopWithError;
return SetupResult::StopWithError;
if (storage->input().trimmed().isEmpty()) {
LocatorFilterEntry entry;
entry.displayName = Tr::tr("Reset Engine");
@@ -385,9 +385,9 @@ LocatorMatcherTasks JavaScriptFilter::matchers()
return AcceptResult();
};
storage->reportOutput({entry});
return TaskAction::StopWithDone;
return SetupResult::StopWithDone;
}
return TaskAction::Continue;
return SetupResult::Continue;
};
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 FilePath input = link.targetFilePath;
if (input.isEmpty())
return TaskAction::StopWithDone;
return SetupResult::StopWithDone;
// only pass the file name part to allow searches like "somepath/*foo"
const std::unique_ptr<MacroExpander> expander(createMacroExpander(input.fileName()));
@@ -189,7 +189,7 @@ LocatorMatcherTasks SpotlightLocatorFilter::matchers()
CommandLine::Raw);
async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(matches, *storage, cmd);
return TaskAction::Continue;
return SetupResult::Continue;
};
return {{AsyncTask<void>(onSetup), storage}};

View File

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

View File

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

View File

@@ -1698,27 +1698,35 @@ void ClientPrivate::log(const ShowMessageParams &message)
LanguageClientValue<MessageActionItem> ClientPrivate::showMessageBox(
const ShowMessageRequestParams &message)
{
auto box = new QMessageBox();
box->setText(message.toString());
box->setAttribute(Qt::WA_DeleteOnClose);
QMessageBox box;
box.setText(message.toString());
switch (message.type()) {
case Error: box->setIcon(QMessageBox::Critical); break;
case Warning: box->setIcon(QMessageBox::Warning); break;
case Info: box->setIcon(QMessageBox::Information); break;
case Log: box->setIcon(QMessageBox::NoIcon); break;
case Error:
box.setIcon(QMessageBox::Critical);
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;
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) {
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);
}
}
if (box->exec() == QDialog::Rejected)
if (box.exec() == QDialog::Rejected || itemForButton.isEmpty())
return {};
const MessageActionItem &item = itemForButton.value(box->clickedButton());
const MessageActionItem &item = itemForButton.value(box.clickedButton());
return item.isValid() ? LanguageClientValue<MessageActionItem>(item)
: 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 QList<SymbolInformation> results = *resultStorage;
if (results.isEmpty())
return TaskAction::StopWithDone;
return SetupResult::StopWithDone;
async.setFutureSynchronizer(ExtensionSystem::PluginManager::futureSynchronizer());
async.setConcurrentCallData(filterResults, *storage, client, results, filter);
return TaskAction::Continue;
return SetupResult::Continue;
};
const Group root {

View File

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

View File

@@ -18,6 +18,7 @@
#include "test/unittest.h"
#endif
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/icontext.h>
#include <coreplugin/icore.h>
@@ -34,9 +35,14 @@
#include <cmakeprojectmanager/cmakeprojectconstants.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <qmljstools/qmljstoolsconstants.h>
#include <utils/filepath.h>
#include <utils/infobar.h>
#include <QAction>
#include <QDateTime>
#include <QTimer>
using namespace Core;
@@ -119,6 +125,41 @@ void McuSupportPlugin::initialize()
&ProjectManager::projectFinishedParsing,
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.registerExamples();
ProjectExplorer::JsonWizardFactory::addWizardPath(":/mcusupport/wizards/");

View File

@@ -70,6 +70,7 @@ public:
{
if (m_widget)
return m_widget->displayIcon();
QTC_ASSERT(m_kit, return {});
return m_kit->displayIcon();
}
@@ -77,6 +78,7 @@ public:
{
if (m_widget)
return m_widget->displayName();
QTC_ASSERT(m_kit, return {});
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->setChecked(m_useTerminal);
m_checkBox->setEnabled(isEnabled());
parent.addItems({{}, m_checkBox.data()});
parent.addItems({empty(), m_checkBox.data()});
connect(m_checkBox.data(), &QAbstractButton::clicked, this, [this] {
m_userSet = true;
m_useTerminal = m_checkBox->isChecked();

View File

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

View File

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

View File

@@ -19,12 +19,13 @@ using namespace ProjectExplorer;
namespace QtSupport {
// opt. drive letter + filename: (2 brackets)
#define FILE_PATTERN R"(^(?<file>(?:[A-Za-z]:)?[^:\(]+\.[^:\(]+))"
#define FILE_PATTERN R"((?<file>(?:[A-Za-z]:)?[^:\(]+\.[^:\(]+))"
QtParser::QtParser() :
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_translationRegExp(R"(^(?<level>[Ww]arning|[Ee]rror):\s+(?<description>.*?) in '(?<file>.*?)'$)")
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_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"));
}
@@ -89,19 +90,22 @@ Utils::OutputLineParser::Result QtParser::handleLine(const QString &line, Utils:
scheduleTask(task, 1);
return {Status::Done, linkSpecs};
}
if (lne.startsWith(QLatin1String("Error:"))) {
constexpr int matchLength = 6;
CompileTask task(Task::TaskType::Error, line.mid(matchLength).trimmed());
match = m_qmlToolsRegExp.match(line);
if (match.hasMatch()) {
const Task::TaskType type = match.captured("level") == "Error" ? Task::Error
: 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);
return Status::Done;
}
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::Done, linkSpecs};
}
return Status::NotHandled;
@@ -220,14 +224,21 @@ void QtSupportPlugin::testQtOutputParser_data()
QLatin1String("dropping duplicate messages"),
Utils::FilePath::fromUserInput(QLatin1String("/some/place/qtcreator_fr.qm")), -1))
<< QString();
QTest::newRow("qmlsc warning") // QTCREATORBUG-28720
<< QString::fromUtf8("Warning: Main.qml:4:1: Warnings occurred while importing module "
QTest::newRow("qmlsc/qmllint warning") // QTCREATORBUG-28720
<< QString::fromLatin1("Warning: Main.qml:4:1: Warnings occurred while importing module "
"\"QtQuick.Controls\": [import]\"")
<< OutputParserTester::STDERR << QString() << QString()
<< (Tasks() << CompileTask(Task::Warning,
QString::fromUtf8(
"Main.qml:4:1: Warnings occurred while importing module "
"\"QtQuick.Controls\": [import]\"")))
"Warnings occurred while importing module \"QtQuick.Controls\": [import]\"",
Utils::FilePath::fromUserInput("Main.qml"), 4, 1))
<< 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();
}

View File

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

View File

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

View File

@@ -167,13 +167,13 @@ SubversionDiffEditorController::SubversionDiffEditorController(IDocument *docume
const auto setupDescription = [this](Process &process) {
if (m_changeNumber == 0)
return TaskAction::StopWithDone;
return SetupResult::StopWithDone;
setupCommand(process, {"log", "-r", QString::number(m_changeNumber)});
CommandLine command = process.commandLine();
command << SubversionClient::AddAuthOptions();
process.setCommand(command);
setDescription(Tr::tr("Waiting for data..."));
return TaskAction::Continue;
return SetupResult::Continue;
};
const auto onDescriptionDone = [this](const Process &process) {
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 &) {
storage->m_log.append({taskId, Handler::Setup});
return action;
@@ -274,7 +274,7 @@ void tst_Tasking::testTree_data()
};
const auto createDynamicTask = [storage, setupDynamicTask, setupDone, setupError](
int taskId, TaskAction action) {
int taskId, SetupResult action) {
return TestTask(setupDynamicTask(taskId, action), setupDone(taskId), setupError(taskId));
};
@@ -302,35 +302,74 @@ void tst_Tasking::testTree_data()
};
const Group root2 {
Storage(storage),
onGroupSetup([] { return TaskAction::Continue; }),
onGroupSetup([] { return SetupResult::Continue; }),
groupDone(0),
groupError(0)
};
const Group root3 {
Storage(storage),
onGroupSetup([] { return TaskAction::StopWithDone; }),
onGroupSetup([] { return SetupResult::StopWithDone; }),
groupDone(0),
groupError(0)
};
const Group root4 {
Storage(storage),
onGroupSetup([] { return TaskAction::StopWithError; }),
onGroupSetup([] { return SetupResult::StopWithError; }),
groupDone(0),
groupError(0)
};
const Log logDone {{0, Handler::GroupDone}};
const Log logError {{0, Handler::GroupError}};
QTest::newRow("Empty") << TestData{storage, root1, 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("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 {
Storage(storage),
createDynamicTask(1, TaskAction::StopWithDone),
createDynamicTask(2, TaskAction::StopWithDone)
createDynamicTask(1, SetupResult::StopWithDone),
createDynamicTask(2, SetupResult::StopWithDone)
};
const Log log {{1, Handler::Setup}, {2, Handler::Setup}};
QTest::newRow("DynamicTaskDone") << TestData{storage, root, log, 2, OnDone::Success};
@@ -339,8 +378,8 @@ void tst_Tasking::testTree_data()
{
const Group root {
Storage(storage),
createDynamicTask(1, TaskAction::StopWithError),
createDynamicTask(2, TaskAction::StopWithError)
createDynamicTask(1, SetupResult::StopWithError),
createDynamicTask(2, SetupResult::StopWithError)
};
const Log log {{1, Handler::Setup}};
QTest::newRow("DynamicTaskError") << TestData{storage, root, log, 2, OnDone::Failure};
@@ -349,10 +388,10 @@ void tst_Tasking::testTree_data()
{
const Group root {
Storage(storage),
createDynamicTask(1, TaskAction::Continue),
createDynamicTask(2, TaskAction::Continue),
createDynamicTask(3, TaskAction::StopWithError),
createDynamicTask(4, TaskAction::Continue)
createDynamicTask(1, SetupResult::Continue),
createDynamicTask(2, SetupResult::Continue),
createDynamicTask(3, SetupResult::StopWithError),
createDynamicTask(4, SetupResult::Continue)
};
const Log log {
{1, Handler::Setup},
@@ -368,10 +407,10 @@ void tst_Tasking::testTree_data()
const Group root {
parallel,
Storage(storage),
createDynamicTask(1, TaskAction::Continue),
createDynamicTask(2, TaskAction::Continue),
createDynamicTask(3, TaskAction::StopWithError),
createDynamicTask(4, TaskAction::Continue)
createDynamicTask(1, SetupResult::Continue),
createDynamicTask(2, SetupResult::Continue),
createDynamicTask(3, SetupResult::StopWithError),
createDynamicTask(4, SetupResult::Continue)
};
const Log log {
{1, Handler::Setup},
@@ -387,12 +426,12 @@ void tst_Tasking::testTree_data()
const Group root {
parallel,
Storage(storage),
createDynamicTask(1, TaskAction::Continue),
createDynamicTask(2, TaskAction::Continue),
createDynamicTask(1, SetupResult::Continue),
createDynamicTask(2, SetupResult::Continue),
Group {
createDynamicTask(3, TaskAction::StopWithError)
createDynamicTask(3, SetupResult::StopWithError)
},
createDynamicTask(4, TaskAction::Continue)
createDynamicTask(4, SetupResult::Continue)
};
const Log log {
{1, Handler::Setup},
@@ -408,16 +447,16 @@ void tst_Tasking::testTree_data()
const Group root {
parallel,
Storage(storage),
createDynamicTask(1, TaskAction::Continue),
createDynamicTask(2, TaskAction::Continue),
createDynamicTask(1, SetupResult::Continue),
createDynamicTask(2, SetupResult::Continue),
Group {
onGroupSetup([storage] {
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 {
{1, Handler::Setup},
@@ -1280,14 +1319,14 @@ void tst_Tasking::testTree_data()
{
const auto createRoot = [storage, createSuccessTask, groupDone, groupError](
TaskAction taskAction) {
SetupResult setupResult) {
return Group {
Storage(storage),
Group {
createSuccessTask(1)
},
Group {
onGroupSetup([=] { return taskAction; }),
onGroupSetup([=] { return setupResult; }),
createSuccessTask(2),
createSuccessTask(3),
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 {
{1, Handler::Setup},
{1, Handler::Done},
@@ -1305,7 +1344,7 @@ void tst_Tasking::testTree_data()
};
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 {
{1, Handler::Setup},
{1, Handler::Done},
@@ -1313,7 +1352,7 @@ void tst_Tasking::testTree_data()
};
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 {
{1, Handler::Setup},
{1, Handler::Done},
@@ -1380,7 +1419,7 @@ void tst_Tasking::testTree_data()
},
Group {
groupSetup(3),
createDynamicTask(3, TaskAction::StopWithDone)
createDynamicTask(3, SetupResult::StopWithDone)
},
Group {
groupSetup(4),
@@ -1424,7 +1463,7 @@ void tst_Tasking::testTree_data()
},
Group {
groupSetup(3),
createDynamicTask(3, TaskAction::StopWithError)
createDynamicTask(3, SetupResult::StopWithError)
},
Group {
groupSetup(4),
@@ -1463,7 +1502,7 @@ void tst_Tasking::testTree_data()
},
Group {
groupSetup(3),
createDynamicTask(3, TaskAction::StopWithError)
createDynamicTask(3, SetupResult::StopWithError)
},
Group {
groupSetup(4),
@@ -1508,7 +1547,7 @@ void tst_Tasking::testTree_data()
},
Group {
groupSetup(3),
createDynamicTask(3, TaskAction::StopWithError)
createDynamicTask(3, SetupResult::StopWithError)
},
Group {
groupSetup(4),
@@ -1605,7 +1644,7 @@ void tst_Tasking::testTree_data()
},
Group {
groupSetup(3),
Group { createDynamicTask(3, TaskAction::StopWithDone) }
Group { createDynamicTask(3, SetupResult::StopWithDone) }
},
Group {
groupSetup(4),
@@ -1650,7 +1689,7 @@ void tst_Tasking::testTree_data()
},
Group {
groupSetup(3),
Group { createDynamicTask(3, TaskAction::StopWithError) }
Group { createDynamicTask(3, SetupResult::StopWithError) }
},
Group {
groupSetup(4),

View File

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

View File

@@ -16,6 +16,7 @@ add_subdirectory(proparser)
# add_subdirectory(qt4projectmanager)
# add_subdirectory(search)
add_subdirectory(shootout)
add_subdirectory(spinner)
add_subdirectory(subdirfileiterator)
add_subdirectory(tasking)
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-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-6-fedora-37-build -f Dockerfile-qt-6-fedora-37-build .

View File

@@ -13,6 +13,7 @@ Project {
"pluginview/pluginview.qbs",
"proparser/testreader.qbs",
"shootout/shootout.qbs",
"spinner/spinner.qbs",
"subdirfileiterator/subdirfileiterator.qbs",
"tasking/demo/demo.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",
]
}