diff --git a/src/libs/qmljs/qmljsdialect.cpp b/src/libs/qmljs/qmljsdialect.cpp index 8e4ff8ca5ae..9746490871f 100644 --- a/src/libs/qmljs/qmljsdialect.cpp +++ b/src/libs/qmljs/qmljsdialect.cpp @@ -280,11 +280,10 @@ bool PathsAndLanguages::maybeInsert(const PathAndLanguage &pathAndLanguage) { if (currentElement.path() == pathAndLanguage.path()) { int j = i; do { - if (pathAndLanguage.language() < currentElement.language()) { - if (currentElement.language() == pathAndLanguage.language()) - return false; + if (pathAndLanguage.language() < currentElement.language()) break; - } + if (currentElement.language() == pathAndLanguage.language()) + return false; ++j; if (j == m_list.length()) break; diff --git a/src/libs/utils/treemodel.cpp b/src/libs/utils/treemodel.cpp index db8923e372a..19f3fc72c48 100644 --- a/src/libs/utils/treemodel.cpp +++ b/src/libs/utils/treemodel.cpp @@ -841,6 +841,18 @@ TreeItem *TreeItem::findAnyChild(const std::function &pred) co return 0; } +TreeItem *TreeItem::reverseFindAnyChild(const std::function &pred) const +{ + auto end = m_children.rend(); + for (auto it = m_children.rbegin(); it != end; ++it) { + if (pred(*it)) + return *it; + if (TreeItem *found = (*it)->reverseFindAnyChild(pred)) + return found; + } + return nullptr; +} + void TreeItem::clear() { while (childCount() != 0) { diff --git a/src/libs/utils/treemodel.h b/src/libs/utils/treemodel.h index 60db234d3b4..d62670867be 100644 --- a/src/libs/utils/treemodel.h +++ b/src/libs/utils/treemodel.h @@ -78,6 +78,8 @@ public: void forSelectedChildren(const std::function &pred) const; void forAllChildren(const std::function &pred) const; TreeItem *findAnyChild(const std::function &pred) const; + // like findAnyChild() but processes children from bottom to top + TreeItem *reverseFindAnyChild(const std::function &pred) const; // Levels are 1-based: Child at Level 1 is an immediate child. void forChildrenAtLevel(int level, const std::function &pred) const; diff --git a/src/plugins/android/adbcommandswidget.cpp b/src/plugins/android/adbcommandswidget.cpp new file mode 100644 index 00000000000..404ec55b2b4 --- /dev/null +++ b/src/plugins/android/adbcommandswidget.cpp @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ +#include "adbcommandswidget.h" +#include "ui_adbcommandswidget.h" + +#include "utils/utilsicons.h" + +#include +#include +#include +#include + +#include + +namespace Android { +namespace Internal { + +using namespace std::placeholders; + +const char defaultCommand[] = "echo \"shell command\""; + +static void swapData(QStringListModel *model, const QModelIndex &srcIndex, + const QModelIndex &destIndex) +{ + if (model) { + QVariant data = model->data(destIndex, Qt::EditRole); // QTBUG-55078 + model->setData(destIndex, model->data(srcIndex, Qt::EditRole)); + model->setData(srcIndex, data); + } +} + +class AdbCommandsWidgetPrivate +{ +public: + AdbCommandsWidgetPrivate(const AdbCommandsWidget &parent, QWidget *parentWidget); + ~AdbCommandsWidgetPrivate(); + +private: + void addString(const QString &str); + void onAddButton(); + void onMoveUpButton(); + void onMoveDownButton(); + void onRemove(); + void onCurrentIndexChanged(const QModelIndex &newIndex, const QModelIndex &prevIndex); + + const AdbCommandsWidget &m_parent; + QGroupBox *m_rootWidget = nullptr; + Ui::AdbCommandsWidget *m_ui = nullptr; + QStringListModel *m_stringModel = nullptr; + friend class AdbCommandsWidget; +}; + +AdbCommandsWidget::AdbCommandsWidget(QWidget *parent) : + QObject(parent), + d(new AdbCommandsWidgetPrivate(*this, parent)) +{ +} + +AdbCommandsWidget::~AdbCommandsWidget() +{ +} + +QStringList AdbCommandsWidget::commandsList() const +{ + return d->m_stringModel->stringList(); +} + +QWidget *AdbCommandsWidget::widget() const +{ + return d->m_rootWidget; +} + +void AdbCommandsWidget::setTitleText(const QString &title) +{ + d->m_rootWidget->setTitle(title); +} + +void AdbCommandsWidget::setCommandList(const QStringList &commands) +{ + d->m_stringModel->setStringList(commands); +} + +AdbCommandsWidgetPrivate::AdbCommandsWidgetPrivate(const AdbCommandsWidget &parent, + QWidget *parentWidget): + m_parent(parent), + m_rootWidget(new QGroupBox(parentWidget)), + m_ui(new Ui::AdbCommandsWidget), + m_stringModel(new QStringListModel) +{ + m_ui->setupUi(m_rootWidget); + m_ui->addButton->setIcon(Utils::Icons::PLUS.icon()); + m_ui->removeButton->setIcon(Utils::Icons::MINUS.icon()); + m_ui->moveUpButton->setIcon(Utils::Icons::ARROW_UP.icon()); + m_ui->moveDownButton->setIcon(Utils::Icons::ARROW_DOWN.icon()); + + auto deleteShortcut = new QShortcut(QKeySequence(QKeySequence::Delete), m_ui->commandsView); + deleteShortcut->setContext(Qt::WidgetShortcut); + QObject::connect(deleteShortcut, &QShortcut::activated, + std::bind(&AdbCommandsWidgetPrivate::onRemove, this)); + + QObject::connect(m_ui->addButton, &QToolButton::clicked, + std::bind(&AdbCommandsWidgetPrivate::onAddButton, this)); + QObject::connect(m_ui->removeButton, &QToolButton::clicked, + std::bind(&AdbCommandsWidgetPrivate::onRemove, this)); + QObject::connect(m_ui->moveUpButton, &QToolButton::clicked, + std::bind(&AdbCommandsWidgetPrivate::onMoveUpButton, this)); + QObject::connect(m_ui->moveDownButton, &QToolButton::clicked, + std::bind(&AdbCommandsWidgetPrivate::onMoveDownButton, this)); + + m_ui->commandsView->setModel(m_stringModel); + QObject::connect(m_stringModel, &QStringListModel::dataChanged, + &m_parent, &AdbCommandsWidget::commandsChanged); + QObject::connect(m_stringModel, &QStringListModel::rowsRemoved, + &m_parent, &AdbCommandsWidget::commandsChanged); + QObject::connect(m_ui->commandsView->selectionModel(), &QItemSelectionModel::currentChanged, + std::bind(&AdbCommandsWidgetPrivate::onCurrentIndexChanged, this, _1, _2)); +} + +AdbCommandsWidgetPrivate::~AdbCommandsWidgetPrivate() +{ + delete m_ui; + delete m_stringModel; +} + +void AdbCommandsWidgetPrivate::addString(const QString &str) +{ + if (!str.isEmpty()) { + m_stringModel->insertRows(m_stringModel->rowCount(), 1); + const QModelIndex lastItemIndex = m_stringModel->index(m_stringModel->rowCount() - 1); + m_stringModel->setData(lastItemIndex, str); + } +} + +void AdbCommandsWidgetPrivate::onAddButton() +{ + addString(defaultCommand); + const QModelIndex index = m_stringModel->index(m_stringModel->rowCount() - 1); + m_ui->commandsView->setCurrentIndex(index); + m_ui->commandsView->edit(index); +} + +void AdbCommandsWidgetPrivate::onMoveUpButton() +{ + QModelIndex index = m_ui->commandsView->currentIndex(); + if (index.row() > 0) { + const QModelIndex newIndex = m_stringModel->index(index.row() - 1, 0); + swapData(m_stringModel, index, newIndex); + m_ui->commandsView->setCurrentIndex(newIndex); + } +} + +void AdbCommandsWidgetPrivate::onMoveDownButton() +{ + QModelIndex index = m_ui->commandsView->currentIndex(); + if (index.row() < m_stringModel->rowCount() - 1) { + const QModelIndex newIndex = m_stringModel->index(index.row() + 1, 0); + swapData(m_stringModel, index, newIndex); + m_ui->commandsView->setCurrentIndex(newIndex); + } +} + +void AdbCommandsWidgetPrivate::onRemove() +{ + const QModelIndex &index = m_ui->commandsView->currentIndex(); + if (index.isValid()) + m_stringModel->removeRow(index.row()); +} + +void AdbCommandsWidgetPrivate::onCurrentIndexChanged(const QModelIndex &newIndex, const QModelIndex &prevIndex) +{ + Q_UNUSED(prevIndex) + m_ui->moveUpButton->setEnabled(newIndex.row() != 0); + m_ui->moveDownButton->setEnabled(newIndex.row() < m_stringModel->rowCount() - 1); + m_ui->removeButton->setEnabled(newIndex.isValid()); +} + +} // Internal +} // Android diff --git a/src/plugins/android/adbcommandswidget.h b/src/plugins/android/adbcommandswidget.h new file mode 100644 index 00000000000..a1404049d04 --- /dev/null +++ b/src/plugins/android/adbcommandswidget.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +#include + +QT_BEGIN_NAMESPACE +class QWidget; +QT_END_NAMESPACE + +namespace Android { +namespace Internal { + +class AdbCommandsWidgetPrivate; + +class AdbCommandsWidget : public QObject +{ + Q_OBJECT +public: + explicit AdbCommandsWidget(QWidget *parent); + ~AdbCommandsWidget(); + + QStringList commandsList() const; + void setCommandList(const QStringList &commands); + + QWidget *widget() const; + + void setTitleText(const QString &title); + +signals: + void commandsChanged(); + +private: + std::unique_ptr d; +}; + +} // Internal +} // Android diff --git a/src/plugins/android/adbcommandswidget.ui b/src/plugins/android/adbcommandswidget.ui new file mode 100644 index 00000000000..3aff5e4fea1 --- /dev/null +++ b/src/plugins/android/adbcommandswidget.ui @@ -0,0 +1,117 @@ + + + AdbCommandsWidget + + + + 0 + 0 + 682 + 391 + + + + Widget + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + Qt::MoveAction + + + QListView::Snap + + + + + + + false + + + + + + Qt::NoArrow + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + false + + + + + + Qt::NoArrow + + + + + + + false + + + + + + + + + + + commandsView + addButton + removeButton + moveUpButton + moveDownButton + + + + diff --git a/src/plugins/android/android.pro b/src/plugins/android/android.pro index 702bb4d7cf7..d4bbcb95d39 100644 --- a/src/plugins/android/android.pro +++ b/src/plugins/android/android.pro @@ -50,7 +50,8 @@ HEADERS += \ androidtoolmanager.h \ androidsdkmanager.h \ androidavdmanager.h \ - androidrunconfigurationwidget.h + androidrunconfigurationwidget.h \ + adbcommandswidget.h SOURCES += \ androidconfigurations.cpp \ @@ -94,7 +95,8 @@ SOURCES += \ androidtoolmanager.cpp \ androidsdkmanager.cpp \ androidavdmanager.cpp \ - androidrunconfigurationwidget.cpp + androidrunconfigurationwidget.cpp \ + adbcommandswidget.cpp FORMS += \ androidsettingswidget.ui \ @@ -103,7 +105,8 @@ FORMS += \ androiddevicedialog.ui \ androiddeployqtwidget.ui \ androidbuildapkwidget.ui \ - androidrunconfigurationwidget.ui + androidrunconfigurationwidget.ui \ + adbcommandswidget.ui RESOURCES = android.qrc diff --git a/src/plugins/android/android.qbs b/src/plugins/android/android.qbs index fcc2e9e52ee..af7106439f3 100644 --- a/src/plugins/android/android.qbs +++ b/src/plugins/android/android.qbs @@ -18,8 +18,11 @@ Project { files: [ "android_global.h", - "addnewavddialog.ui", "android.qrc", + "adbcommandswidget.cpp", + "adbcommandswidget.h", + "adbcommandswidget.ui", + "addnewavddialog.ui", "androidanalyzesupport.cpp", "androidanalyzesupport.h", "androidavdmanager.cpp", diff --git a/src/plugins/android/androidrunconfiguration.cpp b/src/plugins/android/androidrunconfiguration.cpp index 7b69ed2364f..79bfa1007fa 100644 --- a/src/plugins/android/androidrunconfiguration.cpp +++ b/src/plugins/android/androidrunconfiguration.cpp @@ -40,7 +40,10 @@ using namespace ProjectExplorer; namespace Android { using namespace Internal; + const char amStartArgsKey[] = "Android.AmStartArgsKey"; +const char preStartShellCmdsKey[] = "Android.PreStartShellCmdListKey"; +const char postFinishShellCmdsKey[] = "Android.PostFinishShellCmdListKey"; AndroidRunConfiguration::AndroidRunConfiguration(Target *parent, Core::Id id) : RunConfiguration(parent, id) @@ -52,6 +55,16 @@ AndroidRunConfiguration::AndroidRunConfiguration(Target *parent, AndroidRunConfi { } +void AndroidRunConfiguration::setPreStartShellCommands(const QStringList &cmdList) +{ + m_preStartShellCommands = cmdList; +} + +void AndroidRunConfiguration::setPostFinishShellCommands(const QStringList &cmdList) +{ + m_postFinishShellCommands = cmdList; +} + void AndroidRunConfiguration::setAmStartExtraArgs(const QStringList &args) { m_amStartExtraArgs = args; @@ -61,8 +74,14 @@ QWidget *AndroidRunConfiguration::createConfigurationWidget() { auto configWidget = new AndroidRunConfigurationWidget(); configWidget->setAmStartArgs(m_amStartExtraArgs); + configWidget->setPreStartShellCommands(m_preStartShellCommands); + configWidget->setPostFinishShellCommands(m_postFinishShellCommands); connect(configWidget, &AndroidRunConfigurationWidget::amStartArgsChanged, this, &AndroidRunConfiguration::setAmStartExtraArgs); + connect(configWidget, &AndroidRunConfigurationWidget::preStartCmdsChanged, + this, &AndroidRunConfiguration::setPreStartShellCommands); + connect(configWidget, &AndroidRunConfigurationWidget::postFinishCmdsChanged, + this, &AndroidRunConfiguration::setPostFinishShellCommands); return configWidget; } @@ -73,6 +92,8 @@ Utils::OutputFormatter *AndroidRunConfiguration::createOutputFormatter() const bool AndroidRunConfiguration::fromMap(const QVariantMap &map) { + m_preStartShellCommands = map.value(preStartShellCmdsKey).toStringList(); + m_postFinishShellCommands = map.value(postFinishShellCmdsKey).toStringList(); m_amStartExtraArgs = map.value(amStartArgsKey).toStringList(); return RunConfiguration::fromMap(map); } @@ -80,6 +101,8 @@ bool AndroidRunConfiguration::fromMap(const QVariantMap &map) QVariantMap AndroidRunConfiguration::toMap() const { QVariantMap res = RunConfiguration::toMap(); + res[preStartShellCmdsKey] = m_preStartShellCommands; + res[postFinishShellCmdsKey] = m_postFinishShellCommands; res[amStartArgsKey] = m_amStartExtraArgs; return res; } @@ -88,4 +111,15 @@ const QStringList &AndroidRunConfiguration::amStartExtraArgs() const { return m_amStartExtraArgs; } + +const QStringList &AndroidRunConfiguration::preStartShellCommands() const +{ + return m_preStartShellCommands; +} + +const QStringList &AndroidRunConfiguration::postFinishShellCommands() const +{ + return m_postFinishShellCommands; +} + } // namespace Android diff --git a/src/plugins/android/androidrunconfiguration.h b/src/plugins/android/androidrunconfiguration.h index 3ba7d25d7db..45b00d8a845 100644 --- a/src/plugins/android/androidrunconfiguration.h +++ b/src/plugins/android/androidrunconfiguration.h @@ -48,15 +48,21 @@ public: QVariantMap toMap() const override; const QStringList &amStartExtraArgs() const; + const QStringList &preStartShellCommands() const; + const QStringList &postFinishShellCommands() const; protected: AndroidRunConfiguration(ProjectExplorer::Target *parent, AndroidRunConfiguration *source); private: + void setPreStartShellCommands(const QStringList &cmdList); + void setPostFinishShellCommands(const QStringList &cmdList); void setAmStartExtraArgs(const QStringList &args); private: QStringList m_amStartExtraArgs; + QStringList m_preStartShellCommands; + QStringList m_postFinishShellCommands; }; } // namespace Android diff --git a/src/plugins/android/androidrunconfigurationwidget.cpp b/src/plugins/android/androidrunconfigurationwidget.cpp index ba4d038408d..a9560fdaaa1 100644 --- a/src/plugins/android/androidrunconfigurationwidget.cpp +++ b/src/plugins/android/androidrunconfigurationwidget.cpp @@ -23,6 +23,7 @@ ** ****************************************************************************/ #include "androidrunconfigurationwidget.h" +#include "adbcommandswidget.h" #include "ui_androidrunconfigurationwidget.h" #include "utils/utilsicons.h" @@ -39,6 +40,26 @@ AndroidRunConfigurationWidget::AndroidRunConfigurationWidget(QWidget *parent): m_ui->setupUi(detailsWidget); m_ui->m_warningIconLabel->setPixmap(Utils::Icons::WARNING.pixmap()); + m_preStartCmdsWidget = new AdbCommandsWidget(detailsWidget); + connect(m_preStartCmdsWidget, &AdbCommandsWidget::commandsChanged, [this]() { + emit preStartCmdsChanged(m_preStartCmdsWidget->commandsList()); + }); + m_preStartCmdsWidget->setTitleText(tr("Shell commands to run on Android device before" + " application launch.")); + + m_postEndCmdsWidget = new AdbCommandsWidget(detailsWidget); + connect(m_postEndCmdsWidget, &AdbCommandsWidget::commandsChanged, [this]() { + emit postFinishCmdsChanged(m_postEndCmdsWidget->commandsList()); + }); + m_postEndCmdsWidget->setTitleText(tr("Shell commands to run on Android device after application" + " quits.")); + + auto mainLayout = static_cast(detailsWidget->layout()); + mainLayout->addWidget(m_preStartCmdsWidget->widget(), mainLayout->rowCount(), + 0, mainLayout->columnCount() - 1, 0); + mainLayout->addWidget(m_postEndCmdsWidget->widget(), mainLayout->rowCount(), + 0, mainLayout->columnCount() - 1, 0); + setWidget(detailsWidget); setSummaryText(tr("Android run settings")); @@ -54,8 +75,17 @@ AndroidRunConfigurationWidget::~AndroidRunConfigurationWidget() void AndroidRunConfigurationWidget::setAmStartArgs(const QStringList &args) { - if (m_ui->m_amStartArgsEdit && !args.isEmpty()) - m_ui->m_amStartArgsEdit->setText(Utils::QtcProcess::joinArgs(args, Utils::OsTypeLinux)); + m_ui->m_amStartArgsEdit->setText(Utils::QtcProcess::joinArgs(args, Utils::OsTypeLinux)); +} + +void AndroidRunConfigurationWidget::setPreStartShellCommands(const QStringList &cmdList) +{ + m_preStartCmdsWidget->setCommandList(cmdList); +} + +void AndroidRunConfigurationWidget::setPostFinishShellCommands(const QStringList &cmdList) +{ + m_postEndCmdsWidget->setCommandList(cmdList); } } // namespace Internal diff --git a/src/plugins/android/androidrunconfigurationwidget.h b/src/plugins/android/androidrunconfigurationwidget.h index 14d1d923dbb..249e00a672c 100644 --- a/src/plugins/android/androidrunconfigurationwidget.h +++ b/src/plugins/android/androidrunconfigurationwidget.h @@ -29,10 +29,14 @@ #include "projectexplorer/runconfiguration.h" #include "utils/detailswidget.h" +#include + +#include namespace Android { namespace Internal { +class AdbCommandsWidget; namespace Ui { class AndroidRunConfigurationWidget; } @@ -45,12 +49,22 @@ public: ~AndroidRunConfigurationWidget(); void setAmStartArgs(const QStringList &args); + void setPreStartShellCommands(const QStringList &cmdList); + void setPostFinishShellCommands(const QStringList &cmdList); signals: void amStartArgsChanged(QStringList args); + void preStartCmdsChanged(const QStringList &cmdList); + void postFinishCmdsChanged(const QStringList &cmdList); + +private: + void addPreStartCommand(const QString &command); + void addPostFinishCommand(const QString &command); private: std::unique_ptr m_ui; + AdbCommandsWidget *m_preStartCmdsWidget = nullptr; + AdbCommandsWidget *m_postEndCmdsWidget = nullptr; }; } // namespace Internal diff --git a/src/plugins/android/androidrunconfigurationwidget.ui b/src/plugins/android/androidrunconfigurationwidget.ui index cd7ed3df801..5cfee96dc89 100644 --- a/src/plugins/android/androidrunconfigurationwidget.ui +++ b/src/plugins/android/androidrunconfigurationwidget.ui @@ -6,25 +6,31 @@ 0 0 - 625 - 73 + 731 + 119 + + + 0 + 0 + + Form - - - - - + + -1 + + + - Conflicting "am start" options might result in the app startup failure. + Activity manager start options: - + @@ -37,7 +43,7 @@ - + Qt::Horizontal @@ -53,13 +59,32 @@ - - + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 12 + + + + + + - Activity manager start options: + Conflicting "am start" options might result in the app startup failure. + + + diff --git a/src/plugins/android/androidrunnable.h b/src/plugins/android/androidrunnable.h index 89ca03d1361..9839dada07b 100644 --- a/src/plugins/android/androidrunnable.h +++ b/src/plugins/android/androidrunnable.h @@ -37,8 +37,8 @@ struct ANDROID_EXPORT AndroidRunnable QString intentName; QStringList amStartExtraArgs; Utils::Environment environment; - QVector beforeStartADBCommands; - QVector afterFinishADBCommands; + QStringList beforeStartAdbCommands; + QStringList afterFinishAdbCommands; QString deviceSerialNumber; QString displayName() const { return packageName; } @@ -51,8 +51,8 @@ inline bool operator==(const AndroidRunnable &r1, const AndroidRunnable &r2) && r1.intentName == r2.intentName && r1.amStartExtraArgs == r2.amStartExtraArgs && r1.environment == r2.environment - && r1.beforeStartADBCommands == r2.beforeStartADBCommands - && r1.afterFinishADBCommands == r2.afterFinishADBCommands + && r1.beforeStartAdbCommands == r2.beforeStartAdbCommands + && r1.afterFinishAdbCommands == r2.afterFinishAdbCommands && r1.deviceSerialNumber == r2.deviceSerialNumber; } diff --git a/src/plugins/android/androidrunner.cpp b/src/plugins/android/androidrunner.cpp index 4a62e028fe9..9b9c8578abb 100644 --- a/src/plugins/android/androidrunner.cpp +++ b/src/plugins/android/androidrunner.cpp @@ -214,14 +214,13 @@ class AndroidRunnerWorker : public QObject }; public: - AndroidRunnerWorker(RunControl *runControl, const QString &packageName, - const QStringList &selector); + AndroidRunnerWorker(RunControl *runControl, const AndroidRunnable &runnable); ~AndroidRunnerWorker(); - void asyncStart(const AndroidRunnable &runnable); - void asyncStop(const AndroidRunnable &runnable); + void asyncStart(); + void asyncStop(); - void setAdbParameters(const QString &packageName, const QStringList &selector); + void setAndroidRunnable(const AndroidRunnable &runnable); void handleRemoteDebuggerRunning(); Utils::Port localGdbServerPort() const { return m_localGdbServerPort; } @@ -238,7 +237,7 @@ private: void logcatReadStandardError(); void logcatReadStandardOutput(); void adbKill(qint64 pid); - QStringList selector() const { return m_selector; } + QStringList selector() const; void forceStop(); void findPs(); void logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError); @@ -264,22 +263,19 @@ private: QString m_gdbserverPath; QString m_gdbserverSocket; QString m_adb; - QStringList m_selector; QRegExp m_logCatRegExp; DebugHandShakeType m_handShakeMethod = SocketHandShake; bool m_customPort = false; - QString m_packageName; + AndroidRunnable m_androidRunnable; int m_socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT; }; -AndroidRunnerWorker::AndroidRunnerWorker(RunControl *runControl, const QString &packageName, - const QStringList &selector) +AndroidRunnerWorker::AndroidRunnerWorker(RunControl *runControl, const AndroidRunnable &runnable) : m_adbLogcatProcess(nullptr, deleter) , m_psIsAlive(nullptr, deleter) - , m_selector(selector) , m_logCatRegExp(regExpLogcat) - , m_packageName(packageName) + , m_androidRunnable(runnable) { auto runConfig = runControl->runConfiguration(); auto aspect = runConfig->extraAspect(); @@ -307,9 +303,9 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunControl *runControl, const QString & } m_adb = AndroidConfigurations::currentConfig().adbToolPath().toString(); - QString packageDir = "/data/data/" + m_packageName; + QString packageDir = "/data/data/" + m_androidRunnable.packageName; m_pingFile = packageDir + "/debug-ping"; - m_pongFile = "/data/local/tmp/qt/debug-pong-" + m_packageName; + m_pongFile = "/data/local/tmp/qt/debug-pong-" + m_androidRunnable.packageName; m_gdbserverSocket = packageDir + "/debug-socket"; const QtSupport::BaseQtVersion *version = QtSupport::QtKitInformation::qtVersion( runConfig->target()->kit()); @@ -347,20 +343,20 @@ AndroidRunnerWorker::~AndroidRunnerWorker() void AndroidRunnerWorker::forceStop() { - runAdb({"shell", "am", "force-stop", m_packageName}, nullptr, 30); + runAdb({"shell", "am", "force-stop", m_androidRunnable.packageName}, nullptr, 30); // try killing it via kill -9 const QByteArray out = Utils::SynchronousProcess() .runBlocking(m_adb, selector() << QStringLiteral("shell") << pidScript) .allRawOutput(); - qint64 pid = extractPID(out.simplified(), m_packageName); + qint64 pid = extractPID(out.simplified(), m_androidRunnable.packageName); if (pid != -1) { adbKill(pid); } } -void AndroidRunnerWorker::asyncStart(const AndroidRunnable &runnable) +void AndroidRunnerWorker::asyncStart() { forceStop(); @@ -378,12 +374,12 @@ void AndroidRunnerWorker::asyncStart(const AndroidRunnable &runnable) if (m_useCppDebugger) runAdb({"shell", "rm", m_pongFile}); // Remove pong file. - for (const QStringList &entry: runnable.beforeStartADBCommands) - runAdb(entry); + for (const QString &entry: m_androidRunnable.beforeStartAdbCommands) + runAdb(entry.split(' ', QString::SkipEmptyParts)); QStringList args({"shell", "am", "start"}); - args << runnable.amStartExtraArgs; - args << "-n" << runnable.intentName; + args << m_androidRunnable.amStartExtraArgs; + args << "-n" << m_androidRunnable.intentName; if (m_useCppDebugger) { if (!runAdb({"forward", "--remove", "tcp:" + m_localGdbServerPort.toString()})){ @@ -395,7 +391,7 @@ void AndroidRunnerWorker::asyncStart(const AndroidRunnable &runnable) return; } - const QString pingPongSocket(m_packageName + ".ping_pong_socket"); + const QString pingPongSocket(m_androidRunnable.packageName + ".ping_pong_socket"); args << "-e" << "debug_ping" << "true"; if (m_handShakeMethod == SocketHandShake) { args << "-e" << "ping_socket" << pingPongSocket; @@ -499,7 +495,8 @@ void AndroidRunnerWorker::asyncStart(const AndroidRunnable &runnable) break; if (i == 20) { - emit remoteProcessFinished(tr("Unable to start \"%1\".").arg(m_packageName)); + emit remoteProcessFinished(tr("Unable to start \"%1\".") + .arg(m_androidRunnable.packageName)); return; } qDebug() << "WAITING FOR " << tmp.fileName(); @@ -512,7 +509,7 @@ void AndroidRunnerWorker::asyncStart(const AndroidRunnable &runnable) QTC_ASSERT(!m_adbLogcatProcess, /**/); m_adbLogcatProcess = std::move(logcatProcess); m_pidFinder = Utils::onResultReady(Utils::runAsync(&findProcessPID, m_adb, selector(), - m_packageName), + m_androidRunnable.packageName), bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1)); } @@ -543,7 +540,7 @@ bool AndroidRunnerWorker::runAdb(const QStringList &args, QString *exitMessage, { Utils::SynchronousProcess adb; adb.setTimeoutS(timeoutS); - Utils::SynchronousProcessResponse response = adb.run(m_adb, m_selector + args); + Utils::SynchronousProcessResponse response = adb.run(m_adb, selector() + args); if (exitMessage) *exitMessage = response.exitMessage(m_adb, timeoutS); return response.result == Utils::SynchronousProcessResponse::Finished; @@ -567,7 +564,7 @@ void AndroidRunnerWorker::handleRemoteDebuggerRunning() // emit remoteProcessStarted(m_localGdbServerPort, m_qmlPort); } -void AndroidRunnerWorker::asyncStop(const AndroidRunnable &runnable) +void AndroidRunnerWorker::asyncStop() { if (!m_pidFinder.isFinished()) m_pidFinder.cancel(); @@ -575,14 +572,11 @@ void AndroidRunnerWorker::asyncStop(const AndroidRunnable &runnable) if (m_processPID != -1) { forceStop(); } - for (const QStringList &entry: runnable.afterFinishADBCommands) - runAdb(entry); } -void AndroidRunnerWorker::setAdbParameters(const QString &packageName, const QStringList &selector) +void AndroidRunnerWorker::setAndroidRunnable(const AndroidRunnable &runnable) { - m_packageName = packageName; - m_selector = selector; + m_androidRunnable = runnable; } void AndroidRunnerWorker::logcatProcess(const QByteArray &text, QByteArray &buffer, bool onlyError) @@ -635,10 +629,14 @@ void AndroidRunnerWorker::onProcessIdChanged(qint64 pid) m_processPID = pid; if (pid == -1) { emit remoteProcessFinished(QLatin1String("\n\n") + tr("\"%1\" died.") - .arg(m_packageName)); + .arg(m_androidRunnable.packageName)); // App died/killed. Reset log and monitor processes. m_adbLogcatProcess.reset(); m_psIsAlive.reset(); + + // Run adb commands after application quit. + for (const QString &entry: m_androidRunnable.afterFinishAdbCommands) + runAdb(entry.split(' ', QString::SkipEmptyParts)); } else { // In debugging cases this will be funneled to the engine to actually start // and attach gdb. Afterwards this ends up in handleRemoteDebuggerRunning() below. @@ -671,7 +669,12 @@ void AndroidRunnerWorker::logcatReadStandardOutput() void AndroidRunnerWorker::adbKill(qint64 pid) { runAdb({"shell", "kill", "-9", QString::number(pid)}); - runAdb({"shell", "run-as", m_packageName, "kill", "-9", QString::number(pid)}); + runAdb({"shell", "run-as", m_androidRunnable.packageName, "kill", "-9", QString::number(pid)}); +} + +QStringList AndroidRunnerWorker::selector() const +{ + return AndroidDeviceInfo::adbSelector(m_androidRunnable.deviceSerialNumber); } AndroidRunner::AndroidRunner(RunControl *runControl) @@ -694,15 +697,19 @@ AndroidRunner::AndroidRunner(RunControl *runControl) auto androidRunConfig = qobject_cast(runControl->runConfiguration()); m_androidRunnable.amStartExtraArgs = androidRunConfig->amStartExtraArgs(); + for (QString shellCmd: androidRunConfig->preStartShellCommands()) + m_androidRunnable.beforeStartAdbCommands.append(QString("shell %1").arg(shellCmd)); - m_worker.reset(new AndroidRunnerWorker(runControl, m_androidRunnable.packageName, - AndroidDeviceInfo::adbSelector(m_androidRunnable.deviceSerialNumber))); + for (QString shellCmd: androidRunConfig->postFinishShellCommands()) + m_androidRunnable.afterFinishAdbCommands.append(QString("shell %1").arg(shellCmd)); + + m_worker.reset(new AndroidRunnerWorker(runControl, m_androidRunnable)); m_worker->moveToThread(&m_thread); connect(this, &AndroidRunner::asyncStart, m_worker.data(), &AndroidRunnerWorker::asyncStart); connect(this, &AndroidRunner::asyncStop, m_worker.data(), &AndroidRunnerWorker::asyncStop); - connect(this, &AndroidRunner::adbParametersChanged, - m_worker.data(), &AndroidRunnerWorker::setAdbParameters); + connect(this, &AndroidRunner::androidRunnableChanged, + m_worker.data(), &AndroidRunnerWorker::setAndroidRunnable); connect(this, &AndroidRunner::remoteDebuggerRunning, m_worker.data(), &AndroidRunnerWorker::handleRemoteDebuggerRunning); @@ -738,7 +745,7 @@ void AndroidRunner::start() } } - emit asyncStart(m_androidRunnable); + emit asyncStart(); } void AndroidRunner::stop() @@ -750,7 +757,7 @@ void AndroidRunner::stop() return; } - emit asyncStop(m_androidRunnable); + emit asyncStop(); } void AndroidRunner::qmlServerPortReady(Port port) @@ -797,8 +804,7 @@ void AndroidRunner::setRunnable(const AndroidRunnable &runnable) { if (runnable != m_androidRunnable) { m_androidRunnable = runnable; - emit adbParametersChanged(runnable.packageName, - AndroidDeviceInfo::adbSelector(runnable.deviceSerialNumber)); + emit androidRunnableChanged(m_androidRunnable); } } @@ -816,8 +822,7 @@ void AndroidRunner::launchAVD() AndroidConfigurations::None); AndroidManager::setDeviceSerialNumber(m_target, info.serialNumber); m_androidRunnable.deviceSerialNumber = info.serialNumber; - emit adbParametersChanged(m_androidRunnable.packageName, - AndroidDeviceInfo::adbSelector(info.serialNumber)); + emit androidRunnableChanged(m_androidRunnable); if (info.isValid()) { AndroidAvdManager avdManager; if (avdManager.findAvd(info.avdname).isEmpty()) { @@ -840,7 +845,7 @@ void AndroidRunner::checkAVD() if (avdManager.isAvdBooted(serialNumber)) { m_checkAVDTimer.stop(); AndroidManager::setDeviceSerialNumber(m_target, serialNumber); - emit asyncStart(m_androidRunnable); + emit asyncStart(); } else if (!config.isConnected(serialNumber)) { // device was disconnected m_checkAVDTimer.stop(); diff --git a/src/plugins/android/androidrunner.h b/src/plugins/android/androidrunner.h index cf24d8423bf..2efd52299cf 100644 --- a/src/plugins/android/androidrunner.h +++ b/src/plugins/android/androidrunner.h @@ -64,12 +64,11 @@ public: void stop() override; signals: - void asyncStart(const AndroidRunnable &runnable); - void asyncStop(const AndroidRunnable &runnable); + void asyncStart(); + void asyncStop(); void remoteDebuggerRunning(); void qmlServerReady(const QUrl &serverUrl); - - void adbParametersChanged(const QString &packageName, const QStringList &selector); + void androidRunnableChanged(const AndroidRunnable &runnable); void avdDetected(); private: diff --git a/src/plugins/autotest/qtest/qttestconfiguration.cpp b/src/plugins/autotest/qtest/qttestconfiguration.cpp index 57ab6fc86a6..2329bfe6bc5 100644 --- a/src/plugins/autotest/qtest/qttestconfiguration.cpp +++ b/src/plugins/autotest/qtest/qttestconfiguration.cpp @@ -35,7 +35,17 @@ namespace Internal { TestOutputReader *QtTestConfiguration::outputReader(const QFutureInterface &fi, QProcess *app) const { - return new QtTestOutputReader(fi, app, buildDirectory()); + static const Core::Id id + = Core::Id(Constants::FRAMEWORK_PREFIX).withSuffix(QtTest::Constants::FRAMEWORK_NAME); + TestFrameworkManager *manager = TestFrameworkManager::instance(); + auto qtSettings = qSharedPointerCast(manager->settingsForTestFramework(id)); + if (qtSettings.isNull()) + return nullptr; + + if (qtSettings->useXMLOutput) + return new QtTestOutputReader(fi, app, buildDirectory(), QtTestOutputReader::XML); + else + return new QtTestOutputReader(fi, app, buildDirectory(), QtTestOutputReader::PlainText); } QStringList QtTestConfiguration::argumentsForTestRunner() const @@ -43,14 +53,15 @@ QStringList QtTestConfiguration::argumentsForTestRunner() const static const Core::Id id = Core::Id(Constants::FRAMEWORK_PREFIX).withSuffix(QtTest::Constants::FRAMEWORK_NAME); - QStringList arguments("-xml"); - if (testCases().count()) - arguments << testCases(); - + QStringList arguments; TestFrameworkManager *manager = TestFrameworkManager::instance(); auto qtSettings = qSharedPointerCast(manager->settingsForTestFramework(id)); if (qtSettings.isNull()) return arguments; + if (qtSettings->useXMLOutput) + arguments << "-xml"; + if (testCases().count()) + arguments << testCases(); const QString &metricsOption = QtTestSettings::metricsTypeToOption(qtSettings->metrics); if (!metricsOption.isEmpty()) diff --git a/src/plugins/autotest/qtest/qttestoutputreader.cpp b/src/plugins/autotest/qtest/qttestoutputreader.cpp index 015bcc179c2..aa86681a486 100644 --- a/src/plugins/autotest/qtest/qttestoutputreader.cpp +++ b/src/plugins/autotest/qtest/qttestoutputreader.cpp @@ -31,6 +31,7 @@ #include #include #include +#include namespace Autotest { namespace Internal { @@ -128,12 +129,26 @@ static QString constructSourceFilePath(const QString &path, const QString &fileP } QtTestOutputReader::QtTestOutputReader(const QFutureInterface &futureInterface, - QProcess *testApplication, const QString &buildDirectory) + QProcess *testApplication, const QString &buildDirectory, + OutputMode mode) : TestOutputReader(futureInterface, testApplication, buildDirectory) + , m_mode(mode) { } void QtTestOutputReader::processOutput(const QByteArray &outputLine) +{ + switch (m_mode) { + case PlainText: + processPlainTextOutput(outputLine); + break; + case XML: + processXMLOutput(outputLine); + break; + } +} + +void QtTestOutputReader::processXMLOutput(const QByteArray &outputLine) { static QStringList validEndTags = {QStringLiteral("Incident"), QStringLiteral("Message"), @@ -162,24 +177,14 @@ void QtTestOutputReader::processOutput(const QByteArray &outputLine) if (currentTag == QStringLiteral("TestCase")) { m_className = m_xmlReader.attributes().value(QStringLiteral("name")).toString(); QTC_ASSERT(!m_className.isEmpty(), continue); - TestResultPtr testResult = TestResultPtr(createDefaultResult()); - testResult->setResult(Result::MessageTestCaseStart); - testResult->setDescription(tr("Executing test case %1").arg(m_className)); - m_futureInterface.reportResult(testResult); + sendStartMessage(false); } else if (currentTag == QStringLiteral("TestFunction")) { m_testCase = m_xmlReader.attributes().value(QStringLiteral("name")).toString(); QTC_ASSERT(!m_testCase.isEmpty(), continue); if (m_testCase == m_formerTestCase) // don't report "Executing..." more than once continue; - TestResultPtr testResult = TestResultPtr(createDefaultResult()); - testResult->setResult(Result::MessageTestCaseStart); - testResult->setDescription(tr("Executing test function %1").arg(m_testCase)); - m_futureInterface.reportResult(testResult); - testResult = TestResultPtr(new QtTestResult); - testResult->setResult(Result::MessageCurrentTest); - testResult->setDescription(tr("Entering test function %1::%2").arg(m_className, - m_testCase)); - m_futureInterface.reportResult(testResult); + sendStartMessage(true); + sendMessageCurrentTest(); } else if (currentTag == QStringLiteral("Duration")) { m_duration = m_xmlReader.attributes().value(QStringLiteral("msecs")).toString(); QTC_ASSERT(!m_duration.isEmpty(), continue); @@ -255,23 +260,13 @@ void QtTestOutputReader::processOutput(const QByteArray &outputLine) m_cdataMode = None; const QStringRef currentTag = m_xmlReader.name(); if (currentTag == QStringLiteral("TestFunction")) { - QtTestResult *testResult = createDefaultResult(); - testResult->setResult(Result::MessageTestCaseEnd); - testResult->setDescription( - m_duration.isEmpty() ? tr("Test function finished.") - : tr("Execution took %1 ms.").arg(m_duration)); - m_futureInterface.reportResult(TestResultPtr(testResult)); + sendFinishMessage(true); m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1); m_dataTag.clear(); m_formerTestCase = m_testCase; m_testCase.clear(); } else if (currentTag == QStringLiteral("TestCase")) { - QtTestResult *testResult = createDefaultResult(); - testResult->setResult(Result::MessageTestCaseEnd); - testResult->setDescription( - m_duration.isEmpty() ? tr("Test finished.") - : tr("Test execution took %1 ms.").arg(m_duration)); - m_futureInterface.reportResult(TestResultPtr(testResult)); + sendFinishMessage(false); } else if (validEndTags.contains(currentTag.toString())) { QtTestResult *testResult = createDefaultResult(); testResult->setResult(m_result); @@ -290,6 +285,138 @@ void QtTestOutputReader::processOutput(const QByteArray &outputLine) } } +static QStringList extractFunctionInformation(const QString &testClassName, + const QString &lineWithoutResultType, + Result::Type resultType) +{ + static QRegularExpression classInformation("^(.+?)\\((.*?)\\)(.*)$"); + QStringList result; + const QRegularExpressionMatch match = classInformation.match(lineWithoutResultType); + if (match.hasMatch()) { + QString fullQualifiedFunc = match.captured(1); + QTC_ASSERT(fullQualifiedFunc.startsWith(testClassName + "::"), return result); + fullQualifiedFunc = fullQualifiedFunc.mid(testClassName.length() + 2); + result.append(fullQualifiedFunc); + if (resultType == Result::Benchmark) { // tag is displayed differently + QString possiblyTag = match.captured(3); + if (!possiblyTag.isEmpty()) + possiblyTag = possiblyTag.mid(2, possiblyTag.length() - 4); + result.append(possiblyTag); + result.append(QString()); + } else { + result.append(match.captured(2)); + result.append(match.captured(3)); + } + } + return result; +} + +void QtTestOutputReader::processPlainTextOutput(const QByteArray &outputLine) +{ + static QRegExp start("^[*]{9} Start testing of (.*) [*]{9}$"); + static QRegExp config("^Config: Using QtTest library (.*), (Qt (\\d+(\\.\\d+){2}) \\(.*\\))$"); + static QRegExp summary("^Totals: \\d+ passed, \\d+ failed, \\d+ skipped(, \\d+ blacklisted)?$"); + static QRegExp finish("^[*]{9} Finished testing of (.*) [*]{9}$"); + + static QRegExp result("^(PASS |FAIL! |XFAIL |XPASS |SKIP |BPASS |BFAIL |RESULT " + "|INFO |QWARN |WARNING|QDEBUG ): (.*)$"); + + static QRegExp benchDetails("^\\s+([\\d,.]+ .* per iteration \\(total: [\\d,.]+, iterations: \\d+\\))$"); + static QRegExp locationUnix("^ Loc: \\[(.*)\\]$"); + static QRegExp locationWin("^(.*\\(\\d+\\)) : failure location$"); + + if (m_futureInterface.isCanceled()) + return; + + const QString &line = QString::fromLatin1(outputLine); + + if (result.exactMatch(line)) { + processResultOutput(result.cap(1).toLower().trimmed(), result.cap(2)); + } else if (locationUnix.exactMatch(line)) { + processLocationOutput(locationUnix.cap(1)); + } else if (locationWin.exactMatch(line)) { + processLocationOutput(locationWin.cap(1)); + } else if (benchDetails.exactMatch(line)) { + m_description = benchDetails.cap(1); + } else if (config.exactMatch(line)) { + handleAndSendConfigMessage(config); + } else if (start.exactMatch(line)) { + m_className = start.cap(1); + QTC_CHECK(!m_className.isEmpty()); + sendStartMessage(false); + } else if (summary.exactMatch(line) || finish.exactMatch(line)) { + processSummaryFinishOutput(); + } else { // we have some plain output, but we cannot say where for sure it belongs to.. + if (!m_description.isEmpty()) + m_description.append('\n'); + m_description.append(line); + } +} + +void QtTestOutputReader::processResultOutput(const QString &result, const QString &message) +{ + if (!m_testCase.isEmpty()) { // report the former result if there is any + sendCompleteInformation(); + m_dataTag.clear(); + m_description.clear(); + m_file.clear(); + m_lineNumber = 0; + } + m_result = TestResult::resultFromString(result); + const QStringList funcWithTag = extractFunctionInformation(m_className, message, m_result); + QTC_ASSERT(funcWithTag.size() == 3, return); + m_testCase = funcWithTag.at(0); + if (m_testCase != m_formerTestCase) { // new test function executed + if (!m_formerTestCase.isEmpty()) { + using namespace std; + swap(m_testCase, m_formerTestCase); // we want formerTestCase to be reported + sendFinishMessage(true); + swap(m_testCase, m_formerTestCase); + } + sendStartMessage(true); + sendMessageCurrentTest(); + } + m_dataTag = funcWithTag.at(1); + const QString description = funcWithTag.at(2); + if (!description.isEmpty()) { + if (!m_description.isEmpty()) + m_description.append('\n'); + m_description.append(description.mid(1)); // cut the first whitespace + } + m_formerTestCase = m_testCase; +} + +void QtTestOutputReader::processLocationOutput(const QString &fileWithLine) +{ + QTC_ASSERT(fileWithLine.endsWith(')'), return); + int openBrace = fileWithLine.lastIndexOf('('); + QTC_ASSERT(openBrace != -1, return); + m_file = constructSourceFilePath(m_buildDir, fileWithLine.left(openBrace)); + QString numberStr = fileWithLine.mid(openBrace + 1); + numberStr.chop(1); + m_lineNumber = numberStr.toInt(); +} + +void QtTestOutputReader::processSummaryFinishOutput() +{ + if (m_className.isEmpty()) // we have reported already + return; + // we still have something to report + sendCompleteInformation(); + m_dataTag.clear(); + // report finished function + sendFinishMessage(true); + m_testCase.clear(); + m_formerTestCase.clear(); + // create and report the finish message for this test class + sendFinishMessage(false); + m_className.clear(); + m_description.clear(); + m_result = Result::Invalid; + m_file.clear(); + m_lineNumber = 0; +} + QtTestResult *QtTestOutputReader::createDefaultResult() const { QtTestResult *result = new QtTestResult(m_className); @@ -298,5 +425,63 @@ QtTestResult *QtTestOutputReader::createDefaultResult() const return result; } +void QtTestOutputReader::sendCompleteInformation() +{ + TestResultPtr testResult = TestResultPtr(createDefaultResult()); + testResult->setResult(m_result); + testResult->setFileName(m_file); + testResult->setLine(m_lineNumber); + testResult->setDescription(m_description); + m_futureInterface.reportResult(testResult); +} + +void QtTestOutputReader::sendMessageCurrentTest() +{ + TestResultPtr testResult = TestResultPtr(new QtTestResult); + testResult->setResult(Result::MessageCurrentTest); + testResult->setDescription(tr("Entering test function %1::%2").arg(m_className, m_testCase)); + m_futureInterface.reportResult(testResult); +} + +void QtTestOutputReader::sendStartMessage(bool isFunction) +{ + TestResultPtr testResult = TestResultPtr(createDefaultResult()); + testResult->setResult(Result::MessageTestCaseStart); + testResult->setDescription(isFunction ? tr("Executing test function %1").arg(m_testCase) + : tr("Executing test case %1").arg(m_className)); + m_futureInterface.reportResult(testResult); +} + +void QtTestOutputReader::sendFinishMessage(bool isFunction) +{ + TestResultPtr testResult = TestResultPtr(createDefaultResult()); + testResult->setResult(Result::MessageTestCaseEnd); + if (m_duration.isEmpty()) { + testResult->setDescription(isFunction ? tr("Execution took %1 ms.").arg(m_duration) + : tr("Test execution took %1 ms.").arg(m_duration)); + } else { + testResult->setDescription(isFunction ? tr("Test function finished.") + : tr("Test finished.")); + } + m_futureInterface.reportResult(testResult); +} + +// TODO factor out tr() strings to avoid duplication (see XML processing of Characters) +void QtTestOutputReader::handleAndSendConfigMessage(const QRegExp &config) +{ + QtTestResult *testResult = createDefaultResult(); + testResult->setResult(Result::MessageInternal); + testResult->setDescription(tr("Qt version: %1").arg(config.cap(3))); + m_futureInterface.reportResult(TestResultPtr(testResult)); + testResult = createDefaultResult(); + testResult->setResult(Result::MessageInternal); + testResult->setDescription(tr("Qt build: %1").arg(config.cap(2))); + m_futureInterface.reportResult(TestResultPtr(testResult)); + testResult = createDefaultResult(); + testResult->setResult(Result::MessageInternal); + testResult->setDescription(tr("QTest version: %1").arg(config.cap(1))); + m_futureInterface.reportResult(TestResultPtr(testResult)); +} + } // namespace Internal } // namespace Autotest diff --git a/src/plugins/autotest/qtest/qttestoutputreader.h b/src/plugins/autotest/qtest/qttestoutputreader.h index 027300c6e9c..28305160642 100644 --- a/src/plugins/autotest/qtest/qttestoutputreader.h +++ b/src/plugins/autotest/qtest/qttestoutputreader.h @@ -40,14 +40,32 @@ class QtTestOutputReader : public TestOutputReader Q_DECLARE_TR_FUNCTIONS(Autotest::Internal::QtTestOutputReader) public: + enum OutputMode + { + XML, + PlainText + }; + QtTestOutputReader(const QFutureInterface &futureInterface, - QProcess *testApplication, const QString &buildDirectory); + QProcess *testApplication, const QString &buildDirectory, + OutputMode mode); protected: void processOutput(const QByteArray &outputLine) override; private: + void processXMLOutput(const QByteArray &outputLine); + void processPlainTextOutput(const QByteArray &outputLine); + void processResultOutput(const QString &result, const QString &message); + void processLocationOutput(const QString &fileWithLine); + void processSummaryFinishOutput(); + // helper functions QtTestResult *createDefaultResult() const; + void sendCompleteInformation(); + void sendMessageCurrentTest(); + void sendStartMessage(bool isFunction); + void sendFinishMessage(bool isFunction); + void handleAndSendConfigMessage(const QRegExp &config); enum CDATAMode { @@ -70,6 +88,8 @@ private: int m_lineNumber = 0; QString m_duration; QXmlStreamReader m_xmlReader; + OutputMode m_mode = XML; + }; } // namespace Internal diff --git a/src/plugins/autotest/qtest/qttestsettings.cpp b/src/plugins/autotest/qtest/qttestsettings.cpp index 103d416989a..1b672cd29dd 100644 --- a/src/plugins/autotest/qtest/qttestsettings.cpp +++ b/src/plugins/autotest/qtest/qttestsettings.cpp @@ -30,6 +30,7 @@ namespace Internal { static const char metricsKey[] = "Metrics"; static const char noCrashhandlerKey[] = "NoCrashhandlerOnDebug"; +static const char useXMLOutputKey[] = "UseXMLOutput"; static MetricsType intToMetrics(int value) { @@ -58,12 +59,14 @@ void QtTestSettings::fromFrameworkSettings(const QSettings *s) { metrics = intToMetrics(s->value(metricsKey, Walltime).toInt()); noCrashHandler = s->value(noCrashhandlerKey, true).toBool(); + useXMLOutput = s->value(useXMLOutputKey, true).toBool(); } void QtTestSettings::toFrameworkSettings(QSettings *s) const { s->setValue(metricsKey, metrics); s->setValue(noCrashhandlerKey, noCrashHandler); + s->setValue(useXMLOutputKey, useXMLOutput); } QString QtTestSettings::metricsTypeToOption(const MetricsType type) diff --git a/src/plugins/autotest/qtest/qttestsettings.h b/src/plugins/autotest/qtest/qttestsettings.h index c80a93165e3..9418059243b 100644 --- a/src/plugins/autotest/qtest/qttestsettings.h +++ b/src/plugins/autotest/qtest/qttestsettings.h @@ -48,6 +48,7 @@ public: MetricsType metrics = Walltime; bool noCrashHandler = true; + bool useXMLOutput = true; protected: void fromFrameworkSettings(const QSettings *s) override; diff --git a/src/plugins/autotest/qtest/qttestsettingspage.cpp b/src/plugins/autotest/qtest/qttestsettingspage.cpp index 54e081a02d3..8258440ec4e 100644 --- a/src/plugins/autotest/qtest/qttestsettingspage.cpp +++ b/src/plugins/autotest/qtest/qttestsettingspage.cpp @@ -46,6 +46,7 @@ QtTestSettingsWidget::QtTestSettingsWidget(QWidget *parent) void QtTestSettingsWidget::setSettings(const QtTestSettings &settings) { m_ui.disableCrashhandlerCB->setChecked(settings.noCrashHandler); + m_ui.useXMLOutputCB->setChecked(settings.useXMLOutput); switch (settings.metrics) { case MetricsType::Walltime: m_ui.walltimeRB->setChecked(true); @@ -72,6 +73,7 @@ QtTestSettings QtTestSettingsWidget::settings() const QtTestSettings result; result.noCrashHandler = m_ui.disableCrashhandlerCB->isChecked(); + result.useXMLOutput = m_ui.useXMLOutputCB->isChecked(); if (m_ui.walltimeRB->isChecked()) result.metrics = MetricsType::Walltime; else if (m_ui.tickcounterRB->isChecked()) diff --git a/src/plugins/autotest/qtest/qttestsettingspage.ui b/src/plugins/autotest/qtest/qttestsettingspage.ui index c45233e4154..38837afee87 100644 --- a/src/plugins/autotest/qtest/qttestsettingspage.ui +++ b/src/plugins/autotest/qtest/qttestsettingspage.ui @@ -31,6 +31,21 @@ + + + + XML output recommended as it avoids parsing issues, while plain text is more human readable. + +Warning: Plain text output is missing some information (e.g. duration) + + + Use XML output + + + true + + + diff --git a/src/plugins/autotest/quick/quicktestconfiguration.cpp b/src/plugins/autotest/quick/quicktestconfiguration.cpp index b92c1252c02..4d3241f0f27 100644 --- a/src/plugins/autotest/quick/quicktestconfiguration.cpp +++ b/src/plugins/autotest/quick/quicktestconfiguration.cpp @@ -35,7 +35,16 @@ namespace Internal { TestOutputReader *QuickTestConfiguration::outputReader(const QFutureInterface &fi, QProcess *app) const { - return new QtTestOutputReader(fi, app, buildDirectory()); + static const Core::Id id + = Core::Id(Constants::FRAMEWORK_PREFIX).withSuffix(QtTest::Constants::FRAMEWORK_NAME); + TestFrameworkManager *manager = TestFrameworkManager::instance(); + auto qtSettings = qSharedPointerCast(manager->settingsForTestFramework(id)); + if (qtSettings.isNull()) + return nullptr; + if (qtSettings->useXMLOutput) + return new QtTestOutputReader(fi, app, buildDirectory(), QtTestOutputReader::XML); + else + return new QtTestOutputReader(fi, app, buildDirectory(), QtTestOutputReader::PlainText); } QStringList QuickTestConfiguration::argumentsForTestRunner() const @@ -43,14 +52,15 @@ QStringList QuickTestConfiguration::argumentsForTestRunner() const static const Core::Id id = Core::Id(Constants::FRAMEWORK_PREFIX).withSuffix(QtTest::Constants::FRAMEWORK_NAME); - QStringList arguments("-xml"); - if (testCases().count()) - arguments << testCases(); - + QStringList arguments; TestFrameworkManager *manager = TestFrameworkManager::instance(); auto qtSettings = qSharedPointerCast(manager->settingsForTestFramework(id)); if (qtSettings.isNull()) return arguments; + if (qtSettings->useXMLOutput) + arguments << "-xml"; + if (testCases().count()) + arguments << testCases(); const QString &metricsOption = QtTestSettings::metricsTypeToOption(qtSettings->metrics); if (!metricsOption.isEmpty()) diff --git a/src/plugins/autotest/testresult.cpp b/src/plugins/autotest/testresult.cpp index 794f7ad18e0..bfef7164034 100644 --- a/src/plugins/autotest/testresult.cpp +++ b/src/plugins/autotest/testresult.cpp @@ -56,7 +56,7 @@ Result::Type TestResult::resultFromString(const QString &resultString) { if (resultString == "pass") return Result::Pass; - if (resultString == "fail") + if (resultString == "fail" || resultString == "fail!") return Result::Fail; if (resultString == "xfail") return Result::ExpectedFail; @@ -64,11 +64,13 @@ Result::Type TestResult::resultFromString(const QString &resultString) return Result::UnexpectedPass; if (resultString == "skip") return Result::Skip; + if (resultString == "result") + return Result::Benchmark; if (resultString == "qdebug") return Result::MessageDebug; - if (resultString == "qinfo") + if (resultString == "qinfo" || resultString == "info") return Result::MessageInfo; - if (resultString == "warn" || resultString == "qwarn") + if (resultString == "warn" || resultString == "qwarn" || resultString == "warning") return Result::MessageWarn; if (resultString == "qfatal") return Result::MessageFatal; diff --git a/src/plugins/autotest/testresultmodel.cpp b/src/plugins/autotest/testresultmodel.cpp index c962b8a19f6..3bfdba93573 100644 --- a/src/plugins/autotest/testresultmodel.cpp +++ b/src/plugins/autotest/testresultmodel.cpp @@ -313,7 +313,7 @@ TestResultItem *TestResultModel::findParentItemFor(const TestResultItem *item, TestResultItem *currentItem = static_cast(it); return currentItem->testResult()->isDirectParentOf(result, &needsIntermediate); }; - TestResultItem *parent = static_cast(root->findAnyChild(predicate)); + TestResultItem *parent = static_cast(root->reverseFindAnyChild(predicate)); if (parent) { if (needsIntermediate) { // check if the intermediate is present already diff --git a/src/plugins/autotest/testrunner.cpp b/src/plugins/autotest/testrunner.cpp index 846a3f81220..ee188ff647c 100644 --- a/src/plugins/autotest/testrunner.cpp +++ b/src/plugins/autotest/testrunner.cpp @@ -378,7 +378,8 @@ void TestRunner::debugTests() outputreader, &QObject::deleteLater); } - connect(this, &TestRunner::requestStopTestRun, runControl, &ProjectExplorer::RunControl::stop); + connect(this, &TestRunner::requestStopTestRun, runControl, + &ProjectExplorer::RunControl::initiateStop); connect(runControl, &ProjectExplorer::RunControl::finished, this, &TestRunner::onFinished); ProjectExplorer::ProjectExplorerPlugin::startRunControl(runControl); } diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp index 69b89109aff..9862a65a0f1 100644 --- a/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp +++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp @@ -79,7 +79,7 @@ ClangStaticAnalyzerToolRunner::ClangStaticAnalyzerToolRunner(RunControl *runCont RunConfiguration *runConfiguration = runControl->runConfiguration(); auto tool = ClangStaticAnalyzerTool::instance(); - connect(tool->stopAction(), &QAction::triggered, runControl, &RunControl::stop); + connect(tool->stopAction(), &QAction::triggered, runControl, &RunControl::initiateStop); ProjectInfo projectInfoBeforeBuild = tool->projectInfoBeforeBuild(); QTC_ASSERT(projectInfoBeforeBuild.isValid(), return); diff --git a/src/plugins/coreplugin/editormanager/editorwindow.cpp b/src/plugins/coreplugin/editormanager/editorwindow.cpp index 38aca997058..69673172038 100644 --- a/src/plugins/coreplugin/editormanager/editorwindow.cpp +++ b/src/plugins/coreplugin/editormanager/editorwindow.cpp @@ -28,9 +28,14 @@ #include "editorarea.h" #include "editormanager_p.h" +#include #include #include +#include +#include +#include +#include #include namespace Core { @@ -46,6 +51,14 @@ EditorWindow::EditorWindow(QWidget *parent) : setLayout(layout); layout->addWidget(m_area); setFocusProxy(m_area); + auto statusBar = new QStatusBar; + layout->addWidget(statusBar); + auto splitter = new NonResizingSplitter(statusBar); + splitter->setChildrenCollapsible(false); + statusBar->addPermanentWidget(splitter, 10); + auto locatorWidget = createStaticLocatorWidget(Locator::instance()); + splitter->addWidget(locatorWidget); + splitter->addWidget(new QWidget); setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_QuitOnClose, false); // don't prevent Qt Creator from closing resize(QSize(800, 600)); @@ -61,6 +74,11 @@ EditorWindow::EditorWindow(QWidget *parent) : deleteLater(); }); updateWindowTitle(); + + // register locator widget for this window + auto agg = new Aggregation::Aggregate; + agg->add(this); + agg->add(locatorWidget); } EditorWindow::~EditorWindow() diff --git a/src/plugins/coreplugin/locator/locator.cpp b/src/plugins/coreplugin/locator/locator.cpp index 3fd604bd7ca..5f0bfaf6a47 100644 --- a/src/plugins/coreplugin/locator/locator.cpp +++ b/src/plugins/coreplugin/locator/locator.cpp @@ -86,6 +86,11 @@ Locator::~Locator() qDeleteAll(m_customFilters); } +Locator *Locator::instance() +{ + return m_instance; +} + void Locator::initialize(CorePlugin *corePlugin, const QStringList &, QString *) { m_corePlugin = corePlugin; @@ -103,15 +108,14 @@ void Locator::initialize(CorePlugin *corePlugin, const QStringList &, QString *) ActionContainer *mtools = ActionManager::actionContainer(Constants::M_TOOLS); mtools->addAction(cmd); - auto locatorWidget = new LocatorWidget(this); - new LocatorPopup(locatorWidget, locatorWidget); // child of locatorWidget + m_locatorWidget = createStaticLocatorWidget(this); StatusBarWidget *view = new StatusBarWidget; - view->setWidget(locatorWidget); + view->setWidget(m_locatorWidget); view->setContext(Context("LocatorWidget")); view->setPosition(StatusBarWidget::First); m_corePlugin->addAutoReleasedObject(view); - new LocatorManager(locatorWidget); + new LocatorManager(this); m_openDocumentsFilter = new OpenDocumentsFilter; m_corePlugin->addObject(m_openDocumentsFilter); @@ -135,6 +139,11 @@ void Locator::initialize(CorePlugin *corePlugin, const QStringList &, QString *) void Locator::extensionsInitialized() { + // register locator widget for main window + auto agg = new Aggregation::Aggregate; + agg->add(ICore::mainWindow()); + agg->add(m_locatorWidget); + m_filters = ExtensionSystem::PluginManager::getObjects(); Utils::sort(m_filters, [](const ILocatorFilter *first, const ILocatorFilter *second) -> bool { if (first->priority() != second->priority()) @@ -341,6 +350,11 @@ void Locator::setRefreshInterval(int interval) m_refreshTimer.start(); } +LocatorWidget *Locator::mainLocatorWidget() +{ + return m_instance->m_locatorWidget; +} + void Locator::refresh(QList filters) { if (filters.isEmpty()) diff --git a/src/plugins/coreplugin/locator/locator.h b/src/plugins/coreplugin/locator/locator.h index c2431b99a04..af5bbca7240 100644 --- a/src/plugins/coreplugin/locator/locator.h +++ b/src/plugins/coreplugin/locator/locator.h @@ -43,6 +43,7 @@ class CorePlugin; class OpenDocumentsFilter; class FileSystemFilter; class LocatorSettingsPage; +class LocatorWidget; class ExternalToolsFilter; class Locator : public QObject @@ -53,6 +54,8 @@ public: Locator(); ~Locator(); + static Locator *instance(); + void initialize(CorePlugin *corePlugin, const QStringList &arguments, QString *errorMessage); void extensionsInitialized(); bool delayedInitialize(); @@ -63,6 +66,7 @@ public: void setCustomFilters(QList f); int refreshInterval(); void setRefreshInterval(int interval); + static LocatorWidget *mainLocatorWidget(); signals: void filtersChanged(); @@ -89,6 +93,7 @@ private: ExecuteFilter *m_executeFilter; CorePlugin *m_corePlugin = nullptr; ExternalToolsFilter *m_externalToolsFilter; + LocatorWidget *m_locatorWidget; }; } // namespace Internal diff --git a/src/plugins/coreplugin/locator/locatormanager.cpp b/src/plugins/coreplugin/locator/locatormanager.cpp index 5e65c4f58a3..54b37f337bb 100644 --- a/src/plugins/coreplugin/locator/locatormanager.cpp +++ b/src/plugins/coreplugin/locator/locatormanager.cpp @@ -29,29 +29,45 @@ #include "locator.h" #include "locatorwidget.h" +#include +#include #include #include +using namespace Core::Internal; + namespace Core { -static Internal::LocatorWidget *m_locatorWidget = 0; - -LocatorManager::LocatorManager(Internal::LocatorWidget *locatorWidget) - : QObject(locatorWidget) +LocatorManager::LocatorManager(QObject *parent) + : QObject(parent) { - m_locatorWidget = locatorWidget; +} + +static LocatorWidget *locatorWidget() +{ + static QPointer popup; + QWidget *window = ICore::dialogParent()->window(); + if (auto *widget = Aggregation::query(window)) { + if (popup) + popup->close(); + return widget; + } + if (!popup) { + popup = createLocatorPopup(Locator::instance(), window); + popup->show(); + } + return popup->inputWidget(); } void LocatorManager::showFilter(ILocatorFilter *filter) { QTC_ASSERT(filter, return); - QTC_ASSERT(m_locatorWidget, return); QString searchText = tr(""); - const QString currentText = m_locatorWidget->currentText().trimmed(); + const QString currentText = locatorWidget()->currentText().trimmed(); // add shortcut string at front or replace existing shortcut string if (!currentText.isEmpty()) { searchText = currentText; - foreach (ILocatorFilter *otherfilter, Internal::Locator::filters()) { + foreach (ILocatorFilter *otherfilter, Locator::filters()) { if (currentText.startsWith(otherfilter->shortcutString() + QLatin1Char(' '))) { searchText = currentText.mid(otherfilter->shortcutString().length() + 1); break; @@ -66,8 +82,7 @@ void LocatorManager::showFilter(ILocatorFilter *filter) void LocatorManager::show(const QString &text, int selectionStart, int selectionLength) { - QTC_ASSERT(m_locatorWidget, return); - m_locatorWidget->showText(text, selectionStart, selectionLength); + locatorWidget()->showText(text, selectionStart, selectionLength); } -} // namespace Internal +} // namespace Core diff --git a/src/plugins/coreplugin/locator/locatormanager.h b/src/plugins/coreplugin/locator/locatormanager.h index 0f020e4c2f7..65079ee7862 100644 --- a/src/plugins/coreplugin/locator/locatormanager.h +++ b/src/plugins/coreplugin/locator/locatormanager.h @@ -40,7 +40,7 @@ class CORE_EXPORT LocatorManager : public QObject Q_OBJECT public: - LocatorManager(Internal::LocatorWidget *locatorWidget); + LocatorManager(QObject *parent = 0); static void showFilter(ILocatorFilter *filter); static void show(const QString &text, int selectionStart = -1, int selectionLength = 0); diff --git a/src/plugins/coreplugin/locator/locatorwidget.cpp b/src/plugins/coreplugin/locator/locatorwidget.cpp index 2b513367c54..61282cfb85b 100644 --- a/src/plugins/coreplugin/locator/locatorwidget.cpp +++ b/src/plugins/coreplugin/locator/locatorwidget.cpp @@ -49,7 +49,9 @@ #include #include +#include #include +#include #include #include #include @@ -112,6 +114,28 @@ public: void showCurrentItemToolTip(); void keyPressEvent(QKeyEvent *event); + bool eventFilter(QObject *watched, QEvent *event); +}; + +class TopLeftLocatorPopup : public LocatorPopup +{ +public: + TopLeftLocatorPopup(LocatorWidget *locatorWidget) + : LocatorPopup(locatorWidget, locatorWidget) {} + +protected: + void updateGeometry() override; + void inputLostFocus() override; +}; + +class CenteredLocatorPopup : public LocatorPopup +{ +public: + CenteredLocatorPopup(LocatorWidget *locatorWidget, QWidget *parent) + : LocatorPopup(locatorWidget, parent) {} + +protected: + void updateGeometry() override; }; // =========== LocatorModel =========== @@ -226,6 +250,8 @@ CompletionList::CompletionList(QWidget *parent) const QStyleOptionViewItem &option = viewOptions(); const QSize shint = itemDelegate()->sizeHint(option, QModelIndex()); setFixedHeight(shint.height() * 17 + frameWidth() * 2); + + installEventFilter(this); } void CompletionList::setModel(QAbstractItemModel *newModel) @@ -241,20 +267,41 @@ void CompletionList::setModel(QAbstractItemModel *newModel) } } -void LocatorPopup::resize() +void LocatorPopup::updateGeometry() { - static const int MIN_WIDTH = 730; - const QSize windowSize = m_window ? m_window->size() : QSize(MIN_WIDTH, 0); - - const int width = qMax(MIN_WIDTH, windowSize.width() * 2 / 3); - m_preferredSize = QSize(width, sizeHint().height()); - QWidget::resize(m_preferredSize); m_tree->resizeHeaders(); } -QSize LocatorPopup::preferredSize() const +void TopLeftLocatorPopup::updateGeometry() { - return m_preferredSize; + QTC_ASSERT(parentWidget(), return); + const QSize size = preferredSize(); + const QRect rect(parentWidget()->mapToGlobal(QPoint(0, -size.height())), size); + setGeometry(rect); + LocatorPopup::updateGeometry(); +} + +void CenteredLocatorPopup::updateGeometry() +{ + QTC_ASSERT(parentWidget(), return); + const QSize size = preferredSize(); + const QSize parentSize = parentWidget()->size(); + const QPoint pos = parentWidget()->mapToGlobal({(parentSize.width() - size.width()) / 2, + parentSize.height() / 2 - size.height()}); + QRect rect(pos, size); + // invisible widget doesn't have the right screen set yet, so use the parent widget to + // check for available geometry + const QRect available = QApplication::desktop()->availableGeometry(parentWidget()); + if (rect.right() > available.right()) + rect.moveRight(available.right()); + if (rect.bottom() > available.bottom()) + rect.moveBottom(available.bottom()); + if (rect.top() < available.top()) + rect.moveTop(available.top()); + if (rect.left() < available.left()) + rect.moveLeft(available.left()); + setGeometry(rect); + LocatorPopup::updateGeometry(); } void LocatorPopup::updateWindow() @@ -271,25 +318,36 @@ void LocatorPopup::updateWindow() bool LocatorPopup::event(QEvent *event) { - if (event->type() == QEvent::ParentChange) + if (event->type() == QEvent::ParentChange) { updateWindow(); + } else if (event->type() == QEvent::Show) + updateGeometry(); return QWidget::event(event); } bool LocatorPopup::eventFilter(QObject *watched, QEvent *event) { if (watched == m_window && event->type() == QEvent::Resize) - resize(); + updateGeometry(); return QWidget::eventFilter(watched, event); } -void LocatorPopup::showPopup() +QSize LocatorPopup::preferredSize() +{ + static const int MIN_WIDTH = 730; + const QSize windowSize = m_window ? m_window->size() : QSize(MIN_WIDTH, 0); + + const int width = qMax(MIN_WIDTH, windowSize.width() * 2 / 3); + return QSize(width, sizeHint().height()); +} + +void TopLeftLocatorPopup::inputLostFocus() +{ + hide(); +} + +void LocatorPopup::inputLostFocus() { - QTC_ASSERT(parentWidget(), return); - const QSize size = preferredSize(); - const QRect rect(parentWidget()->mapToGlobal(QPoint(0, -size.height())), size); - setGeometry(rect); - show(); } void CompletionList::resizeHeaders() @@ -300,10 +358,9 @@ void CompletionList::resizeHeaders() LocatorPopup::LocatorPopup(LocatorWidget *locatorWidget, QWidget *parent) : QWidget(parent), - m_tree(new CompletionList(this)) + m_tree(new CompletionList(this)), + m_inputWidget(locatorWidget) { - setWindowFlags(Qt::ToolTip); - m_tree->setFrameStyle(QFrame::NoFrame); m_tree->setModel(locatorWidget->model()); @@ -315,8 +372,9 @@ LocatorPopup::LocatorPopup(LocatorWidget *locatorWidget, QWidget *parent) layout->addWidget(m_tree); connect(locatorWidget, &LocatorWidget::parentChanged, this, &LocatorPopup::updateWindow); - connect(locatorWidget, &LocatorWidget::showPopup, this, &LocatorPopup::showPopup); - connect(locatorWidget, &LocatorWidget::hidePopup, this, &LocatorPopup::hide); + connect(locatorWidget, &LocatorWidget::showPopup, this, &LocatorPopup::show); + connect(locatorWidget, &LocatorWidget::hidePopup, this, &LocatorPopup::close); + connect(locatorWidget, &LocatorWidget::lostFocus, this, &LocatorPopup::inputLostFocus); connect(locatorWidget, &LocatorWidget::selectRow, m_tree, [this](int row) { m_tree->setCurrentIndex(m_tree->model()->index(row, 0)); }); @@ -331,7 +389,7 @@ LocatorPopup::LocatorPopup(LocatorWidget *locatorWidget, QWidget *parent) locatorWidget->scheduleAcceptEntry(index); }); - resize(); + updateGeometry(); } CompletionList *LocatorPopup::completionList() const @@ -339,6 +397,11 @@ CompletionList *LocatorPopup::completionList() const return m_tree; } +LocatorWidget *LocatorPopup::inputWidget() const +{ + return m_inputWidget; +} + void LocatorPopup::focusOutEvent(QFocusEvent *event) { if (event->reason() == Qt::ActiveWindowFocusReason) hide(); @@ -402,6 +465,22 @@ void CompletionList::keyPressEvent(QKeyEvent *event) Utils::TreeView::keyPressEvent(event); } +bool CompletionList::eventFilter(QObject *watched, QEvent *event) +{ + if (watched == this && event->type() == QEvent::ShortcutOverride) { + QKeyEvent *ke = static_cast(event); + switch (ke->key()) { + case Qt::Key_Escape: + if (!ke->modifiers()) { + event->accept(); + return true; + } + break; + } + } + return Utils::TreeView::eventFilter(watched, event); +} + // =========== LocatorWidget =========== LocatorWidget::LocatorWidget(Locator *locator) : @@ -576,7 +655,7 @@ bool LocatorWidget::eventFilter(QObject *obj, QEvent *event) } } } else if (obj == m_fileLineEdit && event->type() == QEvent::FocusOut) { - emit hidePopup(); + emit lostFocus(); } else if (obj == m_fileLineEdit && event->type() == QEvent::FocusIn) { QFocusEvent *fev = static_cast(event); if (fev->reason() != Qt::ActiveWindowFocusReason) @@ -796,5 +875,23 @@ void LocatorWidget::addSearchResults(int firstIndex, int endIndex) } } +LocatorWidget *createStaticLocatorWidget(Locator *locator) +{ + auto widget = new LocatorWidget(locator); + auto popup = new TopLeftLocatorPopup(widget); // owned by widget + popup->setWindowFlags(Qt::ToolTip); + return widget; +} + +LocatorPopup *createLocatorPopup(Locator *locator, QWidget *parent) +{ + auto widget = new LocatorWidget(locator); + auto popup = new CenteredLocatorPopup(widget, parent); + popup->layout()->addWidget(widget); + popup->setWindowFlags(Qt::Popup); + popup->setAttribute(Qt::WA_DeleteOnClose); + return popup; +} + } // namespace Internal } // namespace Core diff --git a/src/plugins/coreplugin/locator/locatorwidget.h b/src/plugins/coreplugin/locator/locatorwidget.h index 29fa810ea95..3f42d05a3aa 100644 --- a/src/plugins/coreplugin/locator/locatorwidget.h +++ b/src/plugins/coreplugin/locator/locatorwidget.h @@ -64,6 +64,7 @@ public: signals: void showCurrentItemToolTip(); + void lostFocus(); void hidePopup(); void selectRow(int row); void handleKey(QKeyEvent *keyEvent); // only use with DirectConnection, event is deleted @@ -109,21 +110,28 @@ public: LocatorPopup(LocatorWidget *locatorWidget, QWidget *parent = 0); CompletionList *completionList() const; + LocatorWidget *inputWidget() const; void focusOutEvent (QFocusEvent *event) override; - void resize(); - QSize preferredSize() const; bool event(QEvent *event) override; bool eventFilter(QObject *watched, QEvent *event) override; +protected: + QSize preferredSize(); + virtual void updateGeometry(); + virtual void inputLostFocus(); + + QPointer m_window; + private: - void showPopup(); + void updateWindow(); CompletionList *m_tree; - QSize m_preferredSize; - QPointer m_window; - void updateWindow(); + LocatorWidget *m_inputWidget; }; +LocatorWidget *createStaticLocatorWidget(Locator *locator); +LocatorPopup *createLocatorPopup(Locator *locator, QWidget *parent); + } // namespace Internal } // namespace Core diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index 0b0049a29b9..a0eb8d7e14b 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -475,7 +475,7 @@ void GdbEngine::handleResponse(const QString &buff) case '@': { QString data = GdbMi::parseCString(from, to); - QString msg = data.mid(2, data.size() - 4); + QString msg = data.left(data.size() - 1); showMessage(msg, AppOutput); break; } diff --git a/src/plugins/projectexplorer/runconfiguration.cpp b/src/plugins/projectexplorer/runconfiguration.cpp index b51b238118b..79f7067104c 100644 --- a/src/plugins/projectexplorer/runconfiguration.cpp +++ b/src/plugins/projectexplorer/runconfiguration.cpp @@ -726,20 +726,10 @@ RunControl::~RunControl() void RunControl::initiateStart() { emit aboutToStart(); - start(); -} - -void RunControl::start() -{ d->initiateStart(); } void RunControl::initiateStop() -{ - stop(); -} - -void RunControl::stop() { d->initiateStop(); } diff --git a/src/plugins/projectexplorer/runconfiguration.h b/src/plugins/projectexplorer/runconfiguration.h index 891c17a57e4..cf1d5c7a9ad 100644 --- a/src/plugins/projectexplorer/runconfiguration.h +++ b/src/plugins/projectexplorer/runconfiguration.h @@ -429,8 +429,8 @@ public: RunControl(RunConfiguration *runConfiguration, Core::Id mode); ~RunControl() override; - void initiateStart(); // Calls start() asynchronously. - void initiateStop(); // Calls stop() asynchronously. + void initiateStart(); + void initiateStop(); bool promptToStop(bool *optionalPrompt = nullptr) const; void setPromptToStop(const std::function &promptToStop); @@ -478,9 +478,6 @@ public: const QString &cancelButtonText = QString(), bool *prompt = nullptr); - virtual void start(); - virtual void stop(); - using WorkerCreator = std::function; static void registerWorkerCreator(Core::Id id, const WorkerCreator &workerCreator); RunWorker *workerById(Core::Id id) const; diff --git a/src/plugins/qmlprofiler/qmlprofilerruncontrol.cpp b/src/plugins/qmlprofiler/qmlprofilerruncontrol.cpp index 4525b72e11e..f1dd71224f3 100644 --- a/src/plugins/qmlprofiler/qmlprofilerruncontrol.cpp +++ b/src/plugins/qmlprofiler/qmlprofilerruncontrol.cpp @@ -102,7 +102,7 @@ QmlProfilerRunner::QmlProfilerRunner(RunControl *runControl) QmlProfilerRunner::~QmlProfilerRunner() { if (runControl()->isRunning() && d->m_profilerState) - runControl()->stop(); + runControl()->initiateStop(); delete d; } diff --git a/src/plugins/qmlprofiler/qmlprofilertool.cpp b/src/plugins/qmlprofiler/qmlprofilertool.cpp index bad4c79ee48..b661fab197f 100644 --- a/src/plugins/qmlprofiler/qmlprofilertool.cpp +++ b/src/plugins/qmlprofiler/qmlprofilertool.cpp @@ -342,10 +342,10 @@ void QmlProfilerTool::finalizeRunControl(QmlProfilerRunner *runWorker) connect(runControl, &RunControl::finished, this, [this, runControl] { d->m_toolBusy = false; updateRunActions(); - disconnect(d->m_stopAction, &QAction::triggered, runControl, &RunControl::stop); + disconnect(d->m_stopAction, &QAction::triggered, runControl, &RunControl::initiateStop); }); - connect(d->m_stopAction, &QAction::triggered, runControl, &RunControl::stop); + connect(d->m_stopAction, &QAction::triggered, runControl, &RunControl::initiateStop); updateRunActions(); runWorker->registerProfilerStateManager(d->m_profilerState); diff --git a/src/plugins/texteditor/basehoverhandler.cpp b/src/plugins/texteditor/basehoverhandler.cpp index 56ad0c0ddb8..025d27b057b 100644 --- a/src/plugins/texteditor/basehoverhandler.cpp +++ b/src/plugins/texteditor/basehoverhandler.cpp @@ -26,6 +26,7 @@ #include "basehoverhandler.h" #include "texteditor.h" +#include #include namespace TextEditor { @@ -33,6 +34,11 @@ namespace TextEditor { BaseHoverHandler::~BaseHoverHandler() {} +bool BaseHoverHandler::isAsyncHandler() const +{ + return m_isAsyncHandler; +} + void BaseHoverHandler::showToolTip(TextEditorWidget *widget, const QPoint &point, bool decorate) { if (decorate) @@ -40,13 +46,18 @@ void BaseHoverHandler::showToolTip(TextEditorWidget *widget, const QPoint &point operateTooltip(widget, point); } -int BaseHoverHandler::checkToolTip(TextEditorWidget *widget, int pos) +void BaseHoverHandler::checkPriority(TextEditorWidget *widget, + int pos, + ReportPriority report) { widget->setContextHelpId(QString()); - process(widget, pos); + process(widget, pos, report); +} - return priority(); +void BaseHoverHandler::cancelAsyncCheck() +{ + QTC_CHECK(false && "BaseHoverHandler: Implement cancelCheck() in derived class!"); } int BaseHoverHandler::priority() const @@ -73,7 +84,7 @@ QString BaseHoverHandler::contextHelpId(TextEditorWidget *widget, int pos) // If the tooltip is visible and there is a help match, this match is used to update // the help id. Otherwise, let the identification process happen. if (!Utils::ToolTip::isVisible() || !lastHelpItemIdentified().isValid()) - process(widget, pos); + process(widget, pos, ReportPriority()); // TODO if (lastHelpItemIdentified().isValid()) return lastHelpItemIdentified().helpId(); @@ -100,13 +111,23 @@ const HelpItem &BaseHoverHandler::lastHelpItemIdentified() const return m_lastHelpItemIdentified; } -void BaseHoverHandler::process(TextEditorWidget *widget, int pos) +void BaseHoverHandler::process(TextEditorWidget *widget, int pos, ReportPriority report) { m_toolTip.clear(); m_priority = -1; m_lastHelpItemIdentified = HelpItem(); - identifyMatch(widget, pos); + if (m_isAsyncHandler) { + identifyMatchAsync(widget, pos, report); + } else { + identifyMatch(widget, pos); + report(priority()); + } +} + +void BaseHoverHandler::setIsAsyncHandler(bool isAsyncHandler) +{ + m_isAsyncHandler = isAsyncHandler; } void BaseHoverHandler::identifyMatch(TextEditorWidget *editorWidget, int pos) @@ -116,6 +137,11 @@ void BaseHoverHandler::identifyMatch(TextEditorWidget *editorWidget, int pos) setToolTip(tooltip); } +void BaseHoverHandler::identifyMatchAsync(TextEditorWidget *, int, BaseHoverHandler::ReportPriority) +{ + QTC_CHECK(false && "BaseHoverHandler: Implement identifyMatchAsync() in derived class!"); +} + void BaseHoverHandler::decorateToolTip() { if (Qt::mightBeRichText(toolTip())) diff --git a/src/plugins/texteditor/basehoverhandler.h b/src/plugins/texteditor/basehoverhandler.h index 44dfc61e922..cdd8735264b 100644 --- a/src/plugins/texteditor/basehoverhandler.h +++ b/src/plugins/texteditor/basehoverhandler.h @@ -28,6 +28,8 @@ #include "texteditor_global.h" #include "helpitem.h" +#include + QT_BEGIN_NAMESPACE class QPoint; QT_END_NAMESPACE @@ -41,9 +43,15 @@ class TEXTEDITOR_EXPORT BaseHoverHandler public: virtual ~BaseHoverHandler(); + bool isAsyncHandler() const; + void setIsAsyncHandler(bool isAsyncHandler); + QString contextHelpId(TextEditorWidget *widget, int pos); - int checkToolTip(TextEditorWidget *widget, int pos); + using ReportPriority = std::function; + void checkPriority(TextEditorWidget *widget, int pos, ReportPriority report); + virtual void cancelAsyncCheck(); + void showToolTip(TextEditorWidget *widget, const QPoint &point, bool decorate = true); protected: @@ -63,11 +71,14 @@ protected: const HelpItem &lastHelpItemIdentified() const; virtual void identifyMatch(TextEditorWidget *editorWidget, int pos); + virtual void identifyMatchAsync(TextEditorWidget *editorWidget, int pos, ReportPriority report); virtual void decorateToolTip(); virtual void operateTooltip(TextEditorWidget *editorWidget, const QPoint &point); private: - void process(TextEditorWidget *widget, int pos); + void process(TextEditorWidget *widget, int pos, ReportPriority report); + + bool m_isAsyncHandler = false; QString m_toolTip; HelpItem m_lastHelpItemIdentified; diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index aa794ad63ce..0e64e0c61ff 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -248,6 +248,119 @@ public: TextEditorFactoryPrivate *m_origin; }; +class HoverHandlerRunner +{ +public: + HoverHandlerRunner(TextEditorWidget *widget, QList &handlers) + : m_widget(widget) + , m_handlers(handlers) + { + } + + void startChecking(const QTextCursor &textCursor, const QPoint &point) + { + if (m_handlers.empty()) + return; + + // Does the last handler still applies? + const int documentRevision = textCursor.document()->revision(); + const int position = Convenience::wordStartCursor(textCursor).position(); + if (m_lastHandlerInfo.applies(documentRevision, position)) { + m_lastHandlerInfo.handler->showToolTip(m_widget, point, /*decorate=*/ false); + return; + } + + // Cancel currently running checks + for (BaseHoverHandler *handler : m_handlers) { + if (handler->isAsyncHandler()) + handler->cancelAsyncCheck(); + } + + // Update invocation data + m_documentRevision = documentRevision; + m_position = position; + m_point = point; + + // Re-initialize process data + m_currentHandlerIndex = 0; + m_bestHandler = nullptr; + m_highestHandlerPriority = -1; + + // Start checking + checkNext(); + } + + void checkNext() + { + QTC_ASSERT(m_currentHandlerIndex < m_handlers.size(), return); + BaseHoverHandler *currentHandler = m_handlers[m_currentHandlerIndex]; + + currentHandler->checkPriority(m_widget, m_position, [this](int priority) { + onHandlerFinished(m_documentRevision, m_position, priority); + }); + } + + void onHandlerFinished(int documentRevision, int position, int priority) + { + QTC_ASSERT(m_currentHandlerIndex < m_handlers.size(), return); + QTC_ASSERT(documentRevision == m_documentRevision, return); + QTC_ASSERT(position == m_position, return); + + BaseHoverHandler *currentHandler = m_handlers[m_currentHandlerIndex]; + if (priority > m_highestHandlerPriority) { + m_highestHandlerPriority = priority; + m_bestHandler = currentHandler; + } + + // There are more, check next + ++m_currentHandlerIndex; + if (m_currentHandlerIndex < m_handlers.size()) { + checkNext(); + return; + } + + // All were queried, run the best + if (m_bestHandler) { + m_lastHandlerInfo = LastHandlerInfo(m_bestHandler, m_documentRevision, m_position); + m_bestHandler->showToolTip(m_widget, m_point); + } + } + +private: + TextEditorWidget *m_widget = nullptr; + const QList &m_handlers; + + struct LastHandlerInfo { + LastHandlerInfo() = default; + LastHandlerInfo(BaseHoverHandler *handler, int documentRevision, int cursorPosition) + : handler(handler) + , documentRevision(documentRevision) + , cursorPosition(cursorPosition) + {} + + bool applies(int documentRevision, int cursorPosition) const + { + return handler + && documentRevision == this->documentRevision + && cursorPosition == this->cursorPosition; + } + + BaseHoverHandler *handler = nullptr; + int documentRevision = -1; + int cursorPosition = -1; + } m_lastHandlerInfo; + + // invocation data + QPoint m_point; + int m_position = -1; + int m_documentRevision = -1; + + // processing data + int m_currentHandlerIndex = -1; + int m_highestHandlerPriority = -1; + BaseHoverHandler *m_bestHandler = nullptr; +}; + class TextEditorWidgetPrivate : public QObject { public: @@ -469,26 +582,8 @@ public: CodeAssistant m_codeAssistant; bool m_assistRelevantContentAdded = false; - struct LastHoverHandlerInfo { - LastHoverHandlerInfo() = default; - LastHoverHandlerInfo(BaseHoverHandler *handler, int documentRevision, int cursorPosition) - : handler(handler) - , documentRevision(documentRevision) - , cursorPosition(cursorPosition) - {} - - bool applies(int documentRevision, int cursorPosition) const - { - return handler - && documentRevision == this->documentRevision - && cursorPosition == this->cursorPosition; - } - - BaseHoverHandler *handler = nullptr; - int documentRevision = -1; - int cursorPosition = -1; - } m_lastHoverHandlerInfo; QList m_hoverHandlers; // Not owned + HoverHandlerRunner m_hoverHandlerRunner; QPointer m_navigationAnimation; @@ -535,6 +630,7 @@ TextEditorWidgetPrivate::TextEditorWidgetPrivate(TextEditorWidget *parent) m_requestMarkEnabled(true), m_lineSeparatorsAllowed(false), m_maybeFakeTooltipEvent(false), + m_hoverHandlerRunner(parent, m_hoverHandlers), m_clipboardAssistProvider(new ClipboardAssistProvider), m_autoCompleter(new AutoCompleter) { @@ -978,7 +1074,7 @@ int TextEditorWidgetPrivate::visualIndent(const QTextBlock &block) const void TextEditorWidgetPrivate::updateAutoCompleteHighlight() { const QTextCharFormat &matchFormat - = q->textDocument()->fontSettings().toTextCharFormat(C_PARENTHESES); + = q->textDocument()->fontSettings().toTextCharFormat(C_AUTOCOMPLETE); QList extraSelections; for (QTextCursor cursor : Utils::asConst(m_autoCompleteHighlightPos)) { @@ -3181,30 +3277,7 @@ void TextEditorWidgetPrivate::processTooltipRequest(const QTextCursor &c) return; } - // Does the last handler still applies? - const int documentRevision = m_document->document()->revision(); - const int cursorPosition = Convenience::wordStartCursor(c).position(); - if (m_lastHoverHandlerInfo.applies(documentRevision, cursorPosition)) { - m_lastHoverHandlerInfo.handler->showToolTip(q, toolTipPoint, /*decorate=*/ false); - return; - } - - // Determine best handler - int highestPriority = -1; - BaseHoverHandler *highest = 0; - foreach (BaseHoverHandler *handler, m_hoverHandlers) { - int priority = handler->checkToolTip(q, c.position()); - if (priority > highestPriority) { - highestPriority = priority; - highest = handler; - } - } - - // Let the best handler show the tooltip - if (highest) { - m_lastHoverHandlerInfo = LastHoverHandlerInfo{highest, documentRevision, cursorPosition}; - highest->showToolTip(q, toolTipPoint); - } + m_hoverHandlerRunner.startChecking(c, toolTipPoint); } bool TextEditorWidgetPrivate::processAnnotaionTooltipRequest(const QTextBlock &block, diff --git a/src/plugins/valgrind/callgrindtool.cpp b/src/plugins/valgrind/callgrindtool.cpp index c93a406c900..1564fcb78d0 100644 --- a/src/plugins/valgrind/callgrindtool.cpp +++ b/src/plugins/valgrind/callgrindtool.cpp @@ -752,7 +752,7 @@ ValgrindToolRunner *CallgrindTool::createRunTool(RunControl *runControl) connect(this, &CallgrindTool::resetRequested, toolRunner, &CallgrindToolRunner::reset); connect(this, &CallgrindTool::pauseToggled, toolRunner, &CallgrindToolRunner::setPaused); - connect(m_stopAction, &QAction::triggered, toolRunner, [runControl] { runControl->stop(); }); + connect(m_stopAction, &QAction::triggered, toolRunner, [runControl] { runControl->initiateStop(); }); // initialize run control toolRunner->setPaused(m_pauseAction->isChecked()); diff --git a/src/plugins/valgrind/memchecktool.cpp b/src/plugins/valgrind/memchecktool.cpp index e6c70188340..5ae5bf3f152 100644 --- a/src/plugins/valgrind/memchecktool.cpp +++ b/src/plugins/valgrind/memchecktool.cpp @@ -567,7 +567,7 @@ RunWorker *MemcheckTool::createRunWorker(RunControl *runControl) connect(runTool, &MemcheckToolRunner::internalParserError, this, &MemcheckTool::internalParserError); connect(runTool, &MemcheckToolRunner::stopped, this, &MemcheckTool::engineFinished); - connect(m_stopAction, &QAction::triggered, runControl, &RunControl::stop); + connect(m_stopAction, &QAction::triggered, runControl, &RunControl::initiateStop); m_toolBusy = true; updateRunActions();