diff options
Diffstat (limited to 'src')
106 files changed, 1693 insertions, 1052 deletions
diff --git a/src/libs/qmljs/qmljscheck.cpp b/src/libs/qmljs/qmljscheck.cpp index d63ae11611..3ce402589c 100644 --- a/src/libs/qmljs/qmljscheck.cpp +++ b/src/libs/qmljs/qmljscheck.cpp @@ -617,7 +617,6 @@ class UnsupportedTypesByQmlUi : public QStringList { public: UnsupportedTypesByQmlUi() : QStringList({"ShaderEffect", - "Component", "Drawer"}) { append(UnsupportedTypesByVisualDesigner()); diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp index 2d3d6edf3b..76f32ccb98 100644 --- a/src/libs/utils/environment.cpp +++ b/src/libs/utils/environment.cpp @@ -136,8 +136,7 @@ void Environment::setupEnglishOutput() set("LANGUAGE", "en_US:en"); } -static FilePath searchInDirectory(const Environment &env, - const QStringList &execs, +static FilePath searchInDirectory(const QStringList &execs, const FilePath &directory, QSet<FilePath> &alreadyChecked) { @@ -226,7 +225,7 @@ static FilePath searchInDirectoriesHelper(const Environment &env, QSet<FilePath> alreadyChecked; for (const FilePath &dir : dirs) { - FilePath tmp = searchInDirectory(env, execs, dir, alreadyChecked); + FilePath tmp = searchInDirectory(execs, dir, alreadyChecked); if (!tmp.isEmpty() && (!func || func(tmp))) return tmp; } @@ -236,7 +235,7 @@ static FilePath searchInDirectoriesHelper(const Environment &env, return FilePath(); for (const FilePath &p : env.path()) { - FilePath tmp = searchInDirectory(env, execs, p, alreadyChecked); + FilePath tmp = searchInDirectory(execs, p, alreadyChecked); if (!tmp.isEmpty() && (!func || func(tmp))) return tmp; } @@ -281,14 +280,14 @@ FilePaths Environment::findAllInPath(const QString &executable, QSet<FilePath> result; QSet<FilePath> alreadyChecked; for (const FilePath &dir : additionalDirs) { - FilePath tmp = searchInDirectory(*this, execs, dir, alreadyChecked); + FilePath tmp = searchInDirectory(execs, dir, alreadyChecked); if (!tmp.isEmpty() && (!func || func(tmp))) result << tmp; } if (!executable.contains('/')) { for (const FilePath &p : path()) { - FilePath tmp = searchInDirectory(*this, execs, p, alreadyChecked); + FilePath tmp = searchInDirectory(execs, p, alreadyChecked); if (!tmp.isEmpty() && (!func || func(tmp))) result << tmp; } diff --git a/src/libs/utils/pathlisteditor.cpp b/src/libs/utils/pathlisteditor.cpp index 61d215fd83..d24be4995b 100644 --- a/src/libs/utils/pathlisteditor.cpp +++ b/src/libs/utils/pathlisteditor.cpp @@ -130,6 +130,7 @@ PathListEditor::PathListEditor(QWidget *parent) : }); addButton(tr("Delete Line"), this, [this] { deletePathAtCursor(); }); addButton(tr("Clear"), this, [this] { d->edit->clear(); }); + connect(d->edit, &QPlainTextEdit::textChanged, this, &PathListEditor::changed); } PathListEditor::~PathListEditor() diff --git a/src/libs/utils/pathlisteditor.h b/src/libs/utils/pathlisteditor.h index 07392fbe6e..edd70336c0 100644 --- a/src/libs/utils/pathlisteditor.h +++ b/src/libs/utils/pathlisteditor.h @@ -58,6 +58,9 @@ public: void setPathList(const QString &pathString); void setFileDialogTitle(const QString &l); +signals: + void changed(); + protected: // Index after which to insert further "Add" buttons static const int lastInsertButtonIndex; diff --git a/src/plugins/android/androidavdmanager.cpp b/src/plugins/android/androidavdmanager.cpp index 27af7b76db..1b30803910 100644 --- a/src/plugins/android/androidavdmanager.cpp +++ b/src/plugins/android/androidavdmanager.cpp @@ -203,6 +203,7 @@ bool AndroidAvdManager::removeAvd(const QString &name) const qCDebug(avdManagerLog) << "Running command (removeAvd):" << command.toUserOutput(); QtcProcess proc; proc.setTimeoutS(5); + proc.setEnvironment(AndroidConfigurations::toolsEnvironment(m_config)); proc.setCommand(command); proc.runBlocking(); return proc.result() == QtcProcess::FinishedWithSuccess; diff --git a/src/plugins/android/androidbuildapkstep.cpp b/src/plugins/android/androidbuildapkstep.cpp index 4c47418c85..dc6b42754c 100644 --- a/src/plugins/android/androidbuildapkstep.cpp +++ b/src/plugins/android/androidbuildapkstep.cpp @@ -196,7 +196,7 @@ QWidget *AndroidBuildApkWidget::createApplicationGroup() }); auto formLayout = new QFormLayout(group); - formLayout->addRow(tr("Android build SDK:"), targetSDKComboBox); + formLayout->addRow(tr("Android build platform SDK:"), targetSDKComboBox); auto createAndroidTemplatesButton = new QPushButton(tr("Create Templates")); createAndroidTemplatesButton->setToolTip( @@ -939,7 +939,8 @@ QVariant AndroidBuildApkStep::data(Utils::Id id) const } if (id == Constants::SdkLocation) return QVariant::fromValue(AndroidConfigurations::currentConfig().sdkLocation()); - if (id == Constants::AndroidABIs) + + if (id == Constants::AndroidMkSpecAbis) return AndroidManager::applicationAbis(target()); return AbstractProcessStep::data(id); diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp index f1eb6379d6..c769a744d2 100644 --- a/src/plugins/android/androidconfigurations.cpp +++ b/src/plugins/android/androidconfigurations.cpp @@ -1127,10 +1127,9 @@ void AndroidConfigurations::removeOldToolChains() void AndroidConfigurations::removeUnusedDebuggers() { - const QList<BaseQtVersion *> qtVersions - = QtVersionManager::versions([](const BaseQtVersion *v) { - return v->type() == Constants::ANDROIDQT; - }); + const QList<BaseQtVersion*> qtVersions = QtVersionManager::versions([](const BaseQtVersion *v) { + return v->type() == Constants::ANDROID_QT_TYPE; + }); QVector<FilePath> uniqueNdks; for (const BaseQtVersion *qt : qtVersions) { @@ -1267,8 +1266,8 @@ void AndroidConfigurations::updateAutomaticKitList() removeUnusedDebuggers(); QHash<Abi, QList<const BaseQtVersion *> > qtVersionsForArch; - const QList<BaseQtVersion *> qtVersions = QtVersionManager::versions([](const BaseQtVersion *v) { - return v->type() == Constants::ANDROIDQT; + const QList<BaseQtVersion*> qtVersions = QtVersionManager::versions([](const BaseQtVersion *v) { + return v->type() == Constants::ANDROID_QT_TYPE; }); for (const BaseQtVersion *qtVersion : qtVersions) { const Abis qtAbis = qtVersion->qtAbis(); @@ -1322,9 +1321,8 @@ void AndroidConfigurations::updateAutomaticKitList() QStringList abis = static_cast<const AndroidQtVersion *>(qt)->androidAbis(); Debugger::DebuggerKitAspect::setDebugger(k, findOrRegisterDebugger(tc, abis)); - k->setSticky(ToolChainKitAspect::id(), true); + BuildDeviceKitAspect::setDeviceId(k, DeviceManager::defaultDesktopDevice()->id()); k->setSticky(QtKitAspect::id(), true); - k->setSticky(DeviceKitAspect::id(), true); k->setMutable(DeviceKitAspect::id(), true); k->setSticky(DeviceTypeKitAspect::id(), true); diff --git a/src/plugins/android/androidconstants.h b/src/plugins/android/androidconstants.h index df8c821f98..02bb50b348 100644 --- a/src/plugins/android/androidconstants.h +++ b/src/plugins/android/androidconstants.h @@ -42,9 +42,9 @@ namespace Internal { namespace Constants { const char ANDROID_SETTINGS_ID[] = "BB.Android Configurations"; const char ANDROID_TOOLCHAIN_TYPEID[] = "Qt4ProjectManager.ToolChain.Android"; -const char ANDROIDQT[] = "Qt4ProjectManager.QtVersion.Android"; +const char ANDROID_QT_TYPE[] = "Qt4ProjectManager.QtVersion.Android"; -const char ANDROID_AMSTARTARGS[] = "Android.AmStartArgs"; +const char ANDROID_AM_START_ARGS[] = "Android.AmStartArgs"; // Note: Can be set on RunConfiguration using an aspect and/or // the AndroidRunnerWorker using recordData() const char ANDROID_PRESTARTSHELLCMDLIST[] = "Android.PreStartShellCmdList"; @@ -67,32 +67,33 @@ const char ANDROID_ARCHITECTURE[] = "Android.Architecture"; const char ANDROID_PACKAGE_SOURCE_DIR[] = "ANDROID_PACKAGE_SOURCE_DIR"; const char ANDROID_EXTRA_LIBS[] = "ANDROID_EXTRA_LIBS"; const char ANDROID_ABI[] = "ANDROID_ABI"; +const char ANDROID_TARGET_ARCH[] = "ANDROID_TARGET_ARCH"; const char ANDROID_ABIS[] = "ANDROID_ABIS"; const char ANDROID_APPLICATION_ARGUMENTS[] = "ANDROID_APPLICATION_ARGUMENTS"; -const char QT_ANDROID_APPLICATION_ARGUMENTS[] = "QT_ANDROID_APPLICATION_ARGUMENTS"; const char ANDROID_DEPLOYMENT_SETTINGS_FILE[] = "ANDROID_DEPLOYMENT_SETTINGS_FILE"; const char ANDROID_SO_LIBS_PATHS[] = "ANDROID_SO_LIBS_PATHS"; -const char ANDROID_PACKAGENAME[] = "Android.PackageName"; -const char ANDROID_PACKAGE_INSTALLATION_STEP_ID[] - = "Qt4ProjectManager.AndroidPackageInstallationStep"; +const char ANDROID_PACKAGE_INSTALL_STEP_ID[] = "Qt4ProjectManager.AndroidPackageInstallationStep"; const char ANDROID_BUILD_APK_ID[] = "QmakeProjectManager.AndroidBuildApkStep"; const char ANDROID_DEPLOY_QT_ID[] = "Qt4ProjectManager.AndroidDeployQtStep"; const char AndroidPackageSourceDir[] = "AndroidPackageSourceDir"; // QString const char AndroidDeploySettingsFile[] = "AndroidDeploySettingsFile"; // QString const char AndroidExtraLibs[] = "AndroidExtraLibs"; // QStringList -// REMOVE ME -const char AndroidArch[] = "AndroidArch"; // QString +const char AndroidAbi[] = "AndroidAbi"; // QString +const char AndroidAbis[] = "AndroidAbis"; // QStringList +const char AndroidMkSpecAbis[] = "AndroidMkSpecAbis"; // QStringList const char AndroidSoLibPath[] = "AndroidSoLibPath"; // QStringList const char AndroidTargets[] = "AndroidTargets"; // QStringList +const char AndroidApplicationArgs[] = "AndroidApplicationArgs"; // QString + +// For qbs support const char AndroidApk[] = "Android.APK"; // QStringList const char AndroidManifest[] = "Android.Manifest"; // QStringList const char AndroidNdkPlatform[] = "AndroidNdkPlatform"; //QString const char NdkLocation[] = "NdkLocation"; // FileName const char SdkLocation[] = "SdkLocation"; // FileName -const char AndroidABIs[] = "AndroidABIs"; // QString // Android Device const Utils::Id AndroidSerialNumber = "AndroidSerialNumber"; diff --git a/src/plugins/android/androiddeployqtstep.cpp b/src/plugins/android/androiddeployqtstep.cpp index 12d3b3e651..8c50c7a52c 100644 --- a/src/plugins/android/androiddeployqtstep.cpp +++ b/src/plugins/android/androiddeployqtstep.cpp @@ -149,14 +149,14 @@ bool AndroidDeployQtStep::init() info = androidDeployQtStep->m_deviceInfo; const BuildSystem *bs = buildSystem(); - auto selectedAbis = bs->property(Constants::ANDROID_ABIS).toStringList(); + auto selectedAbis = bs->property(Constants::AndroidAbis).toStringList(); const QString buildKey = target()->activeBuildKey(); if (selectedAbis.isEmpty()) - selectedAbis = bs->extraData(buildKey, Constants::ANDROID_ABIS).toStringList(); + selectedAbis = bs->extraData(buildKey, Constants::AndroidAbis).toStringList(); if (selectedAbis.isEmpty()) - selectedAbis.append(bs->extraData(buildKey, Constants::AndroidArch).toString()); + selectedAbis.append(bs->extraData(buildKey, Constants::AndroidAbi).toString()); if (!info.isValid()) { const IDevice *dev = DeviceKitAspect::device(kit()).data(); diff --git a/src/plugins/android/androidmanager.cpp b/src/plugins/android/androidmanager.cpp index 7d2a568303..e8944fba17 100644 --- a/src/plugins/android/androidmanager.cpp +++ b/src/plugins/android/androidmanager.cpp @@ -408,7 +408,7 @@ QString AndroidManager::apkDevicePreferredAbi(const Target *target) auto libsPath = androidBuildDirectory(target).pathAppended("libs"); if (!libsPath.exists()) { if (const ProjectNode *node = currentProjectNode(target)) - return preferredAbi(node->data(Android::Constants::ANDROID_ABIS).toStringList(), + return preferredAbi(node->data(Android::Constants::AndroidAbis).toStringList(), target); } QStringList apkAbis; diff --git a/src/plugins/android/androidpackageinstallationstep.cpp b/src/plugins/android/androidpackageinstallationstep.cpp index cc951859ea..e1eb82e0cc 100644 --- a/src/plugins/android/androidpackageinstallationstep.cpp +++ b/src/plugins/android/androidpackageinstallationstep.cpp @@ -196,7 +196,7 @@ void AndroidPackageInstallationStep::doRun() AndroidPackageInstallationFactory::AndroidPackageInstallationFactory() { - registerStep<AndroidPackageInstallationStep>(Constants::ANDROID_PACKAGE_INSTALLATION_STEP_ID); + registerStep<AndroidPackageInstallationStep>(Constants::ANDROID_PACKAGE_INSTALL_STEP_ID); setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_BUILD); setSupportedDeviceType(Android::Constants::ANDROID_DEVICE_TYPE); setRepeatable(false); diff --git a/src/plugins/android/androidpotentialkit.cpp b/src/plugins/android/androidpotentialkit.cpp index 128c853c84..79a1afebec 100644 --- a/src/plugins/android/androidpotentialkit.cpp +++ b/src/plugins/android/androidpotentialkit.cpp @@ -74,7 +74,7 @@ bool AndroidPotentialKit::isEnabled() const } return QtSupport::QtVersionManager::version([](const QtSupport::BaseQtVersion *v) { - return v->isValid() && v->type() == QString::fromLatin1(Constants::ANDROIDQT); + return v->isValid() && v->type() == QString::fromLatin1(Constants::ANDROID_QT_TYPE); }); } diff --git a/src/plugins/android/androidqtversion.cpp b/src/plugins/android/androidqtversion.cpp index 3cc9b06ccd..b53dc9e8d0 100644 --- a/src/plugins/android/androidqtversion.cpp +++ b/src/plugins/android/androidqtversion.cpp @@ -197,7 +197,7 @@ void AndroidQtVersion::parseMkSpec(ProFileEvaluator *evaluator) const { m_androidAbis = evaluator->values("ALL_ANDROID_ABIS"); if (m_androidAbis.isEmpty()) - m_androidAbis = QStringList{evaluator->value("ANDROID_TARGET_ARCH")}; + m_androidAbis = QStringList{evaluator->value(Constants::ANDROID_TARGET_ARCH)}; const QString androidPlatform = evaluator->value("ANDROID_PLATFORM"); if (!androidPlatform.isEmpty()) { const QRegularExpression regex("android-(\\d+)"); @@ -232,7 +232,7 @@ QSet<Utils::Id> AndroidQtVersion::targetDeviceTypes() const AndroidQtVersionFactory::AndroidQtVersionFactory() { setQtVersionCreator([] { return new AndroidQtVersion; }); - setSupportedType(Constants::ANDROIDQT); + setSupportedType(Constants::ANDROID_QT_TYPE); setPriority(90); setRestrictionChecker([](const SetupData &setup) { diff --git a/src/plugins/android/androidrunconfiguration.cpp b/src/plugins/android/androidrunconfiguration.cpp index 5b834fa673..e1477c2615 100644 --- a/src/plugins/android/androidrunconfiguration.cpp +++ b/src/plugins/android/androidrunconfiguration.cpp @@ -82,13 +82,13 @@ AndroidRunConfiguration::AndroidRunConfiguration(Target *target, Utils::Id id) if (target->buildConfigurations().first()->buildType() == BuildConfiguration::BuildType::Release) { const QString buildKey = target->activeBuildKey(); target->buildSystem()->setExtraData(buildKey, - Android::Constants::ANDROID_APPLICATION_ARGUMENTS, - extraAppArgsAspect->arguments(target->macroExpander())); + Android::Constants::AndroidApplicationArgs, + extraAppArgsAspect->arguments(target->macroExpander())); } }); auto amStartArgsAspect = addAspect<StringAspect>(); - amStartArgsAspect->setId(Constants::ANDROID_AMSTARTARGS); + amStartArgsAspect->setId(Constants::ANDROID_AM_START_ARGS); amStartArgsAspect->setSettingsKey("Android.AmStartArgsKey"); amStartArgsAspect->setLabelText(tr("Activity manager start options:")); amStartArgsAspect->setDisplayStyle(StringAspect::LineEditDisplay); diff --git a/src/plugins/android/androidrunnerworker.cpp b/src/plugins/android/androidrunnerworker.cpp index db28291511..83c512a46a 100644 --- a/src/plugins/android/androidrunnerworker.cpp +++ b/src/plugins/android/androidrunnerworker.cpp @@ -279,7 +279,7 @@ AndroidRunnerWorker::AndroidRunnerWorker(RunWorker *runner, const QString &packa m_extraAppParams = runControl->runnable().command.arguments(); } - if (auto aspect = runControl->aspect(Constants::ANDROID_AMSTARTARGS)) { + if (auto aspect = runControl->aspect(Constants::ANDROID_AM_START_ARGS)) { QTC_CHECK(aspect->value().type() == QVariant::String); const QString startArgs = aspect->value().toString(); m_amStartExtraArgs = ProcessArgs::splitArgs(startArgs, OsTypeOtherUnix); diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index fea5314001..5b3e76bee8 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -66,14 +66,18 @@ #include <utils/runextensions.h> #include <QCheckBox> +#include <QDateTime> +#include <QElapsedTimer> #include <QFile> #include <QHash> #include <QPair> #include <QPointer> #include <QRegularExpression> +#include <QtConcurrent> #include <set> #include <unordered_map> +#include <utility> using namespace CPlusPlus; using namespace Core; @@ -89,6 +93,7 @@ static Q_LOGGING_CATEGORY(clangdLog, "qtc.clangcodemodel.clangd", QtWarningMsg); static Q_LOGGING_CATEGORY(clangdLogServer, "qtc.clangcodemodel.clangd.server", QtWarningMsg); static Q_LOGGING_CATEGORY(clangdLogAst, "qtc.clangcodemodel.clangd.ast", QtWarningMsg); static Q_LOGGING_CATEGORY(clangdLogHighlight, "qtc.clangcodemodel.clangd.highlight", QtWarningMsg); +static Q_LOGGING_CATEGORY(clangdLogTiming, "qtc.clangcodemodel.clangd.timing", QtWarningMsg); static QString indexingToken() { return "backgroundIndexProgress"; } class AstNode : public JsonObject @@ -171,6 +176,12 @@ public: bool isNamespace() const { return role() == "declaration" && kind() == "Namespace"; } + bool isTemplateParameterDeclaration() const + { + return role() == "declaration" && (kind() == "TemplateTypeParm" + || kind() == "NonTypeTemplateParm"); + }; + QString type() const { const Utils::optional<QString> arcanaString = arcana(); @@ -305,14 +316,6 @@ static QList<AstNode> getAstPath(const AstNode &root, const Range &range) return path; } -static AstNode getAstNode(const AstNode &root, const Range &range) -{ - const QList<AstNode> path = getAstPath(root, range); - if (!path.isEmpty()) - return path.last(); - return {}; -} - static Usage::Type getUsageType(const QList<AstNode> &path) { bool potentialWrite = false; @@ -773,6 +776,109 @@ private: std::unordered_map<DocType, VersionedDocData<DocType, DataType>> m_data; }; +class TaskTimer +{ +public: + TaskTimer(const QString &task) : m_task(task) {} + + void stopTask() + { + // This can happen due to the RAII mechanism employed with SubtaskTimer. + // The subtask timers will expire immediately after, so this does not distort + // the timing data. + if (m_subtasks > 0) { + QTC_CHECK(m_timer.isValid()); + m_elapsedMs += m_timer.elapsed(); + m_timer.invalidate(); + m_subtasks = 0; + } + m_started = false; + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": took " << m_elapsedMs + << " ms in UI thread"; + } + void startSubtask() + { + // We have some callbacks that are either synchronous or asynchronous, depending on + // dynamic conditions. In the sync case, we will have nested subtasks, in which case + // the inner ones must not collect timing data, as their code blocks are already covered. + if (++m_subtasks > 1) + return; + if (!m_started) { + QTC_ASSERT(m_elapsedMs == 0, m_elapsedMs = 0); + m_started = true; + m_finalized = false; + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": starting"; + } + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": subtask started at " + << QDateTime::currentDateTime().toString(); + QTC_CHECK(!m_timer.isValid()); + m_timer.start(); + } + void stopSubtask(bool isFinalizing) + { + if (m_subtasks == 0) // See stopTask(). + return; + if (isFinalizing) + m_finalized = true; + if (--m_subtasks > 0) // See startSubtask(). + return; + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": subtask stopped at " + << QDateTime::currentDateTime().toString(); + QTC_CHECK(m_timer.isValid()); + m_elapsedMs += m_timer.elapsed(); + m_timer.invalidate(); + if (m_finalized) + stopTask(); + } + +private: + const QString m_task; + QElapsedTimer m_timer; + qint64 m_elapsedMs = 0; + int m_subtasks = 0; + bool m_started = false; + bool m_finalized = false; +}; + +class SubtaskTimer +{ +public: + SubtaskTimer(TaskTimer &timer) : m_timer(timer) { m_timer.startSubtask(); } + ~SubtaskTimer() { m_timer.stopSubtask(m_isFinalizing); } + +protected: + void makeFinalizing() { m_isFinalizing = true; } + +private: + TaskTimer &m_timer; + bool m_isFinalizing = false; +}; + +class FinalizingSubtaskTimer : public SubtaskTimer +{ +public: + FinalizingSubtaskTimer(TaskTimer &timer) : SubtaskTimer(timer) { makeFinalizing(); } +}; + +class ThreadedSubtaskTimer +{ +public: + ThreadedSubtaskTimer(const QString &task) : m_task(task) + { + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": starting thread"; + m_timer.start(); + } + + ~ThreadedSubtaskTimer() + { + qCDebug(clangdLogTiming).noquote().nospace() << m_task << ": took " << m_timer.elapsed() + << " ms in dedicated thread"; + } +private: + const QString m_task; + QElapsedTimer m_timer; +}; + class ClangdClient::Private { public: @@ -812,7 +918,7 @@ public: using TextDocOrFile = const Utils::variant<const TextDocument *, Utils::FilePath>; using AstHandler = const std::function<void(const AstNode &ast, const MessageId &)>; MessageId getAndHandleAst(TextDocOrFile &doc, AstHandler &astHandler, - AstCallbackMode callbackMode); + AstCallbackMode callbackMode, const Range &range = {}); ClangdClient * const q; const CppEditor::ClangdSettings::Data settings; @@ -821,9 +927,13 @@ public: Utils::optional<SwitchDeclDefData> switchDeclDefData; Utils::optional<LocalRefsData> localRefsData; Utils::optional<QVersionNumber> versionNumber; - std::unordered_map<TextDocument *, CppEditor::SemanticHighlighter> highlighters; + + // The highlighters are owned by their respective documents. + std::unordered_map<TextDocument *, CppEditor::SemanticHighlighter *> highlighters; + VersionedDataCache<const TextDocument *, AstNode> astCache; VersionedDataCache<Utils::FilePath, AstNode> externalAstCache; + TaskTimer highlightingTimer{"highlighting"}; quint64 nextJobId = 0; bool isFullyIndexed = false; bool isTesting = false; @@ -1453,16 +1563,16 @@ void ClangdClient::followSymbol(TextDocument *document, return; } - const auto astHandler = [this, id = d->followSymbolData->id, range = Range(cursor)] + const auto astHandler = [this, id = d->followSymbolData->id] (const AstNode &ast, const MessageId &) { qCDebug(clangdLog) << "received ast response for cursor"; if (!d->followSymbolData || d->followSymbolData->id != id) return; - d->followSymbolData->cursorNode = getAstNode(ast, range); + d->followSymbolData->cursorNode = ast; if (d->followSymbolData->defLink.hasValidTarget()) d->handleGotoDefinitionResult(); }; - d->getAndHandleAst(document, astHandler, Private::AstCallbackMode::SyncIfPossible); + d->getAndHandleAst(document, astHandler, Private::AstCallbackMode::AlwaysAsync, Range(cursor)); } void ClangdClient::switchDeclDef(TextDocument *document, const QTextCursor &cursor, @@ -1912,18 +2022,19 @@ void ClangdClient::Private::handleGotoImplementationResult( : TextDocOrFile(defLinkFilePath); const Position defLinkPos(followSymbolData->defLink.targetLine - 1, followSymbolData->defLink.targetColumn); - const auto astHandler = [this, range = Range(defLinkPos, defLinkPos), id = followSymbolData->id] + const auto astHandler = [this, id = followSymbolData->id] (const AstNode &ast, const MessageId &) { qCDebug(clangdLog) << "received ast response for def link"; if (!followSymbolData || followSymbolData->id != id) return; - followSymbolData->defLinkNode = getAstNode(ast, range); + followSymbolData->defLinkNode = ast; if (followSymbolData->pendingSymbolInfoRequests.isEmpty() && followSymbolData->pendingGotoDefRequests.isEmpty()) { handleDocumentInfoResults(); } }; - getAndHandleAst(defLinkDocVariant, astHandler, AstCallbackMode::SyncIfPossible); + getAndHandleAst(defLinkDocVariant, astHandler, AstCallbackMode::AlwaysAsync, + Range(defLinkPos, defLinkPos)); } void ClangdClient::Private::handleDocumentInfoResults() @@ -2013,446 +2124,37 @@ void ClangdClient::Private::setHelpItemForTooltip(const MessageId &token, const q->hoverHandler()->setHelpItem(token, helpItem); } -static void collectExtraResults(QFutureInterface<HighlightingResult> &future, - HighlightingResults &results, const AstNode &ast, - QTextDocument *doc, const QString &docContent) +class ExtraHighlightingResultsCollector { - if (!ast.isValid()) - return; - - static const auto lessThan = [](const HighlightingResult &r1, - const HighlightingResult &r2) { - return r1.line < r2.line || (r1.line == r2.line && r1.column < r2.column) - || (r1.line == r2.line && r1.column == r2.column && r1.length < r2.length); - }; - const auto insert = [&](const HighlightingResult &result) { - if (!result.isValid()) // Some nodes don't have a range. - return; - const auto it = std::lower_bound(results.begin(), results.end(), result, lessThan); - if (it == results.end() || *it != result) { - qCDebug(clangdLogHighlight) << "adding additional highlighting result" - << result.line << result.column << result.length; - results.insert(it, result); - return; - } - - // This is for conversion operators, whose type part is only reported as a type by clangd. - if ((it->textStyles.mainStyle == C_TYPE - || it->textStyles.mainStyle == C_PRIMITIVE_TYPE) - && !result.textStyles.mixinStyles.empty() - && result.textStyles.mixinStyles.at(0) == C_OPERATOR) { - it->textStyles.mixinStyles = result.textStyles.mixinStyles; - } - }; - const auto setFromRange = [doc](HighlightingResult &result, const Range &range) { - if (!range.isValid()) - return; - const Position startPos = range.start(); - const Position endPos = range.end(); - result.line = startPos.line() + 1; - result.column = startPos.character() + 1; - result.length = endPos.toPositionInDocument(doc) - startPos.toPositionInDocument(doc); - }; - static const auto onlyIndexOf = [](const QStringView &view, const QStringView &s, - int from = 0) { - const int firstIndex = view.indexOf(s, from); - if (firstIndex == -1) - return -1; - const int nextIndex = view.indexOf(s, firstIndex + 1); - - // The second condion deals with the off-by-one error in TemplateSpecialization nodes; - // see below. - return nextIndex == -1 || nextIndex == firstIndex + 1 ? firstIndex : -1; - }; - - QList<AstNode> nodes = {ast}; - while (!nodes.isEmpty()) { - if (future.isCanceled()) - return; - const AstNode node = nodes.takeFirst(); - const QList<AstNode> children = node.children().value_or(QList<AstNode>()); - nodes << children; - - if (node.kind().endsWith("Literal")) { - HighlightingResult result; - result.useTextSyles = true; - const bool isStringLike = node.kind().startsWith("String") - || node.kind().startsWith("Character"); - result.textStyles.mainStyle = isStringLike ? C_STRING : C_NUMBER; - setFromRange(result, node.range()); - insert(result); - continue; - } - if (node.role() == "type" && node.kind() == "Builtin") { - HighlightingResult result; - result.useTextSyles = true; - result.textStyles.mainStyle = C_PRIMITIVE_TYPE; - setFromRange(result, node.range()); - insert(result); - continue; - } - if (node.role() == "attribute" && (node.kind() == "Override" || node.kind() == "Final")) { - HighlightingResult result; - result.useTextSyles = true; - result.textStyles.mainStyle = C_KEYWORD; - setFromRange(result, node.range()); - insert(result); - continue; - } - - const bool isExpression = node.role() == "expression"; - const bool isDeclaration = node.role() == "declaration"; - - // Unfortunately, the exact position of a specific token is usually not - // recorded in the AST, so if we need that, we have to search for it textually. - // In corner cases, this might get sabotaged by e.g. comments, in which case we give up. - const auto posForNodeStart = [doc](const AstNode &node) { - return Utils::Text::positionInText(doc, node.range().start().line() + 1, - node.range().start().character() + 1); - }; - const auto posForNodeEnd = [doc](const AstNode &node) { - return Utils::Text::positionInText(doc, node.range().end().line() + 1, - node.range().end().character() + 1); - }; - const int nodeStartPos = posForNodeStart(node); - const int nodeEndPos = posForNodeEnd(node); - - // Match question mark and colon in ternary operators. - if (isExpression && node.kind() == "ConditionalOperator") { - if (children.size() != 3) - continue; - - // The question mark is between sub-expressions 1 and 2, the colon is between - // sub-expressions 2 and 3. - const int searchStartPosQuestionMark = posForNodeEnd(children.first()); - const int searchEndPosQuestionMark = posForNodeStart(children.at(1)); - QStringView content = QStringView(docContent).mid(searchStartPosQuestionMark, - searchEndPosQuestionMark - searchStartPosQuestionMark); - const int questionMarkPos = onlyIndexOf(content, QStringView(QStringLiteral("?"))); - if (questionMarkPos == -1) - continue; - const int searchStartPosColon = posForNodeEnd(children.at(1)); - const int searchEndPosColon = posForNodeStart(children.at(2)); - content = QStringView(docContent).mid(searchStartPosColon, - searchEndPosColon - searchStartPosColon); - const int colonPos = onlyIndexOf(content, QStringView(QStringLiteral(":"))); - if (colonPos == -1) - continue; - - const int absQuestionMarkPos = searchStartPosQuestionMark + questionMarkPos; - const int absColonPos = searchStartPosColon + colonPos; - if (absQuestionMarkPos > absColonPos) - continue; - - HighlightingResult result; - result.useTextSyles = true; - result.textStyles.mainStyle = C_PUNCTUATION; - result.textStyles.mixinStyles.push_back(C_OPERATOR); - Utils::Text::convertPosition(doc, absQuestionMarkPos, &result.line, &result.column); - result.length = 1; - result.kind = CppEditor::SemanticHighlighter::TernaryIf; - insert(result); - Utils::Text::convertPosition(doc, absColonPos, &result.line, &result.column); - result.kind = CppEditor::SemanticHighlighter::TernaryElse; - insert(result); - continue; - } - - // The following functions are for matching the "<" and ">" brackets of template - // declarations, specializations and instantiations. - const auto insertAngleBracketInfo = [&docContent, doc, &insert]( - int searchStart1, int searchEnd1, int searchStart2, int searchEnd2) { - const int openingAngleBracketPos = onlyIndexOf( - QStringView(docContent).mid(searchStart1, searchEnd1 - searchStart1), - QStringView(QStringLiteral("<"))); - if (openingAngleBracketPos == -1) - return; - const int absOpeningAngleBracketPos = searchStart1 + openingAngleBracketPos; - if (absOpeningAngleBracketPos > searchStart2) - searchStart2 = absOpeningAngleBracketPos + 1; - if (searchStart2 >= searchEnd2) - return; - const int closingAngleBracketPos = onlyIndexOf( - QStringView(docContent).mid(searchStart2, searchEnd2 - searchStart2), - QStringView(QStringLiteral(">"))); - if (closingAngleBracketPos == -1) - return; - - const int absClosingAngleBracketPos = searchStart2 + closingAngleBracketPos; - if (absOpeningAngleBracketPos > absClosingAngleBracketPos) - return; - - HighlightingResult result; - result.useTextSyles = true; - result.textStyles.mainStyle = C_PUNCTUATION; - Utils::Text::convertPosition(doc, absOpeningAngleBracketPos, - &result.line, &result.column); - result.length = 1; - result.kind = CppEditor::SemanticHighlighter::AngleBracketOpen; - insert(result); - Utils::Text::convertPosition(doc, absClosingAngleBracketPos, - &result.line, &result.column); - result.kind = CppEditor::SemanticHighlighter::AngleBracketClose; - insert(result); - }; - - if (isDeclaration && (node.kind() == "FunctionTemplate" - || node.kind() == "ClassTemplate")) { - // The child nodes are the template parameters and and the function or class. - // The opening angle bracket is before the first child node, the closing angle - // bracket is before the function child node and after the last param node. - const QString classOrFunctionKind = QLatin1String(node.kind() == "FunctionTemplate" - ? "Function" : "CXXRecord"); - const auto functionOrClassIt = std::find_if(children.begin(), children.end(), - [&classOrFunctionKind](const AstNode &n) { - return n.role() == "declaration" && n.kind() == classOrFunctionKind; - }); - if (functionOrClassIt == children.end() || functionOrClassIt == children.begin()) - continue; - const int firstTemplateParamStartPos = posForNodeStart(children.first()); - const int lastTemplateParamEndPos = posForNodeEnd(*(functionOrClassIt - 1)); - const int functionOrClassStartPos = posForNodeStart(*functionOrClassIt); - insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, - lastTemplateParamEndPos, functionOrClassStartPos); - continue; - } - - static const auto findTemplateParam = [](const AstNode &n) { - return n.role() == "declaration" && (n.kind() == "TemplateTypeParm" - || n.kind() == "NonTypeTemplateParm"); - }; - - if (isDeclaration && node.kind() == "TypeAliasTemplate") { - // Children are one node of type TypeAlias and the template parameters. - // The opening angle bracket is before the first parameter and the closing - // angle bracket is after the last parameter. - // The TypeAlias node seems to appear first in the AST, even though lexically - // is comes after the parameters. We don't rely on the order here. - // Note that there is a second pair of angle brackets. That one is part of - // a TemplateSpecialization, which is handled further below. - const auto firstTemplateParam = std::find_if(children.begin(), children.end(), - findTemplateParam); - if (firstTemplateParam == children.end()) - continue; - const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(), - findTemplateParam); - QTC_ASSERT(lastTemplateParam != children.rend(), continue); - const auto typeAlias = std::find_if(children.begin(), children.end(), - [](const AstNode &n) { return n.kind() == "TypeAlias"; }); - if (typeAlias == children.end()) - continue; - - const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam); - const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam); - const int searchEndPos = posForNodeStart(*typeAlias); - insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, - lastTemplateParamEndPos, searchEndPos); - continue; - } - - if (isDeclaration && node.kind() == "ClassTemplateSpecialization") { - // There is one child of kind TemplateSpecialization. The first pair - // of angle brackets comes before that. - if (children.size() == 1) { - const int childNodePos = posForNodeStart(children.first()); - insertAngleBracketInfo(nodeStartPos, childNodePos, nodeStartPos, childNodePos); - } - continue; - } - - if (isDeclaration && node.kind() == "TemplateTemplateParm") { - // The child nodes are template arguments and template parameters. - // Arguments seem to appear before parameters in the AST, even though they - // come after them in the source code. We don't rely on the order here. - const auto firstTemplateParam = std::find_if(children.begin(), children.end(), - findTemplateParam); - if (firstTemplateParam == children.end()) - continue; - const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(), - findTemplateParam); - QTC_ASSERT(lastTemplateParam != children.rend(), continue); - const auto templateArg = std::find_if(children.begin(), children.end(), - [](const AstNode &n) { return n.role() == "template argument"; }); - - const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam); - const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam); - const int searchEndPos = templateArg == children.end() - ? nodeEndPos : posForNodeStart(*templateArg); - insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, - lastTemplateParamEndPos, searchEndPos); - continue; - } - - // {static,dynamic,reinterpret}_cast<>(). - if (isExpression && node.kind().startsWith("CXX") && node.kind().endsWith("Cast")) { - // First child is type, second child is expression. - // The opening angle bracket is before the first child, the closing angle bracket - // is between the two children. - if (children.size() == 2) { - insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.first()), - posForNodeEnd(children.first()), - posForNodeStart(children.last())); - } - continue; - } - - if (node.kind() == "TemplateSpecialization") { - // First comes the template type, then the template arguments. - // The opening angle bracket is before the first template argument, - // the closing angle bracket is after the last template argument. - // The first child node has no range, so we start searching at the parent node. - if (children.size() >= 2) { - int searchStart2 = posForNodeEnd(children.last()); - int searchEnd2 = nodeEndPos; - - // There is a weird off-by-one error on the clang side: If there is a - // nested template instantiation *and* there is no space between - // the closing angle brackets, then the inner TemplateSpecialization node's range - // will extend one character too far, covering the outer's closing angle bracket. - // This is what we are correcting for here. - // This issue is tracked at https://github.com/clangd/clangd/issues/871. - if (searchStart2 == searchEnd2) - --searchStart2; - insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.at(1)), - searchStart2, searchEnd2); - } - continue; - } - - if (!isExpression && !isDeclaration) - continue; - - // Operators, overloaded ones in particular. - static const QString operatorPrefix = "operator"; - QString detail = node.detail().value_or(QString()); - const bool isCallToNew = node.kind() == "CXXNew"; - const bool isCallToDelete = node.kind() == "CXXDelete"; - if (!isCallToNew && !isCallToDelete - && (!detail.startsWith(operatorPrefix) || detail == operatorPrefix)) { - continue; - } - - if (!isCallToNew && !isCallToDelete) - detail.remove(0, operatorPrefix.length()); - - HighlightingResult result; - result.useTextSyles = true; - const bool isConversionOp = node.kind() == "CXXConversion"; - const bool isOverloaded = !isConversionOp - && (isDeclaration || ((!isCallToNew && !isCallToDelete) - || node.arcanaContains("CXXMethod"))); - result.textStyles.mainStyle = isConversionOp - ? C_PRIMITIVE_TYPE - : isCallToNew || isCallToDelete || detail.at(0).isSpace() - ? C_KEYWORD : C_PUNCTUATION; - result.textStyles.mixinStyles.push_back(C_OPERATOR); - if (isOverloaded) - result.textStyles.mixinStyles.push_back(C_OVERLOADED_OPERATOR); - if (isDeclaration) - result.textStyles.mixinStyles.push_back(C_DECLARATION); - - const QStringView nodeText = QStringView(docContent) - .mid(nodeStartPos, nodeEndPos - nodeStartPos); - - if (isCallToNew || isCallToDelete) { - result.line = node.range().start().line() + 1; - result.column = node.range().start().character() + 1; - result.length = isCallToNew ? 3 : 6; - insert(result); - if (node.arcanaContains("array")) { - const int openingBracketOffset = nodeText.indexOf('['); - if (openingBracketOffset == -1) - continue; - const int closingBracketOffset = nodeText.lastIndexOf(']'); - if (closingBracketOffset == -1 || closingBracketOffset < openingBracketOffset) - continue; - - result.textStyles.mainStyle = C_PUNCTUATION; - result.length = 1; - Utils::Text::convertPosition(doc, - nodeStartPos + openingBracketOffset, - &result.line, &result.column); - insert(result); - Utils::Text::convertPosition(doc, - nodeStartPos + closingBracketOffset, - &result.line, &result.column); - insert(result); - } - continue; - } - - if (isExpression && (detail == QLatin1String("()") || detail == QLatin1String("[]"))) { - result.line = node.range().start().line() + 1; - result.column = node.range().start().character() + 1; - result.length = 1; - insert(result); - result.line = node.range().end().line() + 1; - result.column = node.range().end().character(); - insert(result); - continue; - } - - const int opStringLen = detail.at(0).isSpace() ? detail.length() - 1 : detail.length(); - - // The simple case: Call to operator+, +=, * etc. - if (nodeEndPos - nodeStartPos == opStringLen) { - setFromRange(result, node.range()); - insert(result); - continue; - } - - const int prefixOffset = nodeText.indexOf(operatorPrefix); - if (prefixOffset == -1) - continue; - - const bool isArray = detail == "[]"; - const bool isCall = detail == "()"; - const bool isArrayNew = detail == " new[]"; - const bool isArrayDelete = detail == " delete[]"; - const QStringView searchTerm = isArray || isCall - ? QStringView(detail).chopped(1) : isArrayNew || isArrayDelete - ? QStringView(detail).chopped(2) : detail; - const int opStringOffset = nodeText.indexOf(searchTerm, prefixOffset - + operatorPrefix.length()); - if (opStringOffset == -1 || nodeText.indexOf(operatorPrefix, opStringOffset) != -1) - continue; - - const int opStringOffsetInDoc = nodeStartPos + opStringOffset - + detail.length() - opStringLen; - Utils::Text::convertPosition(doc, opStringOffsetInDoc, &result.line, &result.column); - result.length = opStringLen; - if (isArray || isCall) - result.length = 1; - else if (isArrayNew || isArrayDelete) - result.length -= 2; - if (!isArray && !isCall) - insert(result); - if (!isArray && !isCall && !isArrayNew && !isArrayDelete) - continue; +public: + ExtraHighlightingResultsCollector(QFutureInterface<HighlightingResult> &future, + HighlightingResults &results, const AstNode &ast, + const QTextDocument *doc, const QString &docContent); - result.textStyles.mainStyle = C_PUNCTUATION; - result.length = 1; - const int openingParenOffset = nodeText.indexOf( - isCall ? '(' : '[', prefixOffset + operatorPrefix.length()); - if (openingParenOffset == -1) - continue; - const int closingParenOffset = nodeText.indexOf(isCall ? ')' : ']', openingParenOffset + 1); - if (closingParenOffset == -1 || closingParenOffset < openingParenOffset) - continue; - Utils::Text::convertPosition(doc, nodeStartPos + openingParenOffset, - &result.line, &result.column); - insert(result); - Utils::Text::convertPosition(doc, nodeStartPos + closingParenOffset, - &result.line, &result.column); - insert(result); - } -} + void collect(); +private: + static bool lessThan(const HighlightingResult &r1, const HighlightingResult &r2); + static int onlyIndexOf(const QStringView &text, const QStringView &subString, int from = 0); + int posForNodeStart(const AstNode &node) const; + int posForNodeEnd(const AstNode &node) const; + void insertResult(const HighlightingResult &result); + void insertAngleBracketInfo(int searchStart1, int searchEnd1, int searchStart2, int searchEnd2); + void setResultPosFromRange(HighlightingResult &result, const Range &range); + void collectFromNode(const AstNode &node); + void visitNode(const AstNode&node); + + QFutureInterface<HighlightingResult> &m_future; + HighlightingResults &m_results; + const AstNode &m_ast; + const QTextDocument * const m_doc; + const QString &m_docContent; +}; // clangd reports also the #ifs, #elses and #endifs around the disabled code as disabled, // and not even in a consistent manner. We don't want this, so we have to clean up here. // But note that we require this behavior, as otherwise we would not be able to grey out // e.g. empty lines after an #fdef, due to the lack of symbols. -static QList<BlockRange> cleanupDisabledCode(HighlightingResults &results, QTextDocument *doc, +static QList<BlockRange> cleanupDisabledCode(HighlightingResults &results, const QTextDocument *doc, const QString &docContent) { QList<BlockRange> ifdefedOutRanges; @@ -2513,12 +2215,13 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future, const QPointer<TextEditorWidget> &widget, int docRevision, const QVersionNumber &clangdVersion) { + ThreadedSubtaskTimer t("highlighting"); if (future.isCanceled()) { future.reportFinished(); return; } - QTextDocument doc(docContents); + const QTextDocument doc(docContents); const auto isOutputParameter = [&ast](const ExpandedSemanticToken &token) { if (token.modifiers.contains("usedAsMutableReference")) return true; @@ -2545,8 +2248,8 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future, return false; }; - const auto toResult = [&ast, &isOutputParameter, &clangdVersion] - (const ExpandedSemanticToken &token) { + const std::function<HighlightingResult(const ExpandedSemanticToken &)> toResult + = [&ast, &isOutputParameter, &clangdVersion](const ExpandedSemanticToken &token) { TextStyles styles; if (token.type == "variable") { if (token.modifiers.contains("functionScope")) { @@ -2629,13 +2332,13 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future, return HighlightingResult(token.line, token.column, token.length, styles); }; - HighlightingResults results = Utils::transform(tokens, toResult); + auto results = QtConcurrent::blockingMapped<HighlightingResults>(tokens, toResult); const QList<BlockRange> ifdefedOutBlocks = cleanupDisabledCode(results, &doc, docContents); QMetaObject::invokeMethod(widget, [widget, ifdefedOutBlocks, docRevision] { if (widget && widget->textDocument()->document()->revision() == docRevision) widget->setIfdefedOutBlocks(ifdefedOutBlocks); }, Qt::QueuedConnection); - collectExtraResults(future, results, ast, &doc, docContents); + ExtraHighlightingResultsCollector(future, results, ast, &doc, docContents).collect(); if (!future.isCanceled()) { qCDebug(clangdLog) << "reporting" << results.size() << "highlighting results"; future.reportResults(QVector<HighlightingResult>(results.cbegin(), @@ -2659,12 +2362,14 @@ static void semanticHighlighter(QFutureInterface<HighlightingResult> &future, void ClangdClient::Private::handleSemanticTokens(TextDocument *doc, const QList<ExpandedSemanticToken> &tokens) { + SubtaskTimer t(highlightingTimer); qCDebug(clangdLog()) << "handling LSP tokens" << doc->filePath() << tokens.size(); for (const ExpandedSemanticToken &t : tokens) qCDebug(clangdLogHighlight()) << '\t' << t.line << t.column << t.length << t.type << t.modifiers; const auto astHandler = [this, tokens, doc](const AstNode &ast, const MessageId &) { + FinalizingSubtaskTimer t(highlightingTimer); if (!q->documentOpen(doc)) return; if (clangdLogAst().isDebugEnabled()) @@ -2692,12 +2397,13 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc, auto it = highlighters.find(doc); if (it == highlighters.end()) { - it = highlighters.emplace(doc, doc).first; + it = highlighters.insert(std::make_pair(doc, new CppEditor::SemanticHighlighter(doc))) + .first; } else { - it->second.updateFormatMapFromFontSettings(); + it->second->updateFormatMapFromFontSettings(); } - it->second.setHighlightingRunner(runner); - it->second.run(); + it->second->setHighlightingRunner(runner); + it->second->run(); }; getAndHandleAst(doc, astHandler, AstCallbackMode::SyncIfPossible); } @@ -3014,26 +2720,30 @@ void ClangdCompletionItem::apply(TextDocumentManipulatorInterface &manipulator, MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc, const AstHandler &astHandler, - AstCallbackMode callbackMode) + AstCallbackMode callbackMode, const Range &range) { const auto textDocPtr = Utils::get_if<const TextDocument *>(&doc); const TextDocument * const textDoc = textDocPtr ? *textDocPtr : nullptr; const Utils::FilePath filePath = textDoc ? textDoc->filePath() : Utils::get<Utils::FilePath>(doc); - // If the document's AST is in the cache and is up to date, call the handler. - if (const auto ast = textDoc ? astCache.get(textDoc) : externalAstCache.get(filePath)) { - qCDebug(clangdLog) << "using AST from cache"; - switch (callbackMode) { - case AstCallbackMode::SyncIfPossible: - astHandler(*ast, {}); - break; - case AstCallbackMode::AlwaysAsync: - QMetaObject::invokeMethod(q, [ast, astHandler] { astHandler(*ast, {}); }, + // If the entire AST is requested and the document's AST is in the cache and it is up to date, + // call the handler. + const bool fullAstRequested = !range.isValid(); + if (fullAstRequested) { + if (const auto ast = textDoc ? astCache.get(textDoc) : externalAstCache.get(filePath)) { + qCDebug(clangdLog) << "using AST from cache"; + switch (callbackMode) { + case AstCallbackMode::SyncIfPossible: + astHandler(*ast, {}); + break; + case AstCallbackMode::AlwaysAsync: + QMetaObject::invokeMethod(q, [ast, astHandler] { astHandler(*ast, {}); }, Qt::QueuedConnection); - break; + break; + } + return {}; } - return {}; } // Otherwise retrieve the AST from clangd. @@ -3041,7 +2751,6 @@ MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc, class AstParams : public JsonObject { public: - AstParams() {} AstParams(const TextDocumentIdentifier &document, const Range &range = {}) { setTextDocument(document); @@ -3071,19 +2780,22 @@ MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc, explicit AstRequest(const AstParams ¶ms) : Request("textDocument/ast", params) {} }; - AstRequest request(AstParams(TextDocumentIdentifier(DocumentUri::fromFilePath(filePath)))); + AstRequest request(AstParams(TextDocumentIdentifier(DocumentUri::fromFilePath(filePath)), + range)); request.setResponseCallback([this, filePath, guardedTextDoc = QPointer(textDoc), astHandler, - docRev = textDoc ? getRevision(textDoc) : -1, + fullAstRequested, docRev = textDoc ? getRevision(textDoc) : -1, fileRev = getRevision(filePath), reqId = request.id()] (AstRequest::Response response) { qCDebug(clangdLog) << "retrieved AST from clangd"; const auto result = response.result(); const AstNode ast = result ? *result : AstNode(); - if (guardedTextDoc) { - if (docRev == getRevision(guardedTextDoc)) - astCache.insert(guardedTextDoc, ast); - } else if (fileRev == getRevision(filePath) && !q->documentForFilePath(filePath)) { - externalAstCache.insert(filePath, ast); + if (fullAstRequested) { + if (guardedTextDoc) { + if (docRev == getRevision(guardedTextDoc)) + astCache.insert(guardedTextDoc, ast); + } else if (fileRev == getRevision(filePath) && !q->documentForFilePath(filePath)) { + externalAstCache.insert(filePath, ast); + } } astHandler(ast, reqId); }); @@ -3092,6 +2804,465 @@ MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc, return request.id(); } +ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector( + QFutureInterface<HighlightingResult> &future, HighlightingResults &results, + const AstNode &ast, const QTextDocument *doc, const QString &docContent) + : m_future(future), m_results(results), m_ast(ast), m_doc(doc), m_docContent(docContent) +{ + +} + +void ExtraHighlightingResultsCollector::collect() +{ + if (!m_ast.isValid()) + return; + visitNode(m_ast); +} + +bool ExtraHighlightingResultsCollector::lessThan(const HighlightingResult &r1, + const HighlightingResult &r2) +{ + return r1.line < r2.line || (r1.line == r2.line && r1.column < r2.column) + || (r1.line == r2.line && r1.column == r2.column && r1.length < r2.length); +} + +int ExtraHighlightingResultsCollector::onlyIndexOf(const QStringView &text, + const QStringView &subString, int from) +{ + const int firstIndex = text.indexOf(subString, from); + if (firstIndex == -1) + return -1; + const int nextIndex = text.indexOf(subString, firstIndex + 1); + + // The second condion deals with the off-by-one error in TemplateSpecialization nodes; + // see collectFromNode(). + return nextIndex == -1 || nextIndex == firstIndex + 1 ? firstIndex : -1; +} + +// Unfortunately, the exact position of a specific token is usually not +// recorded in the AST, so if we need that, we have to search for it textually. +// In corner cases, this might get sabotaged by e.g. comments, in which case we give up. +int ExtraHighlightingResultsCollector::posForNodeStart(const AstNode &node) const +{ + return Utils::Text::positionInText(m_doc, node.range().start().line() + 1, + node.range().start().character() + 1); +} + +int ExtraHighlightingResultsCollector::posForNodeEnd(const AstNode &node) const +{ + return Utils::Text::positionInText(m_doc, node.range().end().line() + 1, + node.range().end().character() + 1); +} + +void ExtraHighlightingResultsCollector::insertResult(const HighlightingResult &result) +{ + if (!result.isValid()) // Some nodes don't have a range. + return; + const auto it = std::lower_bound(m_results.begin(), m_results.end(), result, lessThan); + if (it == m_results.end() || *it != result) { + qCDebug(clangdLogHighlight) << "adding additional highlighting result" + << result.line << result.column << result.length; + m_results.insert(it, result); + return; + } + + // This is for conversion operators, whose type part is only reported as a type by clangd. + if ((it->textStyles.mainStyle == C_TYPE + || it->textStyles.mainStyle == C_PRIMITIVE_TYPE) + && !result.textStyles.mixinStyles.empty() + && result.textStyles.mixinStyles.at(0) == C_OPERATOR) { + it->textStyles.mixinStyles = result.textStyles.mixinStyles; + } +} + +// For matching the "<" and ">" brackets of template declarations, specializations +// and instantiations. +void ExtraHighlightingResultsCollector::insertAngleBracketInfo(int searchStart1, int searchEnd1, + int searchStart2, int searchEnd2) +{ + const int openingAngleBracketPos = onlyIndexOf( + QStringView(m_docContent).mid(searchStart1, searchEnd1 - searchStart1), + QStringView(QStringLiteral("<"))); + if (openingAngleBracketPos == -1) + return; + const int absOpeningAngleBracketPos = searchStart1 + openingAngleBracketPos; + if (absOpeningAngleBracketPos > searchStart2) + searchStart2 = absOpeningAngleBracketPos + 1; + if (searchStart2 >= searchEnd2) + return; + const int closingAngleBracketPos = onlyIndexOf( + QStringView(m_docContent).mid(searchStart2, searchEnd2 - searchStart2), + QStringView(QStringLiteral(">"))); + if (closingAngleBracketPos == -1) + return; + + const int absClosingAngleBracketPos = searchStart2 + closingAngleBracketPos; + if (absOpeningAngleBracketPos > absClosingAngleBracketPos) + return; + + HighlightingResult result; + result.useTextSyles = true; + result.textStyles.mainStyle = C_PUNCTUATION; + Utils::Text::convertPosition(m_doc, absOpeningAngleBracketPos, &result.line, &result.column); + result.length = 1; + result.kind = CppEditor::SemanticHighlighter::AngleBracketOpen; + insertResult(result); + Utils::Text::convertPosition(m_doc, absClosingAngleBracketPos, &result.line, &result.column); + result.kind = CppEditor::SemanticHighlighter::AngleBracketClose; + insertResult(result); +} + +void ExtraHighlightingResultsCollector::setResultPosFromRange(HighlightingResult &result, + const Range &range) +{ + if (!range.isValid()) + return; + const Position startPos = range.start(); + const Position endPos = range.end(); + result.line = startPos.line() + 1; + result.column = startPos.character() + 1; + result.length = endPos.toPositionInDocument(m_doc) - startPos.toPositionInDocument(m_doc); +} + +void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node) +{ + if (node.kind().endsWith("Literal")) { + HighlightingResult result; + result.useTextSyles = true; + const bool isStringLike = node.kind().startsWith("String") + || node.kind().startsWith("Character"); + result.textStyles.mainStyle = isStringLike ? C_STRING : C_NUMBER; + setResultPosFromRange(result, node.range()); + insertResult(result); + return; + } + if (node.role() == "type" && node.kind() == "Builtin") { + HighlightingResult result; + result.useTextSyles = true; + result.textStyles.mainStyle = C_PRIMITIVE_TYPE; + setResultPosFromRange(result, node.range()); + insertResult(result); + return; + } + if (node.role() == "attribute" && (node.kind() == "Override" || node.kind() == "Final")) { + HighlightingResult result; + result.useTextSyles = true; + result.textStyles.mainStyle = C_KEYWORD; + setResultPosFromRange(result, node.range()); + insertResult(result); + return; + } + + const bool isExpression = node.role() == "expression"; + const bool isDeclaration = node.role() == "declaration"; + + const int nodeStartPos = posForNodeStart(node); + const int nodeEndPos = posForNodeEnd(node); + const QList<AstNode> children = node.children().value_or(QList<AstNode>()); + + // Match question mark and colon in ternary operators. + if (isExpression && node.kind() == "ConditionalOperator") { + if (children.size() != 3) + return; + + // The question mark is between sub-expressions 1 and 2, the colon is between + // sub-expressions 2 and 3. + const int searchStartPosQuestionMark = posForNodeEnd(children.first()); + const int searchEndPosQuestionMark = posForNodeStart(children.at(1)); + QStringView content = QStringView(m_docContent).mid( + searchStartPosQuestionMark, + searchEndPosQuestionMark - searchStartPosQuestionMark); + const int questionMarkPos = onlyIndexOf(content, QStringView(QStringLiteral("?"))); + if (questionMarkPos == -1) + return; + const int searchStartPosColon = posForNodeEnd(children.at(1)); + const int searchEndPosColon = posForNodeStart(children.at(2)); + content = QStringView(m_docContent).mid(searchStartPosColon, + searchEndPosColon - searchStartPosColon); + const int colonPos = onlyIndexOf(content, QStringView(QStringLiteral(":"))); + if (colonPos == -1) + return; + + const int absQuestionMarkPos = searchStartPosQuestionMark + questionMarkPos; + const int absColonPos = searchStartPosColon + colonPos; + if (absQuestionMarkPos > absColonPos) + return; + + HighlightingResult result; + result.useTextSyles = true; + result.textStyles.mainStyle = C_PUNCTUATION; + result.textStyles.mixinStyles.push_back(C_OPERATOR); + Utils::Text::convertPosition(m_doc, absQuestionMarkPos, &result.line, &result.column); + result.length = 1; + result.kind = CppEditor::SemanticHighlighter::TernaryIf; + insertResult(result); + Utils::Text::convertPosition(m_doc, absColonPos, &result.line, &result.column); + result.kind = CppEditor::SemanticHighlighter::TernaryElse; + insertResult(result); + return; + } + + if (isDeclaration && (node.kind() == "FunctionTemplate" + || node.kind() == "ClassTemplate")) { + // The child nodes are the template parameters and and the function or class. + // The opening angle bracket is before the first child node, the closing angle + // bracket is before the function child node and after the last param node. + const QString classOrFunctionKind = QLatin1String(node.kind() == "FunctionTemplate" + ? "Function" : "CXXRecord"); + const auto functionOrClassIt = std::find_if(children.begin(), children.end(), + [&classOrFunctionKind](const AstNode &n) { + return n.role() == "declaration" && n.kind() == classOrFunctionKind; + }); + if (functionOrClassIt == children.end() || functionOrClassIt == children.begin()) + return; + const int firstTemplateParamStartPos = posForNodeStart(children.first()); + const int lastTemplateParamEndPos = posForNodeEnd(*(functionOrClassIt - 1)); + const int functionOrClassStartPos = posForNodeStart(*functionOrClassIt); + insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, + lastTemplateParamEndPos, functionOrClassStartPos); + return; + } + + const auto isTemplateParamDecl = [](const AstNode &node) { + return node.isTemplateParameterDeclaration(); + }; + if (isDeclaration && node.kind() == "TypeAliasTemplate") { + // Children are one node of type TypeAlias and the template parameters. + // The opening angle bracket is before the first parameter and the closing + // angle bracket is after the last parameter. + // The TypeAlias node seems to appear first in the AST, even though lexically + // is comes after the parameters. We don't rely on the order here. + // Note that there is a second pair of angle brackets. That one is part of + // a TemplateSpecialization, which is handled further below. + const auto firstTemplateParam = std::find_if(children.begin(), children.end(), + isTemplateParamDecl); + if (firstTemplateParam == children.end()) + return; + const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(), + isTemplateParamDecl); + QTC_ASSERT(lastTemplateParam != children.rend(), return); + const auto typeAlias = std::find_if(children.begin(), children.end(), + [](const AstNode &n) { return n.kind() == "TypeAlias"; }); + if (typeAlias == children.end()) + return; + + const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam); + const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam); + const int searchEndPos = posForNodeStart(*typeAlias); + insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, + lastTemplateParamEndPos, searchEndPos); + return; + } + + if (isDeclaration && node.kind() == "ClassTemplateSpecialization") { + // There is one child of kind TemplateSpecialization. The first pair + // of angle brackets comes before that. + if (children.size() == 1) { + const int childNodePos = posForNodeStart(children.first()); + insertAngleBracketInfo(nodeStartPos, childNodePos, nodeStartPos, childNodePos); + } + return; + } + + if (isDeclaration && node.kind() == "TemplateTemplateParm") { + // The child nodes are template arguments and template parameters. + // Arguments seem to appear before parameters in the AST, even though they + // come after them in the source code. We don't rely on the order here. + const auto firstTemplateParam = std::find_if(children.begin(), children.end(), + isTemplateParamDecl); + if (firstTemplateParam == children.end()) + return; + const auto lastTemplateParam = std::find_if(children.rbegin(), children.rend(), + isTemplateParamDecl); + QTC_ASSERT(lastTemplateParam != children.rend(), return); + const auto templateArg = std::find_if(children.begin(), children.end(), + [](const AstNode &n) { return n.role() == "template argument"; }); + + const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam); + const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam); + const int searchEndPos = templateArg == children.end() + ? nodeEndPos : posForNodeStart(*templateArg); + insertAngleBracketInfo(nodeStartPos, firstTemplateParamStartPos, + lastTemplateParamEndPos, searchEndPos); + return; + } + + // {static,dynamic,reinterpret}_cast<>(). + if (isExpression && node.kind().startsWith("CXX") && node.kind().endsWith("Cast")) { + // First child is type, second child is expression. + // The opening angle bracket is before the first child, the closing angle bracket + // is between the two children. + if (children.size() == 2) { + insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.first()), + posForNodeEnd(children.first()), + posForNodeStart(children.last())); + } + return; + } + + if (node.kind() == "TemplateSpecialization") { + // First comes the template type, then the template arguments. + // The opening angle bracket is before the first template argument, + // the closing angle bracket is after the last template argument. + // The first child node has no range, so we start searching at the parent node. + if (children.size() >= 2) { + int searchStart2 = posForNodeEnd(children.last()); + int searchEnd2 = nodeEndPos; + + // There is a weird off-by-one error on the clang side: If there is a + // nested template instantiation *and* there is no space between + // the closing angle brackets, then the inner TemplateSpecialization node's range + // will extend one character too far, covering the outer's closing angle bracket. + // This is what we are correcting for here. + // This issue is tracked at https://github.com/clangd/clangd/issues/871. + if (searchStart2 == searchEnd2) + --searchStart2; + insertAngleBracketInfo(nodeStartPos, posForNodeStart(children.at(1)), + searchStart2, searchEnd2); + } + return; + } + + if (!isExpression && !isDeclaration) + return; + + // Operators, overloaded ones in particular. + static const QString operatorPrefix = "operator"; + QString detail = node.detail().value_or(QString()); + const bool isCallToNew = node.kind() == "CXXNew"; + const bool isCallToDelete = node.kind() == "CXXDelete"; + if (!isCallToNew && !isCallToDelete + && (!detail.startsWith(operatorPrefix) || detail == operatorPrefix)) { + return; + } + + if (!isCallToNew && !isCallToDelete) + detail.remove(0, operatorPrefix.length()); + + HighlightingResult result; + result.useTextSyles = true; + const bool isConversionOp = node.kind() == "CXXConversion"; + const bool isOverloaded = !isConversionOp + && (isDeclaration || ((!isCallToNew && !isCallToDelete) + || node.arcanaContains("CXXMethod"))); + result.textStyles.mainStyle = isConversionOp + ? C_PRIMITIVE_TYPE + : isCallToNew || isCallToDelete || detail.at(0).isSpace() + ? C_KEYWORD : C_PUNCTUATION; + result.textStyles.mixinStyles.push_back(C_OPERATOR); + if (isOverloaded) + result.textStyles.mixinStyles.push_back(C_OVERLOADED_OPERATOR); + if (isDeclaration) + result.textStyles.mixinStyles.push_back(C_DECLARATION); + + const QStringView nodeText = QStringView(m_docContent) + .mid(nodeStartPos, nodeEndPos - nodeStartPos); + + if (isCallToNew || isCallToDelete) { + result.line = node.range().start().line() + 1; + result.column = node.range().start().character() + 1; + result.length = isCallToNew ? 3 : 6; + insertResult(result); + if (node.arcanaContains("array")) { + const int openingBracketOffset = nodeText.indexOf('['); + if (openingBracketOffset == -1) + return; + const int closingBracketOffset = nodeText.lastIndexOf(']'); + if (closingBracketOffset == -1 || closingBracketOffset < openingBracketOffset) + return; + + result.textStyles.mainStyle = C_PUNCTUATION; + result.length = 1; + Utils::Text::convertPosition(m_doc, + nodeStartPos + openingBracketOffset, + &result.line, &result.column); + insertResult(result); + Utils::Text::convertPosition(m_doc, + nodeStartPos + closingBracketOffset, + &result.line, &result.column); + insertResult(result); + } + return; + } + + if (isExpression && (detail == QLatin1String("()") || detail == QLatin1String("[]"))) { + result.line = node.range().start().line() + 1; + result.column = node.range().start().character() + 1; + result.length = 1; + insertResult(result); + result.line = node.range().end().line() + 1; + result.column = node.range().end().character(); + insertResult(result); + return; + } + + const int opStringLen = detail.at(0).isSpace() ? detail.length() - 1 : detail.length(); + + // The simple case: Call to operator+, +=, * etc. + if (nodeEndPos - nodeStartPos == opStringLen) { + setResultPosFromRange(result, node.range()); + insertResult(result); + return; + } + + const int prefixOffset = nodeText.indexOf(operatorPrefix); + if (prefixOffset == -1) + return; + + const bool isArray = detail == "[]"; + const bool isCall = detail == "()"; + const bool isArrayNew = detail == " new[]"; + const bool isArrayDelete = detail == " delete[]"; + const QStringView searchTerm = isArray || isCall + ? QStringView(detail).chopped(1) : isArrayNew || isArrayDelete + ? QStringView(detail).chopped(2) : detail; + const int opStringOffset = nodeText.indexOf(searchTerm, prefixOffset + + operatorPrefix.length()); + if (opStringOffset == -1 || nodeText.indexOf(operatorPrefix, opStringOffset) != -1) + return; + + const int opStringOffsetInDoc = nodeStartPos + opStringOffset + + detail.length() - opStringLen; + Utils::Text::convertPosition(m_doc, opStringOffsetInDoc, &result.line, &result.column); + result.length = opStringLen; + if (isArray || isCall) + result.length = 1; + else if (isArrayNew || isArrayDelete) + result.length -= 2; + if (!isArray && !isCall) + insertResult(result); + if (!isArray && !isCall && !isArrayNew && !isArrayDelete) + return; + + result.textStyles.mainStyle = C_PUNCTUATION; + result.length = 1; + const int openingParenOffset = nodeText.indexOf( + isCall ? '(' : '[', prefixOffset + operatorPrefix.length()); + if (openingParenOffset == -1) + return; + const int closingParenOffset = nodeText.indexOf(isCall ? ')' : ']', openingParenOffset + 1); + if (closingParenOffset == -1 || closingParenOffset < openingParenOffset) + return; + Utils::Text::convertPosition(m_doc, nodeStartPos + openingParenOffset, + &result.line, &result.column); + insertResult(result); + Utils::Text::convertPosition(m_doc, nodeStartPos + closingParenOffset, + &result.line, &result.column); + insertResult(result); +} + +void ExtraHighlightingResultsCollector::visitNode(const AstNode &node) +{ + if (m_future.isCanceled()) + return; + collectFromNode(node); + const auto children = node.children(); + if (!children) + return; + for (const AstNode &childNode : *children) + visitNode(childNode); +} + } // namespace Internal } // namespace ClangCodeModel diff --git a/src/plugins/clangformat/CMakeLists.txt b/src/plugins/clangformat/CMakeLists.txt index eb6e0687e6..5db5f8111a 100644 --- a/src/plugins/clangformat/CMakeLists.txt +++ b/src/plugins/clangformat/CMakeLists.txt @@ -8,6 +8,7 @@ add_qtc_plugin(ClangFormat clangformatchecks.ui clangformatconfigwidget.cpp clangformatconfigwidget.h clangformatconfigwidget.ui clangformatconstants.h + clangformatfile.cpp clangformatfile.h clangformatindenter.cpp clangformatindenter.h clangformatplugin.cpp clangformatplugin.h clangformatsettings.cpp clangformatsettings.h diff --git a/src/plugins/clangformat/clangformat.pro b/src/plugins/clangformat/clangformat.pro index 4d751db2ba..da7645613c 100644 --- a/src/plugins/clangformat/clangformat.pro +++ b/src/plugins/clangformat/clangformat.pro @@ -15,6 +15,7 @@ unix:!macos:QMAKE_LFLAGS += -Wl,--exclude-libs,ALL SOURCES += \ clangformatconfigwidget.cpp \ + clangformatfile.cpp \ clangformatindenter.cpp \ clangformatplugin.cpp \ clangformatsettings.cpp \ @@ -22,6 +23,7 @@ SOURCES += \ HEADERS += \ clangformatconfigwidget.h \ + clangformatfile.h \ clangformatindenter.h \ clangformatplugin.h \ clangformatsettings.h \ diff --git a/src/plugins/clangformat/clangformat.qbs b/src/plugins/clangformat/clangformat.qbs index ef8ae44781..fcd77b7c1b 100644 --- a/src/plugins/clangformat/clangformat.qbs +++ b/src/plugins/clangformat/clangformat.qbs @@ -33,6 +33,8 @@ QtcPlugin { "clangformatconfigwidget.h", "clangformatconfigwidget.ui", "clangformatconstants.h", + "clangformatfile.cpp", + "clangformatfile.h", "clangformatindenter.cpp", "clangformatindenter.h", "clangformatplugin.cpp", diff --git a/src/plugins/clangformat/clangformatconfigwidget.cpp b/src/plugins/clangformat/clangformatconfigwidget.cpp index f0cc5d9700..0dc6d4c54f 100644 --- a/src/plugins/clangformat/clangformatconfigwidget.cpp +++ b/src/plugins/clangformat/clangformatconfigwidget.cpp @@ -27,6 +27,7 @@ #include "clangformatconstants.h" #include "clangformatindenter.h" +#include "clangformatfile.h" #include "clangformatsettings.h" #include "clangformatutils.h" #include "ui_clangformatchecks.h" @@ -121,6 +122,12 @@ ClangFormatConfigWidget::ClangFormatConfigWidget(ProjectExplorer::Project *proje { m_ui->setupUi(this); + Utils::FilePath filePath = Core::ICore::userResourcePath(); + if (m_project) + filePath = filePath / "clang-format/" / currentProjectUniqueId(); + filePath = filePath / QLatin1String(Constants::SETTINGS_FILE_NAME); + m_config = std::make_unique<ClangFormatFile>(filePath); + initChecksAndPreview(); if (m_project) { @@ -147,6 +154,8 @@ ClangFormatConfigWidget::ClangFormatConfigWidget(ProjectExplorer::Project *proje connectChecks(); } +ClangFormatConfigWidget::~ClangFormatConfigWidget() = default; + void ClangFormatConfigWidget::initChecksAndPreview() { m_checksScrollArea = new QScrollArea(); @@ -191,7 +200,7 @@ void ClangFormatConfigWidget::connectChecks() continue; } - auto button = qobject_cast<QPushButton *>(child); + const auto button = qobject_cast<QPushButton *>(child); if (button != nullptr) connect(button, &QPushButton::clicked, this, &ClangFormatConfigWidget::onTableChanged); } @@ -202,15 +211,7 @@ void ClangFormatConfigWidget::onTableChanged() if (m_disableTableUpdate) return; - const std::string newConfig = tableToString(sender()); - if (newConfig.empty()) - return; - const std::string oldConfig = m_project ? currentProjectConfigText() - : currentGlobalConfigText(); - saveConfig(newConfig); - fillTable(); - updatePreview(); - saveConfig(oldConfig); + saveChanges(sender()); } void ClangFormatConfigWidget::hideGlobalCheckboxes() @@ -379,16 +380,17 @@ void ClangFormatConfigWidget::fillTable() } } -std::string ClangFormatConfigWidget::tableToString(QObject *sender) +void ClangFormatConfigWidget::saveChanges(QObject *sender) { - std::stringstream content; - content << "---"; - if (sender->objectName() == "BasedOnStyle") { - auto *basedOnStyle = m_checksWidget->findChild<QComboBox *>("BasedOnStyle"); - content << "\nBasedOnStyle: " << basedOnStyle->currentText().toStdString() << '\n'; + const auto *basedOnStyle = m_checksWidget->findChild<QComboBox *>("BasedOnStyle"); + m_config->setBasedOnStyle(basedOnStyle->currentText()); } else { + QList<ClangFormatFile::Field> fields; + for (QObject *child : m_checksWidget->children()) { + if (child->objectName() == "BasedOnStyle") + continue; auto *label = qobject_cast<QLabel *>(child); if (!label) continue; @@ -396,7 +398,7 @@ std::string ClangFormatConfigWidget::tableToString(QObject *sender) QWidget *valueWidget = m_checksWidget->findChild<QWidget *>(label->text().trimmed()); if (!valueWidget) { // Currently BraceWrapping only. - content << '\n' << label->text().toStdString() << ":"; + fields.append({label->text(), ""}); continue; } @@ -410,46 +412,34 @@ std::string ClangFormatConfigWidget::tableToString(QObject *sender) if (plainText->toPlainText().trimmed().isEmpty()) continue; - content << '\n' << label->text().toStdString() << ":"; + + std::stringstream content; QStringList list = plainText->toPlainText().split('\n'); for (const QString &line : list) content << "\n " << line.toStdString(); + + fields.append({label->text(), QString::fromStdString(content.str())}); } else { - auto *comboBox = qobject_cast<QComboBox *>(valueWidget); - std::string text; - if (comboBox) { - text = comboBox->currentText().toStdString(); + QString text; + if (auto *comboBox = qobject_cast<QComboBox *>(valueWidget)) { + text = comboBox->currentText(); } else { auto *lineEdit = qobject_cast<QLineEdit *>(valueWidget); QTC_ASSERT(lineEdit, continue;); - text = lineEdit->text().toStdString(); + text = lineEdit->text(); } - if (!text.empty() && text != "Default") - content << '\n' << label->text().toStdString() << ": " << text; + if (!text.isEmpty() && text != "Default") + fields.append({label->text(), text}); } } - content << '\n'; + m_config->changeFields(fields); } - std::string text = content.str(); - clang::format::FormatStyle style; - style.Language = clang::format::FormatStyle::LK_Cpp; - const std::error_code error = clang::format::parseConfiguration(text, &style); - if (error.value() != static_cast<int>(clang::format::ParseError::Success)) { - QMessageBox::warning(this, - tr("Error in ClangFormat configuration"), - QString::fromStdString(error.message())); - fillTable(); - updatePreview(); - return std::string(); - } - - return text; + fillTable(); + updatePreview(); } -ClangFormatConfigWidget::~ClangFormatConfigWidget() = default; - void ClangFormatConfigWidget::apply() { ClangFormatSettings &settings = ClangFormatSettings::instance(); @@ -466,28 +456,7 @@ void ClangFormatConfigWidget::apply() if (!m_checksWidget->isVisible()) return; - const std::string config = tableToString(this); - if (config.empty()) - return; - - saveConfig(config); - fillTable(); - updatePreview(); -} - -void ClangFormatConfigWidget::saveConfig(const std::string &text) const -{ - QString filePath = Core::ICore::userResourcePath().toString(); - if (m_project) - filePath += "/clang-format/" + currentProjectUniqueId(); - filePath += "/" + QLatin1String(Constants::SETTINGS_FILE_NAME); - - QFile file(filePath); - if (!file.open(QFile::WriteOnly)) - return; - - file.write(text.c_str()); - file.close(); + saveChanges(this); } } // namespace ClangFormat diff --git a/src/plugins/clangformat/clangformatconfigwidget.h b/src/plugins/clangformat/clangformatconfigwidget.h index 869ddeccb7..4c261f9992 100644 --- a/src/plugins/clangformat/clangformatconfigwidget.h +++ b/src/plugins/clangformat/clangformatconfigwidget.h @@ -44,6 +44,7 @@ namespace Ui { class ClangFormatConfigWidget; class ClangFormatChecksWidget; } +class ClangFormatFile; class ClangFormatConfigWidget : public TextEditor::CodeStyleEditorWidget { @@ -66,18 +67,17 @@ private: void connectChecks(); void fillTable(); - std::string tableToString(QObject *sender); + void saveChanges(QObject *sender); void hideGlobalCheckboxes(); void showGlobalCheckboxes(); - - void saveConfig(const std::string &text) const; void updatePreview(); ProjectExplorer::Project *m_project; QWidget *m_checksWidget; QScrollArea *m_checksScrollArea; TextEditor::SnippetEditorWidget *m_preview; + std::unique_ptr<ClangFormatFile> m_config; std::unique_ptr<Ui::ClangFormatChecksWidget> m_checks; std::unique_ptr<Ui::ClangFormatConfigWidget> m_ui; bool m_disableTableUpdate = false; diff --git a/src/plugins/clangformat/clangformatfile.cpp b/src/plugins/clangformat/clangformatfile.cpp new file mode 100644 index 0000000000..8e532ea797 --- /dev/null +++ b/src/plugins/clangformat/clangformatfile.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 "clangformatfile.h" +#include <sstream> + +using namespace ClangFormat; + +ClangFormatFile::ClangFormatFile(Utils::FilePath filePath) + : m_filePath(filePath) +{ + if (!m_filePath.exists()) { + resetStyleToLLVM(); + return; + } + + m_style.Language = clang::format::FormatStyle::LK_Cpp; + const std::error_code error + = clang::format::parseConfiguration(m_filePath.fileContents().toStdString(), &m_style); + if (error.value() != static_cast<int>(clang::format::ParseError::Success)) { + resetStyleToLLVM(); + } +} + +clang::format::FormatStyle ClangFormatFile::format() { + return m_style; +} + +Utils::FilePath ClangFormatFile::filePath() +{ + return m_filePath; +} + +void ClangFormatFile::setStyle(clang::format::FormatStyle style) +{ + m_style = style; + saveNewFormat(); +} + +void ClangFormatFile::resetStyleToLLVM() +{ + m_style = clang::format::getLLVMStyle(); + saveNewFormat(); +} + +void ClangFormatFile::setBasedOnStyle(QString styleName) +{ + changeField({"BasedOnStyle", styleName}); + saveNewFormat(); +} + +QString ClangFormatFile::setStyle(QString style) +{ + const std::error_code error = clang::format::parseConfiguration(style.toStdString(), &m_style); + if (error.value() != static_cast<int>(clang::format::ParseError::Success)) { + return QString::fromStdString(error.message()); + } + + saveNewFormat(style.toUtf8()); + return ""; +} + +QString ClangFormatFile::changeField(Field field) +{ + return changeFields({field}); +} + +QString ClangFormatFile::changeFields(QList<Field> fields) +{ + std::stringstream content; + content << "---" << "\n"; + + for (const auto &field : fields) { + content << field.first.toStdString() << ": " << field.second.toStdString() << "\n"; + } + + return setStyle(QString::fromStdString(content.str())); +} + +void ClangFormatFile::saveNewFormat() +{ + std::string style = clang::format::configurationAsText(m_style); + + // workaround: configurationAsText() add comment "# " before BasedOnStyle line + const int pos = style.find("# BasedOnStyle"); + if (pos < style.size()) + style.erase(pos, 2); + m_filePath.writeFileContents(QByteArray::fromStdString(style)); +} + +void ClangFormatFile::saveNewFormat(QByteArray style) +{ + m_filePath.writeFileContents(style); +} diff --git a/src/plugins/clangformat/clangformatfile.h b/src/plugins/clangformat/clangformatfile.h new file mode 100644 index 0000000000..3fe9010f44 --- /dev/null +++ b/src/plugins/clangformat/clangformatfile.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 "utils/filepath.h" +#include <clang/Format/Format.h> + +namespace ClangFormat { + +class ClangFormatFile +{ +public: + explicit ClangFormatFile(Utils::FilePath file); + clang::format::FormatStyle format(); + + Utils::FilePath filePath(); + void resetStyleToLLVM(); + void setBasedOnStyle(QString styleName); + void setStyle(clang::format::FormatStyle style); + QString setStyle(QString style); + void clearBasedOnStyle(); + + using Field = std::pair<QString, QString>; + QString changeFields(QList<Field> fields); + QString changeField(Field field); + +private: + void saveNewFormat(); + void saveNewFormat(QByteArray style); + +private: + Utils::FilePath m_filePath; + clang::format::FormatStyle m_style; +}; + + +} // namespace ClangFormat diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp index 5371aae25a..f43decd940 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp @@ -936,7 +936,7 @@ CMakeBuildConfiguration::CMakeBuildConfiguration(Target *target, Id id) initialArgs.append("-DCMAKE_TOOLCHAIN_FILE:PATH=" + ndkLocation.pathAppended("build/cmake/android.toolchain.cmake").path()); - auto androidAbis = bs->data(Android::Constants::AndroidABIs).toStringList(); + auto androidAbis = bs->data(Android::Constants::AndroidMkSpecAbis).toStringList(); QString preferredAbi; if (androidAbis.contains(ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A)) { preferredAbi = ProjectExplorer::Constants::ANDROID_ABI_ARMEABI_V7A; diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp index 6c84e3d872..86d97babfe 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp @@ -128,20 +128,26 @@ QVariant CMakeTargetNode::data(Utils::Id role) const return {}; }; + if (role == Android::Constants::AndroidAbi) + return value(Android::Constants::ANDROID_ABI); + + if (role == Android::Constants::AndroidAbis) + return value(Android::Constants::ANDROID_ABIS); + + // TODO: Concerns the variables below. Qt 6 uses target properties which cannot be read + // by the current mechanism, and the variables start with "Qt_" prefix. + if (role == Android::Constants::AndroidPackageSourceDir) return value(Android::Constants::ANDROID_PACKAGE_SOURCE_DIR); - if (role == Android::Constants::AndroidDeploySettingsFile) - return value(Android::Constants::ANDROID_DEPLOYMENT_SETTINGS_FILE); - if (role == Android::Constants::AndroidExtraLibs) return value(Android::Constants::ANDROID_EXTRA_LIBS); - if (role == Android::Constants::ANDROID_APPLICATION_ARGUMENTS) - return value(Android::Constants::QT_ANDROID_APPLICATION_ARGUMENTS); + if (role == Android::Constants::AndroidDeploySettingsFile) + return value(Android::Constants::ANDROID_DEPLOYMENT_SETTINGS_FILE); - if (role == Android::Constants::AndroidArch) - return value(Android::Constants::ANDROID_ABI); + if (role == Android::Constants::AndroidApplicationArgs) + return value(Android::Constants::ANDROID_APPLICATION_ARGUMENTS); if (role == Android::Constants::ANDROID_ABIS) return value(Android::Constants::ANDROID_ABIS); @@ -152,6 +158,9 @@ QVariant CMakeTargetNode::data(Utils::Id role) const if (role == Android::Constants::AndroidTargets) return values("TARGETS_BUILD_PATH"); + if (role == Android::Constants::AndroidApk) + return {}; + if (role == Ios::Constants::IosTarget) { // For some reason the artifact is e.g. "Debug/untitled.app/untitled" which is wrong. // It actually is e.g. "Debug-iphonesimulator/untitled.app/untitled". diff --git a/src/plugins/cmakeprojectmanager/cmaketool.cpp b/src/plugins/cmakeprojectmanager/cmaketool.cpp index 51daa1bb5f..d407adeb66 100644 --- a/src/plugins/cmakeprojectmanager/cmaketool.cpp +++ b/src/plugins/cmakeprojectmanager/cmaketool.cpp @@ -360,7 +360,7 @@ Utils::optional<CMakeTool::ReaderType> CMakeTool::readerType() const FilePath CMakeTool::searchQchFile(const FilePath &executable) { - if (executable.isEmpty()) + if (executable.isEmpty() || executable.needsDevice()) // do not register docs from devices return {}; FilePath prefixDir = executable.parentDir().parentDir(); diff --git a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp index d893370028..cabac0b3b9 100644 --- a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp +++ b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp @@ -322,8 +322,14 @@ static QStringList splitFragments(const QStringList &fragments) return result; } +bool isPchFile(const FilePath &buildDirectory, const FilePath &path) +{ + return path.isChildOf(buildDirectory) && path.fileName().startsWith("cmake_pch"); +} + RawProjectParts generateRawProjectParts(const PreprocessedData &input, - const FilePath &sourceDirectory) + const FilePath &sourceDirectory, + const FilePath &buildDirectory) { RawProjectParts rpps; @@ -331,8 +337,12 @@ RawProjectParts generateRawProjectParts(const PreprocessedData &input, for (const TargetDetails &t : input.targetDetails) { QDir sourceDir(sourceDirectory.toString()); + // Do not tread generated files and CMake precompiled headers as project files + const auto sourceFiles = Utils::filtered(t.sources, [buildDirectory](const SourceInfo &si) { + return !si.isGenerated && !isPchFile(buildDirectory, FilePath::fromString(si.path)); + }); CppEditor::ProjectFileCategorizer - categorizer({}, transform<QList>(t.sources, [&sourceDir](const SourceInfo &si) { + categorizer({}, transform<QList>(sourceFiles, [&sourceDir](const SourceInfo &si) { return sourceDir.absoluteFilePath(si.path); })); @@ -531,6 +541,11 @@ void addCompileGroups(ProjectNode *targetRoot, auto node = std::make_unique<FileNode>(sourcePath, Node::fileTypeForFileName(sourcePath)); node->setIsGenerated(si.isGenerated); + // CMake pch files are generated at configured time, but not marked as generated + // so that a "clean" step won't remove them and at a subsequent build they won't exist. + if (isPchFile(buildDirectory, sourcePath)) + node->setIsGenerated(true); + // Where does the file node need to go? if (sourcePath.isChildOf(buildDirectory) && !inSourceBuild) { buildFileNodes.emplace_back(std::move(node)); @@ -638,9 +653,16 @@ std::unique_ptr<CMakeProjectNode> generateRootProjectNode( void setupLocationInfoForTargets(CMakeProjectNode *rootNode, const QList<CMakeBuildTarget> &targets) { + const QSet<QString> titles = Utils::transform<QSet>(targets, &CMakeBuildTarget::title); + QHash<QString, FolderNode *> buildKeyToNode; + rootNode->forEachGenericNode([&buildKeyToNode, &titles](Node *node) { + FolderNode *folderNode = node->asFolderNode(); + const QString &buildKey = node->buildKey(); + if (folderNode && titles.contains(buildKey)) + buildKeyToNode.insert(buildKey, folderNode); + }); for (const CMakeBuildTarget &t : targets) { - FolderNode *folderNode = static_cast<FolderNode *>( - rootNode->findNode(Utils::equal(&Node::buildKey, t.title))); + FolderNode *folderNode = buildKeyToNode.value(t.title); if (folderNode) { QSet<std::pair<FilePath, int>> locations; auto dedup = [&locations](const Backtrace &bt) { @@ -705,7 +727,7 @@ FileApiQtcData extractData(FileApiData &input, result.buildTargets = generateBuildTargets(data, sourceDirectory, buildDirectory, haveLibrariesRelativeToBuildDirectory); result.cmakeFiles = std::move(data.cmakeFiles); - result.projectParts = generateRawProjectParts(data, sourceDirectory); + result.projectParts = generateRawProjectParts(data, sourceDirectory, buildDirectory); auto rootProjectNode = generateRootProjectNode(data, sourceDirectory, buildDirectory); ProjectTree::applyTreeManager(rootProjectNode.get()); // QRC nodes diff --git a/src/plugins/coreplugin/dialogs/newdialogwidget.cpp b/src/plugins/coreplugin/dialogs/newdialogwidget.cpp index f8a77c4e8d..66569b85cf 100644 --- a/src/plugins/coreplugin/dialogs/newdialogwidget.cpp +++ b/src/plugins/coreplugin/dialogs/newdialogwidget.cpp @@ -32,7 +32,6 @@ #include <utils/qtcassert.h> #include <utils/utilsicons.h> -#include <QAbstractProxyModel> #include <QDebug> #include <QItemDelegate> #include <QKeyEvent> diff --git a/src/plugins/coreplugin/locator/filesystemfilter.cpp b/src/plugins/coreplugin/locator/filesystemfilter.cpp index 95c9bf16ea..9e0751e570 100644 --- a/src/plugins/coreplugin/locator/filesystemfilter.cpp +++ b/src/plugins/coreplugin/locator/filesystemfilter.cpp @@ -176,8 +176,9 @@ void FileSystemFilter::accept(LocatorFilterEntry selection, { Q_UNUSED(selectionLength) if (selection.filePath.isDir()) { - const QString value = shortcutString() + ' ' - + selection.filePath.absoluteFilePath().toUserOutput() + '/'; + const QString value + = shortcutString() + ' ' + + selection.filePath.absoluteFilePath().cleanPath().pathAppended("/").toUserOutput(); *newText = value; *selectionStart = value.length(); } else { diff --git a/src/plugins/coreplugin/manhattanstyle.cpp b/src/plugins/coreplugin/manhattanstyle.cpp index 9a8934b989..50837d9f15 100644 --- a/src/plugins/coreplugin/manhattanstyle.cpp +++ b/src/plugins/coreplugin/manhattanstyle.cpp @@ -73,14 +73,20 @@ bool styleEnabled(const QWidget *widget) return true; } +static bool isInDialogOrPopup(const QWidget *widget) +{ + // Do not style dialogs or explicitly ignored widgets + const Qt::WindowType windowType = widget->window()->windowType(); + return (windowType == Qt::Dialog || windowType == Qt::Popup); +} + // Consider making this a QStyle state bool panelWidget(const QWidget *widget) { if (!widget) return false; - // Do not style dialogs or explicitly ignored widgets - if ((widget->window()->windowFlags() & Qt::WindowType_Mask) == Qt::Dialog) + if (isInDialogOrPopup(widget)) return false; if (qobject_cast<const FancyMainWindow *>(widget)) @@ -107,8 +113,7 @@ bool lightColored(const QWidget *widget) if (!widget) return false; - // Don't style dialogs or explicitly ignored widgets - if ((widget->window()->windowFlags() & Qt::WindowType_Mask) == Qt::Dialog) + if (isInDialogOrPopup(widget)) return false; const QWidget *p = widget; diff --git a/src/plugins/cppcheck/cppcheckoptions.cpp b/src/plugins/cppcheck/cppcheckoptions.cpp index 735557daf2..d6c7b0196c 100644 --- a/src/plugins/cppcheck/cppcheckoptions.cpp +++ b/src/plugins/cppcheck/cppcheckoptions.cpp @@ -39,9 +39,10 @@ #include <debugger/analyzer/analyzericons.h> #include <QCheckBox> -#include <QDir> #include <QFormLayout> +using namespace Utils; + namespace Cppcheck { namespace Internal { @@ -102,7 +103,7 @@ OptionsWidget::OptionsWidget(QWidget *parent) void OptionsWidget::load(const CppcheckOptions &options) { - m_binary->setPath(options.binary); + m_binary->setFilePath(options.binary); m_customArguments->setText(options.customArguments); m_ignorePatterns->setText(options.ignoredPatterns); m_warning->setChecked(options.warning); @@ -121,7 +122,7 @@ void OptionsWidget::load(const CppcheckOptions &options) void OptionsWidget::save(CppcheckOptions &options) const { - options.binary = m_binary->filePath().toString(); + options.binary = m_binary->filePath(); options.customArguments = m_customArguments->text(); options.ignoredPatterns = m_ignorePatterns->text(); options.warning = m_warning->isChecked(); @@ -149,14 +150,13 @@ CppcheckOptionsPage::CppcheckOptionsPage(CppcheckTool &tool, CppcheckTrigger &tr setCategoryIconPath(Analyzer::Icons::SETTINGSCATEGORY_ANALYZER); CppcheckOptions options; - if (Utils::HostOsInfo::isAnyUnixHost()) { + if (HostOsInfo::isAnyUnixHost()) { options.binary = "cppcheck"; } else { - QString programFiles = QDir::fromNativeSeparators( - QString::fromLocal8Bit(qgetenv("PROGRAMFILES"))); + FilePath programFiles = FilePath::fromUserInput(qEnvironmentVariable("PROGRAMFILES")); if (programFiles.isEmpty()) programFiles = "C:/Program Files"; - options.binary = programFiles + "/Cppcheck/cppcheck.exe"; + options.binary = programFiles / "Cppcheck/cppcheck.exe"; } load(options); @@ -190,7 +190,7 @@ void CppcheckOptionsPage::save(const CppcheckOptions &options) const QSettings *s = Core::ICore::settings(); QTC_ASSERT(s, return); s->beginGroup(Constants::SETTINGS_ID); - s->setValue(Constants::SETTINGS_BINARY, options.binary); + s->setValue(Constants::SETTINGS_BINARY, options.binary.toString()); s->setValue(Constants::SETTINGS_CUSTOM_ARGUMENTS, options.customArguments); s->setValue(Constants::SETTINGS_IGNORE_PATTERNS, options.ignoredPatterns); s->setValue(Constants::SETTINGS_WARNING, options.warning); @@ -213,8 +213,8 @@ void CppcheckOptionsPage::load(CppcheckOptions &options) const QSettings *s = Core::ICore::settings(); QTC_ASSERT(s, return); s->beginGroup(Constants::SETTINGS_ID); - options.binary = s->value(Constants::SETTINGS_BINARY, - options.binary).toString(); + options.binary = FilePath::fromString(s->value(Constants::SETTINGS_BINARY, + options.binary.toString()).toString()); options.customArguments = s->value(Constants::SETTINGS_CUSTOM_ARGUMENTS, options.customArguments).toString(); options.ignoredPatterns = s->value(Constants::SETTINGS_IGNORE_PATTERNS, diff --git a/src/plugins/cppcheck/cppcheckoptions.h b/src/plugins/cppcheck/cppcheckoptions.h index 639ffca6c9..4d4ca83cb6 100644 --- a/src/plugins/cppcheck/cppcheckoptions.h +++ b/src/plugins/cppcheck/cppcheckoptions.h @@ -26,6 +26,7 @@ #pragma once #include <coreplugin/dialogs/ioptionspage.h> +#include <utils/filepath.h> #include <QCoreApplication> #include <QPointer> @@ -36,9 +37,7 @@ class QLineEdit; class QCheckBox; QT_END_NAMESPACE -namespace Utils { -class PathChooser; -} +namespace Utils { class PathChooser; } namespace Cppcheck { namespace Internal { @@ -50,7 +49,7 @@ class OptionsWidget; class CppcheckOptions final { public: - QString binary; + Utils::FilePath binary; bool warning = true; bool style = true; diff --git a/src/plugins/cppcheck/cppcheckrunner.cpp b/src/plugins/cppcheck/cppcheckrunner.cpp index 2f6cffb1eb..261ccb3b63 100644 --- a/src/plugins/cppcheck/cppcheckrunner.cpp +++ b/src/plugins/cppcheck/cppcheckrunner.cpp @@ -74,7 +74,7 @@ CppcheckRunner::~CppcheckRunner() m_queueTimer.stop(); } -void CppcheckRunner::reconfigure(const QString &binary, const QString &arguments) +void CppcheckRunner::reconfigure(const FilePath &binary, const QString &arguments) { m_binary = binary; m_arguments = arguments; @@ -157,7 +157,7 @@ void CppcheckRunner::checkQueued() else m_queue.begin().value() = files; - m_process->setCommand(CommandLine(FilePath::fromString(m_binary), arguments, CommandLine::Raw)); + m_process->setCommand(CommandLine(m_binary, arguments, CommandLine::Raw)); m_process->start(); } diff --git a/src/plugins/cppcheck/cppcheckrunner.h b/src/plugins/cppcheck/cppcheckrunner.h index 83aca95192..d49a9016de 100644 --- a/src/plugins/cppcheck/cppcheckrunner.h +++ b/src/plugins/cppcheck/cppcheckrunner.h @@ -45,7 +45,7 @@ public: explicit CppcheckRunner(CppcheckTool &tool); ~CppcheckRunner() override; - void reconfigure(const QString &binary, const QString &arguments); + void reconfigure(const Utils::FilePath &binary, const QString &arguments); void addToQueue(const Utils::FilePaths &files, const QString &additionalArguments = {}); void removeFromQueue(const Utils::FilePaths &files); @@ -63,7 +63,7 @@ private: CppcheckTool &m_tool; Utils::QtcProcess *m_process = nullptr; - QString m_binary; + Utils::FilePath m_binary; QString m_arguments; QHash<QString, Utils::FilePaths> m_queue; Utils::FilePaths m_currentFiles; diff --git a/src/plugins/cppeditor/CppEditor.json.in b/src/plugins/cppeditor/CppEditor.json.in index 437e85c35f..b3c0149291 100644 --- a/src/plugins/cppeditor/CppEditor.json.in +++ b/src/plugins/cppeditor/CppEditor.json.in @@ -61,7 +61,7 @@ \" <!-- Find include guards of header files without extension, for\", \" example, STL ones like <string>. Those can have a big initial\", \" comment exceeding 1000 chars, though. -->\", - \" <magic priority=\'50\'>\", + \" <magic priority=\'40\'>\", \" <match value=\'#ifndef \' type=\'string\' offset=\'0:2000\'/>\", \" <match value=\'#if \' type=\'string\' offset=\'0:2000\'/>\", \" <match value=\'#include \' type=\'string\' offset=\'0:2000\'/>\", diff --git a/src/plugins/cppeditor/semantichighlighter.cpp b/src/plugins/cppeditor/semantichighlighter.cpp index b4c1a6ff1f..f3112a725b 100644 --- a/src/plugins/cppeditor/semantichighlighter.cpp +++ b/src/plugins/cppeditor/semantichighlighter.cpp @@ -34,6 +34,7 @@ #include <utils/algorithm.h> #include <utils/qtcassert.h> +#include <QElapsedTimer> #include <QLoggingCategory> #include <QTextDocument> @@ -165,6 +166,8 @@ void SemanticHighlighter::onHighlighterResultAvailable(int from, int to) return; // aborted qCDebug(log) << "onHighlighterResultAvailable()" << from << to; + QElapsedTimer t; + t.start(); SyntaxHighlighter *highlighter = m_baseTextDocument->syntaxHighlighter(); QTC_ASSERT(highlighter, return); @@ -234,11 +237,17 @@ void SemanticHighlighter::onHighlighterResultAvailable(int from, int to) } if (parentheses.first.isValid()) TextDocumentLayout::setParentheses(parentheses.first, parentheses.second); + + qCDebug(log) << "onHighlighterResultAvailable() took" << t.elapsed() << "ms"; } void SemanticHighlighter::onHighlighterFinished() { QTC_ASSERT(m_watcher, return); + + QElapsedTimer t; + t.start(); + if (!m_watcher->isCanceled() && documentRevision() == m_revision) { SyntaxHighlighter *highlighter = m_baseTextDocument->syntaxHighlighter(); if (QTC_GUARD(highlighter)) { @@ -273,6 +282,7 @@ void SemanticHighlighter::onHighlighterFinished() } m_watcher.reset(); + qCDebug(log) << "onHighlighterFinished() took" << t.elapsed() << "ms"; } void SemanticHighlighter::connectWatcher() diff --git a/src/plugins/debugger/shared/cdbsymbolpathlisteditor.cpp b/src/plugins/debugger/shared/cdbsymbolpathlisteditor.cpp index 2e67bf675e..18fcdce5dc 100644 --- a/src/plugins/debugger/shared/cdbsymbolpathlisteditor.cpp +++ b/src/plugins/debugger/shared/cdbsymbolpathlisteditor.cpp @@ -35,7 +35,6 @@ #include "symbolpathsdialog.h" #include <QCheckBox> -#include <QDir> #include <QDebug> #include <QAction> #include <QFormLayout> @@ -165,7 +164,7 @@ void CdbSymbolPathListEditor::addSymbolPath(CdbSymbolPathListEditor::SymbolPathM { FilePath cacheDir; if (promptCacheDirectory(this, &cacheDir)) - insertPathAtCursor(CdbSymbolPathListEditor::symbolPath(cacheDir.path(), mode)); + insertPathAtCursor(CdbSymbolPathListEditor::symbolPath(cacheDir, mode)); } void CdbSymbolPathListEditor::setupSymbolPaths() @@ -174,13 +173,13 @@ void CdbSymbolPathListEditor::setupSymbolPaths() const int indexOfSymbolServer = indexOfSymbolPath(currentPaths, SymbolServerPath); const int indexOfSymbolCache = indexOfSymbolPath(currentPaths, SymbolCachePath); - QString path; + FilePath path; if (indexOfSymbolServer != -1) - path = currentPaths.at(indexOfSymbolServer); + path = FilePath::fromString(currentPaths.at(indexOfSymbolServer)); if (path.isEmpty() && indexOfSymbolCache != -1) - path = currentPaths.at(indexOfSymbolCache); + path = FilePath::fromString(currentPaths.at(indexOfSymbolCache)); if (path.isEmpty()) - path = TemporaryDirectory::masterDirectoryPath() + "/symbolcache"; + path = FilePath::fromString(TemporaryDirectory::masterDirectoryPath() + "/symbolcache"); bool useSymbolServer = true; bool useSymbolCache = true; @@ -193,20 +192,20 @@ void CdbSymbolPathListEditor::setupSymbolPaths() if (useSymbolCache) { insertPathAtCursor(CdbSymbolPathListEditor::symbolPath(path, SymbolCachePath)); if (useSymbolServer) - insertPathAtCursor(CdbSymbolPathListEditor::symbolPath(QString(), SymbolServerPath)); + insertPathAtCursor(CdbSymbolPathListEditor::symbolPath({}, SymbolServerPath)); } else if (useSymbolServer) { insertPathAtCursor(CdbSymbolPathListEditor::symbolPath(path, SymbolServerPath)); } } -QString CdbSymbolPathListEditor::symbolPath(const QString &cacheDir, +QString CdbSymbolPathListEditor::symbolPath(const FilePath &cacheDir, CdbSymbolPathListEditor::SymbolPathMode mode) { if (mode == SymbolCachePath) - return symbolCachePrefixC + QDir::toNativeSeparators(cacheDir); + return symbolCachePrefixC + cacheDir.toUserOutput(); QString s = QLatin1String(symbolServerPrefixC); if (!cacheDir.isEmpty()) - s += QDir::toNativeSeparators(cacheDir) + '*'; + s += cacheDir.toUserOutput() + '*'; s += QLatin1String(symbolServerPostfixC); return s; } diff --git a/src/plugins/debugger/shared/cdbsymbolpathlisteditor.h b/src/plugins/debugger/shared/cdbsymbolpathlisteditor.h index df64b35bfe..f7dffcdda0 100644 --- a/src/plugins/debugger/shared/cdbsymbolpathlisteditor.h +++ b/src/plugins/debugger/shared/cdbsymbolpathlisteditor.h @@ -53,7 +53,7 @@ public: static bool promptCacheDirectory(QWidget *parent, Utils::FilePath *cacheDirectory); // Format a symbol path specification - static QString symbolPath(const QString &cacheDir, SymbolPathMode mode); + static QString symbolPath(const Utils::FilePath &cacheDir, SymbolPathMode mode); // Check for a symbol server path and extract local cache directory static bool isSymbolServerPath(const QString &path, QString *cacheDir = nullptr); // Check for a symbol cache path and extract local cache directory diff --git a/src/plugins/debugger/shared/symbolpathsdialog.cpp b/src/plugins/debugger/shared/symbolpathsdialog.cpp index db0354cca4..be8ef21afe 100644 --- a/src/plugins/debugger/shared/symbolpathsdialog.cpp +++ b/src/plugins/debugger/shared/symbolpathsdialog.cpp @@ -26,10 +26,14 @@ #include "symbolpathsdialog.h" #include "ui_symbolpathsdialog.h" +#include <utils/filepath.h> + #include <QMessageBox> -using namespace Debugger; -using namespace Internal; +using namespace Utils; + +namespace Debugger { +namespace Internal { SymbolPathsDialog::SymbolPathsDialog(QWidget *parent) : QDialog(parent), @@ -54,9 +58,9 @@ bool SymbolPathsDialog::useSymbolServer() const return ui->useSymbolServer->isChecked(); } -QString SymbolPathsDialog::path() const +FilePath SymbolPathsDialog::path() const { - return ui->pathChooser->filePath().toString(); + return ui->pathChooser->filePath(); } void SymbolPathsDialog::setUseSymbolCache(bool useSymbolCache) @@ -69,13 +73,13 @@ void SymbolPathsDialog::setUseSymbolServer(bool useSymbolServer) ui->useSymbolServer->setChecked(useSymbolServer); } -void SymbolPathsDialog::setPath(const QString &path) +void SymbolPathsDialog::setPath(const FilePath &path) { - ui->pathChooser->setPath(path); + ui->pathChooser->setFilePath(path); } bool SymbolPathsDialog::useCommonSymbolPaths(bool &useSymbolCache, bool &useSymbolServer, - QString &path) + FilePath &path) { SymbolPathsDialog dialog; dialog.setUseSymbolCache(useSymbolCache); @@ -87,3 +91,6 @@ bool SymbolPathsDialog::useCommonSymbolPaths(bool &useSymbolCache, bool &useSymb path = dialog.path(); return ret == QDialog::Accepted; } + +} // Internal +} // Debugger diff --git a/src/plugins/debugger/shared/symbolpathsdialog.h b/src/plugins/debugger/shared/symbolpathsdialog.h index c3534fbcf6..c77e112ea4 100644 --- a/src/plugins/debugger/shared/symbolpathsdialog.h +++ b/src/plugins/debugger/shared/symbolpathsdialog.h @@ -28,6 +28,8 @@ #include <QDialog> #include <QString> +namespace Utils { class FilePath; } + namespace Debugger { namespace Internal { @@ -43,15 +45,15 @@ public: bool useSymbolCache() const; bool useSymbolServer() const; - QString path() const; + Utils::FilePath path() const; bool doNotAskAgain() const; void setUseSymbolCache(bool useSymbolCache); void setUseSymbolServer(bool useSymbolServer); - void setPath(const QString &path); + void setPath(const Utils::FilePath &path); void setDoNotAskAgain(bool doNotAskAgain) const; - static bool useCommonSymbolPaths(bool &useSymbolCache, bool &useSymbolServer, QString &path); + static bool useCommonSymbolPaths(bool &useSymbolCache, bool &useSymbolServer, Utils::FilePath &path); private: Ui::SymbolPathsDialog *ui; diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 624f3eab49..638fb91db0 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -49,17 +49,18 @@ #include <utils/algorithm.h> #include <utils/basetreeview.h> #include <utils/environment.h> +#include <utils/fileutils.h> #include <utils/hostosinfo.h> -#include <utils/utilsicons.h> #include <utils/layoutbuilder.h> #include <utils/overridecursor.h> +#include <utils/pathlisteditor.h> #include <utils/port.h> #include <utils/qtcassert.h> #include <utils/qtcprocess.h> #include <utils/stringutils.h> #include <utils/temporaryfile.h> #include <utils/treemodel.h> -#include <utils/fileutils.h> +#include <utils/utilsicons.h> #include <QApplication> #include <QCheckBox> @@ -75,8 +76,8 @@ #include <QRandomGenerator> #include <QRegularExpression> #include <QTextBrowser> -#include <QToolButton> #include <QThread> +#include <QToolButton> #include <numeric> @@ -394,15 +395,13 @@ public: dockerDevice->tryCreateLocalFileAccess(); }); - m_pathsLineEdit = new QLineEdit; - m_pathsLineEdit->setText(data.repo); - m_pathsLineEdit->setToolTip(tr("Paths in this semi-colon separated list will be " - "mapped one-to-one into the Docker container.")); - m_pathsLineEdit->setText(data.mounts.join(';')); - m_pathsLineEdit->setPlaceholderText(tr("List project source directories here")); + m_pathsListEdit = new PathListEditor; + m_pathsListEdit->setToolTip(tr("Paths in this list will be mapped one-to-one into the " + "Docker container.")); + m_pathsListEdit->setPathList(data.mounts); - connect(m_pathsLineEdit, &QLineEdit::textChanged, this, [dockerDevice](const QString &text) { - dockerDevice->setMounts(text.split(';', Qt::SkipEmptyParts)); + connect(m_pathsListEdit, &PathListEditor::changed, this, [dockerDevice, this]() { + dockerDevice->setMounts(m_pathsListEdit->pathList()); }); auto logView = new QTextBrowser; @@ -443,7 +442,10 @@ public: daemonStateLabel, m_daemonReset, m_daemonState, Break(), m_runAsOutsideUser, Break(), m_usePathMapping, Break(), - tr("Paths to mount:"), m_pathsLineEdit, Break(), + Column { + new QLabel(tr("Paths to mount:")), + m_pathsListEdit, + }, Break(), Column { Space(20), Row { autoDetectButton, undoAutoDetectButton, listAutoDetectedButton, Stretch() }, @@ -463,7 +465,7 @@ private: QLabel *m_daemonState; QCheckBox *m_runAsOutsideUser; QCheckBox *m_usePathMapping; - QLineEdit *m_pathsLineEdit; + Utils::PathListEditor *m_pathsListEdit; KitDetector m_kitItemDetector; }; @@ -813,34 +815,51 @@ static QString getLocalIPv4Address() void DockerDevicePrivate::startContainer() { - QString tempFileName; - - { - TemporaryFile temp("qtc-docker-XXXXXX"); - temp.open(); - tempFileName = temp.fileName(); - } - const QString display = HostOsInfo::isWindowsHost() ? QString(getLocalIPv4Address() + ":0.0") : QString(":0"); - CommandLine dockerRun{"docker", {"run", "-i", "--cidfile=" + tempFileName, - "--rm", - "-e", QString("DISPLAY=%1").arg(display), - "-e", "XAUTHORITY=/.Xauthority", - "--net", "host"}}; + CommandLine dockerCreate{"docker", {"create", + "-i", + "--rm", + "-e", QString("DISPLAY=%1").arg(display), + "-e", "XAUTHORITY=/.Xauthority", + "--net", "host"}}; #ifdef Q_OS_UNIX + // no getuid() and getgid() on Windows. if (m_data.useLocalUidGid) - dockerRun.addArgs({"-u", QString("%1:%2").arg(getuid()).arg(getgid())}); + dockerCreate.addArgs({"-u", QString("%1:%2").arg(getuid()).arg(getgid())}); #endif - for (const QString &mount : qAsConst(m_data.mounts)) { - if (!mount.isEmpty()) - dockerRun.addArgs({"-v", mount + ':' + mount}); + for (QString mount : qAsConst(m_data.mounts)) { + if (mount.isEmpty()) + continue; + // make sure to convert windows style paths to unix style paths with the file system case: + // C:/dev/src -> /c/dev/src + if (const FilePath mountPath = FilePath::fromUserInput(mount).normalizedPathName(); + mountPath.startsWithDriveLetter()) { + const QChar lowerDriveLetter = mountPath.path().at(0).toLower(); + const FilePath path = FilePath::fromUserInput(mountPath.path().mid(2)); // strip C: + mount = '/' + lowerDriveLetter + path.path(); + } + dockerCreate.addArgs({"-v", mount + ':' + mount}); } - dockerRun.addArgs({"--entrypoint", "/bin/sh", m_data.imageId}); + dockerCreate.addArgs({"--entrypoint", "/bin/sh", m_data.imageId}); + + LOG("RUNNING: " << dockerCreate.toUserOutput()); + QtcProcess createProcess; + createProcess.setCommand(dockerCreate); + createProcess.runBlocking(); + + if (createProcess.result() != QtcProcess::FinishedWithSuccess) + return; + + m_container = createProcess.stdOut().trimmed(); + if (m_container.isEmpty()) + return; + LOG("Container via process: " << m_container); + CommandLine dockerRun{"docker", {"container" , "start", "-i", "-a", m_container}}; LOG("RUNNING: " << dockerRun.toUserOutput()); QPointer<QtcProcess> shell = new QtcProcess(ProcessMode::Writer); connect(shell, &QtcProcess::finished, this, [this, shell] { @@ -875,23 +894,6 @@ void DockerDevicePrivate::startContainer() return; } - LOG("CHECKING: " << tempFileName); - for (int i = 0; i <= 20; ++i) { - QFile file(tempFileName); - if (file.open(QIODevice::ReadOnly)) { - m_container = QString::fromUtf8(file.readAll()).trimmed(); - if (!m_container.isEmpty()) { - LOG("Container: " << m_container); - break; - } - } - if (i == 20 || !DockerPlugin::isDaemonRunning().value_or(true)) { - qWarning("Docker cid file empty."); - return; // No - } - qApp->processEvents(); // FIXME turn this for-loop into - QThread::msleep(100); - } DockerPlugin::setGlobalDaemonState(true); } @@ -1288,12 +1290,8 @@ QDateTime DockerDevice::lastModified(const FilePath &filePath) const return res; } - QtcProcess proc; - proc.setCommand({"stat", {"-c", "%Y", filePath.path()}}); - runProcess(proc); - proc.waitForFinished(); - - const qint64 secs = proc.rawStdOut().toLongLong(); + const QString output = d->outputForRunInShell({"stat", {"-c", "%Y", filePath.path()}}); + qint64 secs = output.toLongLong(); const QDateTime dt = QDateTime::fromSecsSinceEpoch(secs, Qt::UTC); return dt; } @@ -1615,6 +1613,12 @@ void DockerDevice::aboutToBeRemoved() const void DockerDevicePrivate::fetchSystemEnviroment() { + if (m_shell) { + const QString remoteOutput = outputForRunInShell({"env", {}}); + m_cachedEnviroment = Environment(remoteOutput.split('\n', Qt::SkipEmptyParts), q->osType()); + return; + } + QtcProcess proc; proc.setCommand({"env", {}}); diff --git a/src/plugins/help/litehtmlhelpviewer.cpp b/src/plugins/help/litehtmlhelpviewer.cpp index c33ebf62ef..8d54e01b69 100644 --- a/src/plugins/help/litehtmlhelpviewer.cpp +++ b/src/plugins/help/litehtmlhelpviewer.cpp @@ -235,8 +235,10 @@ bool LiteHtmlHelpViewer::eventFilter(QObject *src, QEvent *e) { if (isScrollWheelZoomingEnabled() && e->type() == QEvent::Wheel) { auto we = static_cast<QWheelEvent *>(e); - if (we->modifiers() == Qt::ControlModifier) + if (we->modifiers() == Qt::ControlModifier) { + e->ignore(); return true; + } } return HelpViewer::eventFilter(src, e); } diff --git a/src/plugins/imageviewer/imageviewerfile.cpp b/src/plugins/imageviewer/imageviewerfile.cpp index 2a6204e11d..3fe26890d0 100644 --- a/src/plugins/imageviewer/imageviewerfile.cpp +++ b/src/plugins/imageviewer/imageviewerfile.cpp @@ -126,7 +126,15 @@ Core::IDocument::OpenResult ImageViewerFile::openImpl(QString *errorString, m_type = TypeMovie; m_movie = new QMovie(fileName, QByteArray(), this); m_movie->setCacheMode(QMovie::CacheAll); - connect(m_movie, &QMovie::finished, m_movie, &QMovie::start); + connect( + m_movie, + &QMovie::finished, + m_movie, + [this] { + if (m_movie->isValid()) + m_movie->start(); + }, + Qt::QueuedConnection); connect(m_movie, &QMovie::resized, this, &ImageViewerFile::imageSizeChanged); m_movie->start(); m_isPaused = false; // force update diff --git a/src/plugins/mcusupport/mcusupportoptions.cpp b/src/plugins/mcusupport/mcusupportoptions.cpp index 6fa37d5a83..ba6107d6fc 100644 --- a/src/plugins/mcusupport/mcusupportoptions.cpp +++ b/src/plugins/mcusupport/mcusupportoptions.cpp @@ -72,15 +72,15 @@ namespace Internal { static const int KIT_VERSION = 8; // Bumps up whenever details in Kit creation change -static QString packagePathFromSettings(const QString &settingsKey, - QSettings::Scope scope = QSettings::UserScope, - const QString &defaultPath = {}) +static FilePath packagePathFromSettings(const QString &settingsKey, + QSettings::Scope scope = QSettings::UserScope, + const FilePath &defaultPath = {}) { QSettings *settings = Core::ICore::settings(scope); const QString key = QLatin1String(Constants::SETTINGS_GROUP) + '/' + QLatin1String(Constants::SETTINGS_KEY_PACKAGE_PREFIX) + settingsKey; - const QString path = settings->value(key, defaultPath).toString(); - return FilePath::fromUserInput(path).toString(); + const QString path = settings->value(key, defaultPath.toString()).toString(); + return FilePath::fromUserInput(path); } static bool automaticKitCreationFromSettings(QSettings::Scope scope = QSettings::UserScope) @@ -99,7 +99,7 @@ static bool kitNeedsQtVersion() return !HostOsInfo::isWindowsHost(); } -McuPackage::McuPackage(const QString &label, const QString &defaultPath, +McuPackage::McuPackage(const QString &label, const FilePath &defaultPath, const QString &detectionPath, const QString &settingsKey, const McuPackageVersionDetector *versionDetector) : m_label(label) @@ -112,14 +112,14 @@ McuPackage::McuPackage(const QString &label, const QString &defaultPath, m_automaticKitCreation = automaticKitCreationFromSettings(QSettings::UserScope); } -QString McuPackage::basePath() const +FilePath McuPackage::basePath() const { - return m_fileChooser != nullptr ? m_fileChooser->filePath().toString() : m_path; + return m_fileChooser != nullptr ? m_fileChooser->filePath() : m_path; } -QString McuPackage::path() const +FilePath McuPackage::path() const { - return QFileInfo(basePath() + m_relativePathModifier).absoluteFilePath(); + return basePath().resolvePath(m_relativePathModifier).absoluteFilePath(); } QString McuPackage::label() const @@ -127,7 +127,7 @@ QString McuPackage::label() const return m_label; } -QString McuPackage::defaultPath() const +FilePath McuPackage::defaultPath() const { return m_defaultPath; } @@ -148,7 +148,7 @@ QWidget *McuPackage::widget() Icons::RESET.icon()); m_fileChooser->lineEdit()->setButtonVisible(FancyLineEdit::Right, true); connect(m_fileChooser->lineEdit(), &FancyLineEdit::rightButtonClicked, this, [&] { - m_fileChooser->setPath(m_defaultPath); + m_fileChooser->setFilePath(m_defaultPath); }); auto layout = new QGridLayout(m_widget); @@ -168,7 +168,7 @@ QWidget *McuPackage::widget() layout->addWidget(m_fileChooser, 0, 0, 1, 2); layout->addWidget(m_infoLabel, 1, 0, 1, -1); - m_fileChooser->setPath(m_path); + m_fileChooser->setFilePath(m_path); QObject::connect(this, &McuPackage::statusChanged, this, [this] { updateStatusUi(); @@ -228,7 +228,7 @@ void McuPackage::writeGeneralSettings() const bool McuPackage::writeToSettings() const { - const QString savedPath = packagePathFromSettings(m_settingsKey, QSettings::UserScope, m_defaultPath); + const FilePath savedPath = packagePathFromSettings(m_settingsKey, QSettings::UserScope, m_defaultPath); const QString key = QLatin1String(Constants::SETTINGS_GROUP) + '/' + QLatin1String(Constants::SETTINGS_KEY_PACKAGE_PREFIX) + m_settingsKey; Core::ICore::settings()->setValueWithDefault(key, m_path, m_defaultPath); @@ -258,17 +258,18 @@ void McuPackage::setAutomaticKitCreationEnabled(const bool enabled) void McuPackage::updatePath() { - m_path = m_fileChooser->rawPath(); + m_path = m_fileChooser->rawFilePath(); m_fileChooser->lineEdit()->button(FancyLineEdit::Right)->setEnabled(m_path != m_defaultPath); updateStatus(); } void McuPackage::updateStatus() { - bool validPath = !m_path.isEmpty() && FilePath::fromString(m_path).exists(); - const FilePath detectionPath = FilePath::fromString(basePath() + "/" + m_detectionPath); + bool validPath = !m_path.isEmpty() && m_path.exists(); + const FilePath detectionPath = basePath() / m_detectionPath; const bool validPackage = m_detectionPath.isEmpty() || detectionPath.exists(); - m_detectedVersion = validPath && validPackage && m_versionDetector ? m_versionDetector->parseVersion(basePath()) : QString(); + m_detectedVersion = validPath && validPackage && m_versionDetector + ? m_versionDetector->parseVersion(basePath().toString()) : QString(); const bool validVersion = m_detectedVersion.isEmpty() || m_versions.isEmpty() || m_versions.contains(m_detectedVersion); @@ -293,7 +294,7 @@ void McuPackage::updateStatusUi() QString McuPackage::statusText() const { - const QString displayPackagePath = FilePath::fromString(m_path).toUserOutput(); + const QString displayPackagePath = m_path.toUserOutput(); const QString displayVersions = QStringList(m_versions.toList()).join(" or "); const QString displayRequiredPath = QString("%1 %2").arg( FilePath::fromString(m_detectionPath).toUserOutput(), @@ -339,7 +340,7 @@ QString McuPackage::statusText() const } McuToolChainPackage::McuToolChainPackage(const QString &label, - const QString &defaultPath, + const FilePath &defaultPath, const QString &detectionPath, const QString &settingsKey, McuToolChainPackage::Type type, @@ -447,11 +448,9 @@ ToolChain *McuToolChainPackage::toolChain(Id language) const else { const QLatin1String compilerName( language == ProjectExplorer::Constants::C_LANGUAGE_ID ? "gcc" : "g++"); - const FilePath compiler = FilePath::fromUserInput( - HostOsInfo::withExecutableSuffix( - path() + ( - m_type == TypeArmGcc - ? "/bin/arm-none-eabi-%1" : "/bar/foo-keil-%1")).arg(compilerName)); + const QString comp = QLatin1String(m_type == TypeArmGcc ? "/bin/arm-none-eabi-%1" : "/bar/foo-keil-%1") + .arg(compilerName); + const FilePath compiler = path().pathAppended(comp).withExecutableSuffix(); tc = armGccToolChain(compiler, language); } @@ -479,11 +478,10 @@ QVariant McuToolChainPackage::debuggerId() const { using namespace Debugger; - const FilePath command = FilePath::fromUserInput( - HostOsInfo::withExecutableSuffix(path() + ( - m_type == TypeArmGcc - ? "/bin/arm-none-eabi-gdb-py" : m_type == TypeIAR - ? "../common/bin/CSpyBat" : "/bar/foo-keil-gdb"))); + QString sub = QString::fromLatin1(m_type == TypeArmGcc ? "bin/arm-none-eabi-gdb-py" + : m_type == TypeIAR ? "../common/bin/CSpyBat" : "bar/foo-keil-gdb"); + + const FilePath command = path().pathAppended(sub).withExecutableSuffix(); const DebuggerItem *debugger = DebuggerItemManager::findByCommand(command); QVariant debuggerId; if (!debugger) { @@ -591,7 +589,7 @@ McuSupportOptions::~McuSupportOptions() void McuSupportOptions::populatePackagesAndTargets() { - setQulDir(FilePath::fromUserInput(qtForMCUsSdkPackage->path())); + setQulDir(qtForMCUsSdkPackage->path()); } static FilePath qulDocsDir() @@ -665,13 +663,12 @@ void McuSupportOptions::setQulDir(const FilePath &dir) FilePath McuSupportOptions::qulDirFromSettings() { - return FilePath::fromUserInput( - packagePathFromSettings(Constants::SETTINGS_KEY_PACKAGE_QT_FOR_MCUS_SDK, - QSettings::UserScope)); + return packagePathFromSettings(Constants::SETTINGS_KEY_PACKAGE_QT_FOR_MCUS_SDK, + QSettings::UserScope); } static void setKitProperties(const QString &kitName, Kit *k, const McuTarget *mcuTarget, - const QString &sdkPath) + const FilePath &sdkPath) { using namespace Constants; @@ -688,7 +685,7 @@ static void setKitProperties(const QString &kitName, Kit *k, const McuTarget *mc if (mcuTarget->toolChainPackage()->isDesktopToolchain()) k->setDeviceTypeForIcon(DEVICE_TYPE); k->setValue(QtSupport::SuppliesQtQuickImportPath::id(), true); - k->setValue(QtSupport::KitQmlImportPath::id(), QVariant(sdkPath + "/include/qul")); + k->setValue(QtSupport::KitQmlImportPath::id(), sdkPath.pathAppended("include/qul").toVariant()); k->setValue(QtSupport::KitHasMergedHeaderPathsWithQmlImportPaths::id(), true); QSet<Id> irrelevant = { SysRootKitAspect::id(), @@ -750,14 +747,13 @@ static void setKitEnvironment(Kit *k, const McuTarget *mcuTarget, // feature of the run configuration. Otherwise, we just prepend the path, here. if (mcuTarget->toolChainPackage()->isDesktopToolchain() && !CMakeProjectManager::CMakeToolManager::defaultCMakeTool()->hasFileApi()) - pathAdditions.append(QDir::toNativeSeparators(qtForMCUsSdkPackage->path() + "/bin")); + pathAdditions.append(qtForMCUsSdkPackage->path().pathAppended("bin").toUserOutput()); auto processPackage = [&pathAdditions, &changes](const McuPackage *package) { if (package->addToPath()) - pathAdditions.append(QDir::toNativeSeparators(package->path())); + pathAdditions.append(package->path().toUserOutput()); if (!package->environmentVariableName().isEmpty()) - changes.append({package->environmentVariableName(), - QDir::toNativeSeparators(package->path())}); + changes.append({package->environmentVariableName(), package->path().toUserOutput()}); }; for (auto package : mcuTarget->packages()) processPackage(package); @@ -808,7 +804,7 @@ static void updateKitEnvironment(Kit *k, const McuTarget *mcuTarget) return item.name == varName; }); const EnvironmentItem item = {package->environmentVariableName(), - QDir::toNativeSeparators(package->path())}; + package->path().toUserOutput()}; if (index != -1) changes.replace(index, item); else @@ -819,7 +815,7 @@ static void updateKitEnvironment(Kit *k, const McuTarget *mcuTarget) EnvironmentKitAspect::setEnvironmentChanges(k, changes); } -static void setKitCMakeOptions(Kit *k, const McuTarget* mcuTarget, const QString &qulDir) +static void setKitCMakeOptions(Kit *k, const McuTarget* mcuTarget, const FilePath &qulDir) { using namespace CMakeProjectManager; @@ -832,7 +828,7 @@ static void setKitCMakeOptions(Kit *k, const McuTarget* mcuTarget, const QString } if (!mcuTarget->toolChainPackage()->isDesktopToolchain()) { - const FilePath cMakeToolchainFile = FilePath::fromString(qulDir + "/lib/cmake/Qul/toolchain/" + const FilePath cMakeToolchainFile = qulDir.pathAppended("lib/cmake/Qul/toolchain/" + mcuTarget->toolChainPackage()->cmakeToolChainFileName()); config.append(CMakeConfigItem( @@ -844,7 +840,7 @@ static void setKitCMakeOptions(Kit *k, const McuTarget* mcuTarget, const QString } } - const FilePath generatorsPath = FilePath::fromString(qulDir + "/lib/cmake/Qul/QulGenerators.cmake"); + const FilePath generatorsPath = qulDir.pathAppended("/lib/cmake/Qul/QulGenerators.cmake"); config.append(CMakeConfigItem("QUL_GENERATORS", generatorsPath.toString().toUtf8())); if (!generatorsPath.exists()) { @@ -947,7 +943,7 @@ QList<Kit *> McuSupportOptions::kitsWithMismatchedDependencies(const McuTarget * EnvironmentKitAspect::environmentChanges(kit))); return Utils::anyOf(mcuTarget->packages(), [&environment](const McuPackage *package) { return !package->environmentVariableName().isEmpty() && - environment.value(package->environmentVariableName()) != QDir::toNativeSeparators(package->path()); + environment.value(package->environmentVariableName()) != package->path().toUserOutput(); }); }); } @@ -1003,13 +999,13 @@ QVersionNumber McuSupportOptions::kitQulVersion(const Kit *kit) .toString()); } -QString kitDependencyPath(const Kit *kit, const QString &variableName) +static FilePath kitDependencyPath(const Kit *kit, const QString &variableName) { for (const NameValueItem &nameValueItem : EnvironmentKitAspect::environmentChanges(kit)) { if (nameValueItem.name == variableName) - return nameValueItem.value; + return FilePath::fromUserInput(nameValueItem.value); } - return QString(); + return FilePath(); } bool McuSupportOptions::kitUpToDate(const Kit *kit, const McuTarget *mcuTarget, @@ -1052,13 +1048,13 @@ void McuSupportOptions::createAutomaticKits() const QString displayPath = FilePath::fromString(qtForMCUsPackage->detectionPath()) .toUserOutput(); printMessage(tr("Path %1 exists, but does not contain %2.") - .arg(qtForMCUsPackage->path(), displayPath), + .arg(qtForMCUsPackage->path().toUserOutput(), displayPath), true); break; } case McuPackage::InvalidPath: { printMessage(tr("Path %1 does not exist. Add the path in Tools > Options > Devices > MCU.") - .arg(qtForMCUsPackage->path()), + .arg(qtForMCUsPackage->path().toUserOutput()), true); break; } @@ -1079,7 +1075,7 @@ void McuSupportOptions::createAutomaticKits() return; } - auto dir = FilePath::fromUserInput(qtForMCUsPackage->path()); + FilePath dir = qtForMCUsPackage->path(); QVector<McuPackage*> packages; QVector<McuTarget*> mcuTargets; Sdk::targetsAndPackages(dir, &packages, &mcuTargets); @@ -1131,7 +1127,7 @@ void McuSupportOptions::upgradeKits(UpgradeOption upgradeOption) auto qtForMCUsPackage = Sdk::createQtForMCUsPackage(); - auto dir = FilePath::fromUserInput(qtForMCUsPackage->path()); + auto dir = qtForMCUsPackage->path(); QVector<McuPackage*> packages; QVector<McuTarget*> mcuTargets; Sdk::targetsAndPackages(dir, &packages, &mcuTargets); @@ -1169,7 +1165,7 @@ void McuSupportOptions::fixKitsDependencies() { auto qtForMCUsPackage = Sdk::createQtForMCUsPackage(); - auto dir = FilePath::fromUserInput(qtForMCUsPackage->path()); + FilePath dir = qtForMCUsPackage->path(); QVector<McuPackage*> packages; QVector<McuTarget*> mcuTargets; Sdk::targetsAndPackages(dir, &packages, &mcuTargets); @@ -1245,7 +1241,7 @@ void McuSupportOptions::fixExistingKits() auto qtForMCUsPackage = Sdk::createQtForMCUsPackage(); qtForMCUsPackage->updateStatus(); if (qtForMCUsPackage->validStatus()) { - auto dir = FilePath::fromUserInput(qtForMCUsPackage->path()); + FilePath dir = qtForMCUsPackage->path(); QVector<McuPackage*> packages; QVector<McuTarget*> mcuTargets; Sdk::targetsAndPackages(dir, &packages, &mcuTargets); diff --git a/src/plugins/mcusupport/mcusupportoptions.h b/src/plugins/mcusupport/mcusupportoptions.h index f70722412d..346c2b9de5 100644 --- a/src/plugins/mcusupport/mcusupportoptions.h +++ b/src/plugins/mcusupport/mcusupportoptions.h @@ -66,15 +66,15 @@ public: ValidPackage }; - McuPackage(const QString &label, const QString &defaultPath, + McuPackage(const QString &label, const Utils::FilePath &defaultPath, const QString &detectionPath, const QString &settingsKey, const McuPackageVersionDetector *versionDetector = nullptr); virtual ~McuPackage() = default; - QString basePath() const; - QString path() const; + Utils::FilePath basePath() const; + Utils::FilePath path() const; QString label() const; - QString defaultPath() const; + Utils::FilePath defaultPath() const; QString detectionPath() const; QString statusText() const; void updateStatus(); @@ -110,12 +110,12 @@ private: Utils::InfoLabel *m_infoLabel = nullptr; const QString m_label; - const QString m_defaultPath; + const Utils::FilePath m_defaultPath; const QString m_detectionPath; const QString m_settingsKey; const McuPackageVersionDetector *m_versionDetector; - QString m_path; + Utils::FilePath m_path; QString m_relativePathModifier; // relative path to m_path to be returned by path() QString m_detectedVersion; QVector<QString> m_versions; @@ -142,7 +142,7 @@ public: }; McuToolChainPackage(const QString &label, - const QString &defaultPath, + const Utils::FilePath &defaultPath, const QString &detectionPath, const QString &settingsKey, Type type, diff --git a/src/plugins/mcusupport/mcusupportoptionspage.cpp b/src/plugins/mcusupport/mcusupportoptionspage.cpp index 9ccffd63e0..71eff41c24 100644 --- a/src/plugins/mcusupport/mcusupportoptionspage.cpp +++ b/src/plugins/mcusupport/mcusupportoptionspage.cpp @@ -193,7 +193,7 @@ void McuSupportOptionsWidget::updateStatus() m_mcuTargetsInfoLabel->setVisible(valid && m_options.mcuTargets.isEmpty()); if (m_mcuTargetsInfoLabel->isVisible()) { m_mcuTargetsInfoLabel->setType(Utils::InfoLabel::NotOk); - const auto sdkPath = Utils::FilePath::fromString(m_options.qtForMCUsSdkPackage->basePath()); + const Utils::FilePath sdkPath = m_options.qtForMCUsSdkPackage->basePath(); QString deprecationMessage; if (Sdk::checkDeprecatedSdkError(sdkPath, deprecationMessage)) m_mcuTargetsInfoLabel->setText(deprecationMessage); diff --git a/src/plugins/mcusupport/mcusupportsdk.cpp b/src/plugins/mcusupport/mcusupportsdk.cpp index b805b4a91a..291f0647b2 100644 --- a/src/plugins/mcusupport/mcusupportsdk.cpp +++ b/src/plugins/mcusupport/mcusupportsdk.cpp @@ -49,15 +49,14 @@ namespace McuSupport { namespace Internal { namespace Sdk { -static QString findInProgramFiles(const QString &folder) +static FilePath findInProgramFiles(const QString &folder) { for (auto envVar : {"ProgramFiles", "ProgramFiles(x86)", "ProgramW6432"}) { if (!qEnvironmentVariableIsSet(envVar)) continue; - const Utils::FilePath dir = - Utils::FilePath::fromUserInput(qEnvironmentVariable(envVar) + "/" + folder); + const FilePath dir = FilePath::fromUserInput(qEnvironmentVariable(envVar)) / folder; if (dir.exists()) - return dir.toString(); + return dir; } return {}; } @@ -66,7 +65,7 @@ McuPackage *createQtForMCUsPackage() { auto result = new McuPackage( McuPackage::tr("Qt for MCUs SDK"), - QDir::homePath(), + FileUtils::homePath(), FilePath("bin/qmltocpp").withExecutableSuffix().toString(), Constants::SETTINGS_KEY_PACKAGE_QT_FOR_MCUS_SDK); result->setEnvironmentVariableName("Qul_DIR"); @@ -92,22 +91,21 @@ static McuToolChainPackage *createArmGccPackage() { const char envVar[] = "ARMGCC_DIR"; - QString defaultPath; + FilePath defaultPath; if (qEnvironmentVariableIsSet(envVar)) - defaultPath = qEnvironmentVariable(envVar); - if (defaultPath.isEmpty() && Utils::HostOsInfo::isWindowsHost()) { - const QDir installDir(findInProgramFiles("/GNU Tools ARM Embedded/")); + defaultPath = FilePath::fromUserInput(qEnvironmentVariable(envVar)); + if (defaultPath.isEmpty() && HostOsInfo::isWindowsHost()) { + const FilePath installDir = findInProgramFiles("GNU Tools ARM Embedded"); if (installDir.exists()) { // If GNU Tools installation dir has only one sub dir, // select the sub dir, otherwise the installation dir. - const QFileInfoList subDirs = - installDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + const FilePaths subDirs = installDir.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot); if (subDirs.count() == 1) - defaultPath = subDirs.first().filePath() + '/'; + defaultPath = subDirs.first(); } } if (defaultPath.isEmpty()) - defaultPath = QDir::homePath(); + defaultPath = FileUtils::homePath(); const QString detectionPath = Utils::HostOsInfo::withExecutableSuffix("bin/arm-none-eabi-g++"); const auto versionDetector = new McuPackageExecutableVersionDetector( @@ -131,8 +129,8 @@ static McuToolChainPackage *createGhsToolchainPackage() { const char envVar[] = "GHS_COMPILER_DIR"; - const QString defaultPath = - qEnvironmentVariableIsSet(envVar) ? qEnvironmentVariable(envVar) : QDir::homePath(); + const FilePath defaultPath = qEnvironmentVariableIsSet(envVar) + ? FilePath::fromUserInput(qEnvironmentVariable(envVar)) : FileUtils::homePath(); const auto versionDetector = new McuPackageExecutableVersionDetector( Utils::HostOsInfo::withExecutableSuffix("as850"), @@ -155,8 +153,8 @@ static McuToolChainPackage *createGhsArmToolchainPackage() { const char envVar[] = "GHS_ARM_COMPILER_DIR"; - const QString defaultPath = - qEnvironmentVariableIsSet(envVar) ? qEnvironmentVariable(envVar) : QDir::homePath(); + const FilePath defaultPath = qEnvironmentVariableIsSet(envVar) + ? FilePath::fromUserInput(qEnvironmentVariable(envVar)) : FileUtils::homePath(); const auto versionDetector = new McuPackageExecutableVersionDetector( Utils::HostOsInfo::withExecutableSuffix("asarm"), @@ -179,20 +177,20 @@ static McuToolChainPackage *createIarToolChainPackage() { const char envVar[] = "IAR_ARM_COMPILER_DIR"; - QString defaultPath; + FilePath defaultPath; if (qEnvironmentVariableIsSet(envVar)) - defaultPath = qEnvironmentVariable(envVar); + defaultPath = FilePath::fromUserInput(qEnvironmentVariable(envVar)); else { const ProjectExplorer::ToolChain *tc = ProjectExplorer::ToolChainManager::toolChain([](const ProjectExplorer::ToolChain *t) { return t->typeId() == BareMetal::Constants::IAREW_TOOLCHAIN_TYPEID; }); if (tc) { - const Utils::FilePath compilerExecPath = tc->compilerCommand(); - defaultPath = compilerExecPath.parentDir().parentDir().toString(); + const FilePath compilerExecPath = tc->compilerCommand(); + defaultPath = compilerExecPath.parentDir().parentDir(); } else - defaultPath = QDir::homePath(); + defaultPath = FileUtils::homePath(); } const QString detectionPath = Utils::HostOsInfo::withExecutableSuffix("bin/iccarm"); @@ -217,17 +215,17 @@ static McuPackage *createRGLPackage() { const char envVar[] = "RGL_DIR"; - QString defaultPath; + FilePath defaultPath; if (qEnvironmentVariableIsSet(envVar)) { - defaultPath = qEnvironmentVariable(envVar); + defaultPath = FilePath::fromUserInput(qEnvironmentVariable(envVar)); } else if (Utils::HostOsInfo::isWindowsHost()) { - defaultPath = QDir::rootPath() + "Renesas_Electronics/D1x_RGL"; - if (QFileInfo::exists(defaultPath)) { - const QFileInfoList subDirs = - QDir(defaultPath).entryInfoList({QLatin1String("rgl_ghs_D1Mx_*")}, + defaultPath = FilePath::fromUserInput(QDir::rootPath() + "Renesas_Electronics/D1x_RGL"); + if (defaultPath.exists()) { + const FilePaths subDirs = + defaultPath.dirEntries({QLatin1String("rgl_ghs_D1Mx_*")}, QDir::Dirs | QDir::NoDotAndDotDot); if (subDirs.count() == 1) - defaultPath = subDirs.first().filePath() + '/'; + defaultPath = subDirs.first(); } } @@ -242,15 +240,15 @@ static McuPackage *createRGLPackage() static McuPackage *createStm32CubeProgrammerPackage() { - QString defaultPath = QDir::homePath(); - const QString cubePath = "/STMicroelectronics/STM32Cube/STM32CubeProgrammer/"; - if (Utils::HostOsInfo::isWindowsHost()) { - const QString programPath = findInProgramFiles(cubePath); + FilePath defaultPath = FileUtils::homePath(); + const QString cubePath = "STMicroelectronics/STM32Cube/STM32CubeProgrammer"; + if (HostOsInfo::isWindowsHost()) { + const FilePath programPath = findInProgramFiles(cubePath); if (!programPath.isEmpty()) defaultPath = programPath; } else { - const QString programPath = QDir::homePath() + cubePath; - if (QFileInfo::exists(programPath)) + const FilePath programPath = FileUtils::homePath() / cubePath; + if (programPath.exists()) defaultPath = programPath; } auto result = new McuPackage( @@ -270,18 +268,18 @@ static McuPackage *createMcuXpressoIdePackage() { const char envVar[] = "MCUXpressoIDE_PATH"; - QString defaultPath; + FilePath defaultPath; if (qEnvironmentVariableIsSet(envVar)) { - defaultPath = qEnvironmentVariable(envVar); - } else if (Utils::HostOsInfo::isWindowsHost()) { - defaultPath = QDir::rootPath() + "nxp"; - if (QFileInfo::exists(defaultPath)) { + defaultPath = FilePath::fromUserInput(qEnvironmentVariable(envVar)); + } else if (HostOsInfo::isWindowsHost()) { + defaultPath = FilePath::fromString(QDir::rootPath() + "nxp"); + if (defaultPath.exists()) { // If default dir has exactly one sub dir that could be the IDE path, pre-select that. - const QFileInfoList subDirs = - QDir(defaultPath).entryInfoList({QLatin1String("MCUXpressoIDE*")}, + const FilePaths subDirs = + defaultPath.dirEntries({QLatin1String("MCUXpressoIDE*")}, QDir::Dirs | QDir::NoDotAndDotDot); if (subDirs.count() == 1) - defaultPath = subDirs.first().filePath() + '/'; + defaultPath = subDirs.first(); } } else { defaultPath = "/usr/local/mcuxpressoide/"; @@ -301,21 +299,20 @@ static McuPackage *createCypressProgrammerPackage() { const char envVar[] = "CYPRESS_AUTO_FLASH_UTILITY_DIR"; - QString defaultPath; + FilePath defaultPath; if (qEnvironmentVariableIsSet(envVar)) { - defaultPath = qEnvironmentVariable(envVar); - } else if (Utils::HostOsInfo::isWindowsHost()) { - auto candidate = findInProgramFiles(QLatin1String("/Cypress/Cypress Auto Flash Utility 1.0/")); - if (QFileInfo::exists(candidate)) { + defaultPath = FilePath::fromUserInput(qEnvironmentVariable(envVar)); + } else if (HostOsInfo::isWindowsHost()) { + FilePath candidate = findInProgramFiles("Cypress/Cypress Auto Flash Utility 1.0"); + if (candidate.exists()) { defaultPath = candidate; } } else { - defaultPath = QLatin1String("/usr"); + defaultPath = "/usr"; } - if (defaultPath.isEmpty()) { - defaultPath = QDir::homePath(); - } + if (defaultPath.isEmpty()) + defaultPath = FileUtils::homePath(); auto result = new McuPackage( "Cypress Auto Flash Utility", @@ -378,18 +375,16 @@ static McuPackage *createBoardSdkPackage(const McuTargetDescription& desc) }; const QString sdkName = desc.boardSdkName.isEmpty() ? generateSdkName(desc.boardSdkEnvVar) : desc.boardSdkName; - const QString defaultPath = [&] { + const FilePath defaultPath = [&] { const auto envVar = desc.boardSdkEnvVar.toLatin1(); - if (qEnvironmentVariableIsSet(envVar)) { - return qEnvironmentVariable(envVar); - } + if (qEnvironmentVariableIsSet(envVar)) + return FilePath::fromUserInput(qEnvironmentVariable(envVar)); if (!desc.boardSdkDefaultPath.isEmpty()) { - QString defaultPath = QDir::rootPath() + desc.boardSdkDefaultPath; - if (QFileInfo::exists(defaultPath)) { + FilePath defaultPath = FilePath::fromUserInput(QDir::rootPath() + desc.boardSdkDefaultPath); + if (defaultPath.exists()) return defaultPath; - } } - return QDir::homePath(); + return FileUtils::homePath(); }(); const auto versionDetector = generatePackageVersionDetector(desc.boardSdkEnvVar); @@ -404,18 +399,18 @@ static McuPackage *createBoardSdkPackage(const McuTargetDescription& desc) return result; } -static McuPackage *createFreeRTOSSourcesPackage(const QString &envVar, const QString &boardSdkDir, +static McuPackage *createFreeRTOSSourcesPackage(const QString &envVar, const FilePath &boardSdkDir, const QString &freeRTOSBoardSdkSubDir) { const QString envVarPrefix = envVar.chopped(int(strlen("_FREERTOS_DIR"))); - QString defaultPath; + FilePath defaultPath; if (qEnvironmentVariableIsSet(envVar.toLatin1())) - defaultPath = qEnvironmentVariable(envVar.toLatin1()); + defaultPath = FilePath::fromUserInput(qEnvironmentVariable(envVar.toLatin1())); else if (!boardSdkDir.isEmpty() && !freeRTOSBoardSdkSubDir.isEmpty()) - defaultPath = boardSdkDir + "/" + freeRTOSBoardSdkSubDir; + defaultPath = boardSdkDir / freeRTOSBoardSdkSubDir; else - defaultPath = QDir::homePath(); + defaultPath = FileUtils::homePath(); auto result = new McuPackage( QString::fromLatin1("FreeRTOS Sources (%1)").arg(envVarPrefix), @@ -473,7 +468,7 @@ protected: if (vendorPkgs.contains(desc.platformVendor)) required3rdPartyPkgs.push_back(vendorPkgs.value(desc.platformVendor)); - QString boardSdkDefaultPath; + FilePath boardSdkDefaultPath; if (!desc.boardSdkEnvVar.isEmpty()) { if (!boardSdkPkgs.contains(desc.boardSdkEnvVar)) { auto boardSdkPkg = desc.boardSdkEnvVar != "RGL_DIR" @@ -551,7 +546,7 @@ protected: required3rdPartyPkgs.push_back(vendorPkgs.value(desc.platformVendor)); // Board SDK specific settings - QString boardSdkDefaultPath; + FilePath boardSdkDefaultPath; if (!desc.boardSdkEnvVar.isEmpty()) { if (!boardSdkPkgs.contains(desc.boardSdkEnvVar)) { auto boardSdkPkg = createBoardSdkPackage(desc); diff --git a/src/plugins/projectexplorer/kitmanager.cpp b/src/plugins/projectexplorer/kitmanager.cpp index 3f77fee414..1c338d730a 100644 --- a/src/plugins/projectexplorer/kitmanager.cpp +++ b/src/plugins/projectexplorer/kitmanager.cpp @@ -238,12 +238,15 @@ void KitManager::restoreKits() kitsToCheck.clear(); // Remove replacement kits for which the original kit has turned up again. - Utils::erase(resultList, [&resultList](const std::unique_ptr<Kit> &k) { - return k->isReplacementKit() - && contains(resultList, [&k](const std::unique_ptr<Kit> &other) { - return other->id() == k->id() && other != k; - }); - }); + for (auto it = resultList.begin(); it != resultList.end();) { + const auto &k = *it; + if (k->isReplacementKit() && contains(resultList, [&k](const std::unique_ptr<Kit> &other) { + return other->id() == k->id() && other != k; })) { + it = resultList.erase(it); + } else { + ++it; + } + } static const auto kitMatchesAbiList = [](const Kit *kit, const Abis &abis) { const QList<ToolChain *> toolchains = ToolChainKitAspect::toolChains(kit); diff --git a/src/plugins/projectexplorer/miniprojecttargetselector.cpp b/src/plugins/projectexplorer/miniprojecttargetselector.cpp index b73d12c578..44fc5ae511 100644 --- a/src/plugins/projectexplorer/miniprojecttargetselector.cpp +++ b/src/plugins/projectexplorer/miniprojecttargetselector.cpp @@ -238,6 +238,18 @@ public: protected: void resetOptimalWidth() { + if (m_resetScheduled) + return; + m_resetScheduled = true; + QMetaObject::invokeMethod(this, &SelectorView::doResetOptimalWidth, Qt::QueuedConnection); + } + +private: + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + void doResetOptimalWidth() + { + m_resetScheduled = false; int width = 0; QFontMetrics fn(font()); theModel()->forItemsAtLevel<1>([this, &width, &fn](const GenericItem *item) { @@ -246,12 +258,9 @@ protected: setOptimalWidth(width); } -private: - void keyPressEvent(QKeyEvent *event) override; - void keyReleaseEvent(QKeyEvent *event) override; - int m_maxCount = 0; int m_optimalWidth = 0; + bool m_resetScheduled = false; }; class ProjectListView : public SelectorView diff --git a/src/plugins/qbsprojectmanager/qbsnodes.cpp b/src/plugins/qbsprojectmanager/qbsnodes.cpp index 2a155d2128..2d6049b570 100644 --- a/src/plugins/qbsprojectmanager/qbsnodes.cpp +++ b/src/plugins/qbsprojectmanager/qbsnodes.cpp @@ -218,7 +218,7 @@ QVariant QbsProductNode::data(Id role) const return m_productData.value("module-properties").toObject() .value("Qt.core.enableKeywords").toBool(); - if (role == Android::Constants::ANDROID_ABIS) { + if (role == Android::Constants::AndroidAbis) { // Try using qbs.architectures QStringList qbsAbis; QMap<QString, QString> archToAbi { diff --git a/src/plugins/qmakeprojectmanager/profilecompletionassist.cpp b/src/plugins/qmakeprojectmanager/profilecompletionassist.cpp index 419b4044de..ff08bb61b9 100644 --- a/src/plugins/qmakeprojectmanager/profilecompletionassist.cpp +++ b/src/plugins/qmakeprojectmanager/profilecompletionassist.cpp @@ -33,21 +33,22 @@ const TextEditor::Keywords &QmakeProjectManager::Internal::qmakeKeywords() { static TextEditor::Keywords keywords( QStringList{ // variables - "ANDROID_ABIS", + Android::Constants::ANDROID_ABI, + Android::Constants::ANDROID_ABIS, "ANDROID_API_VERSION", - QLatin1String(Android::Constants::ANDROID_APPLICATION_ARGUMENTS), + Android::Constants::ANDROID_APPLICATION_ARGUMENTS, "ANDROID_BUNDLED_JAR_DEPENDENCIES", "ANDROID_DEPLOYMENT_DEPENDENCIES", - QLatin1String(Android::Constants::ANDROID_DEPLOYMENT_SETTINGS_FILE), - QLatin1String(Android::Constants::ANDROID_EXTRA_LIBS), + Android::Constants::ANDROID_DEPLOYMENT_SETTINGS_FILE, + Android::Constants::ANDROID_EXTRA_LIBS, "ANDROID_EXTRA_PLUGINS", "ANDROID_FEATURES", "ANDROID_LIB_DEPENDENCIES", "ANDROID_MIN_SDK_VERSION", - QLatin1String(Android::Constants::ANDROID_PACKAGE_SOURCE_DIR), + Android::Constants::ANDROID_PACKAGE_SOURCE_DIR, "ANDROID_PERMISSIONS", "ANDROID_TARGET_SDK_VERSION", - "ANDROID_TARGET_ARCH", + Android::Constants::ANDROID_TARGET_ARCH, "ANDROID_VERSION_CODE", "ANDROID_VERSION_NAME", "ARGC", diff --git a/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp b/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp index beef0a160d..6b28aa6b44 100644 --- a/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp +++ b/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp @@ -157,7 +157,7 @@ QmakeBuildConfiguration::QmakeBuildConfiguration(Target *target, Utils::Id id) if (DeviceTypeKitAspect::deviceTypeId(target->kit()) == Android::Constants::ANDROID_DEVICE_TYPE) { - buildSteps()->appendStep(Android::Constants::ANDROID_PACKAGE_INSTALLATION_STEP_ID); + buildSteps()->appendStep(Android::Constants::ANDROID_PACKAGE_INSTALL_STEP_ID); buildSteps()->appendStep(Android::Constants::ANDROID_BUILD_APK_ID); } diff --git a/src/plugins/qmakeprojectmanager/qmakenodes.cpp b/src/plugins/qmakeprojectmanager/qmakenodes.cpp index 1214b79d64..46e95f0a68 100644 --- a/src/plugins/qmakeprojectmanager/qmakenodes.cpp +++ b/src/plugins/qmakeprojectmanager/qmakenodes.cpp @@ -364,16 +364,16 @@ QStringList QmakeProFileNode::targetApplications() const QVariant QmakeProFileNode::data(Utils::Id role) const { - if (role == Android::Constants::ANDROID_ABIS) + if (role == Android::Constants::AndroidAbis) return variableValue(Variable::AndroidAbis); + if (role == Android::Constants::AndroidAbi) + return singleVariableValue(Variable::AndroidAbi); + if (role == Android::Constants::AndroidExtraLibs) + return variableValue(Variable::AndroidExtraLibs); if (role == Android::Constants::AndroidPackageSourceDir) return singleVariableValue(Variable::AndroidPackageSourceDir); if (role == Android::Constants::AndroidDeploySettingsFile) return singleVariableValue(Variable::AndroidDeploySettingsFile); - if (role == Android::Constants::AndroidExtraLibs) - return variableValue(Variable::AndroidExtraLibs); - if (role == Android::Constants::AndroidArch) - return singleVariableValue(Variable::AndroidArch); if (role == Android::Constants::AndroidSoLibPath) { TargetInformation info = targetInformation(); QStringList res = {info.buildDir.toString()}; @@ -431,8 +431,9 @@ bool QmakeProFileNode::setData(Utils::Id role, const QVariant &value) const if (Target *target = m_buildSystem->target()) { QtSupport::BaseQtVersion *version = QtSupport::QtKitAspect::qtVersion(target->kit()); if (version && !version->supportsMultipleQtAbis()) { - const QString arch = pro->singleVariableValue(Variable::AndroidArch); - scope = "contains(ANDROID_TARGET_ARCH," + arch + ')'; + const QString arch = pro->singleVariableValue(Variable::AndroidAbi); + scope = QString("contains(%1,%2)").arg(Android::Constants::ANDROID_TARGET_ARCH) + .arg(arch); flags |= QmakeProjectManager::Internal::ProWriter::MultiLine; } } @@ -443,7 +444,7 @@ bool QmakeProFileNode::setData(Utils::Id role, const QVariant &value) const if (role == Android::Constants::AndroidPackageSourceDir) return pro->setProVariable(QLatin1String(Android::Constants::ANDROID_PACKAGE_SOURCE_DIR), {value.toString()}, scope, flags); - if (role == Android::Constants::ANDROID_APPLICATION_ARGUMENTS) + if (role == Android::Constants::AndroidApplicationArgs) return pro->setProVariable(QLatin1String(Android::Constants::ANDROID_APPLICATION_ARGUMENTS), {value.toString()}, scope, flags); diff --git a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp index 6cc4634375..f64aecd296 100644 --- a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp @@ -1562,11 +1562,11 @@ QmakeEvalResult *QmakeProFile::evaluate(const QmakeEvalInput &input) = exactReader->values(QLatin1String("TARGET_VERSION_EXT")); result->newVarValues[Variable::StaticLibExtension] = exactReader->values(QLatin1String("QMAKE_EXTENSION_STATICLIB")); result->newVarValues[Variable::ShLibExtension] = exactReader->values(QLatin1String("QMAKE_EXTENSION_SHLIB")); - result->newVarValues[Variable::AndroidArch] = exactReader->values(QLatin1String("ANDROID_TARGET_ARCH")); + result->newVarValues[Variable::AndroidAbi] = exactReader->values(QLatin1String(Android::Constants::ANDROID_TARGET_ARCH)); result->newVarValues[Variable::AndroidDeploySettingsFile] = exactReader->values(QLatin1String(Android::Constants::ANDROID_DEPLOYMENT_SETTINGS_FILE)); result->newVarValues[Variable::AndroidPackageSourceDir] = exactReader->values(QLatin1String(Android::Constants::ANDROID_PACKAGE_SOURCE_DIR)); - result->newVarValues[Variable::AndroidAbis] = exactReader->values(QLatin1String("ANDROID_ABIS")); - result->newVarValues[Variable::AndroidApplicationArguments] = exactReader->values(QLatin1String(Android::Constants::ANDROID_APPLICATION_ARGUMENTS)); + result->newVarValues[Variable::AndroidAbis] = exactReader->values(QLatin1String(Android::Constants::ANDROID_ABIS)); + result->newVarValues[Variable::AndroidApplicationArgs] = exactReader->values(QLatin1String(Android::Constants::ANDROID_APPLICATION_ARGUMENTS)); result->newVarValues[Variable::AndroidExtraLibs] = exactReader->values(QLatin1String(Android::Constants::ANDROID_EXTRA_LIBS)); result->newVarValues[Variable::AppmanPackageDir] = exactReader->values(QLatin1String("AM_PACKAGE_DIR")); result->newVarValues[Variable::AppmanManifest] = exactReader->values(QLatin1String("AM_MANIFEST")); diff --git a/src/plugins/qmakeprojectmanager/qmakeparsernodes.h b/src/plugins/qmakeprojectmanager/qmakeparsernodes.h index fc90e0759d..cfa86b181f 100644 --- a/src/plugins/qmakeprojectmanager/qmakeparsernodes.h +++ b/src/plugins/qmakeprojectmanager/qmakeparsernodes.h @@ -96,12 +96,12 @@ enum class Variable { TargetVersionExt, StaticLibExtension, ShLibExtension, - AndroidArch, - AndroidDeploySettingsFile, + AndroidAbi, AndroidAbis, + AndroidDeploySettingsFile, AndroidPackageSourceDir, AndroidExtraLibs, - AndroidApplicationArguments, + AndroidApplicationArgs, AppmanPackageDir, AppmanManifest, IsoIcons, diff --git a/src/plugins/qmakeprojectmanager/qmakeproject.cpp b/src/plugins/qmakeprojectmanager/qmakeproject.cpp index 793ec73ba8..54395bfc37 100644 --- a/src/plugins/qmakeprojectmanager/qmakeproject.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeproject.cpp @@ -674,8 +674,9 @@ void QmakeBuildSystem::asyncUpdate() connect(watcher, &QFutureWatcher<void>::canceled, this, [this, watcher] { if (!m_qmakeGlobals) return; - watcher->disconnect(); m_qmakeGlobals->killProcesses(); + watcher->disconnect(); + watcher->deleteLater(); }); connect(watcher, &QFutureWatcher<void>::finished, this, [watcher] { watcher->disconnect(); diff --git a/src/plugins/qmakeprojectmanager/qmakestep.cpp b/src/plugins/qmakeprojectmanager/qmakestep.cpp index 7cc65532af..9e7c7f2230 100644 --- a/src/plugins/qmakeprojectmanager/qmakestep.cpp +++ b/src/plugins/qmakeprojectmanager/qmakestep.cpp @@ -632,7 +632,7 @@ void QMakeStep::abisChanged() if (BaseQtVersion *qtVersion = QtKitAspect::qtVersion(target()->kit())) { if (qtVersion->hasAbi(Abi::LinuxOS, Abi::AndroidLinuxFlavor)) { - const QString prefix = "ANDROID_ABIS="; + const QString prefix = QString("%1=").arg(Android::Constants::ANDROID_ABIS); QStringList args = m_extraArgs; for (auto it = args.begin(); it != args.end(); ++it) { if (it->startsWith(prefix)) { @@ -643,8 +643,7 @@ void QMakeStep::abisChanged() if (!m_selectedAbis.isEmpty()) args << prefix + '"' + m_selectedAbis.join(' ') + '"'; setExtraArguments(args); - - buildSystem()->setProperty(Android::Constants::ANDROID_ABIS, m_selectedAbis); + buildSystem()->setProperty(Android::Constants::AndroidAbis, m_selectedAbis); } else if (qtVersion->hasAbi(Abi::DarwinOS) && !isIos(target()->kit())) { const QString prefix = "QMAKE_APPLE_DEVICE_ARCHS="; QStringList args = m_extraArgs; diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h index 4fd6fdf159..49505d2ca7 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h +++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h @@ -222,6 +222,9 @@ const char addImagesDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources const char addFontsDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", "Font Files"); const char addSoundsDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", "Sound Files"); const char addShadersDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", "Shader Files"); +const char add3DAssetsDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", "3D Assets"); +const char addQt3DSPresentationsDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", + "Qt 3D Studio Presentations"); const char addCustomEffectDialogDisplayString[] = QT_TRANSLATE_NOOP("QmlDesignerAddResources", "Add Custom Effect"); diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index d452889363..8ca977e742 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -262,7 +262,7 @@ bool DesignerActionManager::externalDragHasSupportedAssets(const QMimeData *mime return false; } -void DesignerActionManager::handleExternalAssetsDrop(const QMimeData *mimeData) const +QHash<QString, QStringList> DesignerActionManager::handleExternalAssetsDrop(const QMimeData *mimeData) const { const QList<AddResourceHandler> handlers = addResourceHandler(); // create suffix to categry and category to operation hashes @@ -283,13 +283,19 @@ void DesignerActionManager::handleExternalAssetsDrop(const QMimeData *mimeData) categoryFiles[category].append(url.toLocalFile()); } + QHash<QString, QStringList> addedCategoryFiles; + // run operations const QStringList categories = categoryFiles.keys(); for (const QString &category : categories) { AddResourceOperation operation = categoryOperation.value(category); QStringList files = categoryFiles.value(category); - operation(files, {}); + bool success = operation(files, {}); + if (success) + addedCategoryFiles.insert(category, files); } + + return addedCategoryFiles; } class VisiblityModelNodeAction : public ModelNodeContextMenuAction diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.h b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.h index e0fe93f601..60e2c7562f 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.h +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.h @@ -137,7 +137,7 @@ public: bool hasModelNodePreviewHandler(const ModelNode &node) const; ModelNodePreviewImageOperation modelNodePreviewOperation(const ModelNode &node) const; bool externalDragHasSupportedAssets(const QMimeData *data) const; - void handleExternalAssetsDrop(const QMimeData *data) const; + QHash<QString, QStringList> handleExternalAssetsDrop(const QMimeData *data) const; private: void addTransitionEffectAction(const TypeName &typeName); diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp index 81066504de..ab2853354c 100644 --- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp +++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp @@ -995,9 +995,8 @@ Utils::FilePath projectFilePath() static bool addFilesToProject(const QStringList &fileNames, const QString &defaultDirectory) { QString directory = AddImagesDialog::getDirectory(fileNames, defaultDirectory); - if (directory.isEmpty()) - return true; + return false; bool allSuccessful = true; QList<QPair<QString, QString>> copyList; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 025b622789..835ba07b5b 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -23,14 +23,15 @@ ** ****************************************************************************/ -#include "edit3dwidget.h" -#include "edit3dview.h" -#include "edit3dcanvas.h" -#include "edit3dactions.h" - -#include "qmldesignerplugin.h" #include "designersettings.h" +#include "edit3dactions.h" +#include "edit3dcanvas.h" +#include "edit3dview.h" +#include "edit3dwidget.h" +#include "metainfo.h" #include "qmldesignerconstants.h" +#include "qmldesignerplugin.h" +#include "qmlvisualnode.h" #include "viewmanager.h" #include <coreplugin/actionmanager/actionmanager.h> @@ -174,7 +175,20 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent) { const DesignerActionManager &actionManager = QmlDesignerPlugin::instance() ->viewManager().designerActionManager(); - actionManager.handleExternalAssetsDrop(dropEvent->mimeData()); + QHash<QString, QStringList> addedAssets = actionManager.handleExternalAssetsDrop(dropEvent->mimeData()); + + // add 3D assets to 3d editor (QtQuick3D import will be added if missing) + ItemLibraryInfo *itemLibInfo = m_view->model()->metaInfo().itemLibraryInfo(); + + const QStringList added3DAssets = addedAssets.value(ComponentCoreConstants::add3DAssetsDisplayString); + for (const QString &assetPath : added3DAssets) { + QString fileName = QFileInfo(assetPath).baseName(); + fileName = fileName.at(0).toUpper() + fileName.mid(1); // capitalize first letter + QString type = QString("Quick3DAssets.%1.%1").arg(fileName); + QList<ItemLibraryEntry> entriesForType = itemLibInfo->entriesForType(type.toLatin1()); + if (!entriesForType.isEmpty()) // should always be true, but just in case + QmlVisualNode::createQml3DNode(view(), entriesForType.at(0), m_canvas->activeScene()).modelNode(); + } } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp index 8200558d18..4f000e1295 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp @@ -99,8 +99,7 @@ FormEditorItem::FormEditorItem(const QmlItemNode &qmlItemNode, FormEditorScene* m_borderWidth(1.0), m_highlightBoundingRect(false), m_blurContent(false), - m_isContentVisible(true), - m_isFormEditorVisible(true) + m_isContentVisible(true) { setCacheMode(QGraphicsItem::NoCache); setup(); @@ -208,17 +207,6 @@ bool FormEditorItem::isContentVisible() const return m_isContentVisible; } - -bool FormEditorItem::isFormEditorVisible() const -{ - return m_isFormEditorVisible; -} -void FormEditorItem::setFormEditorVisible(bool isVisible) -{ - m_isFormEditorVisible = isVisible; - setVisible(isVisible); -} - QPointF FormEditorItem::center() const { return mapToScene(qmlItemNode().instanceBoundingRect().center()); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.h b/src/plugins/qmldesigner/components/formeditor/formeditoritem.h index 769488aa4a..2213f42a66 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.h @@ -104,9 +104,6 @@ public: void setContentVisible(bool visible); bool isContentVisible() const; - bool isFormEditorVisible() const; - void setFormEditorVisible(bool isVisible); - QPointF center() const; qreal selectionWeigth(const QPointF &point, int iteration); @@ -152,7 +149,6 @@ private: // variables bool m_highlightBoundingRect; bool m_blurContent; bool m_isContentVisible; - bool m_isFormEditorVisible; }; class FormEditorFlowItem : public FormEditorItem diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp index 0ee760b9a1..10e1373dde 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp @@ -91,6 +91,9 @@ void FormEditorView::modelAttached(Model *model) //This function does the setup of the initial FormEditorItem tree in the scene void FormEditorView::setupFormEditorItemTree(const QmlItemNode &qmlItemNode) { + if (!qmlItemNode.hasFormEditorItem()) + return; + if (qmlItemNode.isFlowTransition()) { m_scene->addFormEditorItem(qmlItemNode, FormEditorScene::FlowTransition); if (qmlItemNode.hasNodeParent()) @@ -199,16 +202,6 @@ void FormEditorView::removeNodeFromScene(const QmlItemNode &qmlItemNode) m_currentTool->itemsAboutToRemoved(removedItemList); } -void FormEditorView::hideNodeFromScene(const QmlItemNode &qmlItemNode) -{ - if (FormEditorItem *item = m_scene->itemForQmlItemNode(qmlItemNode)) { - QList<FormEditorItem*> removedItems = scene()->itemsForQmlItemNodes(qmlItemNode.allSubModelNodes()); - removedItems.append(item); - m_currentTool->itemsAboutToRemoved(removedItems); - item->setFormEditorVisible(false); - } -} - void FormEditorView::createFormEditorWidget() { m_formEditorWidget = QPointer<FormEditorWidget>(new FormEditorWidget(this)); @@ -248,10 +241,7 @@ void FormEditorView::temporaryBlockView(int duration) void FormEditorView::nodeCreated(const ModelNode &node) { - //If the node has source for components/custom parsers we ignore it. - if (QmlItemNode::isValidQmlItemNode(node) && node.nodeSourceType() == ModelNode::NodeWithoutSource) //only setup QmlItems - setupFormEditorItemTree(QmlItemNode(node)); - else if (QmlVisualNode::isFlowTransition(node)) + if (QmlVisualNode::isFlowTransition(node)) setupFormEditorItemTree(QmlItemNode(node)); } @@ -349,8 +339,26 @@ static inline bool hasNodeSourceParent(const ModelNode &node) void FormEditorView::nodeReparented(const ModelNode &node, const NodeAbstractProperty &/*newPropertyParent*/, const NodeAbstractProperty &/*oldPropertyParent*/, AbstractView::PropertyChangeFlags /*propertyChange*/) { - if (hasNodeSourceParent(node)) - hideNodeFromScene(node); + // If node is not connected to scene root, don't do anything yet to avoid duplicated effort, + // as any removal or addition will remove or add descendants as well. + if (!node.isInHierarchy()) + return; + + QmlItemNode itemNode(node); + if (hasNodeSourceParent(node)) { + if (FormEditorItem *item = m_scene->itemForQmlItemNode(itemNode)) { + QList<FormEditorItem *> removed = scene()->itemsForQmlItemNodes(itemNode.allSubModelNodes()); + removed.append(item); + m_currentTool->itemsAboutToRemoved(removed); + removeNodeFromScene(itemNode); + } + } else if (itemNode.isValid() && node.nodeSourceType() == ModelNode::NodeWithoutSource) { + if (!m_scene->itemForQmlItemNode(itemNode)) { + setupFormEditorItemTree(itemNode); + // Simulate selection change to refresh tools + selectedNodesChanged(selectedModelNodes(), {}); + } + } } WidgetInfo FormEditorView::widgetInfo() @@ -603,8 +611,7 @@ void FormEditorView::auxiliaryDataChanged(const ModelNode &node, const PropertyN if (name == "invisible") { if (FormEditorItem *item = scene()->itemForQmlItemNode(QmlItemNode(node))) { bool isInvisible = data.toBool(); - if (item->isFormEditorVisible()) - item->setVisible(!isInvisible); + item->setVisible(!isInvisible); ModelNode newNode(node); if (isInvisible) newNode.deselectNode(); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.h b/src/plugins/qmldesigner/components/formeditor/formeditorview.h index ba2b79df99..04b7d1e83e 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorview.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.h @@ -144,7 +144,6 @@ protected: private: void setupFormEditorItemTree(const QmlItemNode &qmlItemNode); void removeNodeFromScene(const QmlItemNode &qmlItemNode); - void hideNodeFromScene(const QmlItemNode &qmlItemNode); void createFormEditorWidget(); void temporaryBlockView(int duration = 1000); void resetNodeInstanceView(); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp index 1f9fc4487b..6b81d1f92e 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp @@ -597,7 +597,14 @@ void FormEditorWidget::dropEvent(QDropEvent *dropEvent) { const DesignerActionManager &actionManager = QmlDesignerPlugin::instance() ->viewManager().designerActionManager(); - actionManager.handleExternalAssetsDrop(dropEvent->mimeData()); + QHash<QString, QStringList> addedAssets = actionManager.handleExternalAssetsDrop(dropEvent->mimeData()); + + // add image assets to Form Editor + const QStringList addedImages = addedAssets.value(ComponentCoreConstants::addImagesDisplayString); + for (const QString &imgPath : addedImages) { + QmlItemNode::createQmlItemNodeFromImage(m_formEditorView, imgPath, {}, + m_formEditorView->scene()->rootFormEditorItem()->qmlItemNode()); + } } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/formeditor/selectiontool.cpp b/src/plugins/qmldesigner/components/formeditor/selectiontool.cpp index 7230e97a1c..f240b32f76 100644 --- a/src/plugins/qmldesigner/components/formeditor/selectiontool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/selectiontool.cpp @@ -34,6 +34,8 @@ #include <nodemetainfo.h> +#include <utils/algorithm.h> + #include <QGraphicsSceneMouseEvent> #include <QDebug> @@ -242,9 +244,23 @@ void SelectionTool::dragMoveEvent(const QList<QGraphicsItem*> &/*itemList*/, QGr { } -void SelectionTool::itemsAboutToRemoved(const QList<FormEditorItem*> &/*itemList*/) +void SelectionTool::itemsAboutToRemoved(const QList<FormEditorItem*> &itemList) { - + const QList<FormEditorItem *> current = items(); + + QList<FormEditorItem *> remaining = Utils::filtered(current, [&itemList](FormEditorItem *item) { + return !itemList.contains(item); + }); + + if (!remaining.isEmpty()) { + m_selectionIndicator.setItems(remaining); + m_resizeIndicator.setItems(remaining); + m_rotationIndicator.setItems(remaining); + m_anchorIndicator.setItems(remaining); + m_bindingIndicator.setItems(remaining); + } else { + clear(); + } } void SelectionTool::clear() diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp index 3bf7447a34..06b84d83f8 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp @@ -240,35 +240,38 @@ void ItemLibraryView::updateImport3DSupport(const QVariantMap &supportMap) DesignerActionManager *actionManager = &QmlDesignerPlugin::instance()->viewManager().designerActionManager(); - // All things importable by QSSGAssetImportManager are considered to be in the same category - // so we don't get multiple separate import dialogs when different file types are imported. - const QString category = tr("3D Assets"); - if (!m_importableExtensions3DMap.isEmpty()) - actionManager->unregisterAddResourceHandlers(category); + actionManager->unregisterAddResourceHandlers(ComponentCoreConstants::add3DAssetsDisplayString); m_importableExtensions3DMap = extMap; - auto handle3DModel = [this](const QStringList &fileNames, const QString &defaultDir) -> bool { + auto import3DModelOperation = [this](const QStringList &fileNames, const QString &defaultDir) -> bool { auto importDlg = new ItemLibraryAssetImportDialog(fileNames, defaultDir, m_importableExtensions3DMap, m_importOptions3DMap, {}, {}, Core::ICore::mainWindow()); - importDlg->show(); + importDlg->exec(); return true; }; - auto add3DHandler = [&](const QString &category, const QString &ext) { + auto add3DHandler = [&](const QString &group, const QString &ext) { const QString filter = QStringLiteral("*.%1").arg(ext); actionManager->registerAddResourceHandler( - AddResourceHandler(category, filter, handle3DModel, 10)); + AddResourceHandler(group, filter, + import3DModelOperation, 10)); + }; + + const QHash<QString, QString> groupNames { + {"3D Scene", ComponentCoreConstants::add3DAssetsDisplayString}, + {"Qt 3D Studio Presentation", ComponentCoreConstants::addQt3DSPresentationsDisplayString} }; const auto groups = extMap.keys(); for (const auto &group : groups) { const QStringList exts = extMap[group].toStringList(); + const QString grp = groupNames.contains(group) ? groupNames.value(group) : group; for (const auto &ext : exts) - add3DHandler(category, ext); + add3DHandler(grp, ext); } } diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index 5abc38eba6..08dfc9fd03 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -732,17 +732,22 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in ModelNode targetNode = targetProperty.parentModelNode(); NodeMetaInfo metaInfo = targetNode.metaInfo(); TypeName typeName = newModelNode.type(); - const PropertyNameList nameList = targetNode.metaInfo().directPropertyNames(); - for (const auto &propertyName : nameList) { - auto testType = metaInfo.propertyTypeName(propertyName); - if (testType == typeName || newModelNode.isSubclassOf(testType)) { - ChooseFromPropertyListDialog *dialog = nullptr; - dialog = new ChooseFromPropertyListDialog(targetNode, testType, Core::ICore::dialogParent()); - dialog->exec(); - if (dialog->result() == QDialog::Accepted) - targetNode.bindingProperty(dialog->selectedProperty()).setExpression(newModelNode.validId()); - delete dialog; - break; + + // Empty components are not supported and having one as property value is generally + // unstable, so let's not offer user to put a fresh Component into a property + if (typeName != "QtQml.Component") { + const PropertyNameList nameList = targetNode.metaInfo().directPropertyNames(); + for (const auto &propertyName : nameList) { + auto testType = metaInfo.propertyTypeName(propertyName); + if (testType == typeName || newModelNode.isSubclassOf(testType)) { + ChooseFromPropertyListDialog *dialog = nullptr; + dialog = new ChooseFromPropertyListDialog(targetNode, testType, Core::ICore::dialogParent()); + dialog->exec(); + if (dialog->result() == QDialog::Accepted) + targetNode.bindingProperty(dialog->selectedProperty()).setExpression(newModelNode.validId()); + delete dialog; + break; + } } } } @@ -1085,10 +1090,24 @@ void NavigatorTreeModel::moveNodesInteractive(NodeAbstractProperty &parentProper if (modelNode.isValid() && modelNode != parentProperty.parentModelNode() && !modelNode.isAncestorOf(parentProperty.parentModelNode()) - && (modelNode.metaInfo().isSubclassOf(propertyQmlType) || propertyQmlType == "alias")) { + && (modelNode.metaInfo().isSubclassOf(propertyQmlType) + || propertyQmlType == "alias" + || parentProperty.name() == "data" + || (parentProperty.parentModelNode().metaInfo().defaultPropertyName() == parentProperty.name() + && propertyQmlType == "<cpp>.QQmlComponent"))) { //### todo: allowing alias is just a heuristic //once the MetaInfo is part of instances we can do this right + // We assume above that "data" property in parent accepts all types. + // This is a workaround for Component parents to accept children, even though they + // do not have an actual "data" property or apparently any other default property. + // When the actual reparenting happens, model will create the "data" property if + // it is missing. + + // We allow move even if target property type doesn't match, if the target property + // is the default property of the parent and is of Component type. + // In that case an implicit component will be created. + bool nodeCanBeMovedToParentProperty = removeModelNodeFromNodeProperty(parentProperty, modelNode); if (nodeCanBeMovedToParentProperty) { reparentModelNodeToNodeProperty(parentProperty, modelNode); diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp b/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp index aecab7c42c..2664f2c7a9 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/qmlanchorbindingproxy.cpp @@ -861,6 +861,7 @@ void QmlAnchorBindingProxy::anchorVertical() m_qmlItemNode.anchors().setAnchor(AnchorLineVerticalCenter, m_verticalTarget, AnchorLineVerticalCenter); } + backupPropertyAndRemove(modelNode(), "y"); m_locked = false; } @@ -874,6 +875,7 @@ void QmlAnchorBindingProxy::anchorHorizontal() } else if (m_relativeVerticalTarget == Center) { m_qmlItemNode.anchors().setAnchor(AnchorLineHorizontalCenter, m_horizontalTarget, AnchorLineHorizontalCenter); } + backupPropertyAndRemove(modelNode(), "x"); m_locked = false; } @@ -993,6 +995,7 @@ void QmlAnchorBindingProxy::setVerticalCentered(bool centered) if (!centered) { m_qmlItemNode.anchors().removeAnchor(AnchorLineVerticalCenter); m_qmlItemNode.anchors().removeMargin(AnchorLineVerticalCenter); + restoreProperty(m_qmlItemNode, "y"); } else { m_relativeVerticalTarget = Center; @@ -1020,6 +1023,7 @@ void QmlAnchorBindingProxy::setHorizontalCentered(bool centered) if (!centered) { m_qmlItemNode.anchors().removeAnchor(AnchorLineHorizontalCenter); m_qmlItemNode.anchors().removeMargin(AnchorLineHorizontalCenter); + restoreProperty(m_qmlItemNode, "x"); } else { m_relativeHorizontalTarget = Center; diff --git a/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp b/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp index 12fff40c79..997852cfb2 100644 --- a/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp +++ b/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp @@ -46,6 +46,7 @@ #include <qmlstate.h> #include <annotationeditor/annotationeditor.h> #include <utils/algorithm.h> +#include <utils/qtcassert.h> namespace QmlDesigner { @@ -103,6 +104,7 @@ void StatesEditorView::removeState(int nodeId) const auto propertyChanges = modelState.propertyChanges(); for (const QmlPropertyChanges &change : propertyChanges) { const ModelNode target = change.target(); + QTC_ASSERT(target.isValid(), continue); if (target.locked()) lockedTargets.push_back(target.id()); } diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp index 704d7c2b0d..ad2dfd88f6 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp @@ -70,14 +70,6 @@ namespace QmlDesigner { -static int deleteKey() -{ - if (Utils::HostOsInfo::isMacHost()) - return Qt::Key_Backspace; - - return Qt::Key_Delete; -} - QList<QmlTimelineKeyframeGroup> allTimelineFrames(const QmlTimeline &timeline) { QList<QmlTimelineKeyframeGroup> returnList; @@ -674,7 +666,7 @@ void TimelineGraphicsScene::keyReleaseEvent(QKeyEvent *keyEvent) return; } - if (deleteKey() == keyEvent->key()) + if (TimelineUtils::isDeleteKey(keyEvent->key())) handleKeyframeDeletion(); QGraphicsScene::keyReleaseEvent(keyEvent); @@ -838,7 +830,7 @@ bool TimelineGraphicsScene::event(QEvent *event) { switch (event->type()) { case QEvent::ShortcutOverride: - if (static_cast<QKeyEvent *>(event)->key() == deleteKey()) { + if (TimelineUtils::isDeleteKey(static_cast<QKeyEvent *>(event)->key())) { QGraphicsScene::keyPressEvent(static_cast<QKeyEvent *>(event)); event->accept(); return true; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineutils.h b/src/plugins/qmldesigner/components/timelineeditor/timelineutils.h index ec0f4cec5b..b31bd8f422 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineutils.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineutils.h @@ -39,6 +39,11 @@ namespace TimelineUtils { enum class Side { Top, Right, Bottom, Left }; +inline bool isDeleteKey(int key) +{ + return (key == Qt::Key_Backspace) || (key == Qt::Key_Delete); +} + template<typename T> inline T clamp(const T &value, const T &lo, const T &hi) { diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp index ab82744cc6..e2b9578356 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp @@ -39,6 +39,7 @@ #include "timelineplaceholder.h" #include "timelinepropertyitem.h" #include "timelinesectionitem.h" +#include "timelineutils.h" #include <designdocumentview.h> #include <exception.h> @@ -73,14 +74,6 @@ namespace QmlDesigner { -static int deleteKey() -{ - if (Utils::HostOsInfo::isMacHost()) - return Qt::Key_Backspace; - - return Qt::Key_Delete; -} - TransitionEditorGraphicsScene::TransitionEditorGraphicsScene(TransitionEditorWidget *parent) : AbstractScrollGraphicsScene(parent) , m_parent(parent) @@ -459,7 +452,7 @@ bool TransitionEditorGraphicsScene::event(QEvent *event) { switch (event->type()) { case QEvent::ShortcutOverride: - if (static_cast<QKeyEvent *>(event)->key() == deleteKey()) { + if (TimelineUtils::isDeleteKey(static_cast<QKeyEvent *>(event)->key())) { QGraphicsScene::keyPressEvent(static_cast<QKeyEvent *>(event)); event->accept(); return true; diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp index 97dc457f39..ccf84152b5 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp @@ -98,6 +98,10 @@ void TransitionEditorView::nodeRemoved(const ModelNode & removedNode, { if (parentProperty.name() == "transitions") widget()->updateData(removedNode); + + const ModelNode parent = parentProperty.parentModelNode(); + if (parent.isValid() && parent.metaInfo().isSubclassOf("QtQuick.Transition")) + asyncUpdate(parent); } void TransitionEditorView::nodeReparented(const ModelNode &node, @@ -110,7 +114,6 @@ void TransitionEditorView::nodeReparented(const ModelNode &node, const ModelNode parent = newPropertyParent.parentModelNode(); - // qDebug() << Q_FUNC_INFO << parent; if (parent.isValid() && parent.metaInfo().isValid() && parent.metaInfo().isSubclassOf("QtQuick.Transition")) { asyncUpdate(parent); diff --git a/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h b/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h index c44467f002..8244fbb489 100644 --- a/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h +++ b/src/plugins/qmldesigner/designercore/include/itemlibraryinfo.h @@ -100,7 +100,8 @@ class QMLDESIGNERCORE_EXPORT ItemLibraryInfo : public QObject public: QList<ItemLibraryEntry> entries() const; - QList<ItemLibraryEntry> entriesForType(const QByteArray &typeName, int majorVersion, int minorVersion) const; + QList<ItemLibraryEntry> entriesForType(const QByteArray &typeName, int majorVersion = 1, + int minorVersion = 0) const; void addEntries(const QList<ItemLibraryEntry> &entries, bool overwriteDuplicate = false); bool containsEntry(const ItemLibraryEntry &entry); diff --git a/src/plugins/qmldesigner/designercore/include/nodehints.h b/src/plugins/qmldesigner/designercore/include/nodehints.h index a22dd40f40..16fe18320c 100644 --- a/src/plugins/qmldesigner/designercore/include/nodehints.h +++ b/src/plugins/qmldesigner/designercore/include/nodehints.h @@ -62,6 +62,7 @@ public: bool canBeDroppedInView3D() const; bool isMovable() const; bool isResizable() const; + bool hasFormEditorItem() const; bool isStackedContainer() const; bool canBeReparentedTo(const ModelNode &potenialParent); QString indexPropertyForStackedContainer() const; diff --git a/src/plugins/qmldesigner/designercore/include/qmlitemnode.h b/src/plugins/qmldesigner/designercore/include/qmlitemnode.h index 579a4ca7b8..9be07edfbf 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlitemnode.h +++ b/src/plugins/qmldesigner/designercore/include/qmlitemnode.h @@ -105,6 +105,7 @@ public: bool modelIsResizable() const; bool modelIsRotatable() const; bool modelIsInLayout() const; + bool hasFormEditorItem() const; QRectF instanceBoundingRect() const; QRectF instanceSceneBoundingRect() const; diff --git a/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h b/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h index 59f99bbe39..7b1cfbe86e 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h +++ b/src/plugins/qmldesigner/designercore/include/qmlobjectnode.h @@ -128,14 +128,13 @@ public: virtual bool isBlocked(const PropertyName &propName) const; friend auto qHash(const QmlObjectNode &node) { return qHash(node.modelNode()); } + QList<QmlModelState> allDefinedStates() const; + QList<QmlModelStateOperation> allInvalidStateOperations() const; protected: NodeInstance nodeInstance() const; QmlObjectNode nodeForInstance(const NodeInstance &instance) const; QmlItemNode itemForInstance(const NodeInstance &instance) const; - -protected: - QList<QmlModelState> allDefinedStates() const; }; QMLDESIGNERCORE_EXPORT QList<ModelNode> toModelNodeList(const QList<QmlObjectNode> &fxObjectNodeList); diff --git a/src/plugins/qmldesigner/designercore/include/qmlstate.h b/src/plugins/qmldesigner/designercore/include/qmlstate.h index bd0aad854b..ce9a8bf3f7 100644 --- a/src/plugins/qmldesigner/designercore/include/qmlstate.h +++ b/src/plugins/qmldesigner/designercore/include/qmlstate.h @@ -50,6 +50,7 @@ public: QList<QmlModelStateOperation> stateOperations(const ModelNode &node) const; QList<QmlPropertyChanges> propertyChanges() const; QList<QmlModelStateOperation> stateOperations() const; + QList<QmlModelStateOperation> allInvalidStateOperations() const; bool hasPropertyChanges(const ModelNode &node) const; diff --git a/src/plugins/qmldesigner/designercore/include/qmltimelinekeyframegroup.h b/src/plugins/qmldesigner/designercore/include/qmltimelinekeyframegroup.h index a20eeae55e..6aee5ea0d5 100644 --- a/src/plugins/qmldesigner/designercore/include/qmltimelinekeyframegroup.h +++ b/src/plugins/qmldesigner/designercore/include/qmltimelinekeyframegroup.h @@ -72,6 +72,7 @@ public: static bool isValidKeyframe(const ModelNode &node); static bool checkKeyframesType(const ModelNode &node); static QmlTimelineKeyframeGroup keyframeGroupForKeyframe(const ModelNode &node); + static QList<QmlTimelineKeyframeGroup> allInvalidTimelineKeyframeGroups(AbstractView *view); void moveAllKeyframes(qreal offset); void scaleAllKeyframes(qreal factor); @@ -84,6 +85,8 @@ public: void toogleRecording(bool b) const; QmlTimeline timeline() const; + + bool isDangling() const; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 6ecde66a30..078a74a534 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -644,7 +644,7 @@ void NodeInstanceView::nodeSourceChanged(const ModelNode &node, const QString & m_nodeInstanceServer->changeNodeSource(changeNodeSourceCommand); // Puppet doesn't deal with node source changes properly, so just reset the puppet for now - delayedRestartProcess(); // TODO: Remove this once the issue is properly fixed (QDS-4955) + resetPuppet(); // TODO: Remove this once the issue is properly fixed (QDS-4955) } } diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp index 26b06cdc42..84cc565074 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/nodehints.cpp @@ -183,6 +183,11 @@ bool NodeHints::isResizable() const return evaluateBooleanExpression("isResizable", true); } +bool NodeHints::hasFormEditorItem() const +{ + return evaluateBooleanExpression("hasFormEditorItem", true); +} + bool NodeHints::isStackedContainer() const { if (!isValid()) diff --git a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp index 03ae0ecd1d..02ecb64efd 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp @@ -395,6 +395,11 @@ bool QmlItemNode::modelIsInLayout() const return false; } +bool QmlItemNode::hasFormEditorItem() const +{ + return NodeHints::fromModelNode(modelNode()).hasFormEditorItem(); +} + QRectF QmlItemNode::instanceBoundingRect() const { return QRectF(QPointF(0, 0), nodeInstance().size()); diff --git a/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp index 556a7624ba..55305c72d1 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlobjectnode.cpp @@ -328,16 +328,58 @@ QmlPropertyChanges QmlObjectNode::propertyChangeForCurrentState() const static void removeStateOperationsForChildren(const QmlObjectNode &node) { if (node.isValid()) { - foreach (QmlModelStateOperation stateOperation, node.allAffectingStatesOperations()) { + for (QmlModelStateOperation stateOperation : node.allAffectingStatesOperations()) { stateOperation.modelNode().destroy(); //remove of belonging StatesOperations } - foreach (const QmlObjectNode &childNode, node.modelNode().directSubModelNodes()) { + for (const QmlObjectNode &childNode : node.modelNode().directSubModelNodes()) { removeStateOperationsForChildren(childNode); } } } +static void removeAnimationsFromAnimation(const ModelNode &animation) +{ + QTC_ASSERT(animation.isValid(), return); + + const QList<ModelNode> propertyAnimations = animation.subModelNodesOfType( + "QtQuick.PropertyAnimation"); + + for (const ModelNode &child : propertyAnimations) { + if (!child.hasBindingProperty("target")) { + ModelNode nonConst = animation; + nonConst.destroy(); + return; + } + } +} + +static void removeAnimationsFromTransition(const ModelNode &transition, const QmlObjectNode &node) +{ + QTC_ASSERT(node.isValid(), return); + QTC_ASSERT(transition.isValid(), return); + + const auto children = transition.directSubModelNodes(); + for (const ModelNode ¶llel : children) + removeAnimationsFromAnimation(parallel); +} + +static void removeDanglingAnimationsFromTransitions(const QmlObjectNode &node) +{ + QTC_ASSERT(node.isValid(), return); + + auto root = node.view()->rootModelNode(); + + if (root.isValid() && root.hasProperty("transitions")) { + NodeAbstractProperty transitions = root.nodeAbstractProperty("transitions"); + if (transitions.isValid()) { + const auto transitionNodes = transitions.directSubNodes(); + for (const auto &transition : transitionNodes) + removeAnimationsFromTransition(transition, node); + } + } +} + static void removeAliasExports(const QmlObjectNode &node) { @@ -368,6 +410,14 @@ static void removeLayerEnabled(const ModelNode &node) } } +static void deleteAllReferencesToNodeAndChildren(const ModelNode &node) +{ + BindingProperty::deleteAllReferencesTo(node); + const auto subNodes = node.allSubModelNodes(); + for (const ModelNode &child : subNodes) + BindingProperty::deleteAllReferencesTo(child); +} + /*! Deletes this object's node and its dependencies from the model. Everything that belongs to this Object, the ModelNode, and ChangeOperations @@ -406,7 +456,9 @@ void QmlObjectNode::destroy() } removeStateOperationsForChildren(modelNode()); - BindingProperty::deleteAllReferencesTo(modelNode()); + deleteAllReferencesToNodeAndChildren(modelNode()); + + removeDanglingAnimationsFromTransitions(modelNode()); QmlFlowViewNode root(view()->rootModelNode()); @@ -515,6 +567,16 @@ QList<QmlModelState> QmlObjectNode::allDefinedStates() const return returnList; } +QList<QmlModelStateOperation> QmlObjectNode::allInvalidStateOperations() const +{ + QList<QmlModelStateOperation> result; + + const auto allStates = allDefinedStates(); + for (const auto &state : allStates) + result.append(state.allInvalidStateOperations()); + return result; +} + /*! Removes a variant property of the object specified by \a name from the diff --git a/src/plugins/qmldesigner/designercore/model/qmlstate.cpp b/src/plugins/qmldesigner/designercore/model/qmlstate.cpp index ea15ba1d81..26fff118d2 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlstate.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlstate.cpp @@ -34,6 +34,7 @@ #include "qmlitemnode.h" #include "annotation.h" +#include <utils/algorithm.h> #include <utils/qtcassert.h> namespace QmlDesigner { @@ -136,6 +137,12 @@ QList<QmlModelStateOperation> QmlModelState::stateOperations() const return returnList; } +QList<QmlModelStateOperation> QmlModelState::allInvalidStateOperations() const +{ + return Utils::filtered(stateOperations(), [](const QmlModelStateOperation &operation) { + return !operation.target().isValid(); + }); +} /*! Adds a change set for \a node to this state, but only if it does not diff --git a/src/plugins/qmldesigner/designercore/model/qmltimelinekeyframegroup.cpp b/src/plugins/qmldesigner/designercore/model/qmltimelinekeyframegroup.cpp index ad0855ad95..85eb51de44 100644 --- a/src/plugins/qmldesigner/designercore/model/qmltimelinekeyframegroup.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmltimelinekeyframegroup.cpp @@ -153,6 +153,13 @@ QmlTimeline QmlTimelineKeyframeGroup::timeline() const return {}; } +bool QmlTimelineKeyframeGroup::isDangling() const +{ + QTC_ASSERT(isValid(), return false); + + return !target().isValid() || keyframes().isEmpty(); +} + void QmlTimelineKeyframeGroup::setValue(const QVariant &value, qreal currentFrame) { QTC_ASSERT(isValid(), return ); @@ -294,6 +301,22 @@ QmlTimelineKeyframeGroup QmlTimelineKeyframeGroup::keyframeGroupForKeyframe(cons return QmlTimelineKeyframeGroup(); } +QList<QmlTimelineKeyframeGroup> QmlTimelineKeyframeGroup::allInvalidTimelineKeyframeGroups(AbstractView *view) +{ + QList<QmlTimelineKeyframeGroup> ret; + + QTC_ASSERT(view, return ret); + QTC_ASSERT(view->model(), return ret); + QTC_ASSERT(view->rootModelNode().isValid(), return ret); + + const auto groups = view->rootModelNode().subModelNodesOfType("QtQuick.Timeline.KeyframeGroup"); + for (const QmlTimelineKeyframeGroup &group : groups) { + if (group.isDangling()) + ret.append(group); + } + return ret; +} + void QmlTimelineKeyframeGroup::moveAllKeyframes(qreal offset) { for (const ModelNode &childNode : modelNode().defaultNodeListProperty().toModelNodeList()) { diff --git a/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp index 802740fc1d..544471b03c 100644 --- a/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/qmlvisualnode.cpp @@ -309,7 +309,11 @@ QmlObjectNode QmlVisualNode::createQmlObjectNode(AbstractView *view, } } - newQmlObjectNode = QmlObjectNode(view->createModelNode(itemLibraryEntry.typeName(), majorVersion, minorVersion, propertyPairList)); + ModelNode::NodeSourceType nodeSourceType = ModelNode::NodeWithoutSource; + if (itemLibraryEntry.typeName() == "QtQml.Component") + nodeSourceType = ModelNode::NodeWithComponentSource; + + newQmlObjectNode = QmlObjectNode(view->createModelNode(itemLibraryEntry.typeName(), majorVersion, minorVersion, propertyPairList, {}, {}, nodeSourceType)); } else { newQmlObjectNode = createQmlObjectNodeFromSource(view, itemLibraryEntry.qmlSource(), position); } diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp index 0209c7a0a7..c678f3b06d 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp @@ -1191,9 +1191,11 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, if (isComponentType(typeName) || isImplicitComponent) setupComponentDelayed(modelNode, differenceHandler.isAmender()); - - if (isCustomParserType(typeName)) + else if (isCustomParserType(typeName)) setupCustomParserNodeDelayed(modelNode, differenceHandler.isAmender()); + else if (!modelNode.nodeSource().isEmpty() || modelNode.nodeSourceType() != ModelNode::NodeWithoutSource) + clearImplicitComponentDelayed(modelNode, differenceHandler.isAmender()); + context->enterScope(astNode); @@ -2079,18 +2081,23 @@ void TextToModelMerger::setupComponent(const ModelNode &node) QString componentText = m_rewriterView->extractText({node}).value(node); - if (componentText.isEmpty()) + if (componentText.isEmpty() && node.nodeSource().isEmpty()) return; QString result = extractComponentFromQml(componentText); - if (result.isEmpty()) + if (result.isEmpty() && node.nodeSource().isEmpty()) return; //No object definition found if (node.nodeSource() != result) ModelNode(node).setNodeSource(result, ModelNode::NodeWithComponentSource); } +void TextToModelMerger::clearImplicitComponent(const ModelNode &node) +{ + ModelNode(node).setNodeSource({}, ModelNode::NodeWithoutSource); +} + void TextToModelMerger::collectLinkErrors(QList<DocumentMessage> *errors, const ReadingContext &ctxt) { foreach (const QmlJS::DiagnosticMessage &diagnosticMessage, ctxt.diagnosticLinkMessages()) { @@ -2237,9 +2244,9 @@ void TextToModelMerger::addIsoIconQrcMapping(const QUrl &fileUrl) } while (dir.cdUp()); } -void TextToModelMerger::setupComponentDelayed(const ModelNode &node, bool synchron) +void TextToModelMerger::setupComponentDelayed(const ModelNode &node, bool synchronous) { - if (synchron) { + if (synchronous) { setupComponent(node); } else { m_setupComponentList.insert(node); @@ -2254,7 +2261,7 @@ void TextToModelMerger::setupCustomParserNode(const ModelNode &node) QString modelText = m_rewriterView->extractText({node}).value(node); - if (modelText.isEmpty()) + if (modelText.isEmpty() && node.nodeSource().isEmpty()) return; if (node.nodeSource() != modelText) @@ -2262,11 +2269,11 @@ void TextToModelMerger::setupCustomParserNode(const ModelNode &node) } -void TextToModelMerger::setupCustomParserNodeDelayed(const ModelNode &node, bool synchron) +void TextToModelMerger::setupCustomParserNodeDelayed(const ModelNode &node, bool synchronous) { Q_ASSERT(isCustomParserType(node.type())); - if (synchron) { + if (synchronous) { setupCustomParserNode(node); } else { m_setupCustomParserList.insert(node); @@ -2274,15 +2281,32 @@ void TextToModelMerger::setupCustomParserNodeDelayed(const ModelNode &node, bool } } +void TextToModelMerger::clearImplicitComponentDelayed(const ModelNode &node, bool synchronous) +{ + Q_ASSERT(!isComponentType(node.type())); + + if (synchronous) { + clearImplicitComponent(node); + } else { + m_clearImplicitComponentList.insert(node); + m_setupTimer.start(); + } +} + void TextToModelMerger::delayedSetup() { - foreach (const ModelNode node, m_setupComponentList) + for (const ModelNode &node : std::as_const(m_setupComponentList)) setupComponent(node); - foreach (const ModelNode node, m_setupCustomParserList) + for (const ModelNode &node : std::as_const(m_setupCustomParserList)) setupCustomParserNode(node); + + for (const ModelNode &node : std::as_const(m_clearImplicitComponentList)) + clearImplicitComponent(node); + m_setupCustomParserList.clear(); m_setupComponentList.clear(); + m_clearImplicitComponentList.clear(); } QSet<QPair<QString, QString> > TextToModelMerger::qrcMapping() const diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.h b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.h index f2e308d02f..ed48d4ebc8 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.h +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.h @@ -128,8 +128,9 @@ public: ReadingContext *context, DifferenceHandler &differenceHandler); - void setupComponentDelayed(const ModelNode &node, bool synchron); - void setupCustomParserNodeDelayed(const ModelNode &node, bool synchron); + void setupComponentDelayed(const ModelNode &node, bool synchronous); + void setupCustomParserNodeDelayed(const ModelNode &node, bool synchronous); + void clearImplicitComponentDelayed(const ModelNode &node, bool synchronous); void delayedSetup(); @@ -140,6 +141,7 @@ public: private: void setupCustomParserNode(const ModelNode &node); void setupComponent(const ModelNode &node); + void clearImplicitComponent(const ModelNode &node); void collectLinkErrors(QList<DocumentMessage> *errors, const ReadingContext &ctxt); void collectImportErrors(QList<DocumentMessage> *errors); void collectSemanticErrorsAndWarnings(QList<DocumentMessage> *errors, @@ -163,6 +165,7 @@ private: QTimer m_setupTimer; QSet<ModelNode> m_setupComponentList; QSet<ModelNode> m_setupCustomParserList; + QSet<ModelNode> m_clearImplicitComponentList; QmlJS::ViewerContext m_vContext; QSet<QPair<QString, QString> > m_qrcMapping; QSet<QmlJS::ImportKey> m_possibleImportKeys; diff --git a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo index bbd91d40a8..019be09973 100644 --- a/src/plugins/qmldesigner/qtquickplugin/quick.metainfo +++ b/src/plugins/qmldesigner/qtquickplugin/quick.metainfo @@ -432,4 +432,51 @@ MetaInfo { } } + Type { + name: "QtQml.Component" + icon: ":/qtquickplugin/images/item-icon16.png" + + Hints { + canBeDroppedInNavigator: true + canBeDroppedInFormEditor: false + } + + ItemLibraryEntry { + name: "Component" + category: "e.Qt Quick - Component" + libraryIcon: ":/qtquickplugin/images/item-icon.png" + version: "2.0" + } + } + + Type { + name: "QtQuick.Loader" + icon: ":/qtquickplugin/images/item-icon16.png" + + ItemLibraryEntry { + name: "Loader" + category: "e.Qt Quick - Component" + libraryIcon: ":/qtquickplugin/images/item-icon.png" + version: "2.0" + Property { name: "width"; type: "int"; value: 200; } + Property { name: "height"; type: "int"; value: 200; } + } + } + + Type { + name: "QtQuick.Repeater" + icon: ":/qtquickplugin/images/item-icon16.png" + + Hints { + canBeDroppedInFormEditor: false + hasFormEditorItem: false + } + + ItemLibraryEntry { + name: "Repeater" + category: "e.Qt Quick - Component" + libraryIcon: ":/qtquickplugin/images/item-icon.png" + version: "2.0" + } + } } diff --git a/src/plugins/qmljseditor/qmljseditorplugin.cpp b/src/plugins/qmljseditor/qmljseditorplugin.cpp index e0d5703102..59cafdcfc1 100644 --- a/src/plugins/qmljseditor/qmljseditorplugin.cpp +++ b/src/plugins/qmljseditor/qmljseditorplugin.cpp @@ -110,6 +110,7 @@ QmlJSEditorPlugin::QmlJSEditorPlugin() QmlJSEditorPlugin::~QmlJSEditorPlugin() { + delete QmlJS::Icons::instance(); // delete object held by singleton delete d; d = nullptr; m_instance = nullptr; @@ -226,8 +227,6 @@ void QmlJSEditorPlugin::extensionsInitialized() ExtensionSystem::IPlugin::ShutdownFlag QmlJSEditorPlugin::aboutToShutdown() { - delete QmlJS::Icons::instance(); // delete object held by singleton - return IPlugin::aboutToShutdown(); } diff --git a/src/plugins/texteditor/basefilefind.cpp b/src/plugins/texteditor/basefilefind.cpp index abe8777b34..f0fe19cc34 100644 --- a/src/plugins/texteditor/basefilefind.cpp +++ b/src/plugins/texteditor/basefilefind.cpp @@ -236,7 +236,7 @@ static QString displayText(const QString &line) QString result = line; auto end = result.end(); for (auto it = result.begin(); it != end; ++it) { - if (!it->isPrint()) + if (!it->isSpace() && !it->isPrint()) *it = QChar('?'); } return result; diff --git a/src/plugins/valgrind/memcheckerrorview.cpp b/src/plugins/valgrind/memcheckerrorview.cpp index 0e45b76d43..19e3199b72 100644 --- a/src/plugins/valgrind/memcheckerrorview.cpp +++ b/src/plugins/valgrind/memcheckerrorview.cpp @@ -46,6 +46,7 @@ #include <QAction> +using namespace Utils; using namespace Valgrind::XmlProtocol; namespace Valgrind { @@ -56,10 +57,10 @@ MemcheckErrorView::MemcheckErrorView(QWidget *parent) { m_suppressAction = new QAction(this); m_suppressAction->setText(tr("Suppress Error")); - const QIcon icon = Utils::Icon({ - {":/utils/images/eye_open.png", Utils::Theme::TextColorNormal}, - {":/valgrind/images/suppressoverlay.png", Utils::Theme::IconsErrorColor}}, - Utils::Icon::Tint | Utils::Icon::PunchEdges).icon(); + const QIcon icon = Icon({ + {":/utils/images/eye_open.png", Theme::TextColorNormal}, + {":/valgrind/images/suppressoverlay.png", Theme::IconsErrorColor}}, + Icon::Tint | Icon::PunchEdges).icon(); m_suppressAction->setIcon(icon); m_suppressAction->setShortcuts({QKeySequence::Delete, QKeySequence::Backspace}); m_suppressAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); @@ -69,12 +70,12 @@ MemcheckErrorView::MemcheckErrorView(QWidget *parent) MemcheckErrorView::~MemcheckErrorView() = default; -void MemcheckErrorView::setDefaultSuppressionFile(const QString &suppFile) +void MemcheckErrorView::setDefaultSuppressionFile(const FilePath &suppFile) { m_defaultSuppFile = suppFile; } -QString MemcheckErrorView::defaultSuppressionFile() const +FilePath MemcheckErrorView::defaultSuppressionFile() const { return m_defaultSuppFile; } diff --git a/src/plugins/valgrind/memcheckerrorview.h b/src/plugins/valgrind/memcheckerrorview.h index 6a22dbf907..888140fc45 100644 --- a/src/plugins/valgrind/memcheckerrorview.h +++ b/src/plugins/valgrind/memcheckerrorview.h @@ -28,6 +28,8 @@ #include <debugger/analyzer/detailederrorview.h> +#include <utils/filepath.h> + #include <QListView> namespace Valgrind { @@ -43,8 +45,8 @@ public: MemcheckErrorView(QWidget *parent = nullptr); ~MemcheckErrorView() override; - void setDefaultSuppressionFile(const QString &suppFile); - QString defaultSuppressionFile() const; + void setDefaultSuppressionFile(const Utils::FilePath &suppFile); + Utils::FilePath defaultSuppressionFile() const; ValgrindBaseSettings *settings() const { return m_settings; } void settingsChanged(ValgrindBaseSettings *settings); @@ -53,7 +55,7 @@ private: QList<QAction *> customActions() const override; QAction *m_suppressAction; - QString m_defaultSuppFile; + Utils::FilePath m_defaultSuppFile; ValgrindBaseSettings *m_settings = nullptr; }; diff --git a/src/plugins/valgrind/memchecktool.cpp b/src/plugins/valgrind/memchecktool.cpp index 95bfb7e756..ff97b153b6 100644 --- a/src/plugins/valgrind/memchecktool.cpp +++ b/src/plugins/valgrind/memchecktool.cpp @@ -128,7 +128,7 @@ public: void start() override; void stop() override; - const QStringList suppressionFiles() const; + const Utils::FilePaths suppressionFiles() const; signals: void internalParserError(const QString &errorString); @@ -212,8 +212,8 @@ QStringList MemcheckToolRunner::toolArguments() const } arguments << "--leak-check=" + leakCheckValue; - for (const QString &file : m_settings.suppressions.value()) - arguments << QString("--suppressions=%1").arg(file); + for (const FilePath &file : m_settings.suppressions.value()) + arguments << QString("--suppressions=%1").arg(file.path()); arguments << QString("--num-callers=%1").arg(m_settings.numCallers.value()); @@ -225,7 +225,7 @@ QStringList MemcheckToolRunner::toolArguments() const return arguments; } -const QStringList MemcheckToolRunner::suppressionFiles() const +const FilePaths MemcheckToolRunner::suppressionFiles() const { return m_settings.suppressions.value(); } @@ -991,15 +991,15 @@ void MemcheckToolPrivate::setupRunner(MemcheckToolRunner *runTool) clearErrorView(); m_loadExternalLogFile->setDisabled(true); - QString dir = runControl->project()->projectDirectory().toString() + '/'; + const FilePath dir = runControl->project()->projectDirectory(); const QString name = runTool->executable().fileName(); - m_errorView->setDefaultSuppressionFile(dir + name + ".supp"); + m_errorView->setDefaultSuppressionFile(dir.pathAppended(name + ".supp")); - const QStringList suppressionFiles = runTool->suppressionFiles(); - for (const QString &file : suppressionFiles) { - QAction *action = m_filterMenu->addAction(FilePath::fromString(file).fileName()); - action->setToolTip(file); + const FilePaths suppressionFiles = runTool->suppressionFiles(); + for (const FilePath &file : suppressionFiles) { + QAction *action = m_filterMenu->addAction(file.fileName()); + action->setToolTip(file.toUserOutput()); connect(action, &QAction::triggered, this, [file] { EditorManager::openEditorAt(file, 0); }); @@ -1425,13 +1425,13 @@ void HeobDialog::updateProfile() int leakRecording = settings->value(heobLeakRecordingC, 2).toInt(); bool attach = settings->value(heobAttachC, false).toBool(); const QString extraArgs = settings->value(heobExtraArgsC).toString(); - QString path = settings->value(heobPathC).toString(); + FilePath path = FilePath::fromVariant(settings->value(heobPathC)); settings->endGroup(); if (path.isEmpty()) { const QString heobPath = QStandardPaths::findExecutable("heob32.exe"); if (!heobPath.isEmpty()) - path = QFileInfo(heobPath).path(); + path = FilePath::fromUserInput(heobPath); } m_xmlEdit->setText(xml); @@ -1444,7 +1444,7 @@ void HeobDialog::updateProfile() m_leakRecordingCombo->setCurrentIndex(leakRecording); m_attachCheck->setChecked(attach); m_extraArgsEdit->setText(extraArgs); - m_pathChooser->setPath(path); + m_pathChooser->setFilePath(path); } void HeobDialog::updateEnabled() diff --git a/src/plugins/valgrind/suppressiondialog.cpp b/src/plugins/valgrind/suppressiondialog.cpp index 4f5b427f09..bde173edcc 100644 --- a/src/plugins/valgrind/suppressiondialog.cpp +++ b/src/plugins/valgrind/suppressiondialog.cpp @@ -51,6 +51,7 @@ #include <QPlainTextEdit> #include <QPushButton> +using namespace Utils; using namespace Valgrind::XmlProtocol; namespace Valgrind { @@ -63,7 +64,7 @@ static QString suppressionText(const Error &error) // workaround: https://bugs.kde.org/show_bug.cgi?id=255822 if (sup.frames().size() >= 24) sup.setFrames(sup.frames().mid(0, 23)); - QTC_ASSERT(sup.frames().size() < 24, /**/); + QTC_CHECK(sup.frames().size() < 24); // try to set some useful name automatically, instead of "insert_name_here" // we take the last stack frame and append the suppression kind, e.g.: @@ -117,7 +118,7 @@ SuppressionDialog::SuppressionDialog(MemcheckErrorView *view, const QList<Error> m_settings(view->settings()), m_cleanupIfCanceled(false), m_errors(errors), - m_fileChooser(new Utils::PathChooser(this)), + m_fileChooser(new PathChooser(this)), m_suppressionEdit(new QPlainTextEdit(this)) { setWindowTitle(tr("Save Suppression")); @@ -140,27 +141,23 @@ SuppressionDialog::SuppressionDialog(MemcheckErrorView *view, const QList<Error> formLayout->addRow(m_suppressionEdit); formLayout->addRow(m_buttonBox); - QFile defaultSuppFile(view->defaultSuppressionFile()); - if (!defaultSuppFile.exists()) { - if (defaultSuppFile.open(QIODevice::WriteOnly)) { - defaultSuppFile.close(); - m_cleanupIfCanceled = true; - } - } + const FilePath defaultSuppFile = view->defaultSuppressionFile(); + if (!defaultSuppFile.exists() && defaultSuppFile.ensureExistingFile()) + m_cleanupIfCanceled = true; - m_fileChooser->setExpectedKind(Utils::PathChooser::File); + m_fileChooser->setExpectedKind(PathChooser::File); m_fileChooser->setHistoryCompleter("Valgrind.Suppression.History"); m_fileChooser->setPath(defaultSuppFile.fileName()); m_fileChooser->setPromptDialogFilter("*.supp"); m_fileChooser->setPromptDialogTitle(tr("Select Suppression File")); QString suppressions; - foreach (const Error &error, m_errors) + for (const Error &error : qAsConst(m_errors)) suppressions += suppressionText(error); m_suppressionEdit->setPlainText(suppressions); - connect(m_fileChooser, &Utils::PathChooser::validChanged, + connect(m_fileChooser, &PathChooser::validChanged, this, &SuppressionDialog::validate); connect(m_suppressionEdit->document(), &QTextDocument::contentsChanged, this, &SuppressionDialog::validate); @@ -178,7 +175,7 @@ void SuppressionDialog::maybeShow(MemcheckErrorView *view) indices.append(view->selectionModel()->currentIndex()); QList<XmlProtocol::Error> errors; - foreach (const QModelIndex &index, indices) { + for (const QModelIndex &index : qAsConst(indices)) { Error error = view->model()->data(index, ErrorListModel::ErrorRole).value<Error>(); if (!error.suppression().isNull()) errors.append(error); @@ -193,11 +190,11 @@ void SuppressionDialog::maybeShow(MemcheckErrorView *view) void SuppressionDialog::accept() { - const Utils::FilePath path = m_fileChooser->filePath(); + const FilePath path = m_fileChooser->filePath(); QTC_ASSERT(!path.isEmpty(), return); QTC_ASSERT(!m_suppressionEdit->toPlainText().trimmed().isEmpty(), return); - Utils::FileSaver saver(path, QIODevice::Append); + FileSaver saver(path, QIODevice::Append); if (!saver.hasError()) { QTextStream stream(saver.file()); stream << m_suppressionEdit->toPlainText(); @@ -216,14 +213,14 @@ void SuppressionDialog::accept() } } - m_settings->suppressions.addSuppressionFile(path.toString()); + m_settings->suppressions.addSuppressionFile(path); QModelIndexList indices = m_view->selectionModel()->selectedRows(); Utils::sort(indices, [](const QModelIndex &l, const QModelIndex &r) { return l.row() > r.row(); }); QAbstractItemModel *model = m_view->model(); - foreach (const QModelIndex &index, indices) { + for (const QModelIndex &index : qAsConst(indices)) { bool removed = model->removeRow(index.row()); QTC_ASSERT(removed, qt_noop()); Q_UNUSED(removed) @@ -234,7 +231,7 @@ void SuppressionDialog::accept() const Error rowError = model->data( model->index(row, 0), ErrorListModel::ErrorRole).value<Error>(); - foreach (const Error &error, m_errors) { + for (const Error &error : qAsConst(m_errors)) { if (equalSuppression(rowError, error)) { bool removed = model->removeRow(row); QTC_CHECK(removed); @@ -254,7 +251,7 @@ void SuppressionDialog::accept() void SuppressionDialog::reject() { if (m_cleanupIfCanceled) - QFile::remove(m_view->defaultSuppressionFile()); + m_view->defaultSuppressionFile().removeFile(); QDialog::reject(); } diff --git a/src/plugins/valgrind/valgrindsettings.cpp b/src/plugins/valgrind/valgrindsettings.cpp index 352ad060ef..21d056e3fd 100644 --- a/src/plugins/valgrind/valgrindsettings.cpp +++ b/src/plugins/valgrind/valgrindsettings.cpp @@ -73,9 +73,9 @@ public: QStandardItemModel m_model; // The volatile value of this aspect. }; -void SuppressionAspect::addSuppressionFile(const QString &suppression) +void SuppressionAspect::addSuppressionFile(const FilePath &suppression) { - QStringList val = value(); + FilePaths val = value(); val.append(suppression); setValue(val); } @@ -141,14 +141,14 @@ SuppressionAspect::~SuppressionAspect() delete d; } -QStringList SuppressionAspect::value() const +FilePaths SuppressionAspect::value() const { - return BaseAspect::value().toStringList(); + return Utils::transform(BaseAspect::value().toStringList(), &FilePath::fromString); } -void SuppressionAspect::setValue(const QStringList &val) +void SuppressionAspect::setValue(const FilePaths &val) { - BaseAspect::setValue(val); + BaseAspect::setValue(Utils::transform<QStringList>(val, &FilePath::toString)); } void SuppressionAspect::addToLayout(LayoutBuilder &builder) @@ -180,7 +180,7 @@ void SuppressionAspect::addToLayout(LayoutBuilder &builder) }; builder.addItem(Span { 2, group }); - setVolatileValue(value()); + setVolatileValue(BaseAspect::value()); } void SuppressionAspect::fromMap(const QVariantMap &map) diff --git a/src/plugins/valgrind/valgrindsettings.h b/src/plugins/valgrind/valgrindsettings.h index 79fc68136e..7d4f01f79e 100644 --- a/src/plugins/valgrind/valgrindsettings.h +++ b/src/plugins/valgrind/valgrindsettings.h @@ -46,8 +46,8 @@ public: explicit SuppressionAspect(bool global); ~SuppressionAspect() final; - QStringList value() const; - void setValue(const QStringList &val); + Utils::FilePaths value() const; + void setValue(const Utils::FilePaths &val); void addToLayout(Utils::LayoutBuilder &builder) final; @@ -57,7 +57,7 @@ public: QVariant volatileValue() const final; void setVolatileValue(const QVariant &val) final; - void addSuppressionFile(const QString &suppressionFile); + void addSuppressionFile(const Utils::FilePath &suppressionFile); private: friend class ValgrindBaseSettings; diff --git a/src/shared/proparser/qmakebuiltins.cpp b/src/shared/proparser/qmakebuiltins.cpp index a697133896..c47bc1b2e7 100644 --- a/src/shared/proparser/qmakebuiltins.cpp +++ b/src/shared/proparser/qmakebuiltins.cpp @@ -474,12 +474,8 @@ void QMakeEvaluator::runProcess(QProcess *proc, const QString &command) const } # endif # ifdef PROEVALUATOR_THREAD_SAFE - m_option->mutex.lock(); - if (m_option->canceled) { - m_option->mutex.unlock(); + if (m_option->canceled) return; - } - m_option->runningProcs << proc; # endif # ifdef Q_OS_WIN proc->setNativeArguments(QLatin1String("/v:off /s /c \"") + command + QLatin1Char('"')); @@ -488,12 +484,21 @@ void QMakeEvaluator::runProcess(QProcess *proc, const QString &command) const proc->start(QLatin1String("/bin/sh"), QStringList() << QLatin1String("-c") << command); # endif # ifdef PROEVALUATOR_THREAD_SAFE - m_option->mutex.unlock(); -# endif - proc->waitForFinished(-1); -# ifdef PROEVALUATOR_THREAD_SAFE - QMutexLocker(&m_option->mutex); - m_option->runningProcs.removeOne(proc); + while (true) { + if (proc->waitForFinished(100)) + break; + if (m_option->canceled) { + proc->terminate(); + if (proc->waitForFinished(1000)) + break; + proc->kill(); + proc->waitForFinished(1000); + break; + } + } +# else + proc->waitForFinished(-1); // If have have single thread we can't cancel it using + // synchronous API of QProcess # endif } #endif diff --git a/src/shared/proparser/qmakeglobals.cpp b/src/shared/proparser/qmakeglobals.cpp index ea429145e3..ca22bdde8b 100644 --- a/src/shared/proparser/qmakeglobals.cpp +++ b/src/shared/proparser/qmakeglobals.cpp @@ -92,11 +92,7 @@ QMakeGlobals::~QMakeGlobals() void QMakeGlobals::killProcesses() { #ifdef PROEVALUATOR_THREAD_SAFE - QMutexLocker lock(&mutex); canceled = true; - for (QProcess * const proc : runningProcs) - proc->kill(); - runningProcs.clear(); #endif } diff --git a/src/shared/proparser/qmakeglobals.h b/src/shared/proparser/qmakeglobals.h index 2fa9a33213..2baeab4bf5 100644 --- a/src/shared/proparser/qmakeglobals.h +++ b/src/shared/proparser/qmakeglobals.h @@ -159,8 +159,7 @@ private: #ifdef PROEVALUATOR_THREAD_SAFE QMutex mutex; - bool canceled = false; - QList<QProcess *> runningProcs; + std::atomic_bool canceled = false; #endif QHash<QMakeBaseKey, QMakeBaseEnv *> baseEnvs; diff --git a/src/shared/qbs b/src/shared/qbs -Subproject 4592eff289852e3e5a81596d1cc6b0c2488e6bc +Subproject a35ff56175cc8c993b54bb1d92ff71ba4532fc8 diff --git a/src/shared/shared.pro b/src/shared/shared.pro index b4c64a02fb..4e4c860c82 100644 --- a/src/shared/shared.pro +++ b/src/shared/shared.pro @@ -6,9 +6,12 @@ QBS_DIRS = \ qbslibexec \ qbsmsbuildlib \ qbsplugins \ - qbsstatic + qbsstatic \ + qbspkgconfig +qbspkgconfig.subdir = qbs/src/lib/pkgconfig qbscorelib.subdir = qbs/src/lib/corelib +qbscorelib.depends = qbspkgconfig qbsapps.subdir = qbs/src/app qbsapps.depends = qbscorelib qbslibexec.subdir = qbs/src/libexec diff --git a/src/tools/qml2puppet/qml2puppet.qbs b/src/tools/qml2puppet/qml2puppet.qbs index 7eff7ba02b..83acc77ba3 100644 --- a/src/tools/qml2puppet/qml2puppet.qbs +++ b/src/tools/qml2puppet/qml2puppet.qbs @@ -16,6 +16,7 @@ QtcTool { ] } Depends { name: "Qt.quick3d-private"; required: false } + Depends { name: "Qt.quick3dparticles-private"; required: false } property bool useQuick3d: Utilities.versionCompare(Qt.core.version, "5.15") >= 0 && Qt["quick3d-private"].present property bool useParticle3d: Utilities.versionCompare(Qt.core.version, "6.2") >= 0 |