Merge remote-tracking branch 'origin/master' into 4.4

Change-Id: I5d3d689e7bd8d51094b8416761caed15b7df7bd8
This commit is contained in:
Eike Ziller
2017-07-03 10:30:16 +02:00
45 changed files with 1254 additions and 237 deletions

View File

@@ -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 (pathAndLanguage.language() < currentElement.language())
break;
if (currentElement.language() == pathAndLanguage.language())
return false;
break;
}
++j;
if (j == m_list.length())
break;

View File

@@ -841,6 +841,18 @@ TreeItem *TreeItem::findAnyChild(const std::function<bool(TreeItem *)> &pred) co
return 0;
}
TreeItem *TreeItem::reverseFindAnyChild(const std::function<bool (TreeItem *)> &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) {

View File

@@ -78,6 +78,8 @@ public:
void forSelectedChildren(const std::function<bool(TreeItem *)> &pred) const;
void forAllChildren(const std::function<void(TreeItem *)> &pred) const;
TreeItem *findAnyChild(const std::function<bool(TreeItem *)> &pred) const;
// like findAnyChild() but processes children from bottom to top
TreeItem *reverseFindAnyChild(const std::function<bool(TreeItem *)> &pred) const;
// Levels are 1-based: Child at Level 1 is an immediate child.
void forChildrenAtLevel(int level, const std::function<void(TreeItem *)> &pred) const;

View File

@@ -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 <QGroupBox>
#include <QItemSelectionModel>
#include <QShortcut>
#include <QStringListModel>
#include <functional>
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

View File

@@ -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 <QObject>
#include <QStringList>
#include <memory>
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<AdbCommandsWidgetPrivate> d;
};
} // Internal
} // Android

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AdbCommandsWidget</class>
<widget class="QGroupBox" name="AdbCommandsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>682</width>
<height>391</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<property name="title">
<string/>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" rowspan="6">
<widget class="QListView" name="commandsView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="defaultDropAction">
<enum>Qt::MoveAction</enum>
</property>
<property name="movement">
<enum>QListView::Snap</enum>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QToolButton" name="moveUpButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
<property name="arrowType">
<enum>Qt::NoArrow</enum>
</property>
</widget>
</item>
<item row="5" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<widget class="QToolButton" name="addButton">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QToolButton" name="moveDownButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
<property name="arrowType">
<enum>Qt::NoArrow</enum>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QToolButton" name="removeButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<tabstops>
<tabstop>commandsView</tabstop>
<tabstop>addButton</tabstop>
<tabstop>removeButton</tabstop>
<tabstop>moveUpButton</tabstop>
<tabstop>moveDownButton</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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

View File

@@ -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<QGridLayout*>(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,10 +75,19 @@ 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));
}
void AndroidRunConfigurationWidget::setPreStartShellCommands(const QStringList &cmdList)
{
m_preStartCmdsWidget->setCommandList(cmdList);
}
void AndroidRunConfigurationWidget::setPostFinishShellCommands(const QStringList &cmdList)
{
m_postEndCmdsWidget->setCommandList(cmdList);
}
} // namespace Internal
} // namespace Android

View File

@@ -29,10 +29,14 @@
#include "projectexplorer/runconfiguration.h"
#include "utils/detailswidget.h"
#include <QStringListModel>
#include <memory>
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<Ui::AndroidRunConfigurationWidget> m_ui;
AdbCommandsWidget *m_preStartCmdsWidget = nullptr;
AdbCommandsWidget *m_postEndCmdsWidget = nullptr;
};
} // namespace Internal

View File

@@ -6,25 +6,31 @@
<rect>
<x>0</x>
<y>0</y>
<width>625</width>
<height>73</height>
<width>731</width>
<height>119</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<widget class="QLineEdit" name="m_amStartArgsEdit"/>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_3">
<property name="spacing">
<number>-1</number>
</property>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>Conflicting &quot;am start&quot; options might result in the app startup failure.</string>
<string>Activity manager start options:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="2" column="1">
<widget class="QLabel" name="m_warningIconLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
@@ -37,7 +43,7 @@
</property>
</widget>
</item>
<item row="1" column="0">
<item row="2" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -53,13 +59,32 @@
</property>
</spacer>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label">
<item row="3" column="0" colspan="3">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>12</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Activity manager start options:</string>
<string>Conflicting "am start" options might result in the app startup failure.</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLineEdit" name="m_amStartArgsEdit"/>
</item>
</layout>
</widget>
<resources/>

View File

@@ -37,8 +37,8 @@ struct ANDROID_EXPORT AndroidRunnable
QString intentName;
QStringList amStartExtraArgs;
Utils::Environment environment;
QVector<QStringList> beforeStartADBCommands;
QVector<QStringList> 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;
}

View File

