From 4edc715b1c86e5985a5c62882f94fbb4f621d739 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Mon, 26 Jun 2017 13:16:21 +0200 Subject: [PATCH 01/10] AutoTest: Speed up finding parent items We normally add new results to the last added item, so processing the search for the parent from bottom to top makes more sense and avoids iterating over almost every item. Change-Id: Iede08b9c0d4c80227d2e8fea9b002354f01d5b35 Reviewed-by: hjk --- src/libs/utils/treemodel.cpp | 12 ++++++++++++ src/libs/utils/treemodel.h | 2 ++ src/plugins/autotest/testresultmodel.cpp | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) 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/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 From cc8bff67b312fe8f18304859eeb421744f1fac6d Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Mon, 19 Jun 2017 15:58:21 +0200 Subject: [PATCH 02/10] AutoTest: Support plaintext output for Qt tests Change-Id: I88ec477777d79c69e699dd906ec4ef1550bcaf44 Reviewed-by: David Schulz --- .../autotest/qtest/qttestconfiguration.cpp | 21 +- .../autotest/qtest/qttestoutputreader.cpp | 237 ++++++++++++++++-- .../autotest/qtest/qttestoutputreader.h | 22 +- src/plugins/autotest/qtest/qttestsettings.cpp | 3 + src/plugins/autotest/qtest/qttestsettings.h | 1 + .../autotest/qtest/qttestsettingspage.cpp | 2 + .../autotest/qtest/qttestsettingspage.ui | 15 ++ .../autotest/quick/quicktestconfiguration.cpp | 20 +- src/plugins/autotest/testresult.cpp | 8 +- 9 files changed, 289 insertions(+), 40 deletions(-) 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; From a6a13bfb76a4814d4b34360d1da29055409ea859 Mon Sep 17 00:00:00 2001 From: Vikas Pachdha Date: Thu, 8 Jun 2017 14:57:09 +0200 Subject: [PATCH 03/10] Android: Let user specify shell commands Let user specify list of shell commands to run before app starts and after app quits. Change-Id: I9794fb96180530ca6c28ce6581fda51a25be28d4 Reviewed-by: hjk --- src/plugins/android/adbcommandswidget.cpp | 198 ++++++++++++++++++ src/plugins/android/adbcommandswidget.h | 64 ++++++ src/plugins/android/adbcommandswidget.ui | 117 +++++++++++ src/plugins/android/android.pro | 9 +- src/plugins/android/android.qbs | 5 +- .../android/androidrunconfiguration.cpp | 34 +++ src/plugins/android/androidrunconfiguration.h | 6 + .../android/androidrunconfigurationwidget.cpp | 34 ++- .../android/androidrunconfigurationwidget.h | 14 ++ .../android/androidrunconfigurationwidget.ui | 51 +++-- src/plugins/android/androidrunnable.h | 8 +- src/plugins/android/androidrunner.cpp | 93 ++++---- src/plugins/android/androidrunner.h | 7 +- 13 files changed, 569 insertions(+), 71 deletions(-) create mode 100644 src/plugins/android/adbcommandswidget.cpp create mode 100644 src/plugins/android/adbcommandswidget.h create mode 100644 src/plugins/android/adbcommandswidget.ui diff --git a/src/plugins/android/adbcommandswidget.cpp b/src/plugins/android/adbcommandswidget.cpp new file mode 100644 index 00000000000..a6f35877644 --- /dev/null +++ b/src/plugins/android/adbcommandswidget.cpp @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** 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 + +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: From ed2d41c5e2402fcfaa440032f09a786366cf3caa Mon Sep 17 00:00:00 2001 From: David Schulz Date: Fri, 30 Jun 2017 14:35:25 +0200 Subject: [PATCH 04/10] Android: fix compile Change-Id: I9d3e82db5055ddb1310573eba9353227268dcea3 Reviewed-by: Vikas Pachdha --- src/plugins/android/adbcommandswidget.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/android/adbcommandswidget.cpp b/src/plugins/android/adbcommandswidget.cpp index a6f35877644..404ec55b2b4 100644 --- a/src/plugins/android/adbcommandswidget.cpp +++ b/src/plugins/android/adbcommandswidget.cpp @@ -32,6 +32,8 @@ #include #include +#include + namespace Android { namespace Internal { From 629c137ef24e7a86015d7f944cf928ab359610a1 Mon Sep 17 00:00:00 2001 From: hjk Date: Mon, 26 Jun 2017 18:40:11 +0200 Subject: [PATCH 05/10] ProjectExplorer: Remove virtual RunControl start/stop trampolin Not needed anymore, effectively replaced by RunWorker start/stop. Change-Id: I7483c841cdd4e05c9e1f7636a27b20ece37947c2 Reviewed-by: Christian Stenger --- src/plugins/autotest/testrunner.cpp | 3 ++- .../clangstaticanalyzerruncontrol.cpp | 2 +- src/plugins/projectexplorer/runconfiguration.cpp | 10 ---------- src/plugins/projectexplorer/runconfiguration.h | 7 ++----- src/plugins/qmlprofiler/qmlprofilerruncontrol.cpp | 2 +- src/plugins/qmlprofiler/qmlprofilertool.cpp | 4 ++-- src/plugins/valgrind/callgrindtool.cpp | 2 +- src/plugins/valgrind/memchecktool.cpp | 2 +- 8 files changed, 10 insertions(+), 22 deletions(-) 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/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/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(); From 38296f6accedd10ca68e113cc2ef10cb47ba9426 Mon Sep 17 00:00:00 2001 From: Tomasz Olszak Date: Thu, 29 Jun 2017 14:59:07 +0200 Subject: [PATCH 06/10] Fix inserting the same multiple items to PathsAndLanguages Change-Id: Ie1df6fad425fd7198a72f531c19b3681627a5210 Reviewed-by: Marco Benelli --- src/libs/qmljs/qmljsdialect.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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; From f870446677a56a88221ddcc80df4840fbd6734a8 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Thu, 15 Jun 2017 14:34:11 +0200 Subject: [PATCH 07/10] Add Locator to extra editor windows And open a popup if other windows are active. That way we avoid switching to the main window. Change-Id: Ia6d8d3fb4361ac31e406356d40056a2c0b79d114 Reviewed-by: David Schulz --- .../coreplugin/editormanager/editorwindow.cpp | 18 +++ src/plugins/coreplugin/locator/locator.cpp | 22 ++- src/plugins/coreplugin/locator/locator.h | 5 + .../coreplugin/locator/locatormanager.cpp | 37 +++-- .../coreplugin/locator/locatormanager.h | 2 +- .../coreplugin/locator/locatorwidget.cpp | 145 +++++++++++++++--- .../coreplugin/locator/locatorwidget.h | 20 ++- 7 files changed, 203 insertions(+), 46 deletions(-) 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 From 6e51fd2a0c5e1bd4a8e8bd12b0b0c2dec4854d66 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Mon, 3 Jul 2017 09:22:08 +0200 Subject: [PATCH 08/10] TextEditor: Use correct format for auto complete highlight Change-Id: Icb9800a286417077985ad9c0acedeb9b06b456cc Reviewed-by: Christian Stenger --- src/plugins/texteditor/texteditor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index aa794ad63ce..bc9a2b2f7f9 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -978,7 +978,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)) { From 999e6bc6714600069eadd916d861f21e70faba95 Mon Sep 17 00:00:00 2001 From: Nikolai Kosjar Date: Mon, 26 Jun 2017 11:15:19 +0200 Subject: [PATCH 09/10] TextEditor: Allow asynchronous hover handlers Change-Id: I956b126e2c779aa81f86e4432d127b45ac1912ff Reviewed-by: David Schulz --- src/plugins/texteditor/basehoverhandler.cpp | 38 ++++- src/plugins/texteditor/basehoverhandler.h | 15 +- src/plugins/texteditor/texteditor.cpp | 159 ++++++++++++++------ 3 files changed, 161 insertions(+), 51 deletions(-) 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 bc9a2b2f7f9..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) { @@ -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, From c3461837689d115ecfe7eea58067b7213036987e Mon Sep 17 00:00:00 2001 From: Alexander Drozdov Date: Mon, 3 Jul 2017 17:17:08 +1000 Subject: [PATCH 10/10] GDB: fix information text truncation in Application Output pane GdbEngine::handleResponse() incorrectly handles messages starts with '@'. Seems that text position pointer by `from` and `to` vars already correctly configured and additional cutting with QString::mid() is not required. Seems issue was introduced by 726b907cc356995b7a9c28ee8dc8b2f2314e9103. Change-Id: I2759d1c1650a1949c9c9feb75cf12e2760920d21 Task-number: QTCREATORBUG-18494 Reviewed-by: hjk Reviewed-by: Alexander Drozdov --- src/plugins/debugger/gdb/gdbengine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; }