diff options
Diffstat (limited to 'src/libs')
59 files changed, 2302 insertions, 1321 deletions
diff --git a/src/libs/3rdparty/cplusplus/Lexer.cpp b/src/libs/3rdparty/cplusplus/Lexer.cpp index 079ae0ca6e..d1858f007c 100644 --- a/src/libs/3rdparty/cplusplus/Lexer.cpp +++ b/src/libs/3rdparty/cplusplus/Lexer.cpp @@ -954,7 +954,8 @@ void Lexer::scanNumericLiteral(Token *tok) yyinp(); while (std::isdigit(_yychar) || (_yychar >= 'a' && _yychar <= 'f') || - (_yychar >= 'A' && _yychar <= 'F')) { + (_yychar >= 'A' && _yychar <= 'F') || + ((_yychar == '\'') && _languageFeatures.cxx14Enabled)) { yyinp(); } if (!scanOptionalIntegerSuffix()) @@ -962,7 +963,8 @@ void Lexer::scanNumericLiteral(Token *tok) goto theEnd; } else if (_yychar == 'b' || _yychar == 'B') { // see n3472 yyinp(); - while (_yychar == '0' || _yychar == '1') + while (_yychar == '0' || _yychar == '1' || + ((_yychar == '\'') && _languageFeatures.cxx14Enabled)) yyinp(); if (!scanOptionalIntegerSuffix()) scanOptionalUserDefinedLiteral(tok); @@ -970,7 +972,8 @@ void Lexer::scanNumericLiteral(Token *tok) } else if (_yychar >= '0' && _yychar <= '7') { do { yyinp(); - } while (_yychar >= '0' && _yychar <= '7'); + } while ((_yychar >= '0' && _yychar <= '7') || + ((_yychar == '\'') && _languageFeatures.cxx14Enabled)); if (!scanOptionalIntegerSuffix()) scanOptionalUserDefinedLiteral(tok); goto theEnd; @@ -989,7 +992,8 @@ void Lexer::scanNumericLiteral(Token *tok) if (scanExponentPart() && !scanOptionalFloatingSuffix()) scanOptionalUserDefinedLiteral(tok); break; - } else if (std::isdigit(_yychar)) { + } else if (std::isdigit(_yychar) || + ((_yychar == '\'') && _languageFeatures.cxx14Enabled)) { yyinp(); } else { if (!scanOptionalIntegerSuffix()) diff --git a/src/libs/3rdparty/cplusplus/Token.h b/src/libs/3rdparty/cplusplus/Token.h index 67378b28a3..36a893efff 100644 --- a/src/libs/3rdparty/cplusplus/Token.h +++ b/src/libs/3rdparty/cplusplus/Token.h @@ -437,6 +437,7 @@ struct LanguageFeatures unsigned int qtKeywordsEnabled : 1; // If Qt is used but QT_NO_KEYWORDS defined unsigned int cxxEnabled : 1; unsigned int cxx11Enabled : 1; + unsigned int cxx14Enabled : 1; unsigned int objCEnabled : 1; unsigned int c99Enabled : 1; }; diff --git a/src/libs/cplusplus/MatchingText.cpp b/src/libs/cplusplus/MatchingText.cpp index 59440813a7..5aea683f1e 100644 --- a/src/libs/cplusplus/MatchingText.cpp +++ b/src/libs/cplusplus/MatchingText.cpp @@ -145,6 +145,7 @@ static LanguageFeatures languageFeatures() features.qtKeywordsEnabled = false; features.qtMocRunEnabled = false; features.cxx11Enabled = true; + features.cxx14Enabled = true; features.cxxEnabled = true; features.c99Enabled = true; features.objCEnabled = true; diff --git a/src/libs/extensionsystem/pluginmanager.cpp b/src/libs/extensionsystem/pluginmanager.cpp index 6fbda8e4ec..9ef7d8cdc7 100644 --- a/src/libs/extensionsystem/pluginmanager.cpp +++ b/src/libs/extensionsystem/pluginmanager.cpp @@ -49,6 +49,7 @@ #include <utils/algorithm.h> #include <utils/benchmarker.h> #include <utils/executeondestruction.h> +#include <utils/fileutils.h> #include <utils/hostosinfo.h> #include <utils/mimetypes/mimedatabase.h> #include <utils/qtcassert.h> @@ -413,10 +414,10 @@ static QString filled(const QString &s, int min) QString PluginManager::systemInformation() const { QString result; - const QString qtdiagBinary = HostOsInfo::withExecutableSuffix( - QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qtdiag"); + CommandLine qtDiag(FilePath::fromString(HostOsInfo::withExecutableSuffix( + QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qtdiag"))); SynchronousProcess qtdiagProc; - const SynchronousProcessResponse response = qtdiagProc.runBlocking(qtdiagBinary, QStringList()); + const SynchronousProcessResponse response = qtdiagProc.runBlocking(qtDiag); if (response.result == SynchronousProcessResponse::Finished) result += response.allOutput() + "\n"; result += "Plugin information:\n\n"; diff --git a/src/libs/languageserverprotocol/clientcapabilities.cpp b/src/libs/languageserverprotocol/clientcapabilities.cpp index f73c4ee04c..1f79ffae0d 100644 --- a/src/libs/languageserverprotocol/clientcapabilities.cpp +++ b/src/libs/languageserverprotocol/clientcapabilities.cpp @@ -76,23 +76,24 @@ bool TextDocumentClientCapabilities::SynchronizationCapabilities::isValid(QStrin bool TextDocumentClientCapabilities::isValid(QStringList *error) const { return checkOptional<SynchronizationCapabilities>(error, synchronizationKey) - && checkOptional<CompletionCapabilities>(error, completionKey) - && checkOptional<HoverCapabilities>(error, hoverKey) - && checkOptional<SignatureHelpCapabilities>(error, signatureHelpKey) - && checkOptional<DynamicRegistrationCapabilities>(error, referencesKey) - && checkOptional<DynamicRegistrationCapabilities>(error, documentHighlightKey) - && checkOptional<SymbolCapabilities>(error, documentSymbolKey) - && checkOptional<DynamicRegistrationCapabilities>(error, formattingKey) - && checkOptional<DynamicRegistrationCapabilities>(error, rangeFormattingKey) - && checkOptional<DynamicRegistrationCapabilities>(error, onTypeFormattingKey) - && checkOptional<DynamicRegistrationCapabilities>(error, definitionKey) - && checkOptional<DynamicRegistrationCapabilities>(error, typeDefinitionKey) - && checkOptional<DynamicRegistrationCapabilities>(error, implementationKey) - && checkOptional<CodeActionCapabilities>(error, codeActionKey) - && checkOptional<DynamicRegistrationCapabilities>(error, codeLensKey) - && checkOptional<DynamicRegistrationCapabilities>(error, documentLinkKey) - && checkOptional<DynamicRegistrationCapabilities>(error, colorProviderKey) - && checkOptional<DynamicRegistrationCapabilities>(error, renameKey); + && checkOptional<CompletionCapabilities>(error, completionKey) + && checkOptional<HoverCapabilities>(error, hoverKey) + && checkOptional<SignatureHelpCapabilities>(error, signatureHelpKey) + && checkOptional<DynamicRegistrationCapabilities>(error, referencesKey) + && checkOptional<DynamicRegistrationCapabilities>(error, documentHighlightKey) + && checkOptional<SymbolCapabilities>(error, documentSymbolKey) + && checkOptional<DynamicRegistrationCapabilities>(error, formattingKey) + && checkOptional<DynamicRegistrationCapabilities>(error, rangeFormattingKey) + && checkOptional<DynamicRegistrationCapabilities>(error, onTypeFormattingKey) + && checkOptional<DynamicRegistrationCapabilities>(error, definitionKey) + && checkOptional<DynamicRegistrationCapabilities>(error, typeDefinitionKey) + && checkOptional<DynamicRegistrationCapabilities>(error, implementationKey) + && checkOptional<CodeActionCapabilities>(error, codeActionKey) + && checkOptional<DynamicRegistrationCapabilities>(error, codeLensKey) + && checkOptional<DynamicRegistrationCapabilities>(error, documentLinkKey) + && checkOptional<DynamicRegistrationCapabilities>(error, colorProviderKey) + && checkOptional<DynamicRegistrationCapabilities>(error, renameKey) + && checkOptional<SemanticHighlightingCapabilities>(error, semanticHighlightingCapabilitiesKey); } bool SymbolCapabilities::isValid(QStringList *error) const diff --git a/src/libs/languageserverprotocol/clientcapabilities.h b/src/libs/languageserverprotocol/clientcapabilities.h index 3664007dd1..ec1bdd25b5 100644 --- a/src/libs/languageserverprotocol/clientcapabilities.h +++ b/src/libs/languageserverprotocol/clientcapabilities.h @@ -120,6 +120,26 @@ public: { insert(synchronizationKey, synchronization); } void clearSynchronization() { remove(synchronizationKey); } + class LANGUAGESERVERPROTOCOL_EXPORT SemanticHighlightingCapabilities : public JsonObject + { + public: + using JsonObject::JsonObject; + + bool semanticHighlighting() const { return typedValue<bool>(semanticHighlightingKey); } + void setSemanticHighlighting(bool semanticHighlighting) + { insert(semanticHighlightingKey, semanticHighlighting); } + + bool isValid(QStringList *error) const override + { return check<bool>(error, semanticHighlightingKey); } + }; + + Utils::optional<SemanticHighlightingCapabilities> semanticHighlightingCapabilities() const + { return optionalValue<SemanticHighlightingCapabilities>(semanticHighlightingCapabilitiesKey); } + void setSemanticHighlightingCapabilities( + const SemanticHighlightingCapabilities &semanticHighlightingCapabilities) + { insert(semanticHighlightingCapabilitiesKey, semanticHighlightingCapabilities); } + void clearSemanticHighlightingCapabilities() { remove(semanticHighlightingCapabilitiesKey); } + class LANGUAGESERVERPROTOCOL_EXPORT CompletionCapabilities : public DynamicRegistrationCapabilities { public: diff --git a/src/libs/languageserverprotocol/jsonkeys.h b/src/libs/languageserverprotocol/jsonkeys.h index e3673085eb..4c4ee054ab 100644 --- a/src/libs/languageserverprotocol/jsonkeys.h +++ b/src/libs/languageserverprotocol/jsonkeys.h @@ -130,6 +130,7 @@ constexpr char labelKey[] = "label"; constexpr char languageIdKey[] = "languageId"; constexpr char languageKey[] = "language"; constexpr char lineKey[] = "line"; +constexpr char linesKey[] = "lines"; constexpr char locationKey[] = "location"; constexpr char messageKey[] = "message"; constexpr char methodKey[] = "method"; @@ -166,8 +167,11 @@ constexpr char rootUriKey[] = "rootUri"; constexpr char saveKey[] = "save"; constexpr char schemeKey[] = "scheme"; constexpr char scopeUriKey[] = "scopeUri"; +constexpr char scopesKey[] = "scopes"; constexpr char sectionKey[] = "section"; constexpr char selectionRangeKey[] = "selectionRange"; +constexpr char semanticHighlightingKey[] = "semanticHighlighting"; +constexpr char semanticHighlightingCapabilitiesKey[] = "semanticHighlightingCapabilities"; constexpr char settingsKey[] = "settings"; constexpr char severityKey[] = "severity"; constexpr char signatureHelpKey[] = "signatureHelp"; @@ -190,6 +194,7 @@ constexpr char textDocumentSyncKey[] = "textDocumentSync"; constexpr char textEditKey[] = "textEdit"; constexpr char textKey[] = "text"; constexpr char titleKey[] = "title"; +constexpr char tokensKey[] = "tokens"; constexpr char traceKey[] = "trace"; constexpr char triggerCharacterKey[] = "triggerCharacter"; constexpr char triggerCharactersKey[] = "triggerCharacters"; diff --git a/src/libs/languageserverprotocol/languagefeatures.cpp b/src/libs/languageserverprotocol/languagefeatures.cpp index cfb4cc6fab..f66b6b7e16 100644 --- a/src/libs/languageserverprotocol/languagefeatures.cpp +++ b/src/libs/languageserverprotocol/languagefeatures.cpp @@ -48,6 +48,7 @@ constexpr const char DocumentRangeFormattingRequest::methodName[]; constexpr const char DocumentOnTypeFormattingRequest::methodName[]; constexpr const char RenameRequest::methodName[]; constexpr const char SignatureHelpRequest::methodName[]; +constexpr const char SemanticHighlightNotification::methodName[]; HoverContent LanguageServerProtocol::Hover::content() const { @@ -441,4 +442,59 @@ bool CodeAction::isValid(QStringList *error) const && checkOptional<Command>(error, commandKey); } +Utils::optional<QList<SemanticHighlightToken>> SemanticHighlightingInformation::tokens() const +{ + QList<SemanticHighlightToken> resultTokens; + + const QByteArray tokensByteArray = QByteArray::fromBase64( + typedValue<QString>(tokensKey).toLocal8Bit()); + constexpr int tokensByteSize = 8; + int index = 0; + while (index + tokensByteSize <= tokensByteArray.size()) { + resultTokens << SemanticHighlightToken(tokensByteArray.mid(index, tokensByteSize)); + index += tokensByteSize; + } + return Utils::make_optional(resultTokens); +} + +void SemanticHighlightingInformation::setTokens(const QList<SemanticHighlightToken> &tokens) +{ + QByteArray byteArray; + byteArray.reserve(8 * tokens.size()); + for (const SemanticHighlightToken &token : tokens) + token.appendToByteArray(byteArray); + insert(tokensKey, QString::fromLocal8Bit(byteArray.toBase64())); +} + +SemanticHighlightToken::SemanticHighlightToken(const QByteArray &token) +{ + QTC_ASSERT(token.size() == 8, return ); + character = ( quint32(token.at(0)) << 24 + | quint32(token.at(1)) << 16 + | quint32(token.at(2)) << 8 + | quint32(token.at(3))); + + length = quint16(token.at(4) << 8 | token.at(5)); + + scope = quint16(token.at(6) << 8 | token.at(7)); +} + +void SemanticHighlightToken::appendToByteArray(QByteArray &byteArray) const +{ + byteArray.append(char((character & 0xff000000) >> 24)); + byteArray.append(char((character & 0x00ff0000) >> 16)); + byteArray.append(char((character & 0x0000ff00) >> 8)); + byteArray.append(char((character & 0x000000ff))); + byteArray.append(char((length & 0xff00) >> 8)); + byteArray.append(char((length & 0x00ff))); + byteArray.append(char((scope & 0xff00) >> 8)); + byteArray.append(char((scope & 0x00ff))); +} + +bool SemanticHighlightingParams::isValid(QStringList *error) const +{ + return check<VersionedTextDocumentIdentifier>(error, textDocumentKey) + && checkArray<SemanticHighlightingInformation>(error, linesKey); +} + } // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/languagefeatures.h b/src/libs/languageserverprotocol/languagefeatures.h index ddd732718a..bb98d7f8bd 100644 --- a/src/libs/languageserverprotocol/languagefeatures.h +++ b/src/libs/languageserverprotocol/languagefeatures.h @@ -806,4 +806,60 @@ public: constexpr static const char methodName[] = "textDocument/rename"; }; +class LANGUAGESERVERPROTOCOL_EXPORT SemanticHighlightToken +{ +public: + // Just accepts token with 8 bytes + SemanticHighlightToken(const QByteArray &token); + SemanticHighlightToken() = default; + + void appendToByteArray(QByteArray &byteArray) const; + + quint32 character = 0; + quint16 length = 0; + quint16 scope = 0; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticHighlightingInformation : public JsonObject +{ +public: + using JsonObject::JsonObject; + + int line() const { return typedValue<int>(lineKey); } + void setLine(int line) { insert(lineKey, line); } + + Utils::optional<QList<SemanticHighlightToken>> tokens() const; + void setTokens(const QList<SemanticHighlightToken> &tokens); + void clearTokens() { remove(tokensKey); } + + bool isValid(QStringList *error) const override + { return check<int>(error, lineKey) && checkOptional<QString>(error, tokensKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticHighlightingParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + VersionedTextDocumentIdentifier textDocument() const + { return typedValue<VersionedTextDocumentIdentifier>(textDocumentKey); } + void setTextDocument(const VersionedTextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + QList<SemanticHighlightingInformation> lines() const + { return array<SemanticHighlightingInformation>(linesKey); } + void setLines(const QList<SemanticHighlightingInformation> &lines) + { insertArray(linesKey, lines); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticHighlightNotification + : public Notification<SemanticHighlightingParams> +{ +public: + using Notification::Notification; + constexpr static const char methodName[] = "textDocument/semanticHighlighting"; +}; + } // namespace LanguageClient diff --git a/src/libs/languageserverprotocol/servercapabilities.cpp b/src/libs/languageserverprotocol/servercapabilities.cpp index 6ebf4f431f..f9dce15b35 100644 --- a/src/libs/languageserverprotocol/servercapabilities.cpp +++ b/src/libs/languageserverprotocol/servercapabilities.cpp @@ -132,7 +132,8 @@ bool ServerCapabilities::isValid(QStringList *error) const && checkOptional<DocumentLinkOptions>(error, documentLinkProviderKey) && checkOptional<TextDocumentRegistrationOptions>(error, colorProviderKey) && checkOptional<ExecuteCommandOptions>(error, executeCommandProviderKey) - && checkOptional<WorkspaceServerCapabilities>(error, workspaceKey); + && checkOptional<WorkspaceServerCapabilities>(error, workspaceKey) + && checkOptional<SemanticHighlightingServerCapabilities>(error, semanticHighlightingKey); } Utils::optional<Utils::variant<QString, bool> > @@ -181,4 +182,44 @@ bool TextDocumentSyncOptions::isValid(QStringList *error) const && checkOptional<SaveOptions>(error, saveKey); } +Utils::optional<QList<QList<QString>>> ServerCapabilities::SemanticHighlightingServerCapabilities::scopes() const +{ + QList<QList<QString>> scopes; + if (!contains(scopesKey)) + return Utils::nullopt; + for (const QJsonValue jsonScopeValue : value(scopesKey).toArray()) { + if (!jsonScopeValue.isArray()) + return {}; + QList<QString> scope; + for (const QJsonValue value : jsonScopeValue.toArray()) { + if (!value.isString()) + return {}; + scope.append(value.toString()); + } + scopes.append(scope); + } + return Utils::make_optional(scopes); +} + +void ServerCapabilities::SemanticHighlightingServerCapabilities::setScopes( + const QList<QList<QString>> &scopes) +{ + QJsonArray jsonScopes; + for (const QList<QString> &scope : scopes) { + QJsonArray jsonScope; + for (const QString &value : scope) + jsonScope.append(value); + jsonScopes.append(jsonScope); + } + insert(scopesKey, jsonScopes); +} + +bool ServerCapabilities::SemanticHighlightingServerCapabilities::isValid(QStringList *) const +{ + return contains(scopesKey) && value(scopesKey).isArray() + && Utils::allOf(value(scopesKey).toArray(), [](const QJsonValue &array) { + return array.isArray() && Utils::allOf(array.toArray(), &QJsonValue::isString); + }); +} + } // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/servercapabilities.h b/src/libs/languageserverprotocol/servercapabilities.h index 71d3e4a496..6345178c57 100644 --- a/src/libs/languageserverprotocol/servercapabilities.h +++ b/src/libs/languageserverprotocol/servercapabilities.h @@ -223,6 +223,17 @@ public: void clearId() { remove(idKey); } }; + class LANGUAGESERVERPROTOCOL_EXPORT SemanticHighlightingServerCapabilities : public JsonObject + { + public: + using JsonObject::JsonObject; + + Utils::optional<QList<QList<QString>>> scopes() const; + void setScopes(const QList<QList<QString>> &scopes); + + bool isValid(QStringList *) const override; + }; + // Defines how text documents are synced. Is either a detailed structure defining each // notification or for backwards compatibility the TextDocumentSyncKind number. using TextDocumentSync = Utils::variant<TextDocumentSyncOptions, int>; @@ -411,6 +422,12 @@ public: void setExperimental(const JsonObject &experimental) { insert(experimentalKey, experimental); } void clearExperimental() { remove(experimentalKey); } + Utils::optional<SemanticHighlightingServerCapabilities> semanticHighlighting() const + { return optionalValue<SemanticHighlightingServerCapabilities>(semanticHighlightingKey); } + void setSemanticHighlighting(const SemanticHighlightingServerCapabilities &semanticHighlighting) + { insert(semanticHighlightingKey, semanticHighlighting); } + void clearSemanticHighlighting() { remove(semanticHighlightingKey); } + bool isValid(QStringList *error) const override; }; diff --git a/src/libs/qmleditorwidgets/contextpanetextwidget.cpp b/src/libs/qmleditorwidgets/contextpanetextwidget.cpp index 70b6c700db..6a37637a6a 100644 --- a/src/libs/qmleditorwidgets/contextpanetextwidget.cpp +++ b/src/libs/qmleditorwidgets/contextpanetextwidget.cpp @@ -103,7 +103,7 @@ ContextPaneTextWidget::ContextPaneTextWidget(QWidget *parent) : connect(ui->bottomAlignmentButton, &QToolButton::toggled, this, &ContextPaneTextWidget::onVerticalAlignmentChanged); - connect(ui->styleComboBox, QOverload<const QString &>::of(&QComboBox::currentIndexChanged), + connect(ui->styleComboBox, &QComboBox::currentTextChanged, this, &ContextPaneTextWidget::onStyleComboBoxChanged); } diff --git a/src/libs/tracing/qml/ButtonsBar.qml b/src/libs/tracing/qml/ButtonsBar.qml index a151eb9f91..32111daa57 100644 --- a/src/libs/tracing/qml/ButtonsBar.qml +++ b/src/libs/tracing/qml/ButtonsBar.qml @@ -24,9 +24,8 @@ ****************************************************************************/ import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import QtQuick.Controls.Styles 1.2 +import QtQuick.Controls 2.0 +import QtQuick.Layouts 1.3 import TimelineTheme 1.0 @@ -55,19 +54,12 @@ ToolBar { return rangeButton.checked } - style: ToolBarStyle { - padding { - left: 0 - right: 0 - top: 0 - bottom: 0 - } - background: Rectangle { - anchors.fill: parent - color: Theme.color(Theme.PanelStatusBarBackgroundColor) - } + background: Rectangle { + anchors.fill: parent + color: Theme.color(Theme.PanelStatusBarBackgroundColor) } + RowLayout { spacing: 0 anchors.fill: parent @@ -77,7 +69,7 @@ ToolBar { Layout.fillHeight: true imageSource: "image://icons/prev" - tooltip: qsTr("Jump to previous event.") + ToolTip.text: qsTr("Jump to previous event.") onClicked: buttons.jumpToPrev() } @@ -86,7 +78,7 @@ ToolBar { Layout.fillHeight: true imageSource: "image://icons/next" - tooltip: qsTr("Jump to next event.") + ToolTip.text: qsTr("Jump to next event.") onClicked: buttons.jumpToNext() } @@ -95,7 +87,7 @@ ToolBar { Layout.fillHeight: true imageSource: "image://icons/zoom" - tooltip: qsTr("Show zoom slider.") + ToolTip.text: qsTr("Show zoom slider.") checkable: true checked: false onCheckedChanged: buttons.zoomControlChanged() @@ -106,7 +98,7 @@ ToolBar { Layout.fillHeight: true imageSource: "image://icons/" + (checked ? "rangeselected" : "rangeselection"); - tooltip: qsTr("Select range.") + ToolTip.text: qsTr("Select range.") checkable: true checked: false onCheckedChanged: buttons.rangeSelectChanged() @@ -117,7 +109,7 @@ ToolBar { Layout.fillHeight: true imageSource: "image://icons/selectionmode" - tooltip: qsTr("View event information on mouseover.") + ToolTip.text: qsTr("View event information on mouseover.") checkable: true checked: false onCheckedChanged: buttons.lockChanged() diff --git a/src/libs/tracing/qml/CategoryLabel.qml b/src/libs/tracing/qml/CategoryLabel.qml index 5945b0f6df..4994d3b962 100644 --- a/src/libs/tracing/qml/CategoryLabel.qml +++ b/src/libs/tracing/qml/CategoryLabel.qml @@ -24,8 +24,7 @@ ****************************************************************************/ import QtQuick 2.1 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 +import QtQuick.Controls 2.2 import TimelineTheme 1.0 @@ -54,6 +53,15 @@ Item { property bool reverseSelect: false + Button { + // dummy button to display a tooltip + anchors.fill: txt + ToolTip.text: labelContainer.text + ToolTip.visible: enabled && hovered + ToolTip.delay: 1000 + background: Item {} + } + MouseArea { id: dragArea anchors.fill: txt @@ -161,7 +169,7 @@ Item { visible: eventIds.length > 0 imageSource: "image://icons/note" - tooltip: texts.join("\n"); + ToolTip.text: texts.join("\n"); onClicked: { if (++currentNote >= eventIds.length) currentNote = 0; @@ -176,7 +184,7 @@ Item { implicitHeight: txt.height - 1 enabled: expanded || (model && !model.empty) imageSource: expanded ? "image://icons/close_split" : "image://icons/split" - tooltip: expanded ? qsTr("Collapse category") : qsTr("Expand category") + ToolTip.text: expanded ? qsTr("Collapse category") : qsTr("Expand category") onClicked: model.expanded = !expanded } diff --git a/src/libs/tracing/qml/FlameGraphView.qml b/src/libs/tracing/qml/FlameGraphView.qml index 9712b91cd5..f98bcb3134 100644 --- a/src/libs/tracing/qml/FlameGraphView.qml +++ b/src/libs/tracing/qml/FlameGraphView.qml @@ -28,7 +28,7 @@ import TimelineTheme 1.0 import QtQml 2.2 import QtQuick 2.9 -import QtQuick.Controls 1.3 +import QtQuick.Controls 2.3 ScrollView { id: root @@ -45,7 +45,7 @@ ScrollView { function resetRoot() { flamegraph.resetRoot(); } property bool zoomed: flamegraph.zoomed - property int sizeRole: -1 + property var sizeRole: modes[Math.max(0, modesMenu.currentIndex)] property var model: null property int typeIdRole: -1 @@ -56,7 +56,7 @@ ScrollView { property int summaryRole: -1 property int noteRole: -1 - property var trRoleNames: [] + property var trRoleNames: ({}) property var modes: [] @@ -314,27 +314,11 @@ ScrollView { } } - Button { + ComboBox { + id: modesMenu x: flickable.width - width y: flickable.contentY - - // It won't listen to anchors.margins and by default it doesn't add any margin. Great. - width: implicitWidth + 20 - - text: qsTr("Visualize %1").arg(trRoleNames[root.sizeRole]) - - menu: Menu { - id: modesMenu - Instantiator { - model: root.modes - MenuItem { - text: root.trRoleNames[modelData] - onTriggered: root.sizeRole = modelData - } - onObjectAdded: modesMenu.insertItem(index, object) - onObjectRemoved: modesMenu.removeItem(object) - } - } + model: root.modes.map(function(role) { return root.trRoleNames[role] }); } } } diff --git a/src/libs/tracing/qml/ImageToolButton.qml b/src/libs/tracing/qml/ImageToolButton.qml index 353e38f122..686fd2db88 100644 --- a/src/libs/tracing/qml/ImageToolButton.qml +++ b/src/libs/tracing/qml/ImageToolButton.qml @@ -24,8 +24,7 @@ ****************************************************************************/ import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Controls.Styles 1.2 +import QtQuick.Controls 2.0 import TimelineTheme 1.0 @@ -34,6 +33,9 @@ ToolButton { property string imageSource + ToolTip.visible: enabled && hovered + ToolTip.delay: 1000 + Image { source: parent.enabled ? parent.imageSource : parent.imageSource + "/disabled" width: 16 @@ -42,13 +44,11 @@ ToolButton { smooth: false } - style: ButtonStyle { - background: Rectangle { - color: (control.checked || control.pressed) - ? Theme.color(Theme.FancyToolButtonSelectedColor) - : control.hovered - ? Theme.color(Theme.FancyToolButtonHoverColor) - : "#00000000" - } + background: Rectangle { + color: (parent.checked || parent.pressed) + ? Theme.color(Theme.FancyToolButtonSelectedColor) + : parent.hovered + ? Theme.color(Theme.FancyToolButtonHoverColor) + : "#00000000" } } diff --git a/src/libs/tracing/qml/MainView.qml b/src/libs/tracing/qml/MainView.qml index 2e1f97a3ba..30eb7c3c3f 100644 --- a/src/libs/tracing/qml/MainView.qml +++ b/src/libs/tracing/qml/MainView.qml @@ -24,8 +24,7 @@ ****************************************************************************/ import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Controls.Styles 1.0 +import QtQuick.Controls 2.0 import TimelineTheme 1.0 @@ -73,7 +72,7 @@ Rectangle { rangeDetails.hide(); selectionRangeMode = false; zoomSlider.externalUpdate = true; - zoomSlider.value = zoomSlider.minimumValue; + zoomSlider.value = zoomSlider.from; } // This is called from outside to synchronize the timeline to other views @@ -192,6 +191,7 @@ Rectangle { TimelineContent { id: content + anchors.left: buttonsBar.right anchors.top: buttonsBar.bottom anchors.bottom: overview.top @@ -255,13 +255,13 @@ Rectangle { onReleased: { if (selectionRange.creationState === selectionRange.creationSecondLimit) { - content.stayInteractive = true; + content.interactive = true; selectionRange.creationState = selectionRange.creationFinished; } } onPressed: { if (selectionRange.creationState === selectionRange.creationFirstLimit) { - content.stayInteractive = false; + content.interactive = false; selectionRange.setPos(selectionRangeControl.mouseX + content.contentX); selectionRange.creationState = selectionRange.creationSecondLimit; } @@ -281,12 +281,12 @@ Rectangle { flickableDirection: Flickable.HorizontalFlick clip: true interactive: false - x: content.x + content.flickableItem.x - y: content.y + content.flickableItem.y + x: content.x + y: content.y height: (selectionRangeMode && selectionRange.creationState !== selectionRange.creationInactive) ? - content.flickableItem.height : 0 - width: content.flickableItem.width + content.height : 0 + width: content.width contentX: content.contentX contentWidth: content.contentWidth @@ -320,11 +320,11 @@ Rectangle { } TimelineRulers { - contentX: buttonsBar.width - content.x - content.flickableItem.x + content.contentX + contentX: buttonsBar.width + content.contentX anchors.left: buttonsBar.right anchors.right: parent.right anchors.top: parent.top - height: content.flickableItem.height + buttonsBar.height + height: content.height + buttonsBar.height windowStart: zoomControl.windowStart viewTimePerPixel: selectionRange.viewTimePerPixel scaleHeight: buttonsBar.height @@ -458,8 +458,8 @@ Rectangle { Slider { id: zoomSlider anchors.fill: parent - minimumValue: 1 - maximumValue: 10000 + from: 1 + to: 10000 stepSize: 100 property int exponent: 3 @@ -478,7 +478,7 @@ Rectangle { } var windowLength = Math.max( - Math.pow(value / maximumValue, exponent) * zoomControl.windowDuration, + Math.pow(value / to, exponent) * zoomControl.windowDuration, minWindowLength); var startTime = Math.max(zoomControl.windowStart, fixedPoint - windowLength / 2) diff --git a/src/libs/tracing/qml/RangeDetails.qml b/src/libs/tracing/qml/RangeDetails.qml index ccb86a5c3c..cb99479ca4 100644 --- a/src/libs/tracing/qml/RangeDetails.qml +++ b/src/libs/tracing/qml/RangeDetails.qml @@ -24,6 +24,8 @@ ****************************************************************************/ import QtQuick 2.9 +import QtQuick.Controls 2.0 + import TimelineTheme 1.0 Item { @@ -108,6 +110,7 @@ Item { implicitHeight: typeTitle.height visible: !rangeDetails.noteReadonly onClicked: noteEdit.focus = true + ToolTip.text: qsTr("Edit note") } ImageToolButton { @@ -117,6 +120,7 @@ Item { anchors.right: closeIcon.left implicitHeight: typeTitle.height onClicked: locked = !locked + ToolTip.text: qsTr("View event information on mouseover.") } ImageToolButton { @@ -126,6 +130,7 @@ Item { implicitHeight: typeTitle.height imageSource: "image://icons/close_window" onClicked: rangeDetails.clearSelection() + ToolTip.text: qsTr("Close") } } diff --git a/src/libs/tracing/qml/RowLabel.qml b/src/libs/tracing/qml/RowLabel.qml index be394b5e7e..e73d51b291 100644 --- a/src/libs/tracing/qml/RowLabel.qml +++ b/src/libs/tracing/qml/RowLabel.qml @@ -24,8 +24,7 @@ ****************************************************************************/ import QtQuick 2.0 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 +import QtQuick.Controls 2.2 import TimelineTheme 1.0 Button { @@ -38,25 +37,26 @@ Button { signal setRowHeight(int newHeight) property string labelText: label.description ? label.description : qsTr("[unknown]") - action: Action { - onTriggered: button.selectBySelectionId(); - tooltip: button.labelText + (label.displayName ? (" (" + label.displayName + ")") : "") + + onPressed: selectBySelectionId(); + ToolTip.text: labelText + (label.displayName ? (" (" + label.displayName + ")") : "") + ToolTip.visible: hovered + ToolTip.delay: 1000 + + background: Rectangle { + border.width: 1 + border.color: Theme.color(Theme.Timeline_DividerColor) + color: Theme.color(Theme.PanelStatusBarBackgroundColor) } - style: ButtonStyle { - background: Rectangle { - border.width: 1 - border.color: Theme.color(Theme.Timeline_DividerColor) - color: Theme.color(Theme.PanelStatusBarBackgroundColor) - } - label: TimelineText { - text: button.labelText - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft - elide: Text.ElideRight - color: Theme.color(Theme.PanelTextColorLight) - } + contentItem: TimelineText { + text: button.labelText + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight + color: Theme.color(Theme.PanelTextColorLight) } + MouseArea { hoverEnabled: true property bool resizing: false diff --git a/src/libs/tracing/qml/SelectionRangeDetails.qml b/src/libs/tracing/qml/SelectionRangeDetails.qml index 458e5cf7ad..288ac7693c 100644 --- a/src/libs/tracing/qml/SelectionRangeDetails.qml +++ b/src/libs/tracing/qml/SelectionRangeDetails.qml @@ -24,6 +24,8 @@ ****************************************************************************/ import QtQuick 2.1 +import QtQuick.Controls 2.0 + import TimelineTheme 1.0 import TimelineTimeFormatter 1.0 @@ -133,5 +135,6 @@ Item { anchors.top: selectionRangeDetails.top implicitHeight: typeTitle.height onClicked: selectionRangeDetails.close() + ToolTip.text: qsTr("Close") } } diff --git a/src/libs/tracing/qml/TimelineContent.qml b/src/libs/tracing/qml/TimelineContent.qml index 636d707356..5eb667822f 100644 --- a/src/libs/tracing/qml/TimelineContent.qml +++ b/src/libs/tracing/qml/TimelineContent.qml @@ -24,20 +24,26 @@ ****************************************************************************/ import QtQuick 2.0 -import QtQuick.Controls 1.2 +import QtQuick.Controls 2.2 import TimelineRenderer 1.0 import QtQml.Models 2.1 -ScrollView { - id: scroller +Flickable { + id: flick + clip: true + contentHeight: timelineView.height + height + flickableDirection: Flickable.HorizontalAndVerticalFlick + boundsBehavior: Flickable.StopAtBounds + pixelAligned: true + + ScrollBar.horizontal: ScrollBar {} + ScrollBar.vertical: ScrollBar {} + + property bool recursionGuard: false property QtObject zoomer property QtObject modelProxy - property int contentX: flick.contentX - property int contentY: flick.contentY - property int contentWidth: flick.contentWidth - property int contentHeight: flick.contentHeight property bool selectionLocked property int typeId @@ -58,146 +64,122 @@ ScrollView { timelineModel.items.move(sourceIndex, targetIndex) } - function scroll() - { - flick.scroll(); + + function guarded(operation) { + if (recursionGuard) + return; + recursionGuard = true; + operation(); + recursionGuard = false; } - // ScrollView will try to deinteractivate it. We don't want that - // as the horizontal flickable is interactive, too. We do occasionally - // switch to non-interactive ourselves, though. - property bool stayInteractive: true - onStayInteractiveChanged: flick.interactive = stayInteractive - - Flickable { - id: flick - contentHeight: timelineView.height + height - flickableDirection: Flickable.HorizontalAndVerticalFlick - boundsBehavior: Flickable.StopAtBounds - pixelAligned: true - - onInteractiveChanged: interactive = stayInteractive - - property bool recursionGuard: false - - function guarded(operation) { - if (recursionGuard) - return; - recursionGuard = true; - operation(); - recursionGuard = false; + // Logically we should bind to flick.width above as we use flick.width in scroll(). + // However, this width changes before flick.width when the window is resized and if we + // don't explicitly set contentX here, for some reason an automatic change in contentX is + // triggered after this width has changed, but before flick.width changes. This would be + // indistinguishabe from a manual flick by the user and thus changes the range position. We + // don't want to change the range position on resizing the window. Therefore we bind to this + // width. + onWidthChanged: scroll() + + // Update the zoom control on scrolling. + onContentXChanged: guarded(function() { + var newStartTime = contentX * zoomer.rangeDuration / width + zoomer.windowStart; + if (isFinite(newStartTime) && Math.abs(newStartTime - zoomer.rangeStart) >= 1) { + var newEndTime = (contentX + width) * zoomer.rangeDuration / width + zoomer.windowStart; + if (isFinite(newEndTime)) + zoomer.setRange(newStartTime, newEndTime); } - - // Logically we should bind to scroller.width above as we use scroller.width in scroll(). - // However, this width changes before scroller.width when the window is resized and if we - // don't explicitly set contentX here, for some reason an automatic change in contentX is - // triggered after this width has changed, but before scroller.width changes. This would be - // indistinguishabe from a manual flick by the user and thus changes the range position. We - // don't want to change the range position on resizing the window. Therefore we bind to this - // width. - onWidthChanged: scroll() - - // Update the zoom control on scrolling. - onContentXChanged: guarded(function() { - var newStartTime = contentX * zoomer.rangeDuration / scroller.width - + zoomer.windowStart; - if (isFinite(newStartTime) && Math.abs(newStartTime - zoomer.rangeStart) >= 1) { - var newEndTime = (contentX + scroller.width) * zoomer.rangeDuration / scroller.width - + zoomer.windowStart; - if (isFinite(newEndTime)) - zoomer.setRange(newStartTime, newEndTime); + }); + + // Scroll when the zoom control is updated + function scroll() { + guarded(function() { + if (zoomer.rangeDuration <= 0) { + contentWidth = 0; + contentX = 0; + } else { + var newWidth = zoomer.windowDuration * width / zoomer.rangeDuration; + if (isFinite(newWidth) && Math.abs(newWidth - contentWidth) >= 1) + contentWidth = newWidth; + var newStartX = (zoomer.rangeStart - zoomer.windowStart) * width / + zoomer.rangeDuration; + if (isFinite(newStartX) && Math.abs(newStartX - contentX) >= 1) + contentX = newStartX; } }); + } - // Scroll when the zoom control is updated - function scroll() { - guarded(function() { - if (zoomer.rangeDuration <= 0) { - contentWidth = 0; - contentX = 0; - } else { - var newWidth = zoomer.windowDuration * scroller.width / zoomer.rangeDuration; - if (isFinite(newWidth) && Math.abs(newWidth - contentWidth) >= 1) - contentWidth = newWidth; - var newStartX = (zoomer.rangeStart - zoomer.windowStart) * scroller.width / - zoomer.rangeDuration; - if (isFinite(newStartX) && Math.abs(newStartX - contentX) >= 1) - contentX = newStartX; - } - }); - } - - Column { - id: timelineView - - signal clearChildren - signal select(int modelIndex, int eventIndex) - - DelegateModel { - id: timelineModel - model: modelProxy.models - delegate: TimelineRenderer { - id: renderer - model: modelData - notes: modelProxy.notes - zoomer: scroller.zoomer - selectionLocked: scroller.selectionLocked - x: 0 - height: modelData.height - property int visualIndex: DelegateModel.itemsIndex - - // paint "under" the vertical scrollbar, so that it always matches with the - // timemarks - width: flick.contentWidth - - Connections { - target: timelineView - onClearChildren: renderer.clearData() - onSelect: { - if (modelIndex === index || modelIndex === -1) { - renderer.selectedItem = eventIndex; - if (eventIndex !== -1) - renderer.recenter(); - } + Column { + id: timelineView + + signal clearChildren + signal select(int modelIndex, int eventIndex) + + DelegateModel { + id: timelineModel + model: modelProxy.models + delegate: TimelineRenderer { + id: renderer + model: modelData + notes: modelProxy.notes + zoomer: flick.zoomer + selectionLocked: flick.selectionLocked + x: 0 + height: modelData.height + property int visualIndex: DelegateModel.itemsIndex + + // paint "under" the vertical scrollbar, so that it always matches with the + // timemarks + width: flick.contentWidth + + Connections { + target: timelineView + onClearChildren: renderer.clearData() + onSelect: { + if (modelIndex === index || modelIndex === -1) { + renderer.selectedItem = eventIndex; + if (eventIndex !== -1) + renderer.recenter(); } } + } - function recenter() { - if (modelData.endTime(selectedItem) < zoomer.rangeStart || - modelData.startTime(selectedItem) > zoomer.rangeEnd) { - - var newStart = Math.max((modelData.startTime(selectedItem) + - modelData.endTime(selectedItem) - - zoomer.rangeDuration) / 2, zoomer.traceStart); - zoomer.setRange(newStart, - Math.min(newStart + zoomer.rangeDuration, zoomer.traceEnd)); - } + function recenter() { + if (modelData.endTime(selectedItem) < zoomer.rangeStart || + modelData.startTime(selectedItem) > zoomer.rangeEnd) { - var row = modelData.row(selectedItem); - var rowStart = modelData.rowOffset(row) + y; - var rowEnd = rowStart + modelData.rowHeight(row); - if (rowStart < flick.contentY || rowEnd - scroller.height > flick.contentY) - flick.contentY = (rowStart + rowEnd - scroller.height) / 2; + var newStart = Math.max((modelData.startTime(selectedItem) + + modelData.endTime(selectedItem) - + zoomer.rangeDuration) / 2, zoomer.traceStart); + zoomer.setRange(newStart, + Math.min(newStart + zoomer.rangeDuration, zoomer.traceEnd)); } - onSelectedItemChanged: scroller.propagateSelection(index, selectedItem); + var row = modelData.row(selectedItem); + var rowStart = modelData.rowOffset(row) + y; + var rowEnd = rowStart + modelData.rowHeight(row); + if (rowStart < flick.contentY || rowEnd - flick.height > flick.contentY) + flick.contentY = (rowStart + rowEnd - flick.height) / 2; + } + + onSelectedItemChanged: flick.propagateSelection(index, selectedItem); - Connections { - target: model - onDetailsChanged: { - if (selectedItem != -1) { - scroller.propagateSelection(-1, -1); - scroller.propagateSelection(index, selectedItem); - } + Connections { + target: model + onDetailsChanged: { + if (selectedItem != -1) { + flick.propagateSelection(-1, -1); + flick.propagateSelection(index, selectedItem); } } } } + } - Repeater { - id: repeater - model: timelineModel - } + Repeater { + id: repeater + model: timelineModel } } } diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 5863c35d1f..5cc79c3182 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -1,6 +1,9 @@ if (IDE_LIBEXEC_PATH AND IDE_BIN_PATH) - file(RELATIVE_PATH RELATIVE_TOOLS_PATH - "${CMAKE_INSTALL_PREFIX}/${IDE_BIN_PATH}" "${CMAKE_INSTALL_PREFIX}/${IDE_LIBEXEC_PATH}") + get_filename_component(bin_path + "${CMAKE_INSTALL_PREFIX}/${IDE_BIN_PATH}" ABSOLUTE "${CMAKE_BINARY_DIR}") + get_filename_component(libexec_path + "${CMAKE_INSTALL_PREFIX}/${IDE_LIBEXEC_PATH}" ABSOLUTE "${CMAKE_BINARY_DIR}") + file(RELATIVE_PATH RELATIVE_TOOLS_PATH "${bin_path}" "${libexec_path}") else() message(WARNING "IDE_LIBEXEC_PATH or IDE_BIN_PATH undefined when calculating tools path") set(RELATIVE_TOOLS_PATH "") @@ -40,8 +43,14 @@ add_qtc_library(Utils elfreader.cpp elfreader.h elidinglabel.cpp elidinglabel.h environment.cpp environment.h + environmentfwd.h environmentdialog.cpp environmentdialog.h environmentmodel.cpp environmentmodel.h + namevaluedictionary.cpp namevaluedictionary.h + namevalueitem.cpp namevalueitem.h + namevaluemodel.cpp namevaluemodel.h + namevaluesdialog.cpp namevaluesdialog.h + namevaluevalidator.cpp namevaluevalidator.h execmenu.cpp execmenu.h executeondestruction.h fadingindicator.cpp fadingindicator.h @@ -84,6 +93,11 @@ add_qtc_library(Utils mimetypes/mimeprovider.cpp mimetypes/mimeprovider_p.h mimetypes/mimetype.cpp mimetypes/mimetype.h mimetypes/mimetype_p.h mimetypes/mimetypeparser.cpp mimetypes/mimetypeparser_p.h + namevaluedictionary.cpp namevaluedictionary.h + namevalueitem.cpp namevalueitem.h + namevaluemodel.cpp namevaluemodel.h + namevaluesdialog.cpp namevaluesdialog.h + namevaluevalidator.cpp namevaluevalidator.h navigationtreeview.cpp navigationtreeview.h networkaccessmanager.cpp networkaccessmanager.h newclasswidget.cpp newclasswidget.h newclasswidget.ui diff --git a/src/libs/utils/algorithm.h b/src/libs/utils/algorithm.h index 237863c573..1e821a4bcb 100644 --- a/src/libs/utils/algorithm.h +++ b/src/libs/utils/algorithm.h @@ -407,6 +407,20 @@ bool allOf(const T &container, F predicate) return std::all_of(std::begin(container), std::end(container), predicate); } +// allOf taking a member function pointer +template<typename T, typename R, typename S> +bool allOf(const T &container, R (S::*predicate)() const) +{ + return std::all_of(std::begin(container), std::end(container), std::mem_fn(predicate)); +} + +// allOf taking a member pointer +template<typename T, typename R, typename S> +bool allOf(const T &container, R S::*member) +{ + return std::all_of(std::begin(container), std::end(container), std::mem_fn(member)); +} + ////////////////// // erase ///////////////// @@ -885,6 +899,8 @@ std::tuple<C, C> partition(const C &container, F predicate) { C hit; C miss; + reserve(hit, container.size()); + reserve(miss, container.size()); auto hitIns = inserter(hit); auto missIns = inserter(miss); for (auto i : container) { diff --git a/src/libs/utils/buildablehelperlibrary.cpp b/src/libs/utils/buildablehelperlibrary.cpp index 33ba624dc4..d1978a1e4f 100644 --- a/src/libs/utils/buildablehelperlibrary.cpp +++ b/src/libs/utils/buildablehelperlibrary.cpp @@ -46,7 +46,7 @@ QString BuildableHelperLibrary::qtChooserToQmakePath(const QString &path) const QString toolDir = QLatin1String("QTTOOLDIR=\""); SynchronousProcess proc; proc.setTimeoutS(1); - SynchronousProcessResponse response = proc.runBlocking(path, QStringList(QLatin1String("-print-env"))); + SynchronousProcessResponse response = proc.runBlocking({FilePath::fromString(path), {"-print-env"}}); if (response.result != SynchronousProcessResponse::Finished) return QString(); const QString output = response.stdOut(); @@ -130,7 +130,7 @@ QString BuildableHelperLibrary::qtVersionForQMake(const QString &qmakePath) SynchronousProcess qmake; qmake.setTimeoutS(5); - SynchronousProcessResponse response = qmake.runBlocking(qmakePath, QStringList(QLatin1String("--version"))); + SynchronousProcessResponse response = qmake.runBlocking({FilePath::fromString(qmakePath), {"--version"}}); if (response.result != SynchronousProcessResponse::Finished) { qWarning() << response.exitMessage(qmakePath, 5); return QString(); diff --git a/src/libs/utils/consoleprocess.h b/src/libs/utils/consoleprocess.h index 3dec9838c3..1ae0bb4496 100644 --- a/src/libs/utils/consoleprocess.h +++ b/src/libs/utils/consoleprocess.h @@ -35,8 +35,9 @@ class QSettings; QT_END_NAMESPACE namespace Utils { + class Environment; -struct ConsoleProcessPrivate; +class CommandLine; class QTCREATOR_UTILS_EXPORT TerminalCommand { @@ -61,6 +62,8 @@ public: ConsoleProcess(QObject *parent = nullptr); ~ConsoleProcess() override; + void setCommand(const Utils::CommandLine &command); + void setWorkingDirectory(const QString &dir); QString workingDirectory() const; @@ -70,9 +73,8 @@ public: QProcess::ProcessError error() const; QString errorString() const; - enum class MetaCharMode { Abort, Ignore }; - bool start(const QString &program, const QString &args, - MetaCharMode metaCharMode = MetaCharMode::Abort); + bool start(); + public slots: void stop(); @@ -158,7 +160,7 @@ private: void cleanupInferior(); #endif - ConsoleProcessPrivate *d; + struct ConsoleProcessPrivate *d; }; } //namespace Utils diff --git a/src/libs/utils/consoleprocess_p.h b/src/libs/utils/consoleprocess_p.h index 5d8d193493..b32be8c586 100644 --- a/src/libs/utils/consoleprocess_p.h +++ b/src/libs/utils/consoleprocess_p.h @@ -53,7 +53,7 @@ struct ConsoleProcessPrivate { Environment m_environment; qint64 m_appPid = 0; int m_appCode; - QString m_executable; + CommandLine m_commandLine; QProcess::ExitStatus m_appStatus; QLocalServer m_stubServer; QLocalSocket *m_stubSocket = nullptr; diff --git a/src/libs/utils/consoleprocess_unix.cpp b/src/libs/utils/consoleprocess_unix.cpp index 43afffa0a2..fe83b95f58 100644 --- a/src/libs/utils/consoleprocess_unix.cpp +++ b/src/libs/utils/consoleprocess_unix.cpp @@ -60,12 +60,17 @@ qint64 ConsoleProcess::applicationMainThreadID() const return -1; } +void ConsoleProcess::setCommand(const Utils::CommandLine &command) +{ + d->m_commandLine = command; +} + void ConsoleProcess::setSettings(QSettings *settings) { d->m_settings = settings; } -bool ConsoleProcess::start(const QString &program, const QString &args, MetaCharMode metaCharMode) +bool ConsoleProcess::start() { if (isRunning()) return false; @@ -74,12 +79,16 @@ bool ConsoleProcess::start(const QString &program, const QString &args, MetaChar d->m_error = QProcess::UnknownError; QtcProcess::SplitError perr; - QtcProcess::Arguments pargs = QtcProcess::prepareArgs(args, &perr, HostOsInfo::hostOs(), - &d->m_environment, &d->m_workingDir, - metaCharMode == MetaCharMode::Abort); + QtcProcess::Arguments pargs = QtcProcess::prepareArgs(d->m_commandLine.arguments(), + &perr, + HostOsInfo::hostOs(), + &d->m_environment, + &d->m_workingDir, + d->m_commandLine.metaCharMode() + == CommandLine::MetaCharMode::Abort); QString pcmd; if (perr == QtcProcess::SplitOk) { - pcmd = program; + pcmd = d->m_commandLine.executable().toString(); } else { if (perr != QtcProcess::FoundMeta) { emitError(QProcess::FailedToStart, tr("Quoting error in command.")); @@ -93,7 +102,8 @@ bool ConsoleProcess::start(const QString &program, const QString &args, MetaChar } pcmd = QLatin1String("/bin/sh"); pargs = QtcProcess::Arguments::createUnixArgs( - QStringList({"-c", (QtcProcess::quoteArg(program) + ' ' + args)})); + {"-c", (QtcProcess::quoteArg(d->m_commandLine.executable().toString()) + + ' ' + d->m_commandLine.arguments())}); } QtcProcess::SplitError qerr; @@ -167,7 +177,6 @@ bool ConsoleProcess::start(const QString &program, const QString &args, MetaChar connect(d->m_stubConnectTimer, &QTimer::timeout, this, &ConsoleProcess::stop); d->m_stubConnectTimer->setSingleShot(true); d->m_stubConnectTimer->start(10000); - d->m_executable = program; return true; } @@ -286,7 +295,7 @@ void ConsoleProcess::readStubOutput() if (out.startsWith("err:chdir ")) { emitError(QProcess::FailedToStart, msgCannotChangeToWorkDir(workingDirectory(), errorMsg(out.mid(10).toInt()))); } else if (out.startsWith("err:exec ")) { - emitError(QProcess::FailedToStart, msgCannotExecute(d->m_executable, errorMsg(out.mid(9).toInt()))); + emitError(QProcess::FailedToStart, msgCannotExecute(d->m_commandLine.executable().toString(), errorMsg(out.mid(9).toInt()))); } else if (out.startsWith("spid ")) { delete d->m_tempFile; d->m_tempFile = nullptr; diff --git a/src/libs/utils/consoleprocess_win.cpp b/src/libs/utils/consoleprocess_win.cpp index be65c42cc8..fdead4db85 100644 --- a/src/libs/utils/consoleprocess_win.cpp +++ b/src/libs/utils/consoleprocess_win.cpp @@ -46,15 +46,18 @@ ConsoleProcess::ConsoleProcess(QObject *parent) : this, &ConsoleProcess::stubConnectionAvailable); } +void ConsoleProcess::setCommand(const Utils::CommandLine &command) +{ + d->m_commandLine = command; +} + qint64 ConsoleProcess::applicationMainThreadID() const { return d->m_appMainThreadId; } -bool ConsoleProcess::start(const QString &program, const QString &args, MetaCharMode metaCharMode) +bool ConsoleProcess::start() { - Q_UNUSED(metaCharMode); - if (isRunning()) return false; @@ -64,11 +67,13 @@ bool ConsoleProcess::start(const QString &program, const QString &args, MetaChar QString pcmd; QString pargs; if (d->m_mode != Run) { // The debugger engines already pre-process the arguments. - pcmd = program; - pargs = args; + pcmd = d->m_commandLine.executable().toString(); + pargs = d->m_commandLine.arguments(); } else { QtcProcess::Arguments outArgs; - QtcProcess::prepareCommand(program, args, &pcmd, &outArgs, OsTypeWindows, + QtcProcess::prepareCommand(d->m_commandLine.executable().toString(), + d->m_commandLine.arguments(), + &pcmd, &outArgs, OsTypeWindows, &d->m_environment, &d->m_workingDir); pargs = outArgs.toWindowsArgs(); } @@ -209,7 +214,7 @@ void ConsoleProcess::readStubOutput() if (out.startsWith("err:chdir ")) { emitError(QProcess::FailedToStart, msgCannotChangeToWorkDir(workingDirectory(), winErrorMessage(out.mid(10).toInt()))); } else if (out.startsWith("err:exec ")) { - emitError(QProcess::FailedToStart, msgCannotExecute(d->m_executable, winErrorMessage(out.mid(9).toInt()))); + emitError(QProcess::FailedToStart, msgCannotExecute(d->m_commandLine.executable().toUserOutput(), winErrorMessage(out.mid(9).toInt()))); } else if (out.startsWith("thread ")) { // Windows only d->m_appMainThreadId = out.mid(7).toLongLong(); } else if (out.startsWith("pid ")) { diff --git a/src/libs/utils/detailsbutton.cpp b/src/libs/utils/detailsbutton.cpp index 329975afdb..abceff7b2d 100644 --- a/src/libs/utils/detailsbutton.cpp +++ b/src/libs/utils/detailsbutton.cpp @@ -200,3 +200,61 @@ QPixmap DetailsButton::cacheRendering(const QSize &size, bool checked) style()->drawPrimitive(checked ? QStyle::PE_IndicatorArrowUp : QStyle::PE_IndicatorArrowDown, &arrowOpt, &p, this); return pixmap; } + +ExpandButton::ExpandButton(QWidget *parent) : QAbstractButton(parent) +{ + setCheckable(true); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); +} + +QSize ExpandButton::sizeHint() const +{ + return {fontMetrics().horizontalAdvance(text()) + 26, HostOsInfo::isMacHost() ? 34 : 22}; +} + +void ExpandButton::paintEvent(QPaintEvent *e) +{ + QWidget::paintEvent(e); + QPainter p(this); + + QPixmap &pixmap = isChecked() ? m_checkedPixmap : m_uncheckedPixmap; + if (pixmap.isNull() || pixmap.size() / pixmap.devicePixelRatio() != contentsRect().size()) + pixmap = cacheRendering(); + p.drawPixmap(contentsRect(), pixmap); + + if (isDown()) { + p.setPen(Qt::NoPen); + p.setBrush(QColor(0, 0, 0, 20)); + p.drawRoundedRect(rect().adjusted(1, 1, -1, -1), 1, 1); + } + if (hasFocus()) { + QStyleOptionFocusRect option; + option.initFrom(this); + style()->drawPrimitive(QStyle::PE_FrameFocusRect, &option, &p, this); + } +} + +QPixmap ExpandButton::cacheRendering() +{ + const QSize size = contentsRect().size(); + const qreal pixelRatio = devicePixelRatio(); + QPixmap pixmap(size * pixelRatio); + pixmap.setDevicePixelRatio(pixelRatio); + pixmap.fill(Qt::transparent); + QPainter p(&pixmap); + p.setRenderHint(QPainter::Antialiasing, true); + p.translate(0.5, 0.5); + p.setPen(Qt::NoPen); + p.drawRoundedRect(0, 0, size.width(), size.height(), 1, 1); + int arrowsize = 15; + QStyleOption arrowOpt; + arrowOpt.initFrom(this); + QPalette pal = arrowOpt.palette; + pal.setBrush(QPalette::All, QPalette::Text, QColor(0, 0, 0)); + arrowOpt.rect = QRect(size.width() - arrowsize - 6, height() / 2 - arrowsize / 2, + arrowsize, arrowsize); + arrowOpt.palette = pal; + style()->drawPrimitive(isChecked() ? QStyle::PE_IndicatorArrowUp + : QStyle::PE_IndicatorArrowDown, &arrowOpt, &p, this); + return pixmap; +} diff --git a/src/libs/utils/detailsbutton.h b/src/libs/utils/detailsbutton.h index f66fa2ad4b..dabbe75918 100644 --- a/src/libs/utils/detailsbutton.h +++ b/src/libs/utils/detailsbutton.h @@ -78,4 +78,22 @@ private: QPixmap m_uncheckedPixmap; float m_fader; }; + +class QTCREATOR_UTILS_EXPORT ExpandButton : public QAbstractButton +{ + Q_OBJECT + +public: + ExpandButton(QWidget *parent = nullptr); + +private: + void paintEvent(QPaintEvent *e) override; + QSize sizeHint() const override; + + QPixmap cacheRendering(); + + QPixmap m_checkedPixmap; + QPixmap m_uncheckedPixmap; +}; + } // namespace Utils diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index d61a75ebf7..1d8498ef0d 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -39,11 +39,12 @@ Q_GLOBAL_STATIC_WITH_ARGS(Utils::Environment, staticSystemEnvironment, Q_GLOBAL_STATIC(QVector<Utils::EnvironmentProvider>, environmentProviders) -static QMap<QString, QString>::iterator findKey(QMap<QString, QString> &input, Utils::OsType osType, - const QString &key) +namespace Utils { + +static NameValueMap::iterator findKey(NameValueMap &input, Utils::OsType osType, const QString &key) { - const Qt::CaseSensitivity casing - = (osType == Utils::OsTypeWindows) ? Qt::CaseInsensitive : Qt::CaseSensitive; + const Qt::CaseSensitivity casing = (osType == Utils::OsTypeWindows) ? Qt::CaseInsensitive + : Qt::CaseSensitive; for (auto it = input.begin(); it != input.end(); ++it) { if (key.compare(it.key(), casing) == 0) return it; @@ -51,12 +52,12 @@ static QMap<QString, QString>::iterator findKey(QMap<QString, QString> &input, U return input.end(); } -static QMap<QString, QString>::const_iterator findKey(const QMap<QString, QString> &input, - Utils::OsType osType, - const QString &key) +static NameValueMap::const_iterator findKey(const NameValueMap &input, + Utils::OsType osType, + const QString &key) { - const Qt::CaseSensitivity casing - = (osType == Utils::OsTypeWindows) ? Qt::CaseInsensitive : Qt::CaseSensitive; + const Qt::CaseSensitivity casing = (osType == Utils::OsTypeWindows) ? Qt::CaseInsensitive + : Qt::CaseSensitive; for (auto it = input.constBegin(); it != input.constEnd(); ++it) { if (key.compare(it.key(), casing) == 0) return it; @@ -64,198 +65,6 @@ static QMap<QString, QString>::const_iterator findKey(const QMap<QString, QStrin return input.constEnd(); } -namespace Utils { - -enum : char -{ -#ifdef Q_OS_WIN - pathSepC = ';' -#else - pathSepC = ':' -#endif -}; - -void EnvironmentItem::sort(QList<EnvironmentItem> *list) -{ - Utils::sort(*list, &EnvironmentItem::name); -} - -QList<EnvironmentItem> EnvironmentItem::fromStringList(const QStringList &list) -{ - QList<EnvironmentItem> result; - for (const QString &string : list) { - int pos = string.indexOf('=', 1); - if (pos == -1) - result.append(EnvironmentItem(string, QString(), EnvironmentItem::Unset)); - else - result.append(EnvironmentItem(string.left(pos), string.mid(pos + 1))); - } - return result; -} - -QStringList EnvironmentItem::toStringList(const QList<EnvironmentItem> &list) -{ - return Utils::transform(list, [](const EnvironmentItem &item) { - if (item.operation == EnvironmentItem::Unset) - return QString(item.name); - return QString(item.name + '=' + item.value); - }); -} - -QList<EnvironmentItem> EnvironmentItem::itemsFromVariantList(const QVariantList &list) -{ - return Utils::transform(list, [](const QVariant &item) { - return itemFromVariantList(item.toList()); - }); -} - -QVariantList EnvironmentItem::toVariantList(const QList<EnvironmentItem> &list) -{ - return Utils::transform(list, [](const EnvironmentItem &item) { - return QVariant(toVariantList(item)); - }); -} - -EnvironmentItem EnvironmentItem::itemFromVariantList(const QVariantList &list) -{ - QTC_ASSERT(list.size() == 3, return EnvironmentItem("", "")); - QString name = list.value(0).toString(); - Operation operation = Operation(list.value(1).toInt()); - QString value = list.value(2).toString(); - return EnvironmentItem(name, value, operation); -} - -QVariantList EnvironmentItem::toVariantList(const EnvironmentItem &item) -{ - return QVariantList() << item.name << item.operation << item.value; -} - -static QString expand(const Environment *e, QString value) -{ - int replaceCount = 0; - for (int i = 0; i < value.size(); ++i) { - if (value.at(i) == '$') { - if ((i + 1) < value.size()) { - const QChar &c = value.at(i+1); - int end = -1; - if (c == '(') - end = value.indexOf(')', i); - else if (c == '{') - end = value.indexOf('}', i); - if (end != -1) { - const QString &name = value.mid(i + 2, end - i - 2); - Environment::const_iterator it = e->constFind(name); - if (it != e->constEnd()) - value.replace(i, end - i + 1, it.value()); - ++replaceCount; - QTC_ASSERT(replaceCount < 100, break); - } - } - } - } - return value; -} - -QDebug operator<<(QDebug debug, const EnvironmentItem &i) -{ - QDebugStateSaver saver(debug); - debug.noquote(); - debug.nospace(); - debug << "EnvironmentItem("; - switch (i.operation) { - case EnvironmentItem::Set: - debug << "set \"" << i.name << "\" to \"" << i.value << '"'; - break; - case EnvironmentItem::Unset: - debug << "unset \"" << i.name << '"'; - break; - case EnvironmentItem::Prepend: - debug << "prepend to \"" << i.name << "\":\"" << i.value << '"'; - break; - case EnvironmentItem::Append: - debug << "append to \"" << i.name << "\":\"" << i.value << '"'; - break; - } - debug << ')'; - return debug; -} - -void EnvironmentItem::apply(Environment *e, Operation op) const -{ - switch (op) { - case Set: - e->set(name, expand(e, value)); - break; - case Unset: - e->unset(name); - break; - case Prepend: { - const Environment::const_iterator it = e->constFind(name); - if (it != e->constEnd()) { - QString v = it.value(); - const QChar pathSep{QLatin1Char(pathSepC)}; - int sepCount = 0; - if (v.startsWith(pathSep)) - ++sepCount; - if (value.endsWith(pathSep)) - ++sepCount; - if (sepCount == 2) - v.remove(0, 1); - else if (sepCount == 0) - v.prepend(pathSep); - v.prepend(expand(e, value)); - e->set(name, v); - } else { - apply(e, Set); - } - } - break; - case Append: { - const Environment::const_iterator it = e->constFind(name); - if (it != e->constEnd()) { - QString v = it.value(); - const QChar pathSep{QLatin1Char(pathSepC)}; - int sepCount = 0; - if (v.endsWith(pathSep)) - ++sepCount; - if (value.startsWith(pathSep)) - ++sepCount; - if (sepCount == 2) - v.chop(1); - else if (sepCount == 0) - v.append(pathSep); - v.append(expand(e, value)); - e->set(name, v); - } else { - apply(e, Set); - } - } - break; - } -} - -Environment::Environment(const QStringList &env, OsType osType) : m_osType(osType) -{ - for (const QString &s : env) { - int i = s.indexOf('=', 1); - if (i >= 0) { - const QString key = s.left(i); - if (!key.contains('=')) { - const QString value = s.mid(i + 1); - set(key, value); - } - } - } -} - -QStringList Environment::toStringList() const -{ - QStringList result; - for (auto it = m_values.constBegin(); it != m_values.constEnd(); ++it) - result.append(it.key() + '=' + it.value()); - return result; -} - QProcessEnvironment Environment::toProcessEnvironment() const { QProcessEnvironment result; @@ -264,27 +73,21 @@ QProcessEnvironment Environment::toProcessEnvironment() const return result; } -void Environment::set(const QString &key, const QString &value) +void Environment::appendOrSetPath(const QString &value) { - QTC_ASSERT(!key.contains('='), return); - auto it = findKey(m_values, m_osType, key); - if (it == m_values.end()) - m_values.insert(key, value); - else - it.value() = value; + appendOrSet("PATH", QDir::toNativeSeparators(value), + QString(OsSpecificAspects::pathListSeparator(m_osType))); } -void Environment::unset(const QString &key) +void Environment::prependOrSetPath(const QString &value) { - QTC_ASSERT(!key.contains('='), return); - auto it = findKey(m_values, m_osType, key); - if (it != m_values.end()) - m_values.erase(it); + prependOrSet("PATH", QDir::toNativeSeparators(value), + QString(OsSpecificAspects::pathListSeparator(m_osType))); } void Environment::appendOrSet(const QString &key, const QString &value, const QString &sep) { - QTC_ASSERT(!key.contains('='), return); + QTC_ASSERT(!key.contains('='), return ); auto it = findKey(m_values, m_osType, key); if (it == m_values.end()) { m_values.insert(key, value); @@ -296,9 +99,9 @@ void Environment::appendOrSet(const QString &key, const QString &value, const QS } } -void Environment::prependOrSet(const QString&key, const QString &value, const QString &sep) +void Environment::prependOrSet(const QString &key, const QString &value, const QString &sep) { - QTC_ASSERT(!key.contains('='), return); + QTC_ASSERT(!key.contains('='), return ); auto it = findKey(m_values, m_osType, key); if (it == m_values.end()) { m_values.insert(key, value); @@ -310,18 +113,6 @@ void Environment::prependOrSet(const QString&key, const QString &value, const QS } } -void Environment::appendOrSetPath(const QString &value) -{ - appendOrSet("PATH", QDir::toNativeSeparators(value), - QString(OsSpecificAspects::pathListSeparator(m_osType))); -} - -void Environment::prependOrSetPath(const QString &value) -{ - prependOrSet("PATH", QDir::toNativeSeparators(value), - QString(OsSpecificAspects::pathListSeparator(m_osType))); -} - void Environment::prependOrSetLibrarySearchPath(const QString &value) { switch (m_osType) { @@ -387,11 +178,6 @@ void Environment::setupEnglishOutput(QStringList *environment) *environment = env.toStringList(); } -void Environment::clear() -{ - m_values.clear(); -} - FilePath Environment::searchInDirectory(const QStringList &execs, const FilePath &directory, QSet<FilePath> &alreadyChecked) const { @@ -489,127 +275,17 @@ FilePath Environment::searchInPath(const QString &executable, FilePathList Environment::path() const { - const QStringList pathComponents = value("PATH") - .split(OsSpecificAspects::pathListSeparator(m_osType), QString::SkipEmptyParts); - return Utils::transform(pathComponents, &FilePath::fromUserInput); -} - -QString Environment::value(const QString &key) const -{ - const auto it = findKey(m_values, m_osType, key); - return it != m_values.end() ? it.value() : QString(); -} - -QString Environment::key(Environment::const_iterator it) const -{ - return it.key(); -} - -QString Environment::value(Environment::const_iterator it) const -{ - return it.value(); -} - -Environment::const_iterator Environment::constBegin() const -{ - return m_values.constBegin(); -} - -Environment::const_iterator Environment::constEnd() const -{ - return m_values.constEnd(); -} - -Environment::const_iterator Environment::constFind(const QString &name) const -{ - return findKey(m_values, m_osType, name); -} - -int Environment::size() const -{ - return m_values.size(); + return pathListValue("PATH"); } -void Environment::modify(const QList<EnvironmentItem> & list) +FilePathList Environment::pathListValue(const QString &varName) const { - Environment resultEnvironment = *this; - for (const EnvironmentItem &item : list) - item.apply(&resultEnvironment); - *this = resultEnvironment; -} - -QList<EnvironmentItem> Environment::diff(const Environment &other, bool checkAppendPrepend) const -{ - QMap<QString, QString>::const_iterator thisIt = constBegin(); - QMap<QString, QString>::const_iterator otherIt = other.constBegin(); - - QList<EnvironmentItem> result; - while (thisIt != constEnd() || otherIt != other.constEnd()) { - if (thisIt == constEnd()) { - result.append(EnvironmentItem(otherIt.key(), otherIt.value())); - ++otherIt; - } else if (otherIt == other.constEnd()) { - result.append(EnvironmentItem(thisIt.key(), QString(), EnvironmentItem::Unset)); - ++thisIt; - } else if (thisIt.key() < otherIt.key()) { - result.append(EnvironmentItem(thisIt.key(), QString(), EnvironmentItem::Unset)); - ++thisIt; - } else if (thisIt.key() > otherIt.key()) { - result.append(EnvironmentItem(otherIt.key(), otherIt.value())); - ++otherIt; - } else { - const QString &oldValue = thisIt.value(); - const QString &newValue = otherIt.value(); - if (oldValue != newValue) { - if (checkAppendPrepend && newValue.startsWith(oldValue)) { - QString appended = newValue.right(newValue.size() - oldValue.size()); - if (appended.startsWith(QLatin1Char(pathSepC))) - appended.remove(0, 1); - result.append(EnvironmentItem(otherIt.key(), appended, - EnvironmentItem::Append)); - } else if (checkAppendPrepend && newValue.endsWith(oldValue)) { - QString prepended = newValue.left(newValue.size() - oldValue.size()); - if (prepended.endsWith(QLatin1Char(pathSepC))) - prepended.chop(1); - result.append(EnvironmentItem(otherIt.key(), prepended, - EnvironmentItem::Prepend)); - } else { - result.append(EnvironmentItem(otherIt.key(), newValue)); - } - } - ++otherIt; - ++thisIt; - } - } - return result; -} - -bool Environment::hasKey(const QString &key) const -{ - return m_values.contains(key); -} - -OsType Environment::osType() const -{ - return m_osType; -} - -QString Environment::userName() const -{ - return value(QString::fromLatin1(m_osType == OsTypeWindows ? "USERNAME" : "USER")); -} - -bool Environment::operator!=(const Environment &other) const -{ - return !(*this == other); -} - -bool Environment::operator==(const Environment &other) const -{ - return m_osType == other.m_osType && m_values == other.m_values; + const QStringList pathComponents = value(varName) + .split(OsSpecificAspects::pathListSeparator(m_osType), QString::SkipEmptyParts); + return transform(pathComponents, &FilePath::fromUserInput); } -void Environment::modifySystemEnvironment(const QList<EnvironmentItem> &list) +void Environment::modifySystemEnvironment(const EnvironmentItems &list) { staticSystemEnvironment->modify(list); } diff --git a/src/libs/utils/environment.h b/src/libs/utils/environment.h index f1e957ab6d..73c883268e 100644 --- a/src/libs/utils/environment.h +++ b/src/libs/utils/environment.h @@ -27,8 +27,9 @@ #include "fileutils.h" #include "hostosinfo.h" +#include "namevaluedictionary.h" +#include "namevalueitem.h" #include "optional.h" -#include "utils_global.h" #include <QMap> #include <QStringList> @@ -39,71 +40,18 @@ QT_FORWARD_DECLARE_CLASS(QDebug) QT_FORWARD_DECLARE_CLASS(QProcessEnvironment) namespace Utils { -class Environment; -class QTCREATOR_UTILS_EXPORT EnvironmentItem +class QTCREATOR_UTILS_EXPORT Environment final : public NameValueDictionary { public: - enum Operation { Set, Unset, Prepend, Append }; + using NameValueDictionary::NameValueDictionary; - EnvironmentItem(const QString &n, const QString &v, Operation op = Set) - : name(n), value(v), operation(op) - {} - - void apply(Environment *e) const { apply(e, operation); } - - QString name; - QString value; - Operation operation; - - bool operator==(const EnvironmentItem &other) const - { - return operation == other.operation && name == other.name && value == other.value; - } - - bool operator!=(const EnvironmentItem &other) const - { - return !(*this == other); - } - - static void sort(QList<EnvironmentItem> *list); - static QList<EnvironmentItem> fromStringList(const QStringList &list); - static QStringList toStringList(const QList<EnvironmentItem> &list); - static QList<EnvironmentItem> itemsFromVariantList(const QVariantList &list); - static QVariantList toVariantList(const QList<EnvironmentItem> &list); - static EnvironmentItem itemFromVariantList(const QVariantList &list); - static QVariantList toVariantList(const EnvironmentItem &item); - -private: - void apply(Environment *e, Operation op) const; -}; - -QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug debug, const EnvironmentItem &i); - -class QTCREATOR_UTILS_EXPORT Environment -{ -public: - using const_iterator = QMap<QString, QString>::const_iterator; - - explicit Environment(OsType osType = HostOsInfo::hostOs()) : m_osType(osType) {} - explicit Environment(const QStringList &env, OsType osType = HostOsInfo::hostOs()); static Environment systemEnvironment(); static void setupEnglishOutput(Environment *environment); static void setupEnglishOutput(QProcessEnvironment *environment); static void setupEnglishOutput(QStringList *environment); - QStringList toStringList() const; QProcessEnvironment toProcessEnvironment() const; - QString value(const QString &key) const; - void set(const QString &key, const QString &value); - void unset(const QString &key); - void modify(const QList<EnvironmentItem> &list); - /// Return the Environment changes necessary to modify this into the other environment. - QList<EnvironmentItem> diff(const Environment &other, bool checkAppendPrepend = false) const; - bool hasKey(const QString &key) const; - OsType osType() const; - - QString userName() const; void appendOrSet(const QString &key, const QString &value, const QString &sep = QString()); void prependOrSet(const QString &key, const QString &value, const QString &sep = QString()); @@ -114,22 +62,13 @@ public: void prependOrSetLibrarySearchPath(const QString &value); void prependOrSetLibrarySearchPaths(const QStringList &values); - void clear(); - int size() const; - - QString key(Environment::const_iterator it) const; - QString value(Environment::const_iterator it) const; - - Environment::const_iterator constBegin() const; - Environment::const_iterator constEnd() const; - Environment::const_iterator constFind(const QString &name) const; - using PathFilter = std::function<bool(const FilePath &)>; FilePath searchInPath(const QString &executable, const FilePathList &additionalDirs = FilePathList(), const PathFilter &func = PathFilter()) const; FilePathList path() const; + FilePathList pathListValue(const QString &varName) const; QStringList appendExeExtensions(const QString &executable) const; bool isSameExecutable(const QString &exe1, const QString &exe2) const; @@ -138,16 +77,11 @@ public: FilePath expandVariables(const FilePath &input) const; QStringList expandVariables(const QStringList &input) const; - bool operator!=(const Environment &other) const; - bool operator==(const Environment &other) const; - - static void modifySystemEnvironment(const QList<EnvironmentItem> &list); // use with care!!! + static void modifySystemEnvironment(const EnvironmentItems &list); // use with care!!! private: FilePath searchInDirectory(const QStringList &execs, const FilePath &directory, QSet<FilePath> &alreadyChecked) const; - QMap<QString, QString> m_values; - OsType m_osType; }; class QTCREATOR_UTILS_EXPORT EnvironmentProvider diff --git a/src/libs/utils/environmentdialog.cpp b/src/libs/utils/environmentdialog.cpp index 07e6155c7d..e9c9fd620f 100644 --- a/src/libs/utils/environmentdialog.cpp +++ b/src/libs/utils/environmentdialog.cpp @@ -35,144 +35,19 @@ namespace Utils { -namespace Internal { - -static QList<EnvironmentItem> cleanUp( - const QList<EnvironmentItem> &items) -{ - QList<EnvironmentItem> uniqueItems; - QSet<QString> uniqueSet; - for (int i = items.count() - 1; i >= 0; i--) { - EnvironmentItem item = items.at(i); - if (HostOsInfo::isWindowsHost()) - item.name = item.name.toUpper(); - const QString &itemName = item.name; - QString emptyName = itemName; - emptyName.remove(QLatin1Char(' ')); - if (!emptyName.isEmpty() && !uniqueSet.contains(itemName)) { - uniqueItems.prepend(item); - uniqueSet.insert(itemName); - } - } - return uniqueItems; -} - -class EnvironmentItemsWidget : public QWidget -{ - Q_OBJECT -public: - explicit EnvironmentItemsWidget(QWidget *parent = nullptr); - - void setEnvironmentItems(const QList<EnvironmentItem> &items); - QList<EnvironmentItem> environmentItems() const; - - void setPlaceholderText(const QString &text); - -private: - QPlainTextEdit *m_editor; -}; - -EnvironmentItemsWidget::EnvironmentItemsWidget(QWidget *parent) : - QWidget(parent) -{ - m_editor = new QPlainTextEdit(this); - auto layout = new QVBoxLayout(this); - layout->setMargin(0); - layout->addWidget(m_editor); -} - -void EnvironmentItemsWidget::setEnvironmentItems(const QList<EnvironmentItem> &items) -{ - QList<EnvironmentItem> sortedItems = items; - EnvironmentItem::sort(&sortedItems); - const QStringList list = EnvironmentItem::toStringList(sortedItems); - m_editor->document()->setPlainText(list.join(QLatin1Char('\n'))); -} - -QList<EnvironmentItem> EnvironmentItemsWidget::environmentItems() const -{ - const QStringList list = m_editor->document()->toPlainText().split(QLatin1String("\n")); - QList<EnvironmentItem> items = EnvironmentItem::fromStringList(list); - return cleanUp(items); -} - -void EnvironmentItemsWidget::setPlaceholderText(const QString &text) -{ - m_editor->setPlaceholderText(text); -} - -class EnvironmentDialogPrivate -{ -public: - EnvironmentItemsWidget *m_editor; -}; - -} // namespace Internal - -EnvironmentDialog::EnvironmentDialog(QWidget *parent) : - QDialog(parent), d(new Internal::EnvironmentDialogPrivate) -{ - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - resize(640, 480); - d->m_editor = new Internal::EnvironmentItemsWidget(this); - auto box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this); - connect(box, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(box, &QDialogButtonBox::rejected, this, &QDialog::reject); - - auto helpLabel = new QLabel(this); - helpLabel->setText(tr("Enter one environment variable per line.\n" - "To set or change a variable, use VARIABLE=VALUE.\n" - "Existing variables can be referenced in a VALUE with ${OTHER}.\n" - "To clear a variable, put its name on a line with nothing else on it.")); - - auto layout = new QVBoxLayout(this); - layout->addWidget(d->m_editor); - layout->addWidget(helpLabel); - - layout->addWidget(box); - - setWindowTitle(tr("Edit Environment")); -} - -EnvironmentDialog::~EnvironmentDialog() -{ - delete d; -} - -void EnvironmentDialog::setEnvironmentItems(const QList<EnvironmentItem> &items) -{ - d->m_editor->setEnvironmentItems(items); -} - -QList<EnvironmentItem> EnvironmentDialog::environmentItems() const +Utils::optional<EnvironmentItems> EnvironmentDialog::getEnvironmentItems( + QWidget *parent, const EnvironmentItems &initial, const QString &placeholderText, Polisher polisher) { - return d->m_editor->environmentItems(); -} - -void EnvironmentDialog::setPlaceholderText(const QString &text) -{ - d->m_editor->setPlaceholderText(text); -} - -QList<EnvironmentItem> EnvironmentDialog::getEnvironmentItems(bool *ok, - QWidget *parent, - const QList<EnvironmentItem> &initial, - const QString &placeholderText, - Polisher polisher) -{ - EnvironmentDialog dlg(parent); - if (polisher) - polisher(&dlg); - dlg.setEnvironmentItems(initial); - dlg.setPlaceholderText(placeholderText); - bool result = dlg.exec() == QDialog::Accepted; - if (ok) - *ok = result; - if (result) - return dlg.environmentItems(); - return QList<EnvironmentItem>(); + return getNameValueItems( + parent, + initial, + placeholderText, + polisher, + tr("Edit Environment"), + tr("Enter one environment variable per line.\n" + "To set or change a variable, use VARIABLE=VALUE.\n" + "Existing variables can be referenced in a VALUE with ${OTHER}.\n" + "To clear a variable, put its name on a line with nothing else on it.")); } } // namespace Utils - -#include "environmentdialog.moc" diff --git a/src/libs/utils/environmentdialog.h b/src/libs/utils/environmentdialog.h index be8218508b..6dbb38e191 100644 --- a/src/libs/utils/environmentdialog.h +++ b/src/libs/utils/environmentdialog.h @@ -25,36 +25,20 @@ #pragma once -#include "utils_global.h" #include "environment.h" - -#include <QDialog> +#include "namevaluesdialog.h" +#include <thread> namespace Utils { -namespace Internal { class EnvironmentDialogPrivate; } - -class QTCREATOR_UTILS_EXPORT EnvironmentDialog : public QDialog +class QTCREATOR_UTILS_EXPORT EnvironmentDialog : public NameValuesDialog { Q_OBJECT public: - explicit EnvironmentDialog(QWidget *parent = nullptr); - ~EnvironmentDialog() override; - - void setEnvironmentItems(const QList<EnvironmentItem> &items); - QList<EnvironmentItem> environmentItems() const; - - void setPlaceholderText(const QString &text); - - using Polisher = std::function<void(QWidget*)>; - static QList<EnvironmentItem> getEnvironmentItems(bool *ok, - QWidget *parent = nullptr, - const QList<EnvironmentItem> &initial = QList<EnvironmentItem>(), - const QString &placeholderText = QString(), - Polisher polish = Polisher()); - -private: - Internal::EnvironmentDialogPrivate *d; + static Utils::optional<EnvironmentItems> getEnvironmentItems(QWidget *parent = nullptr, + const EnvironmentItems &initial = {}, + const QString &placeholderText = {}, + Polisher polish = {}); }; } // namespace Utils diff --git a/src/libs/utils/environmentfwd.h b/src/libs/utils/environmentfwd.h new file mode 100644 index 0000000000..04a57418b5 --- /dev/null +++ b/src/libs/utils/environmentfwd.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 <QtGlobal> + +QT_BEGIN_NAMESPACE +template<typename Type> +class QList; +class QTreeView; +QT_END_NAMESPACE + +namespace Utils { +class NameValueDictionary; +class NameValueItem; +using NameValueItems = QVector<NameValueItem>; + +class Environment; +using EnvironmentItem = NameValueItem; +using EnvironmentItems = NameValueItems; + +class PreprocessorMacroDictionary; +using PreprocessorMacroItem = NameValueItem; +using PreprocessorMacroItems = NameValueItems; + +class NameValueModel; +class EnvironmentModel; +} // namespace Utils diff --git a/src/libs/utils/environmentmodel.cpp b/src/libs/utils/environmentmodel.cpp index 045a6d2b65..c7efcce930 100644 --- a/src/libs/utils/environmentmodel.cpp +++ b/src/libs/utils/environmentmodel.cpp @@ -33,360 +33,12 @@ #include <QFont> namespace Utils { -namespace Internal { - -class EnvironmentModelPrivate -{ -public: - void updateResultEnvironment() - { - m_resultEnvironment = m_baseEnvironment; - m_resultEnvironment.modify(m_items); - // Add removed variables again and mark them as "<UNSET>" so - // that the user can actually see those removals: - foreach (const EnvironmentItem &item, m_items) { - if (item.operation == EnvironmentItem::Unset) - m_resultEnvironment.set(item.name, EnvironmentModel::tr("<UNSET>")); - } - } - - int findInChanges(const QString &name) const - { - for (int i=0; i<m_items.size(); ++i) - if (m_items.at(i).name == name) - return i; - return -1; - } - - int findInResultInsertPosition(const QString &name) const - { - Environment::const_iterator it; - int i = 0; - for (it = m_resultEnvironment.constBegin(); it != m_resultEnvironment.constEnd(); ++it, ++i) - if (m_resultEnvironment.key(it) > name) - return i; - return m_resultEnvironment.size(); - } - - int findInResult(const QString &name) const - { - Environment::const_iterator it; - int i = 0; - for (it = m_resultEnvironment.constBegin(); it != m_resultEnvironment.constEnd(); ++it, ++i) - if (m_resultEnvironment.key(it) == name) - return i; - return -1; - } - - Environment m_baseEnvironment; - Environment m_resultEnvironment; - QList<EnvironmentItem> m_items; -}; - -} // namespace Internal - -EnvironmentModel::EnvironmentModel(QObject *parent) : - QAbstractTableModel(parent), - d(new Internal::EnvironmentModelPrivate) -{ } - -EnvironmentModel::~EnvironmentModel() -{ - delete d; -} - -QString EnvironmentModel::indexToVariable(const QModelIndex &index) const +const Environment &EnvironmentModel::baseEnvironment() const { - return d->m_resultEnvironment.key(d->m_resultEnvironment.constBegin() + index.row()); + return static_cast<const Environment &>(baseNameValueDictionary()); } - void EnvironmentModel::setBaseEnvironment(const Environment &env) { - if (d->m_baseEnvironment == env) - return; - beginResetModel(); - d->m_baseEnvironment = env; - d->updateResultEnvironment(); - endResetModel(); -} - -int EnvironmentModel::rowCount(const QModelIndex &parent) const -{ - if (parent.isValid()) - return 0; - - return d->m_resultEnvironment.size(); -} -int EnvironmentModel::columnCount(const QModelIndex &parent) const -{ - if (parent.isValid()) - return 0; - - return 2; -} - -bool EnvironmentModel::changes(const QString &name) const -{ - return d->findInChanges(name) >= 0; -} - -Environment EnvironmentModel::baseEnvironment() const -{ - return d->m_baseEnvironment; -} - -QVariant EnvironmentModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::ToolTipRole) { - if (index.column() == 0) { - return d->m_resultEnvironment.key(d->m_resultEnvironment.constBegin() + index.row()); - } else if (index.column() == 1) { - // Do not return "<UNSET>" when editing a previously unset variable: - if (role == Qt::EditRole) { - int pos = d->findInChanges(indexToVariable(index)); - if (pos >= 0) - return d->m_items.at(pos).value; - } - QString value = d->m_resultEnvironment.value(d->m_resultEnvironment.constBegin() + index.row()); - if (role == Qt::ToolTipRole && value.length() > 80) { - // Use html to enable text wrapping - value = value.toHtmlEscaped(); - value.prepend(QLatin1String("<html><body>")); - value.append(QLatin1String("</body></html>")); - } - return value; - } - } - if (role == Qt::FontRole) { - // check whether this environment variable exists in d->m_items - if (changes(d->m_resultEnvironment.key(d->m_resultEnvironment.constBegin() + index.row()))) { - QFont f; - f.setBold(true); - return QVariant(f); - } - return QFont(); - } - return QVariant(); -} - -Qt::ItemFlags EnvironmentModel::flags(const QModelIndex &index) const -{ - Q_UNUSED(index) - return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled; -} - -QVariant EnvironmentModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Vertical || role != Qt::DisplayRole) - return QVariant(); - return section == 0 ? tr("Variable") : tr("Value"); + setBaseNameValueDictionary(env); } - -/// ***************** -/// Utility functions -/// ***************** -QModelIndex EnvironmentModel::variableToIndex(const QString &name) const -{ - int row = d->findInResult(name); - if (row == -1) - return QModelIndex(); - return index(row, 0); -} - -bool EnvironmentModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (!index.isValid() || role != Qt::EditRole) - return false; - - // ignore changes to already set values: - if (data(index, role) == value) - return true; - - const QString oldName = data(this->index(index.row(), 0, QModelIndex())).toString(); - const QString oldValue = data(this->index(index.row(), 1, QModelIndex()), Qt::EditRole).toString(); - int changesPos = d->findInChanges(oldName); - - if (index.column() == 0) { - //fail if a variable with the same name already exists - const QString &newName = HostOsInfo::isWindowsHost() - ? value.toString().toUpper() : value.toString(); - if (newName.isEmpty() || newName.contains('=')) - return false; - // Does the new name exist already? - if (d->m_resultEnvironment.hasKey(newName) || newName.isEmpty()) - return false; - - EnvironmentItem newVariable(newName, oldValue); - - if (changesPos != -1) - resetVariable(oldName); // restore the original base variable again - - QModelIndex newIndex = addVariable(newVariable); // add the new variable - emit focusIndex(newIndex.sibling(newIndex.row(), 1)); // hint to focus on the value - return true; - } else if (index.column() == 1) { - // We are changing an existing value: - const QString stringValue = value.toString(); - if (changesPos != -1) { - // We have already changed this value - if (d->m_baseEnvironment.hasKey(oldName) && stringValue == d->m_baseEnvironment.value(oldName)) { - // ... and now went back to the base value - d->m_items.removeAt(changesPos); - } else { - // ... and changed it again - d->m_items[changesPos].value = stringValue; - d->m_items[changesPos].operation = EnvironmentItem::Set; - } - } else { - // Add a new change item: - d->m_items.append(EnvironmentItem(oldName, stringValue)); - } - d->updateResultEnvironment(); - emit dataChanged(index, index); - emit userChangesChanged(); - return true; - } - return false; -} - -QModelIndex EnvironmentModel::addVariable() -{ - //: Name when inserting a new variable - return addVariable(EnvironmentItem(tr("<VARIABLE>"), - //: Value when inserting a new variable - tr("<VALUE>"))); -} - -QModelIndex EnvironmentModel::addVariable(const EnvironmentItem &item) -{ - - // Return existing index if the name is already in the result set: - int pos = d->findInResult(item.name); - if (pos >= 0 && pos < d->m_resultEnvironment.size()) - return index(pos, 0, QModelIndex()); - - int insertPos = d->findInResultInsertPosition(item.name); - int changePos = d->findInChanges(item.name); - if (d->m_baseEnvironment.hasKey(item.name)) { - // We previously unset this! - Q_ASSERT(changePos >= 0); - // Do not insert a line here as we listed the variable as <UNSET> before! - Q_ASSERT(d->m_items.at(changePos).name == item.name); - Q_ASSERT(d->m_items.at(changePos).operation == EnvironmentItem::Unset); - Q_ASSERT(d->m_items.at(changePos).value.isEmpty()); - d->m_items[changePos] = item; - emit dataChanged(index(insertPos, 0, QModelIndex()), index(insertPos, 1, QModelIndex())); - } else { - // We add something that is not in the base environment - // Insert a new line! - beginInsertRows(QModelIndex(), insertPos, insertPos); - Q_ASSERT(changePos < 0); - d->m_items.append(item); - d->updateResultEnvironment(); - endInsertRows(); - } - emit userChangesChanged(); - return index(insertPos, 0, QModelIndex()); -} - -void EnvironmentModel::resetVariable(const QString &name) -{ - int rowInChanges = d->findInChanges(name); - if (rowInChanges < 0) - return; - - int rowInResult = d->findInResult(name); - if (rowInResult < 0) - return; - - if (d->m_baseEnvironment.hasKey(name)) { - d->m_items.removeAt(rowInChanges); - d->updateResultEnvironment(); - emit dataChanged(index(rowInResult, 0, QModelIndex()), index(rowInResult, 1, QModelIndex())); - emit userChangesChanged(); - } else { - // Remove the line completely! - beginRemoveRows(QModelIndex(), rowInResult, rowInResult); - d->m_items.removeAt(rowInChanges); - d->updateResultEnvironment(); - endRemoveRows(); - emit userChangesChanged(); - } -} - -void EnvironmentModel::unsetVariable(const QString &name) -{ - // This does not change the number of rows as we will display a <UNSET> - // in place of the original variable! - int row = d->findInResult(name); - if (row < 0) - return; - - // look in d->m_items for the variable - int pos = d->findInChanges(name); - if (pos != -1) { - d->m_items[pos].operation = EnvironmentItem::Unset; - d->m_items[pos].value.clear(); - d->updateResultEnvironment(); - emit dataChanged(index(row, 0, QModelIndex()), index(row, 1, QModelIndex())); - emit userChangesChanged(); - return; - } - d->m_items.append(EnvironmentItem(name, QString(), EnvironmentItem::Unset)); - d->updateResultEnvironment(); - emit dataChanged(index(row, 0, QModelIndex()), index(row, 1, QModelIndex())); - emit userChangesChanged(); -} - -bool EnvironmentModel::canUnset(const QString &name) -{ - int pos = d->findInChanges(name); - if (pos != -1) - return d->m_items.at(pos).operation == EnvironmentItem::Unset; - else - return false; -} - -bool EnvironmentModel::canReset(const QString &name) -{ - return d->m_baseEnvironment.hasKey(name); -} - -QList<EnvironmentItem> EnvironmentModel::userChanges() const -{ - return d->m_items; -} - -void EnvironmentModel::setUserChanges(const QList<EnvironmentItem> &list) -{ - QList<EnvironmentItem> filtered = Utils::filtered(list, [](const EnvironmentItem &i) { - return i.name != "export " && !i.name.contains('='); - }); - // We assume nobody is reordering the items here. - if (filtered == d->m_items) - return; - beginResetModel(); - d->m_items = filtered; - for (EnvironmentItem &item : d->m_items) { - QString &name = item.name; - name = name.trimmed(); - if (name.startsWith("export ")) - name = name.mid(7).trimmed(); - if (d->m_baseEnvironment.osType() == OsTypeWindows) { - // Environment variable names are case-insensitive under windows, but we still - // want to preserve the case of pre-existing variables. - auto it = d->m_baseEnvironment.constFind(name); - if (it != d->m_baseEnvironment.constEnd()) - name = d->m_baseEnvironment.key(it); - } - } - - d->updateResultEnvironment(); - endResetModel(); - emit userChangesChanged(); -} - } // namespace Utils diff --git a/src/libs/utils/environmentmodel.h b/src/libs/utils/environmentmodel.h index db20224651..ac5f017448 100644 --- a/src/libs/utils/environmentmodel.h +++ b/src/libs/utils/environmentmodel.h @@ -25,55 +25,17 @@ #pragma once -#include "utils_global.h" - -#include <QAbstractTableModel> +#include "namevaluemodel.h" namespace Utils { -class Environment; -class EnvironmentItem; - -namespace Internal { class EnvironmentModelPrivate; } -class QTCREATOR_UTILS_EXPORT EnvironmentModel : public QAbstractTableModel +class QTCREATOR_UTILS_EXPORT EnvironmentModel : public NameValueModel { Q_OBJECT public: - explicit EnvironmentModel(QObject *parent = nullptr); - ~EnvironmentModel() override; - - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const override; - - QModelIndex addVariable(); - QModelIndex addVariable(const EnvironmentItem &item); - void resetVariable(const QString &name); - void unsetVariable(const QString &name); - bool canUnset(const QString &name); - bool canReset(const QString &name); - QString indexToVariable(const QModelIndex &index) const; - QModelIndex variableToIndex(const QString &name) const; - bool changes(const QString &key) const; - Environment baseEnvironment() const; + const Environment &baseEnvironment() const; void setBaseEnvironment(const Environment &env); - QList<EnvironmentItem> userChanges() const; - void setUserChanges(const QList<EnvironmentItem> &list); - -signals: - void userChangesChanged(); - /// Hint to the view where it should make sense to focus on next - // This is a hack since there is no way for a model to suggest - // the next interesting place to focus on to the view. - void focusIndex(const QModelIndex &index); - -private: - Internal::EnvironmentModelPrivate *d; }; } // namespace Utils diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index acf9c6190d..22fb05240b 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -74,6 +74,23 @@ namespace Utils { */ +CommandLine::CommandLine(const FilePath &executable) + : m_executable(executable) +{} + +CommandLine::CommandLine(const FilePath &exe, const QStringList &args, MetaCharMode metaCharMode) + : m_executable(exe) + , m_metaCharMode(metaCharMode) +{ + addArgs(args); +} + +CommandLine::CommandLine(const FilePath &exe, const QString &args, RawType) + : m_executable(exe) +{ + addArgs(args, Raw); +} + void CommandLine::addArg(const QString &arg, OsType osType) { QtcProcess::addArg(&m_arguments, arg, osType); @@ -85,7 +102,7 @@ void CommandLine::addArgs(const QStringList &inArgs, OsType osType) addArg(arg, osType); } -void CommandLine::addArgs(const QString &inArgs) +void CommandLine::addArgs(const QString &inArgs, RawType) { QtcProcess::addArgs(&m_arguments, inArgs); } @@ -95,6 +112,11 @@ QString CommandLine::toUserOutput() const return m_executable.toUserOutput() + ' ' + m_arguments; } +QStringList CommandLine::splitArguments(OsType osType) const +{ + return QtcProcess::splitArgs(m_arguments, osType); +} + /*! \class Utils::FileUtils \brief The FileUtils class contains file and directory related convenience diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index ec018b9c3b..4b5ce6dc48 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -131,24 +131,32 @@ using FileNameList = FilePathList; class QTCREATOR_UTILS_EXPORT CommandLine { public: - CommandLine() {} + enum RawType { Raw }; + enum class MetaCharMode { Abort, Ignore }; - CommandLine(const FilePath &executable, const QString &arguments) - : m_executable(executable), m_arguments(arguments) - {} + CommandLine() {} + explicit CommandLine(const FilePath &executable); + CommandLine(const FilePath &exe, + const QStringList &args, + MetaCharMode metaCharMode = MetaCharMode::Abort); + CommandLine(const FilePath &exe, const QString &unparsedArgs, RawType); void addArg(const QString &arg, OsType osType = HostOsInfo::hostOs()); void addArgs(const QStringList &inArgs, OsType osType = HostOsInfo::hostOs()); - void addArgs(const QString &inArgs); + + void addArgs(const QString &inArgs, RawType); QString toUserOutput() const; FilePath executable() const { return m_executable; } QString arguments() const { return m_arguments; } + MetaCharMode metaCharMode() const { return m_metaCharMode; } + QStringList splitArguments(OsType osType = HostOsInfo::hostOs()) const; private: FilePath m_executable; QString m_arguments; + MetaCharMode m_metaCharMode; }; class QTCREATOR_UTILS_EXPORT FileUtils { diff --git a/src/libs/utils/listmodel.h b/src/libs/utils/listmodel.h index 5f4de811e6..4a28041b66 100644 --- a/src/libs/utils/listmodel.h +++ b/src/libs/utils/listmodel.h @@ -67,9 +67,9 @@ public: void clear() { rootItem()->removeChildren(); } - using const_iterator = typename QVector<TreeItem *>::const_iterator; - const_iterator begin() const { return rootItem()->begin(); } - const_iterator end() const { return rootItem()->end(); } + using const_iterator = typename QVector<ChildType *>::const_iterator; + const_iterator begin() const { return const_iterator(rootItem()->begin()); } + const_iterator end() const { return const_iterator(rootItem()->end()); } }; diff --git a/src/libs/utils/namevaluedictionary.cpp b/src/libs/utils/namevaluedictionary.cpp new file mode 100644 index 0000000000..30e3c18448 --- /dev/null +++ b/src/libs/utils/namevaluedictionary.cpp @@ -0,0 +1,282 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "algorithm.h" +#include "namevaluedictionary.h" +#include "qtcassert.h" + +#include <QDir> + +namespace Utils { + +namespace { +NameValueMap::iterator findKey(NameValueMap &input, Utils::OsType osType, const QString &key) +{ + const Qt::CaseSensitivity casing = (osType == Utils::OsTypeWindows) ? Qt::CaseInsensitive + : Qt::CaseSensitive; + for (auto it = input.begin(); it != input.end(); ++it) { + if (key.compare(it.key(), casing) == 0) + return it; + } + return input.end(); +} + +NameValueMap::const_iterator findKey(const NameValueMap &input, Utils::OsType osType, const QString &key) +{ + const Qt::CaseSensitivity casing = (osType == Utils::OsTypeWindows) ? Qt::CaseInsensitive + : Qt::CaseSensitive; + for (auto it = input.constBegin(); it != input.constEnd(); ++it) { + if (key.compare(it.key(), casing) == 0) + return it; + } + return input.constEnd(); +} +} // namespace + +NameValueDictionary::NameValueDictionary(const QStringList &env, OsType osType) + : m_osType(osType) +{ + for (const QString &s : env) { + int i = s.indexOf('=', 1); + if (i >= 0) { + const QString key = s.left(i); + if (!key.contains('=')) { + const QString value = s.mid(i + 1); + set(key, value); + } + } + } +} + +NameValueDictionary::NameValueDictionary(const NameValuePairs &nameValues) +{ + for (const auto &nameValue : nameValues) + set(nameValue.first, nameValue.second); +} + +QStringList NameValueDictionary::toStringList() const +{ + QStringList result; + for (auto it = m_values.constBegin(); it != m_values.constEnd(); ++it) + result.append(it.key() + '=' + it.value()); + return result; +} + +void NameValueDictionary::set(const QString &key, const QString &value) +{ + QTC_ASSERT(!key.contains('='), return ); + auto it = findKey(m_values, m_osType, key); + if (it == m_values.end()) + m_values.insert(key, value); + else + it.value() = value; +} + +void NameValueDictionary::unset(const QString &key) +{ + QTC_ASSERT(!key.contains('='), return ); + auto it = findKey(m_values, m_osType, key); + if (it != m_values.end()) + m_values.erase(it); +} + +void NameValueDictionary::clear() +{ + m_values.clear(); +} + +QString NameValueDictionary::value(const QString &key) const +{ + const auto it = findKey(m_values, m_osType, key); + return it != m_values.end() ? it.value() : QString(); +} + +NameValueDictionary::const_iterator NameValueDictionary::constFind(const QString &name) const +{ + return findKey(m_values, m_osType, name); +} + +int NameValueDictionary::size() const +{ + return m_values.size(); +} + +void NameValueDictionary::modify(const NameValueItems &items) +{ + NameValueDictionary resultKeyValueDictionary = *this; + for (const NameValueItem &item : items) + item.apply(&resultKeyValueDictionary); + *this = resultKeyValueDictionary; +} + +enum : char { +#ifdef Q_OS_WIN + pathSepC = ';' +#else + pathSepC = ':' +#endif +}; + +NameValueItems NameValueDictionary::diff(const NameValueDictionary &other, bool checkAppendPrepend) const +{ + NameValueMap::const_iterator thisIt = constBegin(); + NameValueMap::const_iterator otherIt = other.constBegin(); + + NameValueItems result; + while (thisIt != constEnd() || otherIt != other.constEnd()) { + if (thisIt == constEnd()) { + result.append(NameValueItem(otherIt.key(), otherIt.value())); + ++otherIt; + } else if (otherIt == other.constEnd()) { + result.append(NameValueItem(thisIt.key(), QString(), NameValueItem::Unset)); + ++thisIt; + } else if (thisIt.key() < otherIt.key()) { + result.append(NameValueItem(thisIt.key(), QString(), NameValueItem::Unset)); + ++thisIt; + } else if (thisIt.key() > otherIt.key()) { + result.append(NameValueItem(otherIt.key(), otherIt.value())); + ++otherIt; + } else { + const QString &oldValue = thisIt.value(); + const QString &newValue = otherIt.value(); + if (oldValue != newValue) { + if (checkAppendPrepend && newValue.startsWith(oldValue)) { + QString appended = newValue.right(newValue.size() - oldValue.size()); + if (appended.startsWith(QLatin1Char(pathSepC))) + appended.remove(0, 1); + result.append(NameValueItem(otherIt.key(), appended, NameValueItem::Append)); + } else if (checkAppendPrepend && newValue.endsWith(oldValue)) { + QString prepended = newValue.left(newValue.size() - oldValue.size()); + if (prepended.endsWith(QLatin1Char(pathSepC))) + prepended.chop(1); + result.append(NameValueItem(otherIt.key(), prepended, NameValueItem::Prepend)); + } else { + result.append(NameValueItem(otherIt.key(), newValue)); + } + } + ++otherIt; + ++thisIt; + } + } + return result; +} + +bool NameValueDictionary::hasKey(const QString &key) const +{ + return m_values.contains(key); +} + +OsType NameValueDictionary::osType() const +{ + return m_osType; +} + +QString NameValueDictionary::userName() const +{ + return value(QString::fromLatin1(m_osType == OsTypeWindows ? "USERNAME" : "USER")); +} + +/** Expand environment variables in a string. + * + * KeyValueDictionary variables are accepted in the following forms: + * $SOMEVAR, ${SOMEVAR} on Unix and %SOMEVAR% on Windows. + * No escapes and quoting are supported. + * If a variable is not found, it is not substituted. + */ +QString NameValueDictionary::expandVariables(const QString &input) const +{ + QString result = input; + + if (m_osType == OsTypeWindows) { + for (int vStart = -1, i = 0; i < result.length();) { + if (result.at(i++) == '%') { + if (vStart > 0) { + const_iterator it = findKey(m_values, m_osType, result.mid(vStart, i - vStart - 1)); + if (it != m_values.constEnd()) { + result.replace(vStart - 1, i - vStart + 1, *it); + i = vStart - 1 + it->length(); + vStart = -1; + } else { + vStart = i; + } + } else { + vStart = i; + } + } + } + } else { + enum { BASE, OPTIONALVARIABLEBRACE, VARIABLE, BRACEDVARIABLE } state = BASE; + int vStart = -1; + + for (int i = 0; i < result.length();) { + QChar c = result.at(i++); + if (state == BASE) { + if (c == '$') + state = OPTIONALVARIABLEBRACE; + } else if (state == OPTIONALVARIABLEBRACE) { + if (c == '{') { + state = BRACEDVARIABLE; + vStart = i; + } else if (c.isLetterOrNumber() || c == '_') { + state = VARIABLE; + vStart = i - 1; + } else { + state = BASE; + } + } else if (state == BRACEDVARIABLE) { + if (c == '}') { + const_iterator it = m_values.constFind(result.mid(vStart, i - 1 - vStart)); + if (it != constEnd()) { + result.replace(vStart - 2, i - vStart + 2, *it); + i = vStart - 2 + it->length(); + } + state = BASE; + } + } else if (state == VARIABLE) { + if (!c.isLetterOrNumber() && c != '_') { + const_iterator it = m_values.constFind(result.mid(vStart, i - vStart - 1)); + if (it != constEnd()) { + result.replace(vStart - 1, i - vStart, *it); + i = vStart - 1 + it->length(); + } + state = BASE; + } + } + } + if (state == VARIABLE) { + const_iterator it = m_values.constFind(result.mid(vStart)); + if (it != constEnd()) + result.replace(vStart - 1, result.length() - vStart + 1, *it); + } + } + return result; +} + +QStringList NameValueDictionary::expandVariables(const QStringList &variables) const +{ + return Utils::transform(variables, [this](const QString &i) { return expandVariables(i); }); +} + +} // namespace Utils diff --git a/src/libs/utils/namevaluedictionary.h b/src/libs/utils/namevaluedictionary.h new file mode 100644 index 0000000000..112d6b094c --- /dev/null +++ b/src/libs/utils/namevaluedictionary.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "fileutils.h" +#include "hostosinfo.h" +#include "namevalueitem.h" + +namespace Utils { + +using NameValuePair = std::pair<QString, QString>; +using NameValuePairs = QVector<NameValuePair>; +using NameValueMap = QMap<QString, QString>; + +class QTCREATOR_UTILS_EXPORT NameValueDictionary +{ +public: + using const_iterator = NameValueMap::const_iterator; + + explicit NameValueDictionary(OsType osType = HostOsInfo::hostOs()) + : m_osType(osType) + {} + explicit NameValueDictionary(const QStringList &env, OsType osType = HostOsInfo::hostOs()); + explicit NameValueDictionary(const NameValuePairs &nameValues); + + QStringList toStringList() const; + QString value(const QString &key) const; + void set(const QString &key, const QString &value); + void unset(const QString &key); + void modify(const NameValueItems &items); + /// Return the KeyValueDictionary changes necessary to modify this into the other environment. + NameValueItems diff(const NameValueDictionary &other, bool checkAppendPrepend = false) const; + bool hasKey(const QString &key) const; + OsType osType() const; + + QString userName() const; + + void clear(); + int size() const; + + QString key(NameValueDictionary::const_iterator it) const { return it.key(); } + + QString value(NameValueDictionary::const_iterator it) const { return it.value(); } + + NameValueDictionary::const_iterator constBegin() const { return m_values.constBegin(); } + + NameValueDictionary::const_iterator constEnd() const { return m_values.constEnd(); } + + NameValueDictionary::const_iterator constFind(const QString &name) const; + + QString expandVariables(const QString &input) const; + QStringList expandVariables(const QStringList &input) const; + + friend bool operator!=(const NameValueDictionary &first, const NameValueDictionary &second) + { + return !(first == second); + } + + friend bool operator==(const NameValueDictionary &first, const NameValueDictionary &second) + { + return first.m_osType == second.m_osType && first.m_values == second.m_values; + } + +protected: + NameValueMap m_values; + OsType m_osType; +}; + +} // namespace Utils diff --git a/src/libs/utils/namevalueitem.cpp b/src/libs/utils/namevalueitem.cpp new file mode 100644 index 0000000000..6be972201c --- /dev/null +++ b/src/libs/utils/namevalueitem.cpp @@ -0,0 +1,199 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "namevalueitem.h" +#include "algorithm.h" +#include "namevaluedictionary.h" +#include "qtcassert.h" + +#include <QDebug> + +namespace Utils { + +void NameValueItem::sort(NameValueItems *list) +{ + Utils::sort(*list, &NameValueItem::name); +} + +NameValueItems NameValueItem::fromStringList(const QStringList &list) +{ + NameValueItems result; + for (const QString &string : list) { + int pos = string.indexOf('=', 1); + if (pos == -1) + result.append(NameValueItem(string, QString(), NameValueItem::Unset)); + else + result.append(NameValueItem(string.left(pos), string.mid(pos + 1))); + } + return result; +} + +QStringList NameValueItem::toStringList(const NameValueItems &list) +{ + return Utils::transform<QStringList>(list, [](const NameValueItem &item) { + if (item.operation == NameValueItem::Unset) + return QString(item.name); + return QString(item.name + '=' + item.value); + }); +} + +NameValueItems NameValueItem::itemsFromVariantList(const QVariantList &list) +{ + return Utils::transform<NameValueItems>(list, [](const QVariant &item) { + return itemFromVariantList(item.toList()); + }); +} + +QVariantList NameValueItem::toVariantList(const NameValueItems &list) +{ + return Utils::transform<QVariantList>(list, [](const NameValueItem &item) { + return QVariant(toVariantList(item)); + }); +} + +NameValueItem NameValueItem::itemFromVariantList(const QVariantList &list) +{ + QTC_ASSERT(list.size() == 3, return NameValueItem("", "")); + QString key = list.value(0).toString(); + Operation operation = Operation(list.value(1).toInt()); + QString value = list.value(2).toString(); + return NameValueItem(key, value, operation); +} + +QVariantList NameValueItem::toVariantList(const NameValueItem &item) +{ + return QVariantList() << item.name << item.operation << item.value; +} + +static QString expand(const NameValueDictionary *dictionary, QString value) +{ + int replaceCount = 0; + for (int i = 0; i < value.size(); ++i) { + if (value.at(i) == '$') { + if ((i + 1) < value.size()) { + const QChar &c = value.at(i + 1); + int end = -1; + if (c == '(') + end = value.indexOf(')', i); + else if (c == '{') + end = value.indexOf('}', i); + if (end != -1) { + const QString &key = value.mid(i + 2, end - i - 2); + NameValueDictionary::const_iterator it = dictionary->constFind(key); + if (it != dictionary->constEnd()) + value.replace(i, end - i + 1, it.value()); + ++replaceCount; + QTC_ASSERT(replaceCount < 100, break); + } + } + } + } + return value; +} + +enum : char { +#ifdef Q_OS_WIN + pathSepC = ';' +#else + pathSepC = ':' +#endif +}; + +void NameValueItem::apply(NameValueDictionary *dictionary, Operation op) const +{ + switch (op) { + case Set: + dictionary->set(name, expand(dictionary, value)); + break; + case Unset: + dictionary->unset(name); + break; + case Prepend: { + const NameValueDictionary::const_iterator it = dictionary->constFind(name); + if (it != dictionary->constEnd()) { + QString v = it.value(); + const QChar pathSep{QLatin1Char(pathSepC)}; + int sepCount = 0; + if (v.startsWith(pathSep)) + ++sepCount; + if (value.endsWith(pathSep)) + ++sepCount; + if (sepCount == 2) + v.remove(0, 1); + else if (sepCount == 0) + v.prepend(pathSep); + v.prepend(expand(dictionary, value)); + dictionary->set(name, v); + } else { + apply(dictionary, Set); + } + } break; + case Append: { + const NameValueDictionary::const_iterator it = dictionary->constFind(name); + if (it != dictionary->constEnd()) { + QString v = it.value(); + const QChar pathSep{QLatin1Char(pathSepC)}; + int sepCount = 0; + if (v.endsWith(pathSep)) + ++sepCount; + if (value.startsWith(pathSep)) + ++sepCount; + if (sepCount == 2) + v.chop(1); + else if (sepCount == 0) + v.append(pathSep); + v.append(expand(dictionary, value)); + dictionary->set(name, v); + } else { + apply(dictionary, Set); + } + } break; + } +} + +QDebug operator<<(QDebug debug, const NameValueItem &i) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "KeyValueItem("; + switch (i.operation) { + case NameValueItem::Set: + debug << "set \"" << i.name << "\" to \"" << i.value << '"'; + break; + case NameValueItem::Unset: + debug << "unset \"" << i.name << '"'; + break; + case NameValueItem::Prepend: + debug << "prepend to \"" << i.name << "\":\"" << i.value << '"'; + break; + case NameValueItem::Append: + debug << "append to \"" << i.name << "\":\"" << i.value << '"'; + break; + } + debug << ')'; + return debug; +} +} // namespace Utils diff --git a/src/libs/utils/namevalueitem.h b/src/libs/utils/namevalueitem.h new file mode 100644 index 0000000000..3ed4cc3cb3 --- /dev/null +++ b/src/libs/utils/namevalueitem.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "environmentfwd.h" +#include "utils_global.h" + +#include <QStringList> +#include <QVariantList> +#include <QVector> + +namespace Utils { + +class QTCREATOR_UTILS_EXPORT NameValueItem +{ +public: + enum Operation : char { Set, Unset, Prepend, Append }; + NameValueItem() = default; + NameValueItem(const QString &key, const QString &value, Operation operation = Set) + : name(key) + , value(value) + , operation(operation) + {} + + void apply(NameValueDictionary *dictionary) const { apply(dictionary, operation); } + + static void sort(NameValueItems *list); + static NameValueItems fromStringList(const QStringList &list); + static QStringList toStringList(const NameValueItems &list); + static NameValueItems itemsFromVariantList(const QVariantList &list); + static QVariantList toVariantList(const NameValueItems &list); + static NameValueItem itemFromVariantList(const QVariantList &list); + static QVariantList toVariantList(const NameValueItem &item); + + friend bool operator==(const NameValueItem &first, const NameValueItem &second) + { + return first.operation == second.operation && first.name == second.name + && first.value == second.value; + } + + friend bool operator!=(const NameValueItem &first, const NameValueItem &second) + { + return !(first == second); + } + +public: + QString name; + QString value; + Operation operation = Unset; + +private: + void apply(NameValueDictionary *dictionary, Operation op) const; +}; + +QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug debug, const NameValueItem &i); + +} // namespace Utils diff --git a/src/libs/utils/namevaluemodel.cpp b/src/libs/utils/namevaluemodel.cpp new file mode 100644 index 0000000000..2a310d6b61 --- /dev/null +++ b/src/libs/utils/namevaluemodel.cpp @@ -0,0 +1,397 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "namevaluemodel.h" + +#include <utils/algorithm.h> +#include <utils/hostosinfo.h> +#include <utils/namevaluedictionary.h> + +#include <QFont> +#include <QString> + +namespace Utils { +namespace Internal { + +class NameValueModelPrivate +{ +public: + void updateResultNameValueDictionary() + { + m_resultNameValueDictionary = m_baseNameValueDictionary; + m_resultNameValueDictionary.modify(m_items); + // Add removed variables again and mark them as "<UNSET>" so + // that the user can actually see those removals: + foreach (const NameValueItem &item, m_items) { + if (item.operation == NameValueItem::Unset) + m_resultNameValueDictionary.set(item.name, NameValueModel::tr("<UNSET>")); + } + } + + int findInChanges(const QString &name) const + { + for (int i = 0; i < m_items.size(); ++i) + if (m_items.at(i).name == name) + return i; + return -1; + } + + int findInResultInsertPosition(const QString &name) const + { + NameValueDictionary::const_iterator it; + int i = 0; + for (it = m_resultNameValueDictionary.constBegin(); + it != m_resultNameValueDictionary.constEnd(); + ++it, ++i) + if (m_resultNameValueDictionary.key(it) > name) + return i; + return m_resultNameValueDictionary.size(); + } + + int findInResult(const QString &name) const + { + NameValueDictionary::const_iterator it; + int i = 0; + for (it = m_resultNameValueDictionary.constBegin(); + it != m_resultNameValueDictionary.constEnd(); + ++it, ++i) + if (m_resultNameValueDictionary.key(it) == name) + return i; + return -1; + } + + NameValueDictionary m_baseNameValueDictionary; + NameValueDictionary m_resultNameValueDictionary; + NameValueItems m_items; +}; + +} // namespace Internal + +NameValueModel::NameValueModel(QObject *parent) + : QAbstractTableModel(parent) + , d(std::make_unique<Internal::NameValueModelPrivate>()) +{} + +NameValueModel::~NameValueModel() = default; + +QString NameValueModel::indexToVariable(const QModelIndex &index) const +{ + return d->m_resultNameValueDictionary.key(d->m_resultNameValueDictionary.constBegin() + + index.row()); +} + +void NameValueModel::setBaseNameValueDictionary(const NameValueDictionary &dictionary) +{ + if (d->m_baseNameValueDictionary == dictionary) + return; + beginResetModel(); + d->m_baseNameValueDictionary = dictionary; + d->updateResultNameValueDictionary(); + endResetModel(); +} + +int NameValueModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return d->m_resultNameValueDictionary.size(); +} +int NameValueModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return 2; +} + +bool NameValueModel::changes(const QString &name) const +{ + return d->findInChanges(name) >= 0; +} + +const NameValueDictionary &NameValueModel::baseNameValueDictionary() const +{ + return d->m_baseNameValueDictionary; +} + +QVariant NameValueModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::ToolTipRole) { + if (index.column() == 0) { + return d->m_resultNameValueDictionary.key(d->m_resultNameValueDictionary.constBegin() + + index.row()); + } else if (index.column() == 1) { + // Do not return "<UNSET>" when editing a previously unset variable: + if (role == Qt::EditRole) { + int pos = d->findInChanges(indexToVariable(index)); + if (pos >= 0) + return d->m_items.at(pos).value; + } + QString value = d->m_resultNameValueDictionary.value( + d->m_resultNameValueDictionary.constBegin() + index.row()); + if (role == Qt::ToolTipRole && value.length() > 80) { + // Use html to enable text wrapping + value = value.toHtmlEscaped(); + value.prepend(QLatin1String("<html><body>")); + value.append(QLatin1String("</body></html>")); + } + return value; + } + } + if (role == Qt::FontRole) { + // check whether this name value item variable exists in d->m_items + if (changes(d->m_resultNameValueDictionary.key(d->m_resultNameValueDictionary.constBegin() + + index.row()))) { + QFont f; + f.setBold(true); + return QVariant(f); + } + return QFont(); + } + return QVariant(); +} + +Qt::ItemFlags NameValueModel::flags(const QModelIndex &index) const +{ + Q_UNUSED(index) + return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled; +} + +QVariant NameValueModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Vertical || role != Qt::DisplayRole) + return QVariant(); + return section == 0 ? tr("Variable") : tr("Value"); +} + +/// ***************** +/// Utility functions +/// ***************** +QModelIndex NameValueModel::variableToIndex(const QString &name) const +{ + int row = d->findInResult(name); + if (row == -1) + return QModelIndex(); + return index(row, 0); +} + +bool NameValueModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || role != Qt::EditRole) + return false; + + // ignore changes to already set values: + if (data(index, role) == value) + return true; + + const QString oldName = data(this->index(index.row(), 0, QModelIndex())).toString(); + const QString oldValue = data(this->index(index.row(), 1, QModelIndex()), Qt::EditRole).toString(); + int changesPos = d->findInChanges(oldName); + + if (index.column() == 0) { + //fail if a variable with the same name already exists + const QString &newName = HostOsInfo::isWindowsHost() ? value.toString().toUpper() + : value.toString(); + if (newName.isEmpty() || newName.contains('=')) + return false; + // Does the new name exist already? + if (d->m_resultNameValueDictionary.hasKey(newName) || newName.isEmpty()) + return false; + + NameValueItem newVariable(newName, oldValue); + + if (changesPos != -1) + resetVariable(oldName); // restore the original base variable again + + QModelIndex newIndex = addVariable(newVariable); // add the new variable + emit focusIndex(newIndex.sibling(newIndex.row(), 1)); // hint to focus on the value + return true; + } else if (index.column() == 1) { + // We are changing an existing value: + const QString stringValue = value.toString(); + if (changesPos != -1) { + // We have already changed this value + if (d->m_baseNameValueDictionary.hasKey(oldName) + && stringValue == d->m_baseNameValueDictionary.value(oldName)) { + // ... and now went back to the base value + d->m_items.removeAt(changesPos); + } else { + // ... and changed it again + d->m_items[changesPos].value = stringValue; + d->m_items[changesPos].operation = NameValueItem::Set; + } + } else { + // Add a new change item: + d->m_items.append(NameValueItem(oldName, stringValue)); + } + d->updateResultNameValueDictionary(); + emit dataChanged(index, index); + emit userChangesChanged(); + return true; + } + return false; +} + +QModelIndex NameValueModel::addVariable() +{ + //: Name when inserting a new variable + return addVariable(NameValueItem(tr("<VARIABLE>"), + //: Value when inserting a new variable + tr("<VALUE>"))); +} + +QModelIndex NameValueModel::addVariable(const NameValueItem &item) +{ + // Return existing index if the name is already in the result set: + int pos = d->findInResult(item.name); + if (pos >= 0 && pos < d->m_resultNameValueDictionary.size()) + return index(pos, 0, QModelIndex()); + + int insertPos = d->findInResultInsertPosition(item.name); + int changePos = d->findInChanges(item.name); + if (d->m_baseNameValueDictionary.hasKey(item.name)) { + // We previously unset this! + Q_ASSERT(changePos >= 0); + // Do not insert a line here as we listed the variable as <UNSET> before! + Q_ASSERT(d->m_items.at(changePos).name == item.name); + Q_ASSERT(d->m_items.at(changePos).operation == NameValueItem::Unset); + Q_ASSERT(d->m_items.at(changePos).value.isEmpty()); + d->m_items[changePos] = item; + emit dataChanged(index(insertPos, 0, QModelIndex()), index(insertPos, 1, QModelIndex())); + } else { + // We add something that is not in the base dictionary + // Insert a new line! + beginInsertRows(QModelIndex(), insertPos, insertPos); + Q_ASSERT(changePos < 0); + d->m_items.append(item); + d->updateResultNameValueDictionary(); + endInsertRows(); + } + emit userChangesChanged(); + return index(insertPos, 0, QModelIndex()); +} + +void NameValueModel::resetVariable(const QString &name) +{ + int rowInChanges = d->findInChanges(name); + if (rowInChanges < 0) + return; + + int rowInResult = d->findInResult(name); + if (rowInResult < 0) + return; + + if (d->m_baseNameValueDictionary.hasKey(name)) { + d->m_items.removeAt(rowInChanges); + d->updateResultNameValueDictionary(); + emit dataChanged(index(rowInResult, 0, QModelIndex()), index(rowInResult, 1, QModelIndex())); + emit userChangesChanged(); + } else { + // Remove the line completely! + beginRemoveRows(QModelIndex(), rowInResult, rowInResult); + d->m_items.removeAt(rowInChanges); + d->updateResultNameValueDictionary(); + endRemoveRows(); + emit userChangesChanged(); + } +} + +void NameValueModel::unsetVariable(const QString &name) +{ + // This does not change the number of rows as we will display a <UNSET> + // in place of the original variable! + int row = d->findInResult(name); + if (row < 0) + return; + + // look in d->m_items for the variable + int pos = d->findInChanges(name); + if (pos != -1) { + d->m_items[pos].operation = NameValueItem::Unset; + d->m_items[pos].value.clear(); + d->updateResultNameValueDictionary(); + emit dataChanged(index(row, 0, QModelIndex()), index(row, 1, QModelIndex())); + emit userChangesChanged(); + return; + } + d->m_items.append(NameValueItem(name, QString(), NameValueItem::Unset)); + d->updateResultNameValueDictionary(); + emit dataChanged(index(row, 0, QModelIndex()), index(row, 1, QModelIndex())); + emit userChangesChanged(); +} + +bool NameValueModel::canUnset(const QString &name) +{ + int pos = d->findInChanges(name); + if (pos != -1) + return d->m_items.at(pos).operation == NameValueItem::Unset; + else + return false; +} + +bool NameValueModel::canReset(const QString &name) +{ + return d->m_baseNameValueDictionary.hasKey(name); +} + +NameValueItems NameValueModel::userChanges() const +{ + return d->m_items; +} + +void NameValueModel::setUserChanges(const NameValueItems &items) +{ + NameValueItems filtered = Utils::filtered(items, [](const NameValueItem &i) { + return i.name != "export " && !i.name.contains('='); + }); + // We assume nobody is reordering the items here. + if (filtered == d->m_items) + return; + beginResetModel(); + d->m_items = filtered; + for (NameValueItem &item : d->m_items) { + QString &name = item.name; + name = name.trimmed(); + if (name.startsWith("export ")) + name = name.mid(7).trimmed(); + if (d->m_baseNameValueDictionary.osType() == OsTypeWindows) { + // NameValueDictionary variable names are case-insensitive under windows, but we still + // want to preserve the case of pre-existing variables. + auto it = d->m_baseNameValueDictionary.constFind(name); + if (it != d->m_baseNameValueDictionary.constEnd()) + name = d->m_baseNameValueDictionary.key(it); + } + } + + d->updateResultNameValueDictionary(); + endResetModel(); + emit userChangesChanged(); +} + +} // namespace Utils diff --git a/src/libs/utils/namevaluemodel.h b/src/libs/utils/namevaluemodel.h new file mode 100644 index 0000000000..96811fec68 --- /dev/null +++ b/src/libs/utils/namevaluemodel.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "environmentfwd.h" +#include "utils_global.h" + +#include <QAbstractTableModel> + +#include <memory> + +namespace Utils { + +namespace Internal { +class NameValueModelPrivate; +} + +class QTCREATOR_UTILS_EXPORT NameValueModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit NameValueModel(QObject *parent = nullptr); + ~NameValueModel() override; + + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant headerData(int section, + Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + + QModelIndex addVariable(); + QModelIndex addVariable(const NameValueItem &item); + void resetVariable(const QString &name); + void unsetVariable(const QString &name); + bool canUnset(const QString &name); + bool canReset(const QString &name); + QString indexToVariable(const QModelIndex &index) const; + QModelIndex variableToIndex(const QString &name) const; + bool changes(const QString &key) const; + const NameValueDictionary &baseNameValueDictionary() const; + void setBaseNameValueDictionary(const NameValueDictionary &dictionary); + NameValueItems userChanges() const; + void setUserChanges(const NameValueItems &items); + +signals: + void userChangesChanged(); + /// Hint to the view where it should make sense to focus on next + // This is a hack since there is no way for a model to suggest + // the next interesting place to focus on to the view. + void focusIndex(const QModelIndex &index); + +private: + std::unique_ptr<Internal::NameValueModelPrivate> d; +}; + +} // namespace Utils diff --git a/src/libs/utils/namevaluesdialog.cpp b/src/libs/utils/namevaluesdialog.cpp new file mode 100644 index 0000000000..ed71d8db7f --- /dev/null +++ b/src/libs/utils/namevaluesdialog.cpp @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "namevaluesdialog.h" + +#include <utils/environment.h> +#include <utils/hostosinfo.h> + +#include <QDialogButtonBox> +#include <QLabel> +#include <QPlainTextEdit> +#include <QVBoxLayout> + +namespace Utils { + +namespace Internal { + +static EnvironmentItems cleanUp(const EnvironmentItems &items) +{ + EnvironmentItems uniqueItems; + QSet<QString> uniqueSet; + for (int i = items.count() - 1; i >= 0; i--) { + EnvironmentItem item = items.at(i); + if (HostOsInfo::isWindowsHost()) + item.name = item.name.toUpper(); + const QString &itemName = item.name; + QString emptyName = itemName; + emptyName.remove(QLatin1Char(' ')); + if (!emptyName.isEmpty() && !uniqueSet.contains(itemName)) { + uniqueItems.prepend(item); + uniqueSet.insert(itemName); + } + } + return uniqueItems; +} + +NameValueItemsWidget::NameValueItemsWidget(QWidget *parent) + : QWidget(parent) +{ + m_editor = new QPlainTextEdit(this); + auto layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->addWidget(m_editor); +} + +void NameValueItemsWidget::setEnvironmentItems(const EnvironmentItems &items) +{ + EnvironmentItems sortedItems = items; + EnvironmentItem::sort(&sortedItems); + const QStringList list = EnvironmentItem::toStringList(sortedItems); + m_editor->document()->setPlainText(list.join(QLatin1Char('\n'))); +} + +EnvironmentItems NameValueItemsWidget::environmentItems() const +{ + const QStringList list = m_editor->document()->toPlainText().split(QLatin1String("\n")); + EnvironmentItems items = EnvironmentItem::fromStringList(list); + return cleanUp(items); +} + +void NameValueItemsWidget::setPlaceholderText(const QString &text) +{ + m_editor->setPlaceholderText(text); +} +} // namespace Internal + +NameValuesDialog::NameValuesDialog(const QString &windowTitle, const QString &helpText, QWidget *parent) + : QDialog(parent) +{ + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + resize(640, 480); + m_editor = new Internal::NameValueItemsWidget(this); + auto box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, + Qt::Horizontal, + this); + connect(box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(box, &QDialogButtonBox::rejected, this, &QDialog::reject); + + auto helpLabel = new QLabel(this); + helpLabel->setText(helpText); + + auto layout = new QVBoxLayout(this); + layout->addWidget(m_editor); + layout->addWidget(helpLabel); + + layout->addWidget(box); + + setWindowTitle(windowTitle); +} + +void NameValuesDialog::setNameValueItems(const EnvironmentItems &items) +{ + m_editor->setEnvironmentItems(items); +} + +EnvironmentItems NameValuesDialog::nameValueItems() const +{ + return m_editor->environmentItems(); +} + +void NameValuesDialog::setPlaceholderText(const QString &text) +{ + m_editor->setPlaceholderText(text); +} + +Utils::optional<NameValueItems> NameValuesDialog::getNameValueItems(QWidget *parent, + const NameValueItems &initial, + const QString &placeholderText, + Polisher polisher, + const QString &windowTitle, + const QString &helpText) +{ + NameValuesDialog dialog(windowTitle, helpText, parent); + if (polisher) + polisher(&dialog); + dialog.setNameValueItems(initial); + dialog.setPlaceholderText(placeholderText); + bool result = dialog.exec() == QDialog::Accepted; + if (result) + return dialog.nameValueItems(); + + return {}; +} + +} // namespace Utils diff --git a/src/libs/utils/namevaluesdialog.h b/src/libs/utils/namevaluesdialog.h new file mode 100644 index 0000000000..8170a99a1d --- /dev/null +++ b/src/libs/utils/namevaluesdialog.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "environmentfwd.h" +#include "optional.h" +#include "utils_global.h" + +#include <QDialog> + +#include <functional> +#include <memory> + +QT_BEGIN_NAMESPACE +class QPlainTextEdit; +QT_END_NAMESPACE + +namespace Utils { + +namespace Internal { +class NameValueItemsWidget : public QWidget +{ + Q_OBJECT +public: + explicit NameValueItemsWidget(QWidget *parent = nullptr); + + void setEnvironmentItems(const EnvironmentItems &items); + EnvironmentItems environmentItems() const; + + void setPlaceholderText(const QString &text); + +private: + QPlainTextEdit *m_editor; +}; +} // namespace Internal + +class QTCREATOR_UTILS_EXPORT NameValuesDialog : public QDialog +{ + Q_OBJECT +public: + void setNameValueItems(const NameValueItems &items); + NameValueItems nameValueItems() const; + + void setPlaceholderText(const QString &text); + + using Polisher = std::function<void(QWidget *)>; + static Utils::optional<NameValueItems> getNameValueItems(QWidget *parent = nullptr, + const NameValueItems &initial = {}, + const QString &placeholderText = {}, + Polisher polish = {}, + const QString &windowTitle = {}, + const QString &helpText = {}); + +protected: + explicit NameValuesDialog(const QString &windowTitle, + const QString &helpText, + QWidget *parent = {}); + +private: + Internal::NameValueItemsWidget *m_editor; +}; + +} // namespace Utils diff --git a/src/libs/utils/namevaluevalidator.cpp b/src/libs/utils/namevaluevalidator.cpp new file mode 100644 index 0000000000..580a476e01 --- /dev/null +++ b/src/libs/utils/namevaluevalidator.cpp @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "namevaluevalidator.h" +#include "namevaluemodel.h" +#include "tooltip/tooltip.h" + +#include <QTreeView> + +namespace Utils { + +NameValueValidator::NameValueValidator(QWidget *parent, + Utils::NameValueModel *model, + QTreeView *view, + const QModelIndex &index, + const QString &toolTipText) + : QValidator(parent) + , m_toolTipText(toolTipText) + , m_model(model) + , m_view(view) + , m_index(index) +{ + m_hideTipTimer.setInterval(2000); + m_hideTipTimer.setSingleShot(true); + connect(&m_hideTipTimer, &QTimer::timeout, this, []() { Utils::ToolTip::hide(); }); +} + +QValidator::State NameValueValidator::validate(QString &in, int &pos) const +{ + Q_UNUSED(pos) + QModelIndex idx = m_model->variableToIndex(in); + if (idx.isValid() && idx != m_index) + return QValidator::Intermediate; + Utils::ToolTip::hide(); + m_hideTipTimer.stop(); + return QValidator::Acceptable; +} + +void NameValueValidator::fixup(QString &input) const +{ + Q_UNUSED(input) + + QPoint pos = m_view->mapToGlobal(m_view->visualRect(m_index).topLeft()); + pos -= Utils::ToolTip::offsetFromPosition(); + Utils::ToolTip::show(pos, m_toolTipText); + m_hideTipTimer.start(); + // do nothing +} + +} // namespace Utils diff --git a/src/libs/utils/namevaluevalidator.h b/src/libs/utils/namevaluevalidator.h new file mode 100644 index 0000000000..e1a8ae13a0 --- /dev/null +++ b/src/libs/utils/namevaluevalidator.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "environmentfwd.h" +#include "utils_global.h" + +#include <QModelIndex> +#include <QTimer> +#include <QValidator> + +namespace Utils { + +class QTCREATOR_UTILS_EXPORT NameValueValidator : public QValidator +{ + Q_OBJECT +public: + NameValueValidator(QWidget *parent, + Utils::NameValueModel *model, + QTreeView *view, + const QModelIndex &index, + const QString &toolTipText); + + QValidator::State validate(QString &in, int &pos) const override; + + void fixup(QString &input) const override; + +private: + const QString &m_toolTipText; + Utils::NameValueModel *m_model; + QTreeView *m_view; + QModelIndex m_index; + mutable QTimer m_hideTipTimer; +}; +} // namespace Utils diff --git a/src/libs/utils/pathchooser.cpp b/src/libs/utils/pathchooser.cpp index f08391d6a6..a114aba310 100644 --- a/src/libs/utils/pathchooser.cpp +++ b/src/libs/utils/pathchooser.cpp @@ -84,7 +84,7 @@ public: QStringList arguments() const { return m_arguments; } void setArguments(const QStringList &arguments) { m_arguments = arguments; } - static QString toolVersion(const QString &binary, const QStringList &arguments); + static QString toolVersion(const CommandLine &cmd); private: // Extension point for concatenating existing tooltips. @@ -108,7 +108,8 @@ bool BinaryVersionToolTipEventFilter::eventFilter(QObject *o, QEvent *e) const QString binary = le->text(); if (!binary.isEmpty()) { - const QString version = BinaryVersionToolTipEventFilter::toolVersion(QDir::cleanPath(binary), m_arguments); + const QString version = BinaryVersionToolTipEventFilter::toolVersion( + CommandLine(FilePath::fromString(QDir::cleanPath(binary)), m_arguments)); if (!version.isEmpty()) { // Concatenate tooltips. QString tooltip = "<html><head/><body>"; @@ -127,13 +128,13 @@ bool BinaryVersionToolTipEventFilter::eventFilter(QObject *o, QEvent *e) return false; } -QString BinaryVersionToolTipEventFilter::toolVersion(const QString &binary, const QStringList &arguments) +QString BinaryVersionToolTipEventFilter::toolVersion(const CommandLine &cmd) { - if (binary.isEmpty()) + if (cmd.executable().isEmpty()) return QString(); SynchronousProcess proc; proc.setTimeoutS(1); - SynchronousProcessResponse response = proc.runBlocking(binary, arguments); + SynchronousProcessResponse response = proc.runBlocking(cmd); if (response.result != SynchronousProcessResponse::Finished) return QString(); return response.allOutput(); @@ -677,7 +678,7 @@ FancyLineEdit *PathChooser::lineEdit() const QString PathChooser::toolVersion(const QString &binary, const QStringList &arguments) { - return BinaryVersionToolTipEventFilter::toolVersion(binary, arguments); + return BinaryVersionToolTipEventFilter::toolVersion({FilePath::fromString(binary), arguments}); } void PathChooser::installLineEditVersionToolTip(QLineEdit *le, const QStringList &arguments) diff --git a/src/libs/utils/settingsaccessor.cpp b/src/libs/utils/settingsaccessor.cpp index f6d90df7e0..881652f5ea 100644 --- a/src/libs/utils/settingsaccessor.cpp +++ b/src/libs/utils/settingsaccessor.cpp @@ -277,7 +277,10 @@ BackingUpSettingsAccessor::readData(const FilePath &path, QWidget *parent) const QApplication::translate("Utils::SettingsAccessor", "<p>No valid settings file could be found.</p>" "<p>All settings files found in directory \"%1\" " - "were unsuitable for the current version of %2.</p>") + "were unsuitable for the current version of %2, " + "for instance because they were written by an incompatible " + "version of %2, or because a different settings path " + "was used.</p>") .arg(path.toUserOutput()).arg(applicationDisplayName), Issue::Type::ERROR); i.buttons.insert(QMessageBox::Ok, DiscardAndContinue); result.issue = i; diff --git a/src/libs/utils/shellcommand.cpp b/src/libs/utils/shellcommand.cpp index 5a84c92f15..99b0b0ace6 100644 --- a/src/libs/utils/shellcommand.cpp +++ b/src/libs/utils/shellcommand.cpp @@ -68,12 +68,11 @@ class ShellCommandPrivate { public: struct Job { - explicit Job(const QString &wd, const FilePath &b, const QStringList &a, int t, + explicit Job(const QString &wd, const CommandLine &command, int t, const ExitCodeInterpreter &interpreter); QString workingDirectory; - FilePath binary; - QStringList arguments; + CommandLine command; ExitCodeInterpreter exitCodeInterpreter; int timeoutS; }; @@ -113,11 +112,10 @@ ShellCommandPrivate::~ShellCommandPrivate() delete m_progressParser; } -ShellCommandPrivate::Job::Job(const QString &wd, const FilePath &b, const QStringList &a, +ShellCommandPrivate::Job::Job(const QString &wd, const CommandLine &command, int t, const ExitCodeInterpreter &interpreter) : workingDirectory(wd), - binary(b), - arguments(a), + command(command), exitCodeInterpreter(interpreter), timeoutS(t) { @@ -146,14 +144,14 @@ QString ShellCommand::displayName() const return d->m_displayName; if (!d->m_jobs.isEmpty()) { const Internal::ShellCommandPrivate::Job &job = d->m_jobs.at(0); - QString result = job.binary.toFileInfo().baseName(); + QString result = job.command.executable().toFileInfo().baseName(); if (!result.isEmpty()) result[0] = result.at(0).toTitleCase(); else result = tr("UNKNOWN"); - if (!job.arguments.isEmpty()) - result += QLatin1Char(' ') + job.arguments.at(0); + if (!job.command.arguments().isEmpty()) + result += ' ' + job.command.arguments().at(0); return result; } @@ -195,17 +193,17 @@ void ShellCommand::addFlags(unsigned f) d->m_flags |= f; } -void ShellCommand::addJob(const FilePath &binary, const QStringList &arguments, +void ShellCommand::addJob(const CommandLine &command, const QString &workingDirectory, const ExitCodeInterpreter &interpreter) { - addJob(binary, arguments, defaultTimeoutS(), workingDirectory, interpreter); + addJob(command, defaultTimeoutS(), workingDirectory, interpreter); } -void ShellCommand::addJob(const FilePath &binary, const QStringList &arguments, int timeoutS, +void ShellCommand::addJob(const CommandLine &command, int timeoutS, const QString &workingDirectory, const ExitCodeInterpreter &interpreter) { - d->m_jobs.push_back(Internal::ShellCommandPrivate::Job(workDirectory(workingDirectory), binary, - arguments, timeoutS, interpreter)); + d->m_jobs.push_back(Internal::ShellCommandPrivate::Job(workDirectory(workingDirectory), command, + timeoutS, interpreter)); } void ShellCommand::execute() @@ -287,7 +285,7 @@ void ShellCommand::run(QFutureInterface<void> &future) for (int j = 0; j < count; j++) { const Internal::ShellCommandPrivate::Job &job = d->m_jobs.at(j); SynchronousProcessResponse resp - = runCommand(job.binary, job.arguments, job.timeoutS, job.workingDirectory, + = runCommand(job.command, job.timeoutS, job.workingDirectory, job.exitCodeInterpreter); stdOut += resp.stdOut(); stdErr += resp.stdErr(); @@ -319,8 +317,7 @@ void ShellCommand::run(QFutureInterface<void> &future) this->deleteLater(); } -SynchronousProcessResponse ShellCommand::runCommand(const FilePath &binary, - const QStringList &arguments, int timeoutS, +SynchronousProcessResponse ShellCommand::runCommand(const CommandLine &command, int timeoutS, const QString &workingDirectory, const ExitCodeInterpreter &interpreter) { @@ -328,7 +325,7 @@ SynchronousProcessResponse ShellCommand::runCommand(const FilePath &binary, const QString dir = workDirectory(workingDirectory); - if (binary.isEmpty()) { + if (command.executable().isEmpty()) { response.result = SynchronousProcessResponse::StartFailed; return response; } @@ -336,31 +333,30 @@ SynchronousProcessResponse ShellCommand::runCommand(const FilePath &binary, QSharedPointer<OutputProxy> proxy(d->m_proxyFactory()); if (!(d->m_flags & SuppressCommandLogging)) - emit proxy->appendCommand(dir, binary, arguments); + emit proxy->appendCommand(dir, command); if ((d->m_flags & FullySynchronously) || (!(d->m_flags & NoFullySync) && QThread::currentThread() == QCoreApplication::instance()->thread())) { - response = runFullySynchronous(binary, arguments, proxy, timeoutS, dir, interpreter); + response = runFullySynchronous(command, proxy, timeoutS, dir, interpreter); } else { - response = runSynchronous(binary, arguments, proxy, timeoutS, dir, interpreter); + response = runSynchronous(command, proxy, timeoutS, dir, interpreter); } if (!d->m_aborted) { // Success/Fail message in appropriate window? if (response.result == SynchronousProcessResponse::Finished) { if (d->m_flags & ShowSuccessMessage) - emit proxy->appendMessage(response.exitMessage(binary.toUserOutput(), timeoutS)); + emit proxy->appendMessage(response.exitMessage(command.toUserOutput(), timeoutS)); } else if (!(d->m_flags & SuppressFailMessage)) { - emit proxy->appendError(response.exitMessage(binary.toUserOutput(), timeoutS)); + emit proxy->appendError(response.exitMessage(command.toUserOutput(), timeoutS)); } } return response; } -SynchronousProcessResponse ShellCommand::runFullySynchronous(const FilePath &binary, - const QStringList &arguments, +SynchronousProcessResponse ShellCommand::runFullySynchronous(const CommandLine &cmd, QSharedPointer<OutputProxy> proxy, int timeoutS, const QString &workingDirectory, @@ -380,7 +376,7 @@ SynchronousProcessResponse ShellCommand::runFullySynchronous(const FilePath &bin process.setTimeoutS(timeoutS); process.setExitCodeInterpreter(interpreter); - SynchronousProcessResponse resp = process.runBlocking(binary.toString(), arguments); + SynchronousProcessResponse resp = process.runBlocking(cmd); if (!d->m_aborted) { const QString stdErr = resp.stdErr(); @@ -399,8 +395,7 @@ SynchronousProcessResponse ShellCommand::runFullySynchronous(const FilePath &bin return resp; } -SynchronousProcessResponse ShellCommand::runSynchronous(const FilePath &binary, - const QStringList &arguments, +SynchronousProcessResponse ShellCommand::runSynchronous(const CommandLine &cmd, QSharedPointer<OutputProxy> proxy, int timeoutS, const QString &workingDirectory, @@ -460,7 +455,7 @@ SynchronousProcessResponse ShellCommand::runSynchronous(const FilePath &binary, process.setTimeoutS(timeoutS); process.setExitCodeInterpreter(interpreter); - return process.run(binary.toString(), arguments); + return process.run(cmd); } const QVariant &ShellCommand::cookie() const diff --git a/src/libs/utils/shellcommand.h b/src/libs/utils/shellcommand.h index f0c427ef53..a54e8fb401 100644 --- a/src/libs/utils/shellcommand.h +++ b/src/libs/utils/shellcommand.h @@ -46,7 +46,7 @@ QT_END_NAMESPACE namespace Utils { -class FilePath; +class CommandLine; namespace Internal { class ShellCommandPrivate; } class QTCREATOR_UTILS_EXPORT ProgressParser @@ -79,8 +79,7 @@ signals: void append(const QString &text); void appendSilently(const QString &text); void appendError(const QString &text); - void appendCommand(const QString &workingDirectory, const Utils::FilePath &binary, - const QStringList &args); + void appendCommand(const QString &workingDirectory, const Utils::CommandLine &command); void appendMessage(const QString &text); }; @@ -112,10 +111,12 @@ public: QString displayName() const; void setDisplayName(const QString &name); - void addJob(const FilePath &binary, const QStringList &arguments, - const QString &workingDirectory = QString(), const ExitCodeInterpreter &interpreter = defaultExitCodeInterpreter); - void addJob(const FilePath &binary, const QStringList &arguments, int timeoutS, - const QString &workingDirectory = QString(), const ExitCodeInterpreter &interpreter = defaultExitCodeInterpreter); + void addJob(const CommandLine &command, + const QString &workingDirectory = QString(), + const ExitCodeInterpreter &interpreter = defaultExitCodeInterpreter); + void addJob(const CommandLine &command, int timeoutS, + const QString &workingDirectory = QString(), + const ExitCodeInterpreter &interpreter = defaultExitCodeInterpreter); void execute(); // Execute tasks asynchronously! void abort(); bool lastExecutionSuccess() const; @@ -145,7 +146,7 @@ public: // This is called once per job in a thread. // When called from the UI thread it will execute fully synchronously, so no signals will // be triggered! - virtual SynchronousProcessResponse runCommand(const FilePath &binary, const QStringList &arguments, + virtual SynchronousProcessResponse runCommand(const CommandLine &command, int timeoutS, const QString &workingDirectory = QString(), const ExitCodeInterpreter &interpreter = defaultExitCodeInterpreter); @@ -171,12 +172,12 @@ private: void run(QFutureInterface<void> &future); // Run without a event loop in fully blocking mode. No signals will be delivered. - SynchronousProcessResponse runFullySynchronous(const FilePath &binary, const QStringList &arguments, + SynchronousProcessResponse runFullySynchronous(const CommandLine &cmd, QSharedPointer<OutputProxy> proxy, int timeoutS, const QString &workingDirectory, const ExitCodeInterpreter &interpreter = defaultExitCodeInterpreter); // Run with an event loop. Signals will be delivered. - SynchronousProcessResponse runSynchronous(const FilePath &binary, const QStringList &arguments, + SynchronousProcessResponse runSynchronous(const CommandLine &cmd, QSharedPointer<OutputProxy> proxy, int timeoutS, const QString &workingDirectory, const ExitCodeInterpreter &interpreter = defaultExitCodeInterpreter); diff --git a/src/libs/utils/stringutils.cpp b/src/libs/utils/stringutils.cpp index 161cf44e74..f73e9d8144 100644 --- a/src/libs/utils/stringutils.cpp +++ b/src/libs/utils/stringutils.cpp @@ -136,6 +136,7 @@ bool AbstractMacroExpander::expandNestedMacros(const QString &str, int *pos, QSt QString *currArg = &varName; QChar prev; QChar c; + QChar replacementChar; bool replaceAll = false; int i = *pos; @@ -192,13 +193,14 @@ bool AbstractMacroExpander::expandNestedMacros(const QString &str, int *pos, QSt } else if (currArg == &varName && c == '-' && prev == ':' && validateVarName(varName)) { varName.chop(1); currArg = &defaultValue; - } else if (currArg == &varName && c == '/' && validateVarName(varName)) { + } else if (currArg == &varName && (c == '/' || c == '#') && validateVarName(varName)) { + replacementChar = c; currArg = &pattern; - if (i < strLen && str.at(i) == '/') { + if (i < strLen && str.at(i) == replacementChar) { ++i; replaceAll = true; } - } else if (currArg == &pattern && c == '/') { + } else if (currArg == &pattern && c == replacementChar) { currArg = &replace; } else { *currArg += c; diff --git a/src/libs/utils/synchronousprocess.cpp b/src/libs/utils/synchronousprocess.cpp index 70e65fc8c9..f197494b4c 100644 --- a/src/libs/utils/synchronousprocess.cpp +++ b/src/libs/utils/synchronousprocess.cpp @@ -271,7 +271,7 @@ public: QTimer m_timer; QEventLoop m_eventLoop; SynchronousProcessResponse m_result; - QString m_binary; + FilePath m_binary; ChannelBuffer m_stdOut; ChannelBuffer m_stdErr; ExitCodeInterpreter m_exitCodeInterpreter = defaultExitCodeInterpreter; @@ -293,7 +293,7 @@ void SynchronousProcessPrivate::clearForRun() m_result.clear(); m_result.codec = m_codec; m_startFailure = false; - m_binary.clear(); + m_binary = {}; } // ----------- SynchronousProcess @@ -446,12 +446,10 @@ static bool isGuiThread() return QThread::currentThread() == QCoreApplication::instance()->thread(); } -SynchronousProcessResponse SynchronousProcess::run(const QString &binary, - const QStringList &args, +SynchronousProcessResponse SynchronousProcess::run(const CommandLine &cmd, const QByteArray &writeData) { - qCDebug(processLog).noquote() << "Starting:" - << QtcProcess::joinArgs(QStringList(binary) + args); + qCDebug(processLog).noquote() << "Starting:" << cmd.toUserOutput(); ExecuteOnDestruction logResult([this] { qCDebug(processLog) << d->m_result; }); @@ -461,12 +459,12 @@ SynchronousProcessResponse SynchronousProcess::run(const QString &binary, // On Windows, start failure is triggered immediately if the // executable cannot be found in the path. Do not start the // event loop in that case. - d->m_binary = binary; + d->m_binary = cmd.executable(); // using QProcess::start() and passing program, args and OpenMode results in a different // quoting of arguments than using QProcess::setArguments() beforehand and calling start() // only with the OpenMode - d->m_process.setProgram(binary); - d->m_process.setArguments(args); + d->m_process.setProgram(cmd.executable().toString()); + d->m_process.setArguments(cmd.splitArguments()); connect(&d->m_process, &QProcess::started, this, [this, writeData] { if (!writeData.isEmpty()) { int pos = 0; @@ -503,10 +501,9 @@ SynchronousProcessResponse SynchronousProcess::run(const QString &binary, return d->m_result; } -SynchronousProcessResponse SynchronousProcess::runBlocking(const QString &binary, const QStringList &args) +SynchronousProcessResponse SynchronousProcess::runBlocking(const CommandLine &cmd) { - qCDebug(processLog).noquote() << "Starting blocking:" - << QtcProcess::joinArgs(QStringList(binary) + args); + qCDebug(processLog).noquote() << "Starting blocking:" << cmd.toUserOutput(); ExecuteOnDestruction logResult([this] { qCDebug(processLog) << d->m_result; }); @@ -516,8 +513,8 @@ SynchronousProcessResponse SynchronousProcess::runBlocking(const QString &binary // On Windows, start failure is triggered immediately if the // executable cannot be found in the path. Do not start the // event loop in that case. - d->m_binary = binary; - d->m_process.start(binary, args, QIODevice::ReadOnly); + d->m_binary = cmd.executable(); + d->m_process.start(cmd.executable().toString(), cmd.splitArguments(), QIODevice::ReadOnly); if (!d->m_process.waitForStarted(d->m_maxHangTimerCount * 1000) && d->m_process.state() == QProcess::NotRunning) { d->m_result.result = SynchronousProcessResponse::StartFailed; @@ -585,7 +582,7 @@ void SynchronousProcess::slotTimeout() if (debug) qDebug() << Q_FUNC_INFO << "HANG detected, killing"; d->m_waitingForUser = true; - const bool terminate = !d->m_timeOutMessageBoxEnabled || askToKill(d->m_binary); + const bool terminate = !d->m_timeOutMessageBoxEnabled || askToKill(d->m_binary.toString()); d->m_waitingForUser = false; if (terminate) { SynchronousProcess::stopProcess(d->m_process); diff --git a/src/libs/utils/synchronousprocess.h b/src/libs/utils/synchronousprocess.h index d8e3275030..071e20e007 100644 --- a/src/libs/utils/synchronousprocess.h +++ b/src/libs/utils/synchronousprocess.h @@ -38,6 +38,7 @@ QT_FORWARD_DECLARE_CLASS(QDebug) namespace Utils { class SynchronousProcessPrivate; +class CommandLine; /* Result of SynchronousProcess execution */ class QTCREATOR_UTILS_EXPORT SynchronousProcessResponse @@ -126,10 +127,10 @@ public: void setExitCodeInterpreter(const ExitCodeInterpreter &interpreter); ExitCodeInterpreter exitCodeInterpreter() const; - // Starts an nested event loop and runs the binary with the arguments - SynchronousProcessResponse run(const QString &binary, const QStringList &args, const QByteArray &writeData = {}); - // Starts the binary with the arguments blocking the UI fully - SynchronousProcessResponse runBlocking(const QString &binary, const QStringList &args); + // Starts a nested event loop and runs the command + SynchronousProcessResponse run(const CommandLine &cmd, const QByteArray &writeData = {}); + // Starts the command blocking the UI fully + SynchronousProcessResponse runBlocking(const CommandLine &cmd); // Create a (derived) processes with flags applied. static QSharedPointer<QProcess> createProcess(unsigned flags); diff --git a/src/libs/utils/utils-lib.pri b/src/libs/utils/utils-lib.pri index a4697e0622..36f3aa14a3 100644 --- a/src/libs/utils/utils-lib.pri +++ b/src/libs/utils/utils-lib.pri @@ -27,6 +27,10 @@ SOURCES += \ $$PWD/environment.cpp \ $$PWD/environmentmodel.cpp \ $$PWD/environmentdialog.cpp \ + $$PWD/namevaluedictionary.cpp \ + $$PWD/namevalueitem.cpp \ + $$PWD/namevaluemodel.cpp \ + $$PWD/namevaluesdialog.cpp \ $$PWD/qrcparser.cpp \ $$PWD/qtcprocess.cpp \ $$PWD/reloadpromptutils.cpp \ @@ -125,19 +129,24 @@ SOURCES += \ $$PWD/fixedsizeclicklabel.cpp \ $$PWD/removefiledialog.cpp \ $$PWD/differ.cpp \ - $$PWD/jsontreeitem.cpp - + $$PWD/jsontreeitem.cpp \ + $$PWD/namevaluevalidator.cpp win32:SOURCES += $$PWD/consoleprocess_win.cpp else:SOURCES += $$PWD/consoleprocess_unix.cpp HEADERS += \ + $$PWD/environmentfwd.h \ $$PWD/genericconstants.h \ $$PWD/globalfilechangeblocker.h \ $$PWD/benchmarker.h \ $$PWD/environment.h \ $$PWD/environmentmodel.h \ $$PWD/environmentdialog.h \ + $$PWD/namevaluedictionary.h \ + $$PWD/namevalueitem.h \ + $$PWD/namevaluemodel.h \ + $$PWD/namevaluesdialog.h \ $$PWD/pointeralgorithm.h \ $$PWD/qrcparser.h \ $$PWD/qtcprocess.h \ @@ -269,7 +278,8 @@ HEADERS += \ $$PWD/differ.h \ $$PWD/cpplanguage_details.h \ $$PWD/jsontreeitem.h \ - $$PWD/listmodel.h + $$PWD/listmodel.h \ + $$PWD/namevaluevalidator.h FORMS += $$PWD/filewizardpage.ui \ $$PWD/newclasswidget.ui \ diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 10368cd539..456d45a245 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -151,6 +151,16 @@ Project { "macroexpander.cpp", "macroexpander.h", "mapreduce.h", + "namevaluedictionary.cpp", + "namevaluedictionary.h", + "namevalueitem.cpp", + "namevalueitem.h", + "namevaluemodel.cpp", + "namevaluemodel.h", + "namevaluesdialog.cpp", + "namevaluesdialog.h", + "namevaluevalidator.cpp", + "namevaluevalidator.h", "navigationtreeview.cpp", "navigationtreeview.h", "networkaccessmanager.cpp", @@ -202,7 +212,9 @@ Project { "qtcprocess.h", "reloadpromptutils.cpp", "reloadpromptutils.h", - "removefiledialog.cpp", "removefiledialog.h", "removefiledialog.ui", + "removefiledialog.cpp", + "removefiledialog.h", + "removefiledialog.ui", "runextensions.cpp", "runextensions.h", "savedaction.cpp", |