@@ -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<Debugger::DebuggerRunConfigurationAspect>();
@@ -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<AndroidRunConfiguration *>(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();

View File

@@ -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:

View File

@@ -35,7 +35,17 @@ namespace Internal {
TestOutputReader *QtTestConfiguration::outputReader(const QFutureInterface<TestResultPtr> &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<QtTestSettings>(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<QtTestSettings>(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())

View File

@@ -31,6 +31,7 @@
#include <QDir>
#include <QFileInfo>
#include <QRegExp>
#include <QRegularExpression>
namespace Autotest {
namespace Internal {
@@ -128,12 +129,26 @@ static QString constructSourceFilePath(const QString &path, const QString &fileP
}
QtTestOutputReader::QtTestOutputReader(const QFutureInterface<TestResultPtr> &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

View File

@@ -40,14 +40,32 @@ class QtTestOutputReader : public TestOutputReader
Q_DECLARE_TR_FUNCTIONS(Autotest::Internal::QtTestOutputReader)
public:
enum OutputMode
{
XML,
PlainText
};
QtTestOutputReader(const QFutureInterface<TestResultPtr> &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

View File

@@ -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)

View File

@@ -48,6 +48,7 @@ public:
MetricsType metrics = Walltime;
bool noCrashHandler = true;
bool useXMLOutput = true;
protected:
void fromFrameworkSettings(const QSettings *s) override;

View File

@@ -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())

View File

@@ -31,6 +31,21 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="useXMLOutputCB">
<property name="toolTip">
<string>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)</string>
</property>
<property name="text">
<string>Use XML output</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">

View File

@@ -35,7 +35,16 @@ namespace Internal {
TestOutputReader *QuickTestConfiguration::outputReader(const QFutureInterface<TestResultPtr> &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<QtTestSettings>(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<QtTestSettings>(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())

View File

@@ -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;

View File

@@ -313,7 +313,7 @@ TestResultItem *TestResultModel::findParentItemFor(const TestResultItem *item,
TestResultItem *currentItem = static_cast<TestResultItem *>(it);
return currentItem->testResult()->isDirectParentOf(result, &needsIntermediate);
};
TestResultItem *parent = static_cast<TestResultItem *>(root->findAnyChild(predicate));
TestResultItem *parent = static_cast<TestResultItem *>(root->reverseFindAnyChild(predicate));
if (parent) {
if (needsIntermediate) {
// check if the intermediate is present already

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -28,9 +28,14 @@
#include "editorarea.h"
#include "editormanager_p.h"
#include <aggregation/aggregate.h>
#include <coreplugin/icontext.h>
#include <coreplugin/icore.h>
#include <coreplugin/locator/locator.h>
#include <coreplugin/locator/locatorwidget.h>
#include <coreplugin/statusbarmanager.h>
#include <QStatusBar>
#include <QVBoxLayout>
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()

View File

@@ -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<ILocatorFilter>();
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<ILocatorFilter *> filters)
{
if (filters.isEmpty())

View File

@@ -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<ILocatorFilter *> 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

View File

@@ -29,29 +29,45 @@
#include "locator.h"
#include "locatorwidget.h"
#include <aggregation/aggregate.h>
#include <coreplugin/icore.h>
#include <extensionsystem/pluginmanager.h>
#include <utils/qtcassert.h>
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<LocatorPopup> popup;
QWidget *window = ICore::dialogParent()->window();
if (auto *widget = Aggregation::query<LocatorWidget>(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("<type here>");
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

View File

@@ -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);

View File

@@ -49,7 +49,9 @@
#include <utils/stylehelper.h>
#include <utils/utilsicons.h>
#include <QApplication>
#include <QColor>
#include <QDesktopWidget>
#include <QFileInfo>
#include <QTimer>
#include <QEvent>
@@ -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<QKeyEvent *>(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<QFocusEvent *>(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

View File

@@ -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<QWidget> m_window;
private:
void showPopup();
void updateWindow();
CompletionList *m_tree;
QSize m_preferredSize;
QPointer<QWidget> m_window;
void updateWindow();
LocatorWidget *m_inputWidget;
};
LocatorWidget *createStaticLocatorWidget(Locator *locator);
LocatorPopup *createLocatorPopup(Locator *locator, QWidget *parent);
} // namespace Internal
} // namespace Core

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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<bool(bool *)> &promptToStop);
@@ -478,9 +478,6 @@ public:
const QString &cancelButtonText = QString(),
bool *prompt = nullptr);
virtual void start();
virtual void stop();
using WorkerCreator = std::function<RunWorker *(RunControl *)>;
static void registerWorkerCreator(Core::Id id, const WorkerCreator &workerCreator);
RunWorker *workerById(Core::Id id) const;

View File

@@ -102,7 +102,7 @@ QmlProfilerRunner::QmlProfilerRunner(RunControl *runControl)
QmlProfilerRunner::~QmlProfilerRunner()
{
if (runControl()->isRunning() && d->m_profilerState)
runControl()->stop();
runControl()->initiateStop();
delete d;
}

View File

@@ -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);

View File

@@ -26,6 +26,7 @@
#include "basehoverhandler.h"
#include "texteditor.h"
#include <utils/qtcassert.h>
#include <utils/tooltip/tooltip.h>
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();
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()))

View File

@@ -28,6 +28,8 @@
#include "texteditor_global.h"
#include "helpitem.h"
#include <functional>
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(int priority)>;
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;

View File

@@ -248,6 +248,119 @@ public:
TextEditorFactoryPrivate *m_origin;
};
class HoverHandlerRunner
{
public:
HoverHandlerRunner(TextEditorWidget *widget, QList<BaseHoverHandler *> &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<BaseHoverHandler *> &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<BaseHoverHandler *> m_hoverHandlers; // Not owned
HoverHandlerRunner m_hoverHandlerRunner;
QPointer<QSequentialAnimationGroup> 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<QTextEdit::ExtraSelection> 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,

View File

@@ -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());

View File

@@ -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();