diff --git a/README.md b/README.md index 7229e53b8e8..8a197626471 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,14 @@ Qt Creator is a cross-platform, integrated development environment (IDE) for application developers to create applications for multiple desktop, embedded, and mobile device platforms. +The Qt Creator Manual is available at: + +https://doc.qt.io/qtcreator/index.html + +For an overview of the Qt Creator IDE, see: + +https://doc.qt.io/qtcreator/creator-overview.html + ## Supported Platforms The standalone binary packages support the following platforms: diff --git a/doc/qtcreator/src/qtquick/library/qtquick-shapes.qdoc b/doc/qtcreator/src/qtquick/library/qtquick-shapes.qdoc index ed178387d8d..786a9c85f7f 100644 --- a/doc/qtcreator/src/qtquick/library/qtquick-shapes.qdoc +++ b/doc/qtcreator/src/qtquick/library/qtquick-shapes.qdoc @@ -39,7 +39,7 @@ However, you can use some of the components in \uicontrol Library to draw basic shapes, such as rectangles. In addition, \QDS comes with a set of more powerful and flexible graphical primitives, that allow creating more - complex shapes, such as lines, triangles, arcs, and pies in + complex shapes, such as borders, triangles, arcs, and pies in \uicontrol {Form Editor}. \image studio-shapes.png "Shapes in Form Editor" @@ -98,11 +98,15 @@ \if defined(qtdesignstudio) \section2 Studio Rectangle - If you want to draw asymmetric shapes or use a dashed border, for example, - select the Rectangle type in the \uicontrol {Studio Components} tab of - \uicontrol Library instead of the basic rectangle type. By setting the - values of properties in the \uicontrol {Corner Radiuses} and - \uicontrol {Corner Bevel} groups, you can create shapes with cut corners. + If you want to modify each corner of the rectangle independently + or use a dashed border, select the Rectangle type in the + \uicontrol {Studio Components} tab of \uicontrol Library instead + of the basic rectangle type. + + By setting the values of properties in the \uicontrol {Corner Radiuses} + group, you can draw each corner independently. By using radius values + in combination with the values in the \uicontrol {Corner Bevel} group, + you can create shapes with cut corners. \image studio-shapes-rectangle.png "A studio rectangle with cut corners" diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 70f8f42000b..86fd7096f2c 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -27,6 +27,7 @@ add_subdirectory(resourceeditor) add_subdirectory(tasklist) add_subdirectory(nim) add_subdirectory(incredibuild) +add_subdirectory(conan) # Level 4: (only depends on Level 3 and below) add_subdirectory(clangpchmanager) diff --git a/src/plugins/autotoolsprojectmanager/autogenstep.cpp b/src/plugins/autotoolsprojectmanager/autogenstep.cpp index 38b2e511e74..58a57c84507 100644 --- a/src/plugins/autotoolsprojectmanager/autogenstep.cpp +++ b/src/plugins/autotoolsprojectmanager/autogenstep.cpp @@ -57,36 +57,34 @@ namespace Internal { * It is possible for the user to specify custom arguments. */ -class AutogenStep : public AbstractProcessStep +class AutogenStep final : public AbstractProcessStep { Q_DECLARE_TR_FUNCTIONS(AutotoolsProjectManager::Internal::AutogenStep) public: - AutogenStep(BuildStepList *bsl, Utils::Id id); + AutogenStep(BuildStepList *bsl, Id id); private: - void doRun() override; + void doRun() final; - StringAspect *m_additionalArgumentsAspect = nullptr; bool m_runAutogen = false; }; -AutogenStep::AutogenStep(BuildStepList *bsl, Utils::Id id) : AbstractProcessStep(bsl, id) +AutogenStep::AutogenStep(BuildStepList *bsl, Id id) : AbstractProcessStep(bsl, id) { - m_additionalArgumentsAspect = addAspect(); - m_additionalArgumentsAspect->setSettingsKey( - "AutotoolsProjectManager.AutogenStep.AdditionalArguments"); - m_additionalArgumentsAspect->setLabelText(tr("Arguments:")); - m_additionalArgumentsAspect->setDisplayStyle(StringAspect::LineEditDisplay); - m_additionalArgumentsAspect->setHistoryCompleter("AutotoolsPM.History.AutogenStepArgs"); + auto arguments = addAspect(); + arguments->setSettingsKey("AutotoolsProjectManager.AutogenStep.AdditionalArguments"); + arguments->setLabelText(tr("Arguments:")); + arguments->setDisplayStyle(StringAspect::LineEditDisplay); + arguments->setHistoryCompleter("AutotoolsPM.History.AutogenStepArgs"); - connect(m_additionalArgumentsAspect, &BaseAspect::changed, this, [this] { + connect(arguments, &BaseAspect::changed, this, [this] { m_runAutogen = true; }); - setCommandLineProvider([this] { + setCommandLineProvider([arguments] { return CommandLine(FilePath::fromString("./autogen.sh"), - m_additionalArgumentsAspect->value(), + arguments->value(), CommandLine::Raw); }); diff --git a/src/plugins/autotoolsprojectmanager/autoreconfstep.cpp b/src/plugins/autotoolsprojectmanager/autoreconfstep.cpp index 030922920af..c1a92298dae 100644 --- a/src/plugins/autotoolsprojectmanager/autoreconfstep.cpp +++ b/src/plugins/autotoolsprojectmanager/autoreconfstep.cpp @@ -55,38 +55,37 @@ namespace Internal { * It is possible for the user to specify custom arguments. */ -class AutoreconfStep : public AbstractProcessStep +class AutoreconfStep final : public AbstractProcessStep { Q_DECLARE_TR_FUNCTIONS(AutotoolsProjectManager::Internal::AutoreconfStep) public: - AutoreconfStep(BuildStepList *bsl, Utils::Id id); + AutoreconfStep(BuildStepList *bsl, Id id); void doRun() override; private: - StringAspect *m_additionalArgumentsAspect = nullptr; bool m_runAutoreconf = false; }; -AutoreconfStep::AutoreconfStep(BuildStepList *bsl, Utils::Id id) +AutoreconfStep::AutoreconfStep(BuildStepList *bsl, Id id) : AbstractProcessStep(bsl, id) { - m_additionalArgumentsAspect = addAspect(); - m_additionalArgumentsAspect->setSettingsKey("AutotoolsProjectManager.AutoreconfStep.AdditionalArguments"); - m_additionalArgumentsAspect->setLabelText(tr("Arguments:")); - m_additionalArgumentsAspect->setValue("--force --install"); - m_additionalArgumentsAspect->setDisplayStyle(StringAspect::LineEditDisplay); - m_additionalArgumentsAspect->setHistoryCompleter("AutotoolsPM.History.AutoreconfStepArgs"); + auto arguments = addAspect(); + arguments->setSettingsKey("AutotoolsProjectManager.AutoreconfStep.AdditionalArguments"); + arguments->setLabelText(tr("Arguments:")); + arguments->setValue("--force --install"); + arguments->setDisplayStyle(StringAspect::LineEditDisplay); + arguments->setHistoryCompleter("AutotoolsPM.History.AutoreconfStepArgs"); - connect(m_additionalArgumentsAspect, &BaseAspect::changed, this, [this] { + connect(arguments, &BaseAspect::changed, this, [this] { m_runAutoreconf = true; }); - setCommandLineProvider([this] { - return Utils::CommandLine(Utils::FilePath::fromString("autoreconf"), - m_additionalArgumentsAspect->value(), - Utils::CommandLine::Raw); + setCommandLineProvider([arguments] { + return CommandLine(FilePath::fromString("autoreconf"), + arguments->value(), + CommandLine::Raw); }); setWorkingDirectoryProvider([this] { return project()->projectDirectory(); }); @@ -107,7 +106,7 @@ void AutoreconfStep::doRun() m_runAutoreconf = true; if (!m_runAutoreconf) { - emit addOutput(tr("Configuration unchanged, skipping autoreconf step."), BuildStep::OutputFormat::NormalMessage); + emit addOutput(tr("Configuration unchanged, skipping autoreconf step."), OutputFormat::NormalMessage); emit finished(true); return; } diff --git a/src/plugins/autotoolsprojectmanager/configurestep.cpp b/src/plugins/autotoolsprojectmanager/configurestep.cpp index dea7c90b1eb..cd8676d0b85 100644 --- a/src/plugins/autotoolsprojectmanager/configurestep.cpp +++ b/src/plugins/autotoolsprojectmanager/configurestep.cpp @@ -74,43 +74,41 @@ static QString projectDirRelativeToBuildDir(BuildConfiguration *bc) // * represented by an instance of the class MakeStepConfigWidget. // */ -class ConfigureStep : public ProjectExplorer::AbstractProcessStep +class ConfigureStep final : public AbstractProcessStep { Q_DECLARE_TR_FUNCTIONS(AutotoolsProjectManager::Internal::ConfigureStep) public: - ConfigureStep(BuildStepList *bsl, Utils::Id id); + ConfigureStep(BuildStepList *bsl, Id id); void setAdditionalArguments(const QString &list); private: - void doRun() override; + void doRun() final; - StringAspect *m_additionalArgumentsAspect = nullptr; bool m_runConfigure = false; }; -ConfigureStep::ConfigureStep(BuildStepList *bsl, Utils::Id id) +ConfigureStep::ConfigureStep(BuildStepList *bsl, Id id) : AbstractProcessStep(bsl, id) { - m_additionalArgumentsAspect = addAspect(); - m_additionalArgumentsAspect->setDisplayStyle(StringAspect::LineEditDisplay); - m_additionalArgumentsAspect->setSettingsKey( - "AutotoolsProjectManager.ConfigureStep.AdditionalArguments"); - m_additionalArgumentsAspect->setLabelText(tr("Arguments:")); - m_additionalArgumentsAspect->setHistoryCompleter("AutotoolsPM.History.ConfigureArgs"); + auto arguments = addAspect(); + arguments->setDisplayStyle(StringAspect::LineEditDisplay); + arguments->setSettingsKey("AutotoolsProjectManager.ConfigureStep.AdditionalArguments"); + arguments->setLabelText(tr("Arguments:")); + arguments->setHistoryCompleter("AutotoolsPM.History.ConfigureArgs"); - connect(m_additionalArgumentsAspect, &BaseAspect::changed, this, [this] { + connect(arguments, &BaseAspect::changed, this, [this] { m_runConfigure = true; }); setWorkingDirectoryProvider([this] { return project()->projectDirectory(); }); - setCommandLineProvider([this] { + setCommandLineProvider([this, arguments] { BuildConfiguration *bc = buildConfiguration(); return CommandLine({FilePath::fromString(projectDirRelativeToBuildDir(bc) + "configure"), - m_additionalArgumentsAspect->value(), + arguments->value(), CommandLine::Raw}); }); @@ -135,7 +133,7 @@ void ConfigureStep::doRun() } if (!m_runConfigure) { - emit addOutput(tr("Configuration unchanged, skipping configure step."), BuildStep::OutputFormat::NormalMessage); + emit addOutput(tr("Configuration unchanged, skipping configure step."), OutputFormat::NormalMessage); emit finished(true); return; } diff --git a/src/plugins/conan/CMakeLists.txt b/src/plugins/conan/CMakeLists.txt new file mode 100644 index 00000000000..fd19c478755 --- /dev/null +++ b/src/plugins/conan/CMakeLists.txt @@ -0,0 +1,6 @@ +add_qtc_plugin(Conan + PLUGIN_DEPENDS Core ProjectExplorer + SOURCES + conaninstallstep.cpp conaninstallstep.h + conanplugin.cpp conanplugin.h +) diff --git a/src/plugins/conan/Conan.json.in b/src/plugins/conan/Conan.json.in new file mode 100644 index 00000000000..ac2cfcd52ef --- /dev/null +++ b/src/plugins/conan/Conan.json.in @@ -0,0 +1,20 @@ +{ + \"Name\" : \"Conan\", + \"Version\" : \"$$QTCREATOR_VERSION\", + \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\", + \"Vendor\" : \"Jochen Seemann\", + \"Copyright\" : \"(C) 2018 Jochen Seemann, (C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\", + \"License\" : [ \"Commercial Usage\", + \"\", + \"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt 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.\", + \"\", + \"GNU General Public License Usage\", + \"\", + \"Alternatively, this plugin 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 plugin. 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.\" + ], + \"Category\" : \"Utilities\", + \"Experimental\" : true, + \"Description\" : \"Conan integration.\", + \"Url\" : \"http://www.qt.io\", + $$dependencyList +} diff --git a/src/plugins/conan/conan.pro b/src/plugins/conan/conan.pro new file mode 100644 index 00000000000..6fc42a8a62e --- /dev/null +++ b/src/plugins/conan/conan.pro @@ -0,0 +1,8 @@ +include(../../qtcreatorplugin.pri) + +SOURCES += \ + conaninstallstep.cpp \ + conanplugin.cpp +HEADERS += \ + conaninstallstep.h \ + conanplugin.h diff --git a/src/plugins/conan/conan.qbs b/src/plugins/conan/conan.qbs new file mode 100644 index 00000000000..8ed2f131d29 --- /dev/null +++ b/src/plugins/conan/conan.qbs @@ -0,0 +1,19 @@ +import qbs 1.0 + +QtcPlugin { + name: "Conan" + + Depends { name: "Qt.widgets" } + Depends { name: "Utils" } + + Depends { name: "Core" } + Depends { name: "ProjectExplorer" } + + files: [ + "conanplugin.h", + "conanplugin.cpp", + "conaninstallstep.h", + "conaninstallstep.cpp" + ] +} + diff --git a/src/plugins/conan/conan_dependencies.pri b/src/plugins/conan/conan_dependencies.pri new file mode 100644 index 00000000000..e9c05fe2e58 --- /dev/null +++ b/src/plugins/conan/conan_dependencies.pri @@ -0,0 +1,6 @@ +QTC_PLUGIN_NAME = Conan +QTC_LIB_DEPENDS += \ + utils +QTC_PLUGIN_DEPENDS += \ + coreplugin \ + projectexplorer diff --git a/src/plugins/conan/conaninstallstep.cpp b/src/plugins/conan/conaninstallstep.cpp new file mode 100644 index 00000000000..d651a2cb877 --- /dev/null +++ b/src/plugins/conan/conaninstallstep.cpp @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Jochen Seemann +** 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 "conaninstallstep.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ProjectExplorer; +using namespace Utils; + +namespace ConanPackageManager { +namespace Internal { + +// ConanInstallStep + +class ConanInstallStep final : public AbstractProcessStep +{ + Q_DECLARE_TR_FUNCTIONS(ConanPackageManager::Internal::ConanInstallStep) + +public: + ConanInstallStep(BuildStepList *bsl, Id id); + +private: + bool init() final; + void setupOutputFormatter(OutputFormatter *formatter) final; +}; + +ConanInstallStep::ConanInstallStep(BuildStepList *bsl, Id id) + : AbstractProcessStep(bsl, id) +{ + setUseEnglishOutput(); + setDisplayName(ConanInstallStep::tr("Conan install")); + + auto conanFile = addAspect(); + conanFile->setSettingsKey("ConanPackageManager.InstallStep.ConanFile"); + conanFile->setFilePath(project()->projectDirectory() / "conanfile.txt"); + conanFile->setLabelText(tr("Conan file:")); + conanFile->setToolTip(tr("Enter location of conanfile.txt or conanfile.py")); + conanFile->setDisplayStyle(StringAspect::PathChooserDisplay); + conanFile->setExpectedKind(PathChooser::File); + + auto additionalArguments = addAspect(); + additionalArguments->setSettingsKey("ConanPackageManager.InstallStep.AdditionalArguments"); + additionalArguments->setLabelText(tr("Additional arguments:")); + additionalArguments->setDisplayStyle(StringAspect::LineEditDisplay); + + setCommandLineProvider([this, conanFile, additionalArguments] { + BuildConfiguration::BuildType bt = buildConfiguration()->buildType(); + const QString buildType = bt == BuildConfiguration::Release ? QString("Release") + : QString("Debug"); + CommandLine cmd("conan"); + cmd.addArgs({"install", "-s", "build_type=" + buildType, conanFile->value()}); + cmd.addArgs(additionalArguments->value(), CommandLine::Raw); + return cmd; + }); + + setSummaryUpdater([this]() -> QString { + QList tcList = ToolChainKitAspect::toolChains(target()->kit()); + if (tcList.isEmpty()) + return "" + ToolChainKitAspect::msgNoToolChainInTarget() + ""; + ProcessParameters param; + setupProcessParameters(¶m); + return param.summary(displayName()); + }); +} + +bool ConanInstallStep::init() +{ + if (!AbstractProcessStep::init()) + return false; + + const QList tcList = ToolChainKitAspect::toolChains(target()->kit()); + if (tcList.isEmpty()) { + emit addTask(Task::compilerMissingTask()); + emitFaultyConfigurationMessage(); + return false; + } + + return true; +} + +void ConanInstallStep::setupOutputFormatter(OutputFormatter *formatter) +{ + formatter->addLineParser(new GnuMakeParser()); + formatter->addLineParsers(kit()->createOutputParsers()); + formatter->addSearchDir(processParameters()->effectiveWorkingDirectory()); + AbstractProcessStep::setupOutputFormatter(formatter); +} + +// ConanInstallStepFactory + +ConanInstallStepFactory::ConanInstallStepFactory() +{ + registerStep("ConanPackageManager.InstallStep"); + setDisplayName(ConanInstallStep::tr("Run conan install")); +} + +} // Internal +} // ConanPackageManager diff --git a/src/plugins/conan/conaninstallstep.h b/src/plugins/conan/conaninstallstep.h new file mode 100644 index 00000000000..f4deb02dd4b --- /dev/null +++ b/src/plugins/conan/conaninstallstep.h @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Jochen Seemann +** 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 + +namespace ConanPackageManager { +namespace Internal { + +class ConanInstallStepFactory final : public ProjectExplorer::BuildStepFactory +{ +public: + ConanInstallStepFactory(); +}; + +} // namespace Internal +} // namespace ConanPackageManager diff --git a/src/plugins/conan/conanplugin.cpp b/src/plugins/conan/conanplugin.cpp new file mode 100644 index 00000000000..bf88c803fc7 --- /dev/null +++ b/src/plugins/conan/conanplugin.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Jochen Seemann +** 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 "conanplugin.h" +#include "conaninstallstep.h" + +#include +#include + +namespace ConanPackageManager { +namespace Internal { + +class ConanPluginRunData +{ +public: + ConanInstallStepFactory installStepFactory; +}; + +ConanPlugin::~ConanPlugin() +{ + delete m_runData; +} + +void ConanPlugin::extensionsInitialized() +{ } + +bool ConanPlugin::initialize(const QStringList &arguments, QString *errorString) +{ + Q_UNUSED(arguments) + Q_UNUSED(errorString) + + m_runData = new ConanPluginRunData; + + return true; +} + +} // namespace Internal +} // namespace ConanPackageManager diff --git a/src/plugins/conan/conanplugin.h b/src/plugins/conan/conanplugin.h new file mode 100644 index 00000000000..d77409ec972 --- /dev/null +++ b/src/plugins/conan/conanplugin.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Jochen Seemann +** 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 + +namespace ConanPackageManager { +namespace Internal { + +class ConanPluginRunData; + +class ConanPlugin final : public ExtensionSystem::IPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Conan.json") + + ~ConanPlugin() final; + + void extensionsInitialized() final; + bool initialize(const QStringList &arguments, QString *errorString) final; + + ConanPluginRunData *m_runData = nullptr; +}; + +} // namespace Internal +} // namespace ConanPackageManager diff --git a/src/plugins/debugger/debuggerprotocol.cpp b/src/plugins/debugger/debuggerprotocol.cpp index ccd834636c6..b62931705f7 100644 --- a/src/plugins/debugger/debuggerprotocol.cpp +++ b/src/plugins/debugger/debuggerprotocol.cpp @@ -89,104 +89,111 @@ void GdbMi::parseResultOrValue(const QChar *&from, const QChar *to) } } +// Reads one \ooo entity. +static bool parseOctalEscapedHelper(const QChar *&from, const QChar *to, QByteArray &buffer) +{ + if (to - from < 4) + return false; + if (*from != '\\') + return false; + + const char c1 = from[1].unicode(); + const char c2 = from[2].unicode(); + const char c3 = from[3].unicode(); + if (!isdigit(c1) || !isdigit(c2) || !isdigit(c3)) + return false; + + buffer += char((c1 - '0') * 64 + (c2 - '0') * 8 + (c3 - '0')); + from += 4; + return true; +} + +static bool parseHexEscapedHelper(const QChar *&from, const QChar *to, QByteArray &buffer) +{ + if (to - from < 4) + return false; + if (from[0]!= '\\') + return false; + if (from[1] != 'x') + return false; + + const char c1 = from[2].unicode(); + const char c2 = from[3].unicode(); + if (!isxdigit(c1) || !isxdigit(c2)) + return false; + + buffer += char(16 * fromhex(c1) + fromhex(c2)); + from += 4; + return true; +} + +static void parseSimpleEscape(const QChar *&from, const QChar *to, QString &result) +{ + if (from == to) { + qDebug() << "MI Parse Error, unterminated backslash escape"; + return; + } + + QChar c = *from++; + switch (c.unicode()) { + case 'a': result += '\a'; break; + case 'b': result += '\b'; break; + case 'f': result += '\f'; break; + case 'n': result += '\n'; break; + case 'r': result += '\r'; break; + case 't': result += '\t'; break; + case 'v': result += '\v'; break; + case '"': result += '"'; break; + case '\'': result += '\''; break; + case '\\': result += '\\'; break; + default: + qDebug() << "MI Parse Error, unrecognized backslash escape"; + } +} + +// Reads subsequent \123 or \x12 entities and converts to Utf8, +// *or* one escaped char, *or* one unescaped char. +static void parseCharOrEscape(const QChar *&from, const QChar *to, QString &result) +{ + QByteArray buffer; + while (parseOctalEscapedHelper(from, to, buffer)) + ; + while (parseHexEscapedHelper(from, to, buffer)) + ; + + if (!buffer.isEmpty()) + result.append(QString::fromUtf8(buffer)); + else if (*from == '\\') + parseSimpleEscape(++from, to, result); + else + result += *from++; +} + QString GdbMi::parseCString(const QChar *&from, const QChar *to) { - QString result; + if (to == from) + return QString(); + //qDebug() << "parseCString: " << QString(from, to - from); if (*from != '"') { qDebug() << "MI Parse Error, double quote expected"; ++from; // So we don't hang return QString(); } - const QChar *ptr = from; - ++ptr; - while (ptr < to) { - if (*ptr == '"') { - ++ptr; - result = QString(from + 1, ptr - from - 2); - break; - } - if (*ptr == '\\') { - ++ptr; - if (ptr == to) { - qDebug() << "MI Parse Error, unterminated backslash escape"; - from = ptr; // So we don't hang - return QString(); - } - } - ++ptr; - } - from = ptr; - int idx = result.indexOf('\\'); - if (idx >= 0) { - QChar *dst = result.data() + idx; - const QChar *src = dst + 1, *end = result.data() + result.length(); - do { - QChar c = *src++; - switch (c.unicode()) { - case 'a': *dst++ = '\a'; break; - case 'b': *dst++ = '\b'; break; - case 'f': *dst++ = '\f'; break; - case 'n': *dst++ = '\n'; break; - case 'r': *dst++ = '\r'; break; - case 't': *dst++ = '\t'; break; - case 'v': *dst++ = '\v'; break; - case '"': *dst++ = '"'; break; - case '\\': *dst++ = '\\'; break; - case 'x': { - c = *src++; - int chars = 0; - uchar prod = 0; - while (true) { - uchar val = fromhex(c.unicode()); - if (val == UCHAR_MAX) - break; - prod = prod * 16 + val; - if (++chars == 3 || src == end) - break; - c = *src++; - } - if (!chars) { - qDebug() << "MI Parse Error, unrecognized hex escape"; - return QString(); - } - *dst++ = prod; - break; - } - default: - { - int chars = 0; - uchar prod = 0; - forever { - if (c < '0' || c > '7') { - --src; - break; - } - prod = prod * 8 + c.unicode() - '0'; - if (++chars == 3 || src == end) - break; - c = *src++; - } - if (!chars) { - qDebug() << "MI Parse Error, unrecognized backslash escape"; - return QString(); - } - *dst++ = prod; - } - } - while (src != end) { - QChar c = *src++; - if (c == '\\') - break; - *dst++ = c; - } - } while (src != end); - *dst = 0; - result.truncate(dst - result.data()); + ++from; // Skip initial quote. + QString result; + result.reserve(to - from); + while (from < to) { + if (*from == '"') { + ++from; + return result; + } + parseCharOrEscape(from, to, result); } - return result; + qDebug() << "MI Parse Error, unfinished string"; + return QString(); } void GdbMi::parseValue(const QChar *&from, const QChar *to) diff --git a/src/plugins/designer/gotoslot_test.cpp b/src/plugins/designer/gotoslot_test.cpp index 8e06f6e2d05..64d5bc4295a 100644 --- a/src/plugins/designer/gotoslot_test.cpp +++ b/src/plugins/designer/gotoslot_test.cpp @@ -195,7 +195,7 @@ public: const auto cppDocumentParser = BuiltinEditorDocumentParser::get(cppFile); QVERIFY(cppDocumentParser); const Document::Ptr cppDocument = cppDocumentParser->document(); - QCOMPARE(cppDocument->editorRevision(), 2); + QVERIFY(cppDocument->editorRevision() >= 2); QVERIFY(checkDiagsnosticMessages(cppDocument)); const auto hDocumentParser = BuiltinEditorDocumentParser::get(hFile); diff --git a/src/plugins/ios/iosbuildstep.cpp b/src/plugins/ios/iosbuildstep.cpp index c529bf37571..dc7c99cf8f7 100644 --- a/src/plugins/ios/iosbuildstep.cpp +++ b/src/plugins/ios/iosbuildstep.cpp @@ -170,13 +170,9 @@ bool IosBuildStep::init() if (!AbstractProcessStep::init()) return false; - BuildConfiguration *bc = buildConfiguration(); - ToolChain *tc = ToolChainKitAspect::cxxToolChain(kit()); - if (!tc) + if (!tc) { emit addTask(Task::compilerMissingTask()); - - if (!bc || !tc) { emitFaultyConfigurationMessage(); return false; } diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro index 12d87206ab3..abf6e510c08 100644 --- a/src/plugins/plugins.pro +++ b/src/plugins/plugins.pro @@ -66,7 +66,8 @@ SUBDIRS = \ webassembly \ mcusupport \ marketplace \ - incredibuild + incredibuild \ + conan qtHaveModule(serialport) { SUBDIRS += serialterminal diff --git a/src/plugins/plugins.qbs b/src/plugins/plugins.qbs index 5ef58f33ebf..e2310ee1ff5 100644 --- a/src/plugins/plugins.qbs +++ b/src/plugins/plugins.qbs @@ -23,6 +23,7 @@ Project { "cmakeprojectmanager/cmakeprojectmanager.qbs", "mesonprojectmanager/mesonprojectmanager.qbs", "compilationdatabaseprojectmanager/compilationdatabaseprojectmanager.qbs", + "conan/conan.qbs", "coreplugin/coreplugin.qbs", "coreplugin/images/logo/logo.qbs", "cpaster/cpaster.qbs", diff --git a/src/plugins/projectexplorer/panelswidget.cpp b/src/plugins/projectexplorer/panelswidget.cpp index 488390e8226..1fd98512dc9 100644 --- a/src/plugins/projectexplorer/panelswidget.cpp +++ b/src/plugins/projectexplorer/panelswidget.cpp @@ -55,18 +55,18 @@ const int PANEL_LEFT_MARGIN = 70; // PanelsWidget /// -PanelsWidget::PanelsWidget(QWidget *parent) : QWidget(parent) +PanelsWidget::PanelsWidget(QWidget *parent) + : QWidget(parent), m_splitter(new Core::MiniSplitter(this)) { - const auto splitter = new Core::MiniSplitter(this); m_root = new QWidget(nullptr); m_root->setFocusPolicy(Qt::NoFocus); m_root->setContentsMargins(0, 0, 40, 0); - splitter->addWidget(m_root); - splitter->addWidget(new QWidget); - splitter->setStretchFactor(1, 100); // Force root widget to its minimum size initially + m_splitter->addWidget(m_root); + m_splitter->addWidget(new QWidget); + m_splitter->setStretchFactor(1, 100); // Force root widget to its minimum size initially const auto scroller = new QScrollArea(this); - scroller->setWidget(splitter); + scroller->setWidget(m_splitter); scroller->setFrameStyle(QFrame::NoFrame); scroller->setWidgetResizable(true); scroller->setFocusPolicy(Qt::NoFocus); @@ -153,4 +153,14 @@ void PanelsWidget::addPropertiesPanel(const QString &displayName, const QIcon &i m_layout->addWidget(widget, widgetRow, 0, 1, 2); } +QByteArray PanelsWidget::saveSplitterState() const +{ + return m_splitter->saveState().toHex(); +} + +void PanelsWidget::loadSplitterState(const QByteArray &state) +{ + m_splitter->restoreState(QByteArray::fromHex(state)); +} + } // ProjectExplorer diff --git a/src/plugins/projectexplorer/panelswidget.h b/src/plugins/projectexplorer/panelswidget.h index 1be7723aaad..35e57351f11 100644 --- a/src/plugins/projectexplorer/panelswidget.h +++ b/src/plugins/projectexplorer/panelswidget.h @@ -34,6 +34,8 @@ class QGridLayout; class QIcon; QT_END_NAMESPACE +namespace Core { class MiniSplitter; } + namespace ProjectExplorer { class PROJECTEXPLORER_EXPORT PanelsWidget : public QWidget @@ -49,8 +51,12 @@ public: void addPropertiesPanel(const QString &displayName, const QIcon &icon, QWidget *widget); + QByteArray saveSplitterState() const; + void loadSplitterState(const QByteArray &state); + private: QGridLayout *m_layout; + Core::MiniSplitter * const m_splitter; QWidget *m_root; }; diff --git a/src/plugins/projectexplorer/targetsettingspanel.cpp b/src/plugins/projectexplorer/targetsettingspanel.cpp index e5a25c0b5fc..5d6970710c0 100644 --- a/src/plugins/projectexplorer/targetsettingspanel.cpp +++ b/src/plugins/projectexplorer/targetsettingspanel.cpp @@ -613,13 +613,32 @@ public: QWidget *panel() const { if (!m_panel) { - m_panel = (m_subIndex == RunPage) - ? new PanelsWidget(RunSettingsWidget::tr("Run Settings"), - QIcon(":/projectexplorer/images/RunSettings.png"), - new RunSettingsWidget(target())) - : new PanelsWidget(QCoreApplication::translate("BuildSettingsPanel", "Build Settings"), - QIcon(":/projectexplorer/images/BuildSettings.png"), - new BuildSettingsWidget(target())); + QString splitterStateKey = "PanelSplitterState:" + + m_project->projectFilePath().toString() + ':'; + if (m_subIndex == RunPage) { + splitterStateKey += "Run"; + m_panel = new PanelsWidget(RunSettingsWidget::tr("Run Settings"), + QIcon(":/projectexplorer/images/RunSettings.png"), + new RunSettingsWidget(target())); + } else { + splitterStateKey += "Build"; + m_panel = new PanelsWidget(QCoreApplication::translate("BuildSettingsPanel", "Build Settings"), + QIcon(":/projectexplorer/images/BuildSettings.png"), + new BuildSettingsWidget(target())); + } + const auto panel = qobject_cast(m_panel.data()); + const auto loadSplitterValue = [panel, splitterStateKey] { + const QByteArray splitterState = SessionManager::value(splitterStateKey).toByteArray(); + if (!splitterState.isEmpty()) + panel->loadSplitterState(splitterState); + }; + loadSplitterValue(); + QObject::connect(SessionManager::instance(), &SessionManager::aboutToSaveSession, + panel, [panel, splitterStateKey] { + SessionManager::setValue(splitterStateKey, panel->saveSplitterState()); + }); + QObject::connect(SessionManager::instance(), &SessionManager::sessionLoaded, + panel, loadSplitterValue); } return m_panel; } diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index aec12dc75a9..0fdd28182cc 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -163,6 +163,8 @@ enum { NExtraSelectionKinds = 12 }; using TransformationMethod = QString(const QString &); using ListTransformationMethod = void(QStringList &); +static constexpr char dropProperty[] = "dropProp"; + class LineColumnLabel : public FixedSizeClickLabel { Q_OBJECT @@ -714,7 +716,7 @@ public: // block selection mode bool m_inBlockSelectionMode = false; QString copyBlockSelection(); - void insertIntoBlockSelection(const QString &text = QString()); + void insertIntoBlockSelection(const QString &text = QString(), const bool selectText = false); void setCursorToColumn(QTextCursor &cursor, int column, QTextCursor::MoveMode moveMode = QTextCursor::MoveAnchor); void removeBlockSelection(); @@ -3802,7 +3804,7 @@ void TextEditorWidgetPrivate::setCursorToColumn(QTextCursor &cursor, int column, cursor.block().text(), column), moveMode); } -void TextEditorWidgetPrivate::insertIntoBlockSelection(const QString &text) +void TextEditorWidgetPrivate::insertIntoBlockSelection(const QString &text, const bool selectText) { // TODO: add autocompleter support QTextCursor cursor = q->textCursor(); @@ -3828,6 +3830,7 @@ void TextEditorWidgetPrivate::insertIntoBlockSelection(const QString &text) int positionBlock = m_blockSelection.positionBlock; int anchorBlock = m_blockSelection.anchorBlock; int column = m_blockSelection.positionColumn; + const int anchorColumn = m_blockSelection.anchorColumn; const QTextBlock &firstBlock = m_document->document()->findBlockByNumber(m_blockSelection.firstBlockNumber()); @@ -3880,7 +3883,10 @@ void TextEditorWidgetPrivate::insertIntoBlockSelection(const QString &text) cursor.endEditBlock(); column += textLength; - m_blockSelection.fromPostition(positionBlock, column, anchorBlock, column); + m_blockSelection.fromPostition(positionBlock, + column, + anchorBlock, + selectText ? anchorColumn : column); q->doSetTextCursor(m_blockSelection.selection(m_document.data()), true); } @@ -7722,8 +7728,9 @@ void TextEditorWidget::insertFromMimeData(const QMimeData *source) if (d->m_codeAssistant.hasContext()) d->m_codeAssistant.destroyContext(); + const bool selectInsertedText = source->property(dropProperty).toBool(); if (d->m_inBlockSelectionMode) { - d->insertIntoBlockSelection(text); + d->insertIntoBlockSelection(text, selectInsertedText); return; } @@ -7738,7 +7745,16 @@ void TextEditorWidget::insertFromMimeData(const QMimeData *source) QTextCursor cursor = textCursor(); if (!tps.m_autoIndent) { cursor.beginEditBlock(); - cursor.insertText(text); + if (selectInsertedText) { + const int anchor = cursor.position(); + cursor.insertText(text); + const int pos = cursor.position(); + cursor.endEditBlock(); + cursor.setPosition(anchor); + cursor.setPosition(pos, QTextCursor::KeepAnchor); + } else { + cursor.insertText(text); + } cursor.endEditBlock(); setTextCursor(cursor); return; @@ -7768,6 +7784,9 @@ void TextEditorWidget::insertFromMimeData(const QMimeData *source) int cursorPosition = cursor.position(); cursor.insertText(text); + const QTextCursor endCursor = cursor; + QTextCursor startCursor = endCursor; + startCursor.setPosition(cursorPosition); int reindentBlockEnd = cursor.blockNumber() - (hasFinalNewline?1:0); @@ -7788,9 +7807,33 @@ void TextEditorWidget::insertFromMimeData(const QMimeData *source) } cursor.endEditBlock(); + if (selectInsertedText) { + cursor.setPosition(startCursor.position()); + cursor.setPosition(endCursor.position(), QTextCursor::KeepAnchor); + } setTextCursor(cursor); } +void TextEditorWidget::dropEvent(QDropEvent *e) +{ + const QMimeData *mime = e->mimeData(); + if (mime && (mime->hasText() || mime->hasHtml())) { + QMimeData *mimeOverwrite = duplicateMimeData(mime); + mimeOverwrite->setProperty(dropProperty, true); + auto dropOverwrite = new QDropEvent(e->pos(), + e->possibleActions(), + mimeOverwrite, + e->mouseButtons(), + e->keyboardModifiers()); + QPlainTextEdit::dropEvent(dropOverwrite); + e->setAccepted(dropOverwrite->isAccepted()); + delete dropOverwrite; + delete mimeOverwrite; + } else { + QPlainTextEdit::dropEvent(e); + } +} + QMimeData *TextEditorWidget::duplicateMimeData(const QMimeData *source) { Q_ASSERT(source); diff --git a/src/plugins/texteditor/texteditor.h b/src/plugins/texteditor/texteditor.h index 1e4ab92497c..6e693e36978 100644 --- a/src/plugins/texteditor/texteditor.h +++ b/src/plugins/texteditor/texteditor.h @@ -530,6 +530,7 @@ protected: QMimeData *createMimeDataFromSelection() const override; bool canInsertFromMimeData(const QMimeData *source) const override; void insertFromMimeData(const QMimeData *source) override; + void dropEvent(QDropEvent *e) override; virtual QString plainTextFromSelection(const QTextCursor &cursor) const; static QString convertToPlainText(const QString &txt); diff --git a/tests/auto/debugger/CMakeLists.txt b/tests/auto/debugger/CMakeLists.txt index 66900623c34..71f48e399c1 100644 --- a/tests/auto/debugger/CMakeLists.txt +++ b/tests/auto/debugger/CMakeLists.txt @@ -32,6 +32,15 @@ add_qtc_test(tst_debugger_gdb "${DEBUGGERDIR}/debuggerprotocol.cpp" "${DEBUGGERDIR}/debuggerprotocol.h" ) +add_qtc_test(tst_debugger_protocol + DEPENDS Qt5::Network Utils + INCLUDES + "${DEBUGGERDIR}" + SOURCES + "${DEBUGGERDIR}/debuggerprotocol.cpp" "${DEBUGGERDIR}/debuggerprotocol.h" + tst_protocol.cpp +) + add_qtc_test(tst_debugger_offsets DEPENDS Qt5::CorePrivate INCLUDES "${DEBUGGERDIR}" diff --git a/tests/auto/debugger/debugger.pro b/tests/auto/debugger/debugger.pro index 284db38b0c8..6d24b5f7999 100644 --- a/tests/auto/debugger/debugger.pro +++ b/tests/auto/debugger/debugger.pro @@ -12,3 +12,4 @@ SUBDIRS += simplifytypes.pro SUBDIRS += dumpers.pro SUBDIRS += disassembler.pro SUBDIRS += offsets.pro +SUBDIRS += protocol.pro diff --git a/tests/auto/debugger/debugger.qbs b/tests/auto/debugger/debugger.qbs index 456c604b261..e4ae1051023 100644 --- a/tests/auto/debugger/debugger.qbs +++ b/tests/auto/debugger/debugger.qbs @@ -7,6 +7,7 @@ Project { "disassembler.qbs", "dumpers.qbs", "gdb.qbs", + "protocol.qbs", "offsets.qbs", "simplifytypes.qbs", ] diff --git a/tests/auto/debugger/protocol.pro b/tests/auto/debugger/protocol.pro new file mode 100644 index 00000000000..1008ba3d930 --- /dev/null +++ b/tests/auto/debugger/protocol.pro @@ -0,0 +1,46 @@ +QT = core network + +msvc: QTC_LIB_DEPENDS += utils +include(../qttest.pri) + +DEBUGGERDIR = $$IDE_SOURCE_TREE/src/plugins/debugger +UTILSDIR = $$IDE_SOURCE_TREE/src/libs/utils + +INCLUDEPATH += $$DEBUGGERDIR + +SOURCES += \ + tst_protocol.cpp \ + $$DEBUGGERDIR/debuggerprotocol.cpp + +HEADERS += \ + $$DEBUGGERDIR/debuggerprotocol.h + +!msvc { + SOURCES += \ + $$UTILSDIR/environment.cpp \ + $$UTILSDIR/fileutils.cpp \ + $$UTILSDIR/hostosinfo.cpp \ + $$UTILSDIR/namevaluedictionary.cpp \ + $$UTILSDIR/namevalueitem.cpp \ + $$UTILSDIR/qtcassert.cpp \ + $$UTILSDIR/qtcprocess.cpp \ + $$UTILSDIR/processhandle.cpp \ + $$UTILSDIR/savefile.cpp \ + + HEADERS += \ + $$UTILSDIR/environment.h \ + $$UTILSDIR/fileutils.h \ + $$UTILSDIR/hostosinfo.h \ + $$UTILSDIR/namevaluedictionary.h \ + $$UTILSDIR/namevalueitem.h \ + $$UTILSDIR/qtcassert.h \ + $$UTILSDIR/qtcprocess.h \ + $$UTILSDIR/processhandle.h \ + $$UTILSDIR/savefile.h \ + + macos: { + HEADERS += $$UTILSDIR/fileutils_mac.h + OBJECTIVE_SOURCES += $$UTILSDIR/fileutils_mac.mm + LIBS += -framework Foundation + } +} diff --git a/tests/auto/debugger/protocol.qbs b/tests/auto/debugger/protocol.qbs new file mode 100644 index 00000000000..ca1b0d8bece --- /dev/null +++ b/tests/auto/debugger/protocol.qbs @@ -0,0 +1,17 @@ +import qbs + +QtcAutotest { + name: "debugger protocol autotest" + Depends { name: "Utils" } + Depends { name: "Qt.network" } // For QHostAddress + Group { + name: "Sources from Debugger plugin" + prefix: project.debuggerDir + files: "debuggerprotocol.cpp" + } + Group { + name: "Test sources" + files: "tst_protocol.cpp" + } + cpp.includePaths: base.concat([project.debuggerDir]) +} diff --git a/tests/auto/debugger/tst_protocol.cpp b/tests/auto/debugger/tst_protocol.cpp new file mode 100644 index 00000000000..6d6f1190537 --- /dev/null +++ b/tests/auto/debugger/tst_protocol.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 + +#include + +//TESTED_COMPONENT=src/plugins/debugger + +class tst_protocol : public QObject +{ + Q_OBJECT + +public: + tst_protocol() {} + +private slots: + void parseCString(); + void parseCString_data(); +}; + +void tst_protocol::parseCString() +{ + QFETCH(QString, input); + QFETCH(QString, expected); + + const QChar *from = input.begin(); + const QChar *to = input.end(); + QString parsed = Debugger::Internal::GdbMi::parseCString(from, to); + + QCOMPARE(parsed, expected); +} + +void tst_protocol::parseCString_data() +{ + QTest::addColumn("input"); + QTest::addColumn("expected"); + + QTest::newRow("empty") + << "" + << ""; + + QTest::newRow("unquoted") + << "irgendwas" + << ""; + + QTest::newRow("plain") + << R"("plain")" + << "plain"; + + // This is expected to throw several warnings + // "MI Parse Error, unrecognized backslash escape" + QChar escapes[] = {'\a', '\b', '\f', '\n', '\r', '\t', '\v', '"', '\'', '\\'}; + QTest::newRow("escaped") + << R"("\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\y\z\"\'\\")" + << QString(escapes, sizeof(escapes)/sizeof(escapes[0])); + + QTest::newRow("octal") + << R"("abc\303\244\303\251def\303\261")" + << R"(abcäédefñ)"; + + QTest::newRow("hex") + << R"("abc\xc3\xa4\xc3\xa9def\xc3\xb1")" + << R"(abcäédefñ)"; +} + +QTEST_APPLESS_MAIN(tst_protocol); + +#include "tst_protocol.moc" +