From 73c40bcefa9832f7b2f84a02a7bdc6e39cd309cb Mon Sep 17 00:00:00 2001 From: Fawzi Mohamed Date: Thu, 24 Jun 2021 17:56:13 +0200 Subject: [PATCH 001/149] qmljstools::LocatorData: improve safety * check that we create LocatorData in the same thread as the ModelManagerInterface * pass this as connect context, to ensure signal disconnect Change-Id: I5e51af90c521fd8c83a6cfe2d105832f5a02a04f Reviewed-by: Jarek Kobus (cherry picked from commit 584f0476eca8676abc652d8e907b5e10c08da787) Reviewed-by: Fabian Kosmale --- src/plugins/qmljstools/qmljslocatordata.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/plugins/qmljstools/qmljslocatordata.cpp b/src/plugins/qmljstools/qmljslocatordata.cpp index 39ef4f4de5a..92611a99a64 100644 --- a/src/plugins/qmljstools/qmljslocatordata.cpp +++ b/src/plugins/qmljstools/qmljslocatordata.cpp @@ -43,6 +43,7 @@ using namespace QmlJS::AST; LocatorData::LocatorData() { ModelManagerInterface *manager = ModelManagerInterface::instance(); + Q_ASSERT(thread() == manager->thread()); // we do not protect accesses below // Force the updating of source file when updating a project (they could be cached, in such // case LocatorData::onDocumentUpdated will not be called. @@ -61,8 +62,10 @@ LocatorData::LocatorData() ProjectExplorer::SessionManager *session = ProjectExplorer::SessionManager::instance(); if (session) - connect(session, &ProjectExplorer::SessionManager::projectRemoved, - [this] (ProjectExplorer::Project*) { m_entries.clear(); }); + connect(session, + &ProjectExplorer::SessionManager::projectRemoved, + this, + [this](ProjectExplorer::Project *) { m_entries.clear(); }); } LocatorData::~LocatorData() = default; From 09e2e23797e39c65f9bd47216ad26ee5eb4f8ff6 Mon Sep 17 00:00:00 2001 From: Johanna Vanhatapio Date: Thu, 24 Jun 2021 14:15:17 +0300 Subject: [PATCH 002/149] Doc: Add info about multiselection ... in 3D Editor. Also add info and links about hiding/showing and locking components in Navigator. Task-number: QDS-4626 Change-Id: I1f8713a9feed19029d9dfc6bb5064e2b936e2333 Reviewed-by: Leena Miettinen --- .../qtquick3d-editor/qtdesignstudio-3d-editor.qdoc | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc index a7ac4612629..68f45d80bec 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc @@ -55,7 +55,10 @@ \uicontrol Properties to transform the component around a point other than its local origin. A line is drawn in \uicontrol {3D Editor} from the pivot point to the center of the component to provide a visual connection between - them. + them. Especially when working with complex scenes, it may be useful to use + the \l {Showing and Hiding Components}{showing and hiding} or the + \l {Locking Components}{locking} features in \l Navigator to avoid + transforming components by mistake while editing your scene. Toggle between local and global orientation to determine whether the gizmos affect only the local transformations of the component or whether they @@ -170,6 +173,12 @@ To toggle the selection mode, press \key Q. + To multiselect, hold \key Ctrl and click the components you wish to select. + + After selecting a component, you can apply the usual \l {Keyboard Shortcuts} + {keyboard shortcuts} applicable to your operating system, for example, + \key Ctrl+C and \key Ctrl+V on Windows to copy-paste components. + \section1 Moving Components \image studio-3d-editor-move.png "3D Editor in move mode" From 20366297ed961a4726e017c43f1c366e4edcb31e Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Sat, 26 Jun 2021 16:20:42 +0200 Subject: [PATCH 003/149] CMakePM: Keep always created run configurations up to date Qt Creator will do an update of run configurations after a project configuration. If an always created target was no longer existing, it shouldn't be part of the run configuration. For some reason this was not the case for CMake projects. With this patchset if a target's name is changed, the previous target name is no longer part of the run configuration. Fixes: QTCREATORBUG-25906 Fixes: QTCREATORBUG-24914 Change-Id: I086a2540eaad9039e41fb48194d5901c7be22be8 Reviewed-by: Eike Ziller --- src/plugins/cmakeprojectmanager/cmakeproject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp index 9a5737064e0..3e7bdb67a02 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp @@ -63,7 +63,7 @@ CMakeProject::CMakeProject(const FilePath &fileName) setProjectLanguages(Core::Context(ProjectExplorer::Constants::CXX_LANGUAGE_ID)); setDisplayName(projectDirectory().fileName()); setCanBuildProducts(); - setKnowsAllBuildExecutables(false); + setKnowsAllBuildExecutables(true); setHasMakeInstallEquivalent(true); } From fc493c3fcb5a09b1cb5dfe09e44c6029c7b850fd Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Fri, 25 Jun 2021 19:17:05 +0200 Subject: [PATCH 004/149] CMakePM: Resolve paths to build directories With the default build directory template the CMake build directories will be displayed as project-source/../build-project-name-kit-build-type which can be irritating. With this commit the build directory will contain the resolved path. Change-Id: I968260caba5b9a47e1bda4eeaea386a04fe817b1 Reviewed-by: hjk Reviewed-by: Alessandro Portale Reviewed-by: Eike Ziller --- src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp index a98fded999e..81b98dd2058 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp @@ -1044,14 +1044,14 @@ FilePath CMakeBuildConfiguration::shadowBuildDirectory(const FilePath &projectFi const QString projectName = projectFilePath.parentDir().fileName(); ProjectMacroExpander expander(projectFilePath, projectName, k, bcName, buildType); - QDir projectDir = QDir(Project::projectDirectory(projectFilePath).toString()); + const FilePath projectDir = Project::projectDirectory(projectFilePath); QString buildPath = expander.expand(ProjectExplorerPlugin::buildDirectoryTemplate()); buildPath.replace(" ", "-"); if (CMakeGeneratorKitAspect::isMultiConfigGenerator(k)) buildPath = buildPath.left(buildPath.lastIndexOf(QString("-%1").arg(bcName))); - return FilePath::fromUserInput(projectDir.absoluteFilePath(buildPath)); + return projectDir.resolvePath(buildPath); } void CMakeBuildConfiguration::buildTarget(const QString &buildTarget) From 3340fa88e41b1ce764ff792f9272b4c462cd9bf2 Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Fri, 25 Jun 2021 18:10:45 +0200 Subject: [PATCH 005/149] CMakePM: Proper restore user saved CMake tools changes If you had an auto detected CMake tool from the sdk you would not be able to change the "Autorun CMake" state from ON to OFF because the sdk value would always be used instead of the user one. Fixes: QTCREATORBUG-25911 Change-Id: Ibe3b393a2b9e4d397251018fda8a9508ad096791 Reviewed-by: Qt CI Bot Reviewed-by: Eike Ziller --- .../cmakeprojectmanager/cmaketoolsettingsaccessor.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/plugins/cmakeprojectmanager/cmaketoolsettingsaccessor.cpp b/src/plugins/cmakeprojectmanager/cmaketoolsettingsaccessor.cpp index 0ebfb1fb365..b1dde077b67 100644 --- a/src/plugins/cmakeprojectmanager/cmaketoolsettingsaccessor.cpp +++ b/src/plugins/cmakeprojectmanager/cmaketoolsettingsaccessor.cpp @@ -128,7 +128,11 @@ mergeTools(std::vector> &sdkTools, std::unique_ptr userTool = std::move(userTools[0]); userTools.erase(std::begin(userTools)); - if (!Utils::contains(result, Utils::equal(&CMakeTool::id, userTool->id()))) { + int userToolIndex = Utils::indexOf(result, Utils::equal(&CMakeTool::id, userTool->id())); + if (userToolIndex >= 0) { + // Replace the sdk tool with the user tool, so any user changes do not get lost + result[userToolIndex] = std::move(userTool); + } else { if (userTool->isAutoDetected() && !Utils::contains(autoDetectedTools, Utils::equal(&CMakeTool::cmakeExecutable, userTool->cmakeExecutable()))) { From 03712d6a92fd8e590c989bfc59d8c6035704fc1a Mon Sep 17 00:00:00 2001 From: Assam Boudjelthia Date: Tue, 20 Apr 2021 15:00:00 +0300 Subject: [PATCH 006/149] Android: remove app lib_name field from the manifest editor UI This field is supposed to hold the name for the *.so lib that contains the main() function, ideally it shouldn't be edited by the user, because some users might use the field to set an invalid name or use the app's human readable name which is wrong. Change-Id: Ie7feb79d6231d1785c29754ed277e057181e9ca0 Reviewed-by: Alessandro Portale --- .../android/androidmanifesteditorwidget.cpp | 62 +------------------ .../android/androidmanifesteditorwidget.h | 4 -- 2 files changed, 2 insertions(+), 64 deletions(-) diff --git a/src/plugins/android/androidmanifesteditorwidget.cpp b/src/plugins/android/androidmanifesteditorwidget.cpp index 159fd00f9e6..db74a953970 100644 --- a/src/plugins/android/androidmanifesteditorwidget.cpp +++ b/src/plugins/android/androidmanifesteditorwidget.cpp @@ -404,12 +404,6 @@ QGroupBox *Android::Internal::AndroidManifestEditorWidget::createApplicationGrou m_activityNameLineEdit = new QLineEdit(applicationGroupBox); formLayout->addRow(tr("Activity name:"), m_activityNameLineEdit); - m_targetLineEdit = new QComboBox(applicationGroupBox); - m_targetLineEdit->setEditable(true); - m_targetLineEdit->setDuplicatesEnabled(true); - m_targetLineEdit->installEventFilter(this); - formLayout->addRow(tr("Run:"), m_targetLineEdit); - m_styleExtractMethod = new QComboBox(applicationGroupBox); formLayout->addRow(tr("Style extraction:"), m_styleExtractMethod); const QList styleMethodsMap = { @@ -467,8 +461,6 @@ QGroupBox *Android::Internal::AndroidManifestEditorWidget::createApplicationGrou this, [this]() { setDirty(); }); connect(m_activityNameLineEdit, &QLineEdit::textEdited, this, [this]() { setDirty(); }); - connect(m_targetLineEdit, &QComboBox::currentTextChanged, - this, [this]() { setDirty(); }); connect(m_styleExtractMethod, QOverload::of(&QComboBox::currentIndexChanged), this, [this]() { setDirty(); }); @@ -532,16 +524,6 @@ void AndroidManifestEditorWidget::initializePage() insertWidget(Source, m_textEditorWidget); } -bool AndroidManifestEditorWidget::eventFilter(QObject *obj, QEvent *event) -{ - if (obj == m_targetLineEdit) { - if (event->type() == QEvent::FocusIn) - QTimer::singleShot(0, this, &AndroidManifestEditorWidget::updateTargetComboBox); - } - - return QWidget::eventFilter(obj, event); -} - void AndroidManifestEditorWidget::focusInEvent(QFocusEvent *event) { if (currentWidget()) { @@ -552,30 +534,6 @@ void AndroidManifestEditorWidget::focusInEvent(QFocusEvent *event) } } -void AndroidManifestEditorWidget::updateTargetComboBox() -{ - QStringList items; - if (Target *target = androidTarget(m_textEditorWidget->textDocument()->filePath())) { - ProjectNode *root = target->project()->rootProjectNode(); - root->forEachProjectNode([&items](const ProjectNode *projectNode) { - items << projectNode->targetApplications(); - }); - items.sort(); - } - - // QComboBox randomly resets what the user has entered - // if all rows are removed, thus we ensure that the current text - // is not removed by first adding it and then removing all old rows - // and then adding the new rows - QString text = m_targetLineEdit->currentText(); - m_targetLineEdit->addItem(text); - while (m_targetLineEdit->count() > 1) - m_targetLineEdit->removeItem(0); - items.removeDuplicates(); - items.removeAll(text); - m_targetLineEdit->addItems(items); -} - void AndroidManifestEditorWidget::updateAfterFileLoad() { QString error; @@ -877,11 +835,7 @@ void AndroidManifestEditorWidget::syncToWidgets(const QDomDocument &doc) enum SplashImageParseGuard {splashNone = 0, splash = 1, portraitSplash = 2, landscapeSplash = 4, splashDone = 8}; int splashParseGuard = SplashImageParseGuard::splashNone; while (!metadataElem.isNull()) { - if (metadataElem.attribute(QLatin1String("android:name")) == QLatin1String("android.app.lib_name") - && !(activityParseGuard & ActivityParseGuard::libName)) { - m_targetLineEdit->setEditText(metadataElem.attribute(QLatin1String("android:value"))); - activityParseGuard |= ActivityParseGuard::libName; - } else if (metadataElem.attribute(QLatin1String("android:name")) + if (metadataElem.attribute(QLatin1String("android:name")) == QLatin1String("android.app.extract_android_style") && !(activityParseGuard & ActivityParseGuard::styleExtract)) { m_styleExtractMethod->setCurrentText( @@ -1392,13 +1346,6 @@ void AndroidManifestEditorWidget::parseActivity(QXmlStreamReader &reader, QXmlSt while (!reader.atEnd()) { if (reader.isEndElement()) { parseSplashScreen(writer); - if (!found) { - writer.writeEmptyElement(QLatin1String("meta-data")); - writer.writeAttribute(QLatin1String("android:name"), - QLatin1String("android.app.lib_name")); - writer.writeAttribute(QLatin1String("android:value"), - m_targetLineEdit->currentText()); - } writer.writeCurrentToken(reader); return; } else if (reader.isStartElement()) { @@ -1429,12 +1376,7 @@ bool AndroidManifestEditorWidget::parseMetaData(QXmlStreamReader &reader, QXmlSt QStringList keys; QStringList values; - if (attributes.value(QLatin1String("android:name")) == QLatin1String("android.app.lib_name")) { - keys = QStringList("android:value"); - values = QStringList(m_targetLineEdit->currentText()); - result = modifyXmlStreamAttributes(attributes, keys, values); - found = true; - } else if (attributes.value(QLatin1String("android:name")) + if (attributes.value(QLatin1String("android:name")) == QLatin1String("android.app.extract_android_style")) { keys = QStringList("android:value"); values = QStringList(m_styleExtractMethod->currentText()); diff --git a/src/plugins/android/androidmanifesteditorwidget.h b/src/plugins/android/androidmanifesteditorwidget.h index 23f97fd0000..1dd134b08c2 100644 --- a/src/plugins/android/androidmanifesteditorwidget.h +++ b/src/plugins/android/androidmanifesteditorwidget.h @@ -115,7 +115,6 @@ signals: void guiChanged(); protected: - bool eventFilter(QObject *obj, QEvent *event) override; void focusInEvent(QFocusEvent *event) override; private: @@ -142,8 +141,6 @@ private: void setInvalidServiceInfo(); void clearInvalidServiceInfo(); - void updateTargetComboBox(); - void parseManifest(QXmlStreamReader &reader, QXmlStreamWriter &writer); void parseApplication(QXmlStreamReader &reader, QXmlStreamWriter &writer); void parseSplashScreen(QXmlStreamWriter &writer); @@ -181,7 +178,6 @@ private: // Application QLineEdit *m_appNameLineEdit; QLineEdit *m_activityNameLineEdit; - QComboBox *m_targetLineEdit; QComboBox *m_styleExtractMethod; QComboBox *m_screenOrientation; AndroidManifestEditorIconContainerWidget *m_iconButtons; From f9a8e47d13b7fec4f26c9f641271843f9ca65394 Mon Sep 17 00:00:00 2001 From: Assam Boudjelthia Date: Tue, 25 May 2021 19:34:25 +0300 Subject: [PATCH 007/149] Android: Copy the target lib to android-build as part of the apk step Make sure to copy the target's main lib file before building the APK. If the lib file is already there, i.e. copied by the underlying build system, this does nothing, but if the file is not copied by default like in cmake with Qt 6, this would copy it and would save us having to add *_prepare_apk_dir in cmake command. Also, this could allow us to remove the step "make install" from qmake step settings. After this we could revert 9dcbb8ca01e0981b6a3c7ea8dd278014343f48e3. Fixes: QTCREATORBUG-25367 Fixes: QTCREATORBUG-25216 Change-Id: I243a16a32e2ea97e175c893470480c9d2c9b1e27 Reviewed-by: Alessandro Portale --- src/plugins/android/androidbuildapkstep.cpp | 46 ++++++++++++++++----- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/src/plugins/android/androidbuildapkstep.cpp b/src/plugins/android/androidbuildapkstep.cpp index d149b2033ca..1e895311724 100644 --- a/src/plugins/android/androidbuildapkstep.cpp +++ b/src/plugins/android/androidbuildapkstep.cpp @@ -748,28 +748,54 @@ void AndroidBuildApkStep::doRun() auto setup = [this] { const auto androidAbis = AndroidManager::applicationAbis(target()); + const QString buildKey = target()->activeBuildKey(); + + QtSupport::BaseQtVersion *version = QtSupport::QtKitAspect::qtVersion(kit()); + if (!version) + return false; + for (const auto &abi : androidAbis) { FilePath androidLibsDir = buildDirectory() / "android-build/libs" / abi; - if (!androidLibsDir.exists() && !QDir{buildDirectory().toString()}.mkpath(androidLibsDir.toString())) - return false; - } + if (!androidLibsDir.exists()) { + if (!QDir{buildDirectory().toString()}.mkpath(androidLibsDir.toString())) { + const QString error = tr("The Android build folder %1 wasn't found and " + "couldn't be created.").arg(androidLibsDir.toString()); + emit addOutput(error, BuildStep::OutputFormat::ErrorMessage); + TaskHub::addTask(BuildSystemTask(Task::Error, error)); + return false; + } else if (version->qtVersion() >= QtSupport::QtVersionNumber{6, 0, 0} + && version->qtVersion() <= QtSupport::QtVersionNumber{6, 1, 1}) { + // 6.0.x <= Qt <= 6.1.1 used to need a manaul call to _prepare_apk_dir target, + // and now it's made directly with ALL target, so this code below ensures + // these versions are not broken. + const QString fileName = QString("lib%1_%2.so").arg(buildKey, abi); + const FilePath from = buildDirectory() / fileName; + const FilePath to = androidLibsDir / fileName; + if (!from.exists() || to.exists()) + continue; - const QString buildKey = target()->activeBuildKey(); - BuildSystem *bs = buildSystem(); + if (!QFile::copy(from.toString(), to.toString())) { + const QString error = tr("Couldn't copy the target's lib file %1 to the " + "Android build folder %2.") + .arg(fileName, androidLibsDir.toString()); + emit addOutput(error, BuildStep::OutputFormat::ErrorMessage); + TaskHub::addTask(BuildSystemTask(Task::Error, error)); + return false; + } + } + } + + } bool inputExists = QFile::exists(m_inputFile); if (inputExists && !AndroidManager::isQtCreatorGenerated(FilePath::fromString(m_inputFile))) return true; // use the generated file if it was not generated by qtcreator + BuildSystem *bs = buildSystem(); auto targets = bs->extraData(buildKey, Android::Constants::AndroidTargets).toStringList(); if (targets.isEmpty()) return inputExists; // qmake does this job for us - - QtSupport::BaseQtVersion *version = QtSupport::QtKitAspect::qtVersion(kit()); - if (!version) - return false; - QJsonObject deploySettings = Android::AndroidManager::deploymentSettings(target()); QString applicationBinary; if (!version->supportsMultipleQtAbis()) { From cd20ad8ff433acdf1629580e4700c2e9e394a962 Mon Sep 17 00:00:00 2001 From: Assam Boudjelthia Date: Mon, 31 May 2021 11:28:12 +0000 Subject: [PATCH 008/149] Revert "Android: add prepare_apk_dir CMake target by default" This reverts commit 9dcbb8ca01e0981b6a3c7ea8dd278014343f48e3. Reason for revert: This workaround is not needed anymore, since Qt 6.1.2 (see 71348437939e62b0e3b86888e966b300ff1e2855). Change-Id: I089457dc58c5b97136b30190fdf17cfb303f8bd8 Reviewed-by: Alessandro Portale --- .../cmakeprojectmanager/cmakebuildstep.cpp | 29 +------------------ 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp b/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp index 5de90d18635..b4d9abfeb6a 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp @@ -32,18 +32,15 @@ #include "cmakeprojectconstants.h" #include "cmaketool.h" -#include #include #include #include -#include #include #include #include #include #include -#include -#include + #include #include @@ -479,30 +476,6 @@ QWidget *CMakeBuildStep::createConfigWidget() connect(this, &CMakeBuildStep::buildTargetsChanged, widget, updateDetails); - // For Qt 6 for Android: Make sure to add "_prepare_apk_dir" if only - // "all" target is selected. This copies the build shared libs to android-build - // folder, partially the same as done in AndroidPackageInstallationStep for - // qmake install step. - const Kit *k = target()->kit(); - if (DeviceTypeKitAspect::deviceTypeId(k) == Android::Constants::ANDROID_DEVICE_TYPE) { - const QtSupport::BaseQtVersion *qt = QtSupport::QtKitAspect::qtVersion(k); - if (qt && qt->qtVersion() >= QtSupport::QtVersionNumber{6, 0, 0}) { - QMetaObject::Connection *const connection = new QMetaObject::Connection; - *connection = connect(this, &CMakeBuildStep::buildTargetsChanged, widget, [this, connection]() { - const QString mainTarget = activeRunConfigTarget(); - if (!mainTarget.isEmpty()) { - QStringList targets{buildTargets()}; - if (targets == QStringList{allTarget()}) { - targets.append(QString("%1_prepare_apk_dir").arg(mainTarget)); - setBuildTargets({targets}); - QObject::disconnect(*connection); - delete connection; - } - } - }); - } - } - return widget; } From ba138a185534269f6a6f32088965b12cbb898337 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Fri, 25 Jun 2021 16:50:52 +0200 Subject: [PATCH 009/149] CppTools: Add dedicated settings and settings page for clangd We plan to add more clangd settings, and it makes sense to have a dedicated place for them both in the code and the UI. Change-Id: Ideb92935b7a5a6a98e07980f4011736fb82042d1 Reviewed-by: David Schulz --- src/plugins/clangcodemodel/clangdclient.cpp | 2 +- .../clangmodelmanagersupport.cpp | 8 +- .../clangcodemodel/test/clangdtests.cpp | 7 +- src/plugins/cppeditor/cppeditortestcase.cpp | 6 +- .../followsymbol_switchmethoddecldef_test.cpp | 9 +-- src/plugins/cpptools/cppcodemodelsettings.cpp | 61 +++++++++++--- src/plugins/cpptools/cppcodemodelsettings.h | 42 +++++++--- .../cpptools/cppcodemodelsettingspage.cpp | 79 +++++++++++++------ .../cpptools/cppcodemodelsettingspage.h | 6 ++ .../cpptools/cppcodemodelsettingspage.ui | 31 +------- src/plugins/cpptools/cpptoolsplugin.cpp | 4 + 11 files changed, 168 insertions(+), 87 deletions(-) diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index da875481714..b3352408f59 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -382,7 +382,7 @@ public: static BaseClientInterface *clientInterface(const Utils::FilePath &jsonDbDir) { - Utils::CommandLine cmd{CppTools::codeModelSettings()->clangdFilePath(), + Utils::CommandLine cmd{CppTools::ClangdSettings::clangdFilePath(), {"--background-index", "--limit-results=0"}}; if (!jsonDbDir.isEmpty()) cmd.addArg("--compile-commands-dir=" + jsonDbDir.toString()); diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp index d2fb17d64de..06746bbb840 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp @@ -119,7 +119,7 @@ ClangModelManagerSupport::ClangModelManagerSupport() connect(sessionManager, &ProjectExplorer::SessionManager::aboutToRemoveProject, this, &ClangModelManagerSupport::onAboutToRemoveProject); - CppTools::CppCodeModelSettings::setDefaultClangdPath(Utils::FilePath::fromString( + CppTools::ClangdSettings::setDefaultClangdPath(Utils::FilePath::fromString( Core::ICore::clangdExecutable(CLANG_BINDIR))); CppTools::CppCodeModelSettings *settings = CppTools::codeModelSettings(); connect(settings, &CppTools::CppCodeModelSettings::clangDiagnosticConfigsInvalidated, @@ -245,7 +245,7 @@ void ClangModelManagerSupport::connectToWidgetsMarkContextMenuRequested(QWidget void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *project, const CppTools::ProjectInfo &projectInfo) { - if (!CppTools::codeModelSettings()->useClangd()) + if (!CppTools::ClangdSettings::useClangd()) return; const auto getJsonDbDir = [project] { if (const ProjectExplorer::Target * const target = project->activeTarget()) { @@ -264,7 +264,7 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr connect(generatorWatcher, &QFutureWatcher::finished, [this, project, projectInfo, getJsonDbDir, jsonDbDir, generatorWatcher] { generatorWatcher->deleteLater(); - if (!CppTools::codeModelSettings()->useClangd()) + if (!CppTools::ClangdSettings::useClangd()) return; if (!ProjectExplorer::SessionManager::hasProject(project)) return; @@ -284,7 +284,7 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr ClangdClient * const client = createClient(project, jsonDbDir); connect(client, &Client::initialized, this, [client, project, projectInfo, jsonDbDir] { using namespace ProjectExplorer; - if (!CppTools::codeModelSettings()->useClangd()) + if (!CppTools::ClangdSettings::useClangd()) return; if (!SessionManager::hasProject(project)) return; diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp index fcc43db4ca3..5bce03e251f 100644 --- a/src/plugins/clangcodemodel/test/clangdtests.cpp +++ b/src/plugins/clangcodemodel/test/clangdtests.cpp @@ -85,14 +85,13 @@ Utils::FilePath ClangdTest::filePath(const QString &fileName) const void ClangdTest::initTestCase() { - const auto settings = CppTools::codeModelSettings(); const QString clangdFromEnv = qEnvironmentVariable("QTC_CLANGD"); if (!clangdFromEnv.isEmpty()) - settings->setClangdFilePath(Utils::FilePath::fromString(clangdFromEnv)); - const auto clangd = settings->clangdFilePath(); + CppTools::ClangdSettings::setClangdFilePath(Utils::FilePath::fromString(clangdFromEnv)); + const auto clangd = CppTools::ClangdSettings::clangdFilePath(); if (clangd.isEmpty() || !clangd.exists()) QSKIP("clangd binary not found"); - settings->setUseClangd(true); + CppTools::ClangdSettings::setUseClangd(true); // Find suitable kit. m_kit = Utils::findOr(KitManager::kits(), nullptr, [](const Kit *k) { diff --git a/src/plugins/cppeditor/cppeditortestcase.cpp b/src/plugins/cppeditor/cppeditortestcase.cpp index b9aaae023ae..7d6c24406fc 100644 --- a/src/plugins/cppeditor/cppeditortestcase.cpp +++ b/src/plugins/cppeditor/cppeditortestcase.cpp @@ -78,19 +78,19 @@ bool TestDocument::hasAnchorMarker() const { return m_anchorPosition != -1; } TestCase::TestCase(bool runGarbageCollector) : CppTools::Tests::TestCase(runGarbageCollector), - m_prevUseClangd(CppTools::codeModelSettings()->useClangd()) + m_prevUseClangd(CppTools::ClangdSettings::useClangd()) { } TestCase::~TestCase() { - CppTools::codeModelSettings()->setUseClangd(m_prevUseClangd); + CppTools::ClangdSettings::setUseClangd(m_prevUseClangd); } void TestCase::setUseClangd() { if (CppEditorPlugin::instance()->m_testKit) - CppTools::codeModelSettings()->setUseClangd(true); + CppTools::ClangdSettings::setUseClangd(true); } bool TestCase::openCppEditor(const QString &fileName, CppEditor **editor, CppEditorWidget **editorWidget) diff --git a/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp b/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp index 5eefae1e02c..90ee657f6c9 100644 --- a/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp +++ b/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp @@ -285,7 +285,7 @@ F2TestCase::F2TestCase(CppEditorAction action, const QString curTestName = QLatin1String(QTest::currentTestFunction()); const QString tag = QLatin1String(QTest::currentDataTag()); - const bool useClangd = CppTools::codeModelSettings()->useClangd(); + const bool useClangd = CppTools::ClangdSettings::useClangd(); if (useClangd) { if (curTestName == "test_FollowSymbolUnderCursor_QObject_connect" || curTestName == "test_FollowSymbolUnderCursor_QObject_oldStyleConnect") { @@ -475,7 +475,7 @@ F2TestCase::F2TestCase(CppEditorAction action, // qDebug() << "Expected line:" << expectedLine; // qDebug() << "Expected column:" << expectedColumn; - if (!CppTools::codeModelSettings()->useClangd()) { + if (!CppTools::ClangdSettings::useClangd()) { QEXPECT_FAIL("globalVarFromEnum", "Contributor works on a fix.", Abort); QEXPECT_FAIL("matchFunctionSignature_Follow_5", "foo(int) resolved as CallAST", Abort); } @@ -541,12 +541,11 @@ namespace Internal { void CppEditorPlugin::initTestCase() { - const auto settings = CppTools::codeModelSettings(); const QString clangdFromEnv = qEnvironmentVariable("QTC_CLANGD"); if (clangdFromEnv.isEmpty()) return; - settings->setClangdFilePath(Utils::FilePath::fromString(clangdFromEnv)); - const auto clangd = settings->clangdFilePath(); + ClangdSettings::setClangdFilePath(Utils::FilePath::fromString(clangdFromEnv)); + const auto clangd = ClangdSettings::clangdFilePath(); if (clangd.isEmpty() || !clangd.exists()) return; diff --git a/src/plugins/cpptools/cppcodemodelsettings.cpp b/src/plugins/cpptools/cppcodemodelsettings.cpp index 5c5113d004d..aa09d409187 100644 --- a/src/plugins/cpptools/cppcodemodelsettings.cpp +++ b/src/plugins/cpptools/cppcodemodelsettings.cpp @@ -29,6 +29,8 @@ #include "cpptoolsconstants.h" #include "cpptoolsreuse.h" +#include + #include #include @@ -172,9 +174,6 @@ void CppCodeModelSettings::fromSettings(QSettings *s) const QVariant indexerFileSizeLimit = s->value(indexerFileSizeLimitKey(), 5); setIndexerFileSizeLimitInMb(indexerFileSizeLimit.toInt()); - setUseClangd(s->value(useClangdKey(), false).toBool()); - setClangdFilePath(FilePath::fromString(s->value(clangdPathKey()).toString())); - s->endGroup(); if (write) @@ -198,8 +197,6 @@ void CppCodeModelSettings::toSettings(QSettings *s) s->setValue(interpretAmbiguousHeadersAsCHeadersKey(), interpretAmbigiousHeadersAsCHeaders()); s->setValue(skipIndexingBigFilesKey(), skipIndexingBigFiles()); s->setValue(indexerFileSizeLimitKey(), indexerFileSizeLimitInMb()); - s->setValue(useClangdKey(), useClangd()); - s->setValue(clangdPathKey(), m_clangdFilePath.toString()); s->endGroup(); @@ -300,14 +297,60 @@ void CppCodeModelSettings::setEnableLowerClazyLevels(bool yesno) m_enableLowerClazyLevels = yesno; } -void CppCodeModelSettings::setDefaultClangdPath(const Utils::FilePath &filePath) + +static bool operator==(const ClangdSettings::Data &s1, const ClangdSettings::Data &s2) +{ + return s1.useClangd == s2.useClangd && s1.executableFilePath == s2.executableFilePath; +} +static bool operator!=(const ClangdSettings::Data &s1, const ClangdSettings::Data &s2) +{ + return !(s1 == s2); +} + +ClangdSettings &ClangdSettings::instance() +{ + static ClangdSettings settings; + return settings; +} + +void ClangdSettings::setDefaultClangdPath(const Utils::FilePath &filePath) { g_defaultClangdFilePath = filePath; } -FilePath CppCodeModelSettings::clangdFilePath() const +FilePath ClangdSettings::clangdFilePath() { - if (!m_clangdFilePath.isEmpty()) - return m_clangdFilePath; + if (!instance().m_data.executableFilePath.isEmpty()) + return instance().m_data.executableFilePath; return fallbackClangdFilePath(); } + +void ClangdSettings::setData(const Data &data) +{ + if (data != instance().m_data) { + instance().m_data = data; + instance().saveSettings(); + } +} + +void ClangdSettings::loadSettings() +{ + QSettings * const s = Core::ICore::settings(); + m_data.useClangd = s->value(useClangdKey(), false).toBool(); + m_data.executableFilePath = FilePath::fromString(s->value(clangdPathKey()).toString()); +} + +void ClangdSettings::saveSettings() +{ + QSettings * const s = Core::ICore::settings(); + s->setValue(useClangdKey(), useClangd()); + s->setValue(clangdPathKey(), m_data.executableFilePath.toString()); +} + +#ifdef WITH_TESTS +void ClangdSettings::setUseClangd(bool use) { instance().m_data.useClangd = use; } +void ClangdSettings::setClangdFilePath(const Utils::FilePath &filePath) +{ + instance().m_data.executableFilePath = filePath; +} +#endif diff --git a/src/plugins/cpptools/cppcodemodelsettings.h b/src/plugins/cpptools/cppcodemodelsettings.h index 5fc981e543a..a84d20186f5 100644 --- a/src/plugins/cpptools/cppcodemodelsettings.h +++ b/src/plugins/cpptools/cppcodemodelsettings.h @@ -78,13 +78,6 @@ public: int indexerFileSizeLimitInMb() const; void setIndexerFileSizeLimitInMb(int sizeInMB); - void setUseClangd(bool use) { m_useClangd = use; } - bool useClangd() const { return m_useClangd; } - - static void setDefaultClangdPath(const Utils::FilePath &filePath); - void setClangdFilePath(const Utils::FilePath &filePath) { m_clangdFilePath = filePath; } - Utils::FilePath clangdFilePath() const; - void setCategorizeFindReferences(bool categorize) { m_categorizeFindReferences = categorize; } bool categorizeFindReferences() const { return m_categorizeFindReferences; } @@ -100,9 +93,40 @@ private: ClangDiagnosticConfigs m_clangCustomDiagnosticConfigs; Utils::Id m_clangDiagnosticConfigId; bool m_enableLowerClazyLevels = true; // For UI behavior only - Utils::FilePath m_clangdFilePath; - bool m_useClangd = false; bool m_categorizeFindReferences = false; // Ephemeral! }; +class CPPTOOLS_EXPORT ClangdSettings +{ +public: + class Data + { + public: + bool useClangd = false; + Utils::FilePath executableFilePath; + }; + + static bool useClangd() { return instance().m_data.useClangd; } + + static void setDefaultClangdPath(const Utils::FilePath &filePath); + static Utils::FilePath clangdFilePath(); + + static void setData(const Data &data); + static Data data() { return instance().m_data; } + +#ifdef WITH_TESTS + static void setUseClangd(bool use); + static void setClangdFilePath(const Utils::FilePath &filePath); +#endif + +private: + ClangdSettings() { loadSettings(); } + static ClangdSettings &instance(); + + void loadSettings(); + void saveSettings(); + + Data m_data; +}; + } // namespace CppTools diff --git a/src/plugins/cpptools/cppcodemodelsettingspage.cpp b/src/plugins/cpptools/cppcodemodelsettingspage.cpp index 00e7b8081ad..7d9fc2078ea 100644 --- a/src/plugins/cpptools/cppcodemodelsettingspage.cpp +++ b/src/plugins/cpptools/cppcodemodelsettingspage.cpp @@ -33,7 +33,9 @@ #include #include +#include +#include #include namespace CppTools { @@ -100,8 +102,6 @@ void CppCodeModelSettingsWidget::setupClangCodeModelWidgets() const bool isClangActive = CppModelManager::instance()->isClangCodeModelActive(); m_ui->clangCodeModelIsDisabledHint->setVisible(!isClangActive); m_ui->clangCodeModelIsEnabledHint->setVisible(isClangActive); - m_ui->clangdCheckBox->setVisible(isClangActive); - m_ui->clangdChooser->setVisible(isClangActive); for (int i = 0; i < m_ui->clangDiagnosticConfigsSelectionWidget->layout()->count(); ++i) { QWidget *widget = m_ui->clangDiagnosticConfigsSelectionWidget->layout()->itemAt(i)->widget(); @@ -120,16 +120,6 @@ void CppCodeModelSettingsWidget::setupGeneralWidgets() const bool ignorePch = m_settings->pchUsage() == CppCodeModelSettings::PchUse_None; m_ui->ignorePCHCheckBox->setChecked(ignorePch); - - m_ui->clangdCheckBox->setChecked(m_settings->useClangd()); - m_ui->clangdCheckBox->setToolTip(tr("Use clangd for locators and \"Find References\".\n" - "Changing this option does not affect projects that are already open.")); - m_ui->clangdChooser->setExpectedKind(Utils::PathChooser::ExistingCommand); - m_ui->clangdChooser->setFilePath(codeModelSettings()->clangdFilePath()); - m_ui->clangdChooser->setEnabled(m_ui->clangdCheckBox->isChecked()); - connect(m_ui->clangdCheckBox, &QCheckBox::toggled, m_ui->clangdChooser, [this](bool checked) { - m_ui->clangdChooser->setEnabled(checked); - }); } bool CppCodeModelSettingsWidget::applyClangCodeModelWidgetsToSettings() const @@ -176,16 +166,6 @@ bool CppCodeModelSettingsWidget::applyGeneralWidgetsToSettings() const m_settings->setIndexerFileSizeLimitInMb(newFileSizeLimit); settingsChanged = true; } - const bool newUseClangd = m_ui->clangdCheckBox->isChecked(); - if (m_settings->useClangd() != newUseClangd) { - m_settings->setUseClangd(newUseClangd); - settingsChanged = true; - } - const Utils::FilePath newClangdPath = m_ui->clangdChooser->rawFilePath(); - if (m_settings->clangdFilePath() != newClangdPath) { - m_settings->setClangdFilePath(newClangdPath); - settingsChanged = true; - } const bool newIgnorePch = m_ui->ignorePCHCheckBox->isChecked(); const bool previousIgnorePch = m_settings->pchUsage() == CppCodeModelSettings::PchUse_None; @@ -210,5 +190,60 @@ CppCodeModelSettingsPage::CppCodeModelSettingsPage(CppCodeModelSettings *setting setWidgetCreator([settings] { return new CppCodeModelSettingsWidget(settings); }); } + +class ClangdSettingsWidget final : public Core::IOptionsPageWidget +{ + Q_DECLARE_TR_FUNCTIONS(CppTools::Internal::ClangdSettingsWidget) + +public: + ClangdSettingsWidget() + { + m_useClangdCheckBox.setText(tr("Use clangd (EXPERIMENTAL)")); + m_useClangdCheckBox.setChecked(ClangdSettings::useClangd()); + m_useClangdCheckBox.setToolTip(tr("Changing this option does not affect projects " + "that are already open.")); + m_clangdChooser.setExpectedKind(Utils::PathChooser::ExistingCommand); + m_clangdChooser.setFilePath(ClangdSettings::clangdFilePath()); + m_clangdChooser.setEnabled(m_useClangdCheckBox.isChecked()); + + const auto layout = new QVBoxLayout(this); + layout->addWidget(&m_useClangdCheckBox); + const auto formLayout = new QFormLayout; + const auto chooserLabel = new QLabel(tr("Path to executable:")); + formLayout->addRow(chooserLabel, &m_clangdChooser); + layout->addLayout(formLayout); + layout->addStretch(1); + + const auto toggleEnabled = [=](const bool checked) { + chooserLabel->setEnabled(checked); + m_clangdChooser.setEnabled(checked); + }; + connect(&m_useClangdCheckBox, &QCheckBox::toggled, toggleEnabled); + toggleEnabled(m_useClangdCheckBox.isChecked()); + } + +private: + void apply() final + { + ClangdSettings::Data data; + data.useClangd = m_useClangdCheckBox.isChecked(); + data.executableFilePath = m_clangdChooser.filePath(); + ClangdSettings::setData(data); + } + + QCheckBox m_useClangdCheckBox; + Utils::PathChooser m_clangdChooser; +}; + +ClangdSettingsPage::ClangdSettingsPage() +{ + setId("K.Clangd"); + setDisplayName(ClangdSettingsWidget::tr("Clangd")); + setCategory(Constants::CPP_SETTINGS_CATEGORY); + setWidgetCreator([] { return new ClangdSettingsWidget; }); +} + + + } // Internal } // CppTools diff --git a/src/plugins/cpptools/cppcodemodelsettingspage.h b/src/plugins/cpptools/cppcodemodelsettingspage.h index dd297af020c..5e48be1ffe0 100644 --- a/src/plugins/cpptools/cppcodemodelsettingspage.h +++ b/src/plugins/cpptools/cppcodemodelsettingspage.h @@ -38,5 +38,11 @@ public: explicit CppCodeModelSettingsPage(CppCodeModelSettings *settings); }; +class ClangdSettingsPage final : public Core::IOptionsPage +{ +public: + explicit ClangdSettingsPage(); +}; + } // Internal namespace } // CppTools namespace diff --git a/src/plugins/cpptools/cppcodemodelsettingspage.ui b/src/plugins/cpptools/cppcodemodelsettingspage.ui index 2362131cbe9..90136f81bdf 100644 --- a/src/plugins/cpptools/cppcodemodelsettingspage.ui +++ b/src/plugins/cpptools/cppcodemodelsettingspage.ui @@ -81,31 +81,7 @@ - - - - - Use clangd (EXPERIMENTAL) - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + @@ -174,11 +150,6 @@ QWidget
cpptools/clangdiagnosticconfigsselectionwidget.h
- - Utils::PathChooser - QLineEdit -
utils/pathchooser.h
-
diff --git a/src/plugins/cpptools/cpptoolsplugin.cpp b/src/plugins/cpptools/cpptoolsplugin.cpp index c5796e0dba2..87ecc25d713 100644 --- a/src/plugins/cpptools/cpptoolsplugin.cpp +++ b/src/plugins/cpptools/cpptoolsplugin.cpp @@ -86,6 +86,7 @@ public: ~CppToolsPluginPrivate() { ExtensionSystem::PluginManager::removeObject(&m_cppProjectUpdaterFactory); + delete m_clangdSettingsPage; } StringTable stringTable; @@ -95,6 +96,7 @@ public: CppFileSettings m_fileSettings; CppFileSettingsPage m_cppFileSettingsPage{&m_fileSettings}; CppCodeModelSettingsPage m_cppCodeModelSettingsPage{&m_codeModelSettings}; + ClangdSettingsPage *m_clangdSettingsPage = nullptr; CppCodeStyleSettingsPage m_cppCodeStyleSettingsPage; CppProjectUpdaterFactory m_cppProjectUpdaterFactory; }; @@ -218,6 +220,8 @@ void CppToolsPlugin::extensionsInitialized() d->m_fileSettings.fromSettings(ICore::settings()); if (!d->m_fileSettings.applySuffixesToMimeDB()) qWarning("Unable to apply cpp suffixes to mime database (cpp mime types not found).\n"); + if (CppModelManager::instance()->isClangCodeModelActive()) + d->m_clangdSettingsPage = new ClangdSettingsPage; } CppCodeModelSettings *CppToolsPlugin::codeModelSettings() From 876cd8e9755aff5bb69264f9aa0c5210b23b3920 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Fri, 25 Jun 2021 17:40:20 +0200 Subject: [PATCH 010/149] Clangd: Allow to switch off background indexing It is conceivable that users don't want to spend the extra CPU time on this. Change-Id: Ic3611c8d17d201ae986fad08b344369a8728ce1b Reviewed-by: David Schulz --- src/plugins/clangcodemodel/clangdclient.cpp | 5 ++++- src/plugins/cpptools/cppcodemodelsettings.cpp | 7 ++++++- src/plugins/cpptools/cppcodemodelsettings.h | 2 ++ src/plugins/cpptools/cppcodemodelsettingspage.cpp | 11 +++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index b3352408f59..7768da70334 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -382,8 +382,11 @@ public: static BaseClientInterface *clientInterface(const Utils::FilePath &jsonDbDir) { + QString indexingOption = "--background-index"; + if (!CppTools::ClangdSettings::indexingEnabled()) + indexingOption += "=0"; Utils::CommandLine cmd{CppTools::ClangdSettings::clangdFilePath(), - {"--background-index", "--limit-results=0"}}; + {indexingOption, "--limit-results=0"}}; if (!jsonDbDir.isEmpty()) cmd.addArg("--compile-commands-dir=" + jsonDbDir.toString()); if (clangdLog().isDebugEnabled()) diff --git a/src/plugins/cpptools/cppcodemodelsettings.cpp b/src/plugins/cpptools/cppcodemodelsettings.cpp index aa09d409187..4c717801608 100644 --- a/src/plugins/cpptools/cppcodemodelsettings.cpp +++ b/src/plugins/cpptools/cppcodemodelsettings.cpp @@ -65,6 +65,7 @@ static QString indexerFileSizeLimitKey() static QString useClangdKey() { return QLatin1String("UseClangd"); } static QString clangdPathKey() { return QLatin1String("ClangdPath"); } +static QString clangdIndexingKey() { return QLatin1String("ClangdIndexing"); } static FilePath g_defaultClangdFilePath; static FilePath fallbackClangdFilePath() @@ -300,7 +301,9 @@ void CppCodeModelSettings::setEnableLowerClazyLevels(bool yesno) static bool operator==(const ClangdSettings::Data &s1, const ClangdSettings::Data &s2) { - return s1.useClangd == s2.useClangd && s1.executableFilePath == s2.executableFilePath; + return s1.useClangd == s2.useClangd + && s1.executableFilePath == s2.executableFilePath + && s1.enableIndexing == s2.enableIndexing; } static bool operator!=(const ClangdSettings::Data &s1, const ClangdSettings::Data &s2) { @@ -338,6 +341,7 @@ void ClangdSettings::loadSettings() QSettings * const s = Core::ICore::settings(); m_data.useClangd = s->value(useClangdKey(), false).toBool(); m_data.executableFilePath = FilePath::fromString(s->value(clangdPathKey()).toString()); + m_data.enableIndexing = s->value(clangdIndexingKey(), true).toBool(); } void ClangdSettings::saveSettings() @@ -345,6 +349,7 @@ void ClangdSettings::saveSettings() QSettings * const s = Core::ICore::settings(); s->setValue(useClangdKey(), useClangd()); s->setValue(clangdPathKey(), m_data.executableFilePath.toString()); + s->setValue(clangdIndexingKey(), m_data.enableIndexing); } #ifdef WITH_TESTS diff --git a/src/plugins/cpptools/cppcodemodelsettings.h b/src/plugins/cpptools/cppcodemodelsettings.h index a84d20186f5..4cc37a0dcba 100644 --- a/src/plugins/cpptools/cppcodemodelsettings.h +++ b/src/plugins/cpptools/cppcodemodelsettings.h @@ -103,6 +103,7 @@ public: { public: bool useClangd = false; + bool enableIndexing = true; Utils::FilePath executableFilePath; }; @@ -110,6 +111,7 @@ public: static void setDefaultClangdPath(const Utils::FilePath &filePath); static Utils::FilePath clangdFilePath(); + static bool indexingEnabled() { return instance().m_data.enableIndexing; } static void setData(const Data &data); static Data data() { return instance().m_data; } diff --git a/src/plugins/cpptools/cppcodemodelsettingspage.cpp b/src/plugins/cpptools/cppcodemodelsettingspage.cpp index 7d9fc2078ea..d3a75af0cb9 100644 --- a/src/plugins/cpptools/cppcodemodelsettingspage.cpp +++ b/src/plugins/cpptools/cppcodemodelsettingspage.cpp @@ -205,18 +205,27 @@ public: m_clangdChooser.setExpectedKind(Utils::PathChooser::ExistingCommand); m_clangdChooser.setFilePath(ClangdSettings::clangdFilePath()); m_clangdChooser.setEnabled(m_useClangdCheckBox.isChecked()); + m_indexingCheckBox.setChecked(ClangdSettings::indexingEnabled()); + m_indexingCheckBox.setToolTip(tr( + "If background indexing is enabled, global symbol searches will yield\n" + "more accurate results, at the cost of additional CPU load when\n" + "the project is first opened.")); const auto layout = new QVBoxLayout(this); layout->addWidget(&m_useClangdCheckBox); const auto formLayout = new QFormLayout; const auto chooserLabel = new QLabel(tr("Path to executable:")); formLayout->addRow(chooserLabel, &m_clangdChooser); + const auto indexingLabel = new QLabel(tr("Enable background indexing:")); + formLayout->addRow(indexingLabel, &m_indexingCheckBox); layout->addLayout(formLayout); layout->addStretch(1); const auto toggleEnabled = [=](const bool checked) { chooserLabel->setEnabled(checked); m_clangdChooser.setEnabled(checked); + indexingLabel->setEnabled(checked); + m_indexingCheckBox.setEnabled(checked); }; connect(&m_useClangdCheckBox, &QCheckBox::toggled, toggleEnabled); toggleEnabled(m_useClangdCheckBox.isChecked()); @@ -228,10 +237,12 @@ private: ClangdSettings::Data data; data.useClangd = m_useClangdCheckBox.isChecked(); data.executableFilePath = m_clangdChooser.filePath(); + data.enableIndexing = m_indexingCheckBox.isChecked(); ClangdSettings::setData(data); } QCheckBox m_useClangdCheckBox; + QCheckBox m_indexingCheckBox; Utils::PathChooser m_clangdChooser; }; From 56cddf38b37c5b36424121d67592a9ce7b6ac10c Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Fri, 25 Jun 2021 18:16:08 +0200 Subject: [PATCH 011/149] Clangd: Let users limit the worker thread count This is particularly interesting for indexing, where users might prefer a slower-building index with less CPU load. Change-Id: Id44c58e9041df2857cd0772e71345673b14623f3 Reviewed-by: David Schulz --- src/plugins/clangcodemodel/clangdclient.cpp | 2 ++ src/plugins/cpptools/cppcodemodelsettings.cpp | 4 ++++ src/plugins/cpptools/cppcodemodelsettings.h | 4 +++- .../cpptools/cppcodemodelsettingspage.cpp | 21 +++++++++++++++++-- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 7768da70334..26721c84219 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -387,6 +387,8 @@ static BaseClientInterface *clientInterface(const Utils::FilePath &jsonDbDir) indexingOption += "=0"; Utils::CommandLine cmd{CppTools::ClangdSettings::clangdFilePath(), {indexingOption, "--limit-results=0"}}; + if (CppTools::ClangdSettings::workerThreadLimit() != 0) + cmd.addArg("-j=" + QString::number(CppTools::ClangdSettings::workerThreadLimit())); if (!jsonDbDir.isEmpty()) cmd.addArg("--compile-commands-dir=" + jsonDbDir.toString()); if (clangdLog().isDebugEnabled()) diff --git a/src/plugins/cpptools/cppcodemodelsettings.cpp b/src/plugins/cpptools/cppcodemodelsettings.cpp index 4c717801608..6fd9a15b534 100644 --- a/src/plugins/cpptools/cppcodemodelsettings.cpp +++ b/src/plugins/cpptools/cppcodemodelsettings.cpp @@ -66,6 +66,7 @@ static QString indexerFileSizeLimitKey() static QString useClangdKey() { return QLatin1String("UseClangd"); } static QString clangdPathKey() { return QLatin1String("ClangdPath"); } static QString clangdIndexingKey() { return QLatin1String("ClangdIndexing"); } +static QString clangdThreadLimitKey() { return QLatin1String("ClangdThreadLimit"); } static FilePath g_defaultClangdFilePath; static FilePath fallbackClangdFilePath() @@ -303,6 +304,7 @@ static bool operator==(const ClangdSettings::Data &s1, const ClangdSettings::Dat { return s1.useClangd == s2.useClangd && s1.executableFilePath == s2.executableFilePath + && s1.workerThreadLimit == s2.workerThreadLimit && s1.enableIndexing == s2.enableIndexing; } static bool operator!=(const ClangdSettings::Data &s1, const ClangdSettings::Data &s2) @@ -342,6 +344,7 @@ void ClangdSettings::loadSettings() m_data.useClangd = s->value(useClangdKey(), false).toBool(); m_data.executableFilePath = FilePath::fromString(s->value(clangdPathKey()).toString()); m_data.enableIndexing = s->value(clangdIndexingKey(), true).toBool(); + m_data.workerThreadLimit = s->value(clangdThreadLimitKey(), 0).toInt(); } void ClangdSettings::saveSettings() @@ -350,6 +353,7 @@ void ClangdSettings::saveSettings() s->setValue(useClangdKey(), useClangd()); s->setValue(clangdPathKey(), m_data.executableFilePath.toString()); s->setValue(clangdIndexingKey(), m_data.enableIndexing); + s->setValue(clangdThreadLimitKey(), m_data.workerThreadLimit); } #ifdef WITH_TESTS diff --git a/src/plugins/cpptools/cppcodemodelsettings.h b/src/plugins/cpptools/cppcodemodelsettings.h index 4cc37a0dcba..eb4abe4537e 100644 --- a/src/plugins/cpptools/cppcodemodelsettings.h +++ b/src/plugins/cpptools/cppcodemodelsettings.h @@ -102,9 +102,10 @@ public: class Data { public: + Utils::FilePath executableFilePath; + int workerThreadLimit = 0; bool useClangd = false; bool enableIndexing = true; - Utils::FilePath executableFilePath; }; static bool useClangd() { return instance().m_data.useClangd; } @@ -112,6 +113,7 @@ public: static void setDefaultClangdPath(const Utils::FilePath &filePath); static Utils::FilePath clangdFilePath(); static bool indexingEnabled() { return instance().m_data.enableIndexing; } + static int workerThreadLimit() { return instance().m_data.workerThreadLimit; } static void setData(const Data &data); static Data data() { return instance().m_data; } diff --git a/src/plugins/cpptools/cppcodemodelsettingspage.cpp b/src/plugins/cpptools/cppcodemodelsettingspage.cpp index d3a75af0cb9..f48f639d16a 100644 --- a/src/plugins/cpptools/cppcodemodelsettingspage.cpp +++ b/src/plugins/cpptools/cppcodemodelsettingspage.cpp @@ -36,6 +36,7 @@ #include #include +#include #include namespace CppTools { @@ -210,6 +211,10 @@ public: "If background indexing is enabled, global symbol searches will yield\n" "more accurate results, at the cost of additional CPU load when\n" "the project is first opened.")); + m_threadLimitCheckBox.setText(tr("Set worker thread count limit")); + m_threadLimitCheckBox.setChecked(ClangdSettings::workerThreadLimit() != 0); + m_threadLimitSpinBox.setMinimum(1); + m_threadLimitSpinBox.setValue(ClangdSettings::workerThreadLimit()); const auto layout = new QVBoxLayout(this); layout->addWidget(&m_useClangdCheckBox); @@ -218,6 +223,10 @@ public: formLayout->addRow(chooserLabel, &m_clangdChooser); const auto indexingLabel = new QLabel(tr("Enable background indexing:")); formLayout->addRow(indexingLabel, &m_indexingCheckBox); + const auto threadLimitLayout = new QHBoxLayout; + threadLimitLayout->addWidget(&m_threadLimitSpinBox); + threadLimitLayout->addStretch(1); + formLayout->addRow(&m_threadLimitCheckBox, threadLimitLayout); layout->addLayout(formLayout); layout->addStretch(1); @@ -226,9 +235,15 @@ public: m_clangdChooser.setEnabled(checked); indexingLabel->setEnabled(checked); m_indexingCheckBox.setEnabled(checked); + m_threadLimitCheckBox.setEnabled(checked); + m_threadLimitSpinBox.setEnabled(checked && m_threadLimitCheckBox.isChecked()); }; connect(&m_useClangdCheckBox, &QCheckBox::toggled, toggleEnabled); + connect(&m_threadLimitCheckBox, &QCheckBox::toggled, + &m_threadLimitSpinBox, &QSpinBox::setEnabled); toggleEnabled(m_useClangdCheckBox.isChecked()); + m_threadLimitSpinBox.setEnabled(m_useClangdCheckBox.isChecked() + && m_threadLimitCheckBox.isChecked()); } private: @@ -238,11 +253,15 @@ private: data.useClangd = m_useClangdCheckBox.isChecked(); data.executableFilePath = m_clangdChooser.filePath(); data.enableIndexing = m_indexingCheckBox.isChecked(); + data.workerThreadLimit = m_threadLimitCheckBox.isChecked() + ? m_threadLimitSpinBox.value() : 0; ClangdSettings::setData(data); } QCheckBox m_useClangdCheckBox; QCheckBox m_indexingCheckBox; + QCheckBox m_threadLimitCheckBox; + QSpinBox m_threadLimitSpinBox; Utils::PathChooser m_clangdChooser; }; @@ -254,7 +273,5 @@ ClangdSettingsPage::ClangdSettingsPage() setWidgetCreator([] { return new ClangdSettingsWidget; }); } - - } // Internal } // CppTools From e539ac263a4c27cf117109f1ff987599d660b125 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Thu, 24 Jun 2021 16:12:26 +0200 Subject: [PATCH 012/149] Doc: Fix QDoc errors introduced by restructuring the manuals - Remove an obsolete file - Fix link targets - Fix navigation link targets - Fix conditional text Change-Id: I2421be9867c350ec04b7a09489b0655de9bafa53 Reviewed-by: Johanna Vanhatapio --- .../src/editors/creator-coding-edit-mode.qdoc | 7 +-- .../qtcreator-ui-best-practices.qdoc | 47 ------------------- doc/qtcreator/src/qtcreator.qdoc | 4 +- .../qtdesignstudio-optimized-3d-scenes.qdoc | 7 ++- .../src/qtquick/qtquick-annotations.qdoc | 4 +- doc/qtcreator/src/qtquick/qtquick-fonts.qdoc | 2 +- doc/qtcreator/src/qtquick/qtquick-states.qdoc | 2 + .../src/widgets/qtdesigner-plugins.qdoc | 2 +- .../src/qtdesignstudio-faq.qdoc | 2 +- .../qtdesignstudio-3d-editor.qdoc | 2 +- 10 files changed, 20 insertions(+), 59 deletions(-) delete mode 100644 doc/qtcreator/src/overview/creator-only/qtcreator-ui-best-practices.qdoc diff --git a/doc/qtcreator/src/editors/creator-coding-edit-mode.qdoc b/doc/qtcreator/src/editors/creator-coding-edit-mode.qdoc index c7e25fbcd78..c2c6c1f2eaf 100644 --- a/doc/qtcreator/src/editors/creator-coding-edit-mode.qdoc +++ b/doc/qtcreator/src/editors/creator-coding-edit-mode.qdoc @@ -184,10 +184,9 @@ split view, select \uicontrol Window > \uicontrol {Remove All Splits} or press \key {Ctrl+E, 1}. - \if defined(qtcreator) \section1 Using Bookmarks - To insert or delete a bookmark: + To insert or delete a bookmark in the \uicontrol Edit mode: \list @@ -245,7 +244,8 @@ \section1 Moving to Symbol Definition or Declaration - You can move directly to the definition or the declaration of a symbol by + You can move directly to the definition or the declaration of a symbol + in the \uicontrol Edit mode by holding the \key Ctrl key and clicking the symbol. If you have multiple splits opened, you can open the link in the next split by holding \key Ctrl and \key Alt while clicking the symbol. @@ -290,6 +290,7 @@ \uicontrol {Open Corresponding Header/Source in Next Split}. You can also press \key F4 or \key {Ctrl+E,F4}, respectively. + \if defined(qtcreator) \section1 Reparsing Externally Changed Files If source files are modified from outside \QC, the opened files will be diff --git a/doc/qtcreator/src/overview/creator-only/qtcreator-ui-best-practices.qdoc b/doc/qtcreator/src/overview/creator-only/qtcreator-ui-best-practices.qdoc deleted file mode 100644 index 5df04a583fa..00000000000 --- a/doc/qtcreator/src/overview/creator-only/qtcreator-ui-best-practices.qdoc +++ /dev/null @@ -1,47 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Creator documentation. -** -** 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 Free Documentation License Usage -** Alternatively, this file may be used under the terms of the GNU Free -** Documentation License version 1.3 as published by the Free Software -** Foundation and appearing in the file included in the packaging of -** this file. Please review the following information to ensure -** the GNU Free Documentation License version 1.3 requirements -** will be met: https://www.gnu.org/licenses/fdl-1.3.html. -** -****************************************************************************/ - -/*! - \previouspage adding-plugins.html - \page creator-ui-best-practices.html - \nextpage creator-usability.html - - \title Best Practices - - Apply the best practices to ensure that the UIs you create work - efficiently on the intended platforms, such as mobile devices. - - \list - \li \l {Optimizing Applications for Mobile Devices} - - Guidelines to help you design and develop usable applications for - mobile devices with varying characteristics. - - \li \l {Qt Quick Best Practices} - - Guidelines describing the most efficient ways to use \QDS to - create UIs and scenes that run flawlessly on the intended platforms. - \endlist -*/ diff --git a/doc/qtcreator/src/qtcreator.qdoc b/doc/qtcreator/src/qtcreator.qdoc index 9492de89225..84ab84257d8 100644 --- a/doc/qtcreator/src/qtcreator.qdoc +++ b/doc/qtcreator/src/qtcreator.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Creator documentation. @@ -77,7 +77,7 @@ \list \li \l{Developing Qt Quick Applications} \li \l{Developing Widget Based Applications} - \li \l{Best Practices} + \li \l{Optimizing Applications for Mobile Devices} \endlist \li \b {\l{Coding}} \list diff --git a/doc/qtcreator/src/qtquick/qtdesignstudio-optimized-3d-scenes.qdoc b/doc/qtcreator/src/qtquick/qtdesignstudio-optimized-3d-scenes.qdoc index 35878e73b9a..b99e555029d 100644 --- a/doc/qtcreator/src/qtquick/qtdesignstudio-optimized-3d-scenes.qdoc +++ b/doc/qtcreator/src/qtquick/qtdesignstudio-optimized-3d-scenes.qdoc @@ -40,7 +40,12 @@ \section1 The Optimal 3D Scene Example - The \l {Optimal 3D Scene} example features four versions of the same kitchen + \if defined(qtdesignstudio) + The \l {Optimal 3D Scene} + \else + The Optimal 3D Scene + \endif + example features four versions of the same kitchen scene that have been created using different strategies: High, Low, Combined, and Vertex Color. The High Scene includes a significantly higher number of objects compared to the other versions of the scene. The Low, Combined and diff --git a/doc/qtcreator/src/qtquick/qtquick-annotations.qdoc b/doc/qtcreator/src/qtquick/qtquick-annotations.qdoc index 79bc0f1e774..8cd779c892e 100644 --- a/doc/qtcreator/src/qtquick/qtquick-annotations.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-annotations.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2020 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Creator documentation. @@ -30,7 +30,7 @@ \nextpage qtquick-prototyping.html \else \previouspage qtquick-fonts.html - \nextpage qtquick-ui-forms.html + \nextpage creator-quick-ui-forms.html \endif \title Annotating Designs diff --git a/doc/qtcreator/src/qtquick/qtquick-fonts.qdoc b/doc/qtcreator/src/qtquick/qtquick-fonts.qdoc index 41a17fc6234..bf79ffa20b9 100644 --- a/doc/qtcreator/src/qtquick/qtquick-fonts.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-fonts.qdoc @@ -25,7 +25,7 @@ /*! \page qtquick-fonts.html - \if defined{qtdesignstudio} + \if defined(qtdesignstudio) \previouspage studio-importing-2d.html \nextpage studio-importing-3d.html \else diff --git a/doc/qtcreator/src/qtquick/qtquick-states.qdoc b/doc/qtcreator/src/qtquick/qtquick-states.qdoc index f02e150a874..1b83f5ae694 100644 --- a/doc/qtcreator/src/qtquick/qtquick-states.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-states.qdoc @@ -147,9 +147,11 @@ when: control.pressed || control.checked && !control.hovered \endcode + \if defined(qtdesignstudio) If you are not familiar with writing expressions, you can use preset \l{Logic Helpers}{logic helpers} from \l Library > \uicontrol Components > \uicontrol {Qt Quick Studio Logic Helper}. + \endif \section1 Using States diff --git a/doc/qtcreator/src/widgets/qtdesigner-plugins.qdoc b/doc/qtcreator/src/widgets/qtdesigner-plugins.qdoc index 1cbb4040ca4..9ceeef0e255 100644 --- a/doc/qtcreator/src/widgets/qtdesigner-plugins.qdoc +++ b/doc/qtcreator/src/widgets/qtdesigner-plugins.qdoc @@ -32,7 +32,7 @@ /*! \previouspage creator-using-qt-designer.html \page adding-plugins.html - \nextpage creator-ui-best-practices.html + \nextpage creator-usability.html \title Adding Qt Designer Plugins diff --git a/doc/qtdesignstudio/src/qtdesignstudio-faq.qdoc b/doc/qtdesignstudio/src/qtdesignstudio-faq.qdoc index 1e8f73ac6fe..bf725577dfa 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio-faq.qdoc +++ b/doc/qtdesignstudio/src/qtdesignstudio-faq.qdoc @@ -94,7 +94,7 @@ Yes, you can create custom components and controls by using wizard templates or move component instances into separate files to turn them into new components that you can create instances of. For more information, see - \l {Creating Components}. + \l {Components}. \section2 What are the 3D import formats for \QDS? diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc index 68f45d80bec..43b307c27ff 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-editor.qdoc @@ -43,7 +43,7 @@ tools, you also import a \l{Cameras}{scene camera}, \l{Lights}{light}, \l{3D Models}{model}, and \l {Materials and Shaders}{materials}. If your scene did not contain - them, you can add the corresponding \l {Using 3D Components}{Qt Quick 3D} + them, you can add the corresponding \l {3D Components}{Qt Quick 3D} components from \l Library > \uicontrol Components > \inlineimage plus.png > \uicontrol {Qt Quick 3D} > \uicontrol {Qt Quick 3D}. From d083fd0227292de132244f453342ec575ae067f9 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Thu, 24 Jun 2021 16:13:14 +0200 Subject: [PATCH 013/149] ClangTools: Make version number available via settings Change-Id: I9501f5a3234fb995a7ca3192adc1569febb8662e Reviewed-by: David Schulz --- src/plugins/clangtools/clangtoolssettings.cpp | 33 +++++++++++++++++++ src/plugins/clangtools/clangtoolssettings.h | 14 ++++++-- src/plugins/clangtools/executableinfo.cpp | 22 ++++++------- src/plugins/clangtools/executableinfo.h | 3 ++ 4 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/plugins/clangtools/clangtoolssettings.cpp b/src/plugins/clangtools/clangtoolssettings.cpp index 2342e12b1db..fe7acf43b93 100644 --- a/src/plugins/clangtools/clangtoolssettings.cpp +++ b/src/plugins/clangtools/clangtoolssettings.cpp @@ -198,5 +198,38 @@ void ClangToolsSettings::writeSettings() emit changed(); } +void ClangToolsSettings::setClangTidyExecutable(const QString &path) +{ + m_clangTidyExecutable = path; + m_clangTidyVersion = {}; +} + +void ClangTools::Internal::ClangToolsSettings::setClazyStandaloneExecutable(const QString &path) +{ + m_clazyStandaloneExecutable = path; + m_clazyVersion = {}; +} + +static QVersionNumber getVersionNumber(QVersionNumber &version, const QString &toolFilePath) +{ + if (version.isNull() && !toolFilePath.isEmpty()) { + version = QVersionNumber::fromString(queryVersion(Utils::FilePath::fromString(toolFilePath), + QueryFailMode::Silent)); + }; + return version; +} + +QVersionNumber ClangToolsSettings::clangTidyVersion() +{ + return getVersionNumber(instance()->m_clangTidyVersion, + ClangTools::Internal::clangTidyExecutable()); +} + +QVersionNumber ClangToolsSettings::clazyVersion() +{ + return getVersionNumber(instance()->m_clazyVersion, + ClangTools::Internal::clazyStandaloneExecutable()); +} + } // namespace Internal } // namespace ClangTools diff --git a/src/plugins/clangtools/clangtoolssettings.h b/src/plugins/clangtools/clangtoolssettings.h index ac2a6b41856..73dbc83bb26 100644 --- a/src/plugins/clangtools/clangtoolssettings.h +++ b/src/plugins/clangtools/clangtoolssettings.h @@ -25,12 +25,15 @@ #pragma once +#include "executableinfo.h" + #include #include #include #include +#include namespace ClangTools { namespace Internal { @@ -75,10 +78,10 @@ public: void writeSettings(); QString clangTidyExecutable() const { return m_clangTidyExecutable; } - void setClangTidyExecutable(const QString &path) { m_clangTidyExecutable = path; } + void setClangTidyExecutable(const QString &path); QString clazyStandaloneExecutable() const { return m_clazyStandaloneExecutable; } - void setClazyStandaloneExecutable(const QString &path) { m_clazyStandaloneExecutable = path; } + void setClazyStandaloneExecutable(const QString &path); CppTools::ClangDiagnosticConfigs diagnosticConfigs() const { return m_diagnosticConfigs; } void setDiagnosticConfigs(const CppTools::ClangDiagnosticConfigs &configs) @@ -87,6 +90,9 @@ public: RunSettings runSettings() const { return m_runSettings; } void setRunSettings(const RunSettings &settings) { m_runSettings = settings; } + static QVersionNumber clangTidyVersion(); + static QVersionNumber clazyVersion(); + signals: void changed(); @@ -103,6 +109,10 @@ private: // Run settings RunSettings m_runSettings; + + // Version info. Ephemeral. + QVersionNumber m_clangTidyVersion; + QVersionNumber m_clazyVersion; }; } // namespace Internal diff --git a/src/plugins/clangtools/executableinfo.cpp b/src/plugins/clangtools/executableinfo.cpp index 850be6ef59d..d904334ece4 100644 --- a/src/plugins/clangtools/executableinfo.cpp +++ b/src/plugins/clangtools/executableinfo.cpp @@ -43,9 +43,7 @@ using namespace Utils; namespace ClangTools { namespace Internal { -enum class FailSilently { Yes, No }; -static QString runExecutable(const Utils::CommandLine &commandLine, - FailSilently failSilently = FailSilently::No) +static QString runExecutable(const Utils::CommandLine &commandLine, QueryFailMode queryFailMode) { if (commandLine.executable().isEmpty() || !commandLine.executable().toFileInfo().isExecutable()) return {}; @@ -58,7 +56,7 @@ static QString runExecutable(const Utils::CommandLine &commandLine, cpp.runBlocking(); if (cpp.result() != QtcProcess::FinishedWithSuccess - && (failSilently == FailSilently::No + && (queryFailMode == QueryFailMode::Noisy || cpp.result() != QtcProcess::FinishedWithError)) { Core::MessageManager::writeFlashing(cpp.exitMessage()); Core::MessageManager::writeFlashing(QString::fromUtf8(cpp.allRawOutput())); @@ -76,7 +74,7 @@ static QStringList queryClangTidyChecks(const QString &executable, arguments.prepend(checksArgument); const CommandLine commandLine(executable, arguments); - QString output = runExecutable(commandLine); + QString output = runExecutable(commandLine, QueryFailMode::Noisy); if (output.isEmpty()) return {}; @@ -105,12 +103,14 @@ static QStringList queryClangTidyChecks(const QString &executable, static ClazyChecks querySupportedClazyChecks(const QString &executablePath) { static const QString queryFlag = "-supported-checks-json"; - QString jsonOutput = runExecutable(CommandLine(executablePath, {queryFlag})); + QString jsonOutput = runExecutable(CommandLine(executablePath, {queryFlag}), + QueryFailMode::Noisy); // Some clazy 1.6.x versions have a bug where they expect an argument after the // option. if (jsonOutput.isEmpty()) - jsonOutput = runExecutable(CommandLine(executablePath, {queryFlag, "dummy"})); + jsonOutput = runExecutable(CommandLine(executablePath, {queryFlag, "dummy"}), + QueryFailMode::Noisy); if (jsonOutput.isEmpty()) return {}; @@ -172,7 +172,7 @@ static FilePath queryResourceDir(const FilePath &clangToolPath) { QString output = runExecutable(CommandLine(clangToolPath, {"someFilePath", "--", "-print-resource-dir"}), - FailSilently::Yes); + QueryFailMode::Silent); // Expected output is (clang-tidy 10): // lib/clang/10.0.1 @@ -189,9 +189,9 @@ static FilePath queryResourceDir(const FilePath &clangToolPath) return {}; } -static QString queryVersion(const FilePath &clangToolPath) +QString queryVersion(const FilePath &clangToolPath, QueryFailMode failMode) { - QString output = runExecutable(CommandLine(clangToolPath, {"--version"})); + QString output = runExecutable(CommandLine(clangToolPath, {"--version"}), failMode); QTextStream stream(&output); while (!stream.atEnd()) { static const QStringList versionPrefixes{"LLVM version ", "clang version: "}; @@ -207,7 +207,7 @@ static QString queryVersion(const FilePath &clangToolPath) QPair getClangIncludeDirAndVersion(const FilePath &clangToolPath) { const FilePath dynamicResourceDir = queryResourceDir(clangToolPath); - const QString dynamicVersion = queryVersion(clangToolPath); + const QString dynamicVersion = queryVersion(clangToolPath, QueryFailMode::Noisy); if (dynamicResourceDir.isEmpty() || dynamicVersion.isEmpty()) return qMakePair(FilePath::fromString(CLANG_INCLUDE_DIR), QString(CLANG_VERSION)); return qMakePair(dynamicResourceDir + "/include", dynamicVersion); diff --git a/src/plugins/clangtools/executableinfo.h b/src/plugins/clangtools/executableinfo.h index aa297dcb5ef..7ce1eccde6b 100644 --- a/src/plugins/clangtools/executableinfo.h +++ b/src/plugins/clangtools/executableinfo.h @@ -36,6 +36,9 @@ namespace Internal { QPair getClangIncludeDirAndVersion(const Utils::FilePath &clangToolPath); +enum class QueryFailMode{ Silent, Noisy }; +QString queryVersion(const Utils::FilePath &clangToolPath, QueryFailMode failMode); + class ClangTidyInfo { public: From a41524f08c376405d28583d09a23fd33fef97ef7 Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 25 Jun 2021 16:18:20 +0200 Subject: [PATCH 014/149] QtSupport: Expand path variables only to local paths It's what it used to do and actually better for the docker case where the two only active use of these in form of %{Qt:QT_INSTALL_PREFIX} are in a cmake build context that would require (device-)local path. Long term we might want to use a variation of registerFileVariable here without changing semantics on the user side. Change-Id: If8dd77ac0b94ede41dbfe322802d5ef6c0b043ee Reviewed-by: Christian Stenger --- src/plugins/qtsupport/baseqtversion.cpp | 32 ++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/plugins/qtsupport/baseqtversion.cpp b/src/plugins/qtsupport/baseqtversion.cpp index 7e4ff317084..5e512f67844 100644 --- a/src/plugins/qtsupport/baseqtversion.cpp +++ b/src/plugins/qtsupport/baseqtversion.cpp @@ -1486,67 +1486,67 @@ BaseQtVersion::createMacroExpander(const std::function QtKitAspect::tr( "The installation prefix of the current Qt version."), versionProperty([](const BaseQtVersion *version) { - return version->prefix().toString(); + return version->prefix().path(); })); expander->registerVariable("Qt:QT_INSTALL_DATA", QtKitAspect::tr( "The installation location of the current Qt version's data."), versionProperty([](const BaseQtVersion *version) { - return version->dataPath().toString(); + return version->dataPath().path(); })); expander->registerVariable("Qt:QT_HOST_PREFIX", QtKitAspect::tr( "The host location of the current Qt version."), versionProperty([](const BaseQtVersion *version) { - return version->hostPrefixPath().toString(); + return version->hostPrefixPath().path(); })); expander->registerVariable("Qt:QT_HOST_LIBEXECS", QtKitAspect::tr("The installation location of the current Qt " "version's internal host executable files."), versionProperty([](const BaseQtVersion *version) { - return version->hostLibexecPath().toString(); + return version->hostLibexecPath().path(); })); expander->registerVariable( "Qt:QT_INSTALL_HEADERS", QtKitAspect::tr("The installation location of the current Qt version's header files."), versionProperty( - [](const BaseQtVersion *version) { return version->headerPath().toString(); })); + [](const BaseQtVersion *version) { return version->headerPath().path(); })); expander->registerVariable( "Qt:QT_INSTALL_LIBS", QtKitAspect::tr("The installation location of the current Qt version's library files."), versionProperty( - [](const BaseQtVersion *version) { return version->libraryPath().toString(); })); + [](const BaseQtVersion *version) { return version->libraryPath().path(); })); expander->registerVariable( "Qt:QT_INSTALL_DOCS", QtKitAspect::tr( "The installation location of the current Qt version's documentation files."), versionProperty( - [](const BaseQtVersion *version) { return version->docsPath().toString(); })); + [](const BaseQtVersion *version) { return version->docsPath().path(); })); expander->registerVariable( "Qt:QT_INSTALL_BINS", QtKitAspect::tr("The installation location of the current Qt version's executable files."), - versionProperty([](const BaseQtVersion *version) { return version->binPath().toString(); })); + versionProperty([](const BaseQtVersion *version) { return version->binPath().path(); })); expander->registerVariable( "Qt:QT_INSTALL_LIBEXECS", QtKitAspect::tr( "The installation location of the current Qt version's internal executable files."), versionProperty( - [](const BaseQtVersion *version) { return version->libExecPath().toString(); })); + [](const BaseQtVersion *version) { return version->libExecPath().path(); })); expander ->registerVariable("Qt:QT_INSTALL_PLUGINS", QtKitAspect::tr( "The installation location of the current Qt version's plugins."), versionProperty([](const BaseQtVersion *version) { - return version->pluginPath().toString(); + return version->pluginPath().path(); })); expander @@ -1554,7 +1554,7 @@ BaseQtVersion::createMacroExpander(const std::function QtKitAspect::tr( "The installation location of the current Qt version's QML files."), versionProperty([](const BaseQtVersion *version) { - return version->qmlPath().toString(); + return version->qmlPath().path(); })); expander @@ -1562,34 +1562,34 @@ BaseQtVersion::createMacroExpander(const std::function QtKitAspect::tr( "The installation location of the current Qt version's imports."), versionProperty([](const BaseQtVersion *version) { - return version->importsPath().toString(); + return version->importsPath().path(); })); expander->registerVariable( "Qt:QT_INSTALL_TRANSLATIONS", QtKitAspect::tr("The installation location of the current Qt version's translation files."), versionProperty( - [](const BaseQtVersion *version) { return version->translationsPath().toString(); })); + [](const BaseQtVersion *version) { return version->translationsPath().path(); })); expander->registerVariable( "Qt:QT_INSTALL_CONFIGURATION", QtKitAspect::tr("The installation location of the current Qt version's translation files."), versionProperty( - [](const BaseQtVersion *version) { return version->configurationPath().toString(); })); + [](const BaseQtVersion *version) { return version->configurationPath().path(); })); expander ->registerVariable("Qt:QT_INSTALL_EXAMPLES", QtKitAspect::tr( "The installation location of the current Qt version's examples."), versionProperty([](const BaseQtVersion *version) { - return version->examplesPath().toString(); + return version->examplesPath().path(); })); expander->registerVariable("Qt:QT_INSTALL_DEMOS", QtKitAspect::tr( "The installation location of the current Qt version's demos."), versionProperty([](const BaseQtVersion *version) { - return version->demosPath().toString(); + return version->demosPath().path(); })); expander->registerVariable("Qt:QMAKE_MKSPECS", From 782779f8f4d1b572c87ee9d984ccd698aee1cdd5 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Thu, 24 Jun 2021 15:44:59 +0200 Subject: [PATCH 015/149] ClangCodeModel: Remove outdated code Diagnostics from clang-tidy and clazy do not take this route anymore. Change-Id: Ifca2d9861d69dda94638277ec1210d28a3350b12 Reviewed-by: David Schulz --- .../clangdiagnostictooltipwidget.cpp | 36 +------------------ 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp b/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp index 499ae623f6b..34d936561c5 100644 --- a/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp +++ b/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp @@ -218,40 +218,6 @@ private: return text; } - static QString documentationUrlForOption(const Utf8String &optionAsUtf8String) - { - if (optionAsUtf8String.isEmpty()) - return QString(); - - QString option = optionAsUtf8String.toString(); - - // Clazy - if (DiagnosticTextInfo::isClazyOption(option)) { - option = optionAsUtf8String.mid(8); // Remove "-Wclazy-" prefix. - return QString::fromUtf8(CppTools::Constants::CLAZY_DOCUMENTATION_URL_TEMPLATE) - .arg(option); - } - - // Clang itself - if (option.startsWith("-W")) - return QString(); - - // Clang-Tidy - return QString::fromUtf8(CppTools::Constants::TIDY_DOCUMENTATION_URL_TEMPLATE).arg(option); - } - - static QString maybeClickableOption(const Utf8String &option) - { - if (option.isEmpty()) - return option; - - const QString link = documentationUrlForOption(option); - if (link.isEmpty()) - return option; - - return wrapInLink(option.toString(), link); - } - static QString diagnosticCategoryAndEnableOptionRow( const ClangBackEnd::DiagnosticContainer &diagnostic) { @@ -260,7 +226,7 @@ private: " %1" "  %2" " ") - .arg(diagnostic.category, maybeClickableOption(diagnostic.enableOption)); + .arg(diagnostic.category, diagnostic.enableOption); return text; } From 54b40229879d72ecf02b53dd4c3634eb90ffdf0e Mon Sep 17 00:00:00 2001 From: hjk Date: Mon, 28 Jun 2021 13:57:10 +0200 Subject: [PATCH 016/149] Utils: Implement FilePath::rename() And uses it in CMake's fileapi reader. Change-Id: I9e719aa4b253eaca17c6b304eab5e7268fcfab29 Reviewed-by: David Schulz --- src/libs/utils/fileutils.cpp | 9 +++++++++ src/libs/utils/fileutils.h | 2 ++ .../cmakeprojectmanager/fileapireader.cpp | 3 +-- src/plugins/docker/dockerdevice.cpp | 17 +++++++++++++++++ src/plugins/docker/dockerdevice.h | 1 + .../devicesupport/devicemanager.cpp | 6 ++++++ .../projectexplorer/devicesupport/idevice.cpp | 8 ++++++++ .../projectexplorer/devicesupport/idevice.h | 1 + 8 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index 46395830f33..9311801e069 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -1415,6 +1415,15 @@ bool FilePath::copyFile(const FilePath &target) const return QFile::copy(path(), target.path()); } +bool FilePath::renameFile(const FilePath &target) const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.renameFile, return false); + return s_deviceHooks.renameFile(*this, target); + } + return QFile::rename(path(), target.path()); +} + QTextStream &operator<<(QTextStream &s, const FilePath &fn) { return s << fn.toString(); diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index b02b7f095f9..808f140ce58 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -79,6 +79,7 @@ public: std::function exists; std::function removeFile; std::function copyFile; + std::function renameFile; std::function searchInPath; std::function(const FilePath &, const QStringList &, QDir::Filters)> dirEntries; std::function fileContents; @@ -167,6 +168,7 @@ public: QFile::Permissions permissions() const; bool removeFile() const; bool copyFile(const FilePath &target) const; + bool renameFile(const FilePath &target) const; Qt::CaseSensitivity caseSensitivity() const; diff --git a/src/plugins/cmakeprojectmanager/fileapireader.cpp b/src/plugins/cmakeprojectmanager/fileapireader.cpp index fd2705049d1..1ae75671b65 100644 --- a/src/plugins/cmakeprojectmanager/fileapireader.cpp +++ b/src/plugins/cmakeprojectmanager/fileapireader.cpp @@ -311,8 +311,7 @@ void FileApiReader::makeBackupConfiguration(bool store) if (reply.exists()) { if (replyPrev.exists()) FileUtils::removeRecursively(replyPrev); - QDir dir; - if (!dir.rename(reply.toString(), replyPrev.toString())) + if (!reply.renameFile(replyPrev)) Core::MessageManager::writeFlashing(tr("Failed to rename %1 to %2.") .arg(reply.toString(), replyPrev.toString())); diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 5510972443c..9939e2d7988 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -887,6 +887,23 @@ bool DockerDevice::copyFile(const FilePath &filePath, const FilePath &target) co return exitCode == 0; } +bool DockerDevice::renameFile(const FilePath &filePath, const FilePath &target) const +{ + QTC_ASSERT(handlesFile(filePath), return false); + QTC_ASSERT(handlesFile(target), return false); + tryCreateLocalFileAccess(); + if (hasLocalFileAccess()) { + const FilePath localAccess = mapToLocalAccess(filePath); + const FilePath localTarget = mapToLocalAccess(target); + const bool res = localAccess.renameFile(localTarget); + LOG("Move " << filePath.toUserOutput() << localAccess.toUserOutput() << localTarget << res); + return res; + } + const CommandLine cmd("mv", {filePath.path(), target.path()}); + const int exitCode = d->runSynchronously(cmd); + return exitCode == 0; +} + QDateTime DockerDevice::lastModified(const FilePath &filePath) const { QTC_ASSERT(handlesFile(filePath), return {}); diff --git a/src/plugins/docker/dockerdevice.h b/src/plugins/docker/dockerdevice.h index 9e6f856d1cf..49d2ed61730 100644 --- a/src/plugins/docker/dockerdevice.h +++ b/src/plugins/docker/dockerdevice.h @@ -83,6 +83,7 @@ public: bool exists(const Utils::FilePath &filePath) const override; bool removeFile(const Utils::FilePath &filePath) const override; bool copyFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override; + bool renameFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override; Utils::FilePath searchInPath(const Utils::FilePath &filePath) const override; QList directoryEntries(const Utils::FilePath &filePath, const QStringList &nameFilters, diff --git a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp index abdd4f446cb..e5bce263ccb 100644 --- a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp @@ -435,6 +435,12 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_uniquecopyFile(filePath, target); }; + deviceHooks.renameFile = [](const FilePath &filePath, const FilePath &target) { + auto device = DeviceManager::deviceForPath(filePath); + QTC_ASSERT(device, return false); + return device->renameFile(filePath, target); + }; + deviceHooks.searchInPath = [](const FilePath &filePath) { auto device = DeviceManager::deviceForPath(filePath); QTC_ASSERT(device, return FilePath{}); diff --git a/src/plugins/projectexplorer/devicesupport/idevice.cpp b/src/plugins/projectexplorer/devicesupport/idevice.cpp index 1528590a785..6292bc85d0b 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/idevice.cpp @@ -289,6 +289,14 @@ bool IDevice::copyFile(const FilePath &filePath, const FilePath &target) const return false; } +bool IDevice::renameFile(const FilePath &filePath, const FilePath &target) const +{ + Q_UNUSED(filePath); + Q_UNUSED(target); + QTC_CHECK(false); + return false; +} + FilePath IDevice::searchInPath(const FilePath &filePath) const { return Environment::systemEnvironment().searchInPath(filePath.path()); diff --git a/src/plugins/projectexplorer/devicesupport/idevice.h b/src/plugins/projectexplorer/devicesupport/idevice.h index 1ec53b63e4a..876bd4bf27e 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.h +++ b/src/plugins/projectexplorer/devicesupport/idevice.h @@ -246,6 +246,7 @@ public: virtual bool exists(const Utils::FilePath &filePath) const; virtual bool removeFile(const Utils::FilePath &filePath) const; virtual bool copyFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const; + virtual bool renameFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const; virtual Utils::FilePath searchInPath(const Utils::FilePath &filePath) const; virtual QList directoryEntries(const Utils::FilePath &filePath, const QStringList &nameFilters, From 7bf52b0c0d3f13bb33048f9ab7d88df92a23c614 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Wed, 2 Jun 2021 17:51:31 +0200 Subject: [PATCH 017/149] ClangCodeModel: Provide diagnostics via clangd Change-Id: Ib45a62ebe200c2b56a1bb1a66f8a92103e60d092 Reviewed-by: David Schulz --- .../clangcodemodel/clangbackendreceiver.cpp | 52 ++++---- src/plugins/clangcodemodel/clangdclient.cpp | 62 ++++++++- src/plugins/clangcodemodel/clangdclient.h | 10 ++ .../clangcodemodel/clangdiagnosticmanager.cpp | 3 +- .../clangcodemodel/clangdiagnosticmanager.h | 2 + .../clangdiagnostictooltipwidget.cpp | 19 ++- .../clangdiagnostictooltipwidget.h | 5 +- .../clangeditordocumentprocessor.cpp | 5 +- src/plugins/clangcodemodel/clangtextmark.cpp | 121 +++++++++++++++++- src/plugins/clangcodemodel/clangtextmark.h | 20 +++ .../test/clangcodecompletion_test.cpp | 1 - src/plugins/languageclient/client.cpp | 12 ++ src/plugins/languageclient/client.h | 6 +- .../languageclient/diagnosticmanager.cpp | 31 ++++- .../languageclient/diagnosticmanager.h | 19 ++- 15 files changed, 320 insertions(+), 48 deletions(-) diff --git a/src/plugins/clangcodemodel/clangbackendreceiver.cpp b/src/plugins/clangcodemodel/clangbackendreceiver.cpp index f9cf7badeaf..181a7f51028 100644 --- a/src/plugins/clangcodemodel/clangbackendreceiver.cpp +++ b/src/plugins/clangcodemodel/clangbackendreceiver.cpp @@ -41,8 +41,6 @@ #define qCDebugIpc() qCDebug(ipcLog) << "<====" -using namespace ClangBackEnd; - namespace ClangCodeModel { namespace Internal { @@ -189,12 +187,12 @@ void BackendReceiver::alive() m_aliveHandler(); } -void BackendReceiver::echo(const EchoMessage &message) +void BackendReceiver::echo(const ClangBackEnd::EchoMessage &message) { qCDebugIpc() << message; } -void BackendReceiver::completions(const CompletionsMessage &message) +void BackendReceiver::completions(const ClangBackEnd::CompletionsMessage &message) { qCDebugIpc() << "CompletionsMessage with" << message.codeCompletions.size() << "items"; @@ -204,7 +202,7 @@ void BackendReceiver::completions(const CompletionsMessage &message) processor->handleAvailableCompletions(message.codeCompletions); } -void BackendReceiver::annotations(const AnnotationsMessage &message) +void BackendReceiver::annotations(const ClangBackEnd::AnnotationsMessage &message) { qCDebugIpc() << "AnnotationsMessage" << "for" << QFileInfo(message.fileContainer.filePath).fileName() << "with" @@ -230,10 +228,10 @@ void BackendReceiver::annotations(const AnnotationsMessage &message) } static -CppTools::CursorInfo::Range toCursorInfoRange(const SourceRangeContainer &sourceRange) +CppTools::CursorInfo::Range toCursorInfoRange(const ClangBackEnd::SourceRangeContainer &sourceRange) { - const SourceLocationContainer &start = sourceRange.start; - const SourceLocationContainer &end = sourceRange.end; + const ClangBackEnd::SourceLocationContainer &start = sourceRange.start; + const ClangBackEnd::SourceLocationContainer &end = sourceRange.end; const int length = end.column - start.column; return {start.line, start.column, length}; @@ -241,13 +239,13 @@ CppTools::CursorInfo::Range toCursorInfoRange(const SourceRangeContainer &source static CppTools::CursorInfo toCursorInfo(const CppTools::SemanticInfo::LocalUseMap &localUses, - const ReferencesMessage &message) + const ClangBackEnd::ReferencesMessage &message) { CppTools::CursorInfo result; - const QVector &references = message.references; + const QVector &references = message.references; result.areUseRangesForLocalVariable = message.isLocalVariable; - for (const SourceRangeContainer &reference : references) + for (const ClangBackEnd::SourceRangeContainer &reference : references) result.useRanges.append(toCursorInfoRange(reference)); result.useRanges.reserve(references.size()); @@ -257,13 +255,13 @@ CppTools::CursorInfo toCursorInfo(const CppTools::SemanticInfo::LocalUseMap &loc } static -CppTools::SymbolInfo toSymbolInfo(const FollowSymbolMessage &message) +CppTools::SymbolInfo toSymbolInfo(const ClangBackEnd::FollowSymbolMessage &message) { CppTools::SymbolInfo result; - const SourceRangeContainer &range = message.result.range; + const ClangBackEnd::SourceRangeContainer &range = message.result.range; - const SourceLocationContainer &start = range.start; - const SourceLocationContainer &end = range.end; + const ClangBackEnd::SourceLocationContainer &start = range.start; + const ClangBackEnd::SourceLocationContainer &end = range.end; result.startLine = start.line; result.startColumn = start.column; result.endLine = end.line; @@ -275,7 +273,7 @@ CppTools::SymbolInfo toSymbolInfo(const FollowSymbolMessage &message) return result; } -void BackendReceiver::references(const ReferencesMessage &message) +void BackendReceiver::references(const ClangBackEnd::ReferencesMessage &message) { qCDebugIpc() << "ReferencesMessage with" << message.references.size() << "references"; @@ -292,22 +290,22 @@ void BackendReceiver::references(const ReferencesMessage &message) futureInterface.reportFinished(); } -static Core::HelpItem::Category toHelpItemCategory(ToolTipInfo::QdocCategory category) +static Core::HelpItem::Category toHelpItemCategory(ClangBackEnd::ToolTipInfo::QdocCategory category) { switch (category) { - case ToolTipInfo::Unknown: + case ClangBackEnd::ToolTipInfo::Unknown: return Core::HelpItem::Unknown; - case ToolTipInfo::ClassOrNamespace: + case ClangBackEnd::ToolTipInfo::ClassOrNamespace: return Core::HelpItem::ClassOrNamespace; - case ToolTipInfo::Enum: + case ClangBackEnd::ToolTipInfo::Enum: return Core::HelpItem::Enum; - case ToolTipInfo::Typedef: + case ClangBackEnd::ToolTipInfo::Typedef: return Core::HelpItem::Typedef; - case ToolTipInfo::Macro: + case ClangBackEnd::ToolTipInfo::Macro: return Core::HelpItem::Macro; - case ToolTipInfo::Brief: + case ClangBackEnd::ToolTipInfo::Brief: return Core::HelpItem::Brief; - case ToolTipInfo::Function: + case ClangBackEnd::ToolTipInfo::Function: return Core::HelpItem::Function; } @@ -325,11 +323,11 @@ static QStringList toStringList(const Utf8StringVector &utf8StringVector) return list; } -static CppTools::ToolTipInfo toToolTipInfo(const ToolTipMessage &message) +static CppTools::ToolTipInfo toToolTipInfo(const ClangBackEnd::ToolTipMessage &message) { CppTools::ToolTipInfo info; - const ToolTipInfo &backendInfo = message.toolTipInfo; + const ClangBackEnd::ToolTipInfo &backendInfo = message.toolTipInfo; info.text = backendInfo.text; info.briefComment = backendInfo.briefComment; @@ -343,7 +341,7 @@ static CppTools::ToolTipInfo toToolTipInfo(const ToolTipMessage &message) return info; } -void BackendReceiver::tooltip(const ToolTipMessage &message) +void BackendReceiver::tooltip(const ClangBackEnd::ToolTipMessage &message) { qCDebugIpc() << "ToolTipMessage" << message.toolTipInfo.text; diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 26721c84219..fd7a8883365 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -25,6 +25,9 @@ #include "clangdclient.h" +#include "clangdiagnosticmanager.h" +#include "clangtextmark.h" + #include #include #include @@ -37,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -604,6 +608,23 @@ public: const int revision; }; +class DiagnosticsCapabilities : public JsonObject +{ +public: + using JsonObject::JsonObject; + void enableCategorySupport() { insert("categorySupport", true); } + void enableCodeActionsInline() {insert("codeActionsInline", true);} +}; + +class ClangdTextDocumentClientCapabilities : public TextDocumentClientCapabilities +{ +public: + using TextDocumentClientCapabilities::TextDocumentClientCapabilities; + + + void setPublishDiagnostics(const DiagnosticsCapabilities &caps) + { insert("publishDiagnostics", caps); } +}; class ClangdClient::Private { @@ -650,12 +671,30 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir) langFilter.mimeTypes = QStringList{"text/x-chdr", "text/x-csrc", "text/x-c++hdr", "text/x-c++src", "text/x-objc++src", "text/x-objcsrc"}; setSupportedLanguage(langFilter); - LanguageServerProtocol::ClientCapabilities caps = Client::defaultClientCapabilities(); + setActivateDocumentAutomatically(true); + ClientCapabilities caps = Client::defaultClientCapabilities(); + Utils::optional textCaps = caps.textDocument(); + if (textCaps) { + ClangdTextDocumentClientCapabilities clangdTextCaps(*textCaps); + clangdTextCaps.clearCompletion(); + clangdTextCaps.clearDocumentHighlight(); + DiagnosticsCapabilities diagnostics; + diagnostics.enableCategorySupport(); + diagnostics.enableCodeActionsInline(); + clangdTextCaps.setPublishDiagnostics(diagnostics); + caps.setTextDocument(clangdTextCaps); + } caps.clearExperimental(); setClientCapabilities(caps); setLocatorsEnabled(false); setProgressTitleForToken(indexingToken(), tr("Parsing C/C++ Files (clangd)")); setCurrentProject(project); + + const auto textMarkCreator = [this](const Utils::FilePath &filePath, + const Diagnostic &diag) { return new ClangdTextMark(filePath, diag, this); }; + const auto hideDiagsHandler = []{ ClangDiagnosticManager::clearTaskHubIssues(); }; + setDiagnosticsHandlers(textMarkCreator, hideDiagsHandler); + connect(this, &Client::workDone, this, [this, project](const ProgressToken &token) { const QString * const val = Utils::get_if(&token); if (val && *val == indexingToken()) { @@ -791,6 +830,17 @@ void ClangdClient::findUsages(TextEditor::TextDocument *document, const QTextCur void ClangdClient::enableTesting() { d->isTesting = true; } +void ClangdClient::handleDiagnostics(const PublishDiagnosticsParams ¶ms) +{ + const DocumentUri &uri = params.uri(); + Client::handleDiagnostics(params); + for (const Diagnostic &diagnostic : params.diagnostics()) { + const ClangdDiagnostic clangdDiagnostic(diagnostic); + for (const CodeAction &action : clangdDiagnostic.codeActions().value_or(QList{})) + LanguageClient::updateCodeActionRefactoringMarker(this, action, uri); + } +} + QVersionNumber ClangdClient::versionNumber() const { if (d->versionNumber) @@ -1506,6 +1556,16 @@ TextEditor::IAssistProcessor *ClangdClient::VirtualFunctionAssistProvider::creat = new VirtualFunctionAssistProcessor(m_data); } +Utils::optional > ClangdDiagnostic::codeActions() const +{ + return optionalArray("codeActions"); +} + +QString ClangdDiagnostic::category() const +{ + return typedValue("category"); +} + } // namespace Internal } // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/clangdclient.h b/src/plugins/clangcodemodel/clangdclient.h index b07de7e95ce..49d60e7c314 100644 --- a/src/plugins/clangcodemodel/clangdclient.h +++ b/src/plugins/clangcodemodel/clangdclient.h @@ -78,6 +78,8 @@ signals: void findUsagesDone(); private: + void handleDiagnostics(const LanguageServerProtocol::PublishDiagnosticsParams ¶ms) override; + class Private; class FollowSymbolData; class VirtualFunctionAssistProcessor; @@ -85,5 +87,13 @@ private: Private * const d; }; +class ClangdDiagnostic : public LanguageServerProtocol::Diagnostic +{ +public: + using Diagnostic::Diagnostic; + Utils::optional> codeActions() const; + QString category() const; +}; + } // namespace Internal } // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/clangdiagnosticmanager.cpp b/src/plugins/clangcodemodel/clangdiagnosticmanager.cpp index 666282d8fda..ee7b787118e 100644 --- a/src/plugins/clangcodemodel/clangdiagnosticmanager.cpp +++ b/src/plugins/clangcodemodel/clangdiagnosticmanager.cpp @@ -298,7 +298,8 @@ void ClangDiagnosticManager::generateFixItAvailableMarkers() addFixItAvailableMarker(m_errorDiagnostics, lineNumbersWithFixItMarker); } -static void addTask(const ClangBackEnd::DiagnosticContainer &diagnostic, bool isChild = false) +void ClangDiagnosticManager::addTask(const ClangBackEnd::DiagnosticContainer &diagnostic, + bool isChild) { using namespace ProjectExplorer; using ::Utils::FilePath; diff --git a/src/plugins/clangcodemodel/clangdiagnosticmanager.h b/src/plugins/clangcodemodel/clangdiagnosticmanager.h index 9d5b7a6571c..5c2ed3c8db4 100644 --- a/src/plugins/clangcodemodel/clangdiagnosticmanager.h +++ b/src/plugins/clangcodemodel/clangdiagnosticmanager.h @@ -66,6 +66,8 @@ public: static void clearTaskHubIssues(); void generateTaskHubIssues(); + static void addTask(const ClangBackEnd::DiagnosticContainer &diagnostic, bool isChild = false); + private: void cleanMarks(); QString filePath() const; diff --git a/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp b/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp index 34d936561c5..326dfee0ef9 100644 --- a/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp +++ b/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.cpp @@ -104,7 +104,7 @@ public: } QWidget *createWidget(const QVector &diagnostics, - const ClangDiagnosticManager *diagMgr) + const std::function &canApplyFixIt) { const QString text = htmlText(diagnostics); @@ -133,7 +133,7 @@ public: const TargetIdToDiagnosticTable table = m_targetIdsToDiagnostics; const bool hideToolTipAfterLinkActivation = m_displayHints.hideTooltipAfterLinkActivation; QObject::connect(label, &QLabel::linkActivated, [table, hideToolTipAfterLinkActivation, - diagMgr](const QString &action) { + canApplyFixIt](const QString &action) { const ClangBackEnd::DiagnosticContainer diagnostic = table.value(action); if (diagnostic == ClangBackEnd::DiagnosticContainer()) @@ -141,10 +141,8 @@ public: else if (action.startsWith(LINK_ACTION_GOTO_LOCATION)) { openEditorAt(diagnostic); } else if (action.startsWith(LINK_ACTION_APPLY_FIX)) { - if (diagMgr && !diagMgr->diagnosticsInvalidated() - && diagMgr->diagnosticsWithFixIts().contains(diagnostic)) { + if (canApplyFixIt && canApplyFixIt()) applyFixit(diagnostic); - } } else { QTC_CHECK(!"Link target cannot be handled."); } @@ -368,14 +366,14 @@ private: }; WidgetFromDiagnostics::DisplayHints toHints(const ClangDiagnosticWidget::Destination &destination, - const ClangDiagnosticManager *diagMgr = nullptr) + const std::function &canApplyFixIt) { WidgetFromDiagnostics::DisplayHints hints; if (destination == ClangDiagnosticWidget::ToolTip) { hints.showCategoryAndEnableOption = true; hints.showFileNameInMainDiagnostic = false; - hints.enableClickableFixits = diagMgr && !diagMgr->diagnosticsInvalidated(); + hints.enableClickableFixits = canApplyFixIt && canApplyFixIt(); hints.limitWidth = true; hints.hideTooltipAfterLinkActivation = true; hints.allowTextSelection = false; @@ -398,7 +396,7 @@ QString ClangDiagnosticWidget::createText( const QVector &diagnostics, const ClangDiagnosticWidget::Destination &destination) { - const QString htmlText = WidgetFromDiagnostics(toHints(destination)).htmlText(diagnostics); + const QString htmlText = WidgetFromDiagnostics(toHints(destination, {})).htmlText(diagnostics); QTextDocument document; document.setHtml(htmlText); @@ -413,9 +411,10 @@ QString ClangDiagnosticWidget::createText( } QWidget *ClangDiagnosticWidget::createWidget(const QVector &diagnostics, - const Destination &destination, const ClangDiagnosticManager *diagMgr) + const Destination &destination, const std::function &canApplyFixIt) { - return WidgetFromDiagnostics(toHints(destination, diagMgr)).createWidget(diagnostics, diagMgr); + return WidgetFromDiagnostics(toHints(destination, canApplyFixIt)) + .createWidget(diagnostics, canApplyFixIt); } } // namespace Internal diff --git a/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.h b/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.h index 48fa0726952..afe35812ae0 100644 --- a/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.h +++ b/src/plugins/clangcodemodel/clangdiagnostictooltipwidget.h @@ -27,6 +27,8 @@ #include +#include + QT_BEGIN_NAMESPACE class QLayout; class QWidget; @@ -43,9 +45,10 @@ public: static QString createText(const QVector &diagnostics, const Destination &destination); + static QWidget *createWidget(const QVector &diagnostics, const Destination &destination, - const ClangDiagnosticManager *diagMgr = nullptr); + const std::function &canApplyFixIt); }; } // namespace Internal diff --git a/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp b/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp index 58eae03422d..803d7de6175 100644 --- a/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp +++ b/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp @@ -188,6 +188,9 @@ void ClangEditorDocumentProcessor::updateCodeWarnings( const ClangBackEnd::DiagnosticContainer &firstHeaderErrorDiagnostic, uint documentRevision) { + if (ClangModelManagerSupport::instance()->clientForFile(m_document.filePath())) + return; + if (documentRevision == revision()) { if (m_invalidationState == InvalidationState::Scheduled) m_invalidationState = InvalidationState::Canceled; @@ -489,7 +492,7 @@ ClangEditorDocumentProcessor::creatorForHeaderErrorDiagnosticWidget( vbox->setSpacing(2); vbox->addWidget(ClangDiagnosticWidget::createWidget({firstHeaderErrorDiagnostic}, - ClangDiagnosticWidget::InfoBar)); + ClangDiagnosticWidget::InfoBar, {})); auto widget = new QWidget; widget->setLayout(vbox); diff --git a/src/plugins/clangcodemodel/clangtextmark.cpp b/src/plugins/clangcodemodel/clangtextmark.cpp index 188e0de08d5..c90c5fcf72c 100644 --- a/src/plugins/clangcodemodel/clangtextmark.cpp +++ b/src/plugins/clangcodemodel/clangtextmark.cpp @@ -26,6 +26,7 @@ #include "clangtextmark.h" #include "clangconstants.h" +#include "clangdclient.h" #include "clangdiagnostictooltipwidget.h" #include "clangeditordocumentprocessor.h" #include "clangmodelmanagersupport.h" @@ -47,10 +48,13 @@ #include #include #include +#include #include using namespace CppTools; using namespace ClangCodeModel::Internal; +using namespace LanguageClient; +using namespace LanguageServerProtocol; using namespace Utils; namespace ClangCodeModel { @@ -272,10 +276,13 @@ void ClangTextMark::updateIcon(bool valid) bool ClangTextMark::addToolTipContent(QLayout *target) const { - + const auto canApplyFixIt = [diag = m_diagnostic, diagMgr = m_diagMgr, c = color()] { + return c != Utils::Theme::Color::IconsDisabledColor + && !diagMgr->diagnosticsInvalidated() + && diagMgr->diagnosticsWithFixIts().contains(diag); + }; QWidget *widget = ClangDiagnosticWidget::createWidget( - {m_diagnostic}, ClangDiagnosticWidget::ToolTip, - color() == Utils::Theme::Color::IconsDisabledColor ? nullptr : m_diagMgr); + {m_diagnostic}, ClangDiagnosticWidget::ToolTip, canApplyFixIt); target->addWidget(widget); return true; @@ -287,5 +294,113 @@ void ClangTextMark::removedFromEditor() m_removedFromEditorHandler(this); } +ClangBackEnd::DiagnosticSeverity convertSeverity(DiagnosticSeverity src) +{ + if (src == DiagnosticSeverity::Error) + return ClangBackEnd::DiagnosticSeverity::Error; + if (src == DiagnosticSeverity::Warning) + return ClangBackEnd::DiagnosticSeverity::Warning; + return ClangBackEnd::DiagnosticSeverity::Note; +} + +ClangBackEnd::SourceRangeContainer convertRange(const FilePath &filePath, const Range &src) +{ + const ClangBackEnd::SourceLocationContainer start(filePath.toString(), src.start().line() + 1, + src.start().character() + 1); + const ClangBackEnd::SourceLocationContainer end(filePath.toString(), src.end().line() + 1, + src.end().character() + 1); + return ClangBackEnd::SourceRangeContainer(start, end); +} + +ClangBackEnd::DiagnosticContainer convertDiagnostic(const ClangdDiagnostic &src, + const FilePath &filePath) +{ + ClangBackEnd::DiagnosticContainer target; + target.ranges.append(convertRange(filePath, src.range())); + target.location = target.ranges.first().start; + target.text = src.message(); + target.category = src.category(); + if (src.severity()) + target.severity = convertSeverity(*src.severity()); + const Diagnostic::Code code = src.code().value_or(Diagnostic::Code()); + const QString * const codeString = Utils::get_if(&code); + if (codeString && codeString->startsWith("-W")) + target.enableOption = *codeString; + for (const CodeAction &codeAction : src.codeActions().value_or(QList())) { + const Utils::optional edit = codeAction.edit(); + if (!edit) + continue; + const Utils::optional changes = edit->changes(); + if (!changes) + continue; + for (auto it = changes->cbegin(); it != changes->cend(); ++it) { + for (const TextEdit &textEdit : it.value()) { + target.fixIts << ClangBackEnd::FixItContainer(textEdit.newText(), + convertRange(it.key().toFilePath(), textEdit.range())); + } + } + } + return target; +} + +ClangdTextMark::ClangdTextMark(const FilePath &filePath, + const Diagnostic &diagnostic, + const Client *client) + : TextEditor::TextMark(filePath, int(diagnostic.range().start().line() + 1), client->id()) + , m_lspDiagnostic(diagnostic) + , m_diagnostic(convertDiagnostic(ClangdDiagnostic(diagnostic), filePath)) + , m_client(client) +{ + setSettingsPage(CppTools::Constants::CPP_CODE_MODEL_SETTINGS_ID); + + const bool isError = diagnostic.severity() + && *diagnostic.severity() == DiagnosticSeverity::Error; + setDefaultToolTip(isError ? tr("Code Model Error") : tr("Code Model Warning")); + setPriority(isError ? TextEditor::TextMark::HighPriority + : TextEditor::TextMark::NormalPriority); + setIcon(isError ? Icons::CODEMODEL_ERROR.icon() : Icons::CODEMODEL_WARNING.icon()); + setLineAnnotation(diagnostic.message()); + setColor(isError ? Theme::CodeModel_Error_TextMarkColor + : Theme::CodeModel_Warning_TextMarkColor); + + // Copy to clipboard action + QVector actions; + QAction *action = new QAction(); + action->setIcon(QIcon::fromTheme("edit-copy", Icons::COPY.icon())); + action->setToolTip(tr("Clang Code Model Marks", "Copy to Clipboard")); + QObject::connect(action, &QAction::triggered, [diag = m_diagnostic]() { + const QString text = ClangDiagnosticWidget::createText({diag}, + ClangDiagnosticWidget::InfoBar); + QApplication::clipboard()->setText(text, QClipboard::Clipboard); + }); + actions << action; + + // Remove diagnostic warning action + ProjectExplorer::Project *project = projectForCurrentEditor(); + if (project && isDiagnosticConfigChangable(project, m_diagnostic)) { + action = new QAction(); + action->setIcon(Icons::BROKEN.icon()); + action->setToolTip(tr("Disable Diagnostic in Current Project")); + QObject::connect(action, &QAction::triggered, [diag = m_diagnostic]() { + disableDiagnosticInCurrentProjectConfig(diag); + }); + actions << action; + } + + setActions(actions); + + ClangDiagnosticManager::addTask(m_diagnostic); +} + +bool ClangdTextMark::addToolTipContent(QLayout *target) const +{ + const auto canApplyFixIt = [c = QPointer(m_client), diag = m_lspDiagnostic, fp = fileName()] { + return c && c->reachable() && c->hasDiagnostic(DocumentUri::fromFilePath(fp), diag); + }; + target->addWidget(ClangDiagnosticWidget::createWidget({m_diagnostic}, + ClangDiagnosticWidget::ToolTip, canApplyFixIt)); + return true; +} + } // namespace Internal } // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/clangtextmark.h b/src/plugins/clangcodemodel/clangtextmark.h index a7fbdc65268..8361f6bf16e 100644 --- a/src/plugins/clangcodemodel/clangtextmark.h +++ b/src/plugins/clangcodemodel/clangtextmark.h @@ -28,10 +28,14 @@ #include #include +#include + #include #include +namespace LanguageClient { class Client; } + namespace ClangCodeModel { namespace Internal { class ClangDiagnosticManager; @@ -60,5 +64,21 @@ private: const ClangDiagnosticManager * const m_diagMgr; }; +class ClangdTextMark : public TextEditor::TextMark +{ + Q_DECLARE_TR_FUNCTIONS(ClangdTextMark) +public: + ClangdTextMark(const ::Utils::FilePath &filePath, + const LanguageServerProtocol::Diagnostic &diagnostic, + const LanguageClient::Client *client); + +private: + bool addToolTipContent(QLayout *target) const override; + + const LanguageServerProtocol::Diagnostic m_lspDiagnostic; + const ClangBackEnd::DiagnosticContainer m_diagnostic; + const LanguageClient::Client * const m_client; +}; + } // namespace Internal } // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp b/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp index a0d8aef4639..36db72ff58a 100644 --- a/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp +++ b/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp @@ -52,7 +52,6 @@ #include #include -using namespace ClangBackEnd; using namespace ClangCodeModel; using namespace ClangCodeModel::Internal; diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index 33ee2c868d7..c627e51b829 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -991,6 +991,18 @@ QList Client::diagnosticsAt(const DocumentUri &uri, const QTextCurso return m_diagnosticManager.diagnosticsAt(uri, cursor); } +bool Client::hasDiagnostic(const LanguageServerProtocol::DocumentUri &uri, + const LanguageServerProtocol::Diagnostic &diag) const +{ + return m_diagnosticManager.hasDiagnostic(uri, documentForFilePath(uri.toFilePath()), diag); +} + +void Client::setDiagnosticsHandlers(const TextMarkCreator &textMarkCreator, + const HideDiagnosticsHandler &hideHandler) +{ + m_diagnosticManager.setDiagnosticsHandlers(textMarkCreator, hideHandler); +} + void Client::start() { if (m_clientInterface->start()) diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h index b76f706c30a..6fd0f3f2934 100644 --- a/src/plugins/languageclient/client.h +++ b/src/plugins/languageclient/client.h @@ -170,6 +170,10 @@ public: QList diagnosticsAt( const LanguageServerProtocol::DocumentUri &uri, const QTextCursor &cursor) const; + bool hasDiagnostic(const LanguageServerProtocol::DocumentUri &uri, + const LanguageServerProtocol::Diagnostic &diag) const; + void setDiagnosticsHandlers(const TextMarkCreator &textMarkCreator, + const HideDiagnosticsHandler &hideHandler); // logging void log(const QString &message) const; @@ -189,6 +193,7 @@ protected: void setProgressTitleForToken(const LanguageServerProtocol::ProgressToken &token, const QString &message); void handleMessage(const LanguageServerProtocol::BaseMessage &message); + virtual void handleDiagnostics(const LanguageServerProtocol::PublishDiagnosticsParams ¶ms); private: void handleResponse(const LanguageServerProtocol::MessageId &id, const QByteArray &content, @@ -196,7 +201,6 @@ private: void handleMethod(const QString &method, const LanguageServerProtocol::MessageId &id, const LanguageServerProtocol::IContent *content); - void handleDiagnostics(const LanguageServerProtocol::PublishDiagnosticsParams ¶ms); void handleSemanticHighlight(const LanguageServerProtocol::SemanticHighlightingParams ¶ms); void initializeCallback(const LanguageServerProtocol::InitializeRequest::Response &initResponse); diff --git a/src/plugins/languageclient/diagnosticmanager.cpp b/src/plugins/languageclient/diagnosticmanager.cpp index ed06bb8dde9..67068c30ea0 100644 --- a/src/plugins/languageclient/diagnosticmanager.cpp +++ b/src/plugins/languageclient/diagnosticmanager.cpp @@ -91,6 +91,8 @@ void DiagnosticManager::hideDiagnostics(TextDocument *doc) if (!doc) return; + if (m_hideHandler) + m_hideHandler(); for (BaseTextEditor *editor : BaseTextEditor::textEditorsForDocument(doc)) editor->editorWidget()->setExtraSelections(TextEditorWidget::CodeWarningsSelection, {}); qDeleteAll(Utils::filtered(doc->marks(), Utils::equal(&TextMark::category, m_clientId))); @@ -127,6 +129,12 @@ void DiagnosticManager::showDiagnostics(const DocumentUri &uri, int version) const auto icon = QIcon::fromTheme("edit-copy", Utils::Icons::COPY.icon()); const QString tooltip = tr("Copy to Clipboard"); for (const Diagnostic &diagnostic : versionedDiagnostics.diagnostics) { + extraSelections << toDiagnosticsSelections(diagnostic, doc->document()); + if (m_textMarkCreator) { + doc->addMark(m_textMarkCreator(filePath, diagnostic)); + continue; + } + QAction *action = new QAction(); action->setIcon(icon); action->setToolTip(tooltip); @@ -137,7 +145,6 @@ void DiagnosticManager::showDiagnostics(const DocumentUri &uri, int version) mark->setActions({action}); doc->addMark(mark); - extraSelections << toDiagnosticsSelections(diagnostic, doc->document()); } } @@ -168,4 +175,26 @@ QList DiagnosticManager::diagnosticsAt(const DocumentUri &uri, }); } +bool DiagnosticManager::hasDiagnostic(const LanguageServerProtocol::DocumentUri &uri, + const TextDocument *doc, + const LanguageServerProtocol::Diagnostic &diag) const +{ + if (!doc) + return false; + const auto it = m_diagnostics.find(uri); + if (it == m_diagnostics.end()) + return {}; + const int revision = doc->document()->revision(); + if (revision != it->version.value_or(revision)) + return false; + return it->diagnostics.contains(diag); +} + +void DiagnosticManager::setDiagnosticsHandlers(const TextMarkCreator &textMarkCreator, + const HideDiagnosticsHandler &removalHandler) +{ + m_textMarkCreator = textMarkCreator; + m_hideHandler = removalHandler; +} + } // namespace LanguageClient diff --git a/src/plugins/languageclient/diagnosticmanager.h b/src/plugins/languageclient/diagnosticmanager.h index ac38169d7ef..7d4303b2404 100644 --- a/src/plugins/languageclient/diagnosticmanager.h +++ b/src/plugins/languageclient/diagnosticmanager.h @@ -31,10 +31,19 @@ #include -namespace TextEditor { class TextDocument; } +#include + +namespace TextEditor { +class TextDocument; +class TextMark; +} namespace LanguageClient { +using TextMarkCreator = std::function; +using HideDiagnosticsHandler = std::function; + class DiagnosticManager { Q_DECLARE_TR_FUNCTIONS(LanguageClient::DiagnosticManager) @@ -55,6 +64,12 @@ public: QList diagnosticsAt( const LanguageServerProtocol::DocumentUri &uri, const QTextCursor &cursor) const; + bool hasDiagnostic(const LanguageServerProtocol::DocumentUri &uri, + const TextEditor::TextDocument *doc, + const LanguageServerProtocol::Diagnostic &diag) const; + + void setDiagnosticsHandlers(const TextMarkCreator &shownHandler, + const HideDiagnosticsHandler &removalHandler); private: struct VersionedDiagnostics { @@ -63,6 +78,8 @@ private: }; QMap m_diagnostics; Utils::Id m_clientId; + TextMarkCreator m_textMarkCreator; + HideDiagnosticsHandler m_hideHandler; }; } // namespace LanguageClient From 20533cc3986f9f118762b3e22da7dbb9cbf23609 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Mon, 28 Jun 2021 15:00:30 +0200 Subject: [PATCH 018/149] Doc: Describe viewing CMake output in Projects mode Task-number: QTCREATORBUG-25642 Change-Id: I4c9ddf59bae1189e672b1a9a6129970ff3299744 Reviewed-by: Cristian Adam --- .../images/qtcreator-build-cmake-output.png | Bin 0 -> 28639 bytes .../creator-projects-cmake-building.qdoc | 20 ++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 doc/qtcreator/images/qtcreator-build-cmake-output.png diff --git a/doc/qtcreator/images/qtcreator-build-cmake-output.png b/doc/qtcreator/images/qtcreator-build-cmake-output.png new file mode 100644 index 0000000000000000000000000000000000000000..886380206524419ff087356ee49a4066ec15bf6b GIT binary patch literal 28639 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 literal 0 HcmV?d00001 diff --git a/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdoc b/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdoc index 57c62502330..66ae154d58c 100644 --- a/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdoc +++ b/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdoc @@ -119,6 +119,26 @@ \image qtcreator-build-run-options-cmake.png "CMake Build & Run options" + \section1 Viewing CMake Output + + Output from CMake is displayed next to the \uicontrol {Build Settings} and + \uicontrol {Run Settings} panes in the \uicontrol Projects mode. + + \image qtcreator-build-cmake-output.png "CMake output in Projects mode" + + To clear the search results, select the \inlineimage clean_pane_small.png + (\uicontrol Clear) button. + + You can enter a string in the \uicontrol Filter field to filter output. + To specify filtering options, select the \inlineimage magnifier.png "Filtering options menu" + button. You can filter output by using regular expressions or + case-sensitivity. Select \uicontrol {Show Non-matching Lines} to + hide the lines that match the filter. + + To increase or decrease the output text size, select \inlineimage plus.png + (\uicontrol {Zoom In}) or \inlineimage minus.png + (\uicontrol {Zoom Out}), or press \key Ctrl++ or \key Ctrl+-. + \section1 CMake Build Steps \QC builds CMake projects by running \c {cmake . --build}, which then runs From 45b41a04c392a61e44a05c501d313cc6100eebc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kama=20W=C3=B3jcik?= Date: Mon, 28 Jun 2021 15:12:42 +0200 Subject: [PATCH 019/149] QmlDesigner: Fix layout and tooltip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix layout for AnimatedImage * Fix tooltip for Animation section Change-Id: Ibdcc2e34317555396ad51fbf873dfcee1adbcf55 Reviewed-by: Henning Gründl Reviewed-by: Tim Jenssen --- .../QtQuick/AnimatedImageSpecifics.qml | 46 +++++++++---------- .../QtQuick/AnimationSection.qml | 2 +- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/AnimatedImageSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/AnimatedImageSpecifics.qml index 6c59408a588..1d4e8fe87ce 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/AnimatedImageSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/AnimatedImageSpecifics.qml @@ -34,11 +34,11 @@ Column { anchors.right: parent.right ImageSection { - caption: qsTr("Animated Image") + caption: qsTr("Image") } Section { - caption: qsTr("Animation Settings") + caption: qsTr("Animated image") anchors.left: parent.left anchors.right: parent.right @@ -62,40 +62,38 @@ Column { enabled: backendValues.speed.isAvailable } - ExpandingSpacer {} - } - - PropertyLabel { - text: qsTr("Paused") - tooltip: qsTr("Whether the animated image is paused.") - disabledState: !backendValues.paused.isAvailable - } - - SecondColumnLayout { - CheckBox { - text: backendValues.paused.valueToString - implicitWidth: StudioTheme.Values.twoControlColumnWidth - + StudioTheme.Values.actionIndicatorWidth - backendValue: backendValues.paused - enabled: backendValues.paused.isAvailable - } + // TODO convert to % and add % label after the spin box ExpandingSpacer {} } PropertyLabel { text: qsTr("Playing") - tooltip: qsTr("Whether the animated image is playing.") - disabledState: !backendValues.playing.isAvailable + tooltip: qsTr("Whether the animation is playing and/or paused.") + disabledState: !backendValues.playing.isAvailable && !backendValues.paused.isAvailable } SecondColumnLayout { CheckBox { - text: backendValues.playing.valueToString + text: StudioTheme.Constants.play implicitWidth: StudioTheme.Values.twoControlColumnWidth - + StudioTheme.Values.actionIndicatorWidth + + StudioTheme.Values.actionIndicatorWidth backendValue: backendValues.playing - enabled: backendValues.playing.isAvailable + enabled: backendValue.isAvailable + fontFamily: StudioTheme.Constants.iconFont.family + fontPixelSize: StudioTheme.Values.myIconFontSize + } + + Spacer { implicitWidth: StudioTheme.Values.twoControlColumnGap } + + CheckBox { + text: StudioTheme.Constants.pause + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.paused + enabled: backendValue.isAvailable + fontFamily: StudioTheme.Constants.iconFont.family + fontPixelSize: StudioTheme.Values.myIconFontSize } ExpandingSpacer {} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/AnimationSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/AnimationSection.qml index cdbf07307d3..e671c73165b 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/AnimationSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/AnimationSection.qml @@ -42,7 +42,7 @@ Section { SectionLayout { PropertyLabel { text: qsTr("Running") - tooltip: qsTr("Whether the animation is running and paused.") + tooltip: qsTr("Whether the animation is running and/or paused.") } SecondColumnLayout { From 3ebe5dbb99f788e1c93e8484431c796b29f2e256 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Fri, 18 Jun 2021 16:19:24 +0200 Subject: [PATCH 020/149] Tracing: Use qt_add_qml_module for tst_flamegraphview Use the CMake-based qml API to create the "QtCreator.TstTracingFlameGraphView" module. This uses qt_add_qml_module, which was introduced with Qt 6.2. For Qt 6.1 and below, everything compiles and runs like before this change. Change-Id: I7b1d7109c5fcff55c7be4b431f21281a63d13332 Reviewed-by: Ulf Hermann Reviewed-by: Qt CI Bot --- src/libs/tracing/CMakeLists.txt | 2 + .../tracing/flamegraphview/CMakeLists.txt | 26 +++++- .../flamegraphview/TestFlameGraphView.qml | 2 +- .../tracing/flamegraphview/flamegraphview.pro | 3 + .../tracing/flamegraphview/flamegraphview.qbs | 1 + .../tracing/flamegraphview/flamegraphview.qrc | 2 +- .../flamegraphview/testflamegraphmodel.h | 93 +++++++++++++++++++ .../flamegraphview/tst_flamegraphview.cpp | 69 +------------- 8 files changed, 129 insertions(+), 69 deletions(-) create mode 100644 tests/auto/tracing/flamegraphview/testflamegraphmodel.h diff --git a/src/libs/tracing/CMakeLists.txt b/src/libs/tracing/CMakeLists.txt index 8d445df2bff..4ebc77b913c 100644 --- a/src/libs/tracing/CMakeLists.txt +++ b/src/libs/tracing/CMakeLists.txt @@ -2,6 +2,8 @@ if (WITH_TESTS) set(TEST_SOURCES runscenegraphtest.cpp runscenegraphtest.h ) +else() + set(TEST_SOURCES "") endif() set(TRACING_CPP_SOURCES diff --git a/tests/auto/tracing/flamegraphview/CMakeLists.txt b/tests/auto/tracing/flamegraphview/CMakeLists.txt index 2a247c9ea18..f64a90d4d3b 100644 --- a/tests/auto/tracing/flamegraphview/CMakeLists.txt +++ b/tests/auto/tracing/flamegraphview/CMakeLists.txt @@ -1,6 +1,26 @@ add_qtc_test(tst_tracing_flamegraphview DEPENDS Tracing Qt5::QuickWidgets Qt5::Quick Utils - SOURCES - tst_flamegraphview.cpp - flamegraphview.qrc ) + +set(TSTFLAMEGRAPHVIEW_CPP_SOURCES + testflamegraphmodel.h + tst_flamegraphview.cpp +) + +if(${Qt5_VERSION} VERSION_LESS "6.2.0") + extend_qtc_test(tst_tracing_flamegraphview + SOURCES + ${TSTFLAMEGRAPHVIEW_CPP_SOURCES} + flamegraphview.qrc + ) +else() # < Qt 6.2 + qt_add_qml_module(tst_tracing_flamegraphview + URI "QtCreator.TstTracingFlameGraphView" + VERSION "1.0" + NO_CREATE_PLUGIN_TARGET + QML_FILES + TestFlameGraphView.qml + SOURCES + ${TSTFLAMEGRAPHVIEW_CPP_SOURCES} + ) +endif() # < Qt 6.2 diff --git a/tests/auto/tracing/flamegraphview/TestFlameGraphView.qml b/tests/auto/tracing/flamegraphview/TestFlameGraphView.qml index 5b8d0cfc206..f1f2d67ec97 100644 --- a/tests/auto/tracing/flamegraphview/TestFlameGraphView.qml +++ b/tests/auto/tracing/flamegraphview/TestFlameGraphView.qml @@ -24,7 +24,7 @@ ****************************************************************************/ import QtCreator.TstTracingFlameGraphView 1.0 -import "../QtCreator/Tracing/" // TODO: Turn into module import when possible +import "../Tracing" // TODO: Turn into module import when possible FlameGraphView { id: root diff --git a/tests/auto/tracing/flamegraphview/flamegraphview.pro b/tests/auto/tracing/flamegraphview/flamegraphview.pro index 3ebd7682fb0..7bb4102e35f 100644 --- a/tests/auto/tracing/flamegraphview/flamegraphview.pro +++ b/tests/auto/tracing/flamegraphview/flamegraphview.pro @@ -6,5 +6,8 @@ include(../../qttest.pri) SOURCES += \ tst_flamegraphview.cpp +HEADERS += \ + testflamegraphmodel.h + RESOURCES += \ flamegraphview.qrc diff --git a/tests/auto/tracing/flamegraphview/flamegraphview.qbs b/tests/auto/tracing/flamegraphview/flamegraphview.qbs index c3331c7a6a7..0be52f5d7ab 100644 --- a/tests/auto/tracing/flamegraphview/flamegraphview.qbs +++ b/tests/auto/tracing/flamegraphview/flamegraphview.qbs @@ -9,6 +9,7 @@ TracingAutotest { Group { name: "Test sources" files: [ + "testflamegraphmodel.h", "tst_flamegraphview.cpp", "flamegraphview.qrc" ] } diff --git a/tests/auto/tracing/flamegraphview/flamegraphview.qrc b/tests/auto/tracing/flamegraphview/flamegraphview.qrc index 912864eacff..951c294936e 100644 --- a/tests/auto/tracing/flamegraphview/flamegraphview.qrc +++ b/tests/auto/tracing/flamegraphview/flamegraphview.qrc @@ -1,5 +1,5 @@ - + TestFlameGraphView.qml diff --git a/tests/auto/tracing/flamegraphview/testflamegraphmodel.h b/tests/auto/tracing/flamegraphview/testflamegraphmodel.h new file mode 100644 index 00000000000..b383b928f53 --- /dev/null +++ b/tests/auto/tracing/flamegraphview/testflamegraphmodel.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** 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 +#include + +class TestFlameGraphModel : public QStandardItemModel +{ + Q_OBJECT +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + QML_ELEMENT + QML_UNCREATABLE("use the context property") +#endif // Qt >= 6.2 +public: + enum Role { + TypeIdRole = Qt::UserRole + 1, + SizeRole, + SourceFileRole, + SourceLineRole, + SourceColumnRole, + DetailsTitleRole, + SummaryRole, + MaxRole + }; + Q_ENUM(Role) + + void fill() { + qreal sizeSum = 0; + for (int i = 1; i < 10; ++i) { + QStandardItem *item = new QStandardItem; + item->setData(i, SizeRole); + item->setData(100 / i, TypeIdRole); + item->setData("trara", SourceFileRole); + item->setData(20, SourceLineRole); + item->setData(10, SourceColumnRole); + item->setData("details", DetailsTitleRole); + item->setData("summary", SummaryRole); + + for (int j = 1; j < i; ++j) { + QStandardItem *item2 = new QStandardItem; + item2->setData(1, SizeRole); + item2->setData(100 / j, TypeIdRole); + item2->setData(1, SourceLineRole); + item2->setData("child", DetailsTitleRole); + item2->setData("childsummary", SummaryRole); + for (int k = 1; k < 10; ++k) { + QStandardItem *skipped = new QStandardItem; + skipped->setData(0.001, SizeRole); + skipped->setData(100 / k, TypeIdRole); + item2->appendRow(skipped); + } + item->appendRow(item2); + } + + appendRow(item); + sizeSum += i; + } + invisibleRootItem()->setData(sizeSum, SizeRole); + invisibleRootItem()->setData(9 * 20, SourceLineRole); + invisibleRootItem()->setData(9 * 10, SourceColumnRole); + } + + Q_INVOKABLE void gotoSourceLocation(const QString &file, int line, int column) + { + Q_UNUSED(file) + Q_UNUSED(line) + Q_UNUSED(column) + } +}; diff --git a/tests/auto/tracing/flamegraphview/tst_flamegraphview.cpp b/tests/auto/tracing/flamegraphview/tst_flamegraphview.cpp index e342dade151..6268dd92a10 100644 --- a/tests/auto/tracing/flamegraphview/tst_flamegraphview.cpp +++ b/tests/auto/tracing/flamegraphview/tst_flamegraphview.cpp @@ -23,76 +23,17 @@ ** ****************************************************************************/ +#include + #include #include #include #include -#include #include #include #include -class TestFlameGraphModel : public QStandardItemModel -{ - Q_OBJECT - Q_ENUMS(Role) -public: - enum Role { - TypeIdRole = Qt::UserRole + 1, - SizeRole, - SourceFileRole, - SourceLineRole, - SourceColumnRole, - DetailsTitleRole, - SummaryRole, - MaxRole - }; - - void fill() { - qreal sizeSum = 0; - for (int i = 1; i < 10; ++i) { - QStandardItem *item = new QStandardItem; - item->setData(i, SizeRole); - item->setData(100 / i, TypeIdRole); - item->setData("trara", SourceFileRole); - item->setData(20, SourceLineRole); - item->setData(10, SourceColumnRole); - item->setData("details", DetailsTitleRole); - item->setData("summary", SummaryRole); - - for (int j = 1; j < i; ++j) { - QStandardItem *item2 = new QStandardItem; - item2->setData(1, SizeRole); - item2->setData(100 / j, TypeIdRole); - item2->setData(1, SourceLineRole); - item2->setData("child", DetailsTitleRole); - item2->setData("childsummary", SummaryRole); - for (int k = 1; k < 10; ++k) { - QStandardItem *skipped = new QStandardItem; - skipped->setData(0.001, SizeRole); - skipped->setData(100 / k, TypeIdRole); - item2->appendRow(skipped); - } - item->appendRow(item2); - } - - appendRow(item); - sizeSum += i; - } - invisibleRootItem()->setData(sizeSum, SizeRole); - invisibleRootItem()->setData(9 * 20, SourceLineRole); - invisibleRootItem()->setData(9 * 10, SourceColumnRole); - } - - Q_INVOKABLE void gotoSourceLocation(const QString &file, int line, int column) - { - Q_UNUSED(file) - Q_UNUSED(line) - Q_UNUSED(column) - } -}; - class DummyTheme : public Utils::Theme { public: @@ -131,16 +72,16 @@ void tst_FlameGraphView::initTestCase() model.fill(); #if QT_VERSION < QT_VERSION_CHECK(6, 2, 0) qmlRegisterType("QtCreator.Tracing", 1, 0, "FlameGraph"); -#endif // Qt < 6.2 qmlRegisterUncreatableType( "QtCreator.TstTracingFlameGraphView", 1, 0, "TestFlameGraphModel", QLatin1String("use the context property")); - +#endif // Qt < 6.2 Timeline::TimelineTheme::setupTheme(widget.engine()); widget.rootContext()->setContextProperty(QStringLiteral("flameGraphModel"), &model); - widget.setSource(QUrl(QStringLiteral("qrc:/tracingtest/TestFlameGraphView.qml"))); + widget.setSource(QUrl(QStringLiteral( + "qrc:/QtCreator/TstTracingFlameGraphView/TestFlameGraphView.qml"))); widget.setResizeMode(QQuickWidget::SizeRootObjectToView); widget.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); From 9624d2cd6924d9053140e1c60e0137b3bb76c414 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Thu, 24 Jun 2021 23:15:42 +0200 Subject: [PATCH 021/149] QmlProfiler: Add qml, register types via qt_add_qml_module Use the CMake-based qml API to create the "QtCreator.QmlProfiler" module. This uses qt_add_qml_module, which was introduced with Qt 6.2. For Qt 6.1 and below, everything compiles and runs like before this change. Change-Id: If381059bbf2a0a2b92c7f62e6da3142036ac6bbc Reviewed-by: Ulf Hermann --- src/plugins/qmlprofiler/CMakeLists.txt | 140 +++++++++++------- src/plugins/qmlprofiler/flamegraphmodel.h | 4 + src/plugins/qmlprofiler/flamegraphview.cpp | 5 +- .../qml/QmlProfilerFlameGraphView.qml | 2 +- src/plugins/qmlprofiler/qml/qmlprofiler.qrc | 2 +- .../qmlprofilerbindingloopsrenderpass.cpp | 10 +- 6 files changed, 104 insertions(+), 59 deletions(-) diff --git a/src/plugins/qmlprofiler/CMakeLists.txt b/src/plugins/qmlprofiler/CMakeLists.txt index 304c69dd35a..7dc0cecb9dc 100644 --- a/src/plugins/qmlprofiler/CMakeLists.txt +++ b/src/plugins/qmlprofiler/CMakeLists.txt @@ -1,54 +1,5 @@ -add_qtc_plugin(QmlProfiler - CONDITION TARGET Tracing - DEPENDS QmlDebug QmlJS Tracing Qt5::QuickWidgets - PLUGIN_DEPENDS Core Debugger ProjectExplorer QtSupport TextEditor - SOURCES - debugmessagesmodel.cpp debugmessagesmodel.h - flamegraphmodel.cpp flamegraphmodel.h - flamegraphview.cpp flamegraphview.h - inputeventsmodel.cpp inputeventsmodel.h - memoryusagemodel.cpp memoryusagemodel.h - pixmapcachemodel.cpp pixmapcachemodel.h - qml/qmlprofiler.qrc - qmlevent.cpp qmlevent.h - qmleventlocation.cpp qmleventlocation.h - qmleventtype.cpp qmleventtype.h - qmlnote.cpp qmlnote.h - qmlprofiler_global.h - qmlprofileractions.cpp qmlprofileractions.h - qmlprofileranimationsmodel.cpp qmlprofileranimationsmodel.h - qmlprofilerattachdialog.cpp qmlprofilerattachdialog.h - qmlprofilerbindingloopsrenderpass.cpp qmlprofilerbindingloopsrenderpass.h - qmlprofilerclientmanager.cpp qmlprofilerclientmanager.h - qmlprofilerconstants.h - qmlprofilerdetailsrewriter.cpp qmlprofilerdetailsrewriter.h - qmlprofilereventsview.h - qmlprofilereventtypes.h - qmlprofilermodelmanager.cpp qmlprofilermodelmanager.h - qmlprofilernotesmodel.cpp qmlprofilernotesmodel.h - qmlprofilerplugin.cpp qmlprofilerplugin.h - qmlprofilerrangemodel.cpp qmlprofilerrangemodel.h - qmlprofilerrunconfigurationaspect.cpp qmlprofilerrunconfigurationaspect.h - qmlprofilerruncontrol.cpp qmlprofilerruncontrol.h - qmlprofilersettings.cpp qmlprofilersettings.h - qmlprofilerstatemanager.cpp qmlprofilerstatemanager.h - qmlprofilerstatewidget.cpp qmlprofilerstatewidget.h - qmlprofilerstatisticsmodel.cpp qmlprofilerstatisticsmodel.h - qmlprofilerstatisticsview.cpp qmlprofilerstatisticsview.h - qmlprofilertextmark.cpp qmlprofilertextmark.h - qmlprofilertimelinemodel.cpp qmlprofilertimelinemodel.h - qmlprofilertool.cpp qmlprofilertool.h - qmlprofilertraceclient.cpp qmlprofilertraceclient.h - qmlprofilertracefile.cpp qmlprofilertracefile.h - qmlprofilertraceview.cpp qmlprofilertraceview.h - qmlprofilerviewmanager.cpp qmlprofilerviewmanager.h - qmltypedevent.cpp qmltypedevent.h - scenegraphtimelinemodel.cpp scenegraphtimelinemodel.h -) - -extend_qtc_plugin(QmlProfiler - CONDITION WITH_TESTS - SOURCES +if(WITH_TESTS) + set(TEST_SOURCES tests/debugmessagesmodel_test.cpp tests/debugmessagesmodel_test.h tests/fakedebugserver.cpp tests/fakedebugserver.h tests/flamegraphmodel_test.cpp tests/flamegraphmodel_test.h @@ -70,4 +21,91 @@ extend_qtc_plugin(QmlProfiler tests/qmlprofilertraceclient_test.cpp tests/qmlprofilertraceclient_test.h tests/qmlprofilertraceview_test.cpp tests/qmlprofilertraceview_test.h tests/tests.qrc + ) +else() + set(TEST_SOURCES "") +endif() + +set(QMLPROFILER_CPP_SOURCES + debugmessagesmodel.cpp debugmessagesmodel.h + flamegraphmodel.cpp flamegraphmodel.h + flamegraphview.cpp flamegraphview.h + inputeventsmodel.cpp inputeventsmodel.h + memoryusagemodel.cpp memoryusagemodel.h + pixmapcachemodel.cpp pixmapcachemodel.h + qmlevent.cpp qmlevent.h + qmleventlocation.cpp qmleventlocation.h + qmleventtype.cpp qmleventtype.h + qmlnote.cpp qmlnote.h + qmlprofiler_global.h + qmlprofileractions.cpp qmlprofileractions.h + qmlprofileranimationsmodel.cpp qmlprofileranimationsmodel.h + qmlprofilerattachdialog.cpp qmlprofilerattachdialog.h + qmlprofilerbindingloopsrenderpass.cpp qmlprofilerbindingloopsrenderpass.h + qmlprofilerclientmanager.cpp qmlprofilerclientmanager.h + qmlprofilerconstants.h + qmlprofilerdetailsrewriter.cpp qmlprofilerdetailsrewriter.h + qmlprofilereventsview.h + qmlprofilereventtypes.h + qmlprofilermodelmanager.cpp qmlprofilermodelmanager.h + qmlprofilernotesmodel.cpp qmlprofilernotesmodel.h + qmlprofilerplugin.cpp qmlprofilerplugin.h + qmlprofilerrangemodel.cpp qmlprofilerrangemodel.h + qmlprofilerrunconfigurationaspect.cpp qmlprofilerrunconfigurationaspect.h + qmlprofilerruncontrol.cpp qmlprofilerruncontrol.h + qmlprofilersettings.cpp qmlprofilersettings.h + qmlprofilerstatemanager.cpp qmlprofilerstatemanager.h + qmlprofilerstatewidget.cpp qmlprofilerstatewidget.h + qmlprofilerstatisticsmodel.cpp qmlprofilerstatisticsmodel.h + qmlprofilerstatisticsview.cpp qmlprofilerstatisticsview.h + qmlprofilertextmark.cpp qmlprofilertextmark.h + qmlprofilertimelinemodel.cpp qmlprofilertimelinemodel.h + qmlprofilertool.cpp qmlprofilertool.h + qmlprofilertraceclient.cpp qmlprofilertraceclient.h + qmlprofilertracefile.cpp qmlprofilertracefile.h + qmlprofilertraceview.cpp qmlprofilertraceview.h + qmlprofilerviewmanager.cpp qmlprofilerviewmanager.h + qmltypedevent.cpp qmltypedevent.h + scenegraphtimelinemodel.cpp scenegraphtimelinemodel.h ) + +add_qtc_plugin(QmlProfiler + DEPENDS QmlDebug QmlJS Tracing Qt5::QuickWidgets + PLUGIN_DEPENDS Core Debugger ProjectExplorer QtSupport TextEditor + SOURCES + ${TEST_SOURCES} +) + +if(${Qt5_VERSION} VERSION_LESS "6.2.0") + extend_qtc_plugin(QmlProfiler + SOURCES + ${QMLPROFILER_CPP_SOURCES} + qml/qmlprofiler.qrc + ) +else() # < Qt 6.2 + set(QMLPROFILER_QML_FILES + qml/QmlProfilerFlameGraphView.qml + ) + + set(QMLPROFILER_QML_RESOURCES + qml/bindingloops.frag + qml/bindingloops.vert + ) + + foreach(file IN LISTS QMLPROFILER_QML_FILES QMLPROFILER_QML_RESOURCES) + get_filename_component(fileName "${file}" NAME) + set_source_files_properties("${file}" PROPERTIES QT_RESOURCE_ALIAS "${fileName}") + endforeach() + + qt_add_qml_module(QmlProfiler + URI "QtCreator.QmlProfiler" + VERSION "1.0" + NO_CREATE_PLUGIN_TARGET + QML_FILES + ${QMLPROFILER_QML_FILES} + RESOURCES + ${QMLPROFILER_QML_RESOURCES} + SOURCES + ${QMLPROFILER_CPP_SOURCES} + ) +endif() # < Qt 6.2 diff --git a/src/plugins/qmlprofiler/flamegraphmodel.h b/src/plugins/qmlprofiler/flamegraphmodel.h index 456502b9322..3e536bf2200 100644 --- a/src/plugins/qmlprofiler/flamegraphmodel.h +++ b/src/plugins/qmlprofiler/flamegraphmodel.h @@ -55,6 +55,10 @@ struct FlameGraphData { class FlameGraphModel : public QAbstractItemModel { Q_OBJECT +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + QML_NAMED_ELEMENT(QmlProfilerFlameGraphModel) + QML_UNCREATABLE("use the context property") +#endif // Qt >= 6.2 public: enum Role { TypeIdRole = Qt::UserRole + 1, // Sort by data, not by displayed string diff --git a/src/plugins/qmlprofiler/flamegraphview.cpp b/src/plugins/qmlprofiler/flamegraphview.cpp index 4008da48a50..19d81c9721a 100644 --- a/src/plugins/qmlprofiler/flamegraphview.cpp +++ b/src/plugins/qmlprofiler/flamegraphview.cpp @@ -48,15 +48,16 @@ FlameGraphView::FlameGraphView(QmlProfilerModelManager *manager, QWidget *parent #if QT_VERSION < QT_VERSION_CHECK(6, 2, 0) qmlRegisterType("QtCreator.Tracing", 1, 0, "FlameGraph"); -#endif // Qt < 6.2 qmlRegisterUncreatableType("QtCreator.QmlProfiler", 1, 0, "QmlProfilerFlameGraphModel", QLatin1String("use the context property")); +#endif // Qt < 6.2 Timeline::TimelineTheme::setupTheme(m_content->engine()); m_content->rootContext()->setContextProperty(QStringLiteral("flameGraphModel"), m_model); - m_content->setSource(QUrl(QStringLiteral("qrc:/qmlprofiler/QmlProfilerFlameGraphView.qml"))); + m_content->setSource( + QUrl(QStringLiteral("qrc:/QtCreator/QmlProfiler/QmlProfilerFlameGraphView.qml"))); m_content->setClearColor(Utils::creatorTheme()->color(Utils::Theme::Timeline_BackgroundColor1)); m_content->setResizeMode(QQuickWidget::SizeRootObjectToView); diff --git a/src/plugins/qmlprofiler/qml/QmlProfilerFlameGraphView.qml b/src/plugins/qmlprofiler/qml/QmlProfilerFlameGraphView.qml index bf7f54b2728..1fd1a3ca538 100644 --- a/src/plugins/qmlprofiler/qml/QmlProfilerFlameGraphView.qml +++ b/src/plugins/qmlprofiler/qml/QmlProfilerFlameGraphView.qml @@ -24,7 +24,7 @@ ****************************************************************************/ import QtCreator.QmlProfiler 1.0 -import "../QtCreator/Tracing/" // TODO: Turn into module import when possible +import "../Tracing" // TODO: Turn into module import when possible FlameGraphView { id: root diff --git a/src/plugins/qmlprofiler/qml/qmlprofiler.qrc b/src/plugins/qmlprofiler/qml/qmlprofiler.qrc index daf9a34b48e..e3582b44eb6 100644 --- a/src/plugins/qmlprofiler/qml/qmlprofiler.qrc +++ b/src/plugins/qmlprofiler/qml/qmlprofiler.qrc @@ -1,5 +1,5 @@ - + bindingloops.vert bindingloops.frag QmlProfilerFlameGraphView.qml diff --git a/src/plugins/qmlprofiler/qmlprofilerbindingloopsrenderpass.cpp b/src/plugins/qmlprofiler/qmlprofilerbindingloopsrenderpass.cpp index 892f384b6ad..e9f056f5693 100644 --- a/src/plugins/qmlprofiler/qmlprofilerbindingloopsrenderpass.cpp +++ b/src/plugins/qmlprofiler/qmlprofilerbindingloopsrenderpass.cpp @@ -317,11 +317,13 @@ BindingLoopMaterialShader::BindingLoopMaterialShader() : QSGMaterialShader() { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - setShaderSourceFile(QOpenGLShader::Vertex, QStringLiteral(":/qmlprofiler/bindingloops.vert")); - setShaderSourceFile(QOpenGLShader::Fragment, QStringLiteral(":/qmlprofiler/bindingloops.frag")); + setShaderSourceFile(QOpenGLShader::Vertex, + QStringLiteral(":/QtCreator/QmlProfiler/bindingloops.vert")); + setShaderSourceFile(QOpenGLShader::Fragment, + QStringLiteral(":/QtCreator/QmlProfiler/bindingloops.frag")); #else // < Qt 6 - setShaderFileName(VertexStage, ":/qmlprofiler/bindingloops.vert"); - setShaderFileName(FragmentStage, ":/qmlprofiler/bindingloops.frag"); + setShaderFileName(VertexStage, ":/QtCreator/QmlProfiler/bindingloops.vert"); + setShaderFileName(FragmentStage, ":/QtCreator/QmlProfiler/bindingloops.frag"); #endif // < Qt 6 } From bb8aa12cbc29e0c3f78d793e7666987691182f77 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Thu, 24 Jun 2021 23:17:09 +0200 Subject: [PATCH 022/149] PerfProfiler: Add qml, register types via qt_add_qml_module Use the CMake-based qml API to create the "QtCreator.PerfProfiler" module. This uses qt_add_qml_module, which was introduced with Qt 6.2. For Qt 6.1 and below, everything compiles and runs like before this change. Change-Id: I7e3a9e544e52592b5daf0117ce415583ab97dafe Reviewed-by: Ulf Hermann Reviewed-by: Qt CI Bot --- src/plugins/perfprofiler/CMakeLists.txt | 102 +++++++++++------- .../PerfProfilerFlameGraphView.qml | 2 +- src/plugins/perfprofiler/perfprofiler.qrc | 4 +- .../perfprofilerflamegraphmodel.h | 5 + .../perfprofilerflamegraphview.cpp | 4 +- 5 files changed, 76 insertions(+), 41 deletions(-) diff --git a/src/plugins/perfprofiler/CMakeLists.txt b/src/plugins/perfprofiler/CMakeLists.txt index b7644fb3fc3..3c0ce3e434a 100644 --- a/src/plugins/perfprofiler/CMakeLists.txt +++ b/src/plugins/perfprofiler/CMakeLists.txt @@ -1,41 +1,69 @@ -add_qtc_plugin(PerfProfiler - CONDITION TARGET Tracing - DEPENDS Tracing Qt5::QuickWidgets - PLUGIN_DEPENDS Core Debugger ProjectExplorer QtSupport - SOURCES - perfconfigeventsmodel.cpp perfconfigeventsmodel.h - perfconfigwidget.cpp perfconfigwidget.h - perfdatareader.cpp perfdatareader.h - perfevent.h - perfeventtype.h - perfloaddialog.cpp perfloaddialog.h perfloaddialog.ui - perfoptionspage.cpp perfoptionspage.h - perfprofiler.qrc - perfprofilerconstants.h - perfprofilerflamegraphmodel.cpp perfprofilerflamegraphmodel.h - perfprofilerflamegraphview.cpp perfprofilerflamegraphview.h - perfprofilerplugin.cpp perfprofilerplugin.h - perfprofilerruncontrol.cpp perfprofilerruncontrol.h - perfprofilerstatisticsmodel.cpp perfprofilerstatisticsmodel.h - perfprofilerstatisticsview.cpp perfprofilerstatisticsview.h - perfprofilertool.cpp perfprofilertool.h - perfprofilertracefile.cpp perfprofilertracefile.h - perfprofilertracemanager.cpp perfprofilertracemanager.h - perfprofilertraceview.cpp perfprofilertraceview.h - perfprofiler_global.h - perfresourcecounter.cpp perfresourcecounter.h - perfrunconfigurationaspect.cpp perfrunconfigurationaspect.h - perfsettings.cpp perfsettings.h - perftimelinemodel.cpp perftimelinemodel.h - perftimelinemodelmanager.cpp perftimelinemodelmanager.h - perftimelineresourcesrenderpass.cpp perftimelineresourcesrenderpass.h - perftracepointdialog.cpp perftracepointdialog.h perftracepointdialog.ui -) - -extend_qtc_plugin(PerfProfiler - CONDITION WITH_TESTS - SOURCES +if(WITH_TESTS) + set(TEST_SOURCES tests/perfprofilertracefile_test.cpp tests/perfprofilertracefile_test.h tests/perfresourcecounter_test.cpp tests/perfresourcecounter_test.h tests/tests.qrc + ) +else() + set(TEST_SOURCES "") +endif() + +set(PERFPROFILER_CPP_SOURCES + perfconfigeventsmodel.cpp perfconfigeventsmodel.h + perfconfigwidget.cpp perfconfigwidget.h + perfdatareader.cpp perfdatareader.h + perfevent.h + perfeventtype.h + perfloaddialog.cpp perfloaddialog.h perfloaddialog.ui + perfoptionspage.cpp perfoptionspage.h + perfprofiler.qrc + perfprofilerconstants.h + perfprofilerflamegraphmodel.cpp perfprofilerflamegraphmodel.h + perfprofilerflamegraphview.cpp perfprofilerflamegraphview.h + perfprofilerplugin.cpp perfprofilerplugin.h + perfprofilerruncontrol.cpp perfprofilerruncontrol.h + perfprofilerstatisticsmodel.cpp perfprofilerstatisticsmodel.h + perfprofilerstatisticsview.cpp perfprofilerstatisticsview.h + perfprofilertool.cpp perfprofilertool.h + perfprofilertracefile.cpp perfprofilertracefile.h + perfprofilertracemanager.cpp perfprofilertracemanager.h + perfprofilertraceview.cpp perfprofilertraceview.h + perfprofiler_global.h + perfresourcecounter.cpp perfresourcecounter.h + perfrunconfigurationaspect.cpp perfrunconfigurationaspect.h + perfsettings.cpp perfsettings.h + perftimelinemodel.cpp perftimelinemodel.h + perftimelinemodelmanager.cpp perftimelinemodelmanager.h + perftimelineresourcesrenderpass.cpp perftimelineresourcesrenderpass.h + perftracepointdialog.cpp perftracepointdialog.h perftracepointdialog.ui ) + +add_qtc_plugin(PerfProfiler + DEPENDS Tracing Qt5::QuickWidgets + PLUGIN_DEPENDS Core Debugger ProjectExplorer QtSupport + SOURCES + ${TEST_SOURCES} +) + +if(${Qt5_VERSION} VERSION_LESS "6.2.0") + extend_qtc_plugin(PerfProfiler + SOURCES + ${PERFPROFILER_CPP_SOURCES} + perfprofiler.qrc + ) +else() # < Qt 6.2 + qt_add_resources(PerfProfiler perfprofiler + PREFIX "/perfprofiler" + tracepoints.sh + ) + + qt_add_qml_module(PerfProfiler + URI "QtCreator.PerfProfiler" + VERSION "1.0" + NO_CREATE_PLUGIN_TARGET + QML_FILES + PerfProfilerFlameGraphView.qml + SOURCES + ${PERFPROFILER_CPP_SOURCES} + ) +endif() # < Qt 6.2 diff --git a/src/plugins/perfprofiler/PerfProfilerFlameGraphView.qml b/src/plugins/perfprofiler/PerfProfilerFlameGraphView.qml index 90bd3b21384..4a3a5ae43b1 100644 --- a/src/plugins/perfprofiler/PerfProfilerFlameGraphView.qml +++ b/src/plugins/perfprofiler/PerfProfilerFlameGraphView.qml @@ -24,7 +24,7 @@ ****************************************************************************/ import QtCreator.PerfProfiler 1.0 -import "../QtCreator/Tracing/" // TODO: Turn into module import when possible +import "../Tracing" // TODO: Turn into module import when possible FlameGraphView { id: root diff --git a/src/plugins/perfprofiler/perfprofiler.qrc b/src/plugins/perfprofiler/perfprofiler.qrc index 05be0370fe1..803828dbb2d 100644 --- a/src/plugins/perfprofiler/perfprofiler.qrc +++ b/src/plugins/perfprofiler/perfprofiler.qrc @@ -1,6 +1,8 @@ - PerfProfilerFlameGraphView.qml tracepoints.sh + + PerfProfilerFlameGraphView.qml + diff --git a/src/plugins/perfprofiler/perfprofilerflamegraphmodel.h b/src/plugins/perfprofiler/perfprofilerflamegraphmodel.h index e794f79be79..ec5595ea252 100644 --- a/src/plugins/perfprofiler/perfprofilerflamegraphmodel.h +++ b/src/plugins/perfprofiler/perfprofilerflamegraphmodel.h @@ -30,6 +30,7 @@ #include #include +#include namespace PerfProfiler { namespace Internal { @@ -38,6 +39,10 @@ class PerfProfilerFlameGraphData; class PerfProfilerFlameGraphModel : public QAbstractItemModel { Q_OBJECT +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + QML_ELEMENT + QML_UNCREATABLE("use the context property") +#endif // Qt >= 6.2 Q_DISABLE_COPY(PerfProfilerFlameGraphModel); public: PerfProfilerFlameGraphModel(PerfProfilerFlameGraphModel &&) = delete; diff --git a/src/plugins/perfprofiler/perfprofilerflamegraphview.cpp b/src/plugins/perfprofiler/perfprofilerflamegraphview.cpp index c6f00f1ef4b..d7082bd8b0a 100644 --- a/src/plugins/perfprofiler/perfprofilerflamegraphview.cpp +++ b/src/plugins/perfprofiler/perfprofilerflamegraphview.cpp @@ -47,15 +47,15 @@ PerfProfilerFlameGraphView::PerfProfilerFlameGraphView(QWidget *parent, PerfProf #if QT_VERSION < QT_VERSION_CHECK(6, 2, 0) qmlRegisterType("QtCreator.Tracing", 1, 0, "FlameGraph"); -#endif // Qt < 6.2 qmlRegisterUncreatableType( "QtCreator.PerfProfiler", 1, 0, "PerfProfilerFlameGraphModel", QLatin1String("use the context property")); +#endif // Qt < 6.2 Timeline::TimelineTheme::setupTheme(engine()); rootContext()->setContextProperty(QStringLiteral("flameGraphModel"), m_model); - setSource(QUrl(QStringLiteral("qrc:/perfprofiler/PerfProfilerFlameGraphView.qml"))); + setSource(QUrl(QStringLiteral("qrc:/QtCreator/PerfProfiler/PerfProfilerFlameGraphView.qml"))); setClearColor(Utils::creatorTheme()->color(Utils::Theme::Timeline_BackgroundColor1)); setResizeMode(QQuickWidget::SizeRootObjectToView); From 2d08b296b724c8e482d88e1b72840b1bd93d5475 Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Mon, 28 Jun 2021 16:29:36 +0200 Subject: [PATCH 023/149] CMakePM: Remove wishlist comment The comment is no longer valid. Change-Id: Ica19e26039e147ccdd77559cc85bfcb5e784b68c Reviewed-by: Eike Ziller --- src/plugins/cmakeprojectmanager/cmakeproject.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp index 3e7bdb67a02..4b2a5157e87 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp @@ -48,11 +48,6 @@ namespace CMakeProjectManager { using namespace Internal; -// QtCreator CMake Generator wishlist: -// Which make targets we need to build to get all executables -// What is the actual compiler executable -// DEFINES - /*! \class CMakeProject */ From 44f0f9ae18346cbc08c80a811879a1a19726eee9 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Mon, 28 Jun 2021 12:21:30 +0200 Subject: [PATCH 024/149] Clangd: use special value text for automatic worker count Change-Id: Iddb75b6716724b5994d8093a8e389f5cb8e2d044 Reviewed-by: Christian Kandeler --- .../cpptools/cppcodemodelsettingspage.cpp | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/plugins/cpptools/cppcodemodelsettingspage.cpp b/src/plugins/cpptools/cppcodemodelsettingspage.cpp index f48f639d16a..05b39cfea2c 100644 --- a/src/plugins/cpptools/cppcodemodelsettingspage.cpp +++ b/src/plugins/cpptools/cppcodemodelsettingspage.cpp @@ -211,10 +211,8 @@ public: "If background indexing is enabled, global symbol searches will yield\n" "more accurate results, at the cost of additional CPU load when\n" "the project is first opened.")); - m_threadLimitCheckBox.setText(tr("Set worker thread count limit")); - m_threadLimitCheckBox.setChecked(ClangdSettings::workerThreadLimit() != 0); - m_threadLimitSpinBox.setMinimum(1); m_threadLimitSpinBox.setValue(ClangdSettings::workerThreadLimit()); + m_threadLimitSpinBox.setSpecialValueText("Automatic"); const auto layout = new QVBoxLayout(this); layout->addWidget(&m_useClangdCheckBox); @@ -226,7 +224,8 @@ public: const auto threadLimitLayout = new QHBoxLayout; threadLimitLayout->addWidget(&m_threadLimitSpinBox); threadLimitLayout->addStretch(1); - formLayout->addRow(&m_threadLimitCheckBox, threadLimitLayout); + const auto threadLimitLabel = new QLabel(tr("Set worker thread count:")); + formLayout->addRow(threadLimitLabel, threadLimitLayout); layout->addLayout(formLayout); layout->addStretch(1); @@ -235,15 +234,11 @@ public: m_clangdChooser.setEnabled(checked); indexingLabel->setEnabled(checked); m_indexingCheckBox.setEnabled(checked); - m_threadLimitCheckBox.setEnabled(checked); - m_threadLimitSpinBox.setEnabled(checked && m_threadLimitCheckBox.isChecked()); + m_threadLimitSpinBox.setEnabled(checked); }; connect(&m_useClangdCheckBox, &QCheckBox::toggled, toggleEnabled); - connect(&m_threadLimitCheckBox, &QCheckBox::toggled, - &m_threadLimitSpinBox, &QSpinBox::setEnabled); toggleEnabled(m_useClangdCheckBox.isChecked()); - m_threadLimitSpinBox.setEnabled(m_useClangdCheckBox.isChecked() - && m_threadLimitCheckBox.isChecked()); + m_threadLimitSpinBox.setEnabled(m_useClangdCheckBox.isChecked()); } private: @@ -253,14 +248,12 @@ private: data.useClangd = m_useClangdCheckBox.isChecked(); data.executableFilePath = m_clangdChooser.filePath(); data.enableIndexing = m_indexingCheckBox.isChecked(); - data.workerThreadLimit = m_threadLimitCheckBox.isChecked() - ? m_threadLimitSpinBox.value() : 0; + data.workerThreadLimit = m_threadLimitSpinBox.value(); ClangdSettings::setData(data); } QCheckBox m_useClangdCheckBox; QCheckBox m_indexingCheckBox; - QCheckBox m_threadLimitCheckBox; QSpinBox m_threadLimitSpinBox; Utils::PathChooser m_clangdChooser; }; From 922f9b159d705c1e8e07419563ab5b8ad9b39cd1 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 22 Jun 2021 15:28:35 +0200 Subject: [PATCH 025/149] LanguageClient: fix diagnostic document revision check We track the document revision in the client nowadays. Change-Id: Ia55bc7cd5eaf100fb19953ca71e8de8f43ae3266 Reviewed-by: Christian Kandeler --- src/plugins/languageclient/client.cpp | 2 +- src/plugins/languageclient/diagnosticmanager.cpp | 14 ++++++++------ src/plugins/languageclient/diagnosticmanager.h | 6 ++++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index c627e51b829..2930146cdc5 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -81,7 +81,7 @@ static Q_LOGGING_CATEGORY(LOGLSPCLIENT, "qtc.languageclient.client", QtWarningMs Client::Client(BaseClientInterface *clientInterface) : m_id(Utils::Id::fromString(QUuid::createUuid().toString())) , m_clientInterface(clientInterface) - , m_diagnosticManager(m_id) + , m_diagnosticManager(this) , m_documentSymbolCache(this) , m_hoverHandler(this) , m_symbolSupport(this) diff --git a/src/plugins/languageclient/diagnosticmanager.cpp b/src/plugins/languageclient/diagnosticmanager.cpp index 67068c30ea0..fa1d22e3120 100644 --- a/src/plugins/languageclient/diagnosticmanager.cpp +++ b/src/plugins/languageclient/diagnosticmanager.cpp @@ -25,6 +25,8 @@ #include "diagnosticmanager.h" +#include "client.h" + #include #include #include @@ -69,8 +71,8 @@ private: const Diagnostic m_diagnostic; }; -DiagnosticManager::DiagnosticManager(const Id &clientId) - : m_clientId(clientId) +DiagnosticManager::DiagnosticManager(Client *client) + : m_client(client) {} DiagnosticManager::~DiagnosticManager() @@ -95,7 +97,7 @@ void DiagnosticManager::hideDiagnostics(TextDocument *doc) m_hideHandler(); for (BaseTextEditor *editor : BaseTextEditor::textEditorsForDocument(doc)) editor->editorWidget()->setExtraSelections(TextEditorWidget::CodeWarningsSelection, {}); - qDeleteAll(Utils::filtered(doc->marks(), Utils::equal(&TextMark::category, m_clientId))); + qDeleteAll(Utils::filtered(doc->marks(), Utils::equal(&TextMark::category, m_client->id()))); } void DiagnosticManager::removeDiagnostics(const LanguageServerProtocol::DocumentUri &uri) @@ -141,7 +143,7 @@ void DiagnosticManager::showDiagnostics(const DocumentUri &uri, int version) QObject::connect(action, &QAction::triggered, [text = diagnostic.message()]() { QApplication::clipboard()->setText(text); }); - auto mark = new TextMark(filePath, diagnostic, m_clientId); + auto mark = new TextMark(filePath, diagnostic, m_client->id()); mark->setActions({action}); doc->addMark(mark); @@ -164,7 +166,7 @@ void DiagnosticManager::clearDiagnostics() QList DiagnosticManager::diagnosticsAt(const DocumentUri &uri, const QTextCursor &cursor) const { - const int documentRevision = cursor.document()->revision(); + const int documentRevision = m_client->documentVersion(uri.toFilePath()); auto it = m_diagnostics.find(uri); if (it == m_diagnostics.end()) return {}; @@ -184,7 +186,7 @@ bool DiagnosticManager::hasDiagnostic(const LanguageServerProtocol::DocumentUri const auto it = m_diagnostics.find(uri); if (it == m_diagnostics.end()) return {}; - const int revision = doc->document()->revision(); + const int revision = m_client->documentVersion(uri.toFilePath()); if (revision != it->version.value_or(revision)) return false; return it->diagnostics.contains(diag); diff --git a/src/plugins/languageclient/diagnosticmanager.h b/src/plugins/languageclient/diagnosticmanager.h index 7d4303b2404..d876a8150de 100644 --- a/src/plugins/languageclient/diagnosticmanager.h +++ b/src/plugins/languageclient/diagnosticmanager.h @@ -40,6 +40,8 @@ class TextMark; namespace LanguageClient { +class Client; + using TextMarkCreator = std::function; using HideDiagnosticsHandler = std::function; @@ -48,7 +50,7 @@ class DiagnosticManager { Q_DECLARE_TR_FUNCTIONS(LanguageClient::DiagnosticManager) public: - explicit DiagnosticManager(const Utils::Id &clientId); + explicit DiagnosticManager(Client *client); ~DiagnosticManager(); void setDiagnostics(const LanguageServerProtocol::DocumentUri &uri, @@ -77,9 +79,9 @@ private: QList diagnostics; }; QMap m_diagnostics; - Utils::Id m_clientId; TextMarkCreator m_textMarkCreator; HideDiagnosticsHandler m_hideHandler; + Client *m_client; }; } // namespace LanguageClient From bf6f446a35ad55ab6f1c52f3d0adb0b2a7be9fe7 Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Tue, 29 Jun 2021 00:07:35 +0200 Subject: [PATCH 026/149] Tracing: Fix build of tst_flamegraphview.cpp Change faulty include from system to local amends: 3ebe5dbb99f788e1c93e8484431c796b29f2e256 Change-Id: I466e683137b5e95f5b70fb2efa3dda1a77af8a04 Reviewed-by: Christian Stenger --- tests/auto/tracing/flamegraphview/tst_flamegraphview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/auto/tracing/flamegraphview/tst_flamegraphview.cpp b/tests/auto/tracing/flamegraphview/tst_flamegraphview.cpp index 6268dd92a10..cf384e5691e 100644 --- a/tests/auto/tracing/flamegraphview/tst_flamegraphview.cpp +++ b/tests/auto/tracing/flamegraphview/tst_flamegraphview.cpp @@ -23,7 +23,7 @@ ** ****************************************************************************/ -#include +#include "testflamegraphmodel.h" #include #include From 74385cc10c9af17e15999e3ac0f34de85882d2b7 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Thu, 24 Jun 2021 16:25:40 +0200 Subject: [PATCH 027/149] ClangTools: Make sure URL for clang-tidy checks is always correct Fixes: QTCREATORBUG-25902 Change-Id: Ifadb6c90cf9a86fc4efb734d6105217b70f9419d Reviewed-by: David Schulz --- src/plugins/clangtools/clangtoolsutils.cpp | 13 ++++++++++++- src/plugins/clangtools/clangtoolsutils.h | 2 ++ src/plugins/clangtools/diagnosticconfigswidget.cpp | 3 +-- src/plugins/cpptools/cpptoolsconstants.h | 8 -------- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/plugins/clangtools/clangtoolsutils.cpp b/src/plugins/clangtools/clangtoolsutils.cpp index 5522fdbb4d6..1c425d115ae 100644 --- a/src/plugins/clangtools/clangtoolsutils.cpp +++ b/src/plugins/clangtools/clangtoolsutils.cpp @@ -308,7 +308,7 @@ QString documentationUrl(const QString &checkName) } else if (name.startsWith(clangStaticAnalyzerPrefix)) { url = CppTools::Constants::CLANG_STATIC_ANALYZER_DOCUMENTATION_URL; } else { - url = QString(CppTools::Constants::TIDY_DOCUMENTATION_URL_TEMPLATE).arg(name); + url = clangTidyDocUrl(name); } return url; @@ -351,5 +351,16 @@ QStringList extraClangToolsAppendOptions() return options; } +QString clangTidyDocUrl(const QString &check) +{ + QVersionNumber version = ClangToolsSettings::clangTidyVersion(); + version = QVersionNumber(version.majorVersion(), 0, 0); + if (version == QVersionNumber(0)) + version = QVersionNumber(12); + static const char urlTemplate[] + = "https://releases.llvm.org/%1/tools/clang/tools/extra/docs/clang-tidy/checks/%2.html"; + return QString::fromLatin1(urlTemplate).arg(version.toString(), check); +} + } // namespace Internal } // namespace ClangTools diff --git a/src/plugins/clangtools/clangtoolsutils.h b/src/plugins/clangtools/clangtoolsutils.h index 4ee378cdc1e..295af853a44 100644 --- a/src/plugins/clangtools/clangtoolsutils.h +++ b/src/plugins/clangtools/clangtoolsutils.h @@ -42,6 +42,8 @@ namespace Debugger { class DiagnosticLocation; } namespace ClangTools { namespace Internal { +QString clangTidyDocUrl(const QString &check); + class Diagnostic; enum class FixitStatus { diff --git a/src/plugins/clangtools/diagnosticconfigswidget.cpp b/src/plugins/clangtools/diagnosticconfigswidget.cpp index 96196aa4561..779a615d0d0 100644 --- a/src/plugins/clangtools/diagnosticconfigswidget.cpp +++ b/src/plugins/clangtools/diagnosticconfigswidget.cpp @@ -449,8 +449,7 @@ public: // 'clang-analyzer-' group if (node->isDir) return CppTools::Constants::CLANG_STATIC_ANALYZER_DOCUMENTATION_URL; - return QString::fromUtf8(CppTools::Constants::TIDY_DOCUMENTATION_URL_TEMPLATE) - .arg(node->fullPath.toString()); + return clangTidyDocUrl(node->fullPath.toString()); } return BaseChecksTreeModel::data(fullIndex, role); diff --git a/src/plugins/cpptools/cpptoolsconstants.h b/src/plugins/cpptools/cpptoolsconstants.h index 7ee83c1f707..9b5b1c6f64e 100644 --- a/src/plugins/cpptools/cpptoolsconstants.h +++ b/src/plugins/cpptools/cpptoolsconstants.h @@ -95,14 +95,6 @@ const char LOCATOR_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("CppTools", "C++ Cl const char SYMBOLS_FIND_FILTER_ID[] = "Symbols"; const char SYMBOLS_FIND_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("CppTools", "C++ Symbols"); -// CLANG-UPGRADE-CHECK: Checks/update URLs. -// -// Upgrade the version in the URL. Note that we cannot use the macro -// CLANG_VERSION here because it might denote a version that was not yet -// released (e.g. 6.0.1, but only 6.0.0 was released). -constexpr const char TIDY_DOCUMENTATION_URL_TEMPLATE[] - = "https://releases.llvm.org/11.0.0/tools/clang/tools/extra/docs/clang-tidy/checks/%1.html"; - constexpr const char CLANG_STATIC_ANALYZER_DOCUMENTATION_URL[] = "https://clang-analyzer.llvm.org/available_checks.html"; From 6f5e20bf6419899238f9b8283b6463a323f5c12b Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Fri, 25 Jun 2021 14:16:09 +0200 Subject: [PATCH 028/149] ClangTools: Fix applying check options The algorithm we used to determine whether an option was applicable did not take the wildcard syntax into account that is used when all sub- checks of a group are enabled. Fixes: QTCREATORBUG-25827 Change-Id: I1d385bcc90ea9bf0bfac5cffe8739a8148aa740f Reviewed-by: David Schulz --- .../cpptools/clangdiagnosticconfig.cpp | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/plugins/cpptools/clangdiagnosticconfig.cpp b/src/plugins/cpptools/clangdiagnosticconfig.cpp index c31fc4f8ce5..95ba5a7328e 100644 --- a/src/plugins/cpptools/clangdiagnosticconfig.cpp +++ b/src/plugins/cpptools/clangdiagnosticconfig.cpp @@ -130,11 +130,26 @@ QString ClangDiagnosticConfig::clangTidyChecksAsJson() const { QString jsonString = "{Checks: '" + clangTidyChecks() + ",-clang-diagnostic-*', CheckOptions: ["; + + // The check is either listed verbatim or covered by the "-*" pattern. + const auto checkIsEnabled = [this](const QString &check) { + for (QString subString = check; !subString.isEmpty(); + subString.chop(subString.length() - subString.lastIndexOf('-'))) { + const int idx = m_clangTidyChecks.indexOf(subString); + if (idx == -1) + continue; + if (idx > 0 && m_clangTidyChecks.at(idx - 1) == '-') + continue; + if (subString == check || QStringView(m_clangTidyChecks) + .mid(idx + subString.length()).startsWith(QLatin1String("-*"))) { + return true; + } + } + return false; + }; + for (auto it = m_tidyChecksOptions.cbegin(); it != m_tidyChecksOptions.cend(); ++it) { - const int idx = m_clangTidyChecks.indexOf(it.key()); - if (idx == -1) - continue; - if (idx > 0 && m_clangTidyChecks.at(idx - 1) == '-') + if (!checkIsEnabled(it.key())) continue; QString optionString; for (auto optIt = it.value().begin(); optIt != it.value().end(); ++optIt) { From 9f29b9e31b0e91a979a194374dec360580779989 Mon Sep 17 00:00:00 2001 From: hjk Date: Mon, 28 Jun 2021 09:58:54 +0200 Subject: [PATCH 029/149] Docker: Use id of current user to run the container This ensures typical files created in the build process are accessible and removable(!) by the user outside. Downside is that some commands (e.g. whoami) won't work well within the container as that user typically is not listed in /etc/passwd etc. inside the container. Buildsteps requiring them won't work until the container image is (re-)created with this data in place. In general, there seems to be no silver bullet for this problem. An extensive discussion of pros and cons can be found at https://jtreminio.com/blog/running-docker-containers-as-current-host-user Change-Id: I0421e031324ffe3d64b9aeede289a43357c46e3d Reviewed-by: Christian Stenger --- src/plugins/docker/dockerdevice.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 9939e2d7988..913dd2d3cc9 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -68,6 +68,11 @@ #include #include +#ifdef Q_OS_UNIX +#include +#include +#endif + using namespace Core; using namespace ProjectExplorer; using namespace QtSupport; @@ -579,6 +584,10 @@ void DockerDevicePrivate::tryCreateLocalFileAccess() "-e", "XAUTHORITY=/.Xauthority", "--net", "host"}}; +#ifdef Q_OS_UNIX + dockerRun.addArgs({"-u", QString("%1:%2").arg(getuid()).arg(getgid())}); +#endif + for (const QString &mount : qAsConst(m_mounts)) dockerRun.addArgs({"-v", mount + ':' + mount}); From 8c7b6a7549d1fc0b0776d9a3993332f2dc3a5c56 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Tue, 29 Jun 2021 08:18:11 +0200 Subject: [PATCH 030/149] Docker: Use directory commonly existing Trying to use /tmp on Windows ends up in a failing process call as this directory usually does not exist. Change-Id: I80a5e45c474d3fe6ff6f2ede67837722cb59078a Reviewed-by: hjk --- src/plugins/docker/dockerdevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 913dd2d3cc9..cafa6e9b850 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -934,7 +934,7 @@ FilePath DockerDevice::searchInPath(const FilePath &filePath) const CommandLine dcmd{"docker", {"exec", d->m_container, "which", path}}; QtcProcess proc; proc.setCommand(dcmd); - proc.setWorkingDirectory("/tmp"); + proc.setWorkingDirectory(QDir::tempPath()); proc.start(); proc.waitForFinished(); From ca1ce3a1bad473b4f68dee9531a12d614301b67e Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Tue, 29 Jun 2021 08:19:45 +0200 Subject: [PATCH 031/149] Docker: Silence assert We query the device with executables that may or may not exist. So, expect the call to fail if the image does not contain the respective executable in PATH. Change-Id: Iff43879c72155de8a8a9276e26085d03ead61f2f Reviewed-by: hjk --- src/plugins/docker/dockerdevice.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index cafa6e9b850..faf7218d38f 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -939,7 +939,8 @@ FilePath DockerDevice::searchInPath(const FilePath &filePath) const proc.waitForFinished(); LOG("Run sync:" << dcmd.toUserOutput() << " result: " << proc.exitCode()); - QTC_ASSERT(proc.exitCode() == 0, return filePath); + if (proc.exitCode() != 0) + return {}; const QString output = proc.stdOut().trimmed(); return mapToGlobalPath(FilePath::fromString(output)); From acec1a3a03037852fa3ea44c971e312f1d53f6a1 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Tue, 29 Jun 2021 08:38:36 +0200 Subject: [PATCH 032/149] Docker: Pass on working directory only if provided Change-Id: I9ae62b441c0006b39d4bef5f06420e798c28c2a5 Reviewed-by: hjk --- src/plugins/docker/dockerdevice.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index faf7218d38f..1ef8a2a7f32 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -991,7 +991,8 @@ void DockerDevice::runProcess(QtcProcess &process) const const CommandLine origCmd = process.commandLine(); CommandLine cmd{"docker", {"exec"}}; - cmd.addArgs({"-w", workingDir.path()}); + if (!workingDir.isEmpty()) + cmd.addArgs({"-w", workingDir.path()}); if (process.keepsWriteChannelOpen()) cmd.addArg("-i"); cmd.addArg(d->m_container); From eeccdd48395ec0add4133018d922ac77dabaa1ee Mon Sep 17 00:00:00 2001 From: Alessandro Portale Date: Mon, 28 Jun 2021 18:41:02 +0200 Subject: [PATCH 033/149] Android: Remove size constraints of the "New AVD Dialog" This removes minimal/maximal sizes of the dialog. Unjustified size restrictions cause poor UX. The initial dialog width is increased from 600 to 800 px. Fixes: QTCREATORBUG-25846 Change-Id: I306fbacba2a833f0c9a3bd6a23c6ff9a1bba9c3b Reviewed-by: Leena Miettinen Reviewed-by: Assam Boudjelthia --- src/plugins/android/addnewavddialog.ui | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/plugins/android/addnewavddialog.ui b/src/plugins/android/addnewavddialog.ui index c60b43fdb3e..f0736429a0c 100644 --- a/src/plugins/android/addnewavddialog.ui +++ b/src/plugins/android/addnewavddialog.ui @@ -6,22 +6,9 @@ 0 0 - 600 - 243 + 800 - - - 600 - 0 - - - - - 1024 - 16777215 - - Create new AVD From 5f8e344a7d8741fd909f52a28aa6ed1eb87118ac Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Mon, 28 Jun 2021 17:23:36 +0200 Subject: [PATCH 034/149] QmlDesigner: Update alias icon in navigator * Replace the aliasOn/Off icon in the navigator with alias icon * Change color for alias icon in Property Editor so it will match with the navigator Task-number: QDS-4646 Change-Id: Idda5e62dac527c645c7c7b9de9c8b448087f31a7 Reviewed-by: Brook Cronin Reviewed-by: Tim Jenssen --- .../propertyEditorQmlSources/QtQuick/ComponentSection.qml | 2 +- .../propertyEditorQmlSources/imports/StudioTheme/Values.qml | 2 ++ .../qmldesigner/components/navigator/navigatorview.cpp | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ComponentSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ComponentSection.qml index f723adaf567..bbab6812539 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ComponentSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ComponentSection.qml @@ -161,7 +161,7 @@ Section { when: hasAliasExport PropertyChanges { target: aliasIndicatorIcon - color: StudioTheme.Values.themeLinkIndicatorColorInteraction + color: StudioTheme.Values.themeAliasIconChecked } }, State { diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml index cbba4a8f3ab..c34e70b3028 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml @@ -200,6 +200,8 @@ QtObject { property string themeError: Theme.color(Theme.DSerrorColor) property string themeDisabled: Theme.color(Theme.DSdisabledColor) + property string themeAliasIconChecked: Theme.color(Theme.DSnavigatorAliasIconChecked) + // Control colors property string themeControlBackground: Theme.color(Theme.DScontrolBackground) property string themeControlBackgroundInteraction: Theme.color(Theme.DScontrolBackgroundInteraction) diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp index 2ba7941b3db..95586fa2eeb 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp @@ -618,8 +618,8 @@ void NavigatorView::setupWidget() const QString visibilityOnUnicode = Theme::getIconUnicode(Theme::Icon::visibilityOn); const QString visibilityOffUnicode = Theme::getIconUnicode(Theme::Icon::visibilityOff); - const QString aliasOnUnicode = Theme::getIconUnicode(Theme::Icon::idAliasOn); - const QString aliasOffUnicode = Theme::getIconUnicode(Theme::Icon::idAliasOff); + const QString aliasOnUnicode = Theme::getIconUnicode(Theme::Icon::alias); + const QString aliasOffUnicode = aliasOnUnicode; const QString lockOnUnicode = Theme::getIconUnicode(Theme::Icon::lockOn); const QString lockOffUnicode = Theme::getIconUnicode(Theme::Icon::lockOff); From 3ac25411b48233b7dea9826614c20c9936cd45e0 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Mon, 28 Jun 2021 19:40:07 +0200 Subject: [PATCH 035/149] QmlDesigner: Update layout specifics * Update GridLayoutSpecifics * Add missing Row-, Column- and StackLayoutSpecifics Task-number: QDS-4648 Change-Id: I8d9c48ea7cfa4a33a84a9f2c039cca0c0b10f62b Reviewed-by: Tim Jenssen --- .../QtQuick/Layouts/ColumnLayoutSpecifics.qml | 52 ++++++ .../QtQuick/Layouts/GridLayoutSpecifics.qml | 170 +++++++++--------- .../QtQuick/Layouts/RowLayoutSpecifics.qml | 52 ++++++ .../QtQuick/Layouts/StackLayoutSpecifics.qml | 52 ++++++ 4 files changed, 243 insertions(+), 83 deletions(-) create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Layouts/ColumnLayoutSpecifics.qml create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Layouts/RowLayoutSpecifics.qml create mode 100644 share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Layouts/StackLayoutSpecifics.qml diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Layouts/ColumnLayoutSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Layouts/ColumnLayoutSpecifics.qml new file mode 100644 index 00000000000..1caca948bd4 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Layouts/ColumnLayoutSpecifics.qml @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import HelperWidgets 2.0 +import StudioTheme 1.0 as StudioTheme + +Section { + anchors.left: parent.left + anchors.right: parent.right + caption: qsTr("Column Layout") + + SectionLayout { + PropertyLabel { text: qsTr("Column spacing") } + + SecondColumnLayout { + SpinBox { + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.spacing + minimumValue: -4000 + maximumValue: 4000 + decimals: 0 + } + + ExpandingSpacer {} + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Layouts/GridLayoutSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Layouts/GridLayoutSpecifics.qml index e51868d78e3..57d7881bbd2 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Layouts/GridLayoutSpecifics.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Layouts/GridLayoutSpecifics.qml @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -23,113 +23,117 @@ ** ****************************************************************************/ -import QtQuick 2.1 +import QtQuick 2.15 +import QtQuick.Layouts 1.15 import HelperWidgets 2.0 -import QtQuick.Layouts 1.0 +import StudioTheme 1.0 as StudioTheme -Column { +Section { anchors.left: parent.left anchors.right: parent.right + caption: qsTr("Grid Layout") - Section { - anchors.left: parent.left - anchors.right: parent.right - caption: qsTr("GridLayout") + SectionLayout { + PropertyLabel { text: qsTr("Columns & Rows") } - - SectionLayout { - - Label { - text: qsTr("Columns") + SecondColumnLayout { + SpinBox { + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.columns + minimumValue: 0 + maximumValue: 2000 + decimals: 0 } - SecondColumnLayout { - SpinBox { - backendValue: backendValues.columns - minimumValue: 0 - maximumValue: 2000 - decimals: 0 - } + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } - ExpandingSpacer { - } + IconLabel { icon: StudioTheme.Constants.columnsAndRows } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + SpinBox { + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.rows + minimumValue: 0 + maximumValue: 2000 + decimals: 0 } - Label { - text: qsTr("Rows") + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + IconLabel { + icon: StudioTheme.Constants.columnsAndRows + rotation: 90 } - SecondColumnLayout { - SpinBox { - backendValue: backendValues.rows - minimumValue: 0 - maximumValue: 2000 - decimals: 0 - } + ExpandingSpacer {} + } - ExpandingSpacer { - } + PropertyLabel { text: qsTr("Spacing") } + + SecondColumnLayout { + SpinBox { + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.columnSpacing + minimumValue: -4000 + maximumValue: 4000 + decimals: 0 } - Label { - text: qsTr("Flow") + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + IconLabel { icon: StudioTheme.Constants.columnsAndRows } + + Spacer { implicitWidth: StudioTheme.Values.controlGap } + + SpinBox { + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.rowSpacing + minimumValue: -4000 + maximumValue: 4000 + decimals: 0 } - SecondColumnLayout { - ComboBox { - model: ["LeftToRight", "TopToBottom"] - backendValue: backendValues.flow - Layout.fillWidth: true - scope: "GridLayout" - } + Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } + + IconLabel { + icon: StudioTheme.Constants.columnsAndRows + rotation: 90 } - Label { - text: qsTr("Layout Direction") + ExpandingSpacer {} + } + + PropertyLabel { text: qsTr("Flow") } + + SecondColumnLayout { + ComboBox { + model: ["LeftToRight", "TopToBottom"] + backendValue: backendValues.flow + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + scope: "GridLayout" } - SecondColumnLayout { - ComboBox { - model: ["LeftToRight", "RightToLeft"] - backendValue: backendValues.layoutDirection - Layout.fillWidth: true - scope: "Qt" - } + ExpandingSpacer {} + } + PropertyLabel { text: qsTr("Layout direction") } + + SecondColumnLayout { + ComboBox { + model: ["LeftToRight", "RightToLeft"] + backendValue: backendValues.layoutDirection + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + scope: "Qt" } - - Label { - text: qsTr("Row Spacing") - } - - SecondColumnLayout { - SpinBox { - backendValue: backendValues.rowSpacing - minimumValue: -4000 - maximumValue: 4000 - decimals: 0 - } - - ExpandingSpacer { - } - } - - Label { - text: qsTr("Column Spacing") - } - - SecondColumnLayout { - SpinBox { - backendValue: backendValues.columnSpacing - minimumValue: -4000 - maximumValue: 4000 - decimals: 0 - } - - ExpandingSpacer { - } - } + ExpandingSpacer {} } } } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Layouts/RowLayoutSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Layouts/RowLayoutSpecifics.qml new file mode 100644 index 00000000000..1cedc5465a8 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Layouts/RowLayoutSpecifics.qml @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import HelperWidgets 2.0 +import StudioTheme 1.0 as StudioTheme + +Section { + anchors.left: parent.left + anchors.right: parent.right + caption: qsTr("Row Layout") + + SectionLayout { + PropertyLabel { text: qsTr("Row spacing") } + + SecondColumnLayout { + SpinBox { + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.spacing + minimumValue: -4000 + maximumValue: 4000 + decimals: 0 + } + + ExpandingSpacer {} + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Layouts/StackLayoutSpecifics.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Layouts/StackLayoutSpecifics.qml new file mode 100644 index 00000000000..ace3447ba53 --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/Layouts/StackLayoutSpecifics.qml @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import HelperWidgets 2.0 +import StudioTheme 1.0 as StudioTheme + +Section { + anchors.left: parent.left + anchors.right: parent.right + caption: qsTr("Stack Layout") + + SectionLayout { + PropertyLabel { text: qsTr("Current index") } + + SecondColumnLayout { + SpinBox { + implicitWidth: StudioTheme.Values.twoControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth + backendValue: backendValues.currentIndex + minimumValue: 0 + maximumValue: 1000 + decimals: 0 + } + + ExpandingSpacer {} + } + } +} From 3f5130ef2cf9f5df08dfb6121d15bbe2495ee3bb Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Mon, 28 Jun 2021 19:43:30 +0200 Subject: [PATCH 036/149] QmlDesigner: Update SplitViewSpecifics Change-Id: Ibb22bd5648b66c4fd12eb8a97f84d5eaa6a33670 Reviewed-by: Tim Jenssen --- .../Controls/OrientationCombobox.qml | 10 ++++++---- .../Controls/SplitViewSpecifics.qml | 16 ++++++---------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/plugins/qmldesigner/componentsplugin/Controls/OrientationCombobox.qml b/src/plugins/qmldesigner/componentsplugin/Controls/OrientationCombobox.qml index 8cb14025c9c..cfa7f8e6a78 100644 --- a/src/plugins/qmldesigner/componentsplugin/Controls/OrientationCombobox.qml +++ b/src/plugins/qmldesigner/componentsplugin/Controls/OrientationCombobox.qml @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -23,13 +23,15 @@ ** ****************************************************************************/ -import QtQuick 2.1 +import QtQuick 2.15 +import QtQuick.Layouts 1.15 import HelperWidgets 2.0 -import QtQuick.Layouts 1.0 +import StudioTheme 1.0 as StudioTheme ComboBox { backendValue: backendValues.orientation - implicitWidth: 180 + implicitWidth: StudioTheme.Values.singleControlColumnWidth + + StudioTheme.Values.actionIndicatorWidth model: [ "Horizontal", "Vertical" ] scope: "Qt" } diff --git a/src/plugins/qmldesigner/componentsplugin/Controls/SplitViewSpecifics.qml b/src/plugins/qmldesigner/componentsplugin/Controls/SplitViewSpecifics.qml index a47e46450fa..ce4f16fe3f6 100644 --- a/src/plugins/qmldesigner/componentsplugin/Controls/SplitViewSpecifics.qml +++ b/src/plugins/qmldesigner/componentsplugin/Controls/SplitViewSpecifics.qml @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -23,9 +23,9 @@ ** ****************************************************************************/ -import QtQuick 2.1 +import QtQuick 2.15 +import QtQuick.Layouts 1.15 import HelperWidgets 2.0 -import QtQuick.Layouts 1.0 Column { anchors.left: parent.left @@ -37,20 +37,16 @@ Column { caption: qsTr("Split View") SectionLayout { - Label { + PropertyLabel { text: qsTr("Orientation") tooltip: qsTr("Orientation of the split view.") } SecondColumnLayout { - OrientationCombobox { - } + OrientationCombobox {} - ExpandingSpacer { - - } + ExpandingSpacer {} } - } } } From b26fb834a8a5f79beb70fbbe5e302a66a201bf59 Mon Sep 17 00:00:00 2001 From: hjk Date: Mon, 28 Jun 2021 11:53:29 +0200 Subject: [PATCH 037/149] Docker: Use categorized logging for DockerDeviceOperation Change-Id: Ib9aa32c65bd38b4b72b356603182e84ce5672e91 Reviewed-by: Christian Stenger --- src/plugins/docker/dockerdevice.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 1ef8a2a7f32..c7af7ead2c9 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -64,6 +64,7 @@ #include #include #include +#include #include #include #include @@ -78,13 +79,11 @@ using namespace ProjectExplorer; using namespace QtSupport; using namespace Utils; -#define LOG(x) -//#define LOG(x) qDebug() << x - namespace Docker { namespace Internal { -const QByteArray pidMarker = "__qtc"; +static Q_LOGGING_CATEGORY(dockerDeviceLog, "qtc.docker.device", QtWarningMsg); +#define LOG(x) qCDebug(dockerDeviceLog) << x class DockerDeviceProcess : public ProjectExplorer::DeviceProcess { From 117fa35e00300c5455b4c38368852ff978c44f88 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Mon, 28 Jun 2021 17:09:06 +0200 Subject: [PATCH 038/149] Clangd: Ensure open document for "Send Goto Implementation" requests Fixes: QTCREATORBUG-25861 Change-Id: Ied14b3ad129b205b05653a40c249a0a339ab686a Reviewed-by: David Schulz --- src/plugins/clangcodemodel/clangdclient.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index fd7a8883365..4ec1ac3d4af 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -1245,6 +1245,10 @@ void ClangdClient::Private::handleGotoDefinitionResult() void ClangdClient::Private::sendGotoImplementationRequest(const Utils::Link &link) { + if (!q->documentForFilePath(link.targetFilePath) + && followSymbolData->openedFiles.insert(link.targetFilePath).second) { + q->openExtraFile(link.targetFilePath); + } const Position position(link.targetLine - 1, link.targetColumn); const TextDocumentIdentifier documentId(DocumentUri::fromFilePath(link.targetFilePath)); GotoImplementationRequest req(TextDocumentPositionParams(documentId, position)); From 24190d9c0bd7198817da11060d55e047d3436c2c Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 29 Jun 2021 09:45:30 +0200 Subject: [PATCH 039/149] CMake: Use local paths with file system watcher Change-Id: I6eb831255f90b56115a5c5d79175a8985052ff83 Reviewed-by: Cristian Adam --- src/plugins/cmakeprojectmanager/fileapiparser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/cmakeprojectmanager/fileapiparser.cpp b/src/plugins/cmakeprojectmanager/fileapiparser.cpp index 762df18f411..99a2810b68b 100644 --- a/src/plugins/cmakeprojectmanager/fileapiparser.cpp +++ b/src/plugins/cmakeprojectmanager/fileapiparser.cpp @@ -852,7 +852,7 @@ bool FileApiParser::setupCMakeFileApi(const FilePath &buildDirectory, Utils::Fil } } - watcher.addDirectory(cmakeReplyDirectory(buildDirectory).toString(), FileSystemWatcher::WatchAllChanges); + watcher.addDirectory(cmakeReplyDirectory(buildDirectory).path(), FileSystemWatcher::WatchAllChanges); return true; } From 54703850852458d225ba33b9f29af9786dfce73c Mon Sep 17 00:00:00 2001 From: Mahmoud Badri Date: Wed, 23 Jun 2021 17:39:08 +0300 Subject: [PATCH 040/149] QmlDesigner: clear assets selection on focus out Fixes: QDS-4625 Change-Id: I4a9ab829522d66e3644bba7d43845bbe82d0773b Reviewed-by: Miikka Heikkinen --- share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml | 4 +++- .../qmldesigner/components/itemlibrary/itemlibrarywidget.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml index 4dbc8adb009..6632f07d57b 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml @@ -64,9 +64,11 @@ Item { } // called from C++ to close context menu on focus out - function closeContextMenu() + function handleViewFocusOut() { contextMenu.close() + selectedAssets = {} + selectedAssetsChanged() } ScrollView { // TODO: experiment using ListView instead of ScrollView + Column diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp index a4ba21335ee..d70d0c19bf8 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp @@ -88,7 +88,7 @@ bool ItemLibraryWidget::eventFilter(QObject *obj, QEvent *event) if (obj == m_itemViewQuickWidget.data()) QMetaObject::invokeMethod(m_itemViewQuickWidget->rootObject(), "closeContextMenu"); else if (obj == m_assetsWidget.data()) - QMetaObject::invokeMethod(m_assetsWidget->rootObject(), "closeContextMenu"); + QMetaObject::invokeMethod(m_assetsWidget->rootObject(), "handleViewFocusOut"); } else if (event->type() == QMouseEvent::MouseMove) { if (m_itemToDrag.isValid()) { QMouseEvent *me = static_cast(event); From 5ead2afeeb306737ff530ef07b1a564f4489f5f2 Mon Sep 17 00:00:00 2001 From: hjk Date: Mon, 28 Jun 2021 15:26:44 +0200 Subject: [PATCH 041/149] Utils: Implement FilePath::removeRecursively Sprinkle a few QTC_ASSERT to make sure we are in a known context. Cmake's FileApiReader uses this before moving directories as QFile::rename does not do anything if the target exists. Change-Id: I555f99e81a9fe7d93ae66145eeebfa9d5880bc51 Reviewed-by: Christian Stenger --- src/libs/utils/fileutils.cpp | 10 ++++++++++ src/libs/utils/fileutils.h | 2 ++ .../cmakeprojectmanager/fileapireader.cpp | 4 ++-- src/plugins/docker/dockerdevice.cpp | 18 ++++++++++++++++++ src/plugins/docker/dockerdevice.h | 1 + .../devicesupport/devicemanager.cpp | 6 ++++++ .../projectexplorer/devicesupport/idevice.cpp | 7 +++++++ .../projectexplorer/devicesupport/idevice.h | 1 + 8 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index 9311801e069..c21a17d86b6 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -84,6 +84,7 @@ static DeviceFileHooks s_deviceHooks; */ bool FileUtils::removeRecursively(const FilePath &filePath, QString *error) { + QTC_ASSERT(!filePath.needsDevice(), return false); QFileInfo fileInfo = filePath.toFileInfo(); if (!fileInfo.exists() && !fileInfo.isSymLink()) return true; @@ -1406,6 +1407,15 @@ bool FilePath::removeFile() const return QFile::remove(path()); } +bool FilePath::removeRecursively() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.removeRecursively, return false); + return s_deviceHooks.removeRecursively(*this); + } + return FileUtils::removeRecursively(*this); +} + bool FilePath::copyFile(const FilePath &target) const { if (needsDevice()) { diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index 808f140ce58..3153b99bea9 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -78,6 +78,7 @@ public: std::function createDir; std::function exists; std::function removeFile; + std::function removeRecursively; std::function copyFile; std::function renameFile; std::function searchInPath; @@ -167,6 +168,7 @@ public: QDateTime lastModified() const; QFile::Permissions permissions() const; bool removeFile() const; + bool removeRecursively() const; bool copyFile(const FilePath &target) const; bool renameFile(const FilePath &target) const; diff --git a/src/plugins/cmakeprojectmanager/fileapireader.cpp b/src/plugins/cmakeprojectmanager/fileapireader.cpp index 1ae75671b65..4d1e11e55e0 100644 --- a/src/plugins/cmakeprojectmanager/fileapireader.cpp +++ b/src/plugins/cmakeprojectmanager/fileapireader.cpp @@ -310,11 +310,11 @@ void FileApiReader::makeBackupConfiguration(bool store) if (reply.exists()) { if (replyPrev.exists()) - FileUtils::removeRecursively(replyPrev); + replyPrev.removeRecursively(); + QTC_CHECK(!replyPrev.exists()); if (!reply.renameFile(replyPrev)) Core::MessageManager::writeFlashing(tr("Failed to rename %1 to %2.") .arg(reply.toString(), replyPrev.toString())); - } FilePath cmakeCacheTxt = m_parameters.buildDirectory.pathAppended("CMakeCache.txt"); diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index c7af7ead2c9..06a3b57d1a9 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -878,6 +878,24 @@ bool DockerDevice::removeFile(const FilePath &filePath) const return exitCode == 0; } +bool DockerDevice::removeRecursively(const FilePath &filePath) const +{ + QTC_ASSERT(handlesFile(filePath), return false); + QTC_ASSERT(filePath.path().startsWith('/'), return false); + tryCreateLocalFileAccess(); + if (hasLocalFileAccess()) { + const FilePath localAccess = mapToLocalAccess(filePath); + const bool res = localAccess.removeRecursively(); + LOG("Remove recursively? " << filePath.toUserOutput() << localAccess.toUserOutput() << res); + return res; + } +// Open this up only when really needed. +// const CommandLine cmd("rm", "-rf", {filePath.path()}); +// const int exitCode = d->runSynchronously(cmd); +// return exitCode == 0; + return false; +} + bool DockerDevice::copyFile(const FilePath &filePath, const FilePath &target) const { QTC_ASSERT(handlesFile(filePath), return false); diff --git a/src/plugins/docker/dockerdevice.h b/src/plugins/docker/dockerdevice.h index 49d2ed61730..ff12113a828 100644 --- a/src/plugins/docker/dockerdevice.h +++ b/src/plugins/docker/dockerdevice.h @@ -82,6 +82,7 @@ public: bool createDirectory(const Utils::FilePath &filePath) const override; bool exists(const Utils::FilePath &filePath) const override; bool removeFile(const Utils::FilePath &filePath) const override; + bool removeRecursively(const Utils::FilePath &filePath) const override; bool copyFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override; bool renameFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override; Utils::FilePath searchInPath(const Utils::FilePath &filePath) const override; diff --git a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp index e5bce263ccb..5e1151c6b70 100644 --- a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp @@ -429,6 +429,12 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_uniqueremoveFile(filePath); }; + deviceHooks.removeRecursively = [](const FilePath &filePath) { + auto device = DeviceManager::deviceForPath(filePath); + QTC_ASSERT(device, return false); + return device->removeRecursively(filePath); + }; + deviceHooks.copyFile = [](const FilePath &filePath, const FilePath &target) { auto device = DeviceManager::deviceForPath(filePath); QTC_ASSERT(device, return false); diff --git a/src/plugins/projectexplorer/devicesupport/idevice.cpp b/src/plugins/projectexplorer/devicesupport/idevice.cpp index 6292bc85d0b..ac36f7d3316 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/idevice.cpp @@ -281,6 +281,13 @@ bool IDevice::removeFile(const FilePath &filePath) const return false; } +bool IDevice::removeRecursively(const FilePath &filePath) const +{ + Q_UNUSED(filePath); + QTC_CHECK(false); + return false; +} + bool IDevice::copyFile(const FilePath &filePath, const FilePath &target) const { Q_UNUSED(filePath); diff --git a/src/plugins/projectexplorer/devicesupport/idevice.h b/src/plugins/projectexplorer/devicesupport/idevice.h index 876bd4bf27e..a114f85c662 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.h +++ b/src/plugins/projectexplorer/devicesupport/idevice.h @@ -245,6 +245,7 @@ public: virtual bool createDirectory(const Utils::FilePath &filePath) const; virtual bool exists(const Utils::FilePath &filePath) const; virtual bool removeFile(const Utils::FilePath &filePath) const; + virtual bool removeRecursively(const Utils::FilePath &filePath) const; virtual bool copyFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const; virtual bool renameFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const; virtual Utils::FilePath searchInPath(const Utils::FilePath &filePath) const; From 13ad1351b03fe721ba8e29254bf6aa4d6b99c2c9 Mon Sep 17 00:00:00 2001 From: hjk Date: Mon, 28 Jun 2021 17:39:14 +0200 Subject: [PATCH 042/149] Utils: Add QtcProcess::setStandardInputFile Needed for SshProcess. Change-Id: Id3c8ca0cc86d7a515371fb3651e2d186cbea4df6 Reviewed-by: Christian Kandeler --- src/libs/utils/qtcprocess.cpp | 5 +++++ src/libs/utils/qtcprocess.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 8aa0099ca85..e7db61665ad 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -444,6 +444,11 @@ bool QtcProcess::keepsWriteChannelOpen() const return d->m_process->m_keepStdInOpen; } +void QtcProcess::setStandardInputFile(const QString &inputFile) +{ + d->m_process->setStandardInputFile(inputFile); +} + void QtcProcess::setRemoteProcessHooks(const DeviceProcessHooks &hooks) { s_deviceHooks = hooks; diff --git a/src/libs/utils/qtcprocess.h b/src/libs/utils/qtcprocess.h index 3f82d70687e..95fe4d22563 100644 --- a/src/libs/utils/qtcprocess.h +++ b/src/libs/utils/qtcprocess.h @@ -182,6 +182,8 @@ public: void setKeepWriteChannelOpen(); bool keepsWriteChannelOpen() const; + void setStandardInputFile(const QString &inputFile); + signals: void started(); void finished(); From d5182a890aa07cb8d09c2c13f69dcef7b3eb3c81 Mon Sep 17 00:00:00 2001 From: Miina Puuronen Date: Thu, 24 Jun 2021 14:08:40 +0300 Subject: [PATCH 043/149] QmlDesigner: Disable Asset Library context menu when not needed Assets Library context menus options are now enabled only when needed and disabled otherwise. Also some renaming etc. Task-number: QDS-4628 Change-Id: I5b2c4209f8389e51cf3f5e470bc14e5f9ffc1f29 Reviewed-by: Mahmoud Badri Reviewed-by: Miikka Heikkinen --- .../itemLibraryQmlSources/Assets.qml | 4 +++ .../itemlibrary/itemlibraryassetsmodel.cpp | 30 ++++++++++++++++--- .../itemlibrary/itemlibraryassetsmodel.h | 14 +++++++-- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml index 6632f07d57b..10a44e3ed79 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/Assets.qml @@ -33,6 +33,7 @@ import StudioTheme 1.0 as StudioTheme Item { property var selectedAssets: ({}) + property int allExpandedState: 0 DropArea { id: dropArea @@ -81,11 +82,13 @@ Item { StudioControls.MenuItem { text: qsTr("Expand All") + enabled: allExpandedState !== 1 onTriggered: assetsModel.toggleExpandAll(true) } StudioControls.MenuItem { text: qsTr("Collapse All") + enabled: allExpandedState !== 2 onTriggered: assetsModel.toggleExpandAll(false) } } @@ -118,6 +121,7 @@ Item { dirExpanded = !dirExpanded } onShowContextMenu: { + allExpandedState = assetsModel.getAllExpandedState() contextMenu.popup() } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp index 9b6bd7c3cca..5429264733a 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.cpp @@ -44,14 +44,35 @@ namespace QmlDesigner { -void ItemLibraryAssetsModel::saveExpandedState(bool expanded, const QString §ionName) +void ItemLibraryAssetsModel::saveExpandedState(bool expanded, const QString &assetPath) { - m_expandedStateHash.insert(sectionName, expanded); + m_expandedStateHash.insert(assetPath, expanded); } -bool ItemLibraryAssetsModel::loadExpandedState(const QString §ionName) +bool ItemLibraryAssetsModel::loadExpandedState(const QString &assetPath) { - return m_expandedStateHash.value(sectionName, true); + return m_expandedStateHash.value(assetPath, true); +} + +ItemLibraryAssetsModel::DirExpandState ItemLibraryAssetsModel::getAllExpandedState() const +{ + const auto keys = m_expandedStateHash.keys(); + bool allExpanded = true; + bool allCollapsed = true; + for (const QString &assetPath : keys) { + bool expanded = m_expandedStateHash.value(assetPath); + + if (expanded) + allCollapsed = false; + if (!expanded) + allExpanded = false; + + if (!allCollapsed && !allExpanded) + break; + } + + return allExpanded ? DirExpandState::AllExpanded : allCollapsed ? DirExpandState::AllCollapsed + : DirExpandState::SomeExpanded; } void ItemLibraryAssetsModel::toggleExpandAll(bool expand) @@ -200,6 +221,7 @@ void ItemLibraryAssetsModel::setRootPath(const QString &path) ItemLibraryAssetsDir *assetsDir = new ItemLibraryAssetsDir(subDir.path(), currDepth, loadExpandedState(subDir.path()), currAssetsDir); currAssetsDir->addDir(assetsDir); + saveExpandedState(loadExpandedState(assetsDir->dirPath()), assetsDir->dirPath()); isEmpty &= parseDirRecursive(assetsDir, currDepth + 1); } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h index 34d5fa2bc78..75c7132df4f 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetsmodel.h @@ -69,10 +69,18 @@ public: const QSet &supportedSuffixes() const; const QSet &previewableSuffixes() const; - static void saveExpandedState(bool expanded, const QString §ionName); - static bool loadExpandedState(const QString §ionName); + static void saveExpandedState(bool expanded, const QString &assetPath); + static bool loadExpandedState(const QString &assetPath); + + enum class DirExpandState { + SomeExpanded, + AllExpanded, + AllCollapsed + }; + Q_ENUM(DirExpandState) Q_INVOKABLE void toggleExpandAll(bool expand); + Q_INVOKABLE DirExpandState getAllExpandedState() const; private: SynchronousImageCache &m_fontImageCache; @@ -83,7 +91,7 @@ private: ItemLibraryAssetsDir *m_assetsDir = nullptr; QHash m_roleNames; - inline static QHash m_expandedStateHash; + inline static QHash m_expandedStateHash; // }; } // namespace QmlDesigner From 75ffccad0b42248afc24362ed86edbfd6b79a20d Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Tue, 29 Jun 2021 10:47:48 +0200 Subject: [PATCH 044/149] Docker: Make usage of UID and GID configurable Make it optional to use UID and GID of the current user. Change-Id: I1a7eeaba0cfae204b05f0ab4d409d965b8fc09ef Reviewed-by: hjk --- src/plugins/docker/dockerdevice.cpp | 16 +++++++++++++++- src/plugins/docker/dockerdevice.h | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 06a3b57d1a9..151a5551aa8 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -59,6 +59,7 @@ #include #include +#include #include #include #include @@ -302,6 +303,12 @@ public: m_repoLineEdit->setText(dockerDevice->data().repo); m_repoLineEdit->setEnabled(false); + m_runAsOutsideUser = new QCheckBox(tr("Run as outside user")); + m_runAsOutsideUser->setToolTip(tr("Use user id and group id of the user running Qt Creator " + "in the docker container.")); + m_runAsOutsideUser->setChecked(dockerDevice->data().useLocalUidGid); + m_runAsOutsideUser->setEnabled(HostOsInfo::isLinuxHost()); + auto logView = new QTextBrowser; auto autoDetectButton = new QPushButton(tr("Auto-detect Kit Items")); @@ -322,6 +329,7 @@ public: Form { m_idLabel, m_idLineEdit, Break(), m_repoLabel, m_repoLineEdit, Break(), + m_runAsOutsideUser, Break(), Column { Space(20), Row { autoDetectButton, undoAutoDetectButton, Stretch() }, @@ -338,6 +346,7 @@ private: QLineEdit *m_idLineEdit; QLabel *m_repoLabel; QLineEdit *m_repoLineEdit; + QCheckBox *m_runAsOutsideUser; }; IDeviceWidget *DockerDevice::createWidget() @@ -584,7 +593,8 @@ void DockerDevicePrivate::tryCreateLocalFileAccess() "--net", "host"}}; #ifdef Q_OS_UNIX - dockerRun.addArgs({"-u", QString("%1:%2").arg(getuid()).arg(getgid())}); + if (m_data.useLocalUidGid) + dockerRun.addArgs({"-u", QString("%1:%2").arg(getuid()).arg(getgid())}); #endif for (const QString &mount : qAsConst(m_mounts)) @@ -677,6 +687,7 @@ const char DockerDeviceDataImageIdKey[] = "DockerDeviceDataImageId"; const char DockerDeviceDataRepoKey[] = "DockerDeviceDataRepo"; const char DockerDeviceDataTagKey[] = "DockerDeviceDataTag"; const char DockerDeviceDataSizeKey[] = "DockerDeviceDataSize"; +const char DockerDeviceUseOutsideUser[] = "DockerDeviceUseUidGid"; void DockerDevice::fromMap(const QVariantMap &map) { @@ -685,6 +696,8 @@ void DockerDevice::fromMap(const QVariantMap &map) d->m_data.repo = map.value(DockerDeviceDataRepoKey).toString(); d->m_data.tag = map.value(DockerDeviceDataTagKey).toString(); d->m_data.size = map.value(DockerDeviceDataSizeKey).toString(); + d->m_data.useLocalUidGid = map.value(DockerDeviceUseOutsideUser, + HostOsInfo::isLinuxHost()).toBool(); } QVariantMap DockerDevice::toMap() const @@ -694,6 +707,7 @@ QVariantMap DockerDevice::toMap() const map.insert(DockerDeviceDataRepoKey, d->m_data.repo); map.insert(DockerDeviceDataTagKey, d->m_data.tag); map.insert(DockerDeviceDataSizeKey, d->m_data.size); + map.insert(DockerDeviceUseOutsideUser, d->m_data.useLocalUidGid); return map; } diff --git a/src/plugins/docker/dockerdevice.h b/src/plugins/docker/dockerdevice.h index ff12113a828..b299880904f 100644 --- a/src/plugins/docker/dockerdevice.h +++ b/src/plugins/docker/dockerdevice.h @@ -43,6 +43,7 @@ public: QString repo; QString tag; QString size; + bool useLocalUidGid = true; }; class DockerDevice : public ProjectExplorer::IDevice From 4271b3a299c2dc367d010fdc2e8399cad747affb Mon Sep 17 00:00:00 2001 From: hjk Date: Mon, 28 Jun 2021 15:48:18 +0200 Subject: [PATCH 045/149] Docker: Bark if there is no container before trying to run on it Change-Id: I64bee9901a515d46aa18e35d5e4a26f9105519ea Reviewed-by: Christian Stenger --- src/plugins/docker/dockerdevice.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 151a5551aa8..3b1e9b1250a 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -1018,6 +1018,14 @@ bool DockerDevice::writeFileContents(const Utils::FilePath &filePath, const QByt void DockerDevice::runProcess(QtcProcess &process) const { + tryCreateLocalFileAccess(); + if (d->m_container.isEmpty()) { + LOG("No container set to run " << process.commandLine().toUserOutput()); + QTC_CHECK(false); + process.setResult(QtcProcess::StartFailed); + return; + } + const FilePath workingDir = process.workingDirectory(); const CommandLine origCmd = process.commandLine(); From a659f445c7664f35a88466fc0870f816bd3cdab9 Mon Sep 17 00:00:00 2001 From: Andre Hartmann Date: Thu, 24 Jun 2021 16:05:27 +0200 Subject: [PATCH 046/149] Debugger: Escape special chars in line annotation By re-using the existing function quoteUnprintable() and moving their code as escapeUnprintable() to watchutils. In contrast to the watches window, where the escaping can be disabled by the context menu, the line annotations are always escaped for simplicity. Change-Id: I76adfd7cd70ec92ff0d7f7ea41fc30ae0057cad0 Reviewed-by: Christian Stenger Reviewed-by: David Schulz --- src/plugins/debugger/sourceutils.cpp | 2 +- src/plugins/debugger/watchhandler.cpp | 31 +----------------------- src/plugins/debugger/watchutils.cpp | 34 +++++++++++++++++++++++++++ src/plugins/debugger/watchutils.h | 2 ++ 4 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/plugins/debugger/sourceutils.cpp b/src/plugins/debugger/sourceutils.cpp index fbb57679d6f..f54f222c6ef 100644 --- a/src/plugins/debugger/sourceutils.cpp +++ b/src/plugins/debugger/sourceutils.cpp @@ -414,7 +414,7 @@ static void setValueAnnotationsHelper(BaseTextEditor *textEditor, const QString expression = expressionUnderCursor(tc); if (expression.isEmpty()) continue; - const QString value = values.take(expression); // Show value one only once. + const QString value = escapeUnprintable(values.take(expression)); // Show value one only once. if (value.isEmpty()) continue; const QString annotation = QString("%1: %2").arg(expression, value); diff --git a/src/plugins/debugger/watchhandler.cpp b/src/plugins/debugger/watchhandler.cpp index 790fd4d8489..1dc061c508c 100644 --- a/src/plugins/debugger/watchhandler.cpp +++ b/src/plugins/debugger/watchhandler.cpp @@ -693,36 +693,7 @@ static QString reformatCharacter(int code, int size, bool isSigned) static QString quoteUnprintable(const QString &str) { - if (theUnprintableBase == 0) - return str; - - QString encoded; - if (theUnprintableBase == -1) { - for (const QChar c : str) { - int u = c.unicode(); - if (c.isPrint()) - encoded += c; - else if (u == '\r') - encoded += "\\r"; - else if (u == '\t') - encoded += "\\t"; - else if (u == '\n') - encoded += "\\n"; - else - encoded += QString("\\%1").arg(c.unicode(), 3, 8, QLatin1Char('0')); - } - return encoded; - } - - for (const QChar c : str) { - if (c.isPrint()) - encoded += c; - else if (theUnprintableBase == 8) - encoded += QString("\\%1").arg(c.unicode(), 3, 8, QLatin1Char('0')); - else - encoded += QString("\\u%1").arg(c.unicode(), 4, 16, QLatin1Char('0')); - } - return encoded; + return escapeUnprintable(str, theUnprintableBase); } static int itemFormat(const WatchItem *item) diff --git a/src/plugins/debugger/watchutils.cpp b/src/plugins/debugger/watchutils.cpp index d257605686f..5c3097ca907 100644 --- a/src/plugins/debugger/watchutils.cpp +++ b/src/plugins/debugger/watchutils.cpp @@ -242,5 +242,39 @@ QString formatToolTipAddress(quint64 a) return "0x" + rc; } +QString escapeUnprintable(const QString &str, int unprintableBase) +{ + if (unprintableBase == 0) + return str; + + QString encoded; + if (unprintableBase == -1) { + for (const QChar c : str) { + int u = c.unicode(); + if (c.isPrint()) + encoded += c; + else if (u == '\r') + encoded += "\\r"; + else if (u == '\t') + encoded += "\\t"; + else if (u == '\n') + encoded += "\\n"; + else + encoded += QString("\\%1").arg(u, 3, 8, QLatin1Char('0')); + } + return encoded; + } + + for (const QChar c : str) { + if (c.isPrint()) + encoded += c; + else if (unprintableBase == 8) + encoded += QString("\\%1").arg(c.unicode(), 3, 8, QLatin1Char('0')); + else + encoded += QString("\\u%1").arg(c.unicode(), 4, 16, QLatin1Char('0')); + } + return encoded; +} + } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/watchutils.h b/src/plugins/debugger/watchutils.h index da23eb6b3c0..6c060a49e81 100644 --- a/src/plugins/debugger/watchutils.h +++ b/src/plugins/debugger/watchutils.h @@ -47,5 +47,7 @@ bool isIntType(const QString &type); QString formatToolTipAddress(quint64 a); QString removeObviousSideEffects(const QString &exp); +QString escapeUnprintable(const QString &str, int unprintableBase = -1); + } // namespace Internal } // namespace Debugger From 4c07f10d33ab3915743ed57d7db36fb526fcec94 Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Tue, 29 Jun 2021 12:32:02 +0200 Subject: [PATCH 047/149] CMake: Fix compilation build with older CMake versions Change-Id: I86dbff49cf5d00bf75d2e32154db2d3d254e2b17 Reviewed-by: Eike Ziller Reviewed-by: Christian Stenger --- cmake/QtCreatorAPIInternal.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/QtCreatorAPIInternal.cmake b/cmake/QtCreatorAPIInternal.cmake index 20e18d5be91..7ebd2d31d71 100644 --- a/cmake/QtCreatorAPIInternal.cmake +++ b/cmake/QtCreatorAPIInternal.cmake @@ -1,6 +1,6 @@ if (CMAKE_VERSION VERSION_LESS 3.18) if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_VERSION VERSION_LESS 3.16) - set(BUILD_WITH_PCH OFF) + set(BUILD_WITH_PCH OFF CACHE BOOL "" FORCE) endif() endif() From a86c480d50cd90edb1974975a81977216ec32810 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 29 Jun 2021 11:58:54 +0300 Subject: [PATCH 048/149] QmlDesigner: Fix warnings about using anchors for layouted items Using anchors for layouted items is undefined behavior, so fix those instances in GradientPresetList. These also caused crash at design mode open when building against Qt 6.2. Fixes: QDS-4647 Change-Id: I801ce4c98c6cda74be522c73c4e1eb5bef7c7e1b Reviewed-by: Mahmoud Badri --- .../imports/HelperWidgets/GradientPresetList.qml | 3 +-- .../imports/HelperWidgets/GradientPresetTabContent.qml | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/GradientPresetList.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/GradientPresetList.qml index f997329b036..345b0ba2cd3 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/GradientPresetList.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/GradientPresetList.qml @@ -79,8 +79,7 @@ HelperWindow { StudioControls.TabBar { id: presetTabBar - anchors.left: parent.left - anchors.right: parent.right + Layout.fillWidth: true StudioControls.TabButton { text: qsTr("System Presets") diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/GradientPresetTabContent.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/GradientPresetTabContent.qml index 94e83a905c2..111fe061169 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/GradientPresetTabContent.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/GradientPresetTabContent.qml @@ -33,10 +33,9 @@ import StudioTheme 1.0 as StudioTheme Rectangle { id: tabBackground - width: parent.width - height: parent.height color: StudioTheme.Values.themeControlBackground - anchors.fill: parent + Layout.fillWidth: true + Layout.fillHeight: true property alias viewModel: gradientTable.model property bool editableName: false From 48348b8dc41b35590ef0a6a338f4e54b10410ef3 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Wed, 23 Jun 2021 16:29:37 +0300 Subject: [PATCH 049/149] QmlPuppet: Change custom 3D geometries to use public API Custom geometries often break and code becomes convoluted with version ifdeffing, so refactor the custom geometries to use public geometry api, which works both in Qt5 and Qt6. Fixes: QDS-4611 Change-Id: I2d78776a14d5c8153b52806842254dbedb5de83d Reviewed-by: Mahmoud Badri --- .../qml2puppet/editor3d/camerageometry.cpp | 70 ++++------ .../qml2puppet/editor3d/camerageometry.h | 18 +-- .../qml2puppet/editor3d/editor3d.pri | 2 + .../qml2puppet/editor3d/geometrybase.cpp | 81 ++++++++++++ .../qml2puppet/editor3d/geometrybase.h | 69 ++++++++++ .../qml2puppet/editor3d/gridgeometry.cpp | 48 ++----- .../qml2puppet/editor3d/gridgeometry.h | 17 +-- .../qml2puppet/editor3d/lightgeometry.cpp | 51 ++----- .../qml2puppet/editor3d/lightgeometry.h | 17 +-- .../qml2puppet/editor3d/linegeometry.cpp | 44 +----- .../qml2puppet/editor3d/linegeometry.h | 18 +-- .../editor3d/selectionboxgeometry.cpp | 125 ++++++++---------- .../editor3d/selectionboxgeometry.h | 22 +-- .../qt5informationnodeinstanceserver.cpp | 2 + src/tools/qml2puppet/CMakeLists.txt | 1 + src/tools/qml2puppet/qml2puppet.qbs | 2 + 16 files changed, 286 insertions(+), 301 deletions(-) create mode 100644 share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/geometrybase.cpp create mode 100644 share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/geometrybase.h diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/camerageometry.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/camerageometry.cpp index c1f533fedaf..c80343bb466 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/camerageometry.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/camerageometry.cpp @@ -27,7 +27,6 @@ #include "camerageometry.h" -#include #include #include #include @@ -35,7 +34,6 @@ #include #include #include -#include #include @@ -43,7 +41,7 @@ namespace QmlDesigner { namespace Internal { CameraGeometry::CameraGeometry() - : QQuick3DGeometry() + : GeometryBase() { } @@ -51,19 +49,6 @@ CameraGeometry::~CameraGeometry() { } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) -QString CameraGeometry::name() const -{ - return objectName(); -} - -void CameraGeometry::setName(const QString &name) -{ - setObjectName(name); - emit nameChanged(); -} -#endif - QQuick3DCamera *CameraGeometry::camera() const { return m_camera; @@ -111,7 +96,7 @@ void CameraGeometry::setCamera(QQuick3DCamera *camera) this, &CameraGeometry::handleCameraPropertyChange); } emit cameraChanged(); - update(); + handleCameraPropertyChange(); } void CameraGeometry::setViewPortRect(const QRectF &rect) @@ -121,26 +106,37 @@ void CameraGeometry::setViewPortRect(const QRectF &rect) m_viewPortRect = rect; emit viewPortRectChanged(); - update(); + updateGeometry(); } void CameraGeometry::handleCameraPropertyChange() { m_cameraUpdatePending = true; + clear(); + setStride(12); // To avoid div by zero inside QtQuick3D update(); } QSSGRenderGraphObject *CameraGeometry::updateSpatialNode(QSSGRenderGraphObject *node) +{ + if (m_cameraUpdatePending) { + m_cameraUpdatePending = false; + updateGeometry(); + } + + return QQuick3DGeometry::updateSpatialNode(node); +} + +void CameraGeometry::doUpdateGeometry() { if (!m_camera) - return node; + return; // If camera properties have been updated, we need to defer updating the frustum geometry // to the next frame to ensure camera's spatial node has been properly updated. if (m_cameraUpdatePending) { - QTimer::singleShot(0, this, &CameraGeometry::update); - m_cameraUpdatePending = false; - return node; + update(); + return; } if (!m_camera->cameraNode()) { @@ -148,11 +144,7 @@ QSSGRenderGraphObject *CameraGeometry::updateSpatialNode(QSSGRenderGraphObject * m_camera->mapToViewport({}, m_viewPortRect.width(), m_viewPortRect.height()); } - setStride(12); // Silence a warning - node = QQuick3DGeometry::updateSpatialNode(node); - QSSGRenderGeometry *geometry = static_cast(node); - - geometry->clear(); + GeometryBase::doUpdateGeometry(); QByteArray vertexData; QByteArray indexData; @@ -160,25 +152,11 @@ QSSGRenderGraphObject *CameraGeometry::updateSpatialNode(QSSGRenderGraphObject * QVector3D maxBounds; fillVertexData(vertexData, indexData, minBounds, maxBounds); - geometry->setStride(12); -#if QT_VERSION < QT_VERSION_CHECK(6, 1, 0) - geometry->addAttribute(QSSGRenderGeometry::Attribute::PositionSemantic, 0, - QSSGRenderGeometry::Attribute::ComponentType::F32Type); - geometry->addAttribute(QSSGRenderGeometry::Attribute::IndexSemantic, 0, - QSSGRenderGeometry::Attribute::ComponentType::U16Type); - geometry->setPrimitiveType(QSSGRenderGeometry::Lines); -#else - geometry->addAttribute(QSSGMesh::RuntimeMeshData::Attribute::PositionSemantic, 0, - QSSGMesh::Mesh::ComponentType::Float32); - geometry->addAttribute(QSSGMesh::RuntimeMeshData::Attribute::IndexSemantic, 0, - QSSGMesh::Mesh::ComponentType::UnsignedInt16); - geometry->setPrimitiveType(QSSGMesh::Mesh::DrawMode::Lines); -#endif - geometry->setVertexData(vertexData); - geometry->setIndexData(indexData); - geometry->setBounds(minBounds, maxBounds); - - return node; + addAttribute(QQuick3DGeometry::Attribute::IndexSemantic, 0, + QQuick3DGeometry::Attribute::U16Type); + setVertexData(vertexData); + setIndexData(indexData); + setBounds(minBounds, maxBounds); } void CameraGeometry::fillVertexData(QByteArray &vertexData, QByteArray &indexData, diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/camerageometry.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/camerageometry.h index 55d31ca389e..5b4fa66fe46 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/camerageometry.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/camerageometry.h @@ -27,29 +27,19 @@ #ifdef QUICK3D_MODULE -#include +#include "geometrybase.h" + #include namespace QmlDesigner { namespace Internal { -class CameraGeometry : public QQuick3DGeometry +class CameraGeometry : public GeometryBase { Q_OBJECT Q_PROPERTY(QQuick3DCamera *camera READ camera WRITE setCamera NOTIFY cameraChanged) Q_PROPERTY(QRectF viewPortRect READ viewPortRect WRITE setViewPortRect NOTIFY viewPortRectChanged) -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - // Name property was removed in Qt 6, so define it here for compatibility. - // Name maps to object name. - Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) -public: - QString name() const; - void setName(const QString &name); -signals: - void nameChanged(); -#endif - public: CameraGeometry(); ~CameraGeometry() override; @@ -68,10 +58,12 @@ signals: protected: QSSGRenderGraphObject *updateSpatialNode(QSSGRenderGraphObject *node) override; + void doUpdateGeometry() override; private: void fillVertexData(QByteArray &vertexData, QByteArray &indexData, QVector3D &minBounds, QVector3D &maxBounds); + QQuick3DCamera *m_camera = nullptr; QRectF m_viewPortRect; bool m_cameraUpdatePending = false; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/editor3d.pri b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/editor3d.pri index bcfbc5e12ed..6eedaad8f1f 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/editor3d.pri +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/editor3d.pri @@ -1,5 +1,6 @@ HEADERS += $$PWD/generalhelper.h \ $$PWD/mousearea3d.h \ + $$PWD/geometrybase.h \ $$PWD/camerageometry.h \ $$PWD/lightgeometry.h \ $$PWD/gridgeometry.h \ @@ -9,6 +10,7 @@ HEADERS += $$PWD/generalhelper.h \ SOURCES += $$PWD/generalhelper.cpp \ $$PWD/mousearea3d.cpp \ + $$PWD/geometrybase.cpp \ $$PWD/camerageometry.cpp \ $$PWD/lightgeometry.cpp \ $$PWD/gridgeometry.cpp \ diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/geometrybase.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/geometrybase.cpp new file mode 100644 index 00000000000..70d48fb20b5 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/geometrybase.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifdef QUICK3D_MODULE + +#include "geometrybase.h" + +namespace QmlDesigner { +namespace Internal { + +GeometryBase::GeometryBase() + : QQuick3DGeometry() +{ + m_updatetimer.setSingleShot(true); + m_updatetimer.setInterval(0); + connect(&m_updatetimer, &QTimer::timeout, this, &GeometryBase::doUpdateGeometry); + updateGeometry(); + setStride(12); // To avoid div by zero inside QtQuick3D +} + +GeometryBase::~GeometryBase() +{ +} + +void GeometryBase::doUpdateGeometry() +{ + clear(); + + setStride(12); + + addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0, + QQuick3DGeometry::Attribute::F32Type); + setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines); + + update(); +} + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +QString GeometryBase::name() const +{ + return objectName(); +} + +void GeometryBase::setName(const QString &name) +{ + setObjectName(name); + emit nameChanged(); +} +#endif + +void GeometryBase::updateGeometry() +{ + m_updatetimer.start(); +} + +} +} + +#endif // QUICK3D_MODULE diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/geometrybase.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/geometrybase.h new file mode 100644 index 00000000000..f8486a6c837 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/geometrybase.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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 + +#ifdef QUICK3D_MODULE + +#include + +#include + +namespace QmlDesigner { +namespace Internal { + +class GeometryBase : public QQuick3DGeometry +{ + Q_OBJECT + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + // Name property was removed in Qt 6, so define it here for compatibility. + // Name maps to object name. + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) +public: + QString name() const; + void setName(const QString &name); +signals: + void nameChanged(); +#endif + +public: + GeometryBase(); + ~GeometryBase() override; + +protected: + void updateGeometry(); + virtual void doUpdateGeometry(); + +private: + QTimer m_updatetimer; +}; + +} +} + +QML_DECLARE_TYPE(QmlDesigner::Internal::GeometryBase) + +#endif // QUICK3D_MODULE diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/gridgeometry.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/gridgeometry.cpp index a9e12f69f45..123153c0c9a 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/gridgeometry.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/gridgeometry.cpp @@ -27,33 +27,19 @@ #include "gridgeometry.h" -#include - namespace QmlDesigner { namespace Internal { GridGeometry::GridGeometry() - : QQuick3DGeometry() + : GeometryBase() { + updateGeometry(); } GridGeometry::~GridGeometry() { } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) -QString GridGeometry::name() const -{ - return objectName(); -} - -void GridGeometry::setName(const QString &name) -{ - setObjectName(name); - emit nameChanged(); -} -#endif - int GridGeometry::lines() const { return m_lines; @@ -79,7 +65,7 @@ void GridGeometry::setLines(int count) return; m_lines = qMax(count, 1); emit linesChanged(); - update(); + updateGeometry(); } // Space between lines @@ -90,7 +76,7 @@ void GridGeometry::setStep(float step) return; m_step = step; emit stepChanged(); - update(); + updateGeometry(); } void GridGeometry::setIsCenterLine(bool enabled) @@ -100,36 +86,22 @@ void GridGeometry::setIsCenterLine(bool enabled) m_isCenterLine = enabled; emit isCenterLineChanged(); - update(); + updateGeometry(); } -QSSGRenderGraphObject *GridGeometry::updateSpatialNode(QSSGRenderGraphObject *node) +void GridGeometry::doUpdateGeometry() { - setStride(12); // Silence a warning - node = QQuick3DGeometry::updateSpatialNode(node); - QSSGRenderGeometry *geometry = static_cast(node); - geometry->clear(); + GeometryBase::doUpdateGeometry(); QByteArray vertexData; fillVertexData(vertexData); - geometry->setStride(12); -#if QT_VERSION < QT_VERSION_CHECK(6, 1, 0) - geometry->addAttribute(QSSGRenderGeometry::Attribute::PositionSemantic, 0, - QSSGRenderGeometry::Attribute::ComponentType::F32Type); - geometry->setPrimitiveType(QSSGRenderGeometry::Lines); -#else - geometry->addAttribute(QSSGMesh::RuntimeMeshData::Attribute::PositionSemantic, 0, - QSSGMesh::Mesh::ComponentType::Float32); - geometry->setPrimitiveType(QSSGMesh::Mesh::DrawMode::Lines); -#endif - geometry->setVertexData(vertexData); + setVertexData(vertexData); int lastIndex = (vertexData.size() - 1) / int(sizeof(QVector3D)); auto vertexPtr = reinterpret_cast(vertexData.data()); - geometry->setBounds(QVector3D(vertexPtr[0][0], vertexPtr[0][1], 0.0), - QVector3D(vertexPtr[lastIndex][0], vertexPtr[lastIndex][1], 0.0)); - return node; + setBounds(QVector3D(vertexPtr[0][0], vertexPtr[0][1], 0.0), + QVector3D(vertexPtr[lastIndex][0], vertexPtr[lastIndex][1], 0.0)); } void GridGeometry::fillVertexData(QByteArray &vertexData) diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/gridgeometry.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/gridgeometry.h index 6074fb42453..39d9c38e2d0 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/gridgeometry.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/gridgeometry.h @@ -27,12 +27,12 @@ #ifdef QUICK3D_MODULE -#include +#include "geometrybase.h" namespace QmlDesigner { namespace Internal { -class GridGeometry : public QQuick3DGeometry +class GridGeometry : public GeometryBase { Q_OBJECT @@ -41,17 +41,6 @@ class GridGeometry : public QQuick3DGeometry Q_PROPERTY(bool isCenterLine READ isCenterLine WRITE setIsCenterLine NOTIFY isCenterLineChanged) Q_PROPERTY(bool isSubdivision MEMBER m_isSubdivision) -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - // Name property was removed in Qt 6, so define it here for compatibility. - // Name maps to object name. - Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) -public: - QString name() const; - void setName(const QString &name); -signals: - void nameChanged(); -#endif - public: GridGeometry(); ~GridGeometry() override; @@ -71,7 +60,7 @@ signals: void isCenterLineChanged(); protected: - QSSGRenderGraphObject *updateSpatialNode(QSSGRenderGraphObject *node) override; + void doUpdateGeometry() override; private: void fillVertexData(QByteArray &vertexData); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/lightgeometry.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/lightgeometry.cpp index 735c237a0fb..cae1090cad9 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/lightgeometry.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/lightgeometry.cpp @@ -37,7 +37,7 @@ namespace QmlDesigner { namespace Internal { LightGeometry::LightGeometry() - : QQuick3DGeometry() + : GeometryBase() { } @@ -45,19 +45,6 @@ LightGeometry::~LightGeometry() { } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) -QString LightGeometry::name() const -{ - return objectName(); -} - -void LightGeometry::setName(const QString &name) -{ - setObjectName(name); - emit nameChanged(); -} -#endif - LightGeometry::LightType LightGeometry::lightType() const { return m_lightType; @@ -71,45 +58,29 @@ void LightGeometry::setLightType(LightGeometry::LightType lightType) m_lightType = lightType; emit lightTypeChanged(); - update(); + updateGeometry(); } -QSSGRenderGraphObject *LightGeometry::updateSpatialNode(QSSGRenderGraphObject *node) +void LightGeometry::doUpdateGeometry() { if (m_lightType == LightType::Invalid) - return node; + return; - setStride(12); // Silence a warning - node = QQuick3DGeometry::updateSpatialNode(node); - QSSGRenderGeometry *geometry = static_cast(node); - - geometry->clear(); + GeometryBase::doUpdateGeometry(); QByteArray vertexData; QByteArray indexData; QVector3D minBounds; QVector3D maxBounds; + fillVertexData(vertexData, indexData, minBounds, maxBounds); - geometry->setStride(12); -#if QT_VERSION < QT_VERSION_CHECK(6, 1, 0) - geometry->addAttribute(QSSGRenderGeometry::Attribute::PositionSemantic, 0, - QSSGRenderGeometry::Attribute::ComponentType::F32Type); - geometry->addAttribute(QSSGRenderGeometry::Attribute::IndexSemantic, 0, - QSSGRenderGeometry::Attribute::ComponentType::U16Type); - geometry->setPrimitiveType(QSSGRenderGeometry::Lines); -#else - geometry->addAttribute(QSSGMesh::RuntimeMeshData::Attribute::PositionSemantic, 0, - QSSGMesh::Mesh::ComponentType::Float32); - geometry->addAttribute(QSSGMesh::RuntimeMeshData::Attribute::IndexSemantic, 0, - QSSGMesh::Mesh::ComponentType::UnsignedInt16); - geometry->setPrimitiveType(QSSGMesh::Mesh::DrawMode::Lines); -#endif - geometry->setVertexData(vertexData); - geometry->setIndexData(indexData); - geometry->setBounds(minBounds, maxBounds); + addAttribute(QQuick3DGeometry::Attribute::IndexSemantic, 0, + QQuick3DGeometry::Attribute::U16Type); - return node; + setVertexData(vertexData); + setIndexData(indexData); + setBounds(minBounds, maxBounds); } void LightGeometry::fillVertexData(QByteArray &vertexData, QByteArray &indexData, diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/lightgeometry.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/lightgeometry.h index e9ba7182183..6c9c2d2a12e 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/lightgeometry.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/lightgeometry.h @@ -27,27 +27,16 @@ #ifdef QUICK3D_MODULE -#include +#include "geometrybase.h" namespace QmlDesigner { namespace Internal { -class LightGeometry : public QQuick3DGeometry +class LightGeometry : public GeometryBase { Q_OBJECT Q_PROPERTY(LightType lightType READ lightType WRITE setLightType NOTIFY lightTypeChanged) -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - // Name property was removed in Qt 6, so define it here for compatibility. - // Name maps to object name. - Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) -public: - QString name() const; - void setName(const QString &name); -signals: - void nameChanged(); -#endif - public: enum class LightType { Invalid, @@ -70,7 +59,7 @@ signals: void lightTypeChanged(); protected: - QSSGRenderGraphObject *updateSpatialNode(QSSGRenderGraphObject *node) override; + void doUpdateGeometry() override; private: void fillVertexData(QByteArray &vertexData, QByteArray &indexData, diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/linegeometry.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/linegeometry.cpp index 314b7a3136b..5feffa19b83 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/linegeometry.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/linegeometry.cpp @@ -27,13 +27,11 @@ #include "linegeometry.h" -#include - namespace QmlDesigner { namespace Internal { LineGeometry::LineGeometry() - : QQuick3DGeometry() + : GeometryBase() { } @@ -41,19 +39,6 @@ LineGeometry::~LineGeometry() { } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) -QString LineGeometry::name() const -{ - return objectName(); -} - -void LineGeometry::setName(const QString &name) -{ - setObjectName(name); - emit nameChanged(); -} -#endif - QVector3D LineGeometry::startPos() const { return m_startPos; @@ -69,7 +54,7 @@ void LineGeometry::setStartPos(const QVector3D &pos) if (pos != m_startPos) { m_startPos = pos; emit startPosChanged(); - update(); + updateGeometry(); } } @@ -78,16 +63,13 @@ void LineGeometry::setEndPos(const QVector3D &pos) if (pos != m_endPos) { m_endPos = pos; emit endPosChanged(); - update(); + updateGeometry(); } } -QSSGRenderGraphObject *LineGeometry::updateSpatialNode(QSSGRenderGraphObject *node) +void LineGeometry::doUpdateGeometry() { - setStride(12); // Silence a warning - node = QQuick3DGeometry::updateSpatialNode(node); - QSSGRenderGeometry *geometry = static_cast(node); - geometry->clear(); + GeometryBase::doUpdateGeometry(); QByteArray vertexData; vertexData.resize(2 * 3 * 4); // 2 vertices of 3 floats each 4 bytes @@ -100,20 +82,8 @@ QSSGRenderGraphObject *LineGeometry::updateSpatialNode(QSSGRenderGraphObject *no dataPtr[4] = m_endPos[1]; dataPtr[5] = m_endPos[2]; - geometry->setStride(12); -#if QT_VERSION < QT_VERSION_CHECK(6, 1, 0) - geometry->addAttribute(QSSGRenderGeometry::Attribute::PositionSemantic, 0, - QSSGRenderGeometry::Attribute::ComponentType::F32Type); - geometry->setPrimitiveType(QSSGRenderGeometry::Lines); -#else - geometry->addAttribute(QSSGMesh::RuntimeMeshData::Attribute::PositionSemantic, 0, - QSSGMesh::Mesh::ComponentType::Float32); - geometry->setPrimitiveType(QSSGMesh::Mesh::DrawMode::Lines); -#endif - geometry->setVertexData(vertexData); - geometry->setBounds(m_startPos, m_endPos); - - return node; + setVertexData(vertexData); + setBounds(m_startPos, m_endPos); } } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/linegeometry.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/linegeometry.h index df83f89b258..034cce0c934 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/linegeometry.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/linegeometry.h @@ -27,29 +27,19 @@ #ifdef QUICK3D_MODULE -#include +#include "geometrybase.h" + #include namespace QmlDesigner { namespace Internal { -class LineGeometry : public QQuick3DGeometry +class LineGeometry : public GeometryBase { Q_OBJECT Q_PROPERTY(QVector3D startPos READ startPos WRITE setStartPos NOTIFY startPosChanged) Q_PROPERTY(QVector3D endPos READ endPos WRITE setEndPos NOTIFY endPosChanged) -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - // Name property was removed in Qt 6, so define it here for compatibility. - // Name maps to object name. - Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) -public: - QString name() const; - void setName(const QString &name); -signals: - void nameChanged(); -#endif - public: LineGeometry(); ~LineGeometry() override; @@ -66,7 +56,7 @@ signals: void endPosChanged(); protected: - QSSGRenderGraphObject *updateSpatialNode(QSSGRenderGraphObject *node) override; + void doUpdateGeometry() override; private: QVector3D m_startPos; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.cpp index 7e340ff25dc..9e2bd181dbd 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.cpp @@ -27,7 +27,6 @@ #include "selectionboxgeometry.h" -#include #include #include #include @@ -36,7 +35,6 @@ #include #include #include -#include #include @@ -49,7 +47,7 @@ static const QVector3D maxVec = QVector3D(floatMax, floatMax, floatMax); static const QVector3D minVec = QVector3D(floatMin, floatMin, floatMin); SelectionBoxGeometry::SelectionBoxGeometry() - : QQuick3DGeometry() + : GeometryBase() { } @@ -60,19 +58,6 @@ SelectionBoxGeometry::~SelectionBoxGeometry() m_connections.clear(); } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) -QString SelectionBoxGeometry::name() const -{ - return objectName(); -} - -void SelectionBoxGeometry::setName(const QString &name) -{ - setObjectName(name); - emit nameChanged(); -} -#endif - QQuick3DNode *SelectionBoxGeometry::targetNode() const { return m_targetNode; @@ -93,11 +78,26 @@ bool QmlDesigner::Internal::SelectionBoxGeometry::isEmpty() const return m_isEmpty; } +void SelectionBoxGeometry::setEmpty(bool isEmpty) +{ + if (m_isEmpty != isEmpty) { + m_isEmpty = isEmpty; + emit isEmptyChanged(); + } +} + QSSGBounds3 SelectionBoxGeometry::bounds() const { return m_bounds; } +void SelectionBoxGeometry::clearGeometry() +{ + clear(); + setStride(12); // To avoid div by zero inside QtQuick3D + setEmpty(true); +} + void SelectionBoxGeometry::setTargetNode(QQuick3DNode *targetNode) { if (m_targetNode == targetNode) @@ -109,17 +109,18 @@ void SelectionBoxGeometry::setTargetNode(QQuick3DNode *targetNode) if (auto model = qobject_cast(m_targetNode)) { QObject::connect(model, &QQuick3DModel::sourceChanged, - this, &SelectionBoxGeometry::targetMeshUpdated, Qt::QueuedConnection); + this, &SelectionBoxGeometry::spatialNodeUpdateNeeded, Qt::QueuedConnection); QObject::connect(model, &QQuick3DModel::geometryChanged, - this, &SelectionBoxGeometry::targetMeshUpdated, Qt::QueuedConnection); + this, &SelectionBoxGeometry::spatialNodeUpdateNeeded, Qt::QueuedConnection); } if (m_targetNode) { QObject::connect(m_targetNode, &QQuick3DNode::parentChanged, - this, &SelectionBoxGeometry::update, Qt::QueuedConnection); + this, &SelectionBoxGeometry::spatialNodeUpdateNeeded, Qt::QueuedConnection); } + clearGeometry(); emit targetNodeChanged(); - update(); + spatialNodeUpdateNeeded(); } void SelectionBoxGeometry::setRootNode(QQuick3DNode *rootNode) @@ -130,7 +131,7 @@ void SelectionBoxGeometry::setRootNode(QQuick3DNode *rootNode) m_rootNode = rootNode; emit rootNodeChanged(); - update(); + spatialNodeUpdateNeeded(); } void SelectionBoxGeometry::setView3D(QQuick3DViewport *view) @@ -141,24 +142,31 @@ void SelectionBoxGeometry::setView3D(QQuick3DViewport *view) m_view3D = view; emit view3DChanged(); - update(); + spatialNodeUpdateNeeded(); } QSSGRenderGraphObject *SelectionBoxGeometry::updateSpatialNode(QSSGRenderGraphObject *node) { - // If target node mesh has been updated, we need to defer updating the box geometry - // to the next frame to ensure target node geometry has been updated - if (m_meshUpdatePending) { - QTimer::singleShot(0, this, &SelectionBoxGeometry::update); - m_meshUpdatePending = false; - return node; + + if (m_spatialNodeUpdatePending) { + m_spatialNodeUpdatePending = false; + updateGeometry(); } - setStride(12); // Silence a warning - node = QQuick3DGeometry::updateSpatialNode(node); - QSSGRenderGeometry *geometry = static_cast(node); + return QQuick3DGeometry::updateSpatialNode(node); +} + +void SelectionBoxGeometry::doUpdateGeometry() +{ + // Some changes require a frame to be rendered for us to be able to calculate geometry, + // so defer calculations until after next frame. + if (m_spatialNodeUpdatePending) { + update(); + return; + } + + GeometryBase::doUpdateGeometry(); - geometry->clear(); for (auto &connection : qAsConst(m_connections)) QObject::disconnect(connection); m_connections.clear(); @@ -186,15 +194,10 @@ QSSGRenderGraphObject *SelectionBoxGeometry::updateSpatialNode(QSSGRenderGraphOb rootRN->localTransform = m; rootRN->markDirty(QSSGRenderNode::TransformDirtyFlag::TransformNotDirty); rootRN->calculateGlobalVariables(); - m_spatialNodeUpdatePending = false; } else if (!m_spatialNodeUpdatePending) { + // Necessary spatial nodes do not yet exist. Defer selection box creation one frame. m_spatialNodeUpdatePending = true; - // A necessary spatial node doesn't yet exist. Defer selection box creation one frame. - // Note: We don't share pending flag with target mesh update, which is checked and - // cleared at the beginning of this method, as there would be potential for an endless - // loop in case we can't ever resolve one of the spatial nodes. - QTimer::singleShot(0, this, &SelectionBoxGeometry::update); - return node; + update(); } getBounds(m_targetNode, vertexData, indexData, minBounds, maxBounds); appendVertexData(QMatrix4x4(), vertexData, indexData, minBounds, maxBounds); @@ -212,34 +215,15 @@ QSSGRenderGraphObject *SelectionBoxGeometry::updateSpatialNode(QSSGRenderGraphOb appendVertexData(QMatrix4x4(), vertexData, indexData, minBounds, maxBounds); } - geometry->setStride(12); -#if QT_VERSION < QT_VERSION_CHECK(6, 1, 0) - geometry->addAttribute(QSSGRenderGeometry::Attribute::PositionSemantic, 0, - QSSGRenderGeometry::Attribute::ComponentType::F32Type); - geometry->addAttribute(QSSGRenderGeometry::Attribute::IndexSemantic, 0, - QSSGRenderGeometry::Attribute::ComponentType::U16Type); - geometry->setPrimitiveType(QSSGRenderGeometry::Lines); -#else - geometry->addAttribute(QSSGMesh::RuntimeMeshData::Attribute::PositionSemantic, 0, - QSSGMesh::Mesh::ComponentType::Float32); - geometry->addAttribute(QSSGMesh::RuntimeMeshData::Attribute::IndexSemantic, 0, - QSSGMesh::Mesh::ComponentType::UnsignedInt16); - geometry->setPrimitiveType(QSSGMesh::Mesh::DrawMode::Lines); -#endif - geometry->setVertexData(vertexData); - geometry->setIndexData(indexData); - geometry->setBounds(minBounds, maxBounds); + addAttribute(QQuick3DGeometry::Attribute::IndexSemantic, 0, + QQuick3DGeometry::Attribute::U16Type); + setVertexData(vertexData); + setIndexData(indexData); + setBounds(minBounds, maxBounds); m_bounds = QSSGBounds3(minBounds, maxBounds); - bool empty = minBounds.isNull() && maxBounds.isNull(); - if (m_isEmpty != empty) { - m_isEmpty = empty; - // Delay notification until we're done with spatial node updates - QTimer::singleShot(0, this, &SelectionBoxGeometry::isEmptyChanged); - } - - return node; + setEmpty(minBounds.isNull() && maxBounds.isNull()); } void SelectionBoxGeometry::getBounds( @@ -405,18 +389,19 @@ void SelectionBoxGeometry::appendVertexData(const QMatrix4x4 &m, QByteArray &ver void SelectionBoxGeometry::trackNodeChanges(QQuick3DNode *node) { m_connections << QObject::connect(node, &QQuick3DNode::sceneScaleChanged, - this, &SelectionBoxGeometry::update, Qt::QueuedConnection); + this, &SelectionBoxGeometry::updateGeometry, Qt::QueuedConnection); m_connections << QObject::connect(node, &QQuick3DNode::sceneRotationChanged, - this, &SelectionBoxGeometry::update, Qt::QueuedConnection); + this, &SelectionBoxGeometry::updateGeometry, Qt::QueuedConnection); m_connections << QObject::connect(node, &QQuick3DNode::scenePositionChanged, - this, &SelectionBoxGeometry::update, Qt::QueuedConnection); + this, &SelectionBoxGeometry::updateGeometry, Qt::QueuedConnection); m_connections << QObject::connect(node, &QQuick3DNode::pivotChanged, - this, &SelectionBoxGeometry::update, Qt::QueuedConnection); + this, &SelectionBoxGeometry::updateGeometry, Qt::QueuedConnection); } -void SelectionBoxGeometry::targetMeshUpdated() +void SelectionBoxGeometry::spatialNodeUpdateNeeded() { - m_meshUpdatePending = true; + m_spatialNodeUpdatePending = true; + clearGeometry(); update(); } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.h index 947ef7d54ab..ff173942f1d 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/selectionboxgeometry.h @@ -27,15 +27,16 @@ #ifdef QUICK3D_MODULE +#include "geometrybase.h" + #include -#include #include #include namespace QmlDesigner { namespace Internal { -class SelectionBoxGeometry : public QQuick3DGeometry +class SelectionBoxGeometry : public GeometryBase { Q_OBJECT Q_PROPERTY(QQuick3DNode *targetNode READ targetNode WRITE setTargetNode NOTIFY targetNodeChanged) @@ -43,17 +44,6 @@ class SelectionBoxGeometry : public QQuick3DGeometry Q_PROPERTY(QQuick3DViewport *view3D READ view3D WRITE setView3D NOTIFY view3DChanged) Q_PROPERTY(bool isEmpty READ isEmpty NOTIFY isEmptyChanged) -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - // Name property was removed in Qt 6, so define it here for compatibility. - // Name maps to object name. - Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) -public: - QString name() const; - void setName(const QString &name); -signals: - void nameChanged(); -#endif - public: SelectionBoxGeometry(); ~SelectionBoxGeometry() override; @@ -62,6 +52,7 @@ public: QQuick3DNode *rootNode() const; QQuick3DViewport *view3D() const; bool isEmpty() const; + void setEmpty(bool isEmpty); QSSGBounds3 bounds() const; @@ -78,6 +69,7 @@ signals: protected: QSSGRenderGraphObject *updateSpatialNode(QSSGRenderGraphObject *node) override; + void doUpdateGeometry() override; private: void getBounds(QQuick3DNode *node, QByteArray &vertexData, @@ -85,7 +77,8 @@ private: void appendVertexData(const QMatrix4x4 &m, QByteArray &vertexData, QByteArray &indexData, const QVector3D &minBounds, const QVector3D &maxBounds); void trackNodeChanges(QQuick3DNode *node); - void targetMeshUpdated(); + void spatialNodeUpdateNeeded(); + void clearGeometry(); QQuick3DNode *m_targetNode = nullptr; QQuick3DViewport *m_view3D = nullptr; @@ -94,7 +87,6 @@ private: QVector m_connections; QSSGBounds3 m_bounds; bool m_spatialNodeUpdatePending = false; - bool m_meshUpdatePending = false; }; } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index 743b06a6ed7..b71729af3b1 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -373,6 +373,8 @@ void Qt5InformationNodeInstanceServer::createEditView3D() #ifdef QUICK3D_MODULE qmlRegisterRevision("MouseArea3D", 1, 0); qmlRegisterType("MouseArea3D", 1, 0, "MouseArea3D"); + qmlRegisterUncreatableType("GeometryBase", 1, 0, "GeometryBase", + "Abstract Base Class"); qmlRegisterType("CameraGeometry", 1, 0, "CameraGeometry"); qmlRegisterType("LightUtils", 1, 0, "LightGeometry"); qmlRegisterType("GridGeometry", 1, 0, "GridGeometry"); diff --git a/src/tools/qml2puppet/CMakeLists.txt b/src/tools/qml2puppet/CMakeLists.txt index 56cbec3f012..41e7b851ee3 100644 --- a/src/tools/qml2puppet/CMakeLists.txt +++ b/src/tools/qml2puppet/CMakeLists.txt @@ -129,6 +129,7 @@ extend_qtc_executable(qml2puppet SOURCES generalhelper.cpp generalhelper.h mousearea3d.cpp mousearea3d.h + geometrybase.cpp geometrybase.h camerageometry.cpp camerageometry.h lightgeometry.cpp lightgeometry.h gridgeometry.cpp gridgeometry.h diff --git a/src/tools/qml2puppet/qml2puppet.qbs b/src/tools/qml2puppet/qml2puppet.qbs index 8d7e3edfd48..6ae72354d5a 100644 --- a/src/tools/qml2puppet/qml2puppet.qbs +++ b/src/tools/qml2puppet/qml2puppet.qbs @@ -232,6 +232,7 @@ QtcTool { "instances/capturenodeinstanceserverdispatcher.h", "editor3d/generalhelper.cpp", "editor3d/mousearea3d.cpp", + "editor3d/geometrybase.cpp", "editor3d/camerageometry.cpp", "editor3d/lightgeometry.cpp", "editor3d/gridgeometry.cpp", @@ -249,6 +250,7 @@ QtcTool { Group { name: "3d-only puppet2 headers" files: [ + "editor3d/geometrybase.h", "editor3d/camerageometry.h", "editor3d/generalhelper.h", "editor3d/gridgeometry.h", From b058b6e8417915e6a6922402aa0d2a06e81915a4 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Mon, 28 Jun 2021 14:04:43 +0300 Subject: [PATCH 050/149] QmlPuppet: Add separate set of qml files for Qt6 for 3D editor Import QtQuick3D 1.15 no longer works in Qt 6.2, so provide a separate set of qml files for each major Qt version. Fixes: QDS-4642 Change-Id: I3e17fb248f71f87fb9a40dcad3c03e037cafb0f4 Reviewed-by: Mahmoud Badri Reviewed-by: Samuel Ghinet Reviewed-by: Qt CI Bot --- .../qtcreator/qml/qmlpuppet/editor3d_qt5.qrc | 55 ++ .../qtcreator/qml/qmlpuppet/editor3d_qt6.qrc | 55 ++ .../mockfiles/{ => qt5}/AdjustableArrow.qml | 0 .../mockfiles/{ => qt5}/AreaLightHandle.qml | 0 .../qmlpuppet/mockfiles/{ => qt5}/Arrow.qml | 2 +- .../mockfiles/{ => qt5}/AutoScaleHelper.qml | 0 .../mockfiles/{ => qt5}/AxisHelper.qml | 0 .../mockfiles/{ => qt5}/AxisHelperArm.qml | 2 +- .../mockfiles/{ => qt5}/CameraFrustum.qml | 0 .../mockfiles/{ => qt5}/CameraGizmo.qml | 0 .../{ => qt5}/DirectionalDraggable.qml | 0 .../{ => qt5}/EditCameraController.qml | 0 .../mockfiles/{ => qt5}/EditView3D.qml | 0 .../mockfiles/{ => qt5}/FadeHandle.qml | 0 .../mockfiles/{ => qt5}/HelperGrid.qml | 0 .../mockfiles/{ => qt5}/IconGizmo.qml | 0 .../mockfiles/{ => qt5}/IconRenderer3D.qml | 0 .../mockfiles/{ => qt5}/LightGizmo.qml | 0 .../mockfiles/{ => qt5}/LightIconGizmo.qml | 0 .../mockfiles/{ => qt5}/LightModel.qml | 0 .../qmlpuppet/mockfiles/{ => qt5}/Line3D.qml | 0 .../mockfiles/{ => qt5}/MaterialNodeView.qml | 0 .../{ => qt5}/ModelNode2DImageView.qml | 0 .../{ => qt5}/ModelNode3DImageView.qml | 0 .../mockfiles/{ => qt5}/ModelNodeView.qml | 0 .../mockfiles/{ => qt5}/MoveGizmo.qml | 0 .../mockfiles/{ => qt5}/NodeNodeView.qml | 0 .../mockfiles/{ => qt5}/Overlay2D.qml | 0 .../mockfiles/{ => qt5}/PlanarDraggable.qml | 0 .../mockfiles/{ => qt5}/PlanarMoveHandle.qml | 0 .../mockfiles/{ => qt5}/PlanarScaleHandle.qml | 0 .../mockfiles/{ => qt5}/RotateGizmo.qml | 0 .../mockfiles/{ => qt5}/RotateRing.qml | 4 +- .../mockfiles/{ => qt5}/ScaleGizmo.qml | 0 .../mockfiles/{ => qt5}/ScaleRod.qml | 2 +- .../mockfiles/{ => qt5}/SceneView3D.qml | 0 .../mockfiles/{ => qt5}/SelectionBox.qml | 0 .../mockfiles/{ => qt5}/SpotLightHandle.qml | 0 .../mockfiles/qt6/AdjustableArrow.qml | 51 + .../mockfiles/qt6/AreaLightHandle.qml | 85 ++ .../qml/qmlpuppet/mockfiles/qt6/Arrow.qml | 63 ++ .../mockfiles/qt6/AutoScaleHelper.qml | 74 ++ .../qmlpuppet/mockfiles/qt6/AxisHelper.qml | 133 +++ .../qmlpuppet/mockfiles/qt6/AxisHelperArm.qml | 70 ++ .../qmlpuppet/mockfiles/qt6/CameraFrustum.qml | 61 ++ .../qmlpuppet/mockfiles/qt6/CameraGizmo.qml | 57 ++ .../mockfiles/qt6/DirectionalDraggable.qml | 144 +++ .../mockfiles/qt6/EditCameraController.qml | 184 ++++ .../qmlpuppet/mockfiles/qt6/EditView3D.qml | 884 ++++++++++++++++++ .../qmlpuppet/mockfiles/qt6/FadeHandle.qml | 128 +++ .../qmlpuppet/mockfiles/qt6/HelperGrid.qml | 108 +++ .../qml/qmlpuppet/mockfiles/qt6/IconGizmo.qml | 121 +++ .../mockfiles/qt6/IconRenderer3D.qml | 88 ++ .../qmlpuppet/mockfiles/qt6/LightGizmo.qml | 356 +++++++ .../mockfiles/qt6/LightIconGizmo.qml | 44 + .../qmlpuppet/mockfiles/qt6/LightModel.qml | 50 + .../qml/qmlpuppet/mockfiles/qt6/Line3D.qml | 50 + .../mockfiles/qt6/MaterialNodeView.qml | 59 ++ .../mockfiles/qt6/ModelNode2DImageView.qml | 45 + .../mockfiles/qt6/ModelNode3DImageView.qml | 124 +++ .../qmlpuppet/mockfiles/qt6/ModelNodeView.qml | 96 ++ .../qml/qmlpuppet/mockfiles/qt6/MoveGizmo.qml | 175 ++++ .../qmlpuppet/mockfiles/qt6/NodeNodeView.qml | 96 ++ .../qml/qmlpuppet/mockfiles/qt6/Overlay2D.qml | 69 ++ .../mockfiles/qt6/PlanarDraggable.qml | 119 +++ .../mockfiles/qt6/PlanarMoveHandle.qml | 64 ++ .../mockfiles/qt6/PlanarScaleHandle.qml | 64 ++ .../qmlpuppet/mockfiles/qt6/RotateGizmo.qml | 293 ++++++ .../qmlpuppet/mockfiles/qt6/RotateRing.qml | 164 ++++ .../qmlpuppet/mockfiles/qt6/ScaleGizmo.qml | 241 +++++ .../qml/qmlpuppet/mockfiles/qt6/ScaleRod.qml | 73 ++ .../qmlpuppet/mockfiles/qt6/SceneView3D.qml | 92 ++ .../qmlpuppet/mockfiles/qt6/SelectionBox.qml | 65 ++ .../mockfiles/qt6/SpotLightHandle.qml | 82 ++ .../qml2puppet/iconrenderer/iconrenderer.cpp | 6 +- .../qt5informationnodeinstanceserver.cpp | 21 +- .../qml/qmlpuppet/qml2puppet/qml2puppet.pri | 3 + share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc | 51 - src/tools/qml2puppet/CMakeLists.txt | 14 + src/tools/qml2puppet/qml2puppet.qbs | 14 + 80 files changed, 4839 insertions(+), 62 deletions(-) create mode 100644 share/qtcreator/qml/qmlpuppet/editor3d_qt5.qrc create mode 100644 share/qtcreator/qml/qmlpuppet/editor3d_qt6.qrc rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/AdjustableArrow.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/AreaLightHandle.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/Arrow.qml (98%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/AutoScaleHelper.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/AxisHelper.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/AxisHelperArm.qml (98%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/CameraFrustum.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/CameraGizmo.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/DirectionalDraggable.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/EditCameraController.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/EditView3D.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/FadeHandle.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/HelperGrid.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/IconGizmo.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/IconRenderer3D.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/LightGizmo.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/LightIconGizmo.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/LightModel.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/Line3D.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/MaterialNodeView.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/ModelNode2DImageView.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/ModelNode3DImageView.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/ModelNodeView.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/MoveGizmo.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/NodeNodeView.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/Overlay2D.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/PlanarDraggable.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/PlanarMoveHandle.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/PlanarScaleHandle.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/RotateGizmo.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/RotateRing.qml (98%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/ScaleGizmo.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/ScaleRod.qml (98%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/SceneView3D.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/SelectionBox.qml (100%) rename share/qtcreator/qml/qmlpuppet/mockfiles/{ => qt5}/SpotLightHandle.qml (100%) create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AdjustableArrow.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AreaLightHandle.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/Arrow.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AutoScaleHelper.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AxisHelper.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AxisHelperArm.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/CameraFrustum.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/CameraGizmo.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/DirectionalDraggable.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditCameraController.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/FadeHandle.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/HelperGrid.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/IconGizmo.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/IconRenderer3D.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/LightGizmo.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/LightIconGizmo.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/LightModel.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/Line3D.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/MaterialNodeView.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNode2DImageView.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNode3DImageView.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNodeView.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/MoveGizmo.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/NodeNodeView.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/Overlay2D.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/PlanarDraggable.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/PlanarMoveHandle.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/PlanarScaleHandle.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/RotateGizmo.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/RotateRing.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ScaleGizmo.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ScaleRod.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/SceneView3D.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/SelectionBox.qml create mode 100644 share/qtcreator/qml/qmlpuppet/mockfiles/qt6/SpotLightHandle.qml diff --git a/share/qtcreator/qml/qmlpuppet/editor3d_qt5.qrc b/share/qtcreator/qml/qmlpuppet/editor3d_qt5.qrc new file mode 100644 index 00000000000..6f1aa104f93 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/editor3d_qt5.qrc @@ -0,0 +1,55 @@ + + + mockfiles/meshes/arrow.mesh + mockfiles/meshes/scalerod.mesh + mockfiles/meshes/ring.mesh + mockfiles/meshes/ringselect.mesh + mockfiles/meshes/axishelper.mesh + mockfiles/images/editor_camera.png + mockfiles/images/editor_camera@2x.png + mockfiles/images/area.png + mockfiles/images/area@2x.png + mockfiles/images/directional.png + mockfiles/images/directional@2x.png + mockfiles/images/point.png + mockfiles/images/point@2x.png + mockfiles/images/spot.png + mockfiles/images/spot@2x.png + mockfiles/qt5/AdjustableArrow.qml + mockfiles/qt5/AreaLightHandle.qml + mockfiles/qt5/Arrow.qml + mockfiles/qt5/AutoScaleHelper.qml + mockfiles/qt5/AxisHelper.qml + mockfiles/qt5/AxisHelperArm.qml + mockfiles/qt5/CameraFrustum.qml + mockfiles/qt5/CameraGizmo.qml + mockfiles/qt5/DirectionalDraggable.qml + mockfiles/qt5/EditCameraController.qml + mockfiles/qt5/EditView3D.qml + mockfiles/qt5/FadeHandle.qml + mockfiles/qt5/HelperGrid.qml + mockfiles/qt5/IconGizmo.qml + mockfiles/qt5/IconRenderer3D.qml + mockfiles/qt5/LightGizmo.qml + mockfiles/qt5/LightIconGizmo.qml + mockfiles/qt5/LightModel.qml + mockfiles/qt5/Line3D.qml + mockfiles/qt5/MaterialNodeView.qml + mockfiles/qt5/ModelNode2DImageView.qml + mockfiles/qt5/ModelNode3DImageView.qml + mockfiles/qt5/ModelNodeView.qml + mockfiles/qt5/MoveGizmo.qml + mockfiles/qt5/NodeNodeView.qml + mockfiles/qt5/Overlay2D.qml + mockfiles/qt5/PlanarDraggable.qml + mockfiles/qt5/PlanarMoveHandle.qml + mockfiles/qt5/PlanarScaleHandle.qml + mockfiles/qt5/RotateGizmo.qml + mockfiles/qt5/RotateRing.qml + mockfiles/qt5/ScaleGizmo.qml + mockfiles/qt5/ScaleRod.qml + mockfiles/qt5/SceneView3D.qml + mockfiles/qt5/SelectionBox.qml + mockfiles/qt5/SpotLightHandle.qml + + diff --git a/share/qtcreator/qml/qmlpuppet/editor3d_qt6.qrc b/share/qtcreator/qml/qmlpuppet/editor3d_qt6.qrc new file mode 100644 index 00000000000..c89e2806f6c --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/editor3d_qt6.qrc @@ -0,0 +1,55 @@ + + + mockfiles/meshes/arrow.mesh + mockfiles/meshes/scalerod.mesh + mockfiles/meshes/ring.mesh + mockfiles/meshes/ringselect.mesh + mockfiles/meshes/axishelper.mesh + mockfiles/images/editor_camera.png + mockfiles/images/editor_camera@2x.png + mockfiles/images/area.png + mockfiles/images/area@2x.png + mockfiles/images/directional.png + mockfiles/images/directional@2x.png + mockfiles/images/point.png + mockfiles/images/point@2x.png + mockfiles/images/spot.png + mockfiles/images/spot@2x.png + mockfiles/qt6/AdjustableArrow.qml + mockfiles/qt6/AreaLightHandle.qml + mockfiles/qt6/Arrow.qml + mockfiles/qt6/AutoScaleHelper.qml + mockfiles/qt6/AxisHelper.qml + mockfiles/qt6/AxisHelperArm.qml + mockfiles/qt6/CameraFrustum.qml + mockfiles/qt6/CameraGizmo.qml + mockfiles/qt6/DirectionalDraggable.qml + mockfiles/qt6/EditCameraController.qml + mockfiles/qt6/EditView3D.qml + mockfiles/qt6/FadeHandle.qml + mockfiles/qt6/HelperGrid.qml + mockfiles/qt6/IconGizmo.qml + mockfiles/qt6/IconRenderer3D.qml + mockfiles/qt6/LightGizmo.qml + mockfiles/qt6/LightIconGizmo.qml + mockfiles/qt6/LightModel.qml + mockfiles/qt6/Line3D.qml + mockfiles/qt6/MaterialNodeView.qml + mockfiles/qt6/ModelNode2DImageView.qml + mockfiles/qt6/ModelNode3DImageView.qml + mockfiles/qt6/ModelNodeView.qml + mockfiles/qt6/MoveGizmo.qml + mockfiles/qt6/NodeNodeView.qml + mockfiles/qt6/Overlay2D.qml + mockfiles/qt6/PlanarDraggable.qml + mockfiles/qt6/PlanarMoveHandle.qml + mockfiles/qt6/PlanarScaleHandle.qml + mockfiles/qt6/RotateGizmo.qml + mockfiles/qt6/RotateRing.qml + mockfiles/qt6/ScaleGizmo.qml + mockfiles/qt6/ScaleRod.qml + mockfiles/qt6/SceneView3D.qml + mockfiles/qt6/SelectionBox.qml + mockfiles/qt6/SpotLightHandle.qml + + diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/AdjustableArrow.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/AdjustableArrow.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/AdjustableArrow.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/AdjustableArrow.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/AreaLightHandle.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/AreaLightHandle.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/AreaLightHandle.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/AreaLightHandle.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/Arrow.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/Arrow.qml similarity index 98% rename from share/qtcreator/qml/qmlpuppet/mockfiles/Arrow.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/Arrow.qml index 8b5993a76f1..b9d0b0b07dc 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/Arrow.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/Arrow.qml @@ -28,7 +28,7 @@ import QtQuick3D 1.15 DirectionalDraggable { id: arrow - source: "meshes/arrow.mesh" + source: "../meshes/arrow.mesh" signal positionCommit() signal positionMove() diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/AutoScaleHelper.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/AutoScaleHelper.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/AutoScaleHelper.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/AutoScaleHelper.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/AxisHelper.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/AxisHelper.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/AxisHelper.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/AxisHelper.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/AxisHelperArm.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/AxisHelperArm.qml similarity index 98% rename from share/qtcreator/qml/qmlpuppet/mockfiles/AxisHelperArm.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/AxisHelperArm.qml index 0121938cada..a148c5beb92 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/AxisHelperArm.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/AxisHelperArm.qml @@ -42,7 +42,7 @@ Node { property bool hovering: false property vector3d cameraRotation: armRoot.camRotPos - source: "meshes/axishelper.mesh" + source: "../meshes/axishelper.mesh" materials: DefaultMaterial { id: posMat diffuseColor: posModel.hovering ? armRoot.hoverColor : armRoot.color diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/CameraFrustum.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/CameraFrustum.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/CameraFrustum.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/CameraFrustum.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/CameraGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/CameraGizmo.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/CameraGizmo.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/CameraGizmo.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/DirectionalDraggable.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/DirectionalDraggable.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/DirectionalDraggable.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/DirectionalDraggable.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/EditCameraController.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditCameraController.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/EditCameraController.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditCameraController.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/EditView3D.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/FadeHandle.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/FadeHandle.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/FadeHandle.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/FadeHandle.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/HelperGrid.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/HelperGrid.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/HelperGrid.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/HelperGrid.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/IconGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/IconGizmo.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/IconGizmo.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/IconGizmo.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/IconRenderer3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/IconRenderer3D.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/IconRenderer3D.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/IconRenderer3D.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/LightGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/LightGizmo.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/LightGizmo.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/LightGizmo.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/LightIconGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/LightIconGizmo.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/LightIconGizmo.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/LightIconGizmo.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/LightModel.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/LightModel.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/LightModel.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/LightModel.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/Line3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/Line3D.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/Line3D.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/Line3D.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/MaterialNodeView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/MaterialNodeView.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/MaterialNodeView.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/MaterialNodeView.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/ModelNode2DImageView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNode2DImageView.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/ModelNode2DImageView.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNode2DImageView.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/ModelNode3DImageView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNode3DImageView.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/ModelNode3DImageView.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNode3DImageView.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/ModelNodeView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNodeView.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/ModelNodeView.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ModelNodeView.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/MoveGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/MoveGizmo.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/MoveGizmo.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/MoveGizmo.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/NodeNodeView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/NodeNodeView.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/NodeNodeView.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/NodeNodeView.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/Overlay2D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/Overlay2D.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/Overlay2D.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/Overlay2D.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/PlanarDraggable.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/PlanarDraggable.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/PlanarDraggable.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/PlanarDraggable.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/PlanarMoveHandle.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/PlanarMoveHandle.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/PlanarMoveHandle.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/PlanarMoveHandle.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/PlanarScaleHandle.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/PlanarScaleHandle.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/PlanarScaleHandle.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/PlanarScaleHandle.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/RotateGizmo.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/RotateGizmo.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/RotateGizmo.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/RotateRing.qml similarity index 98% rename from share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/RotateRing.qml index e54b6301be1..003fd985177 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/RotateRing.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/RotateRing.qml @@ -49,12 +49,12 @@ Model { signal rotateCommit() signal rotateChange() - source: "meshes/ring.mesh" + source: "../meshes/ring.mesh" Model { id: pickModel objectName: "PickModel for " + rotateRing.objectName - source: "meshes/ringselect.mesh" + source: "../meshes/ringselect.mesh" pickable: true } diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/ScaleGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ScaleGizmo.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/ScaleGizmo.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ScaleGizmo.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/ScaleRod.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ScaleRod.qml similarity index 98% rename from share/qtcreator/qml/qmlpuppet/mockfiles/ScaleRod.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ScaleRod.qml index a6f684ce62c..06cfdf5c3a9 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/ScaleRod.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/ScaleRod.qml @@ -29,7 +29,7 @@ import MouseArea3D 1.0 DirectionalDraggable { id: scaleRod - source: "meshes/scalerod.mesh" + source: "../meshes/scalerod.mesh" property vector3d axis diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/SceneView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/SceneView3D.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/SceneView3D.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/SceneView3D.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/SelectionBox.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/SelectionBox.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/SelectionBox.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/SelectionBox.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/SpotLightHandle.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/SpotLightHandle.qml similarity index 100% rename from share/qtcreator/qml/qmlpuppet/mockfiles/SpotLightHandle.qml rename to share/qtcreator/qml/qmlpuppet/mockfiles/qt5/SpotLightHandle.qml diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AdjustableArrow.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AdjustableArrow.qml new file mode 100644 index 00000000000..d71b245d334 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AdjustableArrow.qml @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import LineGeometry 1.0 + +DirectionalDraggable { + id: arrowRoot + + Model { + geometry: LineGeometry { + id: lineGeometry + name: "Edit 3D ScalableArrow" + startPos: Qt.vector3d(0, 0, 0) + endPos: Qt.vector3d(0, 1, 0) + } + scale: Qt.vector3d(1, arrowRoot.length, 1) + materials: [ arrowRoot.material ] + } + + Model { + id: arrowHead + source: "#Cone" + materials: [ arrowRoot.material ] + y: arrowRoot.length - 3 + scale: Qt.vector3d(0.02, 0.035, 0.02) + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AreaLightHandle.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AreaLightHandle.qml new file mode 100644 index 00000000000..78e97e81d42 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AreaLightHandle.qml @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 + +DirectionalDraggable { + id: handleRoot + + property string currentLabel + property point currentMousePos + property string propName + property real propValue: 0 + property real newValue: 0 + property real baseScale: 5 + + scale: autoScaler.getScale(Qt.vector3d(baseScale, baseScale, baseScale)) + length: 3 + offset: -1.5 + + Model { + id: handle + source: "#Sphere" + materials: [ handleRoot.material ] + scale: Qt.vector3d(0.02, 0.02, 0.02) + } + + AutoScaleHelper { + id: autoScaler + active: handleRoot.active + view3D: handleRoot.view3D + } + + property real _startValue + property real _startScale + + signal valueCommit() + signal valueChange() + + function updateValue(relativeDistance, screenPos) + { + handleRoot.newValue = Math.round(Math.min(999999, Math.max(0, _startValue + (relativeDistance * _startScale)))); + var l = Qt.locale(); + handleRoot.currentLabel = propName + qsTr(": ") + Number(newValue).toLocaleString(l, 'f', 0); + handleRoot.currentMousePos = screenPos; + } + + onPressed: (mouseArea, screenPos)=> { + _startScale = autoScaler.relativeScale * baseScale; + _startValue = propValue; + updateValue(0, screenPos); + } + + onDragged: (mouseArea, sceneRelativeDistance, relativeDistance, screenPos)=> { + updateValue(relativeDistance, screenPos); + handleRoot.valueChange(); + } + + onReleased: (mouseArea, sceneRelativeDistance, relativeDistance, screenPos)=> { + updateValue(relativeDistance, screenPos); + handleRoot.valueCommit(); + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/Arrow.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/Arrow.qml new file mode 100644 index 00000000000..b49d5bbf5f7 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/Arrow.qml @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 + +DirectionalDraggable { + id: arrow + source: "../meshes/arrow.mesh" + + signal positionCommit() + signal positionMove() + + function localPos(sceneRelativeDistance) + { + var newScenePos = Qt.vector3d( + _targetStartPos.x + sceneRelativeDistance.x, + _targetStartPos.y + sceneRelativeDistance.y, + _targetStartPos.z + sceneRelativeDistance.z); + return targetNode.parent ? targetNode.parent.mapPositionFromScene(newScenePos) : newScenePos; + } + + onPressed: { + if (targetNode == multiSelectionNode) + _generalHelper.restartMultiSelection(); + } + + onDragged: (mouseArea, sceneRelativeDistance)=> { + targetNode.position = localPos(sceneRelativeDistance); + if (targetNode == multiSelectionNode) + _generalHelper.moveMultiSelection(false); + positionMove(); + } + + onReleased: (mouseArea, sceneRelativeDistance)=> { + targetNode.position = localPos(sceneRelativeDistance); + if (targetNode == multiSelectionNode) + _generalHelper.moveMultiSelection(true); + positionCommit(); + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AutoScaleHelper.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AutoScaleHelper.qml new file mode 100644 index 00000000000..3ff484b61a4 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AutoScaleHelper.qml @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import MouseArea3D 1.0 + +Node { + id: overlayNode + + property View3D view3D + property Camera camera: view3D.camera + property bool active: true + + // Read-only + property real relativeScale: 1 + + onActiveChanged: updateScale() + onSceneTransformChanged: updateScale() + // Trigger delayed update on camera change to ensure camera values are correct + onCameraChanged: _generalHelper.requestOverlayUpdate(); + + Connections { + target: camera + function onSceneTransformChanged() { updateScale() } + } + + Connections { + target: _generalHelper + function onOverlayUpdateNeeded() { updateScale() } + } + + function getScale(baseScale) + { + return Qt.vector3d(baseScale.x * relativeScale, baseScale.y * relativeScale, + baseScale.z * relativeScale); + } + + function updateScale() + { + if (active) + relativeScale = helper.getRelativeScale(overlayNode); + else + relativeScale = 1; + } + + MouseArea3D { + id: helper + active: false + view3D: overlayNode.view3D + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AxisHelper.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AxisHelper.qml new file mode 100644 index 00000000000..eba9beab23f --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AxisHelper.qml @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 + +View3D { + id: axisHelperView + + property var editCameraCtrl + property Node selectedNode + + camera: axisHelperCamera + + Node { + OrthographicCamera { + id: axisHelperCamera + rotation: editCameraCtrl.camera ? editCameraCtrl.camera.rotation : Qt.quaternion(1, 0, 0, 0) + position: editCameraCtrl.camera ? editCameraCtrl.camera.position.minus(editCameraCtrl._lookAtPoint) + .normalized().times(600) : Qt.vector3d(0, 0, 0) + } + + AutoScaleHelper { + id: autoScale + view3D: axisHelperView + position: axisHelperGizmo.scenePosition + } + + Node { + id: axisHelperGizmo + scale: autoScale.getScale(Qt.vector3d(4, 4, 4)) + + AxisHelperArm { + id: armX + eulerRotation: Qt.vector3d(0, 0, -90) + color: Qt.rgba(1, 0, 0, 1) + hoverColor: Qt.lighter(Qt.rgba(1, 0, 0, 1)) + view3D: axisHelperView + camRotPos: Qt.vector3d(0, 90, 0) + camRotNeg: Qt.vector3d(0, -90, 0) + } + + AxisHelperArm { + id: armY + eulerRotation: Qt.vector3d(0, 0, 0) + color: Qt.rgba(0, 0.6, 0, 1) + hoverColor: Qt.lighter(Qt.rgba(0, 0.6, 0, 1)) + view3D: axisHelperView + camRotPos: Qt.vector3d(-90, 0, 0) + camRotNeg: Qt.vector3d(90, 0, 0) + } + + AxisHelperArm { + id: armZ + eulerRotation: Qt.vector3d(90, 0, 0) + color: Qt.rgba(0, 0, 1, 1) + hoverColor: Qt.lighter(Qt.rgba(0, 0, 1, 1)) + view3D: axisHelperView + camRotPos: Qt.vector3d(0, 0, 0) + camRotNeg: Qt.vector3d(0, 180, 0) + } + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton + + property var pickObj: null + + function cancelHover() + { + if (pickObj) { + pickObj.hovering = false; + pickObj = null; + } + } + + function pick(mouse) + { + var result = axisHelperView.pick(mouse.x, mouse.y); + if (result.objectHit) { + if (result.objectHit !== pickObj) { + cancelHover(); + pickObj = result.objectHit; + pickObj.hovering = true; + } + } else { + cancelHover(); + } + } + + onPositionChanged: (mouse)=> { + pick(mouse); + } + + onPressed: (mouse)=> { + pick(mouse); + if (pickObj) { + axisHelperView.editCameraCtrl.focusObject(axisHelperView.selectedNode, + pickObj.cameraRotation, false, false); + } else { + mouse.accepted = false; + } + } + + onExited: cancelHover() + onCanceled: cancelHover() + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AxisHelperArm.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AxisHelperArm.qml new file mode 100644 index 00000000000..cb50b0efbc4 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/AxisHelperArm.qml @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 + +Node { + id: armRoot + property alias posModel: posModel + property alias negModel: negModel + property View3D view3D + property color hoverColor + property color color + property vector3d camRotPos + property vector3d camRotNeg + + Model { + id: posModel + + property bool hovering: false + property vector3d cameraRotation: armRoot.camRotPos + + source: "../meshes/axishelper.mesh" + materials: DefaultMaterial { + id: posMat + diffuseColor: posModel.hovering ? armRoot.hoverColor : armRoot.color + lighting: DefaultMaterial.NoLighting + } + pickable: true + } + + Model { + id: negModel + + property bool hovering: false + property vector3d cameraRotation: armRoot.camRotNeg + + source: "#Sphere" + y: -6 + scale: Qt.vector3d(0.025, 0.025, 0.025) + materials: DefaultMaterial { + id: negMat + diffuseColor: negModel.hovering ? armRoot.hoverColor : armRoot.color + lighting: DefaultMaterial.NoLighting + } + pickable: true + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/CameraFrustum.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/CameraFrustum.qml new file mode 100644 index 00000000000..adf3d53ab9b --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/CameraFrustum.qml @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import CameraGeometry 1.0 + +Model { + id: cameraFrustum + + property alias geometryName: cameraGeometry.name // Name must be unique for each geometry + property alias viewPortRect: cameraGeometry.viewPortRect + property Node targetNode: null + property Node scene: null + property bool selected: false + + function updateGeometry() + { + cameraGeometry.update(); + } + + position: targetNode ? targetNode.scenePosition : Qt.vector3d(0, 0, 0) + rotation: targetNode ? targetNode.sceneRotation : Qt.quaternion(1, 0, 0, 0) + + geometry: cameraGeometry + materials: [ + DefaultMaterial { + id: defaultMaterial + diffuseColor: cameraFrustum.selected ? "#FF0000" : "#555555" + lighting: DefaultMaterial.NoLighting + cullMode: Material.NoCulling + } + ] + + CameraGeometry { + id: cameraGeometry + camera: cameraFrustum.scene && cameraFrustum.targetNode ? cameraFrustum.targetNode : null + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/CameraGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/CameraGizmo.qml new file mode 100644 index 00000000000..42a7bc7bfaa --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/CameraGizmo.qml @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 + +IconGizmo { + id: cameraGizmo + + property Model frustumModel: null + + iconSource: "qrc:///qtquickplugin/mockfiles/images/editor_camera.png" + + function connectFrustum(frustum) + { + frustumModel = frustum; + + frustum.selected = selected; + frustum.selected = Qt.binding(function() {return selected;}); + + frustum.scene = scene; + frustum.scene = Qt.binding(function() {return scene;}); + + frustum.targetNode = targetNode; + frustum.targetNode = Qt.binding(function() {return targetNode;}); + + frustum.visible = visible || (targetNode && selected && activeScene === scene); + frustum.visible = Qt.binding(function() {return visible || (targetNode && selected && activeScene === scene);}); + } + + onActiveSceneChanged: { + if (frustumModel && activeScene == scene) + frustumModel.updateGeometry(); + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/DirectionalDraggable.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/DirectionalDraggable.qml new file mode 100644 index 00000000000..5a2f0911dd8 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/DirectionalDraggable.qml @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import MouseArea3D 1.0 + +Model { + id: rootModel + + property View3D view3D + property alias color: material.diffuseColor + property Node targetNode: null + property bool dragging: mouseAreaYZ.dragging || mouseAreaXZ.dragging + property bool active: false + property MouseArea3D dragHelper: null + property alias material: material + property real length: 12 + property real offset: 0 + + readonly property bool hovering: mouseAreaYZ.hovering || mouseAreaXZ.hovering + + property vector3d _scenePosPressed + property real _posPressed + property vector3d _targetStartPos + + signal pressed(var mouseArea, point screenPos) + signal dragged(var mouseArea, vector3d sceneRelativeDistance, real relativeDistance, point screenPos) + signal released(var mouseArea, vector3d sceneRelativeDistance, real relativeDistance, point screenPos) + + DefaultMaterial { + id: material + diffuseColor: "white" + lighting: DefaultMaterial.NoLighting + } + + materials: [ material ] + + function handlePressed(mouseArea, planePos, screenPos) + { + if (!targetNode) + return; + + var maskedPosition = Qt.vector3d(planePos.x, 0, 0); + _posPressed = planePos.x; + _scenePosPressed = mouseArea.dragHelper.mapPositionToScene(maskedPosition); + _targetStartPos = mouseArea.pivotScenePosition(targetNode); + pressed(mouseArea, screenPos); + } + + function calcRelativeDistance(mouseArea, planePos) + { + var maskedPosition = Qt.vector3d(planePos.x, 0, 0); + var scenePointerPos = mouseArea.dragHelper.mapPositionToScene(maskedPosition); + return scenePointerPos.minus(_scenePosPressed); + } + + function handleDragged(mouseArea, planePos, screenPos) + { + if (!targetNode) + return; + + dragged(mouseArea, calcRelativeDistance(mouseArea, planePos), planePos.x - _posPressed, screenPos); + } + + function handleReleased(mouseArea, planePos, screenPos) + { + if (!targetNode) + return; + + released(mouseArea, calcRelativeDistance(mouseArea, planePos), planePos.x - _posPressed, screenPos); + } + + MouseArea3D { + id: mouseAreaYZ + view3D: rootModel.view3D + x: rootModel.offset + y: -1.5 + width: rootModel.length + height: 3 + eulerRotation: Qt.vector3d(0, 0, 90) + grabsMouse: targetNode + active: rootModel.active + dragHelper: rootModel.dragHelper + priority: 5 + + onPressed: (planePos, screenPos)=> { + rootModel.handlePressed(mouseAreaYZ, planePos, screenPos); + } + onDragged: (planePos, screenPos)=> { + rootModel.handleDragged(mouseAreaYZ, planePos, screenPos); + } + onReleased: (planePos, screenPos)=> { + rootModel.handleReleased(mouseAreaYZ, planePos, screenPos); + } + } + + MouseArea3D { + id: mouseAreaXZ + view3D: rootModel.view3D + x: rootModel.offset + y: -1.5 + width: rootModel.length + height: 3 + eulerRotation: Qt.vector3d(0, 90, 90) + grabsMouse: targetNode + active: rootModel.active + dragHelper: rootModel.dragHelper + priority: 5 + + onPressed: (planePos, screenPos)=> { + rootModel.handlePressed(mouseAreaXZ, planePos, screenPos); + } + onDragged: (planePos, screenPos)=> { + rootModel.handleDragged(mouseAreaXZ, planePos, screenPos); + } + onReleased: (planePos, screenPos)=> { + rootModel.handleReleased(mouseAreaXZ, planePos, screenPos); + } + } +} + diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditCameraController.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditCameraController.qml new file mode 100644 index 00000000000..223f8ab74f0 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditCameraController.qml @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 + +Item { + id: cameraCtrl + + property Camera camera: null + property View3D view3d: null + property string sceneId + property vector3d _lookAtPoint + property vector3d _pressPoint + property vector3d _prevPoint + property vector3d _startRotation + property vector3d _startPosition + property vector3d _startLookAtPoint + property matrix4x4 _startTransform + property bool _dragging + property int _button + property real _zoomFactor: 1 + property Camera _prevCamera: null + readonly property vector3d _defaultCameraPosition: Qt.vector3d(0, 600, 600) + readonly property vector3d _defaultCameraRotation: Qt.vector3d(-45, 0, 0) + readonly property real _defaultCameraLookAtDistance: _defaultCameraPosition.length() + property bool ignoreToolState: false + + function restoreCameraState(cameraState) + { + if (!camera || ignoreToolState) + return; + + _lookAtPoint = cameraState[0]; + _zoomFactor = cameraState[1]; + camera.position = cameraState[2]; + camera.rotation = cameraState[3]; + _generalHelper.zoomCamera(camera, 0, _defaultCameraLookAtDistance, _lookAtPoint, + _zoomFactor, false); + } + + function restoreDefaultState() + { + if (!camera) + return; + + _lookAtPoint = Qt.vector3d(0, 0, 0); + _zoomFactor = 1; + camera.position = _defaultCameraPosition; + camera.eulerRotation = _defaultCameraRotation; + _generalHelper.zoomCamera(camera, 0, _defaultCameraLookAtDistance, _lookAtPoint, + _zoomFactor, false); + } + + function storeCameraState(delay) + { + if (!camera || ignoreToolState) + return; + + var cameraState = []; + cameraState[0] = _lookAtPoint; + cameraState[1] = _zoomFactor; + cameraState[2] = camera.position; + cameraState[3] = camera.rotation; + _generalHelper.storeToolState(sceneId, "editCamState", cameraState, delay); + } + + + function focusObject(targetObject, rotation, updateZoom, closeUp) + { + if (!camera) + return; + + camera.eulerRotation = rotation; + var newLookAtAndZoom = _generalHelper.focusObjectToCamera( + camera, _defaultCameraLookAtDistance, targetObject, view3d, _zoomFactor, + updateZoom, closeUp); + _lookAtPoint = newLookAtAndZoom.toVector3d(); + _zoomFactor = newLookAtAndZoom.w; + storeCameraState(0); + } + + function zoomRelative(distance) + { + if (!camera) + return; + + _zoomFactor = _generalHelper.zoomCamera(camera, distance, _defaultCameraLookAtDistance, + _lookAtPoint, _zoomFactor, true); + } + + onCameraChanged: { + if (camera && _prevCamera) { + // Reset zoom on previous camera to ensure it's properties are good to copy to new cam + _generalHelper.zoomCamera(_prevCamera, 0, _defaultCameraLookAtDistance, _lookAtPoint, + 1, false); + + camera.position = _prevCamera.position; + camera.rotation = _prevCamera.rotation; + + // Apply correct zoom to new camera + _generalHelper.zoomCamera(camera, 0, _defaultCameraLookAtDistance, _lookAtPoint, + _zoomFactor, false); + } + _prevCamera = camera; + } + + MouseArea { + id: mouseHandler + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + hoverEnabled: false + anchors.fill: parent + onPositionChanged: (mouse)=> { + if (cameraCtrl.camera && mouse.modifiers === Qt.AltModifier && cameraCtrl._dragging) { + var currentPoint = Qt.vector3d(mouse.x, mouse.y, 0); + if (cameraCtrl._button == Qt.LeftButton) { + _generalHelper.orbitCamera(cameraCtrl.camera, cameraCtrl._startRotation, + cameraCtrl._lookAtPoint, cameraCtrl._pressPoint, + currentPoint); + } else if (cameraCtrl._button == Qt.MiddleButton) { + cameraCtrl._lookAtPoint = _generalHelper.panCamera( + cameraCtrl.camera, cameraCtrl._startTransform, + cameraCtrl._startPosition, cameraCtrl._startLookAtPoint, + cameraCtrl._pressPoint, currentPoint, _zoomFactor); + } else if (cameraCtrl._button == Qt.RightButton) { + cameraCtrl.zoomRelative(currentPoint.y - cameraCtrl._prevPoint.y) + cameraCtrl._prevPoint = currentPoint; + } + } + } + onPressed: (mouse)=> { + if (cameraCtrl.camera && mouse.modifiers === Qt.AltModifier) { + cameraCtrl._dragging = true; + cameraCtrl._startRotation = cameraCtrl.camera.eulerRotation; + cameraCtrl._startPosition = cameraCtrl.camera.position; + cameraCtrl._startLookAtPoint = _lookAtPoint; + cameraCtrl._pressPoint = Qt.vector3d(mouse.x, mouse.y, 0); + cameraCtrl._prevPoint = cameraCtrl._pressPoint; + cameraCtrl._button = mouse.button; + cameraCtrl._startTransform = cameraCtrl.camera.sceneTransform; + } else { + mouse.accepted = false; + } + } + + function handleRelease() { + cameraCtrl._dragging = false; + cameraCtrl.storeCameraState(0); + } + + onReleased: handleRelease() + onCanceled: handleRelease() + + onWheel: (wheel)=> { + if (cameraCtrl.camera) { + // Empirically determined divisor for nice zoom + cameraCtrl.zoomRelative(wheel.angleDelta.y / -40); + cameraCtrl.storeCameraState(500); + } + } + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml new file mode 100644 index 00000000000..18f444baa50 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml @@ -0,0 +1,884 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import MouseArea3D 1.0 + +Item { + id: viewRoot + width: 1024 + height: 768 + visible: true + + property Node activeScene: null + property View3D editView: null + property string sceneId + + property bool showEditLight: false + property bool showGrid: true + property bool usePerspective: true + property bool globalOrientation: false + property alias contentItem: contentItem + + enum SelectionMode { Item, Group } + enum TransformMode { Move, Rotate, Scale } + + property int selectionMode: EditView3D.SelectionMode.Item + property int transformMode: EditView3D.TransformMode.Move + + property Node selectedNode: null // This is multiSelectionNode in multi-selection case + property var selectedNodes: [] // All selected nodes + + property var lightIconGizmos: [] + property var cameraGizmos: [] + property var selectionBoxes: [] + property rect viewPortRect: Qt.rect(0, 0, 1000, 1000) + + property bool shuttingDown: false + + property real fps: 0 + + signal selectionChanged(var selectedNodes) + signal commitObjectProperty(var objects, var propNames) + signal changeObjectProperty(var objects, var propNames) + signal notifyActiveSceneChange() + + onUsePerspectiveChanged: _generalHelper.storeToolState(sceneId, "usePerspective", usePerspective) + onShowEditLightChanged: _generalHelper.storeToolState(sceneId, "showEditLight", showEditLight) + onGlobalOrientationChanged: _generalHelper.storeToolState(sceneId, "globalOrientation", globalOrientation) + onShowGridChanged: _generalHelper.storeToolState(sceneId, "showGrid", showGrid); + onSelectionModeChanged: _generalHelper.storeToolState(sceneId, "selectionMode", selectionMode); + onTransformModeChanged: _generalHelper.storeToolState(sceneId, "transformMode", transformMode); + + onActiveSceneChanged: updateActiveScene() + + function aboutToShutDown() + { + shuttingDown = true; + } + + function createEditView() + { + var component = Qt.createComponent("SceneView3D.qml"); + if (component.status === Component.Ready) { + editView = component.createObject(viewRect, + {"usePerspective": usePerspective, + "showSceneLight": showEditLight, + "showGrid": showGrid, + "importScene": activeScene, + "cameraZoomFactor": cameraControl._zoomFactor, + "z": 1}); + editView.usePerspective = Qt.binding(function() {return usePerspective;}); + editView.showSceneLight = Qt.binding(function() {return showEditLight;}); + editView.showGrid = Qt.binding(function() {return showGrid;}); + editView.cameraZoomFactor = Qt.binding(function() {return cameraControl._zoomFactor;}); + + selectionBoxes.length = 0; + return true; + } + return false; + } + + function updateActiveScene() + { + if (editView) { + // Destroy is async, so make sure we don't get any more updates for the old editView + _generalHelper.enableItemUpdate(editView, false); + editView.visible = false; + editView.destroy(); + } + + // importScene cannot be updated after initial set, so we need to reconstruct entire View3D + if (createEditView()) { + if (activeScene) { + var toolStates = _generalHelper.getToolStates(sceneId); + if (Object.keys(toolStates).length > 0) { + updateToolStates(toolStates, true); + } else { + // Don't inherit the edit light state from the previous scene, but rather + // turn the edit light on for scenes that do not have any scene + // lights, and turn it off for scenes that have. + var hasSceneLight = false; + for (var i = 0; i < lightIconGizmos.length; ++i) { + if (lightIconGizmos[i].scene === activeScene) { + hasSceneLight = true; + break; + } + } + showEditLight = !hasSceneLight; + storeCurrentToolStates(); + } + } else { + // When active scene is deleted, this function gets called by object deletion + // handlers without going through setActiveScene, so make sure sceneId is cleared. + // This is skipped during application shutdown, as calling QQuickText::setText() + // during application shutdown can crash the application. + if (!shuttingDown) { + sceneId = ""; + storeCurrentToolStates(); + } + } + + notifyActiveSceneChange(); + } + } + + function setActiveScene(newScene, newSceneId) + { + var needExplicitUpdate = !activeScene && !newScene; + + sceneId = newSceneId; + activeScene = newScene; + + if (needExplicitUpdate) + updateActiveScene(); + } + + // Disables edit view update if scene doesn't match current activeScene. + // If it matches, updates are enabled. + function enableEditViewUpdate(scene) + { + if (editView) + _generalHelper.enableItemUpdate(editView, (scene && scene === activeScene)); + } + + function handleActiveSceneIdChange(newId) + { + if (sceneId !== newId) { + sceneId = newId; + storeCurrentToolStates(); + } + } + + function fitToView() + { + if (editView) { + var targetNode = selectionBoxes.length > 0 + ? selectionBoxes[0].model : null; + cameraControl.focusObject(targetNode, editView.camera.eulerRotation, true, false); + } + } + + // If resetToDefault is true, tool states not specifically set to anything will be reset to + // their default state. + function updateToolStates(toolStates, resetToDefault) + { + if ("showEditLight" in toolStates) + showEditLight = toolStates.showEditLight; + else if (resetToDefault) + showEditLight = false; + + if ("showGrid" in toolStates) + showGrid = toolStates.showGrid; + else if (resetToDefault) + showGrid = true; + + if ("usePerspective" in toolStates) + usePerspective = toolStates.usePerspective; + else if (resetToDefault) + usePerspective = true; + + if ("globalOrientation" in toolStates) + globalOrientation = toolStates.globalOrientation; + else if (resetToDefault) + globalOrientation = false; + + if ("selectionMode" in toolStates) + selectionMode = toolStates.selectionMode; + else if (resetToDefault) + selectionMode = EditView3D.SelectionMode.Item; + + if ("transformMode" in toolStates) + transformMode = toolStates.transformMode; + else if (resetToDefault) + transformMode = EditView3D.TransformMode.Move; + + if ("editCamState" in toolStates) + cameraControl.restoreCameraState(toolStates.editCamState); + else if (resetToDefault) + cameraControl.restoreDefaultState(); + } + + function storeCurrentToolStates() + { + _generalHelper.storeToolState(sceneId, "showEditLight", showEditLight) + _generalHelper.storeToolState(sceneId, "showGrid", showGrid) + _generalHelper.storeToolState(sceneId, "usePerspective", usePerspective) + _generalHelper.storeToolState(sceneId, "globalOrientation", globalOrientation) + _generalHelper.storeToolState(sceneId, "selectionMode", selectionMode); + _generalHelper.storeToolState(sceneId, "transformMode", transformMode); + + cameraControl.storeCameraState(0); + } + + function ensureSelectionBoxes(count) + { + var needMore = count - selectionBoxes.length + if (needMore > 0) { + var component = Qt.createComponent("SelectionBox.qml"); + if (component.status === Component.Ready) { + for (var i = 0; i < needMore; ++i) { + var geometryName = _generalHelper.generateUniqueName("SelectionBoxGeometry"); + var boxParent = null; + if (editView) + boxParent = editView.sceneHelpers; + var box = component.createObject(boxParent, {"view3D": editView, + "geometryName": geometryName}); + selectionBoxes[selectionBoxes.length] = box; + box.view3D = Qt.binding(function() {return editView;}); + } + } + } + } + + function selectObjects(objects) + { + // Create selection boxes as necessary. One more box than is actually needed is created, so + // that we always have a previously created box to use for new selection. + // This fixes an occasional visual glitch when creating a new box. + ensureSelectionBoxes(objects.length + 1) + + var i; + for (i = 0; i < objects.length; ++i) + selectionBoxes[i].targetNode = objects[i]; + for (i = objects.length; i < selectionBoxes.length; ++i) + selectionBoxes[i].targetNode = null; + + selectedNodes = objects; + if (objects.length === 0) { + selectedNode = null; + } else if (objects.length > 1) { + selectedNode = multiSelectionNode; + _generalHelper.setMultiSelectionTargets(multiSelectionNode, objects); + } else { + selectedNode = objects[0]; + } + } + + function handleObjectClicked(object, multi) + { + var clickedObject; + + // Click on locked object is treated same as click on empty space + if (!_generalHelper.isLocked(object)) + clickedObject = object; + + if (selectionMode === EditView3D.SelectionMode.Group) { + while (clickedObject && clickedObject !== activeScene + && (activeScene instanceof Model || clickedObject.parent !== activeScene)) { + clickedObject = clickedObject.parent; + } + } + // Object selection logic: + // Regular click: Clear any multiselection, single-selects the clicked object + // Ctrl-click: No objects selected: Act as single select + // One or more objects selected: Multiselect + // Null object always clears entire selection + var newSelection = []; + if (clickedObject) { + if (multi && selectedNodes.length > 0) { + var deselect = false; + for (var i = 0; i < selectedNodes.length; ++i) { + // Multiselecting already selected object clears that object from selection + if (selectedNodes[i] !== clickedObject) + newSelection[newSelection.length] = selectedNodes[i]; + else + deselect = true; + } + if (!deselect) + newSelection[newSelection.length] = clickedObject; + } else { + newSelection[0] = clickedObject; + } + } + selectObjects(newSelection); + selectionChanged(newSelection); + } + + function addLightGizmo(scene, obj) + { + // Insert into first available gizmo if we don't already have gizmo for this object + var slotFound = -1; + for (var i = 0; i < lightIconGizmos.length; ++i) { + if (!lightIconGizmos[i].targetNode) { + slotFound = i; + } else if (lightIconGizmos[i].targetNode === obj) { + lightIconGizmos[i].scene = scene; + return; + } + } + + if (slotFound !== -1) { + lightIconGizmos[slotFound].scene = scene; + lightIconGizmos[slotFound].targetNode = obj; + lightIconGizmos[slotFound].locked = _generalHelper.isLocked(obj); + lightIconGizmos[slotFound].hidden = _generalHelper.isHidden(obj); + _generalHelper.registerGizmoTarget(obj); + return; + } + + // No free gizmos available, create a new one + var gizmoComponent = Qt.createComponent("LightIconGizmo.qml"); + if (gizmoComponent.status === Component.Ready) { + _generalHelper.registerGizmoTarget(obj); + var gizmo = gizmoComponent.createObject(overlayView, + {"view3D": overlayView, "targetNode": obj, + "selectedNodes": selectedNodes, "scene": scene, + "activeScene": activeScene, + "locked": _generalHelper.isLocked(obj), + "hidden": _generalHelper.isHidden(obj)}); + lightIconGizmos[lightIconGizmos.length] = gizmo; + gizmo.clicked.connect(handleObjectClicked); + gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;}); + gizmo.activeScene = Qt.binding(function() {return activeScene;}); + } + } + + function addCameraGizmo(scene, obj) + { + // Insert into first available gizmo if we don't already have gizmo for this object + var slotFound = -1; + for (var i = 0; i < cameraGizmos.length; ++i) { + if (!cameraGizmos[i].targetNode) { + slotFound = i; + } else if (cameraGizmos[i].targetNode === obj) { + cameraGizmos[i].scene = scene; + return; + } + } + + if (slotFound !== -1) { + cameraGizmos[slotFound].scene = scene; + cameraGizmos[slotFound].targetNode = obj; + cameraGizmos[slotFound].locked = _generalHelper.isLocked(obj); + cameraGizmos[slotFound].hidden = _generalHelper.isHidden(obj); + _generalHelper.registerGizmoTarget(obj); + return; + } + + // No free gizmos available, create a new one + var gizmoComponent = Qt.createComponent("CameraGizmo.qml"); + var frustumComponent = Qt.createComponent("CameraFrustum.qml"); + if (gizmoComponent.status === Component.Ready && frustumComponent.status === Component.Ready) { + _generalHelper.registerGizmoTarget(obj); + var geometryName = _generalHelper.generateUniqueName("CameraGeometry"); + var frustum = frustumComponent.createObject( + overlayScene, + {"geometryName": geometryName, "viewPortRect": viewPortRect}); + var gizmo = gizmoComponent.createObject( + overlayView, + {"view3D": overlayView, "targetNode": obj, + "selectedNodes": selectedNodes, "scene": scene, "activeScene": activeScene, + "locked": _generalHelper.isLocked(obj), "hidden": _generalHelper.isHidden(obj)}); + + cameraGizmos[cameraGizmos.length] = gizmo; + gizmo.clicked.connect(handleObjectClicked); + gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;}); + gizmo.activeScene = Qt.binding(function() {return activeScene;}); + frustum.viewPortRect = Qt.binding(function() {return viewPortRect;}); + gizmo.connectFrustum(frustum); + } + } + + function releaseLightGizmo(obj) + { + for (var i = 0; i < lightIconGizmos.length; ++i) { + if (lightIconGizmos[i].targetNode === obj) { + lightIconGizmos[i].scene = null; + lightIconGizmos[i].targetNode = null; + _generalHelper.unregisterGizmoTarget(obj); + return; + } + } + } + + function releaseCameraGizmo(obj) + { + for (var i = 0; i < cameraGizmos.length; ++i) { + if (cameraGizmos[i].targetNode === obj) { + cameraGizmos[i].scene = null; + cameraGizmos[i].targetNode = null; + _generalHelper.unregisterGizmoTarget(obj); + return; + } + } + } + + function updateLightGizmoScene(scene, obj) + { + for (var i = 0; i < lightIconGizmos.length; ++i) { + if (lightIconGizmos[i].targetNode === obj) { + lightIconGizmos[i].scene = scene; + return; + } + } + } + + function updateCameraGizmoScene(scene, obj) + { + for (var i = 0; i < cameraGizmos.length; ++i) { + if (cameraGizmos[i].targetNode === obj) { + cameraGizmos[i].scene = scene; + return; + } + } + } + + Component.onCompleted: { + createEditView(); + selectObjects([]); + // Work-around the fact that the projection matrix for the camera is not calculated until + // the first frame is rendered, so any initial calls to mapFrom3DScene() will fail. + _generalHelper.requestOverlayUpdate(); + } + + onWidthChanged: _generalHelper.requestOverlayUpdate() + onHeightChanged: _generalHelper.requestOverlayUpdate() + + Connections { + target: _generalHelper + function onLockedStateChanged(node) + { + for (var i = 0; i < cameraGizmos.length; ++i) { + if (cameraGizmos[i].targetNode === node) { + cameraGizmos[i].locked = _generalHelper.isLocked(node); + return; + } + } + for (var i = 0; i < lightIconGizmos.length; ++i) { + if (lightIconGizmos[i].targetNode === node) { + lightIconGizmos[i].locked = _generalHelper.isLocked(node); + return; + } + } + } + function onHiddenStateChanged(node) + { + for (var i = 0; i < cameraGizmos.length; ++i) { + if (cameraGizmos[i].targetNode === node) { + cameraGizmos[i].hidden = _generalHelper.isHidden(node); + return; + } + } + for (var i = 0; i < lightIconGizmos.length; ++i) { + if (lightIconGizmos[i].targetNode === node) { + lightIconGizmos[i].hidden = _generalHelper.isHidden(node); + return; + } + } + } + } + + Node { + id: overlayScene + + PerspectiveCamera { + id: overlayPerspectiveCamera + clipFar: viewRoot.editView ? viewRoot.editView.perspectiveCamera.clipFar : 1000 + clipNear: viewRoot.editView ? viewRoot.editView.perspectiveCamera.clipNear : 1 + position: viewRoot.editView ? viewRoot.editView.perspectiveCamera.position : Qt.vector3d(0, 0, 0) + rotation: viewRoot.editView ? viewRoot.editView.perspectiveCamera.rotation : Qt.quaternion(1, 0, 0, 0) + } + + OrthographicCamera { + id: overlayOrthoCamera + clipFar: viewRoot.editView ? viewRoot.editView.orthoCamera.clipFar : 1000 + clipNear: viewRoot.editView ? viewRoot.editView.orthoCamera.clipNear : 1 + position: viewRoot.editView ? viewRoot.editView.orthoCamera.position : Qt.vector3d(0, 0, 0) + rotation: viewRoot.editView ? viewRoot.editView.orthoCamera.rotation : Qt.quaternion(1, 0, 0, 0) + scale: viewRoot.editView ? viewRoot.editView.orthoCamera.scale : Qt.vector3d(0, 0, 0) + } + + MouseArea3D { + id: gizmoDragHelper + view3D: overlayView + } + + Node { + id: multiSelectionNode + objectName: "multiSelectionNode" + } + + MoveGizmo { + id: moveGizmo + scale: autoScale.getScale(Qt.vector3d(5, 5, 5)) + highlightOnHover: true + targetNode: viewRoot.selectedNode + globalOrientation: viewRoot.globalOrientation + visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Move + view3D: overlayView + dragHelper: gizmoDragHelper + property var propertyNames: ["position"] + + onPositionCommit: { + if (targetNode == multiSelectionNode) + viewRoot.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNames); + else + viewRoot.commitObjectProperty([viewRoot.selectedNode], propertyNames); + } + onPositionMove: { + if (targetNode == multiSelectionNode) + viewRoot.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNames); + else + viewRoot.changeObjectProperty([viewRoot.selectedNode], propertyNames); + } + } + + ScaleGizmo { + id: scaleGizmo + scale: autoScale.getScale(Qt.vector3d(5, 5, 5)) + highlightOnHover: true + targetNode: viewRoot.selectedNode + visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Scale + view3D: overlayView + dragHelper: gizmoDragHelper + property var propertyNames: ["scale"] + property var propertyNamesMulti: ["position", "scale"] + + onScaleCommit: { + if (targetNode == multiSelectionNode) + viewRoot.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti); + else + viewRoot.commitObjectProperty([viewRoot.selectedNode], propertyNames); + } + onScaleChange: { + if (targetNode == multiSelectionNode) + viewRoot.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti); + else + viewRoot.changeObjectProperty([viewRoot.selectedNode], propertyNames); + } + } + + RotateGizmo { + id: rotateGizmo + scale: autoScale.getScale(Qt.vector3d(7, 7, 7)) + highlightOnHover: true + targetNode: viewRoot.selectedNode + globalOrientation: viewRoot.globalOrientation + visible: viewRoot.selectedNode && transformMode === EditView3D.TransformMode.Rotate + view3D: overlayView + dragHelper: gizmoDragHelper + property var propertyNames: ["eulerRotation"] + property var propertyNamesMulti: ["position", "eulerRotation"] + + onRotateCommit: { + if (targetNode == multiSelectionNode) + viewRoot.commitObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti); + else + viewRoot.commitObjectProperty([viewRoot.selectedNode], propertyNames); + } + onRotateChange: { + if (targetNode == multiSelectionNode) + viewRoot.changeObjectProperty(_generalHelper.multiSelectionTargets(), propertyNamesMulti); + else + viewRoot.changeObjectProperty([viewRoot.selectedNode], propertyNames); + } + } + + LightGizmo { + id: lightGizmo + targetNode: viewRoot.selectedNode != multiSelectionNode ? viewRoot.selectedNode : null + view3D: overlayView + dragHelper: gizmoDragHelper + + onPropertyValueCommit: (propName)=> { + viewRoot.commitObjectProperty([targetNode], [propName]); + } + onPropertyValueChange: (propName)=> { + viewRoot.changeObjectProperty([targetNode], [propName]); + } + } + + AutoScaleHelper { + id: autoScale + view3D: overlayView + position: moveGizmo.scenePosition + } + + AutoScaleHelper { + id: pivotAutoScale + view3D: overlayView + position: pivotLine.startPos + } + + Line3D { + id: pivotLine + visible: viewRoot.selectedNode && viewRoot.selectedNode != multiSelectionNode + name: "3D Edit View Pivot Line" + color: "#ddd600" + + startPos: viewRoot.selectedNode ? viewRoot.selectedNode.scenePosition + : Qt.vector3d(0, 0, 0) + Connections { + target: viewRoot + function onSelectedNodeChanged() + { + pivotLine.endPos = gizmoDragHelper.pivotScenePosition(viewRoot.selectedNode); + } + } + Connections { + target: viewRoot.selectedNode + function onSceneTransformChanged() + { + pivotLine.endPos = gizmoDragHelper.pivotScenePosition(viewRoot.selectedNode); + } + } + + Model { + id: pivotCap + source: "#Sphere" + scale: pivotAutoScale.getScale(Qt.vector3d(0.03, 0.03, 0.03)) + position: pivotLine.startPos + materials: [ + DefaultMaterial { + id: lineMat + lighting: DefaultMaterial.NoLighting + cullMode: Material.NoCulling + diffuseColor: pivotLine.color + } + ] + } + } + } + + Item { + id: contentItem + anchors.fill: parent + + Rectangle { + id: viewRect + anchors.fill: parent + + gradient: Gradient { + GradientStop { position: 1.0; color: "#222222" } + GradientStop { position: 0.0; color: "#999999" } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton + hoverEnabled: false + + property MouseArea3D freeDraggerArea + property point pressPoint + property bool initialMoveBlock: false + + onPressed: (mouse)=> { + if (viewRoot.editView) { + var pickResult = viewRoot.editView.pick(mouse.x, mouse.y); + handleObjectClicked(_generalHelper.resolvePick(pickResult.objectHit), + mouse.modifiers & Qt.ControlModifier); + + if (pickResult.objectHit) { + if (transformMode === EditView3D.TransformMode.Move) + freeDraggerArea = moveGizmo.freeDraggerArea; + else if (transformMode === EditView3D.TransformMode.Rotate) + freeDraggerArea = rotateGizmo.freeDraggerArea; + else if (transformMode === EditView3D.TransformMode.Scale) + freeDraggerArea = scaleGizmo.freeDraggerArea; + pressPoint.x = mouse.x; + pressPoint.y = mouse.y; + initialMoveBlock = true; + } else { + mouse.accepted = false; + } + } + } + onPositionChanged: (mouse)=> { + if (freeDraggerArea) { + if (initialMoveBlock && Math.abs(pressPoint.x - mouse.x) + Math.abs(pressPoint.y - mouse.y) > 10) { + // Don't force press event at actual press, as that puts the gizmo + // in free-dragging state, which is bad UX if drag is not actually done + freeDraggerArea.forcePressEvent(pressPoint.x, pressPoint.y); + freeDraggerArea.forceMoveEvent(mouse.x, mouse.y); + initialMoveBlock = false; + } else { + freeDraggerArea.forceMoveEvent(mouse.x, mouse.y); + } + } + } + + function handleRelease(mouse) + { + if (freeDraggerArea) { + if (initialMoveBlock) + freeDraggerArea.forceReleaseEvent(pressPoint.x, pressPoint.y); + else + freeDraggerArea.forceReleaseEvent(mouse.x, mouse.y); + freeDraggerArea = null; + } + } + + onReleased: (mouse)=> { + handleRelease(mouse); + } + onCanceled: (mouse)=> { + handleRelease(mouse); + } + } + + DropArea { + anchors.fill: parent + } + + View3D { + id: overlayView + anchors.fill: parent + camera: viewRoot.usePerspective ? overlayPerspectiveCamera : overlayOrthoCamera + importScene: overlayScene + z: 2 + } + + Overlay2D { + id: gizmoLabel + targetNode: moveGizmo.visible ? moveGizmo : scaleGizmo + targetView: overlayView + visible: targetNode.dragging + z: 3 + + Rectangle { + color: "white" + x: -width / 2 + y: -height - 8 + width: gizmoLabelText.width + 4 + height: gizmoLabelText.height + 4 + border.width: 1 + Text { + id: gizmoLabelText + text: { + // This is skipped during application shutdown, as calling QQuickText::setText() + // during application shutdown can crash the application. + if (shuttingDown) + return text; + var l = Qt.locale(); + var targetProperty; + if (viewRoot.selectedNode) { + if (gizmoLabel.targetNode === moveGizmo) + targetProperty = viewRoot.selectedNode.position; + else + targetProperty = viewRoot.selectedNode.scale; + return qsTr("x:") + Number(targetProperty.x).toLocaleString(l, 'f', 1) + + qsTr(" y:") + Number(targetProperty.y).toLocaleString(l, 'f', 1) + + qsTr(" z:") + Number(targetProperty.z).toLocaleString(l, 'f', 1); + } else { + return ""; + } + } + anchors.centerIn: parent + } + } + } + + Rectangle { + id: rotateGizmoLabel + color: "white" + x: rotateGizmo.currentMousePos.x - (10 + width) + y: rotateGizmo.currentMousePos.y - (10 + height) + width: rotateGizmoLabelText.width + 4 + height: rotateGizmoLabelText.height + 4 + border.width: 1 + visible: rotateGizmo.dragging + parent: rotateGizmo.view3D + z: 3 + + Text { + id: rotateGizmoLabelText + text: { + // This is skipped during application shutdown, as calling QQuickText::setText() + // during application shutdown can crash the application. + if (shuttingDown) + return text; + var l = Qt.locale(); + if (rotateGizmo.targetNode) { + var degrees = rotateGizmo.currentAngle * (180 / Math.PI); + return Number(degrees).toLocaleString(l, 'f', 1); + } else { + return ""; + } + } + anchors.centerIn: parent + } + } + + Rectangle { + id: lightGizmoLabel + color: "white" + x: lightGizmo.currentMousePos.x - (10 + width) + y: lightGizmo.currentMousePos.y - (10 + height) + width: lightGizmoLabelText.width + 4 + height: lightGizmoLabelText.height + 4 + border.width: 1 + visible: lightGizmo.dragging + parent: lightGizmo.view3D + z: 3 + + Text { + id: lightGizmoLabelText + text: lightGizmo.currentLabel + anchors.centerIn: parent + } + } + + EditCameraController { + id: cameraControl + camera: viewRoot.editView ? viewRoot.editView.camera : null + anchors.fill: parent + view3d: viewRoot.editView + sceneId: viewRoot.sceneId + } + } + + AxisHelper { + anchors.right: parent.right + anchors.top: parent.top + width: 100 + height: width + editCameraCtrl: cameraControl + selectedNode : viewRoot.selectedNodes.length ? selectionBoxes[0].model : null + } + + Text { + id: sceneLabel + text: viewRoot.sceneId + anchors.top: parent.top + anchors.left: parent.left + anchors.margins: 4 + font.pixelSize: 14 + color: "white" + } + + Text { + id: fpsLabel + text: viewRoot.fps + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.margins: 4 + font.pixelSize: 12 + color: "white" + visible: viewRoot.fps > 0 + } + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/FadeHandle.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/FadeHandle.qml new file mode 100644 index 00000000000..c50406c147f --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/FadeHandle.qml @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 + +DirectionalDraggable { + id: handleRoot + + property string currentLabel + property point currentMousePos + property real fadeScale + property real baseScale: 5 + property real dragScale: 1 + + scale: autoScaler.getScale(Qt.vector3d(baseScale, baseScale, baseScale)) + length: 3 + offset: -1.5 + + Model { + id: handle + source: "#Sphere" + materials: [ handleRoot.material ] + scale: Qt.vector3d(0.02, 0.02, 0.02) + } + + AutoScaleHelper { + id: autoScaler + active: handleRoot.active + view3D: handleRoot.view3D + } + + property real _q // quadratic fade + property real _l // linear fade + property real _c // constant fade + property real _d: 20 // Divisor from fadeScale calc in lightGizmo + property real _startScale + property real _startFadeScale + property string _currentProp + + signal valueCommit(string propName) + signal valueChange(string propName) + + function updateFade(relativeDistance, screenPos) + { + // Solved from fadeScale equation in LightGizmo + var newValue = 0; + var _x = Math.max(0, (_startFadeScale - (relativeDistance * _startScale)) / 100); + + // Fades capped to range 0-10 because property editor caps them to that range + if (_currentProp === "quadraticFade") { + if (_x === 0) + newValue = 10; + else + newValue = Math.max(0, Math.min(10, -(_c - _d + (_l * _x)) / (_x * _x))); + if (newValue < 0.01) + newValue = 0; // To avoid having tiny positive value when UI shows 0.00 + targetNode.quadraticFade = newValue; + } else if (_currentProp === "linearFade") { + if (_x === 0) + newValue = 10; + else + newValue = Math.max(0, Math.min(10, -(_c - _d) / _x)); + if (newValue < 0.01) + newValue = 0; // To avoid having tiny positive value when UI shows 0.00 + targetNode.linearFade = newValue; + } else { + // Since pure constant fade equates to infinitely long cone, fadeScale calc assumes + // linear fade of one in this case. + newValue = Math.max(0, Math.min(10, _d - _x)); + targetNode.constantFade = newValue; + } + + var l = Qt.locale(); + handleRoot.currentLabel = _currentProp + qsTr(": ") + Number(newValue).toLocaleString(l, 'f', 2); + handleRoot.currentMousePos = screenPos; + } + + onPressed: (mouseArea, screenPos)=> { + _startScale = autoScaler.relativeScale * baseScale * dragScale; + _startFadeScale = fadeScale; + _l = targetNode.linearFade; + _c = targetNode.constantFade; + _q = targetNode.quadraticFade; + if (targetNode.quadraticFade === 0) { + if (targetNode.linearFade === 0) { + _currentProp = "constantFade"; + } else { + _currentProp = "linearFade"; + } + } else { + _currentProp = "quadraticFade"; + } + updateFade(0, screenPos); + } + + onDragged: (mouseArea, sceneRelativeDistance, relativeDistance, screenPos)=> { + updateFade(relativeDistance, screenPos); + handleRoot.valueChange(_currentProp); + } + + onReleased: (mouseArea, sceneRelativeDistance, relativeDistance, screenPos)=> { + updateFade(relativeDistance, screenPos); + handleRoot.valueCommit(_currentProp); + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/HelperGrid.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/HelperGrid.qml new file mode 100644 index 00000000000..b7807d8192f --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/HelperGrid.qml @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import GridGeometry 1.0 + +Node { + id: grid + + property alias lines: gridGeometry.lines + property alias step: gridGeometry.step + property alias subdivAlpha: subGridMaterial.opacity + + eulerRotation.x: 90 + + // Note: Only one instance of HelperGrid is supported, as the geometry names are fixed + + Model { // Main grid lines + geometry: GridGeometry { + id: gridGeometry + name: "3D Edit View Helper Grid" + } + + materials: [ + DefaultMaterial { + id: mainGridMaterial + diffuseColor: "#aaaaaa" + lighting: DefaultMaterial.NoLighting + cullMode: Material.NoCulling + } + ] + } + + Model { // Subdivision lines + geometry: GridGeometry { + lines: gridGeometry.lines + step: gridGeometry.step + isSubdivision: true + name: "3D Edit View Helper Grid subdivisions" + } + + materials: [ + DefaultMaterial { + id: subGridMaterial + diffuseColor: mainGridMaterial.diffuseColor + lighting: DefaultMaterial.NoLighting + cullMode: Material.NoCulling + } + ] + } + + Model { // Z Axis + geometry: GridGeometry { + lines: gridGeometry.lines + step: gridGeometry.step + isCenterLine: true + name: "3D Edit View Helper Grid Z Axis" + } + materials: [ + DefaultMaterial { + id: vCenterLineMaterial + diffuseColor: "#00a1d2" + lighting: DefaultMaterial.NoLighting + cullMode: Material.NoCulling + } + ] + } + Model { // X Axis + eulerRotation.z: 90 + geometry: GridGeometry { + lines: gridGeometry.lines + step: gridGeometry.step + isCenterLine: true + name: "3D Edit View Helper Grid X Axis" + } + materials: [ + DefaultMaterial { + id: hCenterLineMaterial + diffuseColor: "#cb211a" + lighting: DefaultMaterial.NoLighting + cullMode: Material.NoCulling + } + ] + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/IconGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/IconGizmo.qml new file mode 100644 index 00000000000..03087b658a9 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/IconGizmo.qml @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 + +Item { + id: iconGizmo + + property Node activeScene: null + property Node scene: null + property View3D view3D + property bool highlightOnHover: true + property Node targetNode: null + property var selectedNodes: [] + readonly property bool selected: { + for (var i = 0; i < selectedNodes.length; ++i) { + if (selectedNodes[i] === targetNode) + return true; + } + return false; + } + property bool hasMouse: false + property bool hidden: false + property bool locked: false + + property alias iconSource: iconImage.source + + signal clicked(Node node, bool multi) + + onSelectedChanged: { + if (selected) + hasMouse = false; + } + + visible: activeScene === scene && !hidden && (targetNode ? targetNode.visible : false) + + Overlay2D { + id: iconOverlay + targetNode: iconGizmo.targetNode + targetView: view3D + visible: iconGizmo.visible && !isBehindCamera + + Rectangle { + id: iconRect + + width: iconImage.width + height: iconImage.height + x: -width / 2 + y: -height / 2 + color: "transparent" + border.color: "#7777ff" + border.width: !iconGizmo.locked && iconGizmo.highlightOnHover && iconGizmo.hasMouse ? 2 : 0 + radius: 5 + opacity: iconGizmo.selected ? 0.2 : 1 + Image { + id: iconImage + fillMode: Image.Pad + MouseArea { + id: iconMouseArea + anchors.fill: parent + onPressed: (mouse)=> { + // Ignore singleselection mouse presses when we have single object selected + // so that the icon gizmo doesn't hijack mouse clicks meant for other gizmos + if (iconGizmo.selected && !(mouse.modifiers & Qt.ControlModifier) + && selectedNodes.length === 1) { + mouse.accepted = false; + } + } + + onClicked: (mouse)=> { + iconGizmo.clicked(iconGizmo.targetNode, + mouse.modifiers & Qt.ControlModifier); + } + hoverEnabled: iconGizmo.highlightOnHover && !iconGizmo.selected + acceptedButtons: Qt.LeftButton + + // onPositionChanged, onContainsMouseAreaChanged, and hasMouse are used instead + // of just using containsMouse directly, because containsMouse + // cannot be relied upon to update correctly in some situations. + // This is likely because the overlapping 3D mouse areas of the gizmos get + // the mouse events instead of this area, so mouse leaving the area + // doesn't always update containsMouse property. + onPositionChanged: { + if (!iconGizmo.selected) + iconGizmo.hasMouse = containsMouse; + } + + onContainsMouseChanged: { + if (!iconGizmo.selected) + iconGizmo.hasMouse = containsMouse; + else + iconGizmo.hasMouse = false; + } + } + } + } + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/IconRenderer3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/IconRenderer3D.qml new file mode 100644 index 00000000000..623bf21cdec --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/IconRenderer3D.qml @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 + +Item { + id: viewRoot + width: 1024 + height: 1024 + visible: true + + property alias view3D: view3D + property alias camPos: viewCamera.position + + function setSceneToBox() + { + selectionBox.targetNode = view3D.importScene; + } + + function fitAndHideBox() + { + cameraControl.focusObject(selectionBox.model, viewCamera.eulerRotation, true, true); + if (cameraControl._zoomFactor < 0.1) + view3D.importScene.scale = view3D.importScene.scale.times(10); + if (cameraControl._zoomFactor > 10) + view3D.importScene.scale = view3D.importScene.scale.times(0.1); + + selectionBox.visible = false; + } + + View3D { + id: view3D + camera: viewCamera + environment: sceneEnv + + SceneEnvironment { + id: sceneEnv + antialiasingMode: SceneEnvironment.MSAA + antialiasingQuality: SceneEnvironment.VeryHigh + } + + PerspectiveCamera { + id: viewCamera + position: Qt.vector3d(-200, 200, 200) + eulerRotation: Qt.vector3d(-45, -45, 0) + } + + DirectionalLight { + rotation: viewCamera.rotation + } + + SelectionBox { + id: selectionBox + view3D: view3D + geometryName: "SB" + } + + EditCameraController { + id: cameraControl + camera: view3D.camera + view3d: view3D + ignoreToolState: true + } + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/LightGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/LightGizmo.qml new file mode 100644 index 00000000000..32f047cff68 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/LightGizmo.qml @@ -0,0 +1,356 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import MouseArea3D 1.0 +import LightUtils 1.0 + +Node { + id: lightGizmo + + property View3D view3D + property Node targetNode: null + property MouseArea3D dragHelper: null + property color color: Qt.rgba(1, 1, 0, 1) + property real fadeScale: { + // Value indicates area where intensity is above certain percent of total brightness. + if (lightGizmo.targetNode instanceof SpotLight || lightGizmo.targetNode instanceof PointLight) { + var l = targetNode.linearFade; + var q = targetNode.quadraticFade; + var c = targetNode.constantFade; + var d = 20; // divisor to target intensity value. E.g. 20 = 1/20 = 5% + if (l === 0 && q === 0) + l = 1; // For pure constant fade, cone would be infinite, so pretend we have linear fade + // Solved from equation in shader: + // 1 / d = 1 / (c + (l + q * dist) * dist); + if (q === 0) + return 100 * Math.max(((d - c) / l), 1); + else + return 100 * ((Math.sqrt(4 * q * (d - c) + (l * l)) - l) / (2 * q)); + } else { + return 100; + } + } + readonly property bool dragging: primaryArrow.dragging + || spotLightHandle.dragging + || spotLightInnerHandle.dragging + || spotLightFadeHandle.dragging + || areaHeightHandle.dragging + || areaWidthHandle.dragging + || pointLightFadeHandle.dragging + property point currentMousePos + property string currentLabel + property int brightnessDecimals: _generalHelper.brightnessScaler() > 10. ? 0 : 2; + property real brightnessMultiplier: Math.pow(10, brightnessDecimals); + + signal propertyValueCommit(string propName) + signal propertyValueChange(string propName) + + position: targetNode ? targetNode.scenePosition : Qt.vector3d(0, 0, 0) + visible: lightGizmo.targetNode instanceof SpotLight + || lightGizmo.targetNode instanceof AreaLight + || lightGizmo.targetNode instanceof DirectionalLight + || lightGizmo.targetNode instanceof PointLight + + AutoScaleHelper { + id: autoScaler + view3D: lightGizmo.view3D + } + + Node { + id: pointLightParts + rotation: lightGizmo.view3D.camera.rotation + visible: lightGizmo.targetNode instanceof PointLight + + LightModel { + id: pointModel + geometryName: "Edit 3D PointLight" + geometryType: LightGeometry.Point + material: lightMaterial + scale: Qt.vector3d(lightGizmo.fadeScale, lightGizmo.fadeScale, lightGizmo.fadeScale) + } + + FadeHandle { + id: pointLightFadeHandle + view3D: lightGizmo.view3D + color: (hovering || dragging) ? Qt.rgba(1, 1, 1, 1) : lightGizmo.color + position: lightGizmo.targetNode instanceof PointLight ? Qt.vector3d(-pointModel.scale.x, 0, 0) + : Qt.vector3d(0, 0, 0) + eulerRotation: Qt.vector3d(0, 0, -90) + targetNode: lightGizmo.targetNode instanceof PointLight ? lightGizmo.targetNode : null + active: lightGizmo.targetNode instanceof PointLight + dragHelper: lightGizmo.dragHelper + fadeScale: lightGizmo.fadeScale + + onCurrentMousePosChanged: { + lightGizmo.currentMousePos = currentMousePos; + lightGizmo.currentLabel = currentLabel; + } + onValueChange: (propName)=> { + lightGizmo.propertyValueChange(propName); + } + onValueCommit: (propName)=> { + lightGizmo.propertyValueCommit(propName); + } + } + } + + + Node { + rotation: !lightGizmo.targetNode ? Qt.quaternion(1, 0, 0, 0) + : lightGizmo.targetNode.sceneRotation + + Node { + id: spotParts + visible: lightGizmo.targetNode instanceof SpotLight + + LightModel { + id: spotModel + + property real coneXYScale: spotParts.visible && lightGizmo.targetNode + ? lightGizmo.fadeScale * Math.tan(Math.PI * lightGizmo.targetNode.coneAngle / 180) + : 1 + + geometryName: "Edit 3D SpotLight Cone" + geometryType: LightGeometry.Spot + material: lightMaterial + scale: Qt.vector3d(coneXYScale, coneXYScale, + spotParts.visible && lightGizmo.targetNode && lightGizmo.targetNode.coneAngle > 90 + ? -lightGizmo.fadeScale : lightGizmo.fadeScale) + } + + LightModel { + id: spotInnerModel + + property real coneXYScale: spotParts.visible && lightGizmo.targetNode + ? lightGizmo.fadeScale * Math.tan(Math.PI * lightGizmo.targetNode.innerConeAngle / 180) + : 1 + + geometryName: "Edit 3D SpotLight Inner Cone" + geometryType: LightGeometry.Spot + material: lightMaterial + scale: Qt.vector3d(coneXYScale, coneXYScale, + spotParts.visible && lightGizmo.targetNode && lightGizmo.targetNode.innerConeAngle > 90 + ? -lightGizmo.fadeScale : lightGizmo.fadeScale) + } + + SpotLightHandle { + id: spotLightHandle + view3D: lightGizmo.view3D + color: (hovering || dragging) ? Qt.rgba(1, 1, 1, 1) : lightGizmo.color + position: lightGizmo.targetNode instanceof SpotLight ? Qt.vector3d(0, spotModel.scale.y, -spotModel.scale.z) + : Qt.vector3d(0, 0, 0) + targetNode: lightGizmo.targetNode instanceof SpotLight ? lightGizmo.targetNode : null + active: lightGizmo.targetNode instanceof SpotLight + dragHelper: lightGizmo.dragHelper + propName: "coneAngle" + propValue: lightGizmo.targetNode instanceof SpotLight ? targetNode.coneAngle : 0 + + onNewValueChanged: targetNode.coneAngle = newValue + onCurrentMousePosChanged: { + lightGizmo.currentMousePos = currentMousePos; + lightGizmo.currentLabel = currentLabel; + } + onValueChange: lightGizmo.propertyValueChange(propName) + onValueCommit: { + if (targetNode.innerConeAngle > targetNode.coneAngle) + targetNode.innerConeAngle = targetNode.coneAngle; + lightGizmo.propertyValueCommit(propName) + lightGizmo.propertyValueCommit(spotLightInnerHandle.propName); + } + } + + SpotLightHandle { + id: spotLightInnerHandle + view3D: lightGizmo.view3D + color: (hovering || dragging) ? Qt.rgba(1, 1, 1, 1) : lightGizmo.color + position: lightGizmo.targetNode instanceof SpotLight ? Qt.vector3d(0, -spotInnerModel.scale.y, -spotInnerModel.scale.z) + : Qt.vector3d(0, 0, 0) + eulerRotation: Qt.vector3d(180, 0, 0) + targetNode: lightGizmo.targetNode instanceof SpotLight ? lightGizmo.targetNode : null + active: lightGizmo.targetNode instanceof SpotLight + dragHelper: lightGizmo.dragHelper + propName: "innerConeAngle" + propValue: lightGizmo.targetNode instanceof SpotLight ? targetNode.innerConeAngle : 0 + + onNewValueChanged: targetNode.innerConeAngle = newValue + onCurrentMousePosChanged: { + lightGizmo.currentMousePos = currentMousePos; + lightGizmo.currentLabel = currentLabel; + } + onValueChange: lightGizmo.propertyValueChange(propName) + onValueCommit: { + if (targetNode.coneAngle < targetNode.innerConeAngle) + targetNode.coneAngle = targetNode.innerConeAngle; + lightGizmo.propertyValueCommit(propName) + lightGizmo.propertyValueCommit(spotLightHandle.propName); + } + } + + FadeHandle { + id: spotLightFadeHandle + view3D: lightGizmo.view3D + color: (hovering || dragging) ? Qt.rgba(1, 1, 1, 1) : lightGizmo.color + position: lightGizmo.targetNode instanceof SpotLight ? Qt.vector3d(spotModel.scale.x / 2, 0, -spotInnerModel.scale.z / 2) + : Qt.vector3d(0, 0, 0) + eulerRotation: Qt.vector3d(90, 0, 0) + targetNode: lightGizmo.targetNode instanceof SpotLight ? lightGizmo.targetNode : null + active: lightGizmo.targetNode instanceof SpotLight + dragHelper: lightGizmo.dragHelper + fadeScale: lightGizmo.fadeScale + dragScale: 2 + + onCurrentMousePosChanged: { + lightGizmo.currentMousePos = currentMousePos; + lightGizmo.currentLabel = currentLabel; + } + onValueChange: (propName)=> { + lightGizmo.propertyValueChange(propName); + } + onValueCommit: (propName)=> { + lightGizmo.propertyValueCommit(propName); + } + } + } + Node { + id: areaParts + visible: lightGizmo.targetNode instanceof AreaLight + + LightModel { + id: areaModel + geometryName: "Edit 3D AreaLight" + geometryType: LightGeometry.Area + material: lightMaterial + scale: areaParts.visible ? Qt.vector3d(lightGizmo.targetNode.width / 2, + lightGizmo.targetNode.height / 2, 1) + .times(lightGizmo.targetNode.scale) + : Qt.vector3d(1, 1, 1) + } + + AreaLightHandle { + id: areaWidthHandle + view3D: lightGizmo.view3D + color: (hovering || dragging) ? Qt.rgba(1, 1, 1, 1) : lightGizmo.color + position: lightGizmo.targetNode instanceof AreaLight ? Qt.vector3d(-areaModel.scale.x, 0, 0) + : Qt.vector3d(0, 0, 0) + eulerRotation: Qt.vector3d(0, 0, 90) + targetNode: lightGizmo.targetNode instanceof AreaLight ? lightGizmo.targetNode : null + active: lightGizmo.targetNode instanceof AreaLight + dragHelper: lightGizmo.dragHelper + propName: "width" + propValue: lightGizmo.targetNode instanceof AreaLight ? targetNode.width : 0 + + onNewValueChanged: targetNode.width = newValue + onCurrentMousePosChanged: { + lightGizmo.currentMousePos = currentMousePos; + lightGizmo.currentLabel = currentLabel; + } + onValueChange: lightGizmo.propertyValueChange(propName) + onValueCommit: lightGizmo.propertyValueCommit(propName) + } + + AreaLightHandle { + id: areaHeightHandle + view3D: lightGizmo.view3D + color: (hovering || dragging) ? Qt.rgba(1, 1, 1, 1) : lightGizmo.color + position: lightGizmo.targetNode instanceof AreaLight ? Qt.vector3d(0, -areaModel.scale.y, 0) + : Qt.vector3d(0, 0, 0) + eulerRotation: Qt.vector3d(0, 0, 180) + targetNode: lightGizmo.targetNode instanceof AreaLight ? lightGizmo.targetNode : null + active: lightGizmo.targetNode instanceof AreaLight + dragHelper: lightGizmo.dragHelper + propName: "height" + propValue: lightGizmo.targetNode instanceof AreaLight ? targetNode.height : 0 + + onNewValueChanged: targetNode.height = newValue + onCurrentMousePosChanged: { + lightGizmo.currentMousePos = currentMousePos; + lightGizmo.currentLabel = currentLabel; + } + onValueChange: lightGizmo.propertyValueChange(propName) + onValueCommit: lightGizmo.propertyValueCommit(propName) + } + } + + LightModel { + id: directionalModel + geometryName: "Edit 3D DirLight" + geometryType: LightGeometry.Directional + material: lightMaterial + visible: lightGizmo.targetNode instanceof DirectionalLight + scale: autoScaler.getScale(Qt.vector3d(50, 50, 50)) + } + + AdjustableArrow { + id: primaryArrow + eulerRotation: Qt.vector3d(-90, 0, 0) + targetNode: lightGizmo.targetNode + color: (hovering || dragging) ? Qt.rgba(1, 1, 1, 1) : lightGizmo.color + view3D: lightGizmo.view3D + active: lightGizmo.visible + dragHelper: lightGizmo.dragHelper + scale: autoScaler.getScale(Qt.vector3d(5, 5, 5)) + length: targetNode ? Math.max(1.0, 1.0 + targetNode.brightness / _generalHelper.brightnessScaler() * 10.0) + 3 : 10 + + property real _startBrightness + + function updateBrightness(relativeDistance, screenPos) + { + var currentValue = Math.max(0, (_startBrightness + relativeDistance * _generalHelper.brightnessScaler() / 10.0)); + currentValue *= brightnessMultiplier; + currentValue = Math.round(currentValue); + currentValue /= brightnessMultiplier; + + var l = Qt.locale(); + lightGizmo.currentLabel = "brightness" + qsTr(": ") + Number(currentValue).toLocaleString(l, 'f', brightnessDecimals); + lightGizmo.currentMousePos = screenPos; + targetNode.brightness = currentValue; + } + + onPressed: (mouseArea, screenPos)=> { + _startBrightness = targetNode.brightness; + updateBrightness(0, screenPos); + } + + onDragged: (mouseArea, sceneRelativeDistance, relativeDistance, screenPos)=> { + updateBrightness(relativeDistance, screenPos); + lightGizmo.propertyValueChange("brightness"); + } + + onReleased: (mouseArea, sceneRelativeDistance, relativeDistance, screenPos)=> { + updateBrightness(relativeDistance, screenPos); + lightGizmo.propertyValueCommit("brightness"); + } + } + + DefaultMaterial { + id: lightMaterial + diffuseColor: lightGizmo.color + lighting: DefaultMaterial.NoLighting + cullMode: Material.NoCulling + } + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/LightIconGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/LightIconGizmo.qml new file mode 100644 index 00000000000..b7bb272b630 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/LightIconGizmo.qml @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import LightUtils 1.0 + +IconGizmo { + id: lightIconGizmo + + property color overlayColor: targetNode ? targetNode.color : "transparent" + + iconSource: targetNode + ? targetNode instanceof DirectionalLight + ? "image://IconGizmoImageProvider/directional.png:" + overlayColor + : targetNode instanceof AreaLight + ? "image://IconGizmoImageProvider/area.png:" + overlayColor + : targetNode instanceof PointLight + ? "image://IconGizmoImageProvider/point.png:" + overlayColor + : "image://IconGizmoImageProvider/spot.png:" + overlayColor + : "image://IconGizmoImageProvider/point.png:" + overlayColor +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/LightModel.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/LightModel.qml new file mode 100644 index 00000000000..c500f6fbb0e --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/LightModel.qml @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import LightUtils 1.0 + +Model { + id: lightModel + + property alias geometryName: lightGeometry.name // Name must be unique for each geometry + property alias geometryType: lightGeometry.lightType + property Material material + + function updateGeometry() + { + lightGeometry.update(); + } + + scale: Qt.vector3d(50, 50, 50) + + geometry: lightGeometry + materials: [ material ] + + LightGeometry { + id: lightGeometry + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/Line3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/Line3D.qml new file mode 100644 index 00000000000..7e034ff75f7 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/Line3D.qml @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import LineGeometry 1.0 + +Node { + id: pivotLine + + property alias startPos: lineGeometry.startPos + property alias endPos: lineGeometry.endPos + property alias name: lineGeometry.name // Name must be unique for each line + property alias color: lineMat.diffuseColor + + Model { + geometry: LineGeometry { + id: lineGeometry + } + materials: [ + DefaultMaterial { + id: lineMat + lighting: DefaultMaterial.NoLighting + cullMode: Material.NoCulling + } + ] + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/MaterialNodeView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/MaterialNodeView.qml new file mode 100644 index 00000000000..7b5e2a15b21 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/MaterialNodeView.qml @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick3D 6.0 + +View3D { + id: root + anchors.fill: parent + environment: sceneEnv + + property Material previewMaterial + + SceneEnvironment { + id: sceneEnv + antialiasingMode: SceneEnvironment.MSAA + antialiasingQuality: SceneEnvironment.High + } + + Node { + DirectionalLight { + eulerRotation.x: -30 + eulerRotation.y: -30 + } + + PerspectiveCamera { + z: 120 + clipFar: 1000 + clipNear: 1 + } + + Model { + id: model + source: "#Sphere" + materials: previewMaterial + } + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNode2DImageView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNode2DImageView.qml new file mode 100644 index 00000000000..827999de4b4 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNode2DImageView.qml @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 + +Item { + id: root + width: 150 + height: 150 + + property alias contentItem: contentItem + + /* + View3D { + // Dummy view to hold the context in case View3D items are used in the component + // TODO remove when QTBUG-87678 is fixed + } + */ + + Item { + id: contentItem + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNode3DImageView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNode3DImageView.qml new file mode 100644 index 00000000000..f0722c660fb --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNode3DImageView.qml @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 + +Item { + id: root + width: 150 + height: 150 + visible: true + + property View3D view: null + property alias contentItem: contentItem + + property var previewObject + + property var materialViewComponent + property var modelViewComponent + property var nodeViewComponent + + property bool ready: false + + function destroyView() + { + previewObject = null; + if (view) + view.destroy(); + } + + function createViewForObject(obj) + { + if (obj instanceof Material) + createViewForMaterial(obj); + else if (obj instanceof Model) + createViewForModel(obj); + else if (obj instanceof Node) + createViewForNode(obj); + + previewObject = obj; + } + + function createViewForMaterial(material) + { + if (!materialViewComponent) + materialViewComponent = Qt.createComponent("MaterialNodeView.qml"); + + // Always recreate the view to ensure material is up to date + if (materialViewComponent.status === Component.Ready) + view = materialViewComponent.createObject(viewRect, {"previewMaterial": material}); + } + + function createViewForModel(model) + { + if (!modelViewComponent) + modelViewComponent = Qt.createComponent("ModelNodeView.qml"); + + // Always recreate the view to ensure model is up to date + if (modelViewComponent.status === Component.Ready) + view = modelViewComponent.createObject(viewRect, {"sourceModel": model}); + } + + function createViewForNode(node) + { + if (!nodeViewComponent) + nodeViewComponent = Qt.createComponent("NodeNodeView.qml"); + + // Always recreate the view to ensure node is up to date + if (nodeViewComponent.status === Component.Ready) + view = nodeViewComponent.createObject(viewRect, {"importScene": node}); + } + + function afterRender() + { + if (previewObject instanceof Node) { + view.fitToViewPort(); + ready = view.ready; + } else { + ready = true; + } + } + + View3D { + // Dummy view to hold the context + // TODO remove when QTBUG-87678 is fixed + } + + Item { + id: contentItem + anchors.fill: parent + + Rectangle { + id: viewRect + anchors.fill: parent + + gradient: Gradient { + GradientStop { position: 1.0; color: "#222222" } + GradientStop { position: 0.0; color: "#999999" } + } + } + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNodeView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNodeView.qml new file mode 100644 index 00000000000..657b0f4c944 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ModelNodeView.qml @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 + +View3D { + id: root + anchors.fill: parent + environment: sceneEnv + camera: theCamera + + property bool ready: false + property real prevZoomFactor: -1 + property Model sourceModel + + function fitToViewPort() + { + cameraControl.focusObject(model, theCamera.eulerRotation, true, false); + + if (cameraControl._zoomFactor < 0.1) { + model.scale = model.scale.times(10); + } else if (cameraControl._zoomFactor > 10) { + model.scale = model.scale.times(0.1); + } else { + // We need one more render after zoom factor change, so only set ready when zoom factor + // or scaling hasn't changed from the previous frame + ready = _generalHelper.fuzzyCompare(cameraControl._zoomFactor, prevZoomFactor); + prevZoomFactor = cameraControl._zoomFactor; + } + } + + SceneEnvironment { + id: sceneEnv + antialiasingMode: SceneEnvironment.MSAA + antialiasingQuality: SceneEnvironment.High + } + + EditCameraController { + id: cameraControl + camera: theCamera + anchors.fill: parent + view3d: root + ignoreToolState: true + } + + DirectionalLight { + eulerRotation.x: -30 + eulerRotation.y: -30 + } + + PerspectiveCamera { + id: theCamera + z: 600 + y: 600 + eulerRotation.x: -45 + clipFar: 10000 + clipNear: 1 + } + + Model { + id: model + eulerRotation.y: 45 + + source: sourceModel.source + geometry: sourceModel.geometry + + materials: [ + DefaultMaterial { + diffuseColor: "#4aee45" + } + ] + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/MoveGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/MoveGizmo.qml new file mode 100644 index 00000000000..b8f968b59ea --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/MoveGizmo.qml @@ -0,0 +1,175 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import MouseArea3D 1.0 + +Node { + id: moveGizmo + + property View3D view3D + property bool highlightOnHover: false + property Node targetNode: null + property bool globalOrientation: true + readonly property bool dragging: arrowX.dragging || arrowY.dragging || arrowZ.dragging + || planeX.dragging || planeY.dragging || planeZ.dragging + || centerBall.dragging + property MouseArea3D dragHelper: null + property alias freeDraggerArea: centerBall.mouseArea + + position: dragHelper.pivotScenePosition(targetNode) + + onTargetNodeChanged: position = dragHelper.pivotScenePosition(targetNode) + + Connections { + target: moveGizmo.targetNode + function onSceneTransformChanged() + { + moveGizmo.position = moveGizmo.dragHelper.pivotScenePosition(moveGizmo.targetNode); + } + } + + signal positionCommit() + signal positionMove() + + Node { + rotation: globalOrientation || !moveGizmo.targetNode ? Qt.quaternion(1, 0, 0, 0) + : moveGizmo.targetNode.sceneRotation + Arrow { + id: arrowX + eulerRotation: Qt.vector3d(0, 0, -90) + targetNode: moveGizmo.targetNode + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(1, 0, 0, 1)) + : Qt.rgba(1, 0, 0, 1) + view3D: moveGizmo.view3D + active: moveGizmo.visible + dragHelper: moveGizmo.dragHelper + + onPositionCommit: moveGizmo.positionCommit() + onPositionMove: moveGizmo.positionMove() + } + + Arrow { + id: arrowY + eulerRotation: Qt.vector3d(0, 0, 0) + targetNode: moveGizmo.targetNode + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0.6, 0, 1)) + : Qt.rgba(0, 0.6, 0, 1) + view3D: moveGizmo.view3D + active: moveGizmo.visible + dragHelper: moveGizmo.dragHelper + + onPositionCommit: moveGizmo.positionCommit() + onPositionMove: moveGizmo.positionMove() + } + + Arrow { + id: arrowZ + eulerRotation: Qt.vector3d(90, 0, 0) + targetNode: moveGizmo.targetNode + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0, 1, 1)) + : Qt.rgba(0, 0, 1, 1) + view3D: moveGizmo.view3D + active: moveGizmo.visible + dragHelper: moveGizmo.dragHelper + + onPositionCommit: moveGizmo.positionCommit() + onPositionMove: moveGizmo.positionMove() + } + + PlanarMoveHandle { + id: planeX + + y: 10 + z: 10 + + eulerRotation: Qt.vector3d(0, 90, 0) + targetNode: moveGizmo.targetNode + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(1, 0, 0, 1)) + : Qt.rgba(1, 0, 0, 1) + view3D: moveGizmo.view3D + active: moveGizmo.visible + dragHelper: moveGizmo.dragHelper + + onPositionCommit: moveGizmo.positionCommit() + onPositionMove: moveGizmo.positionMove() + } + + PlanarMoveHandle { + id: planeY + + x: 10 + z: 10 + + eulerRotation: Qt.vector3d(90, 0, 0) + targetNode: moveGizmo.targetNode + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0.6, 0, 1)) + : Qt.rgba(0, 0.6, 0, 1) + view3D: moveGizmo.view3D + active: moveGizmo.visible + dragHelper: moveGizmo.dragHelper + + onPositionCommit: moveGizmo.positionCommit() + onPositionMove: moveGizmo.positionMove() + } + + PlanarMoveHandle { + id: planeZ + + x: 10 + y: 10 + + eulerRotation: Qt.vector3d(0, 0, 0) + targetNode: moveGizmo.targetNode + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0, 1, 1)) + : Qt.rgba(0, 0, 1, 1) + view3D: moveGizmo.view3D + active: moveGizmo.visible + dragHelper: moveGizmo.dragHelper + + onPositionCommit: moveGizmo.positionCommit() + onPositionMove: moveGizmo.positionMove() + } + } + + PlanarMoveHandle { + id: centerBall + + source: "#Sphere" + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0.5, 0.5, 0.5, 1)) + : Qt.rgba(0.5, 0.5, 0.5, 1) + rotation: view3D.camera.rotation + priority: 10 + targetNode: moveGizmo.targetNode + + view3D: moveGizmo.view3D + active: moveGizmo.visible + dragHelper: moveGizmo.dragHelper + + onPositionCommit: moveGizmo.positionCommit() + onPositionMove: moveGizmo.positionMove() + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/NodeNodeView.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/NodeNodeView.qml new file mode 100644 index 00000000000..49d88d4c8a9 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/NodeNodeView.qml @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 + +View3D { + id: root + anchors.fill: parent + environment: sceneEnv + camera: theCamera + + property bool ready: false + property bool first: true + property real prevZoomFactor: -1 + + function fitToViewPort() + { + if (first) { + first = false; + selectionBox.targetNode = root.importScene; + } else { + cameraControl.focusObject(selectionBox.model, theCamera.eulerRotation, true, false); + + if (cameraControl._zoomFactor < 0.1) { + root.importScene.scale = root.importScene.scale.times(10); + } else if (cameraControl._zoomFactor > 10) { + root.importScene.scale = root.importScene.scale.times(0.1); + } else { + // We need one more render after zoom factor change, so only set ready when zoom factor + // or scaling hasn't changed from the previous frame + ready = _generalHelper.fuzzyCompare(cameraControl._zoomFactor, prevZoomFactor); + prevZoomFactor = cameraControl._zoomFactor; + selectionBox.visible = false; + } + } + } + + SceneEnvironment { + id: sceneEnv + antialiasingMode: SceneEnvironment.MSAA + antialiasingQuality: SceneEnvironment.High + } + + SelectionBox { + id: selectionBox + view3D: root + geometryName: "NodeNodeViewSB" + } + + EditCameraController { + id: cameraControl + camera: theCamera + anchors.fill: parent + view3d: root + ignoreToolState: true + } + + DirectionalLight { + eulerRotation.x: -30 + eulerRotation.y: -30 + } + + PerspectiveCamera { + id: theCamera + z: 600 + y: 600 + x: 600 + eulerRotation.x: -45 + eulerRotation.y: -45 + clipFar: 10000 + clipNear: 1 + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/Overlay2D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/Overlay2D.qml new file mode 100644 index 00000000000..30655a2787f --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/Overlay2D.qml @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 + +Item { + id: root + property Node targetNode + property View3D targetView + + property vector3d offset: Qt.vector3d(0, 0, 0) + + property bool isBehindCamera + + onTargetNodeChanged: updateOverlay() + + Connections { + target: targetNode + function onSceneTransformChanged() { updateOverlay() } + } + + Connections { + target: targetView.camera + function onSceneTransformChanged() { updateOverlay() } + } + + Connections { + target: _generalHelper + function onOverlayUpdateNeeded() { updateOverlay() } + } + + function updateOverlay() + { + var scenePos = targetNode ? targetNode.scenePosition : Qt.vector3d(0, 0, 0); + // Need separate variable as scenePos is reference to read-only property + var scenePosWithOffset = Qt.vector3d(scenePos.x + offset.x, + scenePos.y + offset.y, + scenePos.z + offset.z); + var viewPos = targetView ? targetView.mapFrom3DScene(scenePosWithOffset) + : Qt.vector3d(0, 0, 0); + root.x = viewPos.x; + root.y = viewPos.y; + + isBehindCamera = viewPos.z <= 0; + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/PlanarDraggable.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/PlanarDraggable.qml new file mode 100644 index 00000000000..1a1fea9f9ce --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/PlanarDraggable.qml @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import MouseArea3D 1.0 + +Model { + id: rootModel + + property View3D view3D + property alias color: gizmoMaterial.diffuseColor + property alias priority: mouseArea.priority + property Node targetNode: null + property bool dragging: mouseArea.dragging + property bool active: false + property MouseArea3D dragHelper: null + property alias mouseArea: mouseArea + + readonly property bool hovering: mouseArea.hovering + + property vector3d _scenePosPressed + property vector2d _planePosPressed + property vector3d _targetStartPos + + signal pressed(var mouseArea) + signal dragged(var mouseArea, vector3d sceneRelativeDistance, vector2d relativeDistance) + signal released(var mouseArea, vector3d sceneRelativeDistance, vector2d relativeDistance) + + source: "#Rectangle" + + DefaultMaterial { + id: gizmoMaterial + diffuseColor: "white" + lighting: DefaultMaterial.NoLighting + cullMode: Material.NoCulling + } + materials: gizmoMaterial + + function handlePressed(mouseArea, planePos) + { + if (!targetNode) + return; + + _planePosPressed = planePos; + _scenePosPressed = mouseArea.dragHelper.mapPositionToScene(planePos.toVector3d()); + _targetStartPos = mouseArea.pivotScenePosition(targetNode); + pressed(mouseArea); + } + + function calcRelativeDistance(mouseArea, planePos) + { + var scenePointerPos = mouseArea.dragHelper.mapPositionToScene(planePos.toVector3d()); + return scenePointerPos.minus(_scenePosPressed); + } + + function handleDragged(mouseArea, planePos) + { + if (!targetNode) + return; + + dragged(mouseArea, calcRelativeDistance(mouseArea, planePos), + planePos.minus(_planePosPressed)); + } + + function handleReleased(mouseArea, planePos) + { + if (!targetNode) + return; + + released(mouseArea, calcRelativeDistance(mouseArea, planePos), + planePos.minus(_planePosPressed)); + } + + MouseArea3D { + id: mouseArea + view3D: rootModel.view3D + x: -60 + y: -60 + width: 120 + height: 120 + grabsMouse: targetNode + active: rootModel.active + dragHelper: rootModel.dragHelper + + onPressed: (planePos)=> { + rootModel.handlePressed(mouseArea, planePos); + } + onDragged: (planePos)=> { + rootModel.handleDragged(mouseArea, planePos); + } + onReleased: (planePos)=> { + rootModel.handleReleased(mouseArea, planePos); + } + } +} + diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/PlanarMoveHandle.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/PlanarMoveHandle.qml new file mode 100644 index 00000000000..795f208a6b6 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/PlanarMoveHandle.qml @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import MouseArea3D 1.0 + +PlanarDraggable { + id: planarHandle + scale: Qt.vector3d(0.024, 0.024, 0.024) + + signal positionCommit() + signal positionMove() + + function localPos(sceneRelativeDistance) + { + var newScenePos = Qt.vector3d( + _targetStartPos.x + sceneRelativeDistance.x, + _targetStartPos.y + sceneRelativeDistance.y, + _targetStartPos.z + sceneRelativeDistance.z); + return targetNode.parent ? targetNode.parent.mapPositionFromScene(newScenePos) : newScenePos; + } + + onPressed: { + if (targetNode == multiSelectionNode) + _generalHelper.restartMultiSelection(); + } + + onDragged: (mouseArea, sceneRelativeDistance)=> { + targetNode.position = localPos(sceneRelativeDistance); + if (targetNode == multiSelectionNode) + _generalHelper.moveMultiSelection(false); + positionMove(); + } + + onReleased: (mouseArea, sceneRelativeDistance)=> { + targetNode.position = localPos(sceneRelativeDistance); + if (targetNode == multiSelectionNode) + _generalHelper.moveMultiSelection(true); + positionCommit(); + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/PlanarScaleHandle.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/PlanarScaleHandle.qml new file mode 100644 index 00000000000..ee4e2e673e5 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/PlanarScaleHandle.qml @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import MouseArea3D 1.0 + +PlanarDraggable { + id: planarHandle + scale: Qt.vector3d(0.024, 0.024, 0.024) + + property bool globalOrientation: false + property vector3d axisX + property vector3d axisY + + signal scaleCommit() + signal scaleChange() + + property vector3d _startScale + + onPressed: { + if (targetNode == multiSelectionNode) + _generalHelper.restartMultiSelection(); + _startScale = targetNode.scale; + } + + onDragged: (mouseArea, sceneRelativeDistance, relativeDistance)=> { + targetNode.scale = mouseArea.getNewScale(_startScale, relativeDistance.times(scale.x), + axisX, axisY); + if (targetNode == multiSelectionNode) + _generalHelper.scaleMultiSelection(false); + scaleChange(); + } + + onReleased: (mouseArea, sceneRelativeDistance, relativeDistance)=> { + targetNode.scale = mouseArea.getNewScale(_startScale, relativeDistance.times(scale.x), + axisX, axisY); + if (targetNode == multiSelectionNode) + _generalHelper.scaleMultiSelection(true); + scaleCommit(); + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/RotateGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/RotateGizmo.qml new file mode 100644 index 00000000000..4998974dabf --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/RotateGizmo.qml @@ -0,0 +1,293 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import MouseArea3D 1.0 + +Node { + id: rotateGizmo + + property View3D view3D + property bool highlightOnHover: true + property Node targetNode: null + property bool globalOrientation: true + readonly property bool dragging: cameraRing.dragging + || rotRingX.dragging || rotRingY.dragging || rotRingZ.dragging + property MouseArea3D dragHelper: null + property real currentAngle + property point currentMousePos + property alias freeDraggerArea: mouseAreaFree + property bool blocked: false + + position: dragHelper.pivotScenePosition(targetNode) + + onTargetNodeChanged: { + position = dragHelper.pivotScenePosition(targetNode); + blocked = _generalHelper.isRotationBlocked(targetNode); + } + + Connections { + target: rotateGizmo.targetNode + function onSceneTransformChanged() + { + rotateGizmo.position = dragHelper.pivotScenePosition(targetNode); + } + } + + Connections { + target: _generalHelper + function onRotationBlocksChanged() + { + blocked = _generalHelper.isRotationBlocked(targetNode); + } + } + + signal rotateCommit() + signal rotateChange() + + function copyRingProperties(srcRing) { + draggingRing.rotation = srcRing.sceneRotation; + draggingRing.color = srcRing.color; + draggingRing.scale = srcRing.scale; + } + + onDraggingChanged: { + if (rotRingX.dragging) + copyRingProperties(rotRingX) + else if (rotRingY.dragging) + copyRingProperties(rotRingY) + else if (rotRingZ.dragging) + copyRingProperties(rotRingZ) + } + + Node { + id: rotNode + rotation: globalOrientation || !rotateGizmo.targetNode ? Qt.quaternion(1, 0, 0, 0) + : rotateGizmo.targetNode.sceneRotation + visible: !rotateGizmo.dragging && !freeRotator.dragging + + RotateRing { + id: rotRingX + objectName: "Rotate Ring X" + eulerRotation: Qt.vector3d(0, 90, 0) + targetNode: rotateGizmo.targetNode + color: rotateGizmo.blocked ? Qt.rgba(0.5, 0.5, 0.5, 1) + : highlightOnHover && (hovering || dragging) + ? Qt.lighter(Qt.rgba(1, 0, 0, 1)) : Qt.rgba(1, 0, 0, 1) + priority: 40 + view3D: rotateGizmo.view3D + active: rotateGizmo.visible && !rotateGizmo.blocked + dragHelper: rotateGizmo.dragHelper + + onRotateCommit: rotateGizmo.rotateCommit() + onRotateChange: rotateGizmo.rotateChange() + onCurrentAngleChanged: rotateGizmo.currentAngle = currentAngle + onCurrentMousePosChanged: rotateGizmo.currentMousePos = currentMousePos + } + + RotateRing { + id: rotRingY + objectName: "Rotate Ring Y" + eulerRotation: Qt.vector3d(90, 0, 0) + targetNode: rotateGizmo.targetNode + color: rotateGizmo.blocked ? Qt.rgba(0.5, 0.5, 0.5, 1) + : highlightOnHover && (hovering || dragging) + ? Qt.lighter(Qt.rgba(0, 0.6, 0, 1)) : Qt.rgba(0, 0.6, 0, 1) + // Just a smidge smaller than higher priority rings so that it doesn't obscure them + scale: Qt.vector3d(0.998, 0.998, 0.998) + priority: 30 + view3D: rotateGizmo.view3D + active: rotateGizmo.visible && !rotateGizmo.blocked + dragHelper: rotateGizmo.dragHelper + + onRotateCommit: rotateGizmo.rotateCommit() + onRotateChange: rotateGizmo.rotateChange() + onCurrentAngleChanged: rotateGizmo.currentAngle = currentAngle + onCurrentMousePosChanged: rotateGizmo.currentMousePos = currentMousePos + } + + RotateRing { + id: rotRingZ + objectName: "Rotate Ring Z" + eulerRotation: Qt.vector3d(0, 0, 0) + targetNode: rotateGizmo.targetNode + color: rotateGizmo.blocked ? Qt.rgba(0.5, 0.5, 0.5, 1) + : highlightOnHover && (hovering || dragging) + ? Qt.lighter(Qt.rgba(0, 0, 1, 1)) : Qt.rgba(0, 0, 1, 1) + // Just a smidge smaller than higher priority rings so that it doesn't obscure them + scale: Qt.vector3d(0.996, 0.996, 0.996) + priority: 20 + view3D: rotateGizmo.view3D + active: rotateGizmo.visible && !rotateGizmo.blocked + dragHelper: rotateGizmo.dragHelper + + onRotateCommit: rotateGizmo.rotateCommit() + onRotateChange: rotateGizmo.rotateChange() + onCurrentAngleChanged: rotateGizmo.currentAngle = currentAngle + onCurrentMousePosChanged: rotateGizmo.currentMousePos = currentMousePos + } + } + + RotateRing { + // This ring is used as visual proxy during dragging to display the currently dragged + // plane in static position, as rotation planes can wobble when ancestors don't have + // uniform scaling. + // Camera ring doesn't need dragging proxy as it doesn't wobble. + id: draggingRing + objectName: "draggingRing" + targetNode: rotateGizmo.targetNode + view3D: rotateGizmo.view3D + active: false + visible: rotRingX.dragging || rotRingY.dragging || rotRingZ.dragging + } + + RotateRing { + id: cameraRing + objectName: "cameraRing" + rotation: rotateGizmo.view3D.camera.rotation + targetNode: rotateGizmo.targetNode + color: rotateGizmo.blocked ? Qt.rgba(0.5, 0.5, 0.5, 1) + : highlightOnHover && (hovering || dragging) + ? Qt.lighter(Qt.rgba(0.5, 0.5, 0.5, 1)) + : Qt.rgba(0.5, 0.5, 0.5, 1) + scale: Qt.vector3d(1.1, 1.1, 1.1) + priority: 10 + view3D: rotateGizmo.view3D + active: rotateGizmo.visible && !rotateGizmo.blocked + dragHelper: rotateGizmo.dragHelper + visible: !rotRingX.dragging && !rotRingY.dragging && !rotRingZ.dragging && !freeRotator.dragging + + onRotateCommit: rotateGizmo.rotateCommit() + onRotateChange: rotateGizmo.rotateChange() + onCurrentAngleChanged: rotateGizmo.currentAngle = currentAngle + onCurrentMousePosChanged: rotateGizmo.currentMousePos = currentMousePos + } + + Model { + id: freeRotator + + source: "#Sphere" + materials: DefaultMaterial { + id: material + diffuseColor: "black" + opacity: mouseAreaFree.hovering && !rotateGizmo.blocked ? 0.15 : 0 + lighting: DefaultMaterial.NoLighting + } + scale: Qt.vector3d(0.15, 0.15, 0.15) + visible: !rotateGizmo.dragging && !dragging + + property bool dragging: false + property vector3d _pointerPosPressed + property vector3d _startRotation + + function handlePressed(screenPos) + { + if (!rotateGizmo.targetNode) + return; + + if (targetNode == multiSelectionNode) + _generalHelper.restartMultiSelection(); + + // Need to recreate vector as we need to adjust it and we can't do that on reference of + // scenePosition, which is read-only property + var scenePos = rotateGizmo.dragHelper.pivotScenePosition(rotateGizmo.targetNode); + _pointerPosPressed = Qt.vector3d(screenPos.x, screenPos.y, 0); + + // Recreate vector so we don't follow the changes in targetNode.rotation + _startRotation = Qt.vector3d(rotateGizmo.targetNode.eulerRotation.x, + rotateGizmo.targetNode.eulerRotation.y, + rotateGizmo.targetNode.eulerRotation.z); + // Ensure we never set NaN values for rotation, even if target node originally has them + if (isNaN(_startRotation.x)) + _startRotation.x = 0; + if (isNaN(_startRotation.y)) + _startRotation.y = 0; + if (isNaN(_startRotation.z)) + _startRotation.z = 0; + + dragging = true; + } + + function handleDragged(screenPos) + { + if (!rotateGizmo.targetNode) + return; + + mouseAreaFree.applyFreeRotation( + rotateGizmo.targetNode, _startRotation, _pointerPosPressed, + Qt.vector3d(screenPos.x, screenPos.y, 0)); + + if (targetNode == multiSelectionNode) + _generalHelper.rotateMultiSelection(false); + + rotateGizmo.rotateChange(); + } + + function handleReleased(screenPos) + { + if (!rotateGizmo.targetNode) + return; + + mouseAreaFree.applyFreeRotation( + rotateGizmo.targetNode, _startRotation, _pointerPosPressed, + Qt.vector3d(screenPos.x, screenPos.y, 0)); + + if (targetNode == multiSelectionNode) + _generalHelper.rotateMultiSelection(true); + + rotateGizmo.rotateCommit(); + dragging = false; + + if (targetNode == multiSelectionNode) + _generalHelper.resetMultiSelectionNode(); + } + + MouseArea3D { + id: mouseAreaFree + view3D: rotateGizmo.view3D + rotation: rotateGizmo.view3D.camera.rotation + objectName: "Free rotator plane" + x: -50 + y: -50 + width: 100 + height: 100 + circlePickArea: Qt.point(25, 50) + grabsMouse: rotateGizmo.targetNode + active: rotateGizmo.visible && !rotateGizmo.blocked + dragHelper: rotateGizmo.dragHelper + + onPressed: (planePos, screenPos)=> { + freeRotator.handlePressed(screenPos); + } + onDragged: (planePos, screenPos)=> { + freeRotator.handleDragged(screenPos); + } + onReleased: (planePos, screenPos)=> { + freeRotator.handleReleased(screenPos); + } + } + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/RotateRing.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/RotateRing.qml new file mode 100644 index 00000000000..385806008ee --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/RotateRing.qml @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import MouseArea3D 1.0 + +Model { + id: rotateRing + + property View3D view3D + property alias color: material.diffuseColor + property Node targetNode: null + property bool dragging: mouseAreaMain.dragging + property bool active: false + property alias hovering: mouseAreaMain.hovering + property alias priority: mouseAreaMain.priority + property real currentAngle + property point currentMousePos + property MouseArea3D dragHelper: null + + property vector3d _pointerPosPressed + property vector3d _targetPosOnScreen + property vector3d _startRotation + property bool _trackBall + + signal rotateCommit() + signal rotateChange() + + source: "../meshes/ring.mesh" + + Model { + id: pickModel + objectName: "PickModel for " + rotateRing.objectName + source: "../meshes/ringselect.mesh" + pickable: true + } + + materials: DefaultMaterial { + id: material + diffuseColor: "white" + lighting: DefaultMaterial.NoLighting + } + + function applyLocalRotation(screenPos) + { + currentAngle = mouseAreaMain.dragHelper.getNewRotationAngle( + targetNode, _pointerPosPressed, Qt.vector3d(screenPos.x, screenPos.y, 0), + _targetPosOnScreen, currentAngle, _trackBall); + mouseAreaMain.dragHelper.applyRotationAngleToNode(targetNode, _startRotation, currentAngle); + } + + function handlePressed(screenPos, angle) + { + if (!targetNode) + return; + + if (targetNode == multiSelectionNode) + _generalHelper.restartMultiSelection(); + + // Need to recreate vector as we need to adjust it and we can't do that on reference of + // scenePosition, which is read-only property + var scenePos = mouseAreaMain.pivotScenePosition(targetNode); + + _targetPosOnScreen = view3D.mapFrom3DScene(scenePos); + _targetPosOnScreen.z = 0; + _pointerPosPressed = Qt.vector3d(screenPos.x, screenPos.y, 0); + _trackBall = angle < 0.1; + + // Recreate vector so we don't follow the changes in targetNode.eulerRotation + _startRotation = Qt.vector3d(targetNode.eulerRotation.x, + targetNode.eulerRotation.y, + targetNode.eulerRotation.z); + // Ensure we never set NaN values for rotation, even if target node originally has them + if (isNaN(_startRotation.x)) + _startRotation.x = 0; + if (isNaN(_startRotation.y)) + _startRotation.y = 0; + if (isNaN(_startRotation.z)) + _startRotation.z = 0; + currentAngle = 0; + currentMousePos = screenPos; + } + + function handleDragged(screenPos) + { + if (!targetNode) + return; + + applyLocalRotation(screenPos); + + if (targetNode == multiSelectionNode) + _generalHelper.rotateMultiSelection(false); + + currentMousePos = screenPos; + rotateChange(); + } + + function handleReleased(screenPos) + { + if (!targetNode) + return; + + applyLocalRotation(screenPos); + + if (targetNode == multiSelectionNode) + _generalHelper.rotateMultiSelection(true); + + rotateCommit(); + currentAngle = 0; + currentMousePos = screenPos; + + if (targetNode == multiSelectionNode) + _generalHelper.resetMultiSelectionNode(); + } + + MouseArea3D { + id: mouseAreaMain + view3D: rotateRing.view3D + objectName: "Main plane of " + rotateRing.objectName + x: -30 + y: -30 + width: 60 + height: 60 + circlePickArea: Qt.point(9.2, 1.4) + grabsMouse: targetNode + active: rotateRing.active + pickNode: pickModel + minAngle: 0.05 + dragHelper: rotateRing.dragHelper + + onPressed: (planePos, screenPos, angle)=> { + rotateRing.handlePressed(screenPos, angle); + } + onDragged: (planePos, screenPos)=> { + rotateRing.handleDragged(screenPos); + } + onReleased: (planePos, screenPos)=> { + rotateRing.handleReleased(screenPos); + } + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ScaleGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ScaleGizmo.qml new file mode 100644 index 00000000000..e76809f32fd --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ScaleGizmo.qml @@ -0,0 +1,241 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import MouseArea3D 1.0 + +Node { + id: scaleGizmo + + property View3D view3D + property bool highlightOnHover: false + property Node targetNode: null + readonly property bool dragging: scaleRodX.dragging || scaleRodY.dragging || scaleRodZ.dragging + || planeX.dragging || planeY.dragging || planeZ.dragging + || centerMouseArea.dragging + property MouseArea3D dragHelper: null + property alias freeDraggerArea: centerMouseArea + + position: dragHelper.pivotScenePosition(targetNode) + + onTargetNodeChanged: position = dragHelper.pivotScenePosition(targetNode) + + Connections { + target: scaleGizmo.targetNode + function onSceneTransformChanged() + { + scaleGizmo.position = scaleGizmo.dragHelper.pivotScenePosition(scaleGizmo.targetNode); + } + } + + signal scaleCommit() + signal scaleChange() + + Node { + rotation: !targetNode ? Qt.quaternion(1, 0, 0, 0) : targetNode.sceneRotation + + ScaleRod { + id: scaleRodX + eulerRotation: Qt.vector3d(0, 0, -90) + axis: Qt.vector3d(1, 0, 0) + targetNode: scaleGizmo.targetNode + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(1, 0, 0, 1)) + : Qt.rgba(1, 0, 0, 1) + view3D: scaleGizmo.view3D + active: scaleGizmo.visible + dragHelper: scaleGizmo.dragHelper + + onScaleCommit: scaleGizmo.scaleCommit() + onScaleChange: scaleGizmo.scaleChange() + } + + ScaleRod { + id: scaleRodY + eulerRotation: Qt.vector3d(0, 0, 0) + axis: Qt.vector3d(0, 1, 0) + targetNode: scaleGizmo.targetNode + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0.6, 0, 1)) + : Qt.rgba(0, 0.6, 0, 1) + view3D: scaleGizmo.view3D + active: scaleGizmo.visible + dragHelper: scaleGizmo.dragHelper + + onScaleCommit: scaleGizmo.scaleCommit() + onScaleChange: scaleGizmo.scaleChange() + } + + ScaleRod { + id: scaleRodZ + eulerRotation: Qt.vector3d(90, 0, 0) + axis: Qt.vector3d(0, 0, 1) + targetNode: scaleGizmo.targetNode + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0, 1, 1)) + : Qt.rgba(0, 0, 1, 1) + view3D: scaleGizmo.view3D + active: scaleGizmo.visible + dragHelper: scaleGizmo.dragHelper + + onScaleCommit: scaleGizmo.scaleCommit() + onScaleChange: scaleGizmo.scaleChange() + } + + PlanarScaleHandle { + id: planeX + + y: 10 + z: 10 + + eulerRotation: Qt.vector3d(0, 90, 0) + axisX: Qt.vector3d(0, 0, -1) + axisY: Qt.vector3d(0, 1, 0) + targetNode: scaleGizmo.targetNode + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(1, 0, 0, 1)) + : Qt.rgba(1, 0, 0, 1) + view3D: scaleGizmo.view3D + active: scaleGizmo.visible + dragHelper: scaleGizmo.dragHelper + + onScaleCommit: scaleGizmo.scaleCommit() + onScaleChange: scaleGizmo.scaleChange() + } + + PlanarScaleHandle { + id: planeY + + x: 10 + z: 10 + + eulerRotation: Qt.vector3d(90, 0, 0) + axisX: Qt.vector3d(1, 0, 0) + axisY: Qt.vector3d(0, 0, 1) + targetNode: scaleGizmo.targetNode + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0.6, 0, 1)) + : Qt.rgba(0, 0.6, 0, 1) + view3D: scaleGizmo.view3D + active: scaleGizmo.visible + dragHelper: scaleGizmo.dragHelper + + onScaleCommit: scaleGizmo.scaleCommit() + onScaleChange: scaleGizmo.scaleChange() + } + + PlanarScaleHandle { + id: planeZ + + x: 10 + y: 10 + + eulerRotation: Qt.vector3d(0, 0, 0) + axisX: Qt.vector3d(1, 0, 0) + axisY: Qt.vector3d(0, 1, 0) + targetNode: scaleGizmo.targetNode + color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0, 0, 1, 1)) + : Qt.rgba(0, 0, 1, 1) + view3D: scaleGizmo.view3D + active: scaleGizmo.visible + dragHelper: scaleGizmo.dragHelper + + onScaleCommit: scaleGizmo.scaleCommit() + onScaleChange: scaleGizmo.scaleChange() + } + } + + Model { + id: centerCube + + source: "#Cube" + scale: Qt.vector3d(0.024, 0.024, 0.024) + materials: DefaultMaterial { + id: material + diffuseColor: highlightOnHover + && (centerMouseArea.hovering || centerMouseArea.dragging) + ? Qt.lighter(Qt.rgba(0.5, 0.5, 0.5, 1)) + : Qt.rgba(0.5, 0.5, 0.5, 1) + lighting: DefaultMaterial.NoLighting + } + + MouseArea3D { + id: centerMouseArea + view3D: scaleGizmo.view3D + x: -60 + y: -60 + width: 120 + height: 120 + rotation: view3D.camera.rotation + grabsMouse: scaleGizmo.targetNode + priority: 10 + active: scaleGizmo.visible + dragHelper: scaleGizmo.dragHelper + + property vector3d _startScale + property point _startScreenPos + + function localScale(screenPos) + { + var yDelta = screenPos.y - _startScreenPos.y; + if (yDelta === 0) + return _startScale; + var scaler = 1.0 + (yDelta * 0.025); + if (scaler === 0) + scaler = 0.0001; + return Qt.vector3d(scaler * _startScale.x, + scaler * _startScale.y, + scaler * _startScale.z); + } + + onPressed: (planePos, screenPos)=> { + if (!scaleGizmo.targetNode) + return; + + if (targetNode == multiSelectionNode) + _generalHelper.restartMultiSelection(); + + // Recreate vector so we don't follow the changes in targetNode.scale + _startScale = Qt.vector3d(scaleGizmo.targetNode.scale.x, + scaleGizmo.targetNode.scale.y, + scaleGizmo.targetNode.scale.z); + _startScreenPos = screenPos; + } + onDragged: (planePos, screenPos)=> { + if (!scaleGizmo.targetNode) + return; + scaleGizmo.targetNode.scale = localScale(screenPos); + if (targetNode == multiSelectionNode) + _generalHelper.scaleMultiSelection(false); + scaleGizmo.scaleChange(); + } + onReleased: (planePos, screenPos)=> { + if (!scaleGizmo.targetNode) + return; + + scaleGizmo.targetNode.scale = localScale(screenPos); + if (targetNode == multiSelectionNode) + _generalHelper.scaleMultiSelection(true); + scaleGizmo.scaleCommit(); + } + } + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ScaleRod.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ScaleRod.qml new file mode 100644 index 00000000000..1857bfddda6 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/ScaleRod.qml @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import MouseArea3D 1.0 + +DirectionalDraggable { + id: scaleRod + source: "../meshes/scalerod.mesh" + + property vector3d axis + + signal scaleCommit() + signal scaleChange() + + property vector3d _startScale + + Model { + source: "#Cube" + y: 10 + scale: Qt.vector3d(0.020, 0.020, 0.020) + materials: DefaultMaterial { + id: material + diffuseColor: scaleRod.color + lighting: DefaultMaterial.NoLighting + } + } + + onPressed: { + if (targetNode == multiSelectionNode) + _generalHelper.restartMultiSelection(); + _startScale = targetNode.scale; + } + + onDragged: (mouseArea, sceneRelativeDistance, relativeDistance)=> { + targetNode.scale = mouseArea.getNewScale(_startScale, Qt.vector2d(relativeDistance, 0), + axis, Qt.vector3d(0, 0, 0)); + if (targetNode == multiSelectionNode) + _generalHelper.scaleMultiSelection(false); + scaleChange(); + } + + onReleased: (mouseArea, sceneRelativeDistance, relativeDistance)=> { + targetNode.scale = mouseArea.getNewScale(_startScale, Qt.vector2d(relativeDistance, 0), + axis, Qt.vector3d(0, 0, 0)); + if (targetNode == multiSelectionNode) + _generalHelper.scaleMultiSelection(true); + scaleCommit(); + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/SceneView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/SceneView3D.qml new file mode 100644 index 00000000000..c75472096e7 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/SceneView3D.qml @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick3D 6.0 + +View3D { + id: sceneView + anchors.fill: parent + + property bool usePerspective: false + property alias showSceneLight: sceneLight.visible + property alias showGrid: helperGrid.visible + property alias sceneHelpers: sceneHelpers + property alias perspectiveCamera: scenePerspectiveCamera + property alias orthoCamera: sceneOrthoCamera + property double cameraZoomFactor: .55; + + // Empirical cameraZoomFactor values at which the grid zoom level is doubled. The values are + // approximately uniformally distributed over the non-linear range of cameraZoomFactor. + readonly property var grid_thresholds: [0.55, 1.10, 2.35, 4.9, 10.0, 20.5, 42.0, 85.0, 999999.0] + property var thresIdx: 1 + property var thresPerc: 1.0 // percentage of cameraZoomFactor to the current grid zoom threshold (0.0 - 1.0) + + camera: usePerspective ? scenePerspectiveCamera : sceneOrthoCamera + + onCameraZoomFactorChanged: { + thresIdx = Math.max(1, grid_thresholds.findIndex(v => v > cameraZoomFactor)); + thresPerc = (grid_thresholds[thresIdx] - cameraZoomFactor) / (grid_thresholds[thresIdx] - grid_thresholds[thresIdx - 1]); + } + + Node { + id: sceneHelpers + + HelperGrid { + id: helperGrid + lines: Math.pow(2, grid_thresholds.length - thresIdx - 1); + step: 100 * grid_thresholds[0] * Math.pow(2, thresIdx - 1); + subdivAlpha: thresPerc; + } + + PointLight { + id: sceneLight + position: usePerspective ? scenePerspectiveCamera.position + : sceneOrthoCamera.position + quadraticFade: 0 + linearFade: 0 + } + + // Initial camera position and rotation should be such that they look at origin. + // Otherwise EditCameraController._lookAtPoint needs to be initialized to correct + // point. + PerspectiveCamera { + id: scenePerspectiveCamera + z: 600 + y: 600 + eulerRotation.x: -45 + clipFar: 100000 + clipNear: 1 + } + + OrthographicCamera { + id: sceneOrthoCamera + z: 600 + y: 600 + eulerRotation.x: -45 + clipFar: 100000 + clipNear: -10000 + } + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/SelectionBox.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/SelectionBox.qml new file mode 100644 index 00000000000..1e7cc7b11ac --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/SelectionBox.qml @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 +import SelectionBoxGeometry 1.0 + +Node { + id: selectionBox + + property View3D view3D + property Node targetNode: null + property alias model: selectionBoxModel + property alias geometryName: selectionBoxGeometry.name + + SelectionBoxGeometry { + id: selectionBoxGeometry + name: "Selection Box of 3D Edit View" + view3D: selectionBox.view3D + targetNode: selectionBox.targetNode + rootNode: selectionBox + } + + Model { + id: selectionBoxModel + geometry: selectionBoxGeometry + + scale: selectionBox.targetNode ? selectionBox.targetNode.scale : Qt.vector3d(1, 1, 1) + rotation: selectionBox.targetNode ? selectionBox.targetNode.rotation : Qt.quaternion(1, 0, 0, 0) + position: selectionBox.targetNode ? selectionBox.targetNode.position : Qt.vector3d(0, 0, 0) + pivot: selectionBox.targetNode ? selectionBox.targetNode.pivot : Qt.vector3d(0, 0, 0) + + visible: selectionBox.targetNode && !selectionBoxGeometry.isEmpty + + materials: [ + DefaultMaterial { + diffuseColor: "#fff600" + lighting: DefaultMaterial.NoLighting + cullMode: Material.NoCulling + } + ] + } +} diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/SpotLightHandle.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/SpotLightHandle.qml new file mode 100644 index 00000000000..1c7322f4e28 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/SpotLightHandle.qml @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 6.0 +import QtQuick3D 6.0 + +DirectionalDraggable { + id: handleRoot + + property string currentLabel + property point currentMousePos + property string propName + property real propValue: 0 + property real newValue: 0 + + scale: autoScaler.getScale(Qt.vector3d(5, 5, 5)) + length: 3 + offset: -1.5 + + Model { + id: handle + source: "#Sphere" + materials: [ handleRoot.material ] + scale: Qt.vector3d(0.02, 0.02, 0.02) + } + + AutoScaleHelper { + id: autoScaler + active: handleRoot.active + view3D: handleRoot.view3D + } + + property real _startAngle + + signal valueCommit() + signal valueChange() + + function updateAngle(relativeDistance, screenPos) + { + handleRoot.newValue = Math.round(Math.min(180, Math.max(0, _startAngle + relativeDistance))); + var l = Qt.locale(); + handleRoot.currentLabel = propName + qsTr(": ") + Number(newValue).toLocaleString(l, 'f', 0); + handleRoot.currentMousePos = screenPos; + } + + onPressed: (mouseArea, screenPos)=> { + _startAngle = propValue; + updateAngle(0, screenPos); + } + + onDragged: (mouseArea, sceneRelativeDistance, relativeDistance, screenPos)=> { + updateAngle(relativeDistance, screenPos); + handleRoot.valueChange(); + } + + onReleased: (mouseArea, sceneRelativeDistance, relativeDistance, screenPos)=> { + updateAngle(relativeDistance, screenPos); + handleRoot.valueCommit(); + } +} diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.cpp index d3dc35f2210..82af9150d35 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.cpp @@ -96,11 +96,13 @@ void IconRenderer::setupRender() if (auto scene = qobject_cast(iconItem)) { qmlRegisterType("SelectionBoxGeometry", 1, 0, "SelectionBoxGeometry"); QQmlComponent component(engine); - component.loadUrl(QUrl("qrc:/qtquickplugin/mockfiles/IconRenderer3D.qml")); - m_containerItem = qobject_cast(component.create()); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + component.loadUrl(QUrl("qrc:/qtquickplugin/mockfiles/qt5/IconRenderer3D.qml")); + m_containerItem = qobject_cast(component.create()); DesignerSupport::setRootItem(view, m_containerItem); #else + component.loadUrl(QUrl("qrc:/qtquickplugin/mockfiles/qt6/IconRenderer3D.qml")); + m_containerItem = qobject_cast(component.create()); m_window->contentItem()->setSize(m_containerItem->size()); m_window->setGeometry(0, 0, m_containerItem->width(), m_containerItem->height()); m_containerItem->setParentItem(m_window->contentItem()); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index b71729af3b1..5f50446b9e8 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -392,8 +392,11 @@ void Qt5InformationNodeInstanceServer::createEditView3D() new QmlDesigner::Internal::IconGizmoImageProvider); m_3dHelper = helper; - createAuxiliaryQuickView(QUrl("qrc:/qtquickplugin/mockfiles/EditView3D.qml"), m_editView3DData); - +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + createAuxiliaryQuickView(QUrl("qrc:/qtquickplugin/mockfiles/qt6/EditView3D.qml"), m_editView3DData); +#else + createAuxiliaryQuickView(QUrl("qrc:/qtquickplugin/mockfiles/qt5/EditView3D.qml"), m_editView3DData); +#endif if (m_editView3DData.rootItem) helper->setParent(m_editView3DData.rootItem); #endif @@ -1192,12 +1195,22 @@ void Qt5InformationNodeInstanceServer::initializeAuxiliaryViews() #ifdef QUICK3D_MODULE if (isQuick3DMode()) createEditView3D(); - createAuxiliaryQuickView(QUrl("qrc:/qtquickplugin/mockfiles/ModelNode3DImageView.qml"), +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + createAuxiliaryQuickView(QUrl("qrc:/qtquickplugin/mockfiles/qt6/ModelNode3DImageView.qml"), + m_modelNode3DImageViewData); +#else + createAuxiliaryQuickView(QUrl("qrc:/qtquickplugin/mockfiles/qt5/ModelNode3DImageView.qml"), m_modelNode3DImageViewData); #endif +#endif - createAuxiliaryQuickView(QUrl("qrc:/qtquickplugin/mockfiles/ModelNode2DImageView.qml"), +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + createAuxiliaryQuickView(QUrl("qrc:/qtquickplugin/mockfiles/qt6/ModelNode2DImageView.qml"), m_modelNode2DImageViewData); +#else + createAuxiliaryQuickView(QUrl("qrc:/qtquickplugin/mockfiles/qt5/ModelNode2DImageView.qml"), + m_modelNode2DImageViewData); +#endif m_modelNode2DImageViewData.window->setDefaultAlphaBuffer(true); m_modelNode2DImageViewData.window->setColor(Qt::transparent); } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri b/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri index 189324f478e..0b364d32b3e 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri @@ -29,6 +29,9 @@ include (import3d/import3d.pri) SOURCES += $$PWD/qml2puppetmain.cpp RESOURCES += $$PWD/../qmlpuppet.qrc +versionAtLeast(QT_VERSION, 6.0.0): RESOURCES += $$PWD/../editor3d_qt6.qrc +else: RESOURCES += $$PWD/../editor3d_qt5.qrc + DISTFILES += Info.plist unix:!openbsd:!osx: LIBS += -lrt # posix shared memory diff --git a/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc b/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc index e0f8dcbb80a..162c955ac14 100644 --- a/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc +++ b/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc @@ -9,58 +9,7 @@ mockfiles/SwipeView.qml mockfiles/GenericBackend.qml mockfiles/Dialog.qml - mockfiles/IconRenderer3D.qml - mockfiles/EditView3D.qml - mockfiles/EditCameraController.qml - mockfiles/Arrow.qml - mockfiles/AutoScaleHelper.qml - mockfiles/MoveGizmo.qml - mockfiles/CameraFrustum.qml - mockfiles/CameraGizmo.qml - mockfiles/LightModel.qml - mockfiles/LightIconGizmo.qml - mockfiles/LightGizmo.qml - mockfiles/AdjustableArrow.qml - mockfiles/FadeHandle.qml - mockfiles/AreaLightHandle.qml - mockfiles/SpotLightHandle.qml - mockfiles/IconGizmo.qml - mockfiles/Overlay2D.qml - mockfiles/HelperGrid.qml - mockfiles/DirectionalDraggable.qml - mockfiles/PlanarDraggable.qml - mockfiles/PlanarMoveHandle.qml - mockfiles/PlanarScaleHandle.qml - mockfiles/ScaleRod.qml - mockfiles/ScaleGizmo.qml mockfiles/ToolBarButton.qml mockfiles/ToggleButton.qml - mockfiles/RotateGizmo.qml - mockfiles/RotateRing.qml - mockfiles/SceneView3D.qml - mockfiles/SelectionBox.qml - mockfiles/AxisHelper.qml - mockfiles/AxisHelperArm.qml - mockfiles/Line3D.qml - mockfiles/ModelNode3DImageView.qml - mockfiles/ModelNode2DImageView.qml - mockfiles/MaterialNodeView.qml - mockfiles/ModelNodeView.qml - mockfiles/NodeNodeView.qml - mockfiles/meshes/arrow.mesh - mockfiles/meshes/scalerod.mesh - mockfiles/meshes/ring.mesh - mockfiles/meshes/ringselect.mesh - mockfiles/meshes/axishelper.mesh - mockfiles/images/editor_camera.png - mockfiles/images/editor_camera@2x.png - mockfiles/images/area.png - mockfiles/images/area@2x.png - mockfiles/images/directional.png - mockfiles/images/directional@2x.png - mockfiles/images/point.png - mockfiles/images/point@2x.png - mockfiles/images/spot.png - mockfiles/images/spot@2x.png diff --git a/src/tools/qml2puppet/CMakeLists.txt b/src/tools/qml2puppet/CMakeLists.txt index 41e7b851ee3..3dbb875ce41 100644 --- a/src/tools/qml2puppet/CMakeLists.txt +++ b/src/tools/qml2puppet/CMakeLists.txt @@ -107,6 +107,20 @@ add_qtc_executable(qml2puppet DESTINATION ${DESTDIR} ) +extend_qtc_executable(qml2puppet + CONDITION Qt5_VERSION VERSION_GREATER_EQUAL 6.0.0 + SOURCES_PREFIX "${SRCDIR}/" + SOURCES + "editor3d_qt6.qrc" +) + +extend_qtc_executable(qml2puppet + CONDITION Qt5_VERSION VERSION_LESS 6.0.0 + SOURCES_PREFIX "${SRCDIR}/" + SOURCES + "editor3d_qt5.qrc" +) + extend_qtc_executable(qml2puppet CONDITION UNIX AND (NOT APPLE) DEPENDS rt diff --git a/src/tools/qml2puppet/qml2puppet.qbs b/src/tools/qml2puppet/qml2puppet.qbs index 6ae72354d5a..c2b57579563 100644 --- a/src/tools/qml2puppet/qml2puppet.qbs +++ b/src/tools/qml2puppet/qml2puppet.qbs @@ -166,6 +166,20 @@ QtcTool { files: ["sharedmemory_qt.cpp"] } + Group { + name: "puppet2 3D editor Qt5" + condition: !product.usesQt6 + prefix: puppetDir + "/" + files: ["editor3d_qt5.qrc"] + } + + Group { + name: "puppet2 3D editor Qt6" + condition: product.usesQt6 + prefix: puppetDir + "/" + files: ["editor3d_qt6.qrc"] + } + Group { name: "puppet2 sources" prefix: puppetDir + "/qml2puppet/" From 1e1c556bc83edbd5a2675e803bfe6df2f2ef9459 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 29 Jun 2021 09:41:53 +0200 Subject: [PATCH 051/149] Utils: Add a FilePath::ensureExistingFile Essentially a kind of 'touch', to be used in the CMake file API. Change-Id: Iaae62b441c0006b39d4bef5f06420e798c28c2a5 Reviewed-by: Christian Stenger --- src/libs/utils/fileutils.cpp | 14 ++++++++++++++ src/libs/utils/fileutils.h | 2 ++ src/plugins/docker/dockerdevice.cpp | 16 ++++++++++++++++ src/plugins/docker/dockerdevice.h | 1 + .../devicesupport/devicemanager.cpp | 6 ++++++ .../projectexplorer/devicesupport/idevice.cpp | 7 +++++++ .../projectexplorer/devicesupport/idevice.h | 1 + 7 files changed, 47 insertions(+) diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index c21a17d86b6..cc51b136971 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -898,6 +898,20 @@ bool FilePath::ensureWritableDir() const return QDir().mkpath(m_data); } +bool FilePath::ensureExistingFile() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.ensureExistingFile, return false); + return s_deviceHooks.ensureExistingFile(*this); + } + QFile f(m_data); + if (f.exists()) + return true; + f.open(QFile::WriteOnly); + f.close(); + return f.exists(); +} + bool FilePath::isExecutableFile() const { if (needsDevice()) { diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index 3153b99bea9..9ef2ae21279 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -75,6 +75,7 @@ public: std::function isWritableDir; std::function isWritableFile; std::function ensureWritableDir; + std::function ensureExistingFile; std::function createDir; std::function exists; std::function removeFile; @@ -135,6 +136,7 @@ public: bool isWritableDir() const; bool isWritableFile() const; bool ensureWritableDir() const; + bool ensureExistingFile() const; bool isExecutableFile() const; bool isReadableFile() const; bool isReadableDir() const; diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 3b1e9b1250a..478f96e91d5 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -877,6 +877,22 @@ bool DockerDevice::exists(const FilePath &filePath) const return exitCode == 0; } +bool DockerDevice::ensureExistingFile(const FilePath &filePath) const +{ + QTC_ASSERT(handlesFile(filePath), return false); + tryCreateLocalFileAccess(); + if (hasLocalFileAccess()) { + const FilePath localAccess = mapToLocalAccess(filePath); + const bool res = localAccess.ensureExistingFile(); + LOG("Ensure existing file? " << filePath.toUserOutput() << localAccess.toUserOutput() << res); + return res; + } + const QString path = filePath.path(); + const CommandLine cmd("touch", {path}); + const int exitCode = d->runSynchronously(cmd); + return exitCode == 0; +} + bool DockerDevice::removeFile(const FilePath &filePath) const { QTC_ASSERT(handlesFile(filePath), return false); diff --git a/src/plugins/docker/dockerdevice.h b/src/plugins/docker/dockerdevice.h index b299880904f..6d302d1815d 100644 --- a/src/plugins/docker/dockerdevice.h +++ b/src/plugins/docker/dockerdevice.h @@ -82,6 +82,7 @@ public: bool isWritableDirectory(const Utils::FilePath &filePath) const override; bool createDirectory(const Utils::FilePath &filePath) const override; bool exists(const Utils::FilePath &filePath) const override; + bool ensureExistingFile(const Utils::FilePath &filePath) const override; bool removeFile(const Utils::FilePath &filePath) const override; bool removeRecursively(const Utils::FilePath &filePath) const override; bool copyFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override; diff --git a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp index 5e1151c6b70..83eecf813a0 100644 --- a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp @@ -411,6 +411,12 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_uniqueensureWritableDirectory(filePath); }; + deviceHooks.ensureExistingFile = [](const FilePath &filePath) { + auto device = DeviceManager::deviceForPath(filePath); + QTC_ASSERT(device, return false); + return device->ensureExistingFile(filePath); + }; + deviceHooks.createDir = [](const FilePath &filePath) { auto device = DeviceManager::deviceForPath(filePath); QTC_ASSERT(device, return false); diff --git a/src/plugins/projectexplorer/devicesupport/idevice.cpp b/src/plugins/projectexplorer/devicesupport/idevice.cpp index ac36f7d3316..3adb3f2d305 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/idevice.cpp @@ -260,6 +260,13 @@ bool IDevice::ensureWritableDirectory(const FilePath &filePath) const return createDirectory(filePath); } +bool IDevice::ensureExistingFile(const FilePath &filePath) const +{ + Q_UNUSED(filePath); + QTC_CHECK(false); + return false; +} + bool IDevice::createDirectory(const FilePath &filePath) const { Q_UNUSED(filePath); diff --git a/src/plugins/projectexplorer/devicesupport/idevice.h b/src/plugins/projectexplorer/devicesupport/idevice.h index a114f85c662..352ed64c67a 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.h +++ b/src/plugins/projectexplorer/devicesupport/idevice.h @@ -242,6 +242,7 @@ public: virtual bool isReadableDirectory(const Utils::FilePath &filePath) const; virtual bool isWritableDirectory(const Utils::FilePath &filePath) const; virtual bool ensureWritableDirectory(const Utils::FilePath &filePath) const; + virtual bool ensureExistingFile(const Utils::FilePath &filePath) const; virtual bool createDirectory(const Utils::FilePath &filePath) const; virtual bool exists(const Utils::FilePath &filePath) const; virtual bool removeFile(const Utils::FilePath &filePath) const; From d857e17b136fd43404c95b52219a7764e54e229f Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 29 Jun 2021 09:40:56 +0200 Subject: [PATCH 052/149] CMake: Use FilePath::ensureExistingFile in file API Change-Id: I38c2e97711bbc1c72c51eeb59429572ea7931e4d Reviewed-by: Christian Stenger --- src/plugins/cmakeprojectmanager/fileapiparser.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/fileapiparser.cpp b/src/plugins/cmakeprojectmanager/fileapiparser.cpp index 99a2810b68b..155813e6ff0 100644 --- a/src/plugins/cmakeprojectmanager/fileapiparser.cpp +++ b/src/plugins/cmakeprojectmanager/fileapiparser.cpp @@ -839,14 +839,8 @@ bool FileApiParser::setupCMakeFileApi(const FilePath &buildDirectory, Utils::Fil bool failedBefore = false; for (const FilePath &filePath : cmakeQueryFilePaths(buildDirectory)) { - QTC_CHECK(!filePath.needsDevice()); - QFile f(filePath.path()); - if (!f.exists()) { - f.open(QFile::WriteOnly); - f.close(); - } - - if (!f.exists() && !failedBefore) { + const bool success = filePath.ensureExistingFile(); + if (!success && !failedBefore) { failedBefore = true; reportFileApiSetupFailure(); } From 4e777fcf908f65a0184d0ef9fad82190ecf103a4 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Tue, 29 Jun 2021 11:01:50 +0200 Subject: [PATCH 053/149] Clangd: Make "Follow Symbol" more robust The AST is only needed for virtual function disambiguation, while the basic functionality works fine without it. So in case clangd fails to retrieve the AST node for some reason, just use the original "go to definition" result. This also makes "Follow Symbol" work with clangd < 12. Change-Id: I2c110e9a51b01dc912fcdf6f4ed45fce766df374 Reviewed-by: David Schulz --- src/plugins/clangcodemodel/clangdclient.cpp | 22 ++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 4ec1ac3d4af..47a9809c1c0 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -526,7 +526,7 @@ public: Utils::Link defLink; QList allLinks; QHash declDefMap; - AstNode cursorNode; + Utils::optional cursorNode; AstNode defLinkNode; SymbolDataList symbolsToDisplay; std::set openedFiles; @@ -1074,11 +1074,16 @@ void ClangdClient::followSymbol( if (!d->followSymbolData || id != d->followSymbolData->id) return; d->followSymbolData->defLink = link; - if (d->followSymbolData->cursorNode.isValid()) + if (d->followSymbolData->cursorNode) d->handleGotoDefinitionResult(); }; symbolSupport().findLinkAt(document, cursor, std::move(gotoDefCallback), true); + if (versionNumber() < QVersionNumber(12)) { + d->followSymbolData->cursorNode.emplace(AstNode()); + return; + } + AstRequest astRequest(AstParams(TextDocumentIdentifier(d->followSymbolData->uri), Range(cursor))); astRequest.setResponseCallback([this, id = d->followSymbolData->id]( @@ -1087,11 +1092,10 @@ void ClangdClient::followSymbol( if (!d->followSymbolData || d->followSymbolData->id != id) return; const auto result = response.result(); - if (!result) { - d->followSymbolData.reset(); - return; - } - d->followSymbolData->cursorNode = *result; + if (result) + d->followSymbolData->cursorNode = *result; + else + d->followSymbolData->cursorNode.emplace(AstNode()); if (d->followSymbolData->defLink.hasValidTarget()) d->handleGotoDefinitionResult(); }); @@ -1229,8 +1233,8 @@ void ClangdClient::Private::handleGotoDefinitionResult() qCDebug(clangdLog) << "handling go to definition result"; // No dis-ambiguation necessary. Call back with the link and finish. - if (!followSymbolData->cursorNode.mightBeAmbiguousVirtualCall() - && !followSymbolData->cursorNode.isPureVirtualDeclaration()) { + if (!followSymbolData->cursorNode->mightBeAmbiguousVirtualCall() + && !followSymbolData->cursorNode->isPureVirtualDeclaration()) { followSymbolData->callback(followSymbolData->defLink); followSymbolData.reset(); return; From bddcf63da9b4c7a3f466c41fee7532d07171b08b Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Tue, 29 Jun 2021 13:46:59 +0200 Subject: [PATCH 054/149] Doc: Document Help > Contact command and dialog - Move All Topics to the top of the page - Create a list of ways to contact the team Task-number: QTCREATORBUG-25642 Change-Id: Ie18cd3ba397f8cf1b3101692713f411d03b25098 Reviewed-by: Johanna Vanhatapio Reviewed-by: Eike Ziller --- doc/qtcreator/src/qtcreator.qdoc | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/doc/qtcreator/src/qtcreator.qdoc b/doc/qtcreator/src/qtcreator.qdoc index 84ab84257d8..cb53dc7b6c7 100644 --- a/doc/qtcreator/src/qtcreator.qdoc +++ b/doc/qtcreator/src/qtcreator.qdoc @@ -52,6 +52,8 @@ see \l{Commercial Features}. \table + \row + \li {4,1} \b {\l{All Topics}} \row \li \inlineimage front-gs.png \li \inlineimage front-projects.png @@ -125,20 +127,20 @@ \li \l{Glossary} \endlist \row - \li {4,1} \l{All Topics} - \row - \li {4,1} \note To report bugs and suggestions to the - \l{https://bugreports.qt.io/}{Qt Project Bug Tracker}, - select \uicontrol Help > \uicontrol {Report Bug}. - To copy and paste detailed information about your system to the - bug report, select \uicontrol Help > - \uicontrol {System Information}. - - You can also join the \QC mailing list at: - \l{http://lists.qt-project.org/mailman/listinfo/} - {lists.qt-project.org Mailing Lists}. - - For credits and a list of third-party libraries, see - \l {Acknowledgements}. + \li {4,1} \b {Contact Us} + \list + \li To report bugs and suggestions to the + \l{https://bugreports.qt.io/}{Qt Project Bug Tracker}, + select \uicontrol Help > \uicontrol {Report Bug}. + \li To copy and paste detailed information about your + system to the bug report, select \uicontrol Help > + \uicontrol {System Information}. + \li To join the \l{https://lists.qt-project.org/listinfo/qt-creator} + {\QC mailing list} or \l{https://web.libera.chat/#qt-creator} + {#qt-creator} channel on Libera.Chat IRC, select + \uicontrol Help > \uicontrol Contact. + \li For credits and a list of third-party libraries, see + \l {Acknowledgements}. + \endlist \endtable */ From b8e6d05ded4571355911ec0c8022b8f766d9c294 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 29 Jun 2021 11:10:27 +0200 Subject: [PATCH 055/149] Utils: Also pass Sort flag to FilePath::dirEntries Change-Id: Ic21c2ba3ef59c6bb3e19552904187b3c6a096d80 Reviewed-by: Christian Stenger --- src/libs/utils/fileutils.cpp | 8 +++++--- src/libs/utils/fileutils.h | 7 +++++-- src/plugins/docker/dockerdevice.cpp | 13 +++++-------- src/plugins/docker/dockerdevice.h | 3 ++- .../projectexplorer/devicesupport/desktopdevice.cpp | 3 ++- .../projectexplorer/devicesupport/desktopdevice.h | 3 ++- .../projectexplorer/devicesupport/devicemanager.cpp | 6 +++--- .../projectexplorer/devicesupport/idevice.cpp | 4 +++- src/plugins/projectexplorer/devicesupport/idevice.h | 3 ++- 9 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index cc51b136971..296a4f398e3 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -952,14 +952,16 @@ bool FilePath::createDir() const return dir.mkpath(dir.absolutePath()); } -QList FilePath::dirEntries(const QStringList &nameFilters, QDir::Filters filters) const +QList FilePath::dirEntries(const QStringList &nameFilters, + QDir::Filters filters, + QDir::SortFlags sort) const { if (needsDevice()) { QTC_ASSERT(s_deviceHooks.dirEntries, return {}); - return s_deviceHooks.dirEntries(*this, nameFilters, filters); + return s_deviceHooks.dirEntries(*this, nameFilters, filters, sort); } - const QFileInfoList entryInfoList = QDir(toString()).entryInfoList(nameFilters, filters); + const QFileInfoList entryInfoList = QDir(m_data).entryInfoList(nameFilters, filters, sort); return Utils::transform(entryInfoList, &FilePath::fromFileInfo); } diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index 9ef2ae21279..585f2dcf2e7 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -83,7 +83,8 @@ public: std::function copyFile; std::function renameFile; std::function searchInPath; - std::function(const FilePath &, const QStringList &, QDir::Filters)> dirEntries; + std::function(const FilePath &, const QStringList &, + QDir::Filters, QDir::SortFlags)> dirEntries; std::function fileContents; std::function writeFileContents; std::function lastModified; @@ -141,7 +142,9 @@ public: bool isReadableFile() const; bool isReadableDir() const; bool createDir() const; - QList dirEntries(const QStringList &nameFilters, QDir::Filters filters) const; + QList dirEntries(const QStringList &nameFilters, + QDir::Filters filters, + QDir::SortFlags sort = QDir::NoSort) const; QList dirEntries(QDir::Filters filters) const; QByteArray fileContents(int maxSize = -1) const; bool writeFileContents(const QByteArray &data) const; diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 478f96e91d5..b81437458d5 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -995,17 +995,14 @@ FilePath DockerDevice::searchInPath(const FilePath &filePath) const QList DockerDevice::directoryEntries(const FilePath &filePath, const QStringList &nameFilters, - QDir::Filters filters) const + QDir::Filters filters, + QDir::SortFlags sort) const { QTC_ASSERT(handlesFile(filePath), return {}); tryCreateLocalFileAccess(); - if (hasLocalFileAccess()) { - const FilePath localAccess = mapToLocalAccess(filePath); - const QFileInfoList entryInfoList = QDir(localAccess.toString()).entryInfoList(nameFilters, filters); - return Utils::transform(entryInfoList, [this](const QFileInfo &fi) { - return mapFromLocalAccess(fi.absoluteFilePath()); - }); - } + if (hasLocalFileAccess()) + return mapToLocalAccess(filePath).dirEntries(nameFilters, filters, sort); + QTC_CHECK(false); // FIXME: Implement return {}; } diff --git a/src/plugins/docker/dockerdevice.h b/src/plugins/docker/dockerdevice.h index 6d302d1815d..760c5f72d5b 100644 --- a/src/plugins/docker/dockerdevice.h +++ b/src/plugins/docker/dockerdevice.h @@ -90,7 +90,8 @@ public: Utils::FilePath searchInPath(const Utils::FilePath &filePath) const override; QList directoryEntries(const Utils::FilePath &filePath, const QStringList &nameFilters, - QDir::Filters filters) const override; + QDir::Filters filters, + QDir::SortFlags sort) const override; QByteArray fileContents(const Utils::FilePath &filePath, int limit) const override; bool writeFileContents(const Utils::FilePath &filePath, const QByteArray &data) const override; QDateTime lastModified(const Utils::FilePath &filePath) const override; diff --git a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp index f63a29b60aa..a11095435c1 100644 --- a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp @@ -180,7 +180,8 @@ bool DesktopDevice::handlesFile(const FilePath &filePath) const QList DesktopDevice::directoryEntries(const FilePath &filePath, const QStringList &nameFilters, - QDir::Filters filters) const + QDir::Filters filters, + QDir::SortFlags sort) const { QTC_CHECK(!filePath.needsDevice()); const QDir dir(filePath.path()); diff --git a/src/plugins/projectexplorer/devicesupport/desktopdevice.h b/src/plugins/projectexplorer/devicesupport/desktopdevice.h index 0a8622260c7..9c1aac3af1f 100644 --- a/src/plugins/projectexplorer/devicesupport/desktopdevice.h +++ b/src/plugins/projectexplorer/devicesupport/desktopdevice.h @@ -57,7 +57,8 @@ public: bool handlesFile(const Utils::FilePath &filePath) const override; QList directoryEntries(const Utils::FilePath &filePath, const QStringList &nameFilters, - QDir::Filters filters) const override; + QDir::Filters filters, + QDir::SortFlags sort) const override; Utils::Environment systemEnvironment() const override; diff --git a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp index 83eecf813a0..5b497f8eb57 100644 --- a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp @@ -459,11 +459,11 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_uniquesearchInPath(filePath); }; - deviceHooks.dirEntries = [](const FilePath &filePath, - const QStringList &nameFilters, QDir::Filters filters) { + deviceHooks.dirEntries = [](const FilePath &filePath, const QStringList &nameFilters, + QDir::Filters filters, QDir::SortFlags sort) { auto device = DeviceManager::deviceForPath(filePath); QTC_ASSERT(device, return FilePaths()); - return device->directoryEntries(filePath, nameFilters, filters); + return device->directoryEntries(filePath, nameFilters, filters, sort); }; deviceHooks.fileContents = [](const FilePath &filePath, int maxSize) { diff --git a/src/plugins/projectexplorer/devicesupport/idevice.cpp b/src/plugins/projectexplorer/devicesupport/idevice.cpp index 3adb3f2d305..a8c1da91f87 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/idevice.cpp @@ -318,11 +318,13 @@ FilePath IDevice::searchInPath(const FilePath &filePath) const QList IDevice::directoryEntries(const FilePath &filePath, const QStringList &nameFilters, - QDir::Filters filters) const + QDir::Filters filters, + QDir::SortFlags sort) const { Q_UNUSED(filePath); Q_UNUSED(nameFilters); Q_UNUSED(filters); + Q_UNUSED(sort); QTC_CHECK(false); return {}; } diff --git a/src/plugins/projectexplorer/devicesupport/idevice.h b/src/plugins/projectexplorer/devicesupport/idevice.h index 352ed64c67a..5cb097e13f1 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.h +++ b/src/plugins/projectexplorer/devicesupport/idevice.h @@ -252,7 +252,8 @@ public: virtual Utils::FilePath searchInPath(const Utils::FilePath &filePath) const; virtual QList directoryEntries(const Utils::FilePath &filePath, const QStringList &nameFilters, - QDir::Filters filters) const; + QDir::Filters filters, + QDir::SortFlags sort = QDir::NoSort) const; virtual QByteArray fileContents(const Utils::FilePath &filePath, int limit) const; virtual bool writeFileContents(const Utils::FilePath &filePath, const QByteArray &data) const; virtual QDateTime lastModified(const Utils::FilePath &filePath) const; From d8bd855c021bbfbe6c3605237c1d70d653faf547 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 29 Jun 2021 14:36:23 +0200 Subject: [PATCH 056/149] CMake: Use FilePath::dirEntries file api parser Change-Id: Id83284518029414721cf1a44111d8f63cae9246f Reviewed-by: Christian Stenger --- src/plugins/cmakeprojectmanager/fileapiparser.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/fileapiparser.cpp b/src/plugins/cmakeprojectmanager/fileapiparser.cpp index 155813e6ff0..bf4c3c692a5 100644 --- a/src/plugins/cmakeprojectmanager/fileapiparser.cpp +++ b/src/plugins/cmakeprojectmanager/fileapiparser.cpp @@ -948,16 +948,12 @@ FileApiData FileApiParser::parseData(QFutureInterface Date: Tue, 29 Jun 2021 15:21:06 +0200 Subject: [PATCH 057/149] Designer: Fix "Go to slot" Amends 8dc4cb17c1, where we forgot to actually open the file in case the slot already exists. Fixes: QTCREATORBUG-25843 Change-Id: Idd9395f5bf72418043f917fd70bc3d6569b4f2fd Reviewed-by: Christian Stenger --- src/plugins/designer/qtcreatorintegration.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/designer/qtcreatorintegration.cpp b/src/plugins/designer/qtcreatorintegration.cpp index c11756674c1..54603775543 100644 --- a/src/plugins/designer/qtcreatorintegration.cpp +++ b/src/plugins/designer/qtcreatorintegration.cpp @@ -550,8 +550,11 @@ bool QtCreatorIntegration::navigateToSlot(const QString &objectName, CppTools::CppRefactoringChanges refactoring(docTable); CppTools::SymbolFinder symbolFinder; - if (symbolFinder.findMatchingDefinition(fun, docTable, true)) + if (const Function *funImpl = symbolFinder.findMatchingDefinition(fun, docTable, true)) { + Core::EditorManager::openEditorAt(QString::fromUtf8(funImpl->fileName()), + funImpl->line() + 2); return true; + } const QString implFilePath = CppTools::correspondingHeaderOrSource(declFilePath); const CppTools::InsertionLocation location = CppTools::insertLocationForMethodDefinition (fun, false, CppTools::NamespaceHandling::CreateMissing, refactoring, implFilePath); From 1afe21d41e7f2adde24d524b7c3e51f1921d3faf Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 29 Jun 2021 12:12:59 +0200 Subject: [PATCH 058/149] Docker: Make mounted volumes user-configurable Still use the so-far hardcoded "/opt" and "/data" as default, i.e. not the intended final setup which should at least pick up the source and build dirs automatically. Change-Id: If632f3f5a6e824a952938c97336df8ff58b39d67 Reviewed-by: Christian Stenger --- src/plugins/docker/dockerdevice.cpp | 43 ++++++++++++++++++++--------- src/plugins/docker/dockerdevice.h | 2 ++ 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index b81437458d5..c7a4efcb868 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -239,10 +239,6 @@ class DockerDevicePrivate : public QObject public: DockerDevicePrivate(DockerDevice *parent) : q(parent) { - // FIXME: Make mounts flexible - m_mounts.append("/opt"); - m_mounts.append("/data"); - connect(&m_mergedDirWatcher, &QFileSystemWatcher::fileChanged, this, [](const QString &path) { Q_UNUSED(path) LOG("Container watcher change, file: " << path); @@ -276,7 +272,6 @@ public: QPointer m_shell; QString m_container; QString m_mergedDir; - QStringList m_mounts; QFileSystemWatcher m_mergedDirWatcher; Environment m_cachedEnviroment; @@ -293,12 +288,12 @@ public: auto dockerDevice = device.dynamicCast(); QTC_ASSERT(dockerDevice, return); - m_idLabel = new QLabel(tr("Image Id:")); + auto idLabel = new QLabel(tr("Image Id:")); m_idLineEdit = new QLineEdit; m_idLineEdit->setText(dockerDevice->data().imageId); m_idLineEdit->setEnabled(false); - m_repoLabel = new QLabel(tr("Repository:")); + auto repoLabel = new QLabel(tr("Repository:")); m_repoLineEdit = new QLineEdit; m_repoLineEdit->setText(dockerDevice->data().repo); m_repoLineEdit->setEnabled(false); @@ -309,6 +304,20 @@ public: m_runAsOutsideUser->setChecked(dockerDevice->data().useLocalUidGid); m_runAsOutsideUser->setEnabled(HostOsInfo::isLinuxHost()); + connect(m_runAsOutsideUser, &QCheckBox::toggled, this, [this, dockerDevice](bool on) { + dockerDevice->data().useLocalUidGid = on; + }); + + m_pathsLineEdit = new QLineEdit; + m_pathsLineEdit->setText(dockerDevice->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(dockerDevice->data().mounts.join(';')); + + connect(m_pathsLineEdit, &QLineEdit::textChanged, this, [this, dockerDevice](const QString &text) { + dockerDevice->data().mounts = text.split(';'); + }); + auto logView = new QTextBrowser; auto autoDetectButton = new QPushButton(tr("Auto-detect Kit Items")); @@ -327,9 +336,10 @@ public: using namespace Layouting; Form { - m_idLabel, m_idLineEdit, Break(), - m_repoLabel, m_repoLineEdit, Break(), + idLabel, m_idLineEdit, Break(), + repoLabel, m_repoLineEdit, Break(), m_runAsOutsideUser, Break(), + tr("Paths to mount:"), m_pathsLineEdit, Break(), Column { Space(20), Row { autoDetectButton, undoAutoDetectButton, Stretch() }, @@ -342,11 +352,10 @@ public: void updateDeviceFromUi() final {} private: - QLabel *m_idLabel; QLineEdit *m_idLineEdit; - QLabel *m_repoLabel; QLineEdit *m_repoLineEdit; QCheckBox *m_runAsOutsideUser; + QLineEdit *m_pathsLineEdit; }; IDeviceWidget *DockerDevice::createWidget() @@ -410,6 +419,11 @@ const DockerDeviceData &DockerDevice::data() const return d->m_data; } +DockerDeviceData &DockerDevice::data() +{ + return d->m_data; +} + void DockerDevicePrivate::undoAutoDetect(QTextBrowser *log) const { const QString id = q->id().toString(); @@ -597,7 +611,7 @@ void DockerDevicePrivate::tryCreateLocalFileAccess() dockerRun.addArgs({"-u", QString("%1:%2").arg(getuid()).arg(getgid())}); #endif - for (const QString &mount : qAsConst(m_mounts)) + for (const QString &mount : qAsConst(m_data.mounts)) dockerRun.addArgs({"-v", mount + ':' + mount}); dockerRun.addArg(m_data.imageId); @@ -661,7 +675,7 @@ FilePath DockerDevice::mapToLocalAccess(const FilePath &filePath) const { QTC_ASSERT(!d->m_mergedDir.isEmpty(), return {}); QString path = filePath.path(); - for (const QString &mount : qAsConst(d->m_mounts)) { + for (const QString &mount : qAsConst(d->m_data.mounts)) { if (path.startsWith(mount + '/')) return FilePath::fromString(path); } @@ -688,6 +702,7 @@ const char DockerDeviceDataRepoKey[] = "DockerDeviceDataRepo"; const char DockerDeviceDataTagKey[] = "DockerDeviceDataTag"; const char DockerDeviceDataSizeKey[] = "DockerDeviceDataSize"; const char DockerDeviceUseOutsideUser[] = "DockerDeviceUseUidGid"; +const char DockerDeviceMappedPaths[] = "DockerDeviceMappedPaths"; void DockerDevice::fromMap(const QVariantMap &map) { @@ -698,6 +713,7 @@ void DockerDevice::fromMap(const QVariantMap &map) d->m_data.size = map.value(DockerDeviceDataSizeKey).toString(); d->m_data.useLocalUidGid = map.value(DockerDeviceUseOutsideUser, HostOsInfo::isLinuxHost()).toBool(); + d->m_data.mounts = map.value(DockerDeviceMappedPaths).toStringList(); } QVariantMap DockerDevice::toMap() const @@ -708,6 +724,7 @@ QVariantMap DockerDevice::toMap() const map.insert(DockerDeviceDataTagKey, d->m_data.tag); map.insert(DockerDeviceDataSizeKey, d->m_data.size); map.insert(DockerDeviceUseOutsideUser, d->m_data.useLocalUidGid); + map.insert(DockerDeviceMappedPaths, d->m_data.mounts); return map; } diff --git a/src/plugins/docker/dockerdevice.h b/src/plugins/docker/dockerdevice.h index 760c5f72d5b..05d42199f9f 100644 --- a/src/plugins/docker/dockerdevice.h +++ b/src/plugins/docker/dockerdevice.h @@ -44,6 +44,7 @@ public: QString tag; QString size; bool useLocalUidGid = true; + QStringList mounts = {"/opt", "/data"}; }; class DockerDevice : public ProjectExplorer::IDevice @@ -100,6 +101,7 @@ public: Utils::Environment systemEnvironment() const override; const DockerDeviceData &data() const; + DockerDeviceData &data(); void tryCreateLocalFileAccess() const; bool hasLocalFileAccess() const; From eb7b5ea34e48c2f961380c6a8d9dc3b71fa1d0b8 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 29 Jun 2021 13:19:01 +0200 Subject: [PATCH 059/149] Docker: Improve logging a bit Most logged items span more than one line, so be generous with extra newlines. Also, complain only about when host-local read access into the docker device ist not possible, writing is generally done only in the separately mounted places after re-mapping to host-local paths in the host file system. Change-Id: I5525749dfb880d4dc3547f86de43314567e92b4d Reviewed-by: Christian Stenger --- src/plugins/docker/dockerdevice.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index c7a4efcb868..5b5de554d67 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -84,7 +84,7 @@ namespace Docker { namespace Internal { static Q_LOGGING_CATEGORY(dockerDeviceLog, "qtc.docker.device", QtWarningMsg); -#define LOG(x) qCDebug(dockerDeviceLog) << x +#define LOG(x) qCDebug(dockerDeviceLog) << x << '\n' class DockerDeviceProcess : public ProjectExplorer::DeviceProcess { @@ -620,6 +620,16 @@ void DockerDevicePrivate::tryCreateLocalFileAccess() LOG("RUNNING: " << dockerRun.toUserOutput()); m_shell = new QtcProcess; m_shell->setCommand(dockerRun); + connect(m_shell, &QtcProcess::finished, this, [this] { + LOG("\nSHELL FINISHED\n"); + if (m_shell) { + LOG("RES: " << m_shell->result() + << " STDOUT: " << m_shell->readAllStandardOutput() + << " STDERR: " << m_shell->readAllStandardError()); + } + m_container.clear(); + }); + m_shell->start(); m_shell->waitForStarted(); @@ -647,20 +657,21 @@ void DockerDevicePrivate::tryCreateLocalFileAccess() QtcProcess proc; proc.setCommand({"docker", {"inspect", "--format={{.GraphDriver.Data.MergedDir}}", m_container}}); - //LOG(proc2.commandLine().toUserOutput()); + LOG(proc.commandLine().toUserOutput()); proc.start(); proc.waitForFinished(); const QString out = proc.stdOut(); m_mergedDir = out.trimmed(); + LOG("Found merged dir: " << m_mergedDir); if (m_mergedDir.endsWith('/')) m_mergedDir.chop(1); - if (!QFileInfo(m_mergedDir).isWritable()) { + if (!QFileInfo(m_mergedDir).isReadable()) { MessageManager::writeFlashing( - tr("Local write access to Docker container %1 unavailable through directory \"%2\".") + tr("Local read access to Docker container %1 unavailable through directory \"%2\".") .arg(m_container, m_mergedDir) - + '\n' + tr("Output: %1").arg(out) - + '\n' + tr("Error: %1").arg(proc.stdErr())); + + '\n' + tr("Output: '%1'").arg(out) + + '\n' + tr("Error: '%1'").arg(proc.stdErr())); } m_mergedDirWatcher.addPath(m_mergedDir); From a43c91b16ddbbf1a099a2571d13a93a209c7f5db Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Tue, 29 Jun 2021 13:29:22 +0200 Subject: [PATCH 060/149] Doc: Document locator filters for version control systems Only Git was mentioned this far. Only Git is supported by QDS, so use conditional text. Task-number: QTCREATORBUG-25642 Change-Id: If2c27bd5def8c4e4bee30558174127024458449a Reviewed-by: Eike Ziller Reviewed-by: Orgad Shaneh --- doc/qtcreator/src/editors/creator-locator.qdoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/qtcreator/src/editors/creator-locator.qdoc b/doc/qtcreator/src/editors/creator-locator.qdoc index cfd44c19de5..cae350b2d58 100644 --- a/doc/qtcreator/src/editors/creator-locator.qdoc +++ b/doc/qtcreator/src/editors/creator-locator.qdoc @@ -139,11 +139,12 @@ \li Executing shell commands (\c {!}) - \li Executing version control system commands (\c {git}). + \li Executing version control system commands \if defined(qtcreator) - For more information, see \l{Using Version Control Systems} + (\c {bzr}, \c {cvs}, \c {git}, \c {hg}, or \c {svn}). + For more information, see \l{Using Version Control Systems}. \else - For more information, see \l{Using Git} + (\c {git}). For more information, see \l{Using Git}. \endif \li Triggering menu items from the main menu (\c {t}) From 89ab11d53978b3693999d437d1baf08835f08dcb Mon Sep 17 00:00:00 2001 From: hjk Date: Mon, 26 Apr 2021 17:12:49 +0200 Subject: [PATCH 061/149] ProjectExplorer: Short-circuit ProcessParameters::effectiveCommand ... in case of remote commands. This is enough for docker related searches for now, on the other hand, Environment::searchInPath is not yet generic enough to cope with remote searches. Add a FIXME so this is not forgotten. Change-Id: I97f3c75069a17256c83dc9f8a73ae1c1c235465f Reviewed-by: Christian Kandeler --- src/plugins/projectexplorer/processparameters.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/plugins/projectexplorer/processparameters.cpp b/src/plugins/projectexplorer/processparameters.cpp index a91f01264f6..7a08b1e7e5d 100644 --- a/src/plugins/projectexplorer/processparameters.cpp +++ b/src/plugins/projectexplorer/processparameters.cpp @@ -119,9 +119,13 @@ FilePath ProcessParameters::effectiveCommand() const FilePath cmd = m_command.executable(); if (m_macroExpander) cmd = m_macroExpander->expand(cmd); - m_effectiveCommand = - m_environment.searchInPath(cmd.toString(), - {effectiveWorkingDirectory()}); + if (cmd.needsDevice()) { + // Assume this is already good. FIXME: It is possibly not, so better fix searchInPath. + m_effectiveCommand = cmd; + } else { + m_effectiveCommand = m_environment.searchInPath(cmd.toString(), + {effectiveWorkingDirectory()}); + } m_commandMissing = m_effectiveCommand.isEmpty(); if (m_commandMissing) m_effectiveCommand = cmd; From 64d274454e6494bc498b7b8ec95facb88496e31c Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Tue, 29 Jun 2021 16:10:01 +0300 Subject: [PATCH 062/149] QmlPuppet: Fix imported 3D asset library icon generation Recent changes to custom geometry handling now require that the zoom process in icon creation does each focus step asynchronously, so that selection box geometry has a chance to update between frames. Fixes: QDS-4652 Change-Id: If92bf580a556a68c10d3af1406c2eabef530254a Reviewed-by: Mahmoud Badri --- .../qml2puppet/iconrenderer/iconrenderer.cpp | 40 +++++++++++++------ .../qml2puppet/iconrenderer/iconrenderer.h | 5 ++- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.cpp index 82af9150d35..2dd653273cd 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.cpp @@ -135,7 +135,7 @@ void IconRenderer::setupRender() resizeContent(m_size); if (!initRhi()) QTimer::singleShot(0, qGuiApp, &QGuiApplication::quit); - QTimer::singleShot(0, this, &IconRenderer::createIcon); + QTimer::singleShot(0, this, &IconRenderer::startCreateIcon); } else { QTimer::singleShot(0, qGuiApp, &QGuiApplication::quit); } @@ -144,25 +144,41 @@ void IconRenderer::setupRender() } } -void IconRenderer::createIcon() +void IconRenderer::startCreateIcon() { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) m_designerSupport.refFromEffectItem(m_containerItem, false); #endif QQuickDesignerSupportItems::disableNativeTextRendering(m_containerItem); + + if (m_is3D) + QTimer::singleShot(0, this, &IconRenderer::focusCamera); + else + QTimer::singleShot(0, this, &IconRenderer::finishCreateIcon); +} + +void IconRenderer::focusCamera() +{ #ifdef QUICK3D_MODULE - if (m_is3D) { - // Render once to make sure scene is up to date before we set up the selection box - render({}); - QMetaObject::invokeMethod(m_containerItem, "setSceneToBox"); - int tries = 0; - while (tries < 10) { - ++tries; - render({}); - QMetaObject::invokeMethod(m_containerItem, "fitAndHideBox"); - } + if (m_focusStep >= 10) { + QTimer::singleShot(0, this, &IconRenderer::finishCreateIcon); + return; } + + render({}); + + if (m_focusStep == 0) { + QMetaObject::invokeMethod(m_containerItem, "setSceneToBox"); + } else if (m_focusStep > 1 && m_focusStep < 10) { + QMetaObject::invokeMethod(m_containerItem, "fitAndHideBox"); + } + ++m_focusStep; + QTimer::singleShot(0, this, &IconRenderer::focusCamera); #endif +} + +void IconRenderer::finishCreateIcon() +{ QFileInfo fi(m_filePath); // Render regular size image diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.h index 6f5e36720d0..64880249305 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/iconrenderer/iconrenderer.h @@ -53,7 +53,9 @@ public: void setupRender(); private: - void createIcon(); + void startCreateIcon(); + void focusCamera(); + void finishCreateIcon(); void render(const QString &fileName); void resizeContent(int dimensions); bool initRhi(); @@ -66,6 +68,7 @@ private: QQuickItem *m_containerItem = nullptr; DesignerSupport m_designerSupport; bool m_is3D = false; + int m_focusStep = 0; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QQuickRenderControl *m_renderControl = nullptr; QRhi *m_rhi = nullptr; From 459b67a77d5f5ad65aa6b4011f6f061605543a64 Mon Sep 17 00:00:00 2001 From: Henning Gruendl Date: Tue, 29 Jun 2021 15:55:44 +0200 Subject: [PATCH 063/149] QmlDesigner: UX improvement in property editor Disable certain geometry property controls (x, y, width, height) in the property editor when an item is controlled by a layout or anchors. Task-number: QDS-3209 Change-Id: Ib8e5dba17c57d67464052321e0323c9fc21539a5 Reviewed-by: Brook Cronin Reviewed-by: Tim Jenssen --- .../QtQuick/GeometrySection.qml | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/GeometrySection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/GeometrySection.qml index 8021e9c0e6a..c4bbef3ba9c 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/GeometrySection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/GeometrySection.qml @@ -30,42 +30,83 @@ import StudioControls 1.0 as StudioControls import StudioTheme 1.0 as StudioTheme Section { + id: root caption: qsTr("Geometry - 2d") anchors.left: parent.left anchors.right: parent.right + readonly property string disbaledTooltip: qsTr("This property is defined by an anchor or a layout.") + + function positionDisabled() { + return anchorBackend.isFilled || anchorBackend.isInLayout + } + + function xDisabled() { + return anchorBackend.leftAnchored + || anchorBackend.rightAnchored + || anchorBackend.horizontalCentered + } + + function yDisabled() { + return anchorBackend.topAnchored + || anchorBackend.bottomAnchored + || anchorBackend.verticalCentered + } + + function sizeDisabled() { + return anchorBackend.isFilled + } + + function widthDisabled() { + return anchorBackend.leftAnchored && anchorBackend.rightAnchored + } + + function heightDisabled() { + return anchorBackend.topAnchored && anchorBackend.bottomAnchored + } + SectionLayout { PropertyLabel { text: qsTr("Position") } SecondColumnLayout { SpinBox { + id: xSpinBox implicitWidth: StudioTheme.Values.twoControlColumnWidth + StudioTheme.Values.actionIndicatorWidth backendValue: backendValues.x maximumValue: 0xffff minimumValue: -0xffff decimals: 0 + enabled: !root.positionDisabled() && !root.xDisabled() } Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } - ControlLabel { text: "X" } + ControlLabel { + text: "X" + tooltip: xSpinBox.enabled ? "X" : root.disbaledTooltip + } Spacer { implicitWidth: StudioTheme.Values.controlGap } SpinBox { + id: ySpinBox implicitWidth: StudioTheme.Values.twoControlColumnWidth + StudioTheme.Values.actionIndicatorWidth backendValue: backendValues.y maximumValue: 0xffff minimumValue: -0xffff decimals: 0 + enabled: !root.positionDisabled() && !root.yDisabled() } Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } - ControlLabel { text: "Y" } + ControlLabel { + text: "Y" + tooltip: xSpinBox.enabled ? "Y" : root.disbaledTooltip + } Spacer { implicitWidth: StudioTheme.Values.controlGap } @@ -78,32 +119,42 @@ Section { SecondColumnLayout { SpinBox { + id: widthSpinBox implicitWidth: StudioTheme.Values.twoControlColumnWidth + StudioTheme.Values.actionIndicatorWidth backendValue: backendValues.width maximumValue: 0xffff minimumValue: 0 decimals: 0 + enabled: !root.sizeDisabled() && !root.widthDisabled() } Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } - ControlLabel { text: qsTr("W") } + ControlLabel { + text: qsTr("W") + tooltip: widthSpinBox.enabled ? qsTr("Width") : root.disbaledTooltip + } Spacer { implicitWidth: StudioTheme.Values.controlGap } SpinBox { + id: heightSpinBox implicitWidth: StudioTheme.Values.twoControlColumnWidth + StudioTheme.Values.actionIndicatorWidth backendValue: backendValues.height maximumValue: 0xffff minimumValue: 0 decimals: 0 + enabled: !root.sizeDisabled() && !root.heightDisabled() } Spacer { implicitWidth: StudioTheme.Values.controlLabelGap } - ControlLabel { text: qsTr("H") } + ControlLabel { + text: qsTr("H") + tooltip: heightSpinBox.enabled ? qsTr("Height") : root.disbaledTooltip + } Spacer { implicitWidth: StudioTheme.Values.controlGap } From 8e1032b83c5a09de7e487756f044fdba3404b73f Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Tue, 29 Jun 2021 14:26:20 +0200 Subject: [PATCH 064/149] Docker: Do not use hard-coded /tmp Amends 8c7b6a7549d1. Change-Id: Ie702c7ccc5ac897920f1b8eda00dd90378a2d80b Reviewed-by: hjk --- src/plugins/docker/dockerdevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 5b5de554d67..01c8bca1dac 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -1118,7 +1118,7 @@ int DockerDevicePrivate::runSynchronously(const CommandLine &cmd) const QtcProcess proc; proc.setCommand(dcmd); - proc.setWorkingDirectory("/tmp"); + proc.setWorkingDirectory(QDir::tempPath()); proc.start(); proc.waitForFinished(); From e12783326b17a2527f1e1bba54b816687bf40d04 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Thu, 17 Jun 2021 14:09:23 +0200 Subject: [PATCH 065/149] Doc: Update info about easing curves The Easing Curve editor can be used to attach easing curves to transitions and animation components, in addition to keyframes in timeline animations. Change-Id: I5b3ae0c1818516d02e6cfacf0c87e2e4f4926f08 Reviewed-by: Knud Dollereder Reviewed-by: Vikas Pachdha --- .../qtquick/qtquick-easing-curve-editor.qdoc | 75 +++++++++++++++---- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/doc/qtcreator/src/qtquick/qtquick-easing-curve-editor.qdoc b/doc/qtcreator/src/qtquick/qtquick-easing-curve-editor.qdoc index 04e03ac5a0b..f277a9f615c 100644 --- a/doc/qtcreator/src/qtquick/qtquick-easing-curve-editor.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-easing-curve-editor.qdoc @@ -26,14 +26,28 @@ /*! \page qtquick-editing-easing-curves.html \previouspage studio-timeline.html - \nextpage qmldesigner-connections.html + \nextpage qtquick-production-quality-animation.html \title Editing Easing Curves - You can use \uicontrol {Easing Curve Editor} to edit the easing curve - between two keyframes. + Easing curves define the progress of animation to make motion appear more + natural because real objects don't usually move at a constant speed nor + start and stop instantly. You can add and edit easing curves for nonlinear + interpolation to make components appear to pick up speed, slow down, or + bounce back at the end of an animation. - \image studio-easing-curve-editor.png "Easing Curve Editor" + You can attach easing curves to: + + \list + \li Keyframes in timeline animations + \li Transitions + \li Property animation component instances + \endlist + + You can use \uicontrol {Easing Curve Editor} to select and edit easing + curves. + + \section1 Selecting Easing Curves You can use the preset curves or modify them by dragging the curve handles around. You can add points to the curve and drag them and the point handles @@ -41,13 +55,28 @@ a custom curve. For more information about easing curve types, see the technical documentation for \l [QML] {PropertyAnimation}{easing curves}. + \image studio-easing-curve-editor.png "Easing Curve Editor" + + To select an easing curve: + + \list 1 + \li Select an easing curve in the \uicontrol Presets tab. + \li In the \uicontrol {Duration (ms)} field, select the + duration of the easing function in milliseconds. + \li Select \uicontrol Preview to preview the curve. + \li Select \uicontrol OK to attach the easing curve and + return to the view where you are adding the curve. + \endlist + To zoom into and out of the easing curve editor, use the mouse roller. To reset the zoom factor, right-click in the picker and select \uicontrol {Reset Zoom}. - You can also use the more advanced \l {Curve Editor} that - shows the interpolated values of an animated property over - the \l{Creating Animations}{animation} range. + \section1 Easing Curves in Timeline Animations + + For timeline animations, you can also use the more advanced + \l {Curve Editor} that shows the interpolated values of an animated + property over the \l{Creating Animations}{animation} range. The animation curves present a more readable view of the animation by showing the effective values of the animated properties over the animation @@ -61,20 +90,14 @@ and are therefore painted in a different color and without handles. \endlist - \section1 Attaching Easing Curves to Keyframes + \section2 Attaching Easing Curves to Keyframes To attach easing curves to keyframes: \list 1 \li Right-click a keyframe in \l Timeline and select \uicontrol {Easing Curve Editor} in the context menu. - \li Select an easing curve in the \uicontrol Presets tab. - \li In the \uicontrol {Duration (ms)} field, select the - duration of the easing function in milliseconds. - \li Select \uicontrol Preview to preview the curve. - \li Select \uicontrol OK to attach the easing curve to the - keyframe, close \uicontrol {Easing Curve Editor}, and - return to \l Timeline. + \li Select an easing curve, as described in \l{Selecting Easing Curves}. \endlist When you attach easing curves to keyframes, the shape of the @@ -82,6 +105,28 @@ \l Timeline changes from \inlineimage keyframe_linear_inactive.png to a marker that describes the type of the selected easing curve. + \section1 Attaching Easing Curves to Transitions + + To attach easing curves to transitions: + + \list 1 + \li In \l{Transition Editor}, select the \inlineimage curve_editor.png + (\uicontrol {Easing Curve Editor}) button. + \li Select an easing curve, as described in \l{Selecting Easing Curves}. + \endlist + + \section1 Attaching Easing Curves to Property Animations + + To attach easing curves to property animations: + + \list 1 + \li In \l Navigator, select an \l{Animations}{Animation} component + instance. + \li In \l Properties, select the \inlineimage curve_editor.png + (\uicontrol {Easing Curve Editor}) button. + \li Select an easing curve, as described in \l{Selecting Easing Curves}. + \endlist + \section1 Customizing Easing Curves To customize easing curves: From 279bf1e1f38070933abe0cc9bb667a25daec0e5c Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Thu, 17 Jun 2021 14:21:27 +0200 Subject: [PATCH 066/149] Doc: Describe optimizing animations Fixes: QDS-4502 Change-Id: I3b7c2a64384d00cd55a75a7c4df4e4c3e49471e9 Reviewed-by: Vikas Pachdha --- .../qtquick/qtquick-optimizing-designs.qdoc | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 doc/qtcreator/src/qtquick/qtquick-optimizing-designs.qdoc diff --git a/doc/qtcreator/src/qtquick/qtquick-optimizing-designs.qdoc b/doc/qtcreator/src/qtquick/qtquick-optimizing-designs.qdoc new file mode 100644 index 00000000000..0f6c31fe84d --- /dev/null +++ b/doc/qtcreator/src/qtquick/qtquick-optimizing-designs.qdoc @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Creator documentation. +** +** 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 Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** +****************************************************************************/ + +/*! + \page qtquick-optimizing-designs.html + \previouspage qtquick-production-quality-animation.html + \nextpage studio-optimized-3d-scenes.html + + \title Optimizing Designs + + You can test your UIs on the target devices to make sure you get the best + performance out of your animations. To solve performance problems, you + typically need to optimize the graphical assets used in the UI, such as + images, effects, or 3D scenes. + + How to optimize UIs for different target devices: + + \list + \li Minimize image size + \li Use transparency sparingly + \endlist + + For more useful information for application developers, see + \l {Performance Considerations And Suggestions}. + + For more information about optimizing 3D scenes, see + \l{Creating Optimized 3D Scenes}. + + \section1 Minimizing Image Size + + Images are a vital part of any user interface. Unfortunately, they are also + a big source of problems due to the time it takes to load them, the amount + of memory they consume, and the way in which they are used. + + We recommend that you make image size as small as possible without + negatively affecting image quality. + + For more information about how to use images efficiently in your UI, see + \l{Images}. + + \section1 Avoid Transparency + + Opaque content is generally a lot faster to draw than transparent because + the latter needs blending and the renderer can potentially optimize opaque + content better. + + An image with one transparent pixel is treated as fully transparent, even + though it is mostly opaque. The same is true for a \l {Border Image} with + transparent edges. +*/ From 2de96b84e140a12bc2842dbca6e075d959e01756 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Thu, 17 Jun 2021 14:40:42 +0200 Subject: [PATCH 067/149] Doc: Describe fine-tuning animation quality Fixes: QDS-4500 Change-Id: Ibb3d203210898a5c0fbdee67e30f7e148a4b1e22 Reviewed-by: Vikas Pachdha --- .../qtquick-production-quality-animation.qdoc | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 doc/qtcreator/src/qtquick/qtquick-production-quality-animation.qdoc diff --git a/doc/qtcreator/src/qtquick/qtquick-production-quality-animation.qdoc b/doc/qtcreator/src/qtquick/qtquick-production-quality-animation.qdoc new file mode 100644 index 00000000000..bfdf435cf6e --- /dev/null +++ b/doc/qtcreator/src/qtquick/qtquick-production-quality-animation.qdoc @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Creator documentation. +** +** 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 Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** +****************************************************************************/ + +/*! + \page qtquick-production-quality-animation.html + \previouspage qtquick-editing-easing-curves.html + \nextpage qtquick-optimizing-designs.html + + \title Production Quality + + After the wireframing and prototyping phases, you can use previewing and + profiling tools to fine-tune your UI for production. + + How to achieve production quality motion in UIs: + + \list + \li Preview the UI to check the FPS refresh rate. + \li Profile the UI code to find causes for slowness, unresponsiveness, + and stuttering. + \endlist + + \section1 FPS Refresh Rate + + As a general rule, animators strive to allow the rendering engine to + achieve a consistent 60 frames-per-second (FPS) refresh rate. 60 FPS + means that there is approximately 16 milliseconds between each frame + in which processing can be done, which includes the processing required + to upload the draw primitives to the graphics hardware. + + The frames-per-second (FPS) refresh rate of animations is displayed in the + \uicontrol FPS field on the \l{Summary of Main Toolbar Actions}{toolbar} + in the \uicontrol Design mode. + + To improve the FPS rate, application developers should: + + \list + \li Use asynchronous, event-driven programming wherever possible. + \li Use worker threads to do significant processing. + \li Never manually spin the event loop. + \li Never spend more than a couple of milliseconds per frame within + blocking functions to avoid skipped frames, which negatively + affect the user experience. + \endlist + + For more information about previewing UIs on devices, see + \l{Validating with Target Hardware}. + + \section1 Profiling UI Code + + You can use \l{Profiling QML Applications}{QML Profiler} that is integrated + into \QC to find causes for typical performance problems in your UI. For + example, your UI might be slow, unresponsive, or stuttering. Typically, such + problems are caused by executing too much JavaScript in too few frames. All + JavaScript must return before the GUI thread can proceed, and frames are + delayed or dropped if the GUI thread is not ready. + + In general, knowing where time is spent in a UI enables you to focus on + problem areas that actually exist, rather than problem areas that + potentially exist. + + Determining which bindings are being run the most often or which functions + your application is spending the most time on enables you to decide whether + you need to optimize the problem areas, or redesign some implementation + details of your application so that the performance is improved. Attempting + to optimize code without profiling is likely to result in very minor rather + than significant performance improvements. + + For more information, see \l{Profiling QML Applications}. +*/ From 525219e84857dc07ae8351ca0dc9dd957bcd0568 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Thu, 17 Jun 2021 14:46:46 +0200 Subject: [PATCH 068/149] Doc: Update contents of Motion Design chapter Add information about new topics in the chapter. Add animation. Change-Id: Idce4c1cad796d8df011afc16413126dba6f2583c Reviewed-by: Johanna Vanhatapio Reviewed-by: Vikas Pachdha --- .../images/timeline-rotation-animation.gif | Bin 0 -> 255054 bytes doc/qtcreator/src/qtcreator-toc.qdoc | 10 +++-- .../library/qtquick-component-instances.qdoc | 5 ++- .../qtdesignstudio-optimized-3d-scenes.qdoc | 8 +++- .../qtquick/qtquick-animation-overview.qdoc | 3 ++ .../src/qtquick/qtquick-designer.qdoc | 2 +- .../qtquick/qtquick-easing-curve-editor.qdoc | 2 +- .../src/qtquick/qtquick-motion-design.qdoc | 36 ++++++++++++------ .../src/qtquick/qtquick-properties.qdoc | 2 +- .../src/qtquick/qtquick-timeline-view.qdoc | 2 +- .../src/qtquick/qtquick-timeline.qdoc | 9 +++-- .../examples/doc/coffeemachine.qdoc | 3 +- .../examples/doc/ebikedesign.qdoc | 3 +- doc/qtdesignstudio/examples/doc/loginui4.qdoc | 13 ++++--- .../examples/doc/progressbar.qdoc | 3 +- doc/qtdesignstudio/examples/doc/sidemenu.qdoc | 3 +- .../examples/doc/washingMachineUI.qdoc | 5 ++- .../examples/doc/webinardemo.qdoc | 3 +- .../src/qtdesignstudio-toc.qdoc | 9 ++--- doc/qtdesignstudio/src/qtdesignstudio.qdoc | 7 ++-- .../exporting-3d/exporting-from-qt3ds.qdoc | 2 +- .../qtdesignstudio-3d-materials-shaders.qdoc | 2 +- 22 files changed, 82 insertions(+), 50 deletions(-) create mode 100644 doc/qtcreator/images/timeline-rotation-animation.gif diff --git a/doc/qtcreator/images/timeline-rotation-animation.gif b/doc/qtcreator/images/timeline-rotation-animation.gif new file mode 100644 index 0000000000000000000000000000000000000000..c03478c5a3528f55cfd092e8921a7fb35f41121c GIT binary patch literal 255054 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 literal 0 HcmV?d00001 diff --git a/doc/qtcreator/src/qtcreator-toc.qdoc b/doc/qtcreator/src/qtcreator-toc.qdoc index b812857a1ab..dfb84f20f71 100644 --- a/doc/qtcreator/src/qtcreator-toc.qdoc +++ b/doc/qtcreator/src/qtcreator-toc.qdoc @@ -150,7 +150,6 @@ \li \l{Creating Scalable Buttons and Borders} \endlist \endlist - \li \l{Creating Optimized 3D Scenes} \li \l{Specifying Component Properties} \li \l{Scalable Layouts} \li \l{Using Custom Fonts} @@ -187,9 +186,14 @@ \li \l{Motion Design} \list \li \l{Introduction to Animation Techniques} - \li \l{Creating Animations} + \li \l{Creating Timeline Animations} \li \l{Editing Easing Curves} - \endlist + \li \l{Production Quality} + \li \l{Optimizing Designs} + \list + \li \l{Creating Optimized 3D Scenes} + \endlist + \endlist \li \l {Browsing ISO 7000 Icons} \li \l {Converting UI Projects to Applications} \endlist diff --git a/doc/qtcreator/src/qtquick/library/qtquick-component-instances.qdoc b/doc/qtcreator/src/qtquick/library/qtquick-component-instances.qdoc index da2e8f49b3e..2a499236a4c 100644 --- a/doc/qtcreator/src/qtquick/library/qtquick-component-instances.qdoc +++ b/doc/qtcreator/src/qtquick/library/qtquick-component-instances.qdoc @@ -65,7 +65,8 @@ \li Add states to apply sets of changes to the property values of one or several component instances in the \uicontrol States view. For more information, see \l{Adding States}. - \li Animate the properties of component instances in the \uicontrol - Timeline view. For more information, see \l{Creating Animations}. + \li Animate the properties of component instances in the + \uicontrol Timeline view. For more information, see + \l{Creating Timeline Animations}. \endlist */ diff --git a/doc/qtcreator/src/qtquick/qtdesignstudio-optimized-3d-scenes.qdoc b/doc/qtcreator/src/qtquick/qtdesignstudio-optimized-3d-scenes.qdoc index b99e555029d..86045cb93f4 100644 --- a/doc/qtcreator/src/qtquick/qtdesignstudio-optimized-3d-scenes.qdoc +++ b/doc/qtcreator/src/qtquick/qtdesignstudio-optimized-3d-scenes.qdoc @@ -25,8 +25,12 @@ /*! \page studio-optimized-3d-scenes.html - \previouspage quick-scalable-image.html - \nextpage qtquick-properties.html + \previouspage qtquick-optimizing-designs.html + \if defined(qtdesignstudio) + \nextpage creator-coding.html + \else + \nextpage qtquick-iso-icon-browser.html + \endif \title Creating Optimized 3D Scenes diff --git a/doc/qtcreator/src/qtquick/qtquick-animation-overview.qdoc b/doc/qtcreator/src/qtquick/qtquick-animation-overview.qdoc index 638cbce6ce6..50f0beaf45f 100644 --- a/doc/qtcreator/src/qtquick/qtquick-animation-overview.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-animation-overview.qdoc @@ -26,9 +26,12 @@ /*! \page quick-animation-overview.html \previouspage qtquick-motion-design.html + \nextpage studio-timeline.html \title Introduction to Animation Techniques + \image timeline-rotation-animation.gif "Timeline animation of rotation and opacity" + \QDS supports the following types of animation techniques that are suitable for different purposes: diff --git a/doc/qtcreator/src/qtquick/qtquick-designer.qdoc b/doc/qtcreator/src/qtquick/qtquick-designer.qdoc index fed9a25ca26..fcdcf9c31a3 100644 --- a/doc/qtcreator/src/qtquick/qtquick-designer.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-designer.qdoc @@ -110,7 +110,7 @@ \li \l Timeline \li Provides a timeline and keyframe based editor for animating the properties of components. - \li \l{Creating Animations} + \li \l{Creating Timeline Animations} \row \li \l{Curve Editor} \li Enables you to view and modify the whole animation curve by diff --git a/doc/qtcreator/src/qtquick/qtquick-easing-curve-editor.qdoc b/doc/qtcreator/src/qtquick/qtquick-easing-curve-editor.qdoc index f277a9f615c..5a6825ca6cb 100644 --- a/doc/qtcreator/src/qtquick/qtquick-easing-curve-editor.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-easing-curve-editor.qdoc @@ -76,7 +76,7 @@ For timeline animations, you can also use the more advanced \l {Curve Editor} that shows the interpolated values of an animated - property over the \l{Creating Animations}{animation} range. + property over the \l{Creating Timeline Animations}{animation} range. The animation curves present a more readable view of the animation by showing the effective values of the animated properties over the animation diff --git a/doc/qtcreator/src/qtquick/qtquick-motion-design.qdoc b/doc/qtcreator/src/qtquick/qtquick-motion-design.qdoc index 9d68b374bbc..91f842e08e0 100644 --- a/doc/qtcreator/src/qtquick/qtquick-motion-design.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-motion-design.qdoc @@ -30,10 +30,15 @@ \title Motion Design - You can use different animation techniques for different purposes. \QDS - supports common motion design techniques, such as timeline and keyframe - based animation and easing curves, as well as screen-to-screen or - state-to-state application flows and data-driven UI logic animation. + \table + \row + \li \image studio-animation.png + \li You can use different animation techniques for different + purposes. \QDS supports common motion design techniques, + such as timeline and keyframe based animation and easing + curves, as well as screen-to-screen or state-to-state + application flows and data-driven UI logic animation. + \endtable \list \li \l {Introduction to Animation Techniques} @@ -41,21 +46,28 @@ Learn more about which animation techniques are supported by \QDS and the use cases they are most suitable for. - \li \l {Creating Animations} + \li \l {Creating Timeline Animations} You can use a timeline and keyframe based editor in the - \uicontrol Timeline view to animate the properties of UI + \l Timeline view to animate the properties of UI components. Animating properties enables their values to move through intermediate values at specified keyframes instead of immediately changing to the target value. - \li Production Quality (TODO) + \li \l{Editing Easing Curves} - After the wireframing and prototyping phases, you can use the - supported motion design techniques to fine-tune your UI for - production. - \li Optimizing for Target Devices (TODO) + Specify easing curves for nonlinear interpolation between + keyframes in timeline animations, as well as between original + and new property values in property animations and between + transitions. + \li \l {Production Quality} + + After the wireframing and prototyping phases, you can use previewing + and profiling tools to fine-tune your UI for production. + \li \l{Optimizing Designs} You can test your UIs on the target devices to make sure you get - the best performance out of your animations. + the best performance out of your animations. To solve performance + problems, you typically need to optimize the graphical assets used + in the UI, such as images, effects, or 3D scenes. \endlist */ diff --git a/doc/qtcreator/src/qtquick/qtquick-properties.qdoc b/doc/qtcreator/src/qtquick/qtquick-properties.qdoc index ad9962daa9a..75b12c420a5 100644 --- a/doc/qtcreator/src/qtquick/qtquick-properties.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-properties.qdoc @@ -132,7 +132,7 @@ Changing a component's opacity does not affect whether the component receives user input events. - You can \l{Creating Animations}{animate} the opacity value to make a + You can \l{Creating Timeline Animations}{animate} the opacity value to make a component fade in and out. If the \uicontrol Clip check box is selected, the component and its children diff --git a/doc/qtcreator/src/qtquick/qtquick-timeline-view.qdoc b/doc/qtcreator/src/qtquick/qtquick-timeline-view.qdoc index 0084125fc67..2c3984d55f9 100644 --- a/doc/qtcreator/src/qtquick/qtquick-timeline-view.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-timeline-view.qdoc @@ -74,7 +74,7 @@ For more information about creating timeline animations, see - \l{Creating Animations}. + \l{Creating Timeline Animations}. \section1 Navigating in Timeline diff --git a/doc/qtcreator/src/qtquick/qtquick-timeline.qdoc b/doc/qtcreator/src/qtquick/qtquick-timeline.qdoc index 05c09eb267b..6c8f5525574 100644 --- a/doc/qtcreator/src/qtquick/qtquick-timeline.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-timeline.qdoc @@ -24,12 +24,15 @@ ****************************************************************************/ /*! - \previouspage qtquick-adding-dynamics.html + \previouspage quick-animation-overview.html \page studio-timeline.html - \nextpage qmldesigner-connections.html + \nextpage qtquick-editing-easing-curves.html - \title Creating Animations + \title Creating Timeline Animations + You can create timeline and keyframe based animations for linear + interpolation through intermediate values at specified keyframes + instead of immediately changing to the target value. \section1 Creating Timelines diff --git a/doc/qtdesignstudio/examples/doc/coffeemachine.qdoc b/doc/qtdesignstudio/examples/doc/coffeemachine.qdoc index 463cf53ac6d..9b954da5869 100644 --- a/doc/qtdesignstudio/examples/doc/coffeemachine.qdoc +++ b/doc/qtdesignstudio/examples/doc/coffeemachine.qdoc @@ -141,7 +141,8 @@ When we deselect the record button to stop recording the timeline, the new timeline appears in the view. - For more information about using the timeline, see \l {Creating Animations}. + For more information about using the timeline, see + \l {Creating Timeline Animations}. \section1 Using States to Move Between Screens diff --git a/doc/qtdesignstudio/examples/doc/ebikedesign.qdoc b/doc/qtdesignstudio/examples/doc/ebikedesign.qdoc index 5f15994e700..ab24a48dcbd 100644 --- a/doc/qtdesignstudio/examples/doc/ebikedesign.qdoc +++ b/doc/qtdesignstudio/examples/doc/ebikedesign.qdoc @@ -89,7 +89,8 @@ to fade out the current screen when moving to another one and to make the speedometer grow and shrink in size depending on its current position. - For more information about using the timeline, see \l {Creating Animations}. + For more information about using the timeline, see + \l {Creating Timeline Animations}. \section1 Using States to Move Between Screens diff --git a/doc/qtdesignstudio/examples/doc/loginui4.qdoc b/doc/qtdesignstudio/examples/doc/loginui4.qdoc index 319fd90e301..91698be34a7 100644 --- a/doc/qtdesignstudio/examples/doc/loginui4.qdoc +++ b/doc/qtdesignstudio/examples/doc/loginui4.qdoc @@ -40,10 +40,11 @@ some basic UI components, such as pages, buttons, and entry fields. Part 4 describes how to use the timeline and states to animate UI components. - In Part 3, you learned how to use states to simulate page changes in a UI - and connections to provide user interaction with it. In Part 4, you will now - learn another way of animating the UI by creating \l{Creating Animations} - {timeline animations} that you bind to states. + In Part 3, you learned how to use states to simulate page changes + in a UI and connections to provide user interaction with it. In + Part 4, you will now learn another way of animating the UI by creating + \l{Creating Timeline Animations}{timeline animations} that you bind + to states. These instructions build on: @@ -128,7 +129,7 @@ \section2 Adding a Timeline and Animation Settings - You are now ready to add the \l{Creating Animations}{timeline}. You will + You are now ready to add the \l{Creating Timeline Animations}{timeline}. You will need two animations, one for moving into the registration page and another for returning to the login page. You can use the same animation for both cases, by running it either from the beginning to the end or from the @@ -314,7 +315,7 @@ All the properties and functions of the components from this module are available in the \uicontrol Design mode, and therefore it is enough to learn how to use \uicontrol Timeline, as described in - \l {Creating Animations}. + \l {Creating Timeline Animations}. \section1 Next Steps diff --git a/doc/qtdesignstudio/examples/doc/progressbar.qdoc b/doc/qtdesignstudio/examples/doc/progressbar.qdoc index ddfc9c69eca..f8d6a9af528 100644 --- a/doc/qtdesignstudio/examples/doc/progressbar.qdoc +++ b/doc/qtdesignstudio/examples/doc/progressbar.qdoc @@ -82,7 +82,8 @@ changing color. To animate the label and indicator, we'll add timelines in the \l Timeline view. - For more information about using the timeline, see \l {Creating Animations}. + For more information about using the timeline, see + \l {Creating Timeline Animations}. \section2 Adding Color Animation diff --git a/doc/qtdesignstudio/examples/doc/sidemenu.qdoc b/doc/qtdesignstudio/examples/doc/sidemenu.qdoc index 2f7e39ec4b1..9725732aa8b 100644 --- a/doc/qtdesignstudio/examples/doc/sidemenu.qdoc +++ b/doc/qtdesignstudio/examples/doc/sidemenu.qdoc @@ -185,7 +185,8 @@ apply when the animation finishes. In the lower part of the dialog, we bind the states that don't have animations to fixed frames. - For more information about using the timeline, see \l {Creating Animations}. + For more information about using the timeline, see + \l {Creating Timeline Animations}. \section1 Connecting the Burger Menu to Actions diff --git a/doc/qtdesignstudio/examples/doc/washingMachineUI.qdoc b/doc/qtdesignstudio/examples/doc/washingMachineUI.qdoc index 56743812022..7a3cd6ab20c 100644 --- a/doc/qtdesignstudio/examples/doc/washingMachineUI.qdoc +++ b/doc/qtdesignstudio/examples/doc/washingMachineUI.qdoc @@ -48,8 +48,9 @@ Users select buttons to navigate between the screens. We use \l{Connecting Components to Signals}{connections} to determine which screen to open when users select a particular button and \l{Adding States} - {states} to show the screens. We use the \l{Creating Animations}{timeline} - to create progress indicators for buttons and the \e Running screen. + {states} to show the screens. We use the \l{Creating Timeline Animations} + {timeline} to create progress indicators for buttons and the \e Running + screen. In addition, all screens contain a small clock component that displays the current time. We implement a \e TimeDate JavaScript object to diff --git a/doc/qtdesignstudio/examples/doc/webinardemo.qdoc b/doc/qtdesignstudio/examples/doc/webinardemo.qdoc index 4dddd367674..805e545cd31 100644 --- a/doc/qtdesignstudio/examples/doc/webinardemo.qdoc +++ b/doc/qtdesignstudio/examples/doc/webinardemo.qdoc @@ -191,5 +191,6 @@ \image webinardemo-timeline.png "Popup animations in the Timeline view" - For more information about using the timeline, see \l {Creating Animations}. + For more information about using the timeline, see + \l {Creating Timeline Animations}. */ diff --git a/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc b/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc index da5b29fca83..1d3fb757af9 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc +++ b/doc/qtdesignstudio/src/qtdesignstudio-toc.qdoc @@ -179,17 +179,16 @@ \li \l{Motion Design} \list \li \l{Introduction to Animation Techniques} - \li \l{Creating Animations} + \li \l{Creating Timeline Animations} \li \l{Editing Easing Curves} - \li \l{Production Quality} (NEW) + \li \l{Production Quality} + \li \l{Optimizing Designs} \list - \li \l{Detailed Motion Design} (NEW) + \li \l{Creating Optimized 3D Scenes} \endlist - \li \l{Optimizing for Target Hardware} (NEW) \endlist \li \l{Implementing Applications} (NEW) \list - \li\l{Creating Optimized 3D Scenes} \li Using Flows in Production (NEW) \li \l{Coding}{Cross-Platform Development} (NEW) \list diff --git a/doc/qtdesignstudio/src/qtdesignstudio.qdoc b/doc/qtdesignstudio/src/qtdesignstudio.qdoc index 1761fe504ce..cdbbf2751ef 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio.qdoc +++ b/doc/qtdesignstudio/src/qtdesignstudio.qdoc @@ -74,11 +74,10 @@ \li \b {\l{Motion Design}} \list \li \l{Introduction to Animation Techniques} - \li \l{Creating Animations} + \li \l{Creating Timeline Animations} \li \l{Editing Easing Curves} - \li \l{Production Quality} (NEW) - \li \l{Importing from Content Creation Tools} - \li \l{Optimizing for Target Hardware} (NEW) + \li \l{Production Quality} + \li \l{Optimizing Designs} \endlist \row \li \inlineimage front-preview.png diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/exporting-3d/exporting-from-qt3ds.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/exporting-3d/exporting-from-qt3ds.qdoc index 96ecbb8b0d3..3b24cf2815a 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/exporting-3d/exporting-from-qt3ds.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/exporting-3d/exporting-from-qt3ds.qdoc @@ -243,7 +243,7 @@ \li Recreate animations in subpresentation_ADAS according to the original project. For more information on creating animations in \QDS, see - \l {Creating Animations}. + \l {Creating Timeline Animations}. \image exporting-from-qt3ds/24-recreate-animations.png "Recreate animations" \li Go to the \uicontrol Timeline View to review the timeline for the diff --git a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-materials-shaders.qdoc b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-materials-shaders.qdoc index 85b5d1b3d65..b593f544e02 100644 --- a/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-materials-shaders.qdoc +++ b/doc/qtdesignstudio/src/qtquick3d-editor/qtdesignstudio-3d-materials-shaders.qdoc @@ -73,7 +73,7 @@ by any other colors specified for the material. You can animate material properties in the \uicontrol Timeline view, as - instructed in \l {Creating Animations}. + instructed in \l {Creating Timeline Animations}. \section1 Blending Colors From 0ef56472b78fd472e68a340a9b74f00f99986157 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Wed, 16 Jun 2021 16:23:18 +0200 Subject: [PATCH 069/149] Doc: Describe the properties of Animation components Fixes: QDS-4465 Change-Id: I42cdad8694feb612946a95d1bca77ddd1d6f5982 Reviewed-by: Johanna Vanhatapio Reviewed-by: Brook Cronin Reviewed-by: Vikas Pachdha --- doc/qtcreator/images/icons/pause-icon.png | Bin 0 -> 95 bytes .../images/qtquick-color-animation.gif | Bin 0 -> 195012 bytes .../images/qtquick-number-animation.gif | Bin 0 -> 181679 bytes .../images/qtquick-properties-animation.png | Bin 0 -> 7840 bytes .../qtquick-properties-coloranimation.png | Bin 0 -> 19595 bytes .../qtquick-properties-numberanimation.png | Bin 0 -> 3123 bytes .../qtquick-properties-propertyaction.png | Bin 0 -> 2542 bytes .../images/qtquick-property-action.gif | Bin 0 -> 78918 bytes .../qtquick/qtquick-animation-overview.qdoc | 85 ++----- .../src/qtquick/qtquick-animation-types.qdoc | 234 +++++++++++++++--- 10 files changed, 215 insertions(+), 104 deletions(-) create mode 100644 doc/qtcreator/images/icons/pause-icon.png create mode 100644 doc/qtcreator/images/qtquick-color-animation.gif create mode 100644 doc/qtcreator/images/qtquick-number-animation.gif create mode 100644 doc/qtcreator/images/qtquick-properties-animation.png create mode 100644 doc/qtcreator/images/qtquick-properties-coloranimation.png create mode 100644 doc/qtcreator/images/qtquick-properties-numberanimation.png create mode 100644 doc/qtcreator/images/qtquick-properties-propertyaction.png create mode 100644 doc/qtcreator/images/qtquick-property-action.gif diff --git a/doc/qtcreator/images/icons/pause-icon.png b/doc/qtcreator/images/icons/pause-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a3eeab1c0e57d847837219bcc63766dcdcba9077 GIT binary patch literal 95 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

NeGaHtCYKiPG(A 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 literal 0 HcmV?d00001 diff --git a/doc/qtcreator/images/qtquick-number-animation.gif b/doc/qtcreator/images/qtquick-number-animation.gif new file mode 100644 index 0000000000000000000000000000000000000000..4b3a9e2b744c65ed121b74a7179202016a9b3c21 GIT binary patch literal 181679 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 literal 0 HcmV?d00001 diff --git a/doc/qtcreator/images/qtquick-properties-animation.png b/doc/qtcreator/images/qtquick-properties-animation.png new file mode 100644 index 0000000000000000000000000000000000000000..06c58d946da5c057739006cf7e4747d3aa7928fc GIT binary patch literal 7840 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 literal 0 HcmV?d00001 diff --git a/doc/qtcreator/images/qtquick-properties-coloranimation.png b/doc/qtcreator/images/qtquick-properties-coloranimation.png new file mode 100644 index 0000000000000000000000000000000000000000..b429265acb102097081ff344301cd3c30cad44d5 GIT binary patch literal 19595 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 Z^taP*l#Rc=VPIfj@O1TaS?83{1OQR#C}RKs literal 0 HcmV?d00001 diff --git a/doc/qtcreator/images/qtquick-properties-numberanimation.png b/doc/qtcreator/images/qtquick-properties-numberanimation.png new file mode 100644 index 0000000000000000000000000000000000000000..863c841805923549930365cc4cc53aa7e9f15e5a GIT binary patch literal 3123 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 N44$rjF6*2UngFz4=s*Af literal 0 HcmV?d00001 diff --git a/doc/qtcreator/images/qtquick-property-action.gif b/doc/qtcreator/images/qtquick-property-action.gif new file mode 100644 index 0000000000000000000000000000000000000000..95f4f9589b6564d7f77d5e88ff5802e9f5d1d747 GIT binary patch literal 78918 zcmZ?wbhEHboX3#Mkio#9_>+Z&oq>Tthk=2C0VKBU|Ns9_UVeW5=Ih0qFRtEs_3ZW6 zdrv+*ef8zW{WsU{zP|hT{pu_<=EILi&p+LN`r+1tw=ds*yK?*0gJ&Nv+<1Qe z`tzF)-oAMA?eUAxw;#T{eEa3Y=btX$dimdTuCKVN_N z@#ELuyN}*qzxU?$qjxXgeZP3)#lvSGUcCKw>*3o6Pd~nX|KrxfclV!uy#M6G^?R>x z+CubN*YCc*a_800Uw?l6{`>pS-yc7J|M~Op$Im~1{{H*@=ilQO zpKd>V`}_CbyN}=9dhq7$#~)8$eSY=s+mBy=-hKLc zn-AabJ$e7=`Nx-Uzdm^O;r^5N&tHFe@%HPz$M2uN`SS1I|92mM-njqz<-2e1KmU6B z;m5=0AD_Pb{QmRL`%mA$djIX&t51(!e7gJS-LGGN?mT)63Qw{y$3F%}4jGRP3l27O z2y4Zh*s$<$yMVIS9FL8Qj&@5JXWcomaq;nf1?MiA&I!l6CTj$*iaEJy>FMbP$)`d( z+f>}-MT=hDIk|cH`S}jbT(VwM(uHS>7mCH6+OqQUQum2rUmO>%y1F`IbJpEcTUTFS zpRmzws@24-8=EsOud@B!s^coJ_2|@G@9pdE?ymU!>h9_3@7TAevUAA!?AY+|aEGvV z+?gGP5BE1H|0?j=x#{WYsfzA(7LuEvpI;!IeeceN_V&vwf>+0#-L>`g^$p3V!)jYh zp5NS2{QBP9-P_(?GHv3P_uI4M@XEi5P>+x+|chsUSq`|scP_xF#_$K%i2DGK~$VAFWe!1(F%Vl$dZe`3>Z_#?SV#&0u zS1VU6TlH$ynn_-*i~m_)PFTG`_0A^YOCLD zI%5{CBeuax`|Xx1Vb;1^4o0nhyX}ryHrpEc7VURCo=nSrxAVoa)$ew_ne>}sjo1AK$9uYCFDVQ&s zzUJdGnd}fBWrr!ExR1cS<(Lwl2=TviAGGdo|zpe!rXTmU!qvi+J9ThaKwcemv?i zf4AcypM_K_)0FVMpHF9`ulxCIPWii^&!;bcsKLKrdfu;>D`wjrd9iZ)yI-#ttly`` z&vqs6_uC!M*ZqFC=li?g?+-lItDd%nJ^#t z{_nRt{6VD~t{B=w`vA&0xi zA)#3xS~PqTIV?SFM0b5?HRyTB?QCEraN|Rp#hHbC(32#z&|nvD#K*3PGmFHwpE#;l^|3pFCs}Nzg|*?Vk3AVak0m!7 zSh4T;*jv!ESmwFMajRP&`zp>P%iP?tOzFkP{sx{U3XCUN-9;JMJA9TXiJv^-X7y>} zgq|fT>N`CcC4`t~oLQn~e)6PW)u+h|c$R9o?>wm{3zW;FfenYbv%8A=_?OqP3|&5HUw z>&BU7X4_BtX4!q7%FeOe;<)FzqFJBkyzp6Wb^WAYksHH|w>`^ko_qRNHZjio`tQtg zyYDCcR2hs}7a{RbYGC zN_H9El^*V1K@;aO&QSGT=@owJA}d2MgF){~pLDOwvu=G^W^s0PM#d%9jii?dtF@>^>tOm*;QfNPlc}XI~W-7c2&f2uWOsS zzOK#|V&cAb>e{wlU)L7&u8w)`b$!>Zt7~}~vRS^Jy1wt%*Yyp&YZBPKZyb`kw!Sj< zGC$wx8^^4^ZR{0oUR*b!=~UFWO*77}Ni#ov^IX-p%?pm|2&vs}yfo|EmKDBhv%*i` zy0+`v)(x)r-)4wP-@bM0+qNBB-{x%o!Yuvc+x7#z>k8Vv?>v(FzT<>%Ucp|2o2(Y! zcV6gSSF+su?yIQpyKbD#E17vA=55vY-4A%zm+#!YOkvN1JuiIMS6x4S@7u2Ldq4E9 zues`dPlvUU<;U6eb>C0l|M%0fQM3MeGCYPKxu6(|NsC0u48xi zoqTZg{FAfSU*3B3arMrt2hTiOx&89hyYDvcyRm8i%`FFSt>1I~?vu|uj@(_fGsB$&0t&PhWYy@8tbs7oMEG`t0$` zFIx`YeE#O!vGb2M@4s>W`ilq8zZ^OD_~G-6vl|cI-+K7r z=(&e`j^Dd>_s!E+Ur%3o`rz58%ePBr>8DI-FEoaj-z+3 z-FtKI$%k{-p6x$*@7BY251xN|^6Kl=d+)B^ef!|qm)j5DA3po&@VQ6V@4q{D{pIPa zFYZ10c;@Ph3pZZfdieg(*+*Ayzut4=-uA<{Ph5KXhhCaNAHl;Dx;Mq;#+%_7pC^2wDxj-cBQzQbD*^LzC2f*Edpun z1uvgA!Ow(kAxdj+d)GuQLmzN!FV=dR&JhDPl-Az&cShRg_A;)EHa#da1uYr7_$p69!J+uPfq*529Q+vKnF zteql(XzlIU`T5EIM(r(!79m=D-`_vj{%`f9bpqbapI=;F9e;k`UT|wK-@d2D>GS8$ zukW9qzhC1wN21`-1{Sf52aOzRE5NP2gr;tWiVMc>AsG)_CDK+rYy-FU6!La-wXiK+ zbO5=vcT6L>yLCc@fR-_&wa3xh%W`t#fe9{RnNKEqfLnWJFH19 z#r&AEhc6(ly+b>09u=BX_Uh&G1#Pg_-XgX~YNEbdUcFlVZ^JPySZnXxuA{4zzr1?A z{s5acthL9tTYGv)!-F@QFSvnQd)aTd-bnLa%aAwg(2?yA+K^g%c{^)&SRP~ka15oj z_ifwhr@Q}vTYJJa?XyGwEI?`Ph4Xwo7!xLV;FwJLn~%p8+99pIq7yX=HJ?uDY=86V zv}SQ9I}cK8@B5q2=dJi3smaNUz*~FfZ@*j)(C6)$-Tx!u>(z*KL~HNtSKT}*r6V^p zmLs+H%5U%3@v?)X;yI+X_xAh!26nw459+2rOf$AnS@#3f+WYZ%g1g?&CsXY6ET)%m zEP%H57PRaAda-0W-!HYiw7g%h)@*;b>oxNZ$urzruA{W}b`?KVGj{CK|p|Gz)i&#THfa3Hnz7BGpQ zIKV7)*tcLuBcippfX)2GLB6C30(nVCdE8*Fy@g!iCk~12GHA^%5LStCI4pGwwY6u} zqd472utiAl384ck)zl z-6Zdz3}|a_5<^~7t5Dg=({aB(O+Uc14BXn2GM=%k;Ih8Tfir1VpJ!g^fw%T9>9o$y zX*`=(^?CLKo^;kbTiAJLz*>9f%65I8`=LkO`uNGEwiXZP{eZOg&e#3=JRj8B`~8yJ z+*64S+}b;Jq0Q>cLU3#E)di0oEBUNXtZ*|wb+NDN%VGs^YwuG3t%r*gJwUC!OVf6J zSqg3KO`kW#yWOMFul&^IdB46uT6?H6`no#dtk(33#HLNNzOKpeT^)5D+S-fNR=UH|#dYKBx{9-_A4gY3cKl99N_s_EheVgYz_jzu0 z_$E`{Bvz4cXP(>r{&~KEZ_5I9-xp5ORu_Z|f@KU2yl}Jrb#X%9mL=-GFTJ9FU7F-) zGW}6QlVA0(%M19nu5kB#6*T+Tl@)SZmn|*n5#I6Z>W03pYtnsRN8SE)ZO1v&wZae8 zOhq1CKft$bL%Z*rBke)fIc@89qM6F_pvM5=242rkE7=2KK51rc`W&Sl9H~C!>38J z|2)y~-+3zh+^1=~|2)+?-!wbT@AItNf1X*K-+8Y5+~;|}|2%h4_r}$7zI6NDsVgt% zu06M6`_-Mt?yuc_ebvruOSfFQa`*k}o!9oBe7JP$<*WBTY&~>m@urK5HeFb}`QqVo zPiL<=bNcG5g&QyIIC5|Ef!mw*-&(NY!pa?2FWh>2_WJ8H*IpgH@NDkdvq#Q9TeIu> zw!`-}?7ey6=G)gFejK^*?7*2P2hTj2zy89UHD{+SJGp$@m8DxQZ$ENx%fUN4j^00e z{ms5pk9Hk@uy*&2)w`~5K6v}s#pfq4zubEG?#0{hF5h`SW98`$`))1RcwyG+GiR>7 z-gWH${?m`Q9=f~dH$sp%_D&0BYV_L{RZ zR-N8`;=#uKx6a*obLi~TtM@*hxcu_y#pg>lUs|{4#_>xpX0AHD_tc{+cRwuKdS(6I zn``&nxN`gDlb4?#J^ys|&Z{>ceq6uz`su4L&)Z}@%sIbdrv-WIe7c#gSU|J&D#&(J$~`|(ybRa?!UQo^TplA z@7M3W@%sJu_4{tV`S5em#`AAK{+Pf1{QlFAmTkTK_{FCeZ@w9MyLJ1~yMt#Q-+A*YJ|X0JYT@5%dz&ptkQ`f<+Mvr9H# zym9Z%wfi3qo_%uY?30sMUar}7?f#Pwn-1K1_WJ9w3(s!efAj6fpUZdOUw`oN#hY(u zuf4i?|Ly6kFAtx8y5-QFeJ3C8I(~oq;k$>=J~?se#iJLWR&2ZS>izc(`)=+#{b=8* zhX>9)e)#;;iA&FqUwVG*;H6K**YAJ0a_7~Z$L}9J{jmG^gKdZJ+&NMjFj?!{&@$6XQ;-!t!at_hZnl1QTBoZ`Ex;$X9*Iciyt3p=^PjH%Z zcnN5n^l+D~_qH`RH)ovPbm`Xx|H<169wWv{kN$34>uhhs#Empg8nID&KkrY6nUI#V zarV74J2$7F>sQo&vUw?_3Qy9GKeuP+ z=jRs|d(Zca{iXDn^SaH=b9;AxfB)d{zixT|eS0cDNp4W)S-c3+a)yqRz6}qkH#pPq zA3RP9YdI@43492@)C3tPW%|+3%o_Rl2(;zgrtzplWm<+(r(lAIfTY(J&^YNajmJGE z*D@aWTHLzP)x|t<;vvvD=@WR%S!L4Y4;liJk}jH0r-Xo8&S@*3PK(LvY?#cj8!J-F`P<9q^J|rq)LCHTq@b4bs~3xVotV^2`9t92q~Mlw zRTs~UG?xU}IO#S>%lX$Tl_g1u@NrUb%lY?ewk1jl@Nv@DpqBIR9O;Bzmkw>ckp>zk zEqnbI)NviPl8*{2E}tWO)k$!fQ^%aTFwsae>bZqJSsc@9VZ31oZWRz zuQZ6PJ%G}3)-}5kYW>rb7p3L={Z1h_(=t`XC#Wsw7VdRCGpZyOptPLB?JU(gE~B-a z?X1`m(wL5cTF$>-Z#b^^`^}c?dDd^YHvDuGgN&21>;L(1L_Gh`$D{EJev3m|&hGku zzg!8=|NHgoZN_J6$|c}&QvH8Fo=ngG_w&W<^?N5@|MCDdPOAU^&zI|vangE&gnx=h zQO8LaFbTeB_?P$qb)0koo8byxgWShVDC49Hxx!~SB{gV@nsqGXN%uf%IXlb7G5P!} zt!NZz_c(&wa@M=Z$4L<_XWvXc2A@?NO=jCqLR-#| zaninEi3F*mJl9S_Th5?y((_ffCeJ(bk72U$q66@jGjyDkHI{A0v4j?Q%NaUOy7h&k zm{$u@%X!kQFH1B`GmXV|H6XQ|!Q-S`SE?N-;nDknXgN!LUE$&TDln08naha>h?X;C zoOGiUx4~OSjs)J-;Bit|%X!wdqzX|^_&6!F4(xwb|?_0>@r0cFRPw@TMl#}ia87DmpX*s|9CYE5;%+dxMCxy10 zr|-PTp~tZdHcq+@(sG`@`vS*0&ST#9KF#`$7$=>+_dG+ehKrk9&rw}faFYIYp~G*>BJs1}mUHj4wguJ>;Fj}@b6b{~pM42xInT{wnemCC z1>AD>+qx?J?5nWdzpid5+p6q#wh_{D-ny>*?CZGSzpfwnW~zMPONWF6q~$#Q?3*-e zP|Nw-W^)ELQ=f;o&|1z1Z9B8{AKrQ4hthI>DBF-V`N6#(=eF-dYB@8;<*TbEGjsUw zI3$kLa+doT+@L97bz;XcXv~{FJY4+bY8UDL(T|f72+wQ+_bJw%Y;JVnb>-OJw73X)~`+n~GzTbb}H+=uC zcR-|5OXA;$4*xxm#LxdYX8rHug#JH|tr?faOnLBW#`!(Z%+LQkSN-qv0{*=(9PK#_ zX8!xK!hi3p@bkZ}?f&<5L;v4b%uJxkcg*4NIcv}FI{t9=t{ZdLoj-K$*^vv+=dHic zyWr@=B_|G?d3x;Pi>b>`?Kt|Nd)|>%JFlO*`ugORSA7eQZ8>=N;_dglPdqwv{mtw( z=MJ8IwqWDM#hWj0*mwKzxo0OXzuJ51@umZJHXppZXw#(`E6;2>bZ^yvu@9=eW#zCx%TG#t#{{dzB_s4_0la@`WGEPe(7b;{G%(j zUt6-}%Fzoi&fR!BdFjcW#~w~war*3yw`=#@?3{D>*rk{2_THMb|#y79oB zrCYBq+<0liqT_o{KHh%h{)!#fwjRE>eB0IOE6;2{`e4!K%d=LWYoC3neb%9lIfr`} z9Gk!4;+~U_*6+K$X7|mNJFZV&c53FTv(uKJ-gfwY+pL3KbB|0|e4=yi;q`lOZ9Q~v z!s6qzR-c`};lh$FmpkShTC@Ac+TAy&uROi`_{081$7ZZLbKvxo4SR1-UvYZk;^RAy zKG=5n-lqL`X0AH3|IE`pCmtO+|NQLrHyyJN9X$JV^{(p+H(p%1^V*tSH+CF*xa;_% z%?IzyT61>s=1ZFn++Mf$=JIXV_MLpZX!E7jJ8!hlKDc^k=F{LQ!LZoWNz?e*dF&$b_V&^`ai*2DKV?7zMB;N9h0uWmnlf8P4@ z(^j0Cv-aG=O_xqwetGcRvsF8;FW7LQZ{g8>r=ILMa(~j&lT((Tn!4ij?$Qx&7Mm?N^s=y}EJ#o%Q=}Z9Z^k->JvjK)Oy`xb^PB?RQ;s z4=>w#W#x`*3pZU{y7daS*7G10Bc)M)b~;Lf9N~jr!;3=P0PVJXnCz zs89A6>J4z}MQPN__ceTTH0MBR)bHJ;^_VR^1hrBB^Rv(faUJD_D2@8hy950V4)&ln z>K`m@*M+0Y(1yc{PW)iwUjuE_!-vB+DK6&nLmLiP``y-))`2!0 zzWS}KQQU!}phi9N;)k>o8r@vrMm^Hv2Qj5&hCmijqh37c!$A&p@ZyJ>#vYa!g^x!> z!gD?zl>je(D1T$9X(T6b-~`g*2heahOUs{6ki`$`$9Wz}vV3{-`Md)=V)27+SC6F3 zgD;l@z>6Qkb0Ndwl1Bdwk3bhcOwauW84h20BBDzaviRY8?)SUU;qY{Y$4ZhcdOuJW zKg9L)NXaPve1fw0p_0RBk>H^hOQwSsKP+Dd9uEKYy6r>-vk2O7_yKFB#XM4I!{P4z zZ9O~hpbdxX?=v)NQaX$}9RB~$;S)1D1R=xW-{1d-HtHROPMly<@c<8pvsob)KRj}B z+n&(`9uBWUUi=_vW@sg$UN-;Z1KY)c%z=BrAK06D`fG*B1EJ9 z5p#-22YUr*@xx+?YCwN;_}G=h+WoasXyplt*aRCQtI2_ujhYpAH zpU7a6??7GraOz^8kdvD-L!*bg*ClYH9=!PB(zIPAOh!tFS<;XeKR|}V=T8V!cXPP1 z2;8U#Eq;IuhpRUC)EsO-y$+4Q##i9Q570(EXz@e7T*{M1?HNdoddT93Y{kW)Di4tx_29)1Yd9pg z9$-dp)Pom4Jd8WR!LNG)wDq~UN#qdrskM1~j}2VyuJ+NhUyaN|DEh!_rs zHtMC=jTRkcYC{Z%LmTzn69RP_9NuO{|3+!lvo-bjI5ro}{*BtGH|I?eY2>@{8@*9) zt)Lw3^56kRqn=UHQGre3JW8Yfjl16`i4z*a0v_<;a9E?>*}+XLq7gnE&cEwIyWf{Z z(zUEcx0<=9K^pY|{kyI#_X9WT^(F+W>Kp(!>J#{P-#G3EY1D6T=rOq13~JOD^zXj& z-0wT2QD4oUQqn2&y-u?rlDJchiA8$1c7&aQ5keGfx+81U2m^EIJNpcQ4v>X~)qA z+mGDed-CyxTkj8_f4=|plPSwib-OB*e)Pf8Emsz8z6|bdfI1v=*PZW}a~RTWpSAkj^p$7&7aiYn@b2+T zFL$4K)VttV-@;=ncU)hy`{t4@S5|DlcINtfqUDM=!jX zz2@Bf4Hu^^KRt8R*|mFaF4%bS*rk^{k3HOW`2MsNr#t5yZku)REV!RBVeyHsxkqT% zY9*oJxuN?cO2gB|b5mmfY>Q%~RkyP?t2xVgOx6Ogx`m7;hpxQXYP|`z>J~hj{N~zf zuAM7lPj6d$8`kiQ-IgKTcJuW1_4oHTFhfU^Ydhf<}|4vi&%Wyy_O*@LbB)aS&zI?W@=8x3WbXMOk(G`pxE}Y!ZjJTnPiO!+?z@ zUuAo77`MV#`br_&kx4rBy4nS7jE`Y4V$outjE_=rj z)Y0VMZ`ZO%9EPsCg^wohW#4fSx()+AntYah#v$0MTi9swU3QMcuvNFP(d4)6GmiZG zgJ^hySKa>Q7T^*)aR9mD8TFxwmuG^Y*@=UEC=JgRS)L2rm<>-|o(l|R@Kv|)hNtZn zMWN}CRkt=g7X+7~jwV-q><;CbAc{Vk{INHc=K{|&*s5FjXmTmf1SU45br_J*7@*PQsXP-H-9Qab_&N+w!*eOm1Eg!ma6hjV8?8Dn(_+pwt1m9JY9JM*q0%%x&=2pLwN(lj(J@}TZfU# z8^G`kwCWZ*nhaToQ94yS0lez=+lCU}3j$(@br{gmf zu%fTS_;KJbuYiac@~T_#It*Su0YyQcliXkuc55NsJi|8-dny2Lf=r=VMzaZ@Rx6bIQlw_KaY6%F9?~P1FyqS z;NN*7(cKTc4nvlI0(Tf>9R{r7dHc^ZUH$;>GUz%CSi|%03tN5x4$Ow9FaHGCXfnLv z8T*Ej5p$S#w6!;MTYDj%Md+=)U7MFVp|$p=uDsZS*4oRtzM2EQwYNQkAHB6#|5*yX zwWp}9hu+$2zvqMA+M9N^3ca;ww`aQpT5E6au3`rC)?V$;_YRNXd#qmHKfk~J?|VrW zjRz>Ly@&_p3FyO&5f96GS|Do&;KPg&kIK1^z}F5yT6+?28KJGcJDGtj}^GJcmHp>!4kwC zD{yP?{=ah1C!kfg$gRC^@2xd3hZ)U2Si7Bothz;N?UjA7*7ty}xjA)?pkd!;GM{1JgcQ8+Rb>v4XVru6?xT;#i`9 zyz2Jk2{*A%);<`kZvDbOS+ilRx(%E5$yyzKkJah8YoDy8IH0R;KhHP;ZS9GDwiZU; zV|6w!?6b8H`W~xuWz#-eJ9a?V4#0*PPoA&4_SssH19gwpsS9mlU#yKVhZ)1ZSVwVy z_E@0|Gfw+rE!VLUWslXV%k!>%u{PmY70`~_+7tU~EsMU#>gu|%uhx+qsA~sKUE4P8 zt2JW>bdMFHwRcT)T^)3f)upf2&K@YMZb7X*vv1bEn8S=^->l_55PPg(t-WpEtXVNu z-9GyE%~~5{)$J>@@778ukcJu8gIC>_eYY0#K-psjT6MeayS3$};|85f4i z)vZzaPiqm3wFA$sw*Rz_I00QdfLL|={in5)2Yl@SthHzU%bNWJeC+_NwO9Vj+R_8D zb^zMi+y2Yi;skV<@fUp6?e|~S<{sNNL5CT^t8RVYW|{xC);fW@>elyNQTcCc9gJ1C zRoj1Cd!0a8I{;dB`~A0doCoUK0l$x3=6|fcQP&Q{gIas#f2{Q|hZ(p3vDQ5ST|4mS zxdXJd_x+Exf(Lx<0Bo4i{I50A+5uQ=FJ46f+}2}5-%r15bYyQxj_l=l3Zb|4UTuEn zh}PEox-ziY8g*n(=f-MQw6@;e9T}YHZM~+?eCTaGb!{>9w%+7>JO|K5_ST)<)d*fE z3Ln|iKR2rbx=s{2vbX-+t`5XHQSiu~{&}em@D3~Z$lm(%R?YuWr<4;K*EBwa@27{1 z>?O9YF?{f_T>-jI6x`NJ>RhAy;8C{$Vx8!-70JD8Y9E64(<9n?DHGS&J_JuGBi4zg zPF>Uc;3@hJt2Hwpz^0U;JFL>@t_ghj9Cbf^#=XPbn{*k+v#x>6*U}kat+E zShgx_<(k9?s5`8lt;$}zCiEf74lA|QIUCm;eE?b~iqzK2-MZ%I1MoUg#FTR0&NZG7 zq3cAyK=;$H7^qoQ_A2SRz=6xI3~dM(?i>O#i!TU zCZMhp)h#){#y9Z`>N?TV%WGm2Aydlmk-f6(Yhn|iQ_4Be{q*Ix*AynerykC=> z0NPKF)Yhy0yk;r-lycqoH3!l5)9cs&UZa`_+E0(%*86>2N<;#*pB`mOS^9*Oas+5U zJxW{8`ot{p8KC|2C~dvy6SEjG_R}vCsE#Q zCuT{a@26iZ_xgks>ksIZGGb(p`=peb1#~|>Vq{PIq!e=nbU!^}WY7ELEa4rHb)wKI zWzag&?2}TwKRzLLSV7u)-6v;pTA=K(0_~??eNu`Ob4vO2$yo|JKs&5Z+j_50&f@q1 z*!CkmQU{(V|XLjrZ3Xy1kw=`&JH=<7r` zc37X8#WDl7pB~=Ui#{_;;09tpJ+!S?eMX8!0(C!q-=-yJf#d?Ckkon{XQe5C;^*N2CowZwe_UW&SH-EbqO-13|=P+YU^2_ zoh38l*Oe8}DP_<)(Y~!~qR&dn+yL#cLTT$&pPeNwfwoR`+m_j9r9>l8r1sd6lJ~vD3 z2I>wg{#_TU&q)bLpsW)Gwe@D7n}xPc6x`O!n=m?uJG|y_YdkL{pv~cOZAn9K>6xwy zY(i`4t+}z98NH>qGlLbqrPuPANds*TS6f@{;7*h|TyO2NL(r3xpmVs{+Ia_{JA$As zy>4x_L+~9z;FjKMZM{RtJAyzbCuy@Cg6^eoCI1t2yW?JTPY!01DeByE*=D(oFumDhFFi;E0mUA*s2@+Xp09|Oj{+vhOv0? z+NucAJ>S4Pg5aZiVyh!~&X|FB1i@Q+VXJ5G-zkIa2!gfrrmdbK@&+`Ai?n#~+Ugk$ ze?W7%kR3tbmY(>YeCWwZ!fR&mp)4N!cwC`;jRXtY;z5J$Yi=;3?WK44zUGDq(#c8S zQ9bjuH$=g6xJY~H%h%puO##i}BJBv;zE*<01T=>WT|5YG>3v^2gBLu93p+XK$Kwg+ z>t-;cEFS#%d_nm-2_dw_gB!N5i{M3HJotUx4L0zOpcCrwmY(_g2rlrBAf%I%%GXQq zKz0NzhmY!QUmqd#1+pUu-qL$BKPkWgJcrBSgJ|jHEJ*5cfS#O$Xz9IKkQC9@XbS>X6TNfCkvDM4nR*%LbUYOEJ|8)0D5u~VpLCOaZ=iWV`jTh zPfl91I7#HdanOz+|5QpxHq?7}Qz4XwQ z-r5yOTMi)ar3bh4bXO+X96;Vn4{qtLU73_|0A(*dXz`%#swAreki~Y1ieMeBhZ=V@J@0ye&z57(0R%yxWpgU@ZGkg2nX;SF7l3`yzNO-9NvTGaFKTez2Ba+$KgYhG-^vPe@D_ZhmT#+s4czs zJCdSeF!s_LD*k+umg3e-rZdphhN-9S*mv^!&@Wn4DwRFQyHEappD

{HSH#1P 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 PqSVy9kbL*dGzMz`i@Ufy literal 0 HcmV?d00001 diff --git a/doc/qtcreator/src/qtquick/qtquick-animation-overview.qdoc b/doc/qtcreator/src/qtquick/qtquick-animation-overview.qdoc index 50f0beaf45f..672e62c8ae4 100644 --- a/doc/qtcreator/src/qtquick/qtquick-animation-overview.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-animation-overview.qdoc @@ -210,8 +210,8 @@ \section2 Programmatic Animation You can control property animation programmatically. Property animations - are created by binding \l{Animations}{Animation} components to property - values of component instances to gradually change the properties values + are created by binding \uicontrol Animation components to property + values of component instances to gradually change the property values over time. The property animations apply smooth movement by interpolating values between property value changes. They provide timing controls and enable different interpolations through easing curves. @@ -222,95 +222,42 @@ You can create instances of preset animation components available in \l Library > \uicontrol Components > \uicontrol {Default Components} > - \uicontrol Animation to create \l{Animations}{animations} depending on - the type of the property that is to be animated and the behavior that - you want. + \uicontrol Animation to create animations depending on the type of the + property and the behavior that you want. + + For more information about \uicontrol Animation components and their + properties, see \l{Animations}. \table \header - \li Animation Type + \li Component \li Use Case \row - \li Property animation + \li \uicontrol {Property Animation} \li Applying animation when the value of a property changes. Color and number animations are property animation types for specific purposes. \row - \li Property action + \li \uicontrol {Property Action} \li Setting non-animated property values during an animation. \row - \li Color animation + \li \uicontrol {Color Animation} \li Applying animation when a color value changes. \row - \li Number animation + \li \uicontrol {Number Animation} \li Applying animation when a numerical value changes. \row - \li Parallel animation + \li \uicontrol {Parallel Animation} \li Running animations in parallel. \row - \li Sequential Animation + \li \uicontrol {Sequential Animation} \li Running animations sequentially. \row - \li Pause animation + \li \uicontrol {Pause Animation} \li Creating a step in a sequential animation where nothing happens for a specified duration. \row - \li Script action + \li \uicontrol {Script Action} \li Executing JavaScript during an animation. \endtable - - \section3 Property Animation and Action - - A property animation is applied when the value of a property changes. - - To immediately change a property value during an animation without - animating the property change, use a property action instead. This - is useful for setting non-animated property values during an animation. - - For example, you can use a sequential animation that contains two property - actions around a number animation to set the value of the opacity property - of an \l{Images}{Image} to \c 0.5, animate the width of the image, and then - set the opacity back to \c 1. - - \section3 Color Animation - - A color animation is a specialized property animation that defines an - animation to be applied when a color value changes. - - For example, you can apply animation to the color property of a \l Rectangle - to change its value from its current color to another color over a period - of time specified in milliseconds. - - \section3 Number Animation - - A number animation is a specialized property animation that defines an - animation to be applied when a numerical value changes. - - For example, you can apply animation to the x property of a \l Rectangle - to make it appear to move from its current position on the x axis to another - position over a period of time specified in milliseconds. - - \section3 Parallel and Sequential Animation - - Animations can run in parallel or in sequence. Parallel animations will - play a group of animations at the same time while sequential animations - play a group of animations in order, one after the other. - - For example, a banner component may have several icons or slogans to - display, one after the other. The opacity property could change to - \c 1.0 denoting an opaque object. Using a sequential animation, the - opacity animations will play after the preceding animation finishes, - whereas a parallel animation will play the animations at the same time. - - Once individual animations are placed into a group of parallel or sequential - animations, they can no longer be started and stopped independently. The - sequential or parallel animations must be started and stopped as a group. - - \section3 Pause Animation - - When used in a sequential animation, a pause animation is a step when - nothing happens, for a specified duration. - - For example, you could specify a 500-millisecond animation sequence, with - a 100-millisecond pause between two animations. */ diff --git a/doc/qtcreator/src/qtquick/qtquick-animation-types.qdoc b/doc/qtcreator/src/qtquick/qtquick-animation-types.qdoc index 101a489a63a..f6ff808a74a 100644 --- a/doc/qtcreator/src/qtquick/qtquick-animation-types.qdoc +++ b/doc/qtcreator/src/qtquick/qtquick-animation-types.qdoc @@ -34,43 +34,207 @@ \title Animations - To create an animation, use an appropriate animation type for the type of - the property that is to be animated, and apply the animation depending on - the type of behavior that is required. + To create an animation, use the appropriate animation type for the property + that is to be animated, and apply the animation depending on the type of + behavior that is required. - You can drag and drop the following components from \l Library > + You can drag-and-drop animation components from \l Library > \uicontrol Components > \uicontrol {Default Components} > - \uicontrol Animation to \l Navigator or \l {Form Editor}: + \uicontrol Animation to \l Navigator or \l {Form Editor} to + create instances of them. - \list - \li \l [QML] {ColorAnimation}{Color Animation} is a specialized - property animation that defines an animation to be applied when a - color value changes. - \li \l [QML] {NumberAnimation}{Number Animation} is a specialized - property animation that defines an animation to be applied when a - numerical value changes. - \li \l [QML] {ParallelAnimation}{Parallel Animation} enables - animations to be run in parallel. - \li \l [QML] {PauseAnimation}{Pause Animation} is used in a - sequential animation to create a step where nothing happens, for + You can achieve similar results by using different animation techniques. + For more information, see \l{Introduction to Animation Techniques}. + + \section1 Applying Animation + + A property animation is applied when the value of a property changes. + Color and number animations are property animation types for specific + purposes. Specify settings for animations in \l Properties > + \uicontrol {Animation Targets}. + + \section2 Animating Color Changes + + For example, you can apply animation to the value of the \uicontrol Color + property of an instance of a \l Rectangle component to change its value + from its current color to another color over a period of time specified in + milliseconds. + + \image qtquick-color-animation.gif "Color animation" + + First create an instance of the \uicontrol {Color Animation} component. + Select the component to animate in the \uicontrol Target field, and enter + the property to animate in the \uicontrol Property field. To animate several + properties, enter them into the \uicontrol Properties field separated by + commas. + + \image qtquick-properties-coloranimation.png "Color Animation properties" + + Select the original color in the \uicontrol {From color} field and the new + color in the \uicontrol {To color} field. Specify the duration of the + animation in the \uicontrol Duration field. + + \section2 Animating Changes in Numerical Values + + Similarly, to apply animation when a numerical value of a property changes, + create an instance of the \uicontrol {Number Animation} component. + + \image qtquick-properties-numberanimation.png "Number Animation properties" + + Select the original value in the \uicontrol From field and the new value in + the \uicontrol To field. Then specify the duration of the animation in the + \uicontrol Duration field. + + For example, you can apply animation to the value of the \uicontrol X + property of a \l Rectangle instance to make it appear to move from its + current position on the x axis to another position over a period of time + specified in milliseconds. To make the component appear to move both on + the x and y axis, enter x and y into the \uicontrol Properties field + separated by a comma. + + \image qtquick-number-animation.gif "Number animation" + + \section2 Setting Non-Animated Properties + + To immediately change a property value during an animation + without animating the property change, create an instance + of the \uicontrol {Property Action} component instead, and + set the value in the \uicontrol Value field. This is useful + for setting non-animated property values during an animation. + + \image qtquick-properties-propertyaction.png "Property Action properties" + + For example, you can create an instance of the + \uicontrol {Sequential Animation} component that contains two instances + of the \uicontrol {Property Action} component around an instance of the + \uicontrol {Number Animation} component. The first property action sets + the \uicontrol Opacity property of a \l{Rectangle} to \c 0.5, the number + animation changes the width of the image, and the second property action + sets the opacity back to \c 1. + + \image qtquick-property-action.gif "Sequential property actions and number animation" + + \section1 Playing Animations + + Specify settings for playing animations in the \uicontrol Animation group. + + \image qtquick-properties-animation.png "Animation properties" + + To run animations automatically, select the \uicontrol Running + check box. Animations are run for the time you specify in the + \uicontrol Duration field. + + You can connect the running property of an animation to a signal emitted + by a component to play the animation when users click a button, for + example. For more information, see \l{Connecting Components to Signals}. + + To run animations several times in a loop, set the number of times they + should play in the \uicontrol Loops field. Set the value to -1 to have + the animation continuously repeat until it is explicitly stopped. + + To specify that animations should run to completion when they are stopped, + select the \uicontrol {Run to end} check box. This behavior is most useful + when the \uicontrol Loops property is set, as the animation will finish + playing normally but not restart. + + All animations defined for a component are run in parallel, + unless you include them in a \uicontrol {Parallel Animation} or + \uicontrol {Sequential Animation} component for managing them as a + group. + + To pause animations, select the \inlineimage icons/pause-icon.png + (\uicontrol Paused) check box. + + To attach an \l{Editing Easing Curves}{easing curve} to + the animation, select the \inlineimage curve_editor.png + (\uicontrol {Easing Curve Editor}) button in the + \uicontrol {Easing Curve} field. + + \section2 Playing Groups of Animations + + You can create several animations that can run in parallel or in sequence. + To manage a group of animations that will play at the same time, create an + instance of a \uicontrol {Parallel Animation} component and drag-and-drop + the other animations to it. To play the animations in the specified order, + one after the other, create an instance of a + \uicontrol {Sequential Animation} instead. + + For example, a banner component may have several icons or slogans to + display, one after the other. The value of the \uicontrol Opacity property + could change to \c 1.0 denoting an opaque object. Using a sequential + animation, each opacity animation will play after the preceding animation + finishes, whereas using a parallel animation will play the animations at + the same time. + + Once individual animations are placed into a group of parallel or sequential + animations, they can no longer be started and stopped independently. The + sequential or parallel animations must be started and stopped as a group. + + When used in a \uicontrol {Sequential Animation}, a + \uicontrol {Pause Animation} is a step when nothing + happens, for a specified duration. To specify a pause + between two animations, select the \uicontrol Paused + check box and specify the duration of the pause in the + \uicontrol Duration field. + + \section1 Performance Considerations + + \QDS enables you to use fluidity and dynamic transitions as well as visual + effects to great effect in a UI. However, you need to take some care when + using some of the supported features because they can affect the + performance of the UI. + + In general, animating a property will cause any bindings which reference + that property to be re-evaluated. Usually, this is what is desired, but in + some cases it may be better to disable the binding prior to performing + the animation and then reassign the binding once the animation has + completed. + + Avoid running JavaScript during animation. For example, running a complex + JavaScript expression for each frame of an x property animation should be + avoided. + + Take special care when creating instances of the \uicontrol {Script Action} + component because script animations are run in the main thread and can + therefore cause frames to be skipped if they take too long to complete. + + \section1 Summary of Animation Components + + The following table lists the components that you can use to animate + component properties programmatically. They are available in \l Library + > \uicontrol Components > \uicontrol {Default Components} > + \uicontrol Animation + + \table + \header + \li Component + \li Use Case + \row + \li \uicontrol {Property Animation} + \li Applying animation when the value of a property changes. Color + and number animations are property animation types for specific + purposes. + \row + \li \uicontrol {Property Action} + \li Setting non-animated property values during an animation. + \row + \li \uicontrol {Color Animation} + \li Applying animation when a color value changes. + \row + \li \uicontrol {Number Animation} + \li Applying animation when a numerical value changes. + \row + \li \uicontrol {Parallel Animation} + \li Running animations in parallel. + \row + \li \uicontrol {Sequential Animation} + \li Running animations sequentially. + \row + \li \uicontrol {Pause Animation} + \li Creating a step in a sequential animation where nothing happens for a specified duration. - \li \l [QML] {PropertyAction}{Property Action} immediately changes - a property value during an animation, without animating the - property change. - \li \l [QML] {PropertyAnimation}{Property Animation} animates - changes in the value of a property. - \li \l [QML] {ScriptAction}{Script Action} defines scripts to be - run during an animation. - \li \l [QML] {SequentialAnimation}{Sequential Animation} enables - animations to be run sequentially. - \endlist - - For more information about using the components, see - \l{Animation and Transitions in Qt Quick}. - - For more information about animating properties in the \l Timeline - view, see \l{Creating Animations}. For more information about animating - property changes in states, see \l{Animating Transitions Between States}. - -//! [qtquick animation types] + \row + \li \uicontrol {Script Action} + \li Executing JavaScript during an animation. + \endtable */ From 85df8989588efaa2f7432e1b4c48eb31bb13504f Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Mon, 14 Jun 2021 17:03:06 +0200 Subject: [PATCH 070/149] Doc: Add descriptions of concepts and terms Fixes: QDS-3475 Change-Id: I82b9c10fe506c0bdc4890e0a53fdd442a80ddb49 Reviewed-by: Vikas Pachdha --- .../src/qtdesignstudio-terms.qdoc | 198 ++++++++++++++++-- 1 file changed, 181 insertions(+), 17 deletions(-) diff --git a/doc/qtdesignstudio/src/qtdesignstudio-terms.qdoc b/doc/qtdesignstudio/src/qtdesignstudio-terms.qdoc index e1460b6f4f1..d31bbc0cf47 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio-terms.qdoc +++ b/doc/qtdesignstudio/src/qtdesignstudio-terms.qdoc @@ -34,18 +34,28 @@ \list \li \l{glossary-asset}{Asset} + \li \l{glossary-binding}{Binding} \li \l{glossary-component}{Component} + \li \l{glossary-connection}{Connection} \li \l{glossary-device}{Device} - \li \l{glossary-Project}{Project} + \li \l{glossary-mode}{Mode} + \li \l{glossary-project}{Project} + \li \l{glossary-property}{Property} + \li \l{glossary-signal}{Signal} + \li \l{glossary-state}{State} + \li \l{glossary-transition}{Transition} \endlist \section1 Asset \target glossary-asset An \e asset is an image, font file, 3D model, or other supported file that - you add to your \e project. + you add to your \l{glossary-project}{project}. - Assets are packaged with \e components for delivery to users. + \image qtquick-assets-tab.png "Assets in Library" + + Assets are packaged with \l{glossary-component}{components} for delivery + to users. Read more about assets: @@ -53,6 +63,34 @@ \li \l{Assets} \endlist + \section1 Binding + \target glossary-binding + + A \e binding is a declarative way of specifying the value of a + \l{glossary-property}{property}. Binding allows a property value + to be expressed as a JavaScript expression that defines the value + relative to other property values or data accessible in the + application. The property value is automatically updated if the + other properties or data values change. + + At its simplest, a binding may be a reference to another property. For + example, the height of a \l{glossary-component}{component} can be bound + to the height of its parent, so that when the parent height changes, the + component height is adjusted automatically. Similarly, the opacity of a + component can be bound to the opacity of its parent component. + + \image qtquick-connection-editor-assignment.png "Binding Editor" + + Property bindings are created implicitly whenever a property is assigned a + JavaScript expression. + + Read more about bindings: + + \list + \li \l{Adding Bindings Between Properties} + \li \l{Setting Bindings} + \endlist + \section1 Component \target glossary-component @@ -62,14 +100,17 @@ creating instances of them. These are similar to \e Symbols in Sketch or \e Prefab in Unity. + \image qtquick-components-tab.png "Preset components in Library" + Some of the \l {Component Types}{preset components} represent simple shapes, text, or images, while others represent complex UI controls with full functionality, such as spin boxes or sliders. You can also add instances of - preset \l {Using 3D Components}{3D components} to your UIs. You can find all + preset \l {3D Components}{3D components} to your UIs. You can find all the preset components in \l Library. To build \l {Creating Component Instances}{your own components}, you can - modify the \e properties of the component instances and combine them. + modify the \l{glossary-property}{properties} of the component instances + and combine them. A component is specified within one file (with the file extension \e ui.qml or \e .qml). For example, a Button component may be defined @@ -87,6 +128,24 @@ \li \l {Creating Custom Components} \endlist + \section1 Connection + \target glossary-connection + + A \e connection can be created between a \l{glossary-component}{component} + and \l{glossary-signal}{signal} to determine how the UI should react to + application events. Another way to create connections between components + is to create \l{glossary-binding}{bindings} between the values of their + \l{glossary-property}{properties}. + + \image qmldesigner-connections.png "Connection View" + + Read more about connections: + + \list + \li \l{Connection View} + \li \l{Adding Connections} + \endlist + \section1 Device \target glossary-device @@ -98,24 +157,138 @@ \li \l{Previewing on Devices} \endlist + \section1 Mode + \target glossary-mode + + A \e mode adapts the \QDS UI to the different UI design + tasks at hand. Each mode has its own view that shows only the information + required for performing a particular task, and provides only the most + relevant features and functions related to it. As a result, the majority + of the \QDS window area is always dedicated to the actual task. + + For a designer, the most important modes are \uicontrol Design for the + actual work, \uicontrol Welcome for opening examples and tutorials, and + \uicontrol Help for reading documentation. The other modes are mostly + needed for application development. + + \image studio-design-mode.png "Design mode" + + Read more about modes: + + \list + \li \l{Selecting Modes} + \li \l{Design Views} + \endlist + \section1 Project \target glossary-project - A project is a container for the \e components and \e assets that you - use in your UI. You can \e package the UI and preview or run it on - different operating systems on the desktop or a \l{glossary-device}{device}. + A project is a container for the \l{glossary-component}{components} and + \l{glossary-asset}{assets} that you use in your UI. You can \e package + the UI and preview or run it on different operating systems on the desktop + or a \l{glossary-device}{device}. You use templates to create different types of projects according to your needs. The templates add preset components to the project by default. For example, if you create a 3D project, preset 3D components are added to it. You can add more preset components in \uicontrol Library. + \image studio-project-wizards.png "New File or Project dialog" + Read more about projects: \list \li \l{Creating Projects} \endlist + \section1 Property + \target glossary-property + + A \e property is an attribute of a \l{glossary-component}{component} + that can be assigned a static value or bound to a dynamic expression. + A property's value can be read by other components. Generally, it can + also be modified by another component, unless a particular component + type has explicitly disallowed this for a specific property. + + \image qtquick-item-properties-common.png "Properties view" + + Read more about properties: + + \list + \li \l{Properties} + \li \l{Preset Components} + \li \l{Specifying Component Properties} + \li \l{Adding Bindings Between Properties} + \li \l{Specifying Dynamic Properties} + \endlist + + \section1 Signal + \target glossary-signal + + A \e signal represents an application event, such as a user clicking a + button or the value of a \l{glossary-property}{property} of a + \l{glossary-component}{component} changing. The application needs this + information to perform actions or to relay it to other applications. + + Components have predefined signals that are emitted when users interact with + the application. For example, the \l {Mouse Area} component has a \c clicked + signal that is emitted whenever the mouse is clicked within the area. Since + the signal name is \c clicked, the signal handler for receiving this signal + is named \c onClicked. + + \image washingmachineui-connections.png "Connection View, Connections tab" + + Further, a signal is automatically emitted when the value of a + \l{glossary-property}{property} changes. + + Read more about signals: + + \list + \li \l{Connecting Components to Signals} + \li \l{Mouse Area} + \endlist + + \section1 State + \target glossary-state + + The \e state of a particular visual \l{glossary-component}{component} is + the set of information that describes how and where the individual parts + of the component are displayed within it, and all the data associated with + that state. Most visual components in a UI will have a limited number of + states, each with well-defined \l{glossary-property}{properties}. + + For example, an element in a list may be either selected or not, and if + selected, it may either be the currently active single selection or it may + be part of a selection group. Each of those states may have certain + associated visual appearance (neutral highlighted, expanded, and so forth). + + Similarly, the appearance of a button can change to indicate a \e pressed + state. + + \image studio-custom-button.gif "Button states" + + Read more about states: + + \list + \li \l{States} + \li \l{Adding States} + \endlist + + \section1 Transition + \target glossary-transition + + When a visual \l{glossary-component}{component} transitions from one + \l{glossary-state}{state} to another, its appearance changes. A + \e transition is an \e edge between two states. It may trigger other + events to occur, as other parts of the application may have behavior + that is triggered when a certain state is entered or left. + + Read more about transitions: + + \list + \li \l{Transition Editor} + \li \l{Animating Transitions Between States} + \endlist \omit ## The following terms might or might not come back, but for now, the @@ -135,15 +308,6 @@ Each kit consists of a set of values that define one environment, such as a \l{glossary-device} {device} to preview the UI on. - \section1 Mode - \target glossary-mode - - Adapts the \QDS UI to the different UI design - tasks at hand. Each mode has its own view that shows only the information - required for performing a particular task, and provides only the most - relevant features and functions related to it. As a result, the majority - of the \QDS window area is always dedicated to the actual task. - \section1 Run Configuration \target glossary-run-config From 0df21e8f1bc2015bc3302141e83178245bed9ced Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Tue, 8 Jun 2021 10:15:06 +0200 Subject: [PATCH 071/149] Doc: Update supported design tools and versions Change-Id: I15eb3a1f395b485a3ca19ae73bdf6815fd0d5935 Reviewed-by: Vikas Pachdha --- doc/qtdesignstudio/src/qtdesignstudio-platforms.qdoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/qtdesignstudio/src/qtdesignstudio-platforms.qdoc b/doc/qtdesignstudio/src/qtdesignstudio-platforms.qdoc index f22c988c771..b0f2e3d1d9f 100644 --- a/doc/qtdesignstudio/src/qtdesignstudio-platforms.qdoc +++ b/doc/qtdesignstudio/src/qtdesignstudio-platforms.qdoc @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Design Studio documentation. @@ -58,7 +58,8 @@ \QB is available for the following design tools: \list - \li Adobe Photoshop version 20.0.0 - \li Sketch 55.1 + \li Adobe Photoshop version 22.3.1 + \li Figma version 97.x + \li Sketch version 72.2 \endlist */ From bce19b73c55563323505b94e4bd00065b876f1b2 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 29 Jun 2021 17:57:49 +0200 Subject: [PATCH 072/149] TextEditor: FilePathify Repository *highlightRepository() Change-Id: I66ed44e199fa26b28e23497c2650224e6331baa7 Reviewed-by: David Schulz --- src/plugins/texteditor/highlighter.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/texteditor/highlighter.cpp b/src/plugins/texteditor/highlighter.cpp index 84243a516d1..6d51eee5922 100644 --- a/src/plugins/texteditor/highlighter.cpp +++ b/src/plugins/texteditor/highlighter.cpp @@ -62,9 +62,9 @@ KSyntaxHighlighting::Repository *highlightRepository() if (!repository) { repository = new KSyntaxHighlighting::Repository(); repository->addCustomSearchPath(TextEditorSettings::highlighterSettings().definitionFilesPath()); - QDir dir(Core::ICore::resourcePath("generic-highlighter/syntax").toDir()); - if (dir.exists() && dir.cdUp()) - repository->addCustomSearchPath(dir.path()); + const Utils::FilePath dir = Core::ICore::resourcePath("generic-highlighter/syntax"); + if (dir.exists()) + repository->addCustomSearchPath(dir.parentDir().path()); } return repository; } From d97c10a42e8e92b56ff540129b0e0d5ede414a87 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 29 Jun 2021 18:00:43 +0200 Subject: [PATCH 073/149] TextEditor: highlighter.cpp code cosmetics static, namespaces. Change-Id: Id824f6979ce6c6ec6bbe4847c808c7ae47d26695 Reviewed-by: David Schulz --- src/plugins/texteditor/highlighter.cpp | 41 +++++++++++++------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/plugins/texteditor/highlighter.cpp b/src/plugins/texteditor/highlighter.cpp index 6d51eee5922..33d5e971cc3 100644 --- a/src/plugins/texteditor/highlighter.cpp +++ b/src/plugins/texteditor/highlighter.cpp @@ -44,25 +44,26 @@ #include #include -#include #include #include -using namespace TextEditor; +using namespace Utils; + +namespace TextEditor { static Q_LOGGING_CATEGORY(highlighterLog, "qtc.editor.highlighter", QtWarningMsg) -static const char kDefinitionForMimeType[] = "definitionForMimeType"; -static const char kDefinitionForExtension[] = "definitionForExtension"; -static const char kDefinitionForFilePath[] = "definitionForFilePath"; +const char kDefinitionForMimeType[] = "definitionForMimeType"; +const char kDefinitionForExtension[] = "definitionForExtension"; +const char kDefinitionForFilePath[] = "definitionForFilePath"; -KSyntaxHighlighting::Repository *highlightRepository() +static KSyntaxHighlighting::Repository *highlightRepository() { static KSyntaxHighlighting::Repository *repository = nullptr; if (!repository) { repository = new KSyntaxHighlighting::Repository(); repository->addCustomSearchPath(TextEditorSettings::highlighterSettings().definitionFilesPath()); - const Utils::FilePath dir = Core::ICore::resourcePath("generic-highlighter/syntax"); + const FilePath dir = Core::ICore::resourcePath("generic-highlighter/syntax"); if (dir.exists()) repository->addCustomSearchPath(dir.parentDir().path()); } @@ -128,20 +129,18 @@ Highlighter::Definitions Highlighter::definitionsForDocument(const TextDocument // If we check the MIME type first and then skip the pattern, the definition for "*.rb.xml" is // never considered. // The KSyntaxHighlighting CLI also completely ignores MIME types. - const Utils::FilePath &filePath = document->filePath(); + const FilePath &filePath = document->filePath(); Definitions definitions = definitionsForFileName(filePath); if (definitions.isEmpty()) { // check for *.in filename since those are usually used for // cmake configure_file input filenames without the .in extension - if (filePath.endsWith(".in")) { - definitions = definitionsForFileName( - Utils::FilePath::fromString(filePath.completeBaseName())); - } + if (filePath.endsWith(".in")) + definitions = definitionsForFileName(FilePath::fromString(filePath.completeBaseName())); if (filePath.fileName() == "qtquickcontrols2.conf") definitions = definitionsForFileName(filePath.stringAppended(".ini")); } if (definitions.isEmpty()) { - const Utils::MimeType &mimeType = Utils::mimeTypeForName(document->mimeType()); + const MimeType &mimeType = Utils::mimeTypeForName(document->mimeType()); if (mimeType.isValid()) definitions = definitionsForMimeType(mimeType.name()); } @@ -171,7 +170,7 @@ Highlighter::Definitions Highlighter::definitionsForMimeType(const QString &mime return definitions; } -Highlighter::Definitions Highlighter::definitionsForFileName(const Utils::FilePath &fileName) +Highlighter::Definitions Highlighter::definitionsForFileName(const FilePath &fileName) { Definitions definitions = highlightRepository()->definitionsForFileName(fileName.fileName()).toList(); @@ -197,7 +196,7 @@ void Highlighter::rememberDefinitionForDocument(const Highlighter::Definition &d if (!definition.isValid()) return; const QString &mimeType = document->mimeType(); - const Utils::FilePath &path = document->filePath(); + const FilePath &path = document->filePath(); const QString &fileExtension = path.completeSuffix(); QSettings *settings = Core::ICore::settings(); settings->beginGroup(Constants::HIGHLIGHTER_SETTINGS_CATEGORY); @@ -233,7 +232,7 @@ void Highlighter::clearDefinitionForDocumentCache() settings->endGroup(); } -void Highlighter::addCustomHighlighterPath(const Utils::FilePath &path) +void Highlighter::addCustomHighlighterPath(const FilePath &path) { highlightRepository()->addCustomSearchPath(path.toString()); } @@ -332,18 +331,18 @@ void Highlighter::applyFormat(int offset, int length, const KSyntaxHighlighting: const QColor textColor = format.textColor(defaultTheme); if (format.hasBackgroundColor(defaultTheme)) { const QColor backgroundColor = format.hasBackgroundColor(defaultTheme); - if (Utils::StyleHelper::isReadableOn(backgroundColor, textColor)) { + if (StyleHelper::isReadableOn(backgroundColor, textColor)) { qformat.setForeground(textColor); qformat.setBackground(backgroundColor); - } else if (Utils::StyleHelper::isReadableOn(qformat.background().color(), textColor)) { + } else if (StyleHelper::isReadableOn(qformat.background().color(), textColor)) { qformat.setForeground(textColor); } - } else if (Utils::StyleHelper::isReadableOn(qformat.background().color(), textColor)) { + } else if (StyleHelper::isReadableOn(qformat.background().color(), textColor)) { qformat.setForeground(textColor); } } else if (format.hasBackgroundColor(defaultTheme)) { const QColor backgroundColor = format.hasBackgroundColor(defaultTheme); - if (Utils::StyleHelper::isReadableOn(backgroundColor, qformat.foreground().color())) + if (StyleHelper::isReadableOn(backgroundColor, qformat.foreground().color())) qformat.setBackground(backgroundColor); } @@ -396,3 +395,5 @@ void Highlighter::applyFolding(int offset, data->setFoldingIndent(TextDocumentLayout::braceDepth(block)); } } + +} // TextEditor From bb195a95c71bfa75f2b4f7f39f5073f03dd6b62b Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Mon, 28 Jun 2021 11:57:08 +0200 Subject: [PATCH 074/149] Remove ExtraCompilerFactoryObserver Was only used by PchManager. Change-Id: I610c1d4215613eb494e48225329660c1e2680d49 Reviewed-by: Christian Kandeler Reviewed-by: Qt CI Bot --- src/plugins/projectexplorer/extracompiler.cpp | 20 +------------------ src/plugins/projectexplorer/extracompiler.h | 18 ----------------- src/plugins/qtsupport/qscxmlcgenerator.cpp | 2 -- src/plugins/qtsupport/uicgenerator.cpp | 2 -- 4 files changed, 1 insertion(+), 41 deletions(-) diff --git a/src/plugins/projectexplorer/extracompiler.cpp b/src/plugins/projectexplorer/extracompiler.cpp index c76bcc41eb8..6dd653774ec 100644 --- a/src/plugins/projectexplorer/extracompiler.cpp +++ b/src/plugins/projectexplorer/extracompiler.cpp @@ -53,7 +53,7 @@ namespace ProjectExplorer { Q_GLOBAL_STATIC(QThreadPool, s_extraCompilerThreadPool); Q_GLOBAL_STATIC(QList, factories); -Q_GLOBAL_STATIC(QVector, observers); + class ExtraCompilerPrivate { public: @@ -322,14 +322,6 @@ ExtraCompilerFactory::~ExtraCompilerFactory() factories->removeAll(this); } -void ExtraCompilerFactory::annouceCreation(const Project *project, - const Utils::FilePath &source, - const Utils::FilePaths &targets) -{ - for (ExtraCompilerFactoryObserver *observer : qAsConst(*observers)) - observer->newExtraCompiler(project, source, targets); -} - QList ExtraCompilerFactory::extraCompilerFactories() { return *factories(); @@ -465,14 +457,4 @@ void ProcessExtraCompiler::cleanUp() setCompileTime(QDateTime::currentDateTime()); } -ExtraCompilerFactoryObserver::ExtraCompilerFactoryObserver() -{ - observers->push_back(this); -} - -ExtraCompilerFactoryObserver::~ExtraCompilerFactoryObserver() -{ - observers->removeOne(this); -} - } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/extracompiler.h b/src/plugins/projectexplorer/extracompiler.h index 884c1bf6830..5c0854a3b4b 100644 --- a/src/plugins/projectexplorer/extracompiler.h +++ b/src/plugins/projectexplorer/extracompiler.h @@ -143,20 +143,6 @@ private: QFutureWatcher *m_watcher = nullptr; }; -class PROJECTEXPLORER_EXPORT ExtraCompilerFactoryObserver -{ - friend class ExtraCompilerFactory; - -protected: - ExtraCompilerFactoryObserver(); - ~ExtraCompilerFactoryObserver(); - - virtual void newExtraCompiler(const Project *project, - const Utils::FilePath &source, - const Utils::FilePaths &targets) - = 0; -}; - class PROJECTEXPLORER_EXPORT ExtraCompilerFactory : public QObject { Q_OBJECT @@ -172,10 +158,6 @@ public: const Utils::FilePaths &targets) = 0; - void annouceCreation(const Project *project, - const Utils::FilePath &source, - const Utils::FilePaths &targets); - static QList extraCompilerFactories(); }; diff --git a/src/plugins/qtsupport/qscxmlcgenerator.cpp b/src/plugins/qtsupport/qscxmlcgenerator.cpp index 3697bc05195..2c7194f254a 100644 --- a/src/plugins/qtsupport/qscxmlcgenerator.cpp +++ b/src/plugins/qtsupport/qscxmlcgenerator.cpp @@ -146,8 +146,6 @@ ExtraCompiler *QScxmlcGeneratorFactory::create( const Project *project, const Utils::FilePath &source, const Utils::FilePaths &targets) { - annouceCreation(project, source, targets); - return new QScxmlcGenerator(project, source, targets, this); } diff --git a/src/plugins/qtsupport/uicgenerator.cpp b/src/plugins/qtsupport/uicgenerator.cpp index 4af7be07e1d..c0979ab13d5 100644 --- a/src/plugins/qtsupport/uicgenerator.cpp +++ b/src/plugins/qtsupport/uicgenerator.cpp @@ -107,8 +107,6 @@ ExtraCompiler *UicGeneratorFactory::create(const Project *project, const Utils::FilePath &source, const Utils::FilePaths &targets) { - annouceCreation(project, source, targets); - return new UicGenerator(project, source, targets, this); } From eef0769d7aca214251f2a9770e50fb0f5cf61fe3 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Wed, 30 Jun 2021 08:05:51 +0200 Subject: [PATCH 075/149] Docker: Remove unused lambda captures Avoids warnings. Change-Id: I9bf836ff24fa6524571d0303e544189340dede32 Reviewed-by: hjk --- src/plugins/docker/dockerdevice.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 01c8bca1dac..271ab489e7f 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -304,7 +304,7 @@ public: m_runAsOutsideUser->setChecked(dockerDevice->data().useLocalUidGid); m_runAsOutsideUser->setEnabled(HostOsInfo::isLinuxHost()); - connect(m_runAsOutsideUser, &QCheckBox::toggled, this, [this, dockerDevice](bool on) { + connect(m_runAsOutsideUser, &QCheckBox::toggled, this, [dockerDevice](bool on) { dockerDevice->data().useLocalUidGid = on; }); @@ -314,7 +314,7 @@ public: "mapped one-to-one into the docker container.")); m_pathsLineEdit->setText(dockerDevice->data().mounts.join(';')); - connect(m_pathsLineEdit, &QLineEdit::textChanged, this, [this, dockerDevice](const QString &text) { + connect(m_pathsLineEdit, &QLineEdit::textChanged, this, [dockerDevice](const QString &text) { dockerDevice->data().mounts = text.split(';'); }); From 673d596c841dd0691a03375c5a633c40d0cfaf67 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Fri, 4 Jun 2021 12:40:26 +0200 Subject: [PATCH 076/149] ClangCodeModel: Provide tooltips via clangd Note that we temporarily lose the ability to hover over an include and get the full path of the header file. This is a valuable feature that we need to restore, preferably by fixing clangd itself. Fixing the remaining few test failures would likely require more complicated code as well as additional LSP round-trips, and as of now I'm not convinced it is worth the effort. Change-Id: I08c72c4bd1268bbd67baeb57bbfd29d9b11303a5 Reviewed-by: David Schulz --- .../languageserverprotocol/jsonrpcmessages.h | 2 +- .../clangcodemodel/clangcodemodelplugin.cpp | 1 + src/plugins/clangcodemodel/clangdclient.cpp | 225 +++++++++++++++++- src/plugins/clangcodemodel/clangdclient.h | 5 + .../clangcodemodel/clanghoverhandler.cpp | 7 + .../clangcodemodel/test/clangdtests.cpp | 139 +++++++++++ src/plugins/clangcodemodel/test/clangdtests.h | 11 + .../test/data/clangtestdata.qrc | 2 + .../test/data/tooltips/tooltips.cpp | 212 +++++++++++++++++ .../test/data/tooltips/tooltips.pro | 3 + .../languageclienthoverhandler.cpp | 27 ++- .../languageclienthoverhandler.h | 15 +- src/plugins/texteditor/texteditor.cpp | 7 + src/plugins/texteditor/texteditor.h | 4 + 14 files changed, 651 insertions(+), 9 deletions(-) create mode 100644 src/plugins/clangcodemodel/test/data/tooltips/tooltips.cpp create mode 100644 src/plugins/clangcodemodel/test/data/tooltips/tooltips.pro diff --git a/src/libs/languageserverprotocol/jsonrpcmessages.h b/src/libs/languageserverprotocol/jsonrpcmessages.h index f0247e3c56b..7a7ab2df89a 100644 --- a/src/libs/languageserverprotocol/jsonrpcmessages.h +++ b/src/libs/languageserverprotocol/jsonrpcmessages.h @@ -241,7 +241,7 @@ public: return Utils::nullopt; return Utils::make_optional(Result(result)); } - void setResult(const Result &result) { m_jsonObject.insert(resultKey, result); } + void setResult(const Result &result) { m_jsonObject.insert(resultKey, QJsonValue(result)); } void clearResult() { m_jsonObject.remove(resultKey); } using Error = ResponseError; diff --git a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp index 2886f637cb3..59bcc4d1914 100644 --- a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp +++ b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp @@ -208,6 +208,7 @@ QVector ClangCodeModelPlugin::createTestObjects() const new Tests::ClangdTestFindReferences, new Tests::ClangdTestFollowSymbol, new Tests::ClangdTestLocalReferences, + new Tests::ClangdTestTooltips, }; } #endif diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 47a9809c1c0..0a3c33327d3 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -176,18 +176,50 @@ public: return true; } + bool isNamespace() const { return role() == "declaration" && kind() == "Namespace"; } + QString type() const { const Utils::optional arcanaString = arcana(); if (!arcanaString) return {}; - const int quote1Offset = arcanaString->indexOf('\''); + return typeFromPos(*arcanaString, 0); + } + + QString typeFromPos(const QString &s, int pos) const + { + const int quote1Offset = s.indexOf('\'', pos); if (quote1Offset == -1) return {}; - const int quote2Offset = arcanaString->indexOf('\'', quote1Offset + 1); + const int quote2Offset = s.indexOf('\'', quote1Offset + 1); if (quote2Offset == -1) return {}; - return arcanaString->mid(quote1Offset + 1, quote2Offset - quote1Offset - 1); + if (s.mid(quote2Offset + 1, 2) == ":'") + return typeFromPos(s, quote2Offset + 2); + return s.mid(quote1Offset + 1, quote2Offset - quote1Offset - 1); + } + + HelpItem::Category qdocCategoryForDeclaration(HelpItem::Category fallback) + { + const auto childList = children(); + if (!childList || childList->size() < 2) + return fallback; + const AstNode c1 = childList->first(); + if (c1.role() != "type" || c1.kind() != "Auto") + return fallback; + QList typeCandidates = {childList->at(1)}; + while (!typeCandidates.isEmpty()) { + const AstNode n = typeCandidates.takeFirst(); + if (n.role() == "type") { + if (n.kind() == "Enum") + return HelpItem::Enum; + if (n.kind() == "Record") + return HelpItem::ClassOrNamespace; + return fallback; + } + typeCandidates << n.children().value_or(QList()); + } + return fallback; } // Returns true <=> the type is "recursively const". @@ -652,6 +684,10 @@ public: QString searchTermFromCursor(const QTextCursor &cursor) const; + void setHelpItemForTooltip(const MessageId &token, const QString &fqn = {}, + HelpItem::Category category = HelpItem::Unknown, + const QString &type = {}); + ClangdClient * const q; QHash runningFindUsages; Utils::optional followSymbolData; @@ -695,6 +731,11 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir) const auto hideDiagsHandler = []{ ClangDiagnosticManager::clearTaskHubIssues(); }; setDiagnosticsHandlers(textMarkCreator, hideDiagsHandler); + hoverHandler()->setHelpItemProvider([this](const HoverRequest::Response &response, + const DocumentUri &uri) { + gatherHelpItemForTooltip(response, uri); + }); + connect(this, &Client::workDone, this, [this, project](const ProgressToken &token) { const QString * const val = Utils::get_if(&token); if (val && *val == indexingToken()) { @@ -1226,6 +1267,154 @@ void ClangdClient::findLocalUsages(TextEditor::TextDocument *document, const QTe symbolSupport().findLinkAt(document, cursor, std::move(gotoDefCallback), true); } +void ClangdClient::gatherHelpItemForTooltip(const HoverRequest::Response &hoverResponse, + const DocumentUri &uri) +{ + // Macros aren't locatable via the AST, so parse the formatted string. + if (const Utils::optional result = hoverResponse.result()) { + const HoverContent content = result->content(); + const MarkupContent * const markup = Utils::get_if(&content); + if (markup) { + const QString markupString = markup->content(); + static const QString magicMacroPrefix = "### macro `"; + if (markupString.startsWith(magicMacroPrefix)) { + const int nameStart = magicMacroPrefix.length(); + const int closingQuoteIndex = markupString.indexOf('`', nameStart); + if (closingQuoteIndex != -1) { + const QString macroName = markupString.mid(nameStart, + closingQuoteIndex - nameStart); + d->setHelpItemForTooltip(hoverResponse.id(), macroName, HelpItem::Macro); + return; + } + } + } + } + + AstRequest req((AstParams(TextDocumentIdentifier(uri)))); + req.setResponseCallback([this, uri, hoverResponse](const AstRequest::Response &response) { + const MessageId id = hoverResponse.id(); + const AstNode ast = response.result().value_or(AstNode()); + const Range range = hoverResponse.result()->range().value_or(Range()); + const QList path = getAstPath(ast, range); + if (path.isEmpty()) { + d->setHelpItemForTooltip(id); + return; + } + AstNode node = path.last(); + if (node.role() == "expression" && node.kind() == "ImplicitCast") { + const Utils::optional> children = node.children(); + if (children && !children->isEmpty()) + node = children->first(); + } + while (node.kind() == "Qualified") { + const Utils::optional> children = node.children(); + if (children && !children->isEmpty()) + node = children->first(); + } + if (clangdLog().isDebugEnabled()) + node.print(0); + + QString type = node.type(); + const auto stripTemplatePartOffType = [&type] { + const int angleBracketIndex = type.indexOf('<'); + if (angleBracketIndex != -1) + type = type.left(angleBracketIndex); + }; + + const bool isMemberFunction = node.role() == "expression" && node.kind() == "Member" + && (node.arcanaContains("member function") || type.contains('(')); + const bool isFunction = node.role() == "expression" && node.kind() == "DeclRef" + && type.contains('('); + if (isMemberFunction || isFunction) { + const TextDocumentPositionParams params(TextDocumentIdentifier(uri), range.start()); + SymbolInfoRequest symReq(params); + symReq.setResponseCallback([this, id, type, isFunction] + (const SymbolInfoRequest::Response &response) { + qCDebug(clangdLog) << "handling symbol info reply"; + QString fqn; + if (const auto result = response.result()) { + if (const auto list = Utils::get_if>(&result.value())) { + if (!list->isEmpty()) { + const SymbolDetails &sd = list->first(); + fqn = sd.containerName() + sd.name(); + } + } + } + + // Unfortunately, the arcana string contains the signature only for + // free functions, so we can't distinguish member function overloads. + // But since HtmlDocExtractor::getFunctionDescription() is always called + // with mainOverload = true, such information would get ignored anyway. + d->setHelpItemForTooltip(id, fqn, HelpItem::Function, isFunction ? type : "()"); + }); + sendContent(symReq); + return; + } + if ((node.role() == "expression" && node.kind() == "DeclRef") + || (node.role() == "declaration" + && (node.kind() == "Var" || node.kind() == "ParmVar" + || node.kind() == "Field"))) { + if (node.arcanaContains("EnumConstant")) { + d->setHelpItemForTooltip(id, node.detail().value_or(QString()), + HelpItem::Enum, type); + return; + } + stripTemplatePartOffType(); + type.remove("&").remove("*").remove("const ").remove(" const") + .remove("volatile ").remove(" volatile"); + type = type.simplified(); + if (type != "int" && !type.contains(" int") + && type != "char" && !type.contains(" char") + && type != "double" && !type.contains(" double") + && type != "float" && type != "bool") { + d->setHelpItemForTooltip(id, type, node.qdocCategoryForDeclaration( + HelpItem::ClassOrNamespace)); + } else { + d->setHelpItemForTooltip(id); + } + return; + } + if (node.isNamespace()) { + QString ns = node.detail().value_or(QString()); + for (auto it = path.rbegin() + 1; it != path.rend(); ++it) { + if (it->isNamespace()) { + const QString name = it->detail().value_or(QString()); + if (!name.isEmpty()) + ns.prepend("::").prepend(name); + } + } + d->setHelpItemForTooltip(hoverResponse.id(), ns, HelpItem::ClassOrNamespace); + return; + } + if (node.role() == "type") { + if (node.kind() == "Enum") { + d->setHelpItemForTooltip(id, node.detail().value_or(QString()), HelpItem::Enum); + } else if (node.kind() == "Record" || node.kind() == "TemplateSpecialization") { + stripTemplatePartOffType(); + d->setHelpItemForTooltip(id, type, HelpItem::ClassOrNamespace); + } else if (node.kind() == "Typedef") { + d->setHelpItemForTooltip(id, type, HelpItem::Typedef); + } else { + d->setHelpItemForTooltip(id); + } + return; + } + if (node.role() == "expression" && node.kind() == "CXXConstruct") { + const QString name = node.detail().value_or(QString()); + if (!name.isEmpty()) + type = name; + d->setHelpItemForTooltip(id, type, HelpItem::ClassOrNamespace); + } + if (node.role() == "specifier" && node.kind() == "NamespaceAlias") { + d->setHelpItemForTooltip(id, node.detail().value_or(QString()).chopped(2), + HelpItem::ClassOrNamespace); + return; + } + d->setHelpItemForTooltip(id); + }); + sendContent(req); +} + void ClangdClient::Private::handleGotoDefinitionResult() { QTC_ASSERT(followSymbolData->defLink.hasValidTarget(), return); @@ -1468,6 +1657,36 @@ QString ClangdClient::Private::searchTermFromCursor(const QTextCursor &cursor) c return termCursor.selectedText(); } +void ClangdClient::Private::setHelpItemForTooltip(const MessageId &token, const QString &fqn, + HelpItem::Category category, + const QString &type) +{ + QStringList helpIds; + QString mark; + if (!fqn.isEmpty()) { + helpIds << fqn; + int sepSearchStart = 0; + while (true) { + sepSearchStart = fqn.indexOf("::", sepSearchStart); + if (sepSearchStart == -1) + break; + sepSearchStart += 2; + helpIds << fqn.mid(sepSearchStart); + } + mark = helpIds.last(); + if (category == HelpItem::Function) + mark += type.mid(type.indexOf('(')); + } + if (category == HelpItem::Enum && !type.isEmpty()) + mark = type; + + HelpItem helpItem(helpIds, mark, category); + if (isTesting) + emit q->helpItemGathered(helpItem); + else + q->hoverHandler()->setHelpItem(token, helpItem); +} + void ClangdClient::VirtualFunctionAssistProcessor::cancel() { resetData(); diff --git a/src/plugins/clangcodemodel/clangdclient.h b/src/plugins/clangcodemodel/clangdclient.h index 49d60e7c314..5b0d61b814b 100644 --- a/src/plugins/clangcodemodel/clangdclient.h +++ b/src/plugins/clangcodemodel/clangdclient.h @@ -70,12 +70,17 @@ public: void findLocalUsages(TextEditor::TextDocument *document, const QTextCursor &cursor, CppTools::RefactoringEngineInterface::RenameCallback &&callback); + void gatherHelpItemForTooltip( + const LanguageServerProtocol::HoverRequest::Response &hoverResponse, + const LanguageServerProtocol::DocumentUri &uri); + void enableTesting(); signals: void indexingFinished(); void foundReferences(const QList &items); void findUsagesDone(); + void helpItemGathered(const Core::HelpItem &helpItem); private: void handleDiagnostics(const LanguageServerProtocol::PublishDiagnosticsParams ¶ms) override; diff --git a/src/plugins/clangcodemodel/clanghoverhandler.cpp b/src/plugins/clangcodemodel/clanghoverhandler.cpp index 3d8d6648d98..250f2886b3d 100644 --- a/src/plugins/clangcodemodel/clanghoverhandler.cpp +++ b/src/plugins/clangcodemodel/clanghoverhandler.cpp @@ -26,6 +26,7 @@ #include "clanghoverhandler.h" #include "clangeditordocumentprocessor.h" +#include "clangmodelmanagersupport.h" #include #include @@ -97,6 +98,12 @@ void ClangHoverHandler::identifyMatch(TextEditorWidget *editorWidget, int pos, BaseHoverHandler::ReportPriority report) { + if (ClangModelManagerSupport::instance() + ->clientForFile(editorWidget->textDocument()->filePath())) { + report(Priority_None); + return; + } + // Reset m_futureWatcher.reset(); m_cursorPosition = -1; diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp index 5bce03e251f..e4772e89742 100644 --- a/src/plugins/clangcodemodel/test/clangdtests.cpp +++ b/src/plugins/clangcodemodel/test/clangdtests.cpp @@ -492,6 +492,145 @@ void ClangdTestLocalReferences::test() QCOMPARE(actualRanges, expectedRanges); } + +// This tests our help item construction, not the actual tooltip contents. Those come +// pre-formatted from clangd. +ClangdTestTooltips::ClangdTestTooltips() +{ + setProjectFileName("tooltips.pro"); + setSourceFileNames({"tooltips.cpp"}); +} + +void ClangdTestTooltips::test_data() +{ + QTest::addColumn("line"); + QTest::addColumn("column"); + QTest::addColumn("expectedIds"); + QTest::addColumn("expectedMark"); + QTest::addColumn("expectedCategory"); + + QTest::newRow("LocalParameterVariableConstRefCustomType") << 12 << 12 + << QStringList("Foo") << QString("Foo") << int(HelpItem::ClassOrNamespace); + QTest::newRow("LocalNonParameterVariableConstRefCustomType") << 14 << 5 + << QStringList("Foo") << QString("Foo") << int(HelpItem::ClassOrNamespace); + QTest::newRow("MemberVariableBuiltinType") << 12 << 16 + << QStringList() << QString() << int(HelpItem::Unknown); + QTest::newRow("MemberFunctionCall") << 21 << 9 + << QStringList{"Bar::mem", "mem"} << QString("mem()") << int(HelpItem::Function); + QTest::newRow("TemplateFunctionCall") << 30 << 5 + << QStringList{"t"} << QString("t(int)") << int(HelpItem::Function); + QTest::newRow("Enum") << 49 << 12 + << QStringList{"EnumType"} << QString("EnumType") << int(HelpItem::Enum); + QTest::newRow("Enumerator") << 49 << 22 + << QStringList{"Custom"} << QString("EnumType") << int(HelpItem::Enum); + QTest::newRow("TemplateTypeFromParameter") << 55 << 25 + << QStringList{"Baz"} << QString("Baz") << int(HelpItem::ClassOrNamespace); + QTest::newRow("TemplateTypeFromNonParameter") << 56 << 19 + << QStringList{"Baz"} << QString("Baz") << int(HelpItem::ClassOrNamespace); + QTest::newRow("IncludeDirective") << 59 << 11 << QStringList{"tooltipinfo.h"} + << QString("tooltipinfo.h") << int(HelpItem::Brief); + QTest::newRow("MacroUse") << 66 << 5 << QStringList{"MACRO_FROM_MAINFILE"} + << QString("MACRO_FROM_MAINFILE") << int(HelpItem::Macro); + QTest::newRow("TypeNameIntroducedByUsingDirectiveQualified") << 77 << 5 + << QStringList{"N::Muu", "Muu"} << QString("Muu") << int(HelpItem::ClassOrNamespace); + QTest::newRow("TypeNameIntroducedByUsingDirectiveResolvedAndQualified") << 82 << 5 + << QStringList{"N::Muu", "Muu"} << QString("Muu") << int(HelpItem::ClassOrNamespace); + QTest::newRow("TypeNameIntroducedByUsingDeclarationQualified") << 87 << 5 + << QStringList{"N::Muu", "Muu"} << QString("Muu") << int(HelpItem::ClassOrNamespace); + QTest::newRow("Namespace") << 106 << 11 + << QStringList{"X"} << QString("X") << int(HelpItem::ClassOrNamespace); + QTest::newRow("NamespaceQualified") << 107 << 11 + << QStringList{"X::Y", "Y"} << QString("Y") << int(HelpItem::ClassOrNamespace); + QTest::newRow("TypeName_ResolveTypeDef") << 122 << 5 << QStringList{"PtrFromTypeDef"} + << QString("PtrFromTypeDef") << int(HelpItem::Typedef); + QTest::newRow("TypeName_ResolveAlias") << 123 << 5 << QStringList{"PtrFromTypeAlias"} + << QString("PtrFromTypeAlias") << int(HelpItem::Typedef); + QTest::newRow("TypeName_ResolveTemplateTypeAlias") << 124 << 5 + << QStringList{"PtrFromTemplateTypeAlias"} << QString("PtrFromTemplateTypeAlias") + << int(HelpItem::Typedef); + QTest::newRow("TemplateClassReference") << 134 << 5 + << QStringList{"Zii"} << QString("Zii") << int(HelpItem::ClassOrNamespace); + QTest::newRow("TemplateClassQualified") << 135 << 5 + << QStringList{"U::Yii", "Yii"} << QString("Yii") << int(HelpItem::ClassOrNamespace); + QTest::newRow("ResolveNamespaceAliasForType") << 144 << 8 + << QStringList{"A::X", "X"} << QString("X") << int(HelpItem::ClassOrNamespace); + QTest::newRow("ResolveNamespaceAlias") << 144 << 5 + << QStringList{"B"} << QString("B") << int(HelpItem::ClassOrNamespace); + QTest::newRow("QualificationForTemplateClassInClassInNamespace") << 153 << 16 + << QStringList{"N::Outer::Inner", "Outer::Inner", "Inner"} << QString("Inner") + << int(HelpItem::ClassOrNamespace); + QTest::newRow("Function") << 165 << 5 << QStringList{"f"} << QString("f()") + << int(HelpItem::Function); + QTest::newRow("Function_QualifiedName") << 166 << 8 + << QStringList{"R::f", "f"} << QString("f()") << int(HelpItem::Function); + QTest::newRow("FunctionWithParameter") << 167 << 5 << QStringList{"f"} << QString("f(int)") + << int(HelpItem::Function); + QTest::newRow("FunctionWithDefaultValue") << 168 << 5 + << QStringList{"z"} << QString("z(int)") << int(HelpItem::Function); + QTest::newRow("PointerToPointerToClass") << 200 << 12 + << QStringList{"Nuu"} << QString("Nuu") << int(HelpItem::ClassOrNamespace); + QTest::newRow("AutoTypeEnum") << 177 << 10 + << QStringList{"EnumType"} << QString("EnumType") << int(HelpItem::Enum); + QTest::newRow("AutoTypeClass") << 178 << 10 + << QStringList{"Bar"} << QString("Bar") << int(HelpItem::ClassOrNamespace); + QTest::newRow("AutoTypeTemplate") << 179 << 10 + << QStringList{"Zii"} << QString("Zii") << int(HelpItem::ClassOrNamespace); + QTest::newRow("Function_DefaultConstructor") << 193 << 5 + << QStringList{"Con"} << QString("Con") << int(HelpItem::ClassOrNamespace); + QTest::newRow("Function_ExplicitDefaultConstructor") << 194 << 5 + << QStringList{"ExplicitCon"} << QString("ExplicitCon") + << int(HelpItem::ClassOrNamespace); + QTest::newRow("Function_CustomConstructor") << 195 << 5 + << QStringList{"ExplicitCon"} << QString("ExplicitCon") + << int(HelpItem::ClassOrNamespace); +} + +void ClangdTestTooltips::test() +{ + QFETCH(int, line); + QFETCH(int, column); + QFETCH(QStringList, expectedIds); + QFETCH(QString, expectedMark); + QFETCH(int, expectedCategory); + + TextEditor::TextDocument * const doc = document("tooltips.cpp"); + QVERIFY(doc); + const auto editor = qobject_cast(EditorManager::currentEditor()); + QVERIFY(editor); + QCOMPARE(editor->document(), doc); + QVERIFY(editor->editorWidget()); + + QSKIP("IncludeDirective", "FIXME: clangd sends empty or no hover data for includes", Abort); + + QTimer timer; + timer.setSingleShot(true); + QEventLoop loop; + QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + HelpItem helpItem; + const auto handler = [&helpItem, &loop](const HelpItem &h) { + helpItem = h; + loop.quit(); + }; + connect(client(), &ClangdClient::helpItemGathered, handler); + + QTextCursor cursor(doc->document()); + const int pos = Utils::Text::positionInText(doc->document(), line, column); + cursor.setPosition(pos); + editor->editorWidget()->processTooltipRequest(cursor); + + timer.start(10000); + loop.exec(); + QVERIFY(timer.isActive()); + timer.stop(); + + QEXPECT_FAIL("TypeName_ResolveTemplateTypeAlias", "typedef already resolved in AST", Abort); + QCOMPARE(int(helpItem.category()), expectedCategory); + QEXPECT_FAIL("TemplateClassQualified", "Additional look-up needed?", Abort); + QEXPECT_FAIL("AutoTypeTemplate", "Additional look-up needed?", Abort); + QCOMPARE(helpItem.helpIds(), expectedIds); + QCOMPARE(helpItem.docMark(), expectedMark); +} + } // namespace Tests } // namespace Internal } // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/test/clangdtests.h b/src/plugins/clangcodemodel/test/clangdtests.h index e794350d7eb..158fe61844f 100644 --- a/src/plugins/clangcodemodel/test/clangdtests.h +++ b/src/plugins/clangcodemodel/test/clangdtests.h @@ -115,6 +115,17 @@ private slots: void test(); }; +class ClangdTestTooltips : public ClangdTest +{ + Q_OBJECT +public: + ClangdTestTooltips(); + +private slots: + void test_data(); + void test(); +}; + } // namespace Tests } // namespace Internal } // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/test/data/clangtestdata.qrc b/src/plugins/clangcodemodel/test/data/clangtestdata.qrc index 5357195bc1f..9f49f6078ab 100644 --- a/src/plugins/clangcodemodel/test/data/clangtestdata.qrc +++ b/src/plugins/clangcodemodel/test/data/clangtestdata.qrc @@ -39,5 +39,7 @@ follow-symbol/main.cpp local-references/local-references.pro local-references/references.cpp + tooltips/tooltips.cpp + tooltips/tooltips.pro diff --git a/src/plugins/clangcodemodel/test/data/tooltips/tooltips.cpp b/src/plugins/clangcodemodel/test/data/tooltips/tooltips.cpp new file mode 100644 index 00000000000..bc1f17066ef --- /dev/null +++ b/src/plugins/clangcodemodel/test/data/tooltips/tooltips.cpp @@ -0,0 +1,212 @@ +void f(int foo, const int *cfoo) +{ + foo++; + cfoo++; +} + + + +struct Foo { int member = 0; }; +int g(const Foo &foo) +{ + return foo.member; + const Foo bar; + bar; +} + +struct Bar { virtual ~Bar(); int mem(){} virtual int virtualConstMem() const; }; +void h(const Foo &foo, Bar &bar) +{ + g(foo); + bar.mem(); + bar.virtualConstMem(); +} + + +template +void t(int foo) { (void)foo; } +void c() +{ + t(3); +} + + + +/** + * \brief This is a crazy function. + */ +void documentedFunction(); +void d() +{ + documentedFunction(); +} + + + +enum EnumType { V1, V2, Custom = V2 + 5 }; +EnumType e() +{ + return EnumType::Custom; +} + + + +template struct Baz { T member; }; +void t2(const Baz &b) { + Baz baz; baz = b; +} + +#include "tooltipinfo.h" + + + +#define MACRO_FROM_MAINFILE(x) x + 3 +void foo() +{ + MACRO_FROM_MAINFILE(7); + MACRO_FROM_HEADER(7); +} + + + +namespace N { struct Muu{}; } +namespace G = N; +void o() +{ + using namespace N; + Muu muu; (void)muu; +} +void n() +{ + using namespace G; + Muu muu; (void)muu; +} +void q() +{ + using N::Muu; + Muu muu; (void)muu; +} + + + +struct Sizes +{ + char memberChar1; + char memberChar2; +}; +enum class FancyEnumType { V1, V2 }; +union Union +{ + char memberChar1; + char memberChar2; +}; + + + +namespace X { +namespace Y { +} +} + + + +template struct Ptr {}; +struct Nuu {}; + +typedef Ptr PtrFromTypeDef; +using PtrFromTypeAlias = Ptr; +template using PtrFromTemplateTypeAlias = Ptr; + +void y() +{ + PtrFromTypeDef b; (void)b; + PtrFromTypeAlias a; (void)a; + PtrFromTemplateTypeAlias c; (void)c; +} + + + +template struct Zii {}; +namespace U { template struct Yii {}; } +void mc() +{ + using namespace U; + Zii zii; (void) zii; + Yii yii; (void) yii; +} + + + +namespace A { struct X {}; } +namespace B = A; +void ab() +{ + B::X x; (void)x; +} + + + +namespace N { +struct Outer +{ + template struct Inner {}; + Inner inner; +}; +} + + + +void f(); +namespace R { void f(); } +void f(int param); +void z(int = 1); +void user() +{ + f(); + R::f(); + f(1); + z(); +} + + + + +void autoTypes() +{ + auto a = 3; (void)a; + auto b = EnumType::V1; (void)b; + auto c = Bar(); (void)c; + auto d = Zii(); (void)d; +} + + + + +struct Con {}; +struct ExplicitCon { + ExplicitCon() = default; + ExplicitCon(int m) :member(m) {} + int member; +}; +void constructor() +{ + Con(); + ExplicitCon(); + ExplicitCon(2); +} + +Nuu **pointers(Nuu **p1) +{ + return p1; +} + +static constexpr int calcValue() { return 1 + 2; } +const auto val = calcValue() + sizeof(char); + +const int zero = 0; + +static void func() +{ + const int i = 5; + const int j = i; +} diff --git a/src/plugins/clangcodemodel/test/data/tooltips/tooltips.pro b/src/plugins/clangcodemodel/test/data/tooltips/tooltips.pro new file mode 100644 index 00000000000..d10195696e4 --- /dev/null +++ b/src/plugins/clangcodemodel/test/data/tooltips/tooltips.pro @@ -0,0 +1,3 @@ +TEMPLATE = app +CONFIG -= qt +SOURCES = tooltips.cpp diff --git a/src/plugins/languageclient/languageclienthoverhandler.cpp b/src/plugins/languageclient/languageclienthoverhandler.cpp index 15f757ece68..3fc137f9387 100644 --- a/src/plugins/languageclient/languageclienthoverhandler.cpp +++ b/src/plugins/languageclient/languageclienthoverhandler.cpp @@ -51,6 +51,18 @@ void HoverHandler::abort() if (m_client && m_client->reachable() && m_currentRequest.has_value()) m_client->cancelRequest(*m_currentRequest); m_currentRequest.reset(); + m_response = {}; +} + +void HoverHandler::setHelpItem(const LanguageServerProtocol::MessageId &msgId, + const Core::HelpItem &help) +{ + if (msgId == m_response.id()) { + setContent(m_response.result().value().content()); + m_response = {}; + setLastHelpItemIdentified(help); + m_report(priority()); + } } void HoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget, @@ -64,10 +76,11 @@ void HoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget, report(Priority_None); return; } - auto uri = DocumentUri::fromFilePath(editorWidget->textDocument()->filePath()); + m_uri = DocumentUri::fromFilePath(editorWidget->textDocument()->filePath()); + m_response = {}; QTextCursor tc = editorWidget->textCursor(); tc.setPosition(pos); - const QList &diagnostics = m_client->diagnosticsAt(uri, tc); + const QList &diagnostics = m_client->diagnosticsAt(m_uri, tc); if (!diagnostics.isEmpty()) { const QStringList messages = Utils::transform(diagnostics, &Diagnostic::message); setToolTip(messages.join('\n')); @@ -101,7 +114,7 @@ void HoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget, m_report = report; QTextCursor cursor = editorWidget->textCursor(); cursor.setPosition(pos); - HoverRequest request((TextDocumentPositionParams(TextDocumentIdentifier(uri), Position(cursor)))); + HoverRequest request((TextDocumentPositionParams(TextDocumentIdentifier(m_uri), Position(cursor)))); m_currentRequest = request.id(); request.setResponseCallback( [this](const HoverRequest::Response &response) { handleResponse(response); }); @@ -115,8 +128,14 @@ void HoverHandler::handleResponse(const HoverRequest::Response &response) if (m_client) m_client->log(error.value()); } - if (Utils::optional result = response.result()) + if (Utils::optional result = response.result()) { + if (m_helpItemProvider) { + m_response = response; + m_helpItemProvider(response, m_uri); + return; + } setContent(result.value().content()); + } m_report(priority()); } diff --git a/src/plugins/languageclient/languageclienthoverhandler.h b/src/plugins/languageclient/languageclienthoverhandler.h index 4c09f11063c..c8bcb759fc6 100644 --- a/src/plugins/languageclient/languageclienthoverhandler.h +++ b/src/plugins/languageclient/languageclienthoverhandler.h @@ -25,14 +25,21 @@ #pragma once +#include "languageclient_global.h" + #include #include +#include + namespace LanguageClient { class Client; -class HoverHandler final : public TextEditor::BaseHoverHandler +using HelpItemProvider = std::function; + +class LANGUAGECLIENT_EXPORT HoverHandler final : public TextEditor::BaseHoverHandler { Q_DECLARE_TR_FUNCTIONS(HoverHandler) public: @@ -41,6 +48,9 @@ public: void abort() override; + void setHelpItemProvider(const HelpItemProvider &provider) { m_helpItemProvider = provider; } + void setHelpItem(const LanguageServerProtocol::MessageId &msgId, const Core::HelpItem &help); + protected: void identifyMatch(TextEditor::TextEditorWidget *editorWidget, int pos, @@ -52,7 +62,10 @@ private: QPointer m_client; Utils::optional m_currentRequest; + LanguageServerProtocol::DocumentUri m_uri; + LanguageServerProtocol::HoverRequest::Response m_response; TextEditor::BaseHoverHandler::ReportPriority m_report; + HelpItemProvider m_helpItemProvider; }; } // namespace LanguageClient diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 60d5930f7b7..b98a5783056 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -5778,6 +5778,13 @@ void TextEditorWidget::removeHoverHandler(BaseHoverHandler *handler) d->m_hoverHandlers.removeAll(handler); } +#ifdef WITH_TESTS +void TextEditorWidget::processTooltipRequest(const QTextCursor &c) +{ + d->processTooltipRequest(c); +} +#endif + void TextEditorWidget::extraAreaLeaveEvent(QEvent *) { d->extraAreaPreviousMarkTooltipRequestedLine = -1; diff --git a/src/plugins/texteditor/texteditor.h b/src/plugins/texteditor/texteditor.h index ceab8fbb53f..70224a18ff9 100644 --- a/src/plugins/texteditor/texteditor.h +++ b/src/plugins/texteditor/texteditor.h @@ -491,6 +491,10 @@ public: void addHoverHandler(BaseHoverHandler *handler); void removeHoverHandler(BaseHoverHandler *handler); +#ifdef WITH_TESTS + void processTooltipRequest(const QTextCursor &c); +#endif + signals: void assistFinished(); // Used in tests. void readOnlyChanged(); From 7fd4691a7a2c8f24f14f9299c601f257dd0869e0 Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Fri, 11 Jun 2021 11:39:45 +0200 Subject: [PATCH 077/149] CMakePM: Try to improve file information for ctest The ctest information regarding the test case itself cannot be as adequate as the information coming from the code model. Nevertheless depending on the used mechanism we might end up in places that will not be helpful when using the test tree to open files of ctest. Try to get closer to the real declaration source if we can. Change-Id: Ia9d217f0cdc6de6230d89dd342b60856cf1312b8 Reviewed-by: Christian Stenger Reviewed-by: David Schulz --- src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp index 688c0f26627..6751359574b 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -960,7 +960,16 @@ void CMakeBuildSystem::runCTest() const int bt = test.value("backtrace").toInt(-1); // we may have no real backtrace due to different registering if (bt != -1) { - const QJsonObject btRef = nodes.at(bt).toObject(); + QSet seen; + std::function findAncestor = [&](int index){ + const QJsonObject node = nodes.at(index).toObject(); + const int parent = node.value("parent").toInt(-1); + if (seen.contains(parent) || parent < 0) + return node; + seen << parent; + return findAncestor(parent); + }; + const QJsonObject btRef = findAncestor(bt); file = btRef.value("file").toInt(-1); line = btRef.value("line").toInt(-1); } From 4c3bbba75c8d0a4941c672f1a5a4f89d940f1183 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 29 Jun 2021 17:13:06 +0200 Subject: [PATCH 078/149] Debugger: Fix typo (target-remote vs target extended-remote) Change-Id: Iec25998654c32652d42d1ef3f82e474646c16424 Reviewed-by: Leena Miettinen Reviewed-by: Christian Stenger --- src/plugins/debugger/debuggerdialogs.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/plugins/debugger/debuggerdialogs.cpp b/src/plugins/debugger/debuggerdialogs.cpp index 7dc5bf67065..24e4b754be6 100644 --- a/src/plugins/debugger/debuggerdialogs.cpp +++ b/src/plugins/debugger/debuggerdialogs.cpp @@ -83,7 +83,7 @@ public: PathChooser *workingDirectory; QCheckBox *breakAtMainCheckBox; QCheckBox *runInTerminalCheckBox; - QCheckBox *useTargetExtendedCheckBox; + QCheckBox *useTargetExtendedRemoteCheckBox; PathChooser *debuginfoPathChooser; QLabel *serverStartScriptLabel; PathChooser *serverStartScriptPathChooser; @@ -128,7 +128,7 @@ public: Runnable runnable; bool breakAtMain = false; bool runInTerminal = false; - bool useTargetExtended = false; + bool useTargetExtendedRemote = false; FilePath serverStartScript; FilePath sysRoot; QString serverInitCommands; @@ -183,7 +183,7 @@ void StartApplicationParameters::toSettings(QSettings *settings) const settings->setValue("LastExternalWorkingDirectory", runnable.workingDirectory); settings->setValue("LastExternalBreakAtMain", breakAtMain); settings->setValue("LastExternalRunInTerminal", runInTerminal); - settings->setValue("LastExternalUseTargetExtended", useTargetExtended); + settings->setValue("LastExternalUseTargetExtended", useTargetExtendedRemote); settings->setValue("LastServerStartScript", serverStartScript.toVariant()); settings->setValue("LastServerInitCommands", serverInitCommands); settings->setValue("LastServerResetCommands", serverResetCommands); @@ -201,7 +201,7 @@ void StartApplicationParameters::fromSettings(const QSettings *settings) runnable.workingDirectory = settings->value("LastExternalWorkingDirectory").toString(); breakAtMain = settings->value("LastExternalBreakAtMain").toBool(); runInTerminal = settings->value("LastExternalRunInTerminal").toBool(); - useTargetExtended = settings->value("LastExternalUseTargetExtended").toBool(); + useTargetExtendedRemote = settings->value("LastExternalUseTargetExtended").toBool(); serverStartScript = FilePath::fromVariant(settings->value("LastServerStartScript")); serverInitCommands = settings->value("LastServerInitCommands").toString(); serverResetCommands = settings->value("LastServerResetCommands").toString(); @@ -258,7 +258,7 @@ StartApplicationDialog::StartApplicationDialog(QWidget *parent) d->breakAtMainCheckBox = new QCheckBox(this); d->breakAtMainCheckBox->setText(QString()); - d->useTargetExtendedCheckBox = new QCheckBox(this); + d->useTargetExtendedRemoteCheckBox = new QCheckBox(this); d->serverStartScriptPathChooser = new PathChooser(this); d->serverStartScriptPathChooser->setExpectedKind(PathChooser::File); @@ -327,7 +327,7 @@ StartApplicationDialog::StartApplicationDialog(QWidget *parent) formLayout->addRow(tr("&Working directory:"), d->workingDirectory); formLayout->addRow(tr("Run in &terminal:"), d->runInTerminalCheckBox); formLayout->addRow(tr("Break at \"&main\":"), d->breakAtMainCheckBox); - formLayout->addRow(tr("Use target-extended to connect:"), d->useTargetExtendedCheckBox); + formLayout->addRow(tr("Use target extended-remote to connect:"), d->useTargetExtendedRemoteCheckBox); formLayout->addRow(d->serverStartScriptLabel, d->serverStartScriptPathChooser); formLayout->addRow(d->sysRootLabel, d->sysRootPathChooser); formLayout->addRow(d->serverInitCommandsLabel, d->serverInitCommandsTextEdit); @@ -469,7 +469,7 @@ void StartApplicationDialog::run(bool attachRemote) debugger->setCommandsAfterConnect(newParameters.serverInitCommands); debugger->setCommandsForReset(newParameters.serverResetCommands); debugger->setUseTerminal(newParameters.runInTerminal); - debugger->setUseExtendedRemote(newParameters.useTargetExtended); + debugger->setUseExtendedRemote(newParameters.useTargetExtendedRemote); if (!newParameters.sysRoot.isEmpty()) debugger->setSysRoot(newParameters.sysRoot); @@ -515,7 +515,7 @@ StartApplicationParameters StartApplicationDialog::parameters() const result.runnable.workingDirectory = d->workingDirectory->filePath().toString(); result.breakAtMain = d->breakAtMainCheckBox->isChecked(); result.runInTerminal = d->runInTerminalCheckBox->isChecked(); - result.useTargetExtended = d->useTargetExtendedCheckBox->isChecked(); + result.useTargetExtendedRemote = d->useTargetExtendedRemoteCheckBox->isChecked(); return result; } @@ -534,7 +534,7 @@ void StartApplicationDialog::setParameters(const StartApplicationParameters &p) d->workingDirectory->setPath(p.runnable.workingDirectory); d->breakAtMainCheckBox->setChecked(p.breakAtMain); d->runInTerminalCheckBox->setChecked(p.runInTerminal); - d->useTargetExtendedCheckBox->setChecked(p.useTargetExtended); + d->useTargetExtendedRemoteCheckBox->setChecked(p.useTargetExtendedRemote); updateState(); } From 4a8c222c183f2576975593f7cf7d756f8cb0aa03 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 29 Jun 2021 15:57:41 +0200 Subject: [PATCH 079/149] Utils: Add FilePath::symLinkTarget And implement it for the docker device. This replaces the previous unused and not really implemented FilePath::resolveSymLinkTarget. Change-Id: I9dcb4f8276dbb88b21959276da0d50135742fba0 Reviewed-by: Christian Stenger --- src/libs/utils/fileutils.cpp | 20 ++++++++++++------- src/libs/utils/fileutils.h | 2 ++ src/plugins/docker/dockerdevice.cpp | 16 +++++++++++++++ src/plugins/docker/dockerdevice.h | 1 + .../devicesupport/devicemanager.cpp | 6 ++++++ .../projectexplorer/devicesupport/idevice.cpp | 7 +++++++ .../projectexplorer/devicesupport/idevice.h | 1 + 7 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index 296a4f398e3..1f5a7b7cbc1 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -391,13 +391,6 @@ FilePath FilePath::resolvePath(const QString &fileName) const return FilePath::fromString(QDir::cleanPath(toString() + QLatin1Char('/') + fileName)); } -FilePath FilePath::resolveSymlinkTarget() const -{ - // FIXME: implement - QTC_CHECK(false); - return *this; -} - FilePath FilePath::cleanPath() const { FilePath result = *this; @@ -1009,6 +1002,19 @@ bool FilePath::needsDevice() const return !m_scheme.isEmpty(); } +/// \returns an empty FilePath if this is not a symbolic linl +FilePath FilePath::symLinkTarget() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.symLinkTarget, return {}); + return s_deviceHooks.symLinkTarget(*this); + } + const QFileInfo info(m_data); + if (!info.isSymLink()) + return {}; + return FilePath::fromString(info.symLinkTarget()); +} + /// Find the parent directory of a given directory. diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index 585f2dcf2e7..6a67aaedd4a 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -83,6 +83,7 @@ public: std::function copyFile; std::function renameFile; std::function searchInPath; + std::function symLinkTarget; std::function(const FilePath &, const QStringList &, QDir::Filters, QDir::SortFlags)> dirEntries; std::function fileContents; @@ -188,6 +189,7 @@ public: FilePath cleanPath() const; FilePath canonicalPath() const; + FilePath symLinkTarget() const; FilePath operator/(const QString &str) const; diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp index 271ab489e7f..5bb48d1310f 100644 --- a/src/plugins/docker/dockerdevice.cpp +++ b/src/plugins/docker/dockerdevice.cpp @@ -1002,6 +1002,22 @@ QDateTime DockerDevice::lastModified(const FilePath &filePath) const return {}; } +FilePath DockerDevice::symLinkTarget(const FilePath &filePath) const +{ + QTC_ASSERT(handlesFile(filePath), return {}); + tryCreateLocalFileAccess(); + if (hasLocalFileAccess()) { + const FilePath localAccess = mapToLocalAccess(filePath); + const FilePath target = localAccess.symLinkTarget(); + LOG("SymLinkTarget? " << filePath.toUserOutput() << localAccess.toUserOutput() << target); + if (target.isEmpty()) + return {}; + return mapToGlobalPath(target); + } + QTC_CHECK(false); + return {}; +} + FilePath DockerDevice::searchInPath(const FilePath &filePath) const { const QString path = filePath.path(); diff --git a/src/plugins/docker/dockerdevice.h b/src/plugins/docker/dockerdevice.h index 05d42199f9f..1bed2eb9ce4 100644 --- a/src/plugins/docker/dockerdevice.h +++ b/src/plugins/docker/dockerdevice.h @@ -89,6 +89,7 @@ public: bool copyFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override; bool renameFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const override; Utils::FilePath searchInPath(const Utils::FilePath &filePath) const override; + Utils::FilePath symLinkTarget(const Utils::FilePath &filePath) const override; QList directoryEntries(const Utils::FilePath &filePath, const QStringList &nameFilters, QDir::Filters filters, diff --git a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp index 5b497f8eb57..9390b0e740a 100644 --- a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp @@ -459,6 +459,12 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_uniquesearchInPath(filePath); }; + deviceHooks.symLinkTarget = [](const FilePath &filePath) { + auto device = DeviceManager::deviceForPath(filePath); + QTC_ASSERT(device, return FilePath{}); + return device->symLinkTarget(filePath); + }; + deviceHooks.dirEntries = [](const FilePath &filePath, const QStringList &nameFilters, QDir::Filters filters, QDir::SortFlags sort) { auto device = DeviceManager::deviceForPath(filePath); diff --git a/src/plugins/projectexplorer/devicesupport/idevice.cpp b/src/plugins/projectexplorer/devicesupport/idevice.cpp index a8c1da91f87..8983576c033 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/idevice.cpp @@ -316,6 +316,13 @@ FilePath IDevice::searchInPath(const FilePath &filePath) const return Environment::systemEnvironment().searchInPath(filePath.path()); } +FilePath IDevice::symLinkTarget(const FilePath &filePath) const +{ + Q_UNUSED(filePath); + QTC_CHECK(false); + return {}; +} + QList IDevice::directoryEntries(const FilePath &filePath, const QStringList &nameFilters, QDir::Filters filters, diff --git a/src/plugins/projectexplorer/devicesupport/idevice.h b/src/plugins/projectexplorer/devicesupport/idevice.h index 5cb097e13f1..46122f0144b 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.h +++ b/src/plugins/projectexplorer/devicesupport/idevice.h @@ -250,6 +250,7 @@ public: virtual bool copyFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const; virtual bool renameFile(const Utils::FilePath &filePath, const Utils::FilePath &target) const; virtual Utils::FilePath searchInPath(const Utils::FilePath &filePath) const; + virtual Utils::FilePath symLinkTarget(const Utils::FilePath &filePath) const; virtual QList directoryEntries(const Utils::FilePath &filePath, const QStringList &nameFilters, QDir::Filters filters, From 6a142dfd95ab497c1fe247cd4289affa13444577 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Tue, 29 Jun 2021 16:56:12 +0200 Subject: [PATCH 080/149] Fix the "Cannot remove a null widget" warning After the 867c0b8d8a53974074b1fff5b132f3ae9f150066 got integrated into qtbase, Qt started issuing a warning on Creator shutdown. The reason is that mode's widget might be deleted before the mode's destruction. Deleting the mode's widget removes it automatically from the contained stacked layout of the mode manager. This means that the indices inside the stacked layout and those inside the ModeManager's d->m_modes list are now out of sync. FancyTabWidget::removeTab() can't find the right widget for the passed index now or returns wrong widget for it. The fix is to remove removeMode() method (and in turn some more unused now). The context objects don't need to be removed on shutdown, and the remaining mode's widgets will be deleted by Qt itself when the main windows is destroyed. Fixes: QTCREATORBUG-25925 Change-Id: I70c2773eea2984c5d06ce3bf71a4271b267efbe0 Reviewed-by: Eike Ziller --- src/plugins/coreplugin/fancytabwidget.cpp | 6 ------ src/plugins/coreplugin/fancytabwidget.h | 7 ------- src/plugins/coreplugin/imode.cpp | 8 -------- src/plugins/coreplugin/imode.h | 1 - src/plugins/coreplugin/modemanager.cpp | 13 ------------- src/plugins/coreplugin/modemanager.h | 1 - 6 files changed, 36 deletions(-) diff --git a/src/plugins/coreplugin/fancytabwidget.cpp b/src/plugins/coreplugin/fancytabwidget.cpp index c72b89762df..00f5d74fcc9 100644 --- a/src/plugins/coreplugin/fancytabwidget.cpp +++ b/src/plugins/coreplugin/fancytabwidget.cpp @@ -551,12 +551,6 @@ void FancyTabWidget::insertTab(int index, QWidget *tab, const QIcon &icon, const m_tabBar->insertTab(index, icon, label, hasMenu); } -void FancyTabWidget::removeTab(int index) -{ - m_modesStack->removeWidget(m_modesStack->widget(index)); - m_tabBar->removeTab(index); -} - void FancyTabWidget::setBackgroundBrush(const QBrush &brush) { QPalette pal; diff --git a/src/plugins/coreplugin/fancytabwidget.h b/src/plugins/coreplugin/fancytabwidget.h index 8c5544665df..c52376c5456 100644 --- a/src/plugins/coreplugin/fancytabwidget.h +++ b/src/plugins/coreplugin/fancytabwidget.h @@ -109,12 +109,6 @@ public: updateGeometry(); } void setEnabled(int index, bool enabled); - void removeTab(int index) - { - FancyTab *tab = m_tabs.takeAt(index); - delete tab; - updateGeometry(); - } void setCurrentIndex(int index); int currentIndex() const { return m_currentIndex; } @@ -148,7 +142,6 @@ public: FancyTabWidget(QWidget *parent = nullptr); void insertTab(int index, QWidget *tab, const QIcon &icon, const QString &label, bool hasMenu); - void removeTab(int index); void setBackgroundBrush(const QBrush &brush); void addCornerWidget(QWidget *widget); void insertCornerWidget(int pos, QWidget *widget); diff --git a/src/plugins/coreplugin/imode.cpp b/src/plugins/coreplugin/imode.cpp index 2e8e27c3bed..0a283ee11e0 100644 --- a/src/plugins/coreplugin/imode.cpp +++ b/src/plugins/coreplugin/imode.cpp @@ -125,14 +125,6 @@ IMode::IMode(QObject *parent) : IContext(parent) ModeManager::addMode(this); } -/*! - Unregisters the mode from \QC and destroys it. -*/ -IMode::~IMode() -{ - ModeManager::removeMode(this); -} - void IMode::setEnabled(bool enabled) { if (m_isEnabled == enabled) diff --git a/src/plugins/coreplugin/imode.h b/src/plugins/coreplugin/imode.h index 335d5f5679c..a937b1e93d4 100644 --- a/src/plugins/coreplugin/imode.h +++ b/src/plugins/coreplugin/imode.h @@ -46,7 +46,6 @@ class CORE_EXPORT IMode : public IContext public: IMode(QObject *parent = nullptr); - ~IMode() override; QString displayName() const { return m_displayName; } QIcon icon() const { return m_icon; } diff --git a/src/plugins/coreplugin/modemanager.cpp b/src/plugins/coreplugin/modemanager.cpp index 46173ef8e94..c8c33519e1d 100644 --- a/src/plugins/coreplugin/modemanager.cpp +++ b/src/plugins/coreplugin/modemanager.cpp @@ -275,19 +275,6 @@ void ModeManagerPrivate::enabledStateChanged(IMode *mode) } } -void ModeManager::removeMode(IMode *mode) -{ - const int index = d->m_modes.indexOf(mode); - d->m_modes.remove(index); - if (d->m_startingUp) - return; - - d->m_modeCommands.remove(index); - d->m_modeStack->removeTab(index); - - d->m_mainWindow->removeContextObject(mode); -} - /*! Adds the \a action to the mode selector's tool bar. Actions are sorted by \a priority in descending order. diff --git a/src/plugins/coreplugin/modemanager.h b/src/plugins/coreplugin/modemanager.h index 811a4d10323..7ba0393ce86 100644 --- a/src/plugins/coreplugin/modemanager.h +++ b/src/plugins/coreplugin/modemanager.h @@ -84,7 +84,6 @@ private: static void extensionsInitialized(); static void addMode(IMode *mode); - static void removeMode(IMode *mode); void currentTabAboutToChange(int index); void currentTabChanged(int index); From f9083cc6ad771093b9d3a4ed1b38cad77cbad80d Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Wed, 30 Jun 2021 13:15:41 +0200 Subject: [PATCH 081/149] ClangCodeModel: Keep a QPointer to the ClangdClient in ClangdTextMark Apparently, the pointer might already be invalid when addToolTipContent() is called. Change-Id: I649bdca0945247b2b8ce4f709721c4d0bf96a11b Reviewed-by: David Schulz --- src/plugins/clangcodemodel/clangtextmark.cpp | 3 +-- src/plugins/clangcodemodel/clangtextmark.h | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/clangcodemodel/clangtextmark.cpp b/src/plugins/clangcodemodel/clangtextmark.cpp index c90c5fcf72c..77ed1f2fa36 100644 --- a/src/plugins/clangcodemodel/clangtextmark.cpp +++ b/src/plugins/clangcodemodel/clangtextmark.cpp @@ -48,7 +48,6 @@ #include #include #include -#include #include using namespace CppTools; @@ -394,7 +393,7 @@ ClangdTextMark::ClangdTextMark(const FilePath &filePath, bool ClangdTextMark::addToolTipContent(QLayout *target) const { - const auto canApplyFixIt = [c = QPointer(m_client), diag = m_lspDiagnostic, fp = fileName()] { + const auto canApplyFixIt = [c = m_client, diag = m_lspDiagnostic, fp = fileName()] { return c && c->reachable() && c->hasDiagnostic(DocumentUri::fromFilePath(fp), diag); }; target->addWidget(ClangDiagnosticWidget::createWidget({m_diagnostic}, diff --git a/src/plugins/clangcodemodel/clangtextmark.h b/src/plugins/clangcodemodel/clangtextmark.h index 8361f6bf16e..15b97ab32f1 100644 --- a/src/plugins/clangcodemodel/clangtextmark.h +++ b/src/plugins/clangcodemodel/clangtextmark.h @@ -32,6 +32,8 @@ #include +#include + #include namespace LanguageClient { class Client; } @@ -77,7 +79,7 @@ private: const LanguageServerProtocol::Diagnostic m_lspDiagnostic; const ClangBackEnd::DiagnosticContainer m_diagnostic; - const LanguageClient::Client * const m_client; + const QPointer m_client; }; } // namespace Internal From 1d39465a1bd93ee8f3b34629907d8fb5072dbdb5 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 22 Jun 2021 14:47:30 +0200 Subject: [PATCH 082/149] LanguageClient: export code action quick fixes Change-Id: I571378580a1f56bc649662c68eb306a081c0c091 Reviewed-by: Christian Kandeler --- .../languageclient/languageclientquickfix.cpp | 34 ++++++++----------- .../languageclient/languageclientquickfix.h | 20 ++++++++++- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/plugins/languageclient/languageclientquickfix.cpp b/src/plugins/languageclient/languageclientquickfix.cpp index 143f9ce1caf..a6d30c03dd7 100644 --- a/src/plugins/languageclient/languageclientquickfix.cpp +++ b/src/plugins/languageclient/languageclientquickfix.cpp @@ -39,28 +39,22 @@ using namespace TextEditor; namespace LanguageClient { -class CodeActionQuickFixOperation : public QuickFixOperation +CodeActionQuickFixOperation::CodeActionQuickFixOperation(const CodeAction &action, Client *client) + : m_action(action) + , m_client(client) { -public: - CodeActionQuickFixOperation(const CodeAction &action, Client *client) - : m_action(action) - , m_client(client) - { setDescription(action.title()); } + setDescription(action.title()); +} - void perform() override - { - if (!m_client) - return; - if (Utils::optional edit = m_action.edit()) - applyWorkspaceEdit(m_client, *edit); - else if (Utils::optional command = m_action.command()) - m_client->executeCommand(*command); - } - -private: - CodeAction m_action; - QPointer m_client; -}; +void CodeActionQuickFixOperation::perform() +{ + if (!m_client) + return; + if (Utils::optional edit = m_action.edit()) + applyWorkspaceEdit(m_client, *edit); + else if (Utils::optional command = m_action.command()) + m_client->executeCommand(*command); +} class CommandQuickFixOperation : public QuickFixOperation { diff --git a/src/plugins/languageclient/languageclientquickfix.h b/src/plugins/languageclient/languageclientquickfix.h index d9b322769fe..27eb4c445bf 100644 --- a/src/plugins/languageclient/languageclientquickfix.h +++ b/src/plugins/languageclient/languageclientquickfix.h @@ -25,13 +25,31 @@ #pragma once +#include "languageclient_global.h" + #include +#include + +#include + +#include namespace LanguageClient { class Client; -class LanguageClientQuickFixProvider : public TextEditor::IAssistProvider +class LANGUAGECLIENT_EXPORT CodeActionQuickFixOperation : public TextEditor::QuickFixOperation +{ +public: + CodeActionQuickFixOperation(const LanguageServerProtocol::CodeAction &action, Client *client); + void perform() override; + +private: + LanguageServerProtocol::CodeAction m_action; + QPointer m_client; +}; + +class LANGUAGECLIENT_EXPORT LanguageClientQuickFixProvider : public TextEditor::IAssistProvider { public: explicit LanguageClientQuickFixProvider(Client *client); From 813c4b807a86ca8e3f56cd0618a7b59b456f6a43 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Tue, 22 Jun 2021 14:40:19 +0200 Subject: [PATCH 083/149] Clangd: add quick fix factory Change-Id: Idea71364b0e3f528e9dd366d46a2797f5056dfed Reviewed-by: Christian Kandeler --- src/plugins/clangcodemodel/CMakeLists.txt | 7 ++- src/plugins/clangcodemodel/clangcodemodel.pro | 6 +- src/plugins/clangcodemodel/clangcodemodel.qbs | 4 +- .../clangcodemodel_dependencies.pri | 2 +- .../clangcodemodel/clangdquickfixfactory.cpp | 62 +++++++++++++++++++ .../clangcodemodel/clangdquickfixfactory.h | 43 +++++++++++++ .../clangmodelmanagersupport.cpp | 10 +-- 7 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 src/plugins/clangcodemodel/clangdquickfixfactory.cpp create mode 100644 src/plugins/clangcodemodel/clangdquickfixfactory.h diff --git a/src/plugins/clangcodemodel/CMakeLists.txt b/src/plugins/clangcodemodel/CMakeLists.txt index 4176bfd1e9f..51c89160480 100644 --- a/src/plugins/clangcodemodel/CMakeLists.txt +++ b/src/plugins/clangcodemodel/CMakeLists.txt @@ -6,8 +6,8 @@ endif() add_qtc_plugin(ClangCodeModel CONDITION TARGET libclang DEPENDS ClangSupport CPlusPlus - PLUGIN_DEPENDS Core CppTools LanguageClient ${TEST_LINK_DEPENDS} TextEditor - PLUGIN_TEST_DEPENDS CppEditor QmakeProjectManager + PLUGIN_DEPENDS Core CppEditor CppTools LanguageClient ${TEST_LINK_DEPENDS} TextEditor + PLUGIN_TEST_DEPENDS QmakeProjectManager SOURCES clangactivationsequencecontextprocessor.cpp clangactivationsequencecontextprocessor.h clangactivationsequenceprocessor.cpp clangactivationsequenceprocessor.h @@ -24,11 +24,12 @@ add_qtc_plugin(ClangCodeModel clangcompletionchunkstotextconverter.cpp clangcompletionchunkstotextconverter.h clangcompletioncontextanalyzer.cpp clangcompletioncontextanalyzer.h clangconstants.h - clangdclient.cpp clangdclient.h clangcurrentdocumentfilter.cpp clangcurrentdocumentfilter.h + clangdclient.cpp clangdclient.h clangdiagnosticfilter.cpp clangdiagnosticfilter.h clangdiagnosticmanager.cpp clangdiagnosticmanager.h clangdiagnostictooltipwidget.cpp clangdiagnostictooltipwidget.h + clangdquickfixfactory.cpp clangdquickfixfactory.h clangeditordocumentparser.cpp clangeditordocumentparser.h clangeditordocumentprocessor.cpp clangeditordocumentprocessor.h clangfixitoperation.cpp clangfixitoperation.h diff --git a/src/plugins/clangcodemodel/clangcodemodel.pro b/src/plugins/clangcodemodel/clangcodemodel.pro index f6d7b79071d..57eeb22d870 100644 --- a/src/plugins/clangcodemodel/clangcodemodel.pro +++ b/src/plugins/clangcodemodel/clangcodemodel.pro @@ -42,7 +42,8 @@ SOURCES += \ clangutils.cpp \ clangoverviewmodel.cpp \ clangdclient.cpp \ - clanggloballocatorfilters.cpp + clangdquickfixfactory.cpp \ + clanggloballocatorfilters.cpp \ HEADERS += \ clangactivationsequencecontextprocessor.h \ @@ -83,7 +84,8 @@ HEADERS += \ clangutils.h \ clangoverviewmodel.h \ clangdclient.h \ - clanggloballocatorfilters.h + clangdquickfixfactory.h \ + clanggloballocatorfilters.h \ FORMS += clangprojectsettingswidget.ui diff --git a/src/plugins/clangcodemodel/clangcodemodel.qbs b/src/plugins/clangcodemodel/clangcodemodel.qbs index d16c11f7326..18e42a0f04e 100644 --- a/src/plugins/clangcodemodel/clangcodemodel.qbs +++ b/src/plugins/clangcodemodel/clangcodemodel.qbs @@ -13,12 +13,12 @@ QtcPlugin { Depends { name: "Utils" } Depends { name: "ClangSupport" } Depends { name: "LanguageClient" } + Depends { name: "CppEditor" } Depends { name: "libclang"; required: false } Depends { name: "clang_defines" } pluginTestDepends: [ - "CppEditor", "QmakeProjectManager", ] @@ -64,6 +64,8 @@ QtcPlugin { "clangdiagnosticmanager.h", "clangdiagnostictooltipwidget.cpp", "clangdiagnostictooltipwidget.h", + "clangdquickfixfactory.cpp", + "clangdquickfixfactory.h", "clangeditordocumentparser.cpp", "clangeditordocumentparser.h", "clangeditordocumentprocessor.cpp", diff --git a/src/plugins/clangcodemodel/clangcodemodel_dependencies.pri b/src/plugins/clangcodemodel/clangcodemodel_dependencies.pri index f32eaa4c4f4..f333b4dda14 100644 --- a/src/plugins/clangcodemodel/clangcodemodel_dependencies.pri +++ b/src/plugins/clangcodemodel/clangcodemodel_dependencies.pri @@ -4,11 +4,11 @@ QTC_LIB_DEPENDS += \ clangsupport QTC_PLUGIN_DEPENDS += \ coreplugin \ + cppeditor \ cpptools \ languageclient \ texteditor QTC_TEST_DEPENDS += \ - cppeditor \ qmakeprojectmanager equals(TEST, 1): QTC_PLUGIN_DEPENDS += qtsupport diff --git a/src/plugins/clangcodemodel/clangdquickfixfactory.cpp b/src/plugins/clangcodemodel/clangdquickfixfactory.cpp new file mode 100644 index 00000000000..5146df8bc5b --- /dev/null +++ b/src/plugins/clangcodemodel/clangdquickfixfactory.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** 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 "clangdquickfixfactory.h" + +#include "clangdclient.h" +#include "clangmodelmanagersupport.h" + +#include + +using namespace LanguageServerProtocol; + +namespace ClangCodeModel { +namespace Internal { + +ClangdQuickFixFactory::ClangdQuickFixFactory() = default; + +void ClangdQuickFixFactory::match(const CppEditor::Internal::CppQuickFixInterface &interface, + QuickFixOperations &result) +{ + const auto client = ClangModelManagerSupport::instance()->clientForFile(interface.filePath()); + if (!client) + return; + + const auto uri = DocumentUri::fromFilePath(interface.filePath()); + QTextCursor cursor(interface.textDocument()); + cursor.setPosition(interface.position()); + cursor.select(QTextCursor::LineUnderCursor); + const QList &diagnostics = client->diagnosticsAt(uri, cursor); + for (const Diagnostic &diagnostic : diagnostics) { + ClangdDiagnostic clangdDiagnostic(diagnostic); + if (const auto actions = clangdDiagnostic.codeActions()) { + for (const CodeAction &action : *actions) + result << new LanguageClient::CodeActionQuickFixOperation(action, client); + } + } +} + +} // namespace Internal +} // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/clangdquickfixfactory.h b/src/plugins/clangcodemodel/clangdquickfixfactory.h new file mode 100644 index 00000000000..022b6033407 --- /dev/null +++ b/src/plugins/clangcodemodel/clangdquickfixfactory.h @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** 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 + +namespace ClangCodeModel { +namespace Internal { + +class ClangdQuickFixFactory : public CppEditor::CppQuickFixFactory +{ +public: + ClangdQuickFixFactory(); + + void match(const CppEditor::Internal::CppQuickFixInterface &interface, + QuickFixOperations &result) override; +}; + +} // namespace Internal +} // namespace ClangCodeModel diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp index 06746bbb840..569cf792c88 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp @@ -26,16 +26,17 @@ #include "clangmodelmanagersupport.h" #include "clangconstants.h" +#include "clangcurrentdocumentfilter.h" #include "clangdclient.h" +#include "clangdquickfixfactory.h" #include "clangeditordocumentprocessor.h" -#include "clangutils.h" #include "clangfollowsymbol.h" +#include "clanggloballocatorfilters.h" #include "clanghoverhandler.h" +#include "clangoverviewmodel.h" #include "clangprojectsettings.h" #include "clangrefactoringengine.h" -#include "clangcurrentdocumentfilter.h" -#include "clanggloballocatorfilters.h" -#include "clangoverviewmodel.h" +#include "clangutils.h" #include #include @@ -128,6 +129,7 @@ ClangModelManagerSupport::ClangModelManagerSupport() // TODO: Enable this once we do document-level stuff with clangd (highlighting etc) // createClient(nullptr, {}); m_generatorSynchronizer.setCancelOnWait(true); + new ClangdQuickFixFactory(); // memory managed by CppEditor::g_cppQuickFixFactories } ClangModelManagerSupport::~ClangModelManagerSupport() From 28abf32b30c4fa4d31992f505bd74c1fb026a0d5 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Wed, 30 Jun 2021 13:22:07 +0200 Subject: [PATCH 084/149] Doc: Describe new refactoring actions Task-number: QTCREATORBUG-25642 Change-Id: I5aeb72ce81d82d72627720145947172135533995 Reviewed-by: Christian Kandeler --- .../src/editors/creator-quick-fixes.qdoc | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/doc/qtcreator/src/editors/creator-quick-fixes.qdoc b/doc/qtcreator/src/editors/creator-quick-fixes.qdoc index 8b1f056a118..efe8ecc058a 100644 --- a/doc/qtcreator/src/editors/creator-quick-fixes.qdoc +++ b/doc/qtcreator/src/editors/creator-quick-fixes.qdoc @@ -571,6 +571,21 @@ \endlist \li \c Q_PROPERTY \row + \li Generate Q_PROPERTY and Missing Members + \li Generates a Q_PROPERTY and adds missing members to it, as + described above. + \li Class member + \row + \li Generate Constant Q_PROPERTY and Missing Members + \li Generates a constant Q_PROPERTY and adds missing members + to it, as described above. + \li Class member + \row + \li Generate Q_PROPERTY and Missing Members with Reset Function + \li Generates a Q_PROPERTY and adds missing members to it, as + described above, but with an additional \c reset function. + \li Class member + \row \li Apply Changes \li Keeps function declarations and definitions synchronized by checking for the matching declaration or definition when you @@ -617,6 +632,19 @@ \li Creates either both getter and setter member functions for member variables or only a getter or setter. \li Member variable in class definition + \row + \li Generate Getter and Setter + \li Creates getter and setter member functions for a member + variable. + \li Member variable in class definition + \row + \li Generate Getter + \li Creates a getter member function for a member variable. + \li Member variable in class definition + \row + \li Generate Setter + \li Creates a setter member function for a member variable. + \li Member variable in class definition \row \li Generate Constructor \li Creates a constructor for a class. From a2790ac95bcec4899e450198a30ed7946124d514 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 29 Jun 2021 16:57:16 +0200 Subject: [PATCH 085/149] Utils: Implement FilePath::withExecutableSuffix() Change-Id: Ibf6919991b229eb91953b3c5b5cbb75a1be7ff5d Reviewed-by: Christian Stenger --- src/libs/utils/fileutils.cpp | 14 ++++++++++++++ src/libs/utils/fileutils.h | 2 ++ .../devicesupport/devicemanager.cpp | 6 ++++++ 3 files changed, 22 insertions(+) diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index 1f5a7b7cbc1..1f32637cbf2 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -29,6 +29,7 @@ #include "algorithm.h" #include "commandline.h" #include "environment.h" +#include "hostosinfo.h" #include "qtcassert.h" #include @@ -1015,6 +1016,19 @@ FilePath FilePath::symLinkTarget() const return FilePath::fromString(info.symLinkTarget()); } +FilePath FilePath::withExecutableSuffix() const +{ + OsType osType; + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.osType, return {}); + osType = s_deviceHooks.osType(*this); + } else { + osType = HostOsInfo::hostOs(); + } + FilePath res = *this; + res.setPath(OsSpecificAspects::withExecutableSuffix(osType, m_data)); + return res; +} /// Find the parent directory of a given directory. diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index 6a67aaedd4a..96c27dc9d00 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -90,6 +90,7 @@ public: std::function writeFileContents; std::function lastModified; std::function permissions; + std::function osType; }; class QTCREATOR_UTILS_EXPORT FilePath @@ -190,6 +191,7 @@ public: FilePath canonicalPath() const; FilePath symLinkTarget() const; + FilePath withExecutableSuffix() const; FilePath operator/(const QString &str) const; diff --git a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp index 9390b0e740a..532240b1331 100644 --- a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp @@ -496,6 +496,12 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_uniquepermissions(filePath); }; + deviceHooks.osType = [](const FilePath &filePath) { + auto device = DeviceManager::deviceForPath(filePath); + QTC_ASSERT(device, return OsTypeOther); + return device->osType(); + }; + FilePath::setDeviceFileHooks(deviceHooks); DeviceProcessHooks processHooks; From 2d8cd0c90c50c4dbb490dd9e851d243ba3c26422 Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Thu, 15 Apr 2021 22:48:23 +0200 Subject: [PATCH 086/149] CMakePM: Update missing expandable CMake variables at project load The following variables: "CMAKE_C_COMPILER", "CMAKE_CXX_COMPILER", "QT_QMAKE_EXECUTABLE", "QT_HOST_PATH", "CMAKE_PREFIX_PATH", "CMAKE_FIND_ROOT_PATH", "CMAKE_PROJECT_INCLUDE_BEFORE", "CMAKE_TOOLCHAIN_FILE" will be checked to see if the existing values have the same values from the initial cmake parameters list. Only the CMakeCache.txt values that do not exist on the file system will be updated. If not, the updated value will be presented in the dialog for upgrade, or marked as bold in the settings dialog. CMAKE_PROJECT_INCLUDE_BEFORE is dependent on Qt Creator version / path and needs to be updated. Fixes: QTCREATORBUG-24443 Change-Id: I1eeb44df3a7914051084ef405af5f5621cc5a4e2 Reviewed-by: Eike Ziller Reviewed-by: Qt CI Bot --- .../cmakebuildconfiguration.cpp | 3 + .../cmakebuildconfiguration.h | 1 + .../cmakeprojectmanager/cmakebuildsystem.cpp | 64 +++++++++++++++++++ .../cmakeprojectmanager/cmakebuildsystem.h | 1 + 4 files changed, 69 insertions(+) diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp index 81b98dd2058..e84491f8041 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp @@ -459,6 +459,9 @@ CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc) connect(bc, &CMakeBuildConfiguration::errorOccurred, this, &CMakeBuildSettingsWidget::setError); connect(bc, &CMakeBuildConfiguration::warningOccurred, this, &CMakeBuildSettingsWidget::setWarning); + connect(bc, &CMakeBuildConfiguration::configurationChanged, this, [this](const CMakeConfig &config) { + m_configModel->setBatchEditConfiguration(config); + }); updateFromKit(); connect(m_buildConfiguration->target(), &Target::kitChanged, diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h index 45519c77e3a..c415e9e4e25 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.h @@ -84,6 +84,7 @@ signals: void errorOccurred(const QString &message); void warningOccurred(const QString &message); void signingFlagsChanged(); + void configurationChanged(const CMakeConfig &config); protected: bool fromMap(const QVariantMap &map) override; diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp index 6751359574b..e1b38dfc86d 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -691,6 +691,8 @@ void CMakeBuildSystem::updateProjectData() } updateQmlJSCodeModel(extraHeaderPaths, moduleMappings); } + updateInitialCMakeExpandableVars(); + emit cmakeBuildConfiguration()->buildTypeChanged(); qCDebug(cmakeBuildSystemLog) << "All CMake project data up to date."; @@ -1243,5 +1245,67 @@ void CMakeBuildSystem::updateQmlJSCodeModel(const QStringList &extraHeaderPaths, modelManager->updateProjectInfo(projectInfo, p); } +void CMakeBuildSystem::updateInitialCMakeExpandableVars() +{ + const CMakeConfig &cm = cmakeBuildConfiguration()->configurationFromCMake(); + const CMakeConfig &initialConfig = + CMakeConfigItem::itemsFromArguments(cmakeBuildConfiguration()->initialCMakeArguments()); + + CMakeConfig config; + + // Replace path values that do not exist on file system + const QByteArrayList singlePathList = { + "CMAKE_C_COMPILER", + "CMAKE_CXX_COMPILER", + "QT_QMAKE_EXECUTABLE", + "QT_HOST_PATH", + "CMAKE_PROJECT_INCLUDE_BEFORE", + "CMAKE_TOOLCHAIN_FILE" + }; + for (const auto &var : singlePathList) { + auto it = std::find_if(cm.cbegin(), cm.cend(), [var](const CMakeConfigItem &item) { + return item.key == var; + }); + + if (it != cm.cend()) { + const QByteArray initialValue = CMakeConfigItem::expandedValueOf(kit(), var, initialConfig).toUtf8(); + if (!initialValue.isEmpty() + && it->value != initialValue + && !FilePath::fromString(QString::fromUtf8(it->value)).exists()) { + CMakeConfigItem item(*it); + item.value = initialValue; + + config << item; + } + } + } + + // Prepend new values to existing path lists + const QByteArrayList multiplePathList = { + "CMAKE_PREFIX_PATH", + "CMAKE_FIND_ROOT_PATH" + }; + for (const auto &var : multiplePathList) { + auto it = std::find_if(cm.cbegin(), cm.cend(), [var](const CMakeConfigItem &item) { + return item.key == var; + }); + + if (it != cm.cend()) { + const QByteArray initialValue = CMakeConfigItem::expandedValueOf(kit(), var, initialConfig).toUtf8(); + if (!initialValue.isEmpty() && !it->value.contains(initialValue)) { + CMakeConfigItem item(*it); + item.value = initialValue; + item.value.append(";"); + item.value.append(it->value); + + config << item; + } + } + } + + if (!config.isEmpty()) + emit cmakeBuildConfiguration()->configurationChanged(config); +} + } // namespace Internal } // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h index 6f0bcea68cb..40489beb442 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.h +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.h @@ -143,6 +143,7 @@ private: QList findExtraCompilers(); void updateQmlJSCodeModel(const QStringList &extraHeaderPaths, const QList &moduleMappings); + void updateInitialCMakeExpandableVars(); void handleParsingSucceeded(); void handleParsingFailed(const QString &msg); From b3c83b79f70546c4ba16f41b30e32b5d163de71a Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Tue, 29 Jun 2021 18:13:32 +0200 Subject: [PATCH 087/149] CMakePM: Project settings widgets grouping Group together Initial CMake Parameters and Re-Configure button. Change-Id: Ieb7d5d65b9465f3e77f1e350bc003777817f1af2 Reviewed-by: Alessandro Portale --- src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp index e84491f8041..8ac33da098f 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp @@ -344,12 +344,11 @@ CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc) Column { Form { buildDirAspect, - bc->aspect(), bc->aspect(), - QString(), clearCMakeConfiguration, + bc->aspect(), + QString(), clearCMakeConfiguration, Break(), qmlDebugAspect }, - Space(10), m_warningMessageLabel, Space(10), cmakeConfiguration, From fbb804c442e34d669f7ce4e6a39aacd554ef8008 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Mon, 28 Jun 2021 14:55:54 +0200 Subject: [PATCH 088/149] Clangd: Add per-project settings Users might want to use clangd for certain project, but not for others. Change-Id: Id29ce3349f0acd359cf7c824ece073b147ed2280 Reviewed-by: David Schulz --- src/plugins/clangcodemodel/clangdclient.cpp | 14 +- .../clangmodelmanagersupport.cpp | 10 +- .../clangcodemodel/test/clangdtests.cpp | 2 +- src/plugins/cppeditor/cppeditortestcase.cpp | 2 +- .../followsymbol_switchmethoddecldef_test.cpp | 6 +- src/plugins/cpptools/cppcodemodelsettings.cpp | 90 +++++++-- src/plugins/cpptools/cppcodemodelsettings.h | 40 +++- .../cpptools/cppcodemodelsettingspage.cpp | 176 ++++++++++++------ .../cpptools/cppcodemodelsettingspage.h | 31 +++ src/plugins/cpptools/cpptoolsplugin.cpp | 9 + 10 files changed, 286 insertions(+), 94 deletions(-) diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 0a3c33327d3..0b027f44752 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -416,15 +416,15 @@ public: : Request("textDocument/symbolInfo", params) {} }; -static BaseClientInterface *clientInterface(const Utils::FilePath &jsonDbDir) +static BaseClientInterface *clientInterface(Project *project, const Utils::FilePath &jsonDbDir) { QString indexingOption = "--background-index"; - if (!CppTools::ClangdSettings::indexingEnabled()) + const CppTools::ClangdSettings settings = CppTools::ClangdProjectSettings(project).settings(); + if (!settings.indexingEnabled()) indexingOption += "=0"; - Utils::CommandLine cmd{CppTools::ClangdSettings::clangdFilePath(), - {indexingOption, "--limit-results=0"}}; - if (CppTools::ClangdSettings::workerThreadLimit() != 0) - cmd.addArg("-j=" + QString::number(CppTools::ClangdSettings::workerThreadLimit())); + Utils::CommandLine cmd{settings.clangdFilePath(), {indexingOption, "--limit-results=0"}}; + if (settings.workerThreadLimit() != 0) + cmd.addArg("-j=" + QString::number(settings.workerThreadLimit())); if (!jsonDbDir.isEmpty()) cmd.addArg("--compile-commands-dir=" + jsonDbDir.toString()); if (clangdLog().isDebugEnabled()) @@ -700,7 +700,7 @@ public: }; ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir) - : Client(clientInterface(jsonDbDir)), d(new Private(this)) + : Client(clientInterface(project, jsonDbDir)), d(new Private(this)) { setName(tr("clangd")); LanguageFilter langFilter; diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp index 569cf792c88..d9a07ef777c 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp @@ -247,7 +247,7 @@ void ClangModelManagerSupport::connectToWidgetsMarkContextMenuRequested(QWidget void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *project, const CppTools::ProjectInfo &projectInfo) { - if (!CppTools::ClangdSettings::useClangd()) + if (!CppTools::ClangdProjectSettings(project).settings().useClangd()) return; const auto getJsonDbDir = [project] { if (const ProjectExplorer::Target * const target = project->activeTarget()) { @@ -266,10 +266,10 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr connect(generatorWatcher, &QFutureWatcher::finished, [this, project, projectInfo, getJsonDbDir, jsonDbDir, generatorWatcher] { generatorWatcher->deleteLater(); - if (!CppTools::ClangdSettings::useClangd()) - return; if (!ProjectExplorer::SessionManager::hasProject(project)) return; + if (!CppTools::ClangdProjectSettings(project).settings().useClangd()) + return; if (cppModelManager()->projectInfo(project) != projectInfo) return; if (getJsonDbDir() != jsonDbDir) @@ -286,10 +286,10 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr ClangdClient * const client = createClient(project, jsonDbDir); connect(client, &Client::initialized, this, [client, project, projectInfo, jsonDbDir] { using namespace ProjectExplorer; - if (!CppTools::ClangdSettings::useClangd()) - return; if (!SessionManager::hasProject(project)) return; + if (!CppTools::ClangdProjectSettings(project).settings().useClangd()) + return; if (cppModelManager()->projectInfo(project) != projectInfo) return; diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp index e4772e89742..2d5fb510d3f 100644 --- a/src/plugins/clangcodemodel/test/clangdtests.cpp +++ b/src/plugins/clangcodemodel/test/clangdtests.cpp @@ -88,7 +88,7 @@ void ClangdTest::initTestCase() const QString clangdFromEnv = qEnvironmentVariable("QTC_CLANGD"); if (!clangdFromEnv.isEmpty()) CppTools::ClangdSettings::setClangdFilePath(Utils::FilePath::fromString(clangdFromEnv)); - const auto clangd = CppTools::ClangdSettings::clangdFilePath(); + const auto clangd = CppTools::ClangdSettings::instance().clangdFilePath(); if (clangd.isEmpty() || !clangd.exists()) QSKIP("clangd binary not found"); CppTools::ClangdSettings::setUseClangd(true); diff --git a/src/plugins/cppeditor/cppeditortestcase.cpp b/src/plugins/cppeditor/cppeditortestcase.cpp index 7d6c24406fc..876c3f2aa84 100644 --- a/src/plugins/cppeditor/cppeditortestcase.cpp +++ b/src/plugins/cppeditor/cppeditortestcase.cpp @@ -78,7 +78,7 @@ bool TestDocument::hasAnchorMarker() const { return m_anchorPosition != -1; } TestCase::TestCase(bool runGarbageCollector) : CppTools::Tests::TestCase(runGarbageCollector), - m_prevUseClangd(CppTools::ClangdSettings::useClangd()) + m_prevUseClangd(CppTools::ClangdSettings::instance().useClangd()) { } diff --git a/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp b/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp index 90ee657f6c9..9af92352c38 100644 --- a/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp +++ b/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp @@ -285,7 +285,7 @@ F2TestCase::F2TestCase(CppEditorAction action, const QString curTestName = QLatin1String(QTest::currentTestFunction()); const QString tag = QLatin1String(QTest::currentDataTag()); - const bool useClangd = CppTools::ClangdSettings::useClangd(); + const bool useClangd = CppTools::ClangdSettings::instance().useClangd(); if (useClangd) { if (curTestName == "test_FollowSymbolUnderCursor_QObject_connect" || curTestName == "test_FollowSymbolUnderCursor_QObject_oldStyleConnect") { @@ -475,7 +475,7 @@ F2TestCase::F2TestCase(CppEditorAction action, // qDebug() << "Expected line:" << expectedLine; // qDebug() << "Expected column:" << expectedColumn; - if (!CppTools::ClangdSettings::useClangd()) { + if (!CppTools::ClangdSettings::instance().useClangd()) { QEXPECT_FAIL("globalVarFromEnum", "Contributor works on a fix.", Abort); QEXPECT_FAIL("matchFunctionSignature_Follow_5", "foo(int) resolved as CallAST", Abort); } @@ -545,7 +545,7 @@ void CppEditorPlugin::initTestCase() if (clangdFromEnv.isEmpty()) return; ClangdSettings::setClangdFilePath(Utils::FilePath::fromString(clangdFromEnv)); - const auto clangd = ClangdSettings::clangdFilePath(); + const auto clangd = ClangdSettings::instance().clangdFilePath(); if (clangd.isEmpty() || !clangd.exists()) return; diff --git a/src/plugins/cpptools/cppcodemodelsettings.cpp b/src/plugins/cpptools/cppcodemodelsettings.cpp index 6fd9a15b534..41a9c99ea2c 100644 --- a/src/plugins/cpptools/cppcodemodelsettings.cpp +++ b/src/plugins/cpptools/cppcodemodelsettings.cpp @@ -30,6 +30,7 @@ #include "cpptoolsreuse.h" #include +#include #include #include @@ -63,10 +64,12 @@ static QString skipIndexingBigFilesKey() static QString indexerFileSizeLimitKey() { return QLatin1String(Constants::CPPTOOLS_INDEXER_FILE_SIZE_LIMIT); } +static QString clangdSettingsKey() { return QLatin1String("ClangdSettings"); } static QString useClangdKey() { return QLatin1String("UseClangd"); } static QString clangdPathKey() { return QLatin1String("ClangdPath"); } static QString clangdIndexingKey() { return QLatin1String("ClangdIndexing"); } static QString clangdThreadLimitKey() { return QLatin1String("ClangdThreadLimit"); } +static QString clangdUseGlobalSettingsKey() { return QLatin1String("useGlobalSettings"); } static FilePath g_defaultClangdFilePath; static FilePath fallbackClangdFilePath() @@ -323,37 +326,29 @@ void ClangdSettings::setDefaultClangdPath(const Utils::FilePath &filePath) g_defaultClangdFilePath = filePath; } -FilePath ClangdSettings::clangdFilePath() +FilePath ClangdSettings::clangdFilePath() const { - if (!instance().m_data.executableFilePath.isEmpty()) - return instance().m_data.executableFilePath; + if (!m_data.executableFilePath.isEmpty()) + return m_data.executableFilePath; return fallbackClangdFilePath(); } void ClangdSettings::setData(const Data &data) { - if (data != instance().m_data) { - instance().m_data = data; - instance().saveSettings(); + if (this == &instance() && data != m_data) { + m_data = data; + saveSettings(); } } void ClangdSettings::loadSettings() { - QSettings * const s = Core::ICore::settings(); - m_data.useClangd = s->value(useClangdKey(), false).toBool(); - m_data.executableFilePath = FilePath::fromString(s->value(clangdPathKey()).toString()); - m_data.enableIndexing = s->value(clangdIndexingKey(), true).toBool(); - m_data.workerThreadLimit = s->value(clangdThreadLimitKey(), 0).toInt(); + m_data.fromMap(Core::ICore::settings()->value(clangdSettingsKey()).toMap()); } void ClangdSettings::saveSettings() { - QSettings * const s = Core::ICore::settings(); - s->setValue(useClangdKey(), useClangd()); - s->setValue(clangdPathKey(), m_data.executableFilePath.toString()); - s->setValue(clangdIndexingKey(), m_data.enableIndexing); - s->setValue(clangdThreadLimitKey(), m_data.workerThreadLimit); + Core::ICore::settings()->setValue(clangdSettingsKey(), m_data.toMap()); } #ifdef WITH_TESTS @@ -363,3 +358,66 @@ void ClangdSettings::setClangdFilePath(const Utils::FilePath &filePath) instance().m_data.executableFilePath = filePath; } #endif + +ClangdProjectSettings::ClangdProjectSettings(ProjectExplorer::Project *project) : m_project(project) +{ + loadSettings(); +} + +ClangdSettings ClangdProjectSettings::settings() const +{ + if (m_useGlobalSettings) + return ClangdSettings::instance(); + return ClangdSettings(m_customSettings); +} + +void ClangdProjectSettings::setSettings(const ClangdSettings::Data &data) +{ + m_customSettings = data; + saveSettings(); +} + +void ClangdProjectSettings::setUseGlobalSettings(bool useGlobal) +{ + m_useGlobalSettings = useGlobal; + saveSettings(); +} + +void ClangdProjectSettings::loadSettings() +{ + if (!m_project) + return; + const QVariantMap data = m_project->namedSettings(clangdSettingsKey()).toMap(); + m_useGlobalSettings = data.value(clangdUseGlobalSettingsKey(), true).toBool(); + if (!m_useGlobalSettings) + m_customSettings.fromMap(data); +} + +void ClangdProjectSettings::saveSettings() +{ + if (!m_project) + return; + QVariantMap data; + if (!m_useGlobalSettings) + data = m_customSettings.toMap(); + data.insert(clangdUseGlobalSettingsKey(), m_useGlobalSettings); + m_project->setNamedSettings(clangdSettingsKey(), data); +} + +QVariantMap ClangdSettings::Data::toMap() const +{ + QVariantMap map; + map.insert(useClangdKey(), useClangd); + map.insert(clangdPathKey(), executableFilePath.toString()); + map.insert(clangdIndexingKey(), enableIndexing); + map.insert(clangdThreadLimitKey(), workerThreadLimit); + return map; +} + +void ClangdSettings::Data::fromMap(const QVariantMap &map) +{ + useClangd = map.value(useClangdKey(), false).toBool(); + executableFilePath = FilePath::fromString(map.value(clangdPathKey()).toString()); + enableIndexing = map.value(clangdIndexingKey(), true).toBool(); + workerThreadLimit = map.value(clangdThreadLimitKey(), 0).toInt(); +} diff --git a/src/plugins/cpptools/cppcodemodelsettings.h b/src/plugins/cpptools/cppcodemodelsettings.h index eb4abe4537e..6bfccc4bf75 100644 --- a/src/plugins/cpptools/cppcodemodelsettings.h +++ b/src/plugins/cpptools/cppcodemodelsettings.h @@ -38,6 +38,8 @@ QT_BEGIN_NAMESPACE class QSettings; QT_END_NAMESPACE +namespace ProjectExplorer { class Project; } + namespace CppTools { class CPPTOOLS_EXPORT CppCodeModelSettings : public QObject @@ -102,21 +104,27 @@ public: class Data { public: + QVariantMap toMap() const; + void fromMap(const QVariantMap &map); + Utils::FilePath executableFilePath; int workerThreadLimit = 0; bool useClangd = false; bool enableIndexing = true; }; - static bool useClangd() { return instance().m_data.useClangd; } + ClangdSettings(const Data &data) : m_data(data) {} + + static ClangdSettings &instance(); + bool useClangd() const { return m_data.useClangd; } static void setDefaultClangdPath(const Utils::FilePath &filePath); - static Utils::FilePath clangdFilePath(); - static bool indexingEnabled() { return instance().m_data.enableIndexing; } - static int workerThreadLimit() { return instance().m_data.workerThreadLimit; } + Utils::FilePath clangdFilePath() const; + bool indexingEnabled() const { return m_data.enableIndexing; } + int workerThreadLimit() const { return m_data.workerThreadLimit; } - static void setData(const Data &data); - static Data data() { return instance().m_data; } + void setData(const Data &data); + Data data() const { return m_data; } #ifdef WITH_TESTS static void setUseClangd(bool use); @@ -125,7 +133,6 @@ public: private: ClangdSettings() { loadSettings(); } - static ClangdSettings &instance(); void loadSettings(); void saveSettings(); @@ -133,4 +140,23 @@ private: Data m_data; }; +class CPPTOOLS_EXPORT ClangdProjectSettings +{ +public: + ClangdProjectSettings(ProjectExplorer::Project *project); + + ClangdSettings settings() const; + void setSettings(const ClangdSettings::Data &data); + bool useGlobalSettings() const { return m_useGlobalSettings; } + void setUseGlobalSettings(bool useGlobal); + +private: + void loadSettings(); + void saveSettings(); + + ProjectExplorer::Project * const m_project; + ClangdSettings::Data m_customSettings; + bool m_useGlobalSettings = true; +}; + } // namespace CppTools diff --git a/src/plugins/cpptools/cppcodemodelsettingspage.cpp b/src/plugins/cpptools/cppcodemodelsettingspage.cpp index 05b39cfea2c..5631f8b8d1c 100644 --- a/src/plugins/cpptools/cppcodemodelsettingspage.cpp +++ b/src/plugins/cpptools/cppcodemodelsettingspage.cpp @@ -191,71 +191,99 @@ CppCodeModelSettingsPage::CppCodeModelSettingsPage(CppCodeModelSettings *setting setWidgetCreator([settings] { return new CppCodeModelSettingsWidget(settings); }); } +class ClangdSettingsWidget::Private +{ +public: + QCheckBox useClangdCheckBox; + QCheckBox indexingCheckBox; + QSpinBox threadLimitSpinBox; + Utils::PathChooser clangdChooser; +}; -class ClangdSettingsWidget final : public Core::IOptionsPageWidget +ClangdSettingsWidget::ClangdSettingsWidget(const ClangdSettings &settings) : d(new Private) +{ + d->useClangdCheckBox.setText(tr("Use clangd (EXPERIMENTAL)")); + d->useClangdCheckBox.setChecked(settings.useClangd()); + d->useClangdCheckBox.setToolTip(tr("Changing this option does not affect projects " + "that are already open.")); + d->clangdChooser.setExpectedKind(Utils::PathChooser::ExistingCommand); + d->clangdChooser.setFilePath(settings.clangdFilePath()); + d->clangdChooser.setEnabled(d->useClangdCheckBox.isChecked()); + d->indexingCheckBox.setChecked(settings.indexingEnabled()); + d->indexingCheckBox.setToolTip(tr( + "If background indexing is enabled, global symbol searches will yield\n" + "more accurate results, at the cost of additional CPU load when\n" + "the project is first opened.")); + d->threadLimitSpinBox.setValue(settings.workerThreadLimit()); + d->threadLimitSpinBox.setSpecialValueText("Automatic"); + + const auto layout = new QVBoxLayout(this); + layout->addWidget(&d->useClangdCheckBox); + const auto formLayout = new QFormLayout; + const auto chooserLabel = new QLabel(tr("Path to executable:")); + formLayout->addRow(chooserLabel, &d->clangdChooser); + const auto indexingLabel = new QLabel(tr("Enable background indexing:")); + formLayout->addRow(indexingLabel, &d->indexingCheckBox); + const auto threadLimitLayout = new QHBoxLayout; + threadLimitLayout->addWidget(&d->threadLimitSpinBox); + threadLimitLayout->addStretch(1); + const auto threadLimitLabel = new QLabel(tr("Set worker thread count:")); + formLayout->addRow(threadLimitLabel, threadLimitLayout); + layout->addLayout(formLayout); + layout->addStretch(1); + + const auto toggleEnabled = [=](const bool checked) { + chooserLabel->setEnabled(checked); + d->clangdChooser.setEnabled(checked); + indexingLabel->setEnabled(checked); + d->indexingCheckBox.setEnabled(checked); + d->threadLimitSpinBox.setEnabled(checked); + }; + connect(&d->useClangdCheckBox, &QCheckBox::toggled, toggleEnabled); + toggleEnabled(d->useClangdCheckBox.isChecked()); + d->threadLimitSpinBox.setEnabled(d->useClangdCheckBox.isChecked()); + + connect(&d->useClangdCheckBox, &QCheckBox::toggled, + this, &ClangdSettingsWidget::settingsDataChanged); + connect(&d->indexingCheckBox, &QCheckBox::toggled, + this, &ClangdSettingsWidget::settingsDataChanged); + connect(&d->threadLimitSpinBox, qOverload(&QSpinBox::valueChanged), + this, &ClangdSettingsWidget::settingsDataChanged); + connect(&d->clangdChooser, &Utils::PathChooser::pathChanged, + this, &ClangdSettingsWidget::settingsDataChanged); +} + +ClangdSettingsWidget::~ClangdSettingsWidget() +{ + delete d; +} + +ClangdSettings::Data ClangdSettingsWidget::settingsData() const +{ + ClangdSettings::Data data; + data.useClangd = d->useClangdCheckBox.isChecked(); + data.executableFilePath = d->clangdChooser.filePath(); + data.enableIndexing = d->indexingCheckBox.isChecked(); + data.workerThreadLimit = d->threadLimitSpinBox.value(); + return data; +} + +class ClangdSettingsPageWidget final : public Core::IOptionsPageWidget { Q_DECLARE_TR_FUNCTIONS(CppTools::Internal::ClangdSettingsWidget) public: - ClangdSettingsWidget() + ClangdSettingsPageWidget() : m_widget(ClangdSettings::instance()) { - m_useClangdCheckBox.setText(tr("Use clangd (EXPERIMENTAL)")); - m_useClangdCheckBox.setChecked(ClangdSettings::useClangd()); - m_useClangdCheckBox.setToolTip(tr("Changing this option does not affect projects " - "that are already open.")); - m_clangdChooser.setExpectedKind(Utils::PathChooser::ExistingCommand); - m_clangdChooser.setFilePath(ClangdSettings::clangdFilePath()); - m_clangdChooser.setEnabled(m_useClangdCheckBox.isChecked()); - m_indexingCheckBox.setChecked(ClangdSettings::indexingEnabled()); - m_indexingCheckBox.setToolTip(tr( - "If background indexing is enabled, global symbol searches will yield\n" - "more accurate results, at the cost of additional CPU load when\n" - "the project is first opened.")); - m_threadLimitSpinBox.setValue(ClangdSettings::workerThreadLimit()); - m_threadLimitSpinBox.setSpecialValueText("Automatic"); const auto layout = new QVBoxLayout(this); - layout->addWidget(&m_useClangdCheckBox); - const auto formLayout = new QFormLayout; - const auto chooserLabel = new QLabel(tr("Path to executable:")); - formLayout->addRow(chooserLabel, &m_clangdChooser); - const auto indexingLabel = new QLabel(tr("Enable background indexing:")); - formLayout->addRow(indexingLabel, &m_indexingCheckBox); - const auto threadLimitLayout = new QHBoxLayout; - threadLimitLayout->addWidget(&m_threadLimitSpinBox); - threadLimitLayout->addStretch(1); - const auto threadLimitLabel = new QLabel(tr("Set worker thread count:")); - formLayout->addRow(threadLimitLabel, threadLimitLayout); - layout->addLayout(formLayout); - layout->addStretch(1); - - const auto toggleEnabled = [=](const bool checked) { - chooserLabel->setEnabled(checked); - m_clangdChooser.setEnabled(checked); - indexingLabel->setEnabled(checked); - m_indexingCheckBox.setEnabled(checked); - m_threadLimitSpinBox.setEnabled(checked); - }; - connect(&m_useClangdCheckBox, &QCheckBox::toggled, toggleEnabled); - toggleEnabled(m_useClangdCheckBox.isChecked()); - m_threadLimitSpinBox.setEnabled(m_useClangdCheckBox.isChecked()); + layout->addWidget(&m_widget); } private: - void apply() final - { - ClangdSettings::Data data; - data.useClangd = m_useClangdCheckBox.isChecked(); - data.executableFilePath = m_clangdChooser.filePath(); - data.enableIndexing = m_indexingCheckBox.isChecked(); - data.workerThreadLimit = m_threadLimitSpinBox.value(); - ClangdSettings::setData(data); - } + void apply() final { ClangdSettings::instance().setData(m_widget.settingsData()); } - QCheckBox m_useClangdCheckBox; - QCheckBox m_indexingCheckBox; - QSpinBox m_threadLimitSpinBox; - Utils::PathChooser m_clangdChooser; + ClangdSettingsWidget m_widget; }; ClangdSettingsPage::ClangdSettingsPage() @@ -263,7 +291,47 @@ ClangdSettingsPage::ClangdSettingsPage() setId("K.Clangd"); setDisplayName(ClangdSettingsWidget::tr("Clangd")); setCategory(Constants::CPP_SETTINGS_CATEGORY); - setWidgetCreator([] { return new ClangdSettingsWidget; }); + setWidgetCreator([] { return new ClangdSettingsPageWidget; }); +} + + +class ClangdProjectSettingsWidget::Private +{ +public: + Private(const ClangdProjectSettings &s) : settings(s), widget(s.settings()) {} + + ClangdProjectSettings settings; + ClangdSettingsWidget widget; + QCheckBox useGlobalSettingsCheckBox; +}; + +ClangdProjectSettingsWidget::ClangdProjectSettingsWidget(const ClangdProjectSettings &settings) + : d(new Private(settings)) +{ + const auto layout = new QVBoxLayout(this); + d->useGlobalSettingsCheckBox.setText(tr("Use global settings")); + layout->addWidget(&d->useGlobalSettingsCheckBox); + const auto separator = new QFrame; + separator->setFrameShape(QFrame::HLine); + layout->addWidget(separator); + layout->addWidget(&d->widget); + + d->useGlobalSettingsCheckBox.setChecked(d->settings.useGlobalSettings()); + d->widget.setEnabled(!d->settings.useGlobalSettings()); + connect(&d->useGlobalSettingsCheckBox, &QCheckBox::toggled, [this](bool checked) { + d->widget.setEnabled(!checked); + d->settings.setUseGlobalSettings(checked); + if (!checked) + d->settings.setSettings(d->widget.settingsData()); + }); + connect(&d->widget, &ClangdSettingsWidget::settingsDataChanged, [this] { + d->settings.setSettings(d->widget.settingsData()); + }); +} + +ClangdProjectSettingsWidget::~ClangdProjectSettingsWidget() +{ + delete d; } } // Internal diff --git a/src/plugins/cpptools/cppcodemodelsettingspage.h b/src/plugins/cpptools/cppcodemodelsettingspage.h index 5e48be1ffe0..b8c595c7c5e 100644 --- a/src/plugins/cpptools/cppcodemodelsettingspage.h +++ b/src/plugins/cpptools/cppcodemodelsettingspage.h @@ -44,5 +44,36 @@ public: explicit ClangdSettingsPage(); }; +class ClangdSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + ClangdSettingsWidget(const ClangdSettings &settings); + ~ClangdSettingsWidget(); + + ClangdSettings::Data settingsData() const; + +signals: + void settingsDataChanged(); + +private: + class Private; + Private * const d; +}; + +class ClangdProjectSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + ClangdProjectSettingsWidget(const ClangdProjectSettings &settings); + ~ClangdProjectSettingsWidget(); + +private: + class Private; + Private * const d; +}; + } // Internal namespace } // CppTools namespace diff --git a/src/plugins/cpptools/cpptoolsplugin.cpp b/src/plugins/cpptools/cpptoolsplugin.cpp index 87ecc25d713..1e77501c0f4 100644 --- a/src/plugins/cpptools/cpptoolsplugin.cpp +++ b/src/plugins/cpptools/cpptoolsplugin.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include @@ -210,6 +211,14 @@ bool CppToolsPlugin::initialize(const QStringList &arguments, QString *error) tr("Insert \"#pragma once\" instead of \"#ifndef\" include guards into header file"), [] { return usePragmaOnce() ? QString("true") : QString(); }); + const auto panelFactory = new ProjectExplorer::ProjectPanelFactory; + panelFactory->setPriority(100); + panelFactory->setDisplayName(tr("Clangd")); + panelFactory->setCreateWidgetFunction([](ProjectExplorer::Project *project) { + return new ClangdProjectSettingsWidget(project); + }); + ProjectExplorer::ProjectPanelFactory::registerFactory(panelFactory); + return true; } From c347534b4222fdb26b58de0b7043463afc6f522e Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Wed, 30 Jun 2021 17:03:21 +0200 Subject: [PATCH 089/149] QtSupport: Hide empty warning icon for qml debugging Change-Id: Ic7c2981235946763417c2aed701f4da921cdbe90 Reviewed-by: Alessandro Portale --- src/plugins/qtsupport/qtbuildaspects.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/qtsupport/qtbuildaspects.cpp b/src/plugins/qtsupport/qtbuildaspects.cpp index 2afc339b60b..90196ff01df 100644 --- a/src/plugins/qtsupport/qtbuildaspects.cpp +++ b/src/plugins/qtsupport/qtbuildaspects.cpp @@ -54,6 +54,7 @@ void QmlDebuggingAspect::addToLayout(LayoutBuilder &builder) SelectionAspect::addToLayout(builder); const auto warningLabel = createSubWidget(QString(), InfoLabel::Warning); warningLabel->setElideMode(Qt::ElideNone); + warningLabel->setVisible(false); builder.addRow({{}, warningLabel}); const auto changeHandler = [this, warningLabel] { QString warningText; From 1a5c15b3e30c260d72e0c4d46908ee66ade96449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kama=20W=C3=B3jcik?= Date: Tue, 29 Jun 2021 15:51:54 +0200 Subject: [PATCH 090/149] QmlDesigner: Add new constant value for qtquick3d MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I57126c7fb9a20b2970addbf55d979f1e72c6e533 Reviewed-by: Henning Gründl Reviewed-by: Tim Jenssen --- .../propertyEditorQmlSources/imports/StudioTheme/Values.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml index c34e70b3028..c3e91a6584c 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/Values.qml @@ -110,6 +110,8 @@ QtObject { property real infinityControlWidth: values.iconAreaWidth property real infinityControlHeight: values.height + property real transform3DSectionSpacing: 15 + // Control sizes property real defaultControlWidth: values.squareComponentWidth * 5 From 2ceb105323082cd666feb91002fcb404f4afa5f8 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 29 Jun 2021 16:42:46 +0200 Subject: [PATCH 091/149] Utils: FilePathify BuildableHelperLibrary Change-Id: I18791de1ed69e598b6c35cbae4ba28ac8129e9de Reviewed-by: Christian Stenger --- src/libs/utils/buildablehelperlibrary.cpp | 60 +++++++++++----------- src/libs/utils/buildablehelperlibrary.h | 6 +-- src/plugins/qtsupport/baseqtversion.cpp | 12 ++--- src/plugins/qtsupport/qtoptionspage.cpp | 5 +- src/plugins/qtsupport/qtversionmanager.cpp | 2 +- 5 files changed, 42 insertions(+), 43 deletions(-) diff --git a/src/libs/utils/buildablehelperlibrary.cpp b/src/libs/utils/buildablehelperlibrary.cpp index d1ad77e54ba..f08e7347459 100644 --- a/src/libs/utils/buildablehelperlibrary.cpp +++ b/src/libs/utils/buildablehelperlibrary.cpp @@ -36,68 +36,68 @@ namespace Utils { -bool BuildableHelperLibrary::isQtChooser(const QFileInfo &info) +bool BuildableHelperLibrary::isQtChooser(const FilePath &filePath) { - return info.isSymLink() && info.symLinkTarget().endsWith(QLatin1String("/qtchooser")); + return filePath.symLinkTarget().endsWith("/qtchooser"); } -QString BuildableHelperLibrary::qtChooserToQmakePath(const QString &path) +FilePath BuildableHelperLibrary::qtChooserToQmakePath(const FilePath &qtChooser) { const QString toolDir = QLatin1String("QTTOOLDIR=\""); QtcProcess proc; proc.setTimeoutS(1); - proc.setCommand({path, {"-print-env"}}); + proc.setCommand({qtChooser, {"-print-env"}}); proc.runBlocking(); if (proc.result() != QtcProcess::FinishedWithSuccess) - return QString(); + return {}; const QString output = proc.stdOut(); int pos = output.indexOf(toolDir); if (pos == -1) - return QString(); + return {}; pos += toolDir.count(); int end = output.indexOf('\"', pos); if (end == -1) - return QString(); + return {}; - return output.mid(pos, end - pos) + QLatin1String("/qmake"); + FilePath qmake = qtChooser; + qmake.setPath(output.mid(pos, end - pos) + "/qmake"); + return qmake; } -static bool isQmake(const QString &path) +static bool isQmake(FilePath path) { if (path.isEmpty()) return false; - QFileInfo fi(path); - if (BuildableHelperLibrary::isQtChooser(fi)) - fi.setFile(BuildableHelperLibrary::qtChooserToQmakePath(fi.symLinkTarget())); - if (!fi.exists() || fi.isDir()) + if (BuildableHelperLibrary::isQtChooser(path)) + path = BuildableHelperLibrary::qtChooserToQmakePath(path.symLinkTarget()); + if (!path.exists()) return false; - return !BuildableHelperLibrary::qtVersionForQMake(fi.absoluteFilePath()).isEmpty(); + return !BuildableHelperLibrary::qtVersionForQMake(path).isEmpty(); } -static FilePath findQmakeInDir(const FilePath &path) +static FilePath findQmakeInDir(const FilePath &dir) { - if (path.isEmpty()) - return FilePath(); + if (dir.isEmpty()) + return {}; - const QString qmake = HostOsInfo::withExecutableSuffix("qmake"); - QDir dir(path.toString()); - if (dir.exists(qmake)) { - const QString qmakePath = dir.absoluteFilePath(qmake); + FilePath qmakePath = dir.pathAppended("qmake").withExecutableSuffix(); + if (qmakePath.exists()) { if (isQmake(qmakePath)) - return FilePath::fromString(qmakePath); + return qmakePath; } // Prefer qmake-qt5 to qmake-qt4 by sorting the filenames in reverse order. - const QFileInfoList candidates = dir.entryInfoList( + const FilePaths candidates = dir.dirEntries( BuildableHelperLibrary::possibleQMakeCommands(), - QDir::Files, QDir::Name | QDir::Reversed); - for (const QFileInfo &fi : candidates) { - if (fi.fileName() == qmake) + QDir::Files, + QDir::Name | QDir::Reversed); + for (const FilePath &candidate : candidates) { + if (candidate == qmakePath) continue; - if (isQmake(fi.absoluteFilePath())) - return FilePath::fromFileInfo(fi); + if (isQmake(candidate)) + return candidate; } - return FilePath(); + return {}; } FilePath BuildableHelperLibrary::findSystemQt(const Environment &env) @@ -124,7 +124,7 @@ FilePaths BuildableHelperLibrary::findQtsInEnvironment(const Environment &env, i return qmakeList; } -QString BuildableHelperLibrary::qtVersionForQMake(const QString &qmakePath) +QString BuildableHelperLibrary::qtVersionForQMake(const FilePath &qmakePath) { if (qmakePath.isEmpty()) return QString(); diff --git a/src/libs/utils/buildablehelperlibrary.h b/src/libs/utils/buildablehelperlibrary.h index 4cca2ccd9ce..67d543f5071 100644 --- a/src/libs/utils/buildablehelperlibrary.h +++ b/src/libs/utils/buildablehelperlibrary.h @@ -39,10 +39,10 @@ public: // at least version 2.0.0 and thus is a qt4 qmake static FilePath findSystemQt(const Environment &env); static FilePaths findQtsInEnvironment(const Environment &env, int maxCount = -1); - static bool isQtChooser(const QFileInfo &info); - static QString qtChooserToQmakePath(const QString &path); + static bool isQtChooser(const FilePath &filePath); + static FilePath qtChooserToQmakePath(const FilePath &path); // return true if the qmake at qmakePath is a Qt (used by QtVersion) - static QString qtVersionForQMake(const QString &qmakePath); + static QString qtVersionForQMake(const FilePath &qmakePath); // returns something like qmake4, qmake, qmake-qt4 or whatever distributions have chosen (used by QtVersion) static QStringList possibleQMakeCommands(); static QString filterForQmakeFileDialog(); diff --git a/src/plugins/qtsupport/baseqtversion.cpp b/src/plugins/qtsupport/baseqtversion.cpp index 5e512f67844..d40faa5e6d3 100644 --- a/src/plugins/qtsupport/baseqtversion.cpp +++ b/src/plugins/qtsupport/baseqtversion.cpp @@ -728,18 +728,18 @@ void BaseQtVersion::fromMap(const QVariantMap &map) d->m_overrideFeatures = Utils::Id::fromStringList(map.value(QTVERSION_OVERRIDE_FEATURES).toStringList()); d->m_qmakeCommand = FilePath::fromVariant(map.value(QTVERSIONQMAKEPATH)); - QString string = d->m_qmakeCommand.toString(); + FilePath qmake = d->m_qmakeCommand; + // FIXME: Check this is still needed or whether ProcessArgs::splitArg handles it. + QString string = d->m_qmakeCommand.path(); if (string.startsWith('~')) string.remove(0, 1).prepend(QDir::homePath()); + qmake.setPath(string); if (!d->m_qmakeCommand.needsDevice()) { - // FIXME: generalize for all devices. - QFileInfo fi(string); - if (BuildableHelperLibrary::isQtChooser(fi)) { + if (BuildableHelperLibrary::isQtChooser(qmake)) { // we don't want to treat qtchooser as a normal qmake // see e.g. QTCREATORBUG-9841, also this lead to users changing what // qtchooser forwards too behind our backs, which will inadvertly lead to bugs - string = BuildableHelperLibrary::qtChooserToQmakePath(fi.symLinkTarget()); - d->m_qmakeCommand = FilePath::fromString(string); + d->m_qmakeCommand = BuildableHelperLibrary::qtChooserToQmakePath(qmake); } } diff --git a/src/plugins/qtsupport/qtoptionspage.cpp b/src/plugins/qtsupport/qtoptionspage.cpp index 54009c47ca4..f6d03ff19c6 100644 --- a/src/plugins/qtsupport/qtoptionspage.cpp +++ b/src/plugins/qtsupport/qtoptionspage.cpp @@ -612,10 +612,9 @@ void QtOptionsPageWidget::addQtDir() if (qtVersion.isEmpty()) return; - QFileInfo fi = qtVersion.toFileInfo(); // should add all qt versions here ? - if (BuildableHelperLibrary::isQtChooser(fi)) - qtVersion = FilePath::fromString(BuildableHelperLibrary::qtChooserToQmakePath(fi.symLinkTarget())); + if (BuildableHelperLibrary::isQtChooser(qtVersion)) + qtVersion = BuildableHelperLibrary::qtChooserToQmakePath(qtVersion.symLinkTarget()); auto checkAlreadyExists = [qtVersion](TreeItem *parent) { for (int i = 0; i < parent->childCount(); ++i) { diff --git a/src/plugins/qtsupport/qtversionmanager.cpp b/src/plugins/qtsupport/qtversionmanager.cpp index 149a30a1879..f7fc7b94d55 100644 --- a/src/plugins/qtsupport/qtversionmanager.cpp +++ b/src/plugins/qtsupport/qtversionmanager.cpp @@ -439,7 +439,7 @@ static void findSystemQt() = BuildableHelperLibrary::findQtsInEnvironment(Environment::systemEnvironment()); systemQMakes.append(gatherQmakePathsFromQtChooser()); for (const FilePath &qmakePath : qAsConst(systemQMakes)) { - if (BuildableHelperLibrary::isQtChooser(qmakePath.toFileInfo())) + if (BuildableHelperLibrary::isQtChooser(qmakePath)) continue; const auto isSameQmake = [qmakePath](const BaseQtVersion *version) { return Environment::systemEnvironment(). From c6dc02313d46ac0d24dd5c2e69f1eb57c4729c2f Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 29 Jun 2021 17:51:56 +0200 Subject: [PATCH 092/149] Core: Filepathify SessionManager::sessions() Change-Id: I68b6a9f597d3a4f568653e7201a81909053df83e Reviewed-by: Christian Stenger --- src/plugins/projectexplorer/session.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plugins/projectexplorer/session.cpp b/src/plugins/projectexplorer/session.cpp index 8f1931e47d1..ce9a28546c6 100644 --- a/src/plugins/projectexplorer/session.cpp +++ b/src/plugins/projectexplorer/session.cpp @@ -782,11 +782,11 @@ QStringList SessionManager::sessions() { if (d->m_sessions.isEmpty()) { // We are not initialized yet, so do that now - QDir sessionDir(ICore::userResourcePath().toDir()); - QFileInfoList sessionFiles = sessionDir.entryInfoList(QStringList() << QLatin1String("*.qws"), QDir::NoFilter, QDir::Time); - foreach (const QFileInfo &fileInfo, sessionFiles) { - const QString &name = fileInfo.completeBaseName(); - d->m_sessionDateTimes.insert(name, fileInfo.lastModified()); + const FilePaths sessionFiles = + ICore::userResourcePath().dirEntries({"*.qws"}, QDir::NoFilter, QDir::Time); + for (const FilePath &file : sessionFiles) { + const QString &name = file.completeBaseName(); + d->m_sessionDateTimes.insert(name, file.lastModified()); if (name != QLatin1String("default")) d->m_sessions << name; } From b52f2d198a58faccf1074b19b6f85f4ccaf1abdf Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 29 Jun 2021 19:01:36 +0200 Subject: [PATCH 093/149] QtSupport: FilePathify BaseQtVersion::qmlsceneCommand() Change-Id: Ia0bc3fcb40bbda31cd114503ab599b65d7597e70 Reviewed-by: Christian Stenger --- .../qmlprojectrunconfiguration.cpp | 2 +- src/plugins/qtsupport/baseqtversion.cpp | 14 ++++++-------- src/plugins/qtsupport/baseqtversion.h | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp index 3f5c6e3ebd5..94e6237b1d7 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp @@ -176,7 +176,7 @@ FilePath QmlProjectRunConfiguration::qmlScenePath() const if (deviceType == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) { // If not given explicitly by Qt Version, try to pick it from $PATH. const bool isDesktop = version->type() == QtSupport::Constants::DESKTOPQT; - return FilePath::fromString(isDesktop ? version->qmlsceneCommand() : QString("qmlscene")); + return isDesktop ? version->qmlsceneCommand() : FilePath::fromString("qmlscene"); } IDevice::ConstPtr dev = DeviceKitAspect::device(kit); diff --git a/src/plugins/qtsupport/baseqtversion.cpp b/src/plugins/qtsupport/baseqtversion.cpp index d40faa5e6d3..36b5cf52dd4 100644 --- a/src/plugins/qtsupport/baseqtversion.cpp +++ b/src/plugins/qtsupport/baseqtversion.cpp @@ -240,7 +240,7 @@ public: QString m_designerCommand; QString m_linguistCommand; QString m_qscxmlcCommand; - QString m_qmlsceneCommand; + FilePath m_qmlsceneCommand; QString m_qmlplugindumpCommand; MacroExpanderWrapper m_expander; @@ -1019,18 +1019,16 @@ QString BaseQtVersion::qscxmlcCommand() const return d->m_qscxmlcCommand; } -QString BaseQtVersion::qmlsceneCommand() const +FilePath BaseQtVersion::qmlsceneCommand() const { if (!isValid()) - return QString(); + return {}; - if (!d->m_qmlsceneCommand.isNull()) + if (!d->m_qmlsceneCommand.isEmpty()) return d->m_qmlsceneCommand; - const QString path - = binPath().pathAppended(HostOsInfo::withExecutableSuffix("qmlscene")).toString(); - - d->m_qmlsceneCommand = QFileInfo(path).isFile() ? path : QString(); + const FilePath path = binPath() / HostOsInfo::withExecutableSuffix("qmlscene"); + d->m_qmlsceneCommand = path.isExecutableFile() ? path : FilePath(); return d->m_qmlsceneCommand; } diff --git a/src/plugins/qtsupport/baseqtversion.h b/src/plugins/qtsupport/baseqtversion.h index 65cf7669d69..c4592c20ab2 100644 --- a/src/plugins/qtsupport/baseqtversion.h +++ b/src/plugins/qtsupport/baseqtversion.h @@ -137,7 +137,7 @@ public: QString designerCommand() const; QString linguistCommand() const; QString qscxmlcCommand() const; - QString qmlsceneCommand() const; + Utils::FilePath qmlsceneCommand() const; QString qmlplugindumpCommand() const; QString qtVersionString() const; From f4a33100591f0a6b2d9c79877540dd8637a6eaf7 Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Thu, 1 Jul 2021 04:17:54 +0200 Subject: [PATCH 094/149] Clangd: Handle settings changes Users no longer have to re-load a project for settings changes to take effect. Change-Id: I86dccccac14a30514c8dac292c7765ee4806f6ba Reviewed-by: David Schulz --- src/plugins/clangcodemodel/clangdclient.cpp | 11 ++++--- src/plugins/clangcodemodel/clangdclient.h | 2 ++ .../clangmodelmanagersupport.cpp | 29 +++++++++++++++++-- .../clangcodemodel/clangmodelmanagersupport.h | 1 + src/plugins/cpptools/cppcodemodelsettings.cpp | 21 ++++---------- src/plugins/cpptools/cppcodemodelsettings.h | 22 ++++++++++++-- .../cpptools/cppcodemodelsettingspage.cpp | 9 +++--- .../cpptools/cppcodemodelsettingspage.h | 2 +- 8 files changed, 66 insertions(+), 31 deletions(-) diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 0b027f44752..134d4bf6103 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -419,7 +418,7 @@ public: static BaseClientInterface *clientInterface(Project *project, const Utils::FilePath &jsonDbDir) { QString indexingOption = "--background-index"; - const CppTools::ClangdSettings settings = CppTools::ClangdProjectSettings(project).settings(); + const CppTools::ClangdSettings settings(CppTools::ClangdProjectSettings(project).settings()); if (!settings.indexingEnabled()) indexingOption += "=0"; Utils::CommandLine cmd{settings.clangdFilePath(), {indexingOption, "--limit-results=0"}}; @@ -661,7 +660,8 @@ public: class ClangdClient::Private { public: - Private(ClangdClient *q) : q(q) {} + Private(ClangdClient *q, Project *project) + : q(q), settings(CppTools::ClangdProjectSettings(project).settings()) {} void handleFindUsagesResult(quint64 key, const QList &locations); static void handleRenameRequest(const SearchResult *search, @@ -689,6 +689,7 @@ public: const QString &type = {}); ClangdClient * const q; + const CppTools::ClangdSettings::Data settings; QHash runningFindUsages; Utils::optional followSymbolData; Utils::optional switchDeclDefData; @@ -700,7 +701,7 @@ public: }; ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir) - : Client(clientInterface(project, jsonDbDir)), d(new Private(this)) + : Client(clientInterface(project, jsonDbDir)), d(new Private(this, project)) { setName(tr("clangd")); LanguageFilter langFilter; @@ -900,6 +901,8 @@ QVersionNumber ClangdClient::versionNumber() const return d->versionNumber.value(); } +CppTools::ClangdSettings::Data ClangdClient::settingsData() const { return d->settings; } + void ClangdClient::Private::handleFindUsagesResult(quint64 key, const QList &locations) { const auto refData = runningFindUsages.find(key); diff --git a/src/plugins/clangcodemodel/clangdclient.h b/src/plugins/clangcodemodel/clangdclient.h index 5b0d61b814b..bdb8f645b57 100644 --- a/src/plugins/clangcodemodel/clangdclient.h +++ b/src/plugins/clangcodemodel/clangdclient.h @@ -25,6 +25,7 @@ #pragma once +#include #include #include #include @@ -49,6 +50,7 @@ public: bool isFullyIndexed() const; QVersionNumber versionNumber() const; + CppTools::ClangdSettings::Data settingsData() const; void openExtraFile(const Utils::FilePath &filePath, const QString &content = {}); void closeExtraFile(const Utils::FilePath &filePath); diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp index d9a07ef777c..db97618a1c3 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp @@ -122,6 +122,8 @@ ClangModelManagerSupport::ClangModelManagerSupport() CppTools::ClangdSettings::setDefaultClangdPath(Utils::FilePath::fromString( Core::ICore::clangdExecutable(CLANG_BINDIR))); + connect(&CppTools::ClangdSettings::instance(), &CppTools::ClangdSettings::changed, + this, &ClangModelManagerSupport::onClangdSettingsChanged); CppTools::CppCodeModelSettings *settings = CppTools::codeModelSettings(); connect(settings, &CppTools::CppCodeModelSettings::clangDiagnosticConfigsInvalidated, this, &ClangModelManagerSupport::onDiagnosticConfigsInvalidated); @@ -247,7 +249,7 @@ void ClangModelManagerSupport::connectToWidgetsMarkContextMenuRequested(QWidget void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *project, const CppTools::ProjectInfo &projectInfo) { - if (!CppTools::ClangdProjectSettings(project).settings().useClangd()) + if (!CppTools::ClangdProjectSettings(project).settings().useClangd) return; const auto getJsonDbDir = [project] { if (const ProjectExplorer::Target * const target = project->activeTarget()) { @@ -268,7 +270,7 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr generatorWatcher->deleteLater(); if (!ProjectExplorer::SessionManager::hasProject(project)) return; - if (!CppTools::ClangdProjectSettings(project).settings().useClangd()) + if (!CppTools::ClangdProjectSettings(project).settings().useClangd) return; if (cppModelManager()->projectInfo(project) != projectInfo) return; @@ -288,7 +290,7 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr using namespace ProjectExplorer; if (!SessionManager::hasProject(project)) return; - if (!CppTools::ClangdProjectSettings(project).settings().useClangd()) + if (!CppTools::ClangdProjectSettings(project).settings().useClangd) return; if (cppModelManager()->projectInfo(project) != projectInfo) return; @@ -587,6 +589,27 @@ void ClangModelManagerSupport::onProjectPartsRemoved(const QStringList &projectP reinitializeBackendDocuments(projectPartIds); } +void ClangModelManagerSupport::onClangdSettingsChanged() +{ + // TODO: Handle also project-less client + for (ProjectExplorer::Project * const project : ProjectExplorer::SessionManager::projects()) { + const CppTools::ClangdSettings settings( + CppTools::ClangdProjectSettings(project).settings()); + ClangdClient * const client = clientForProject(project); + if (!client) { + if (settings.useClangd()) + updateLanguageClient(project, cppModelManager()->projectInfo(project)); + continue; + } + if (!settings.useClangd()) { + LanguageClientManager::shutdownClient(client); + continue; + } + if (client->settingsData() != settings.data()) + updateLanguageClient(project, cppModelManager()->projectInfo(project)); + } +} + static ClangEditorDocumentProcessors clangProcessorsWithDiagnosticConfig( const QVector<::Utils::Id> &configIds) { diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.h b/src/plugins/clangcodemodel/clangmodelmanagersupport.h index 1c7724b5d50..9122e7a7a45 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.h +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.h @@ -114,6 +114,7 @@ private: void onProjectPartsUpdated(ProjectExplorer::Project *project); void onProjectPartsRemoved(const QStringList &projectPartIds); + void onClangdSettingsChanged(); void onDiagnosticConfigsInvalidated(const QVector<::Utils::Id> &configIds); diff --git a/src/plugins/cpptools/cppcodemodelsettings.cpp b/src/plugins/cpptools/cppcodemodelsettings.cpp index 41a9c99ea2c..c7cfc2d0cb8 100644 --- a/src/plugins/cpptools/cppcodemodelsettings.cpp +++ b/src/plugins/cpptools/cppcodemodelsettings.cpp @@ -303,18 +303,6 @@ void CppCodeModelSettings::setEnableLowerClazyLevels(bool yesno) } -static bool operator==(const ClangdSettings::Data &s1, const ClangdSettings::Data &s2) -{ - return s1.useClangd == s2.useClangd - && s1.executableFilePath == s2.executableFilePath - && s1.workerThreadLimit == s2.workerThreadLimit - && s1.enableIndexing == s2.enableIndexing; -} -static bool operator!=(const ClangdSettings::Data &s1, const ClangdSettings::Data &s2) -{ - return !(s1 == s2); -} - ClangdSettings &ClangdSettings::instance() { static ClangdSettings settings; @@ -338,6 +326,7 @@ void ClangdSettings::setData(const Data &data) if (this == &instance() && data != m_data) { m_data = data; saveSettings(); + emit changed(); } } @@ -364,23 +353,25 @@ ClangdProjectSettings::ClangdProjectSettings(ProjectExplorer::Project *project) loadSettings(); } -ClangdSettings ClangdProjectSettings::settings() const +ClangdSettings::Data ClangdProjectSettings::settings() const { if (m_useGlobalSettings) - return ClangdSettings::instance(); - return ClangdSettings(m_customSettings); + return ClangdSettings::instance().data(); + return m_customSettings; } void ClangdProjectSettings::setSettings(const ClangdSettings::Data &data) { m_customSettings = data; saveSettings(); + emit ClangdSettings::instance().changed(); } void ClangdProjectSettings::setUseGlobalSettings(bool useGlobal) { m_useGlobalSettings = useGlobal; saveSettings(); + emit ClangdSettings::instance().changed(); } void ClangdProjectSettings::loadSettings() diff --git a/src/plugins/cpptools/cppcodemodelsettings.h b/src/plugins/cpptools/cppcodemodelsettings.h index 6bfccc4bf75..d4da25f3b09 100644 --- a/src/plugins/cpptools/cppcodemodelsettings.h +++ b/src/plugins/cpptools/cppcodemodelsettings.h @@ -98,10 +98,11 @@ private: bool m_categorizeFindReferences = false; // Ephemeral! }; -class CPPTOOLS_EXPORT ClangdSettings +class CPPTOOLS_EXPORT ClangdSettings : public QObject { + Q_OBJECT public: - class Data + class CPPTOOLS_EXPORT Data { public: QVariantMap toMap() const; @@ -131,6 +132,9 @@ public: static void setClangdFilePath(const Utils::FilePath &filePath); #endif +signals: + void changed(); + private: ClangdSettings() { loadSettings(); } @@ -140,12 +144,24 @@ private: Data m_data; }; +inline bool operator==(const ClangdSettings::Data &s1, const ClangdSettings::Data &s2) +{ + return s1.useClangd == s2.useClangd + && s1.executableFilePath == s2.executableFilePath + && s1.workerThreadLimit == s2.workerThreadLimit + && s1.enableIndexing == s2.enableIndexing; +} +inline bool operator!=(const ClangdSettings::Data &s1, const ClangdSettings::Data &s2) +{ + return !(s1 == s2); +} + class CPPTOOLS_EXPORT ClangdProjectSettings { public: ClangdProjectSettings(ProjectExplorer::Project *project); - ClangdSettings settings() const; + ClangdSettings::Data settings() const; void setSettings(const ClangdSettings::Data &data); bool useGlobalSettings() const { return m_useGlobalSettings; } void setUseGlobalSettings(bool useGlobal); diff --git a/src/plugins/cpptools/cppcodemodelsettingspage.cpp b/src/plugins/cpptools/cppcodemodelsettingspage.cpp index 5631f8b8d1c..dc04dc3cb89 100644 --- a/src/plugins/cpptools/cppcodemodelsettingspage.cpp +++ b/src/plugins/cpptools/cppcodemodelsettingspage.cpp @@ -200,12 +200,12 @@ public: Utils::PathChooser clangdChooser; }; -ClangdSettingsWidget::ClangdSettingsWidget(const ClangdSettings &settings) : d(new Private) +ClangdSettingsWidget::ClangdSettingsWidget(const ClangdSettings::Data &settingsData) + : d(new Private) { + const ClangdSettings settings(settingsData); d->useClangdCheckBox.setText(tr("Use clangd (EXPERIMENTAL)")); d->useClangdCheckBox.setChecked(settings.useClangd()); - d->useClangdCheckBox.setToolTip(tr("Changing this option does not affect projects " - "that are already open.")); d->clangdChooser.setExpectedKind(Utils::PathChooser::ExistingCommand); d->clangdChooser.setFilePath(settings.clangdFilePath()); d->clangdChooser.setEnabled(d->useClangdCheckBox.isChecked()); @@ -273,9 +273,8 @@ class ClangdSettingsPageWidget final : public Core::IOptionsPageWidget Q_DECLARE_TR_FUNCTIONS(CppTools::Internal::ClangdSettingsWidget) public: - ClangdSettingsPageWidget() : m_widget(ClangdSettings::instance()) + ClangdSettingsPageWidget() : m_widget(ClangdSettings::instance().data()) { - const auto layout = new QVBoxLayout(this); layout->addWidget(&m_widget); } diff --git a/src/plugins/cpptools/cppcodemodelsettingspage.h b/src/plugins/cpptools/cppcodemodelsettingspage.h index b8c595c7c5e..220645ca3e8 100644 --- a/src/plugins/cpptools/cppcodemodelsettingspage.h +++ b/src/plugins/cpptools/cppcodemodelsettingspage.h @@ -49,7 +49,7 @@ class ClangdSettingsWidget : public QWidget Q_OBJECT public: - ClangdSettingsWidget(const ClangdSettings &settings); + ClangdSettingsWidget(const ClangdSettings::Data &settingsData); ~ClangdSettingsWidget(); ClangdSettings::Data settingsData() const; From c0a44be27bce4dbfc64218db4b2a2caf27718a3c Mon Sep 17 00:00:00 2001 From: Christian Kandeler Date: Tue, 8 Jun 2021 14:28:25 +0200 Subject: [PATCH 095/149] ClangCodeModel: Provide outline via clangd Note that we used to encode the information about symbol visibility and static-ness in the icons, which we can't do anymore, because clangd does not provide this information. On the upside, this change likely fixes a ton of bugs, as our own outline was rather "quirky". Change-Id: I099f11ec4e3c6f52cd461fb43080bbdde3bed5e5 Reviewed-by: David Schulz --- src/libs/languageserverprotocol/lsptypes.h | 3 +++ src/plugins/clangcodemodel/clangdclient.cpp | 25 +++++++++++++++++++ .../clangmodelmanagersupport.cpp | 9 +++++-- .../clangcodemodel/clangmodelmanagersupport.h | 5 ++-- src/plugins/cppeditor/cppeditorwidget.cpp | 25 ++++++++++++------- src/plugins/cppeditor/cppoutline.cpp | 6 ++++- src/plugins/cpptools/cppmodelmanager.cpp | 5 ++++ src/plugins/cpptools/cppmodelmanager.h | 1 + src/plugins/cpptools/cppmodelmanagersupport.h | 1 + src/plugins/languageclient/client.cpp | 10 ++++++++ src/plugins/languageclient/client.h | 3 +++ .../languageclient/languageclientoutline.cpp | 22 +++++++++++++--- 12 files changed, 97 insertions(+), 18 deletions(-) diff --git a/src/libs/languageserverprotocol/lsptypes.h b/src/libs/languageserverprotocol/lsptypes.h index 3231f010a29..4348e4fd19d 100644 --- a/src/libs/languageserverprotocol/lsptypes.h +++ b/src/libs/languageserverprotocol/lsptypes.h @@ -40,6 +40,8 @@ #include #include +#include + namespace LanguageServerProtocol { class LANGUAGESERVERPROTOCOL_EXPORT DocumentUri : public QUrl @@ -560,6 +562,7 @@ enum class SymbolKind { TypeParameter = 26, LastSymbolKind = TypeParameter, }; +using SymbolStringifier = std::function; namespace CompletionItemKind { enum Kind { diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 134d4bf6103..bbdbb9cec7a 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -732,6 +732,31 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir) const auto hideDiagsHandler = []{ ClangDiagnosticManager::clearTaskHubIssues(); }; setDiagnosticsHandlers(textMarkCreator, hideDiagsHandler); + static const auto symbolStringifier = [](SymbolKind kind, const QString &name, + const QString &detail) -> QString + { + switch (kind) { + case LanguageServerProtocol::SymbolKind::Constructor: + return name + detail; + case LanguageServerProtocol::SymbolKind::Method: + case LanguageServerProtocol::SymbolKind::Function: { + const int parenOffset = detail.indexOf(" ("); + if (parenOffset == -1) + return name; + return name + detail.mid(parenOffset + 1) + " -> " + detail.mid(0, parenOffset); + } + case LanguageServerProtocol::SymbolKind::Variable: + case LanguageServerProtocol::SymbolKind::Field: + case LanguageServerProtocol::SymbolKind::Constant: + if (detail.isEmpty()) + return name; + return name + " -> " + detail; + default: + return name; + } + }; + setSymbolStringifier(symbolStringifier); + hoverHandler()->setHelpItemProvider([this](const HoverRequest::Response &response, const DocumentUri &uri) { gatherHelpItemForTooltip(response, uri); diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp index db97618a1c3..90be3893dfc 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp @@ -171,6 +171,11 @@ std::unique_ptr ClangModelManagerSupport::creat return std::make_unique(); } +bool ClangModelManagerSupport::supportsOutline(const TextEditor::TextDocument *document) const +{ + return !clientForFile(document->filePath()); +} + CppTools::BaseEditorDocumentProcessor *ClangModelManagerSupport::createEditorDocumentProcessor( TextEditor::TextDocument *baseTextDocument) { @@ -344,7 +349,7 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr } ClangdClient *ClangModelManagerSupport::clientForProject( - const ProjectExplorer::Project *project) + const ProjectExplorer::Project *project) const { const QList clients = Utils::filtered( LanguageClientManager::clientsForProject(project), @@ -357,7 +362,7 @@ ClangdClient *ClangModelManagerSupport::clientForProject( return clients.empty() ? nullptr : qobject_cast(clients.first()); } -ClangdClient *ClangModelManagerSupport::clientForFile(const Utils::FilePath &file) +ClangdClient *ClangModelManagerSupport::clientForFile(const Utils::FilePath &file) const { return clientForProject(ProjectExplorer::SessionManager::projectForFile(file)); } diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.h b/src/plugins/clangcodemodel/clangmodelmanagersupport.h index 9122e7a7a45..97a0bfaf275 100644 --- a/src/plugins/clangcodemodel/clangmodelmanagersupport.h +++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.h @@ -72,6 +72,7 @@ public: CppTools::FollowSymbolInterface &followSymbolInterface() override; CppTools::RefactoringEngineInterface &refactoringEngineInterface() override; std::unique_ptr createOverviewModel() override; + bool supportsOutline(const TextEditor::TextDocument *document) const override; BackendCommunicator &communicator(); QString dummyUiHeaderOnDiskDirPath() const; @@ -79,8 +80,8 @@ public: ClangProjectSettings &projectSettings(ProjectExplorer::Project *project) const; - ClangdClient *clientForProject(const ProjectExplorer::Project *project); - ClangdClient *clientForFile(const Utils::FilePath &file); + ClangdClient *clientForProject(const ProjectExplorer::Project *project) const; + ClangdClient *clientForFile(const Utils::FilePath &file) const; static ClangModelManagerSupport *instance(); diff --git a/src/plugins/cppeditor/cppeditorwidget.cpp b/src/plugins/cppeditor/cppeditorwidget.cpp index 74ae3d5c843..ee242640bc0 100644 --- a/src/plugins/cppeditor/cppeditorwidget.cpp +++ b/src/plugins/cppeditor/cppeditorwidget.cpp @@ -118,7 +118,7 @@ public: QPointer m_modelManager; CppEditorDocument *m_cppEditorDocument; - CppEditorOutline *m_cppEditorOutline; + CppEditorOutline *m_cppEditorOutline = nullptr; QTimer m_updateFunctionDeclDefLinkTimer; SemanticInfo m_lastSemanticInfo; @@ -139,7 +139,6 @@ public: CppEditorWidgetPrivate::CppEditorWidgetPrivate(CppEditorWidget *q) : m_modelManager(CppModelManager::instance()) , m_cppEditorDocument(qobject_cast(q->textDocument())) - , m_cppEditorOutline(new CppEditorOutline(q)) , m_declDefLinkFinder(new FunctionDeclDefLinkFinder(q)) , m_localRenaming(q) , m_useSelectionsUpdater(q) @@ -160,8 +159,11 @@ void CppEditorWidget::finalizeInitialization() // clang-format off // function combo box sorting - connect(CppEditorPlugin::instance(), &CppEditorPlugin::outlineSortingChanged, - outline(), &CppEditorOutline::setSorted); + if (CppModelManager::supportsOutline(d->m_cppEditorDocument)) { + d->m_cppEditorOutline = new CppEditorOutline(this); + connect(CppEditorPlugin::instance(), &CppEditorPlugin::outlineSortingChanged, + outline(), &CppEditorOutline::setSorted); + } connect(d->m_cppEditorDocument, &CppEditorDocument::codeWarningsUpdated, this, &CppEditorWidget::onCodeWarningsUpdated); @@ -195,8 +197,10 @@ void CppEditorWidget::finalizeInitialization() }); connect(&d->m_localRenaming, &CppLocalRenaming::processKeyPressNormally, this, &CppEditorWidget::processKeyNormally); - connect(this, &QPlainTextEdit::cursorPositionChanged, - d->m_cppEditorOutline, &CppEditorOutline::updateIndex); + if (d->m_cppEditorOutline) { + connect(this, &QPlainTextEdit::cursorPositionChanged, + d->m_cppEditorOutline, &CppEditorOutline::updateIndex); + } connect(cppEditorDocument(), &CppEditorDocument::preprocessorSettingsChanged, this, [this](bool customSettings) { @@ -232,7 +236,8 @@ void CppEditorWidget::finalizeInitialization() }); // Toolbar: Outline/Overview combo box - insertExtraToolBarWidget(TextEditorWidget::Left, d->m_cppEditorOutline->widget()); + if (d->m_cppEditorOutline) + insertExtraToolBarWidget(TextEditorWidget::Left, d->m_cppEditorOutline->widget()); // clang-format on // Toolbar: '#' Button @@ -266,7 +271,8 @@ void CppEditorWidget::finalizeInitializationAfterDuplication(TextEditorWidget *o if (cppEditorWidget->isSemanticInfoValidExceptLocalUses()) updateSemanticInfo(cppEditorWidget->semanticInfo()); - d->m_cppEditorOutline->update(); + if (d->m_cppEditorOutline) + d->m_cppEditorOutline->update(); const Id selectionKind = CodeWarningsSelection; setExtraSelections(selectionKind, cppEditorWidget->extraSelections(selectionKind)); @@ -325,7 +331,8 @@ void CppEditorWidget::selectAll() void CppEditorWidget::onCppDocumentUpdated() { - d->m_cppEditorOutline->update(); + if (d->m_cppEditorOutline) + d->m_cppEditorOutline->update(); } void CppEditorWidget::onCodeWarningsUpdated(unsigned revision, diff --git a/src/plugins/cppeditor/cppoutline.cpp b/src/plugins/cppeditor/cppoutline.cpp index c071f61a437..758b11b1fb3 100644 --- a/src/plugins/cppeditor/cppoutline.cpp +++ b/src/plugins/cppeditor/cppoutline.cpp @@ -28,6 +28,7 @@ #include "cppeditor.h" #include +#include #include #include @@ -212,7 +213,10 @@ bool CppOutlineWidget::syncCursor() bool CppOutlineWidgetFactory::supportsEditor(Core::IEditor *editor) const { - return qobject_cast(editor); + const auto cppEditor = qobject_cast(editor); + if (!cppEditor) + return false; + return CppTools::CppModelManager::supportsOutline(cppEditor->textDocument()); } TextEditor::IOutlineWidget *CppOutlineWidgetFactory::createWidget(Core::IEditor *editor) diff --git a/src/plugins/cpptools/cppmodelmanager.cpp b/src/plugins/cpptools/cppmodelmanager.cpp index fbee2cebbd5..f92248b2228 100644 --- a/src/plugins/cpptools/cppmodelmanager.cpp +++ b/src/plugins/cpptools/cppmodelmanager.cpp @@ -1310,6 +1310,11 @@ bool CppModelManager::isCppEditor(Core::IEditor *editor) return editor->context().contains(ProjectExplorer::Constants::CXX_LANGUAGE_ID); } +bool CppModelManager::supportsOutline(const TextEditor::TextDocument *document) +{ + return instance()->d->m_activeModelManagerSupport->supportsOutline(document); +} + bool CppModelManager::isClangCodeModelActive() const { return d->m_activeModelManagerSupport != d->m_builtinModelManagerSupport; diff --git a/src/plugins/cpptools/cppmodelmanager.h b/src/plugins/cpptools/cppmodelmanager.h index 98991f9c6ce..402f37bee6d 100644 --- a/src/plugins/cpptools/cppmodelmanager.h +++ b/src/plugins/cpptools/cppmodelmanager.h @@ -142,6 +142,7 @@ public: void emitAbstractEditorSupportRemoved(const QString &filePath); static bool isCppEditor(Core::IEditor *editor); + static bool supportsOutline(const TextEditor::TextDocument *document); bool isClangCodeModelActive() const; QSet abstractEditorSupports() const; diff --git a/src/plugins/cpptools/cppmodelmanagersupport.h b/src/plugins/cpptools/cppmodelmanagersupport.h index ccf7ee83de6..13b6108fab2 100644 --- a/src/plugins/cpptools/cppmodelmanagersupport.h +++ b/src/plugins/cpptools/cppmodelmanagersupport.h @@ -61,6 +61,7 @@ public: virtual FollowSymbolInterface &followSymbolInterface() = 0; virtual RefactoringEngineInterface &refactoringEngineInterface() = 0; virtual std::unique_ptr createOverviewModel() = 0; + virtual bool supportsOutline(const TextEditor::TextDocument *) const { return true; } }; class CPPTOOLS_EXPORT ModelManagerSupportProvider diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index 2930146cdc5..5f40a3cc852 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -1003,6 +1003,16 @@ void Client::setDiagnosticsHandlers(const TextMarkCreator &textMarkCreator, m_diagnosticManager.setDiagnosticsHandlers(textMarkCreator, hideHandler); } +void Client::setSymbolStringifier(const LanguageServerProtocol::SymbolStringifier &stringifier) +{ + m_symbolStringifier = stringifier; +} + +SymbolStringifier Client::symbolStringifier() const +{ + return m_symbolStringifier; +} + void Client::start() { if (m_clientInterface->start()) diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h index 6fd0f3f2934..f45e79bb995 100644 --- a/src/plugins/languageclient/client.h +++ b/src/plugins/languageclient/client.h @@ -174,6 +174,8 @@ public: const LanguageServerProtocol::Diagnostic &diag) const; void setDiagnosticsHandlers(const TextMarkCreator &textMarkCreator, const HideDiagnosticsHandler &hideHandler); + void setSymbolStringifier(const LanguageServerProtocol::SymbolStringifier &stringifier); + LanguageServerProtocol::SymbolStringifier symbolStringifier() const; // logging void log(const QString &message) const; @@ -270,6 +272,7 @@ private: SemanticTokenSupport m_tokentSupport; QString m_serverName; QString m_serverVersion; + LanguageServerProtocol::SymbolStringifier m_symbolStringifier; bool m_locatorsEnabled = true; }; diff --git a/src/plugins/languageclient/languageclientoutline.cpp b/src/plugins/languageclient/languageclientoutline.cpp index c83a35411f8..4257efb1ff8 100644 --- a/src/plugins/languageclient/languageclientoutline.cpp +++ b/src/plugins/languageclient/languageclientoutline.cpp @@ -55,14 +55,15 @@ public: , m_type(info.kind()) { } - LanguageClientOutlineItem(const DocumentSymbol &info) + LanguageClientOutlineItem(const DocumentSymbol &info, const SymbolStringifier &stringifier) : m_name(info.name()) , m_detail(info.detail().value_or(QString())) , m_range(info.range()) + , m_symbolStringifier(stringifier) , m_type(info.kind()) { for (const DocumentSymbol &child : info.children().value_or(QList())) - appendChild(new LanguageClientOutlineItem(child)); + appendChild(new LanguageClientOutlineItem(child, stringifier)); } // TreeItem interface @@ -72,7 +73,9 @@ public: case Qt::DecorationRole: return symbolIcon(m_type); case Qt::DisplayRole: - return m_name; + return m_symbolStringifier + ? m_symbolStringifier(static_cast(m_type), m_name, m_detail) + : m_name; default: return Utils::TreeItem::data(column, role); } @@ -85,6 +88,7 @@ private: QString m_name; QString m_detail; Range m_range; + SymbolStringifier m_symbolStringifier; int m_type = -1; }; @@ -102,8 +106,16 @@ public: { clear(); for (const DocumentSymbol &symbol : info) - rootItem()->appendChild(new LanguageClientOutlineItem(symbol)); + rootItem()->appendChild(new LanguageClientOutlineItem(symbol, m_symbolStringifier)); } + + void setSymbolStringifier(const SymbolStringifier &stringifier) + { + m_symbolStringifier = stringifier; + } + +private: + SymbolStringifier m_symbolStringifier; }; class LanguageClientOutlineWidget : public TextEditor::IOutlineWidget @@ -153,6 +165,7 @@ LanguageClientOutlineWidget::LanguageClientOutlineWidget(Client *client, layout->setSpacing(0); layout->addWidget(Core::ItemViewFind::createSearchableWrapper(&m_view)); setLayout(layout); + m_model.setSymbolStringifier(m_client->symbolStringifier()); m_view.setModel(&m_model); m_view.setHeaderHidden(true); m_view.setExpandsOnDoubleClick(false); @@ -295,6 +308,7 @@ OutlineComboBox::OutlineComboBox(Client *client, TextEditor::BaseTextEditor *edi , m_editorWidget(editor->editorWidget()) , m_uri(DocumentUri::fromFilePath(editor->document()->filePath())) { + m_model.setSymbolStringifier(client->symbolStringifier()); setModel(&m_model); setMinimumContentsLength(13); QSizePolicy policy = sizePolicy(); From 6f5d78a297a8b0f2f0ead9f7ca9d174887d35d92 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 29 Jun 2021 19:26:12 +0200 Subject: [PATCH 096/149] QtSupport: Use FilePath for several BaseQtVersion members uicCommand(), designerCommand(), linguistCommand(), qscxmlcCommand(), qmlsceneCommand(), qmlplugindumpCommand(). No change in functionality intented. Change-Id: I43121de559019f96c2c1ff3b423974dddfc37124 Reviewed-by: Christian Stenger --- .../qmakeprojectmanager/externaleditors.cpp | 5 +- src/plugins/qmldesigner/generateresource.cpp | 26 +++--- src/plugins/qmljstools/qmljsmodelmanager.cpp | 2 +- src/plugins/qtsupport/baseqtversion.cpp | 82 +++++++++---------- src/plugins/qtsupport/baseqtversion.h | 12 +-- src/plugins/qtsupport/qscxmlcgenerator.cpp | 2 +- src/plugins/qtsupport/uicgenerator.cpp | 2 +- 7 files changed, 65 insertions(+), 66 deletions(-) diff --git a/src/plugins/qmakeprojectmanager/externaleditors.cpp b/src/plugins/qmakeprojectmanager/externaleditors.cpp index fd7f6bc149b..453ee727863 100644 --- a/src/plugins/qmakeprojectmanager/externaleditors.cpp +++ b/src/plugins/qmakeprojectmanager/externaleditors.cpp @@ -43,6 +43,7 @@ #include using namespace ProjectExplorer; +using namespace Utils; enum { debug = 0 }; @@ -65,14 +66,14 @@ static inline QString msgAppNotFound(const QString &id) static QString linguistBinary(const QtSupport::BaseQtVersion *qtVersion) { if (qtVersion) - return qtVersion->linguistCommand(); + return qtVersion->linguistCommand().toString(); return QLatin1String(Utils::HostOsInfo::isMacHost() ? "Linguist" : "linguist"); } static QString designerBinary(const QtSupport::BaseQtVersion *qtVersion) { if (qtVersion) - return qtVersion->designerCommand(); + return qtVersion->designerCommand().toString(); return QLatin1String(Utils::HostOsInfo::isMacHost() ? "Designer" : "designer"); } diff --git a/src/plugins/qmldesigner/generateresource.cpp b/src/plugins/qmldesigner/generateresource.cpp index 182ede79dc1..fdef3228eaa 100644 --- a/src/plugins/qmldesigner/generateresource.cpp +++ b/src/plugins/qmldesigner/generateresource.cpp @@ -61,6 +61,8 @@ #include #include +using namespace Utils; + namespace QmlDesigner { QTableWidget* GenerateResource::createFilesTable(const QList &fileNames) @@ -183,7 +185,7 @@ void GenerateResource::generateMenuEntry() QtSupport::BaseQtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion( currentProject->activeTarget()->kit()); - QString rccBinary = qtVersion->rccCommand(); + FilePath rccBinary = qtVersion->rccCommand(); Utils::QtcProcess rccProcess; rccProcess.setWorkingDirectory(projectPath); @@ -207,7 +209,7 @@ void GenerateResource::generateMenuEntry() Core::MessageManager::writeDisrupting( QCoreApplication::translate("QmlDesigner::GenerateResource", "A timeout occurred running \"%1\"") - .arg(rccBinary + " " + arguments.join(" "))); + .arg(rccProcess.commandLine().toUserOutput())); return; } @@ -220,14 +222,14 @@ void GenerateResource::generateMenuEntry() if (rccProcess.exitStatus() != QProcess::NormalExit) { Core::MessageManager::writeDisrupting( QCoreApplication::translate("QmlDesigner::GenerateResource", "\"%1\" crashed.") - .arg(rccBinary + " " + arguments.join(" "))); + .arg(rccProcess.commandLine().toUserOutput())); return; } if (rccProcess.exitCode() != 0) { Core::MessageManager::writeDisrupting( QCoreApplication::translate("QmlDesigner::GenerateResource", "\"%1\" failed (exit code %2).") - .arg(rccBinary + " " + arguments.join(" ")) + .arg(rccProcess.commandLine().toUserOutput()) .arg(rccProcess.exitCode())); return; } @@ -360,9 +362,9 @@ void GenerateResource::generateMenuEntry() QtSupport::BaseQtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion( currentProject->activeTarget()->kit()); - QString rccBinary = qtVersion->rccCommand(); + FilePath rccBinary = qtVersion->rccCommand(); - Utils::QtcProcess rccProcess; + QtcProcess rccProcess; rccProcess.setWorkingDirectory(projectPath); QXmlStreamReader reader; @@ -392,7 +394,7 @@ void GenerateResource::generateMenuEntry() Core::MessageManager::writeDisrupting( QCoreApplication::translate("QmlDesigner::GenerateResource", "A timeout occurred running \"%1\"") - .arg(rccBinary + " " + arguments.join(" "))); + .arg(rccProcess.commandLine().toUserOutput())); return; } if (!stdOut.trimmed().isEmpty()) @@ -404,14 +406,14 @@ void GenerateResource::generateMenuEntry() if (rccProcess.exitStatus() != QProcess::NormalExit) { Core::MessageManager::writeDisrupting( QCoreApplication::translate("QmlDesigner::GenerateResource", "\"%1\" crashed.") - .arg(rccBinary + " " + arguments.join(" "))); + .arg(rccProcess.commandLine().toUserOutput())); return; } if (rccProcess.exitCode() != 0) { Core::MessageManager::writeDisrupting( QCoreApplication::translate("QmlDesigner::GenerateResource", "\"%1\" failed (exit code %2).") - .arg(rccBinary + " " + arguments.join(" ")) + .arg(rccProcess.commandLine().toUserOutput()) .arg(rccProcess.exitCode())); return; } @@ -526,7 +528,7 @@ void GenerateResource::generateMenuEntry() Core::MessageManager::writeDisrupting( QCoreApplication::translate("QmlDesigner::GenerateResource", "A timeout occurred running \"%1\"") - .arg(rccBinary + " " + arguments.join(" "))); + .arg(rccProcess.commandLine().toUserOutput())); return; } @@ -539,14 +541,14 @@ void GenerateResource::generateMenuEntry() if (rccProcess.exitStatus() != QProcess::NormalExit) { Core::MessageManager::writeDisrupting( QCoreApplication::translate("QmlDesigner::GenerateResource", "\"%1\" crashed.") - .arg(rccBinary + " " + arguments.join(" "))); + .arg(rccProcess.commandLine().toUserOutput())); return; } if (rccProcess.exitCode() != 0) { Core::MessageManager::writeDisrupting( QCoreApplication::translate("QmlDesigner::GenerateResource", "\"%1\" failed (exit code %2).") - .arg(rccBinary + " " + arguments.join(" ")) + .arg(rccProcess.commandLine().toUserOutput()) .arg(rccProcess.exitCode())); return; } diff --git a/src/plugins/qmljstools/qmljsmodelmanager.cpp b/src/plugins/qmljstools/qmljsmodelmanager.cpp index 9b5344fe6e6..5eb23083685 100644 --- a/src/plugins/qmljstools/qmljsmodelmanager.cpp +++ b/src/plugins/qmljstools/qmljsmodelmanager.cpp @@ -156,7 +156,7 @@ ModelManagerInterface::ProjectInfo ModelManager::defaultProjectInfoForProject( projectInfo.qmlDumpPath.clear(); const QtSupport::BaseQtVersion *version = QtSupport::QtKitAspect::qtVersion(activeKit); if (version && projectInfo.tryQmlDump) { - projectInfo.qmlDumpPath = version->qmlplugindumpCommand(); + projectInfo.qmlDumpPath = version->qmlplugindumpCommand().toString(); projectInfo.qmlDumpHasRelocatableFlag = version->hasQmlDumpWithRelocatableFlag(); } diff --git a/src/plugins/qtsupport/baseqtversion.cpp b/src/plugins/qtsupport/baseqtversion.cpp index 36b5cf52dd4..a1c5331c270 100644 --- a/src/plugins/qtsupport/baseqtversion.cpp +++ b/src/plugins/qtsupport/baseqtversion.cpp @@ -184,7 +184,7 @@ public: void updateVersionInfo(); - QString findHostBinary(HostBinaries binary) const; + FilePath findHostBinary(HostBinaries binary) const; void updateMkspec(); QHash versionInfo(); static bool queryQMakeVariables(const FilePath &binary, @@ -235,13 +235,13 @@ public: FilePath m_qmakeCommand; - QString m_rccCommand; - QString m_uicCommand; - QString m_designerCommand; - QString m_linguistCommand; - QString m_qscxmlcCommand; + FilePath m_rccCommand; + FilePath m_uicCommand; + FilePath m_designerCommand; + FilePath m_linguistCommand; + FilePath m_qscxmlcCommand; FilePath m_qmlsceneCommand; - QString m_qmlplugindumpCommand; + FilePath m_qmlplugindumpCommand; MacroExpanderWrapper m_expander; }; @@ -991,30 +991,30 @@ FilePath BaseQtVersion::qtPackageSourcePath() const return d->m_data.qtSources; } -QString BaseQtVersion::designerCommand() const +FilePath BaseQtVersion::designerCommand() const { if (!isValid()) - return QString(); - if (d->m_designerCommand.isNull()) + return {}; + if (d->m_designerCommand.isEmpty()) d->m_designerCommand = d->findHostBinary(Designer); return d->m_designerCommand; } -QString BaseQtVersion::linguistCommand() const +FilePath BaseQtVersion::linguistCommand() const { if (!isValid()) - return QString(); - if (d->m_linguistCommand.isNull()) + return {}; + if (d->m_linguistCommand.isEmpty()) d->m_linguistCommand = d->findHostBinary(Linguist); return d->m_linguistCommand; } -QString BaseQtVersion::qscxmlcCommand() const +FilePath BaseQtVersion::qscxmlcCommand() const { if (!isValid()) - return QString(); + return {}; - if (d->m_qscxmlcCommand.isNull()) + if (d->m_qscxmlcCommand.isEmpty()) d->m_qscxmlcCommand = d->findHostBinary(QScxmlc); return d->m_qscxmlcCommand; } @@ -1033,40 +1033,38 @@ FilePath BaseQtVersion::qmlsceneCommand() const return d->m_qmlsceneCommand; } -QString BaseQtVersion::qmlplugindumpCommand() const +FilePath BaseQtVersion::qmlplugindumpCommand() const { if (!isValid()) - return QString(); + return {}; - if (!d->m_qmlplugindumpCommand.isNull()) + if (!d->m_qmlplugindumpCommand.isEmpty()) return d->m_qmlplugindumpCommand; - const QString path - = binPath().pathAppended(HostOsInfo::withExecutableSuffix("qmlplugindump")).toString(); - - d->m_qmlplugindumpCommand = QFileInfo(path).isFile() ? path : QString(); + const FilePath path = binPath() / HostOsInfo::withExecutableSuffix("qmlplugindump"); + d->m_qmlplugindumpCommand = path.isExecutableFile() ? path : FilePath(); return d->m_qmlplugindumpCommand; } -QString BaseQtVersionPrivate::findHostBinary(HostBinaries binary) const +FilePath BaseQtVersionPrivate::findHostBinary(HostBinaries binary) const { - QString baseDir; + FilePath baseDir; if (q->qtVersion() < QtVersionNumber(5, 0, 0)) { - baseDir = q->binPath().toString(); + baseDir = q->binPath(); } else { switch (binary) { case Designer: case Linguist: case QScxmlc: - baseDir = q->hostBinPath().toString(); + baseDir = q->hostBinPath(); break; case Rcc: case Uic: if (q->qtVersion() >= QtVersionNumber(6, 1)) - baseDir = q->hostLibexecPath().toString(); + baseDir = q->hostLibexecPath(); else - baseDir = q->hostBinPath().toString(); + baseDir = q->hostBinPath(); break; default: // Can't happen @@ -1075,9 +1073,7 @@ QString BaseQtVersionPrivate::findHostBinary(HostBinaries binary) const } if (baseDir.isEmpty()) - return QString(); - if (!baseDir.endsWith('/')) - baseDir += '/'; + return {}; QStringList possibleCommands; switch (binary) { @@ -1115,29 +1111,29 @@ QString BaseQtVersionPrivate::findHostBinary(HostBinaries binary) const default: Q_ASSERT(false); } - foreach (const QString &possibleCommand, possibleCommands) { - const QString fullPath = baseDir + possibleCommand; - if (QFileInfo(fullPath).isFile()) - return QDir::cleanPath(fullPath); + for (const QString &possibleCommand : qAsConst(possibleCommands)) { + const FilePath fullPath = baseDir / possibleCommand; + if (fullPath.isExecutableFile()) + return fullPath; } - return QString(); + return {}; } -QString BaseQtVersion::rccCommand() const +FilePath BaseQtVersion::rccCommand() const { if (!isValid()) - return QString(); - if (!d->m_rccCommand.isNull()) + return {}; + if (!d->m_rccCommand.isEmpty()) return d->m_rccCommand; d->m_rccCommand = d->findHostBinary(Rcc); return d->m_rccCommand; } -QString BaseQtVersion::uicCommand() const +FilePath BaseQtVersion::uicCommand() const { if (!isValid()) - return QString(); - if (!d->m_uicCommand.isNull()) + return {}; + if (!d->m_uicCommand.isEmpty()) return d->m_uicCommand; d->m_uicCommand = d->findHostBinary(Uic); return d->m_uicCommand; diff --git a/src/plugins/qtsupport/baseqtversion.h b/src/plugins/qtsupport/baseqtversion.h index c4592c20ab2..e58523f2ee5 100644 --- a/src/plugins/qtsupport/baseqtversion.h +++ b/src/plugins/qtsupport/baseqtversion.h @@ -131,14 +131,14 @@ public: bool isInSourceDirectory(const Utils::FilePath &filePath); bool isSubProject(const Utils::FilePath &filePath) const; - QString rccCommand() const; + Utils::FilePath rccCommand() const; // used by UiCodeModelSupport - QString uicCommand() const; - QString designerCommand() const; - QString linguistCommand() const; - QString qscxmlcCommand() const; + Utils::FilePath uicCommand() const; + Utils::FilePath designerCommand() const; + Utils::FilePath linguistCommand() const; + Utils::FilePath qscxmlcCommand() const; Utils::FilePath qmlsceneCommand() const; - QString qmlplugindumpCommand() const; + Utils::FilePath qmlplugindumpCommand() const; QString qtVersionString() const; QtVersionNumber qtVersion() const; diff --git a/src/plugins/qtsupport/qscxmlcgenerator.cpp b/src/plugins/qtsupport/qscxmlcgenerator.cpp index 2c7194f254a..3c2f0ff7cf3 100644 --- a/src/plugins/qtsupport/qscxmlcgenerator.cpp +++ b/src/plugins/qtsupport/qscxmlcgenerator.cpp @@ -84,7 +84,7 @@ Utils::FilePath QScxmlcGenerator::command() const if (!version) return Utils::FilePath(); - return Utils::FilePath::fromString(version->qscxmlcCommand()); + return version->qscxmlcCommand(); } QStringList QScxmlcGenerator::arguments() const diff --git a/src/plugins/qtsupport/uicgenerator.cpp b/src/plugins/qtsupport/uicgenerator.cpp index c0979ab13d5..454dcad35fb 100644 --- a/src/plugins/qtsupport/uicgenerator.cpp +++ b/src/plugins/qtsupport/uicgenerator.cpp @@ -62,7 +62,7 @@ Utils::FilePath UicGenerator::command() const if (!version) return Utils::FilePath(); - return Utils::FilePath::fromString(version->uicCommand()); + return version->uicCommand(); } QStringList UicGenerator::arguments() const From 79ca424782dca9e57352eecc3c9feac7399c4ac7 Mon Sep 17 00:00:00 2001 From: Leena Miettinen Date: Tue, 29 Jun 2021 15:33:01 +0200 Subject: [PATCH 097/149] Doc: Describe code indentation options - Describe the related menu options in Edit > Advanced - Describe showing (context-sensitive) right margin in editor Task-number: QTCREATORBUG-25642 Change-Id: I6377488d2417faedb58ae7dce65158b7d62c3f9d Reviewed-by: Eike Ziller --- .../images/qtcreator-editor-settings.png | Bin 23557 -> 25503 bytes .../qtcreator-options-text-editor-display.png | Bin 0 -> 14533 bytes .../src/editors/creator-code-indentation.qdoc | 38 +++++++++++++++++- .../creator-semantic-highlighting.qdoc | 4 ++ 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 doc/qtcreator/images/qtcreator-options-text-editor-display.png diff --git a/doc/qtcreator/images/qtcreator-editor-settings.png b/doc/qtcreator/images/qtcreator-editor-settings.png index 0c5265a6a2f78dc391c4cc92c66a3071c9ffc6f0..2ad6d12e21756c7751461394fb368aca4d359db4 100644 GIT binary patch literal 25503 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π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 literal 23557 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

yF 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 \uicontrol Advanced menu + or use \l{Keyboard Shortcuts}{keyboard shortcuts}: + + \list + \li To automatically indent the highlighted text, select + \uicontrol {Auto-indent Selection} or press \key {Ctrl+I}. + \li To automatically format the highlighted text, select + \uicontrol {Auto-format Selection} or press \key {Ctrl+;}. + \li To adjust the wrapping of the selected paragraph, select + \uicontrol {Rewrap Paragraph} or press \key {Ctrl+E} + followed by \key R. + \li To toggle text wrapping, select \uicontrol {Enable Text Wrapping} + or press \key {Ctrl+E} followed by \key {Ctrl+W}. + \li To visualize whitespace in the editor, select + \uicontrol {Visualize Whitespace} or press \key {Ctrl+E} + followed by \key {Ctrl+V}. + \li To clear all whitespace characters from the currently open file, + select \uicontrol {Clean Whitespace}. + \endlist + + \section1 Specifying Indentation Settings + You can also specify indentation separately for each project. You can specify several sets of code style settings and easily switch between them. In addition, you can import and export code style settings. @@ -61,7 +84,20 @@ \image qtcreator-options-text-editor-behavior.png "Text Editor Behavior options" + To visualize whitespace in the editor, select \uicontrol Tools > + \uicontrol Options > \uicontrol {Text Editor} > \uicontrol Display > + \uicontrol {Visualize whitespace}. + + \image qtcreator-options-text-editor-display.png "Text Editor Display options" + + To help you keep line length at a particular number of characters, set the + number of characters in the \uicontrol {Display right margin at column} + field. To use a context-specific margin when available, select the + \uicontrol {Use context-specific margin} check box. \if defined(qtcreator) + For example, the margin could be set by the \c ColumnLimit option of the + \l{Specifying Code Style Settings}{Clang Format plugin}. + \section1 Indenting C++ Files To specify indentation settings for the C++ editor: diff --git a/doc/qtcreator/src/editors/creator-semantic-highlighting.qdoc b/doc/qtcreator/src/editors/creator-semantic-highlighting.qdoc index 728936dec27..0545c133138 100644 --- a/doc/qtcreator/src/editors/creator-semantic-highlighting.qdoc +++ b/doc/qtcreator/src/editors/creator-semantic-highlighting.qdoc @@ -101,6 +101,8 @@ \uicontrol {Text Editor} > \uicontrol Display > \uicontrol {Display folding markers}. This option is enabled by default. + \image qtcreator-options-text-editor-display.png "Text Editor Display options" + When the cursor is on a brace, the matching brace is animated by default. To turn off the animation and just highlight the block and the braces, select \uicontrol Tools > \uicontrol Options > \uicontrol {Text Editor} > @@ -117,4 +119,6 @@ \key {Ctrl+Alt+Shift+U}. To enable smart block selection, select \uicontrol Tools > \uicontrol Options > \uicontrol {Text Editor} > \uicontrol Behavior > \uicontrol {Enable smart selection changing}. + + \image qtcreator-options-text-editor-behavior.png "Text Editor Behavior options" */ From 8ff74cc46c9996e0b7995821180bacbacfd94104 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Thu, 1 Jul 2021 08:00:51 +0200 Subject: [PATCH 098/149] LanguageClient: prevent sending symbol request to unreachable server Change-Id: Ie7b7ee2a89b3e7e7d55b2ef1714b574c87a2897e Reviewed-by: Christian Kandeler --- src/plugins/languageclient/documentsymbolcache.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plugins/languageclient/documentsymbolcache.cpp b/src/plugins/languageclient/documentsymbolcache.cpp index 218758b66f7..c635e10d198 100644 --- a/src/plugins/languageclient/documentsymbolcache.cpp +++ b/src/plugins/languageclient/documentsymbolcache.cpp @@ -62,6 +62,10 @@ void DocumentSymbolCache::requestSymbols(const DocumentUri &uri) void DocumentSymbolCache::requestSymbolsImpl() { + if (!m_client->reachable()) { + m_compressionTimer.start(200); + return; + } for (const DocumentUri &uri : qAsConst(m_compressedUris)) { auto entry = m_cache.find(uri); if (entry != m_cache.end()) { From 4aaae4939a7e10c64dc743c0ab6aa3828f54f289 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Thu, 1 Jul 2021 14:40:22 +0200 Subject: [PATCH 099/149] Simplify the assert condition Change-Id: I59d6fe1bcfeabc33b24efc68c6e691d4e549faf7 Reviewed-by: Christian Stenger --- src/plugins/autotest/testcodeparser.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/autotest/testcodeparser.cpp b/src/plugins/autotest/testcodeparser.cpp index f2fc52778de..8b6ee0c6780 100644 --- a/src/plugins/autotest/testcodeparser.cpp +++ b/src/plugins/autotest/testcodeparser.cpp @@ -439,9 +439,9 @@ void TestCodeParser::onFinished() void TestCodeParser::onPartialParsingFinished() { - QTC_ASSERT(m_fullUpdatePostponed != m_partialUpdatePostponed - || ((m_fullUpdatePostponed || m_partialUpdatePostponed) == false), - m_partialUpdatePostponed = false;m_postponedFiles.clear();); + // fail only when both are true + QTC_ASSERT(!m_fullUpdatePostponed || !m_partialUpdatePostponed, + m_partialUpdatePostponed = false; m_postponedFiles.clear()); if (m_fullUpdatePostponed) { m_fullUpdatePostponed = false; qCDebug(LOG) << "calling updateTestTree (onPartialParsingFinished)"; From b1171f83c1d1ad0dfa41cb7aff9e64a137a93021 Mon Sep 17 00:00:00 2001 From: Eike Ziller Date: Thu, 1 Jul 2021 13:12:17 +0200 Subject: [PATCH 100/149] CMake build: Enable BUILD_WITH_PCH for builds with Qt 6 The issues have been fixed in the meantime Fixes: QTCREATORBUG-25950 Change-Id: I22cf765acd3008a5b216a9e6afe5854589c7b515 Reviewed-by: Mahmoud Badri Reviewed-by: Alessandro Portale --- CMakeLists.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e7aa764b2e..3a25deece0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,20 +52,17 @@ find_package(Qt5 ) if (Qt5_VERSION VERSION_LESS 6.0.0) install(TARGETS Qt6Core5Compat EXPORT QtCreator) - set(BUILD_WITH_PCH_DEFAULT ON) # Specify standards conformance mode to MSVC 2017 and later # Qt6 has the values as part of the Qt6::Platform target interface if (MSVC AND MSVC_VERSION GREATER_EQUAL 1910) add_compile_options(/permissive- /Zc:__cplusplus) endif() -else() - set(BUILD_WITH_PCH_DEFAULT OFF) endif() find_package(Qt5 COMPONENTS LinguistTools QUIET) find_package(Qt5 COMPONENTS Designer DesignerComponents Help SerialPort Svg Tools QUIET) -option(BUILD_WITH_PCH "Build with precompiled headers" ${BUILD_WITH_PCH_DEFAULT}) +option(BUILD_WITH_PCH "Build with precompiled headers" ON) find_package(Threads) find_package(Clang QUIET) From 111f1465c8a3734a2e74e0db12d1d80307e3d1ab Mon Sep 17 00:00:00 2001 From: Christian Stenger Date: Thu, 1 Jul 2021 15:58:18 +0200 Subject: [PATCH 101/149] Wizards: Fix QtQuick Stack wizard Amends 1e80ed9b786070962c16ad0417a3cedf5673e1f1. Change-Id: I2a30c38ff84a9793452a66265b584d47938444a7 Reviewed-by: Eike Ziller --- .../wizards/projects/qtquickapplication/stack/wizard.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/qtcreator/templates/wizards/projects/qtquickapplication/stack/wizard.json b/share/qtcreator/templates/wizards/projects/qtquickapplication/stack/wizard.json index 8752c9062c0..bf9cb86daa0 100644 --- a/share/qtcreator/templates/wizards/projects/qtquickapplication/stack/wizard.json +++ b/share/qtcreator/templates/wizards/projects/qtquickapplication/stack/wizard.json @@ -132,7 +132,7 @@ "QtQuickControlsVersion": "2.5", "QtQuickVirtualKeyboardImport": "QtQuick.VirtualKeyboard 2.4" } - }, + } ] } }, From 18f24ffd685ee201f60f67a223548744ec3a38b0 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Thu, 1 Jul 2021 16:24:02 +0300 Subject: [PATCH 102/149] QmlPuppet: Fix multiselection fit object in 3D editor Multiselection is now properly fit to the view when fit object is used. Fixes: QDS-4608 Change-Id: Ia80133fc861bd177b9102423ebef37b592c74758 Reviewed-by: Mahmoud Badri --- .../mockfiles/qt5/EditCameraController.qml | 13 ++- .../qmlpuppet/mockfiles/qt5/EditView3D.qml | 16 ++- .../mockfiles/qt6/EditCameraController.qml | 13 ++- .../qmlpuppet/mockfiles/qt6/EditView3D.qml | 16 ++- .../qml2puppet/editor3d/generalhelper.cpp | 110 ++++++++++++------ .../qml2puppet/editor3d/generalhelper.h | 8 +- 6 files changed, 121 insertions(+), 55 deletions(-) diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditCameraController.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditCameraController.qml index 2b23117aa28..55e310f053d 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditCameraController.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditCameraController.qml @@ -88,14 +88,21 @@ Item { } - function focusObject(targetObject, rotation, updateZoom, closeUp) + function focusObject(targetNodes, rotation, updateZoom, closeUp) { if (!camera) return; + // targetNodes could be a list of nodes or a single node + var nodes = []; + if (targetNodes instanceof Node) + nodes.push(targetNodes); + else + nodes = targetNodes + camera.eulerRotation = rotation; - var newLookAtAndZoom = _generalHelper.focusObjectToCamera( - camera, _defaultCameraLookAtDistance, targetObject, view3d, _zoomFactor, + var newLookAtAndZoom = _generalHelper.focusNodesToCamera( + camera, _defaultCameraLookAtDistance, nodes, view3d, _zoomFactor, updateZoom, closeUp); _lookAtPoint = newLookAtAndZoom.toVector3d(); _zoomFactor = newLookAtAndZoom.w; diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml index 318673e0c9e..cda0808b734 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt5/EditView3D.qml @@ -176,9 +176,16 @@ Item { function fitToView() { if (editView) { - var targetNode = selectionBoxes.length > 0 - ? selectionBoxes[0].model : null; - cameraControl.focusObject(targetNode, editView.camera.eulerRotation, true, false); + var boxModels = []; + if (selectedNodes.length > 1) { + for (var i = 0; i < selectedNodes.length; ++i) { + if (selectionBoxes.length > i) + boxModels.push(selectionBoxes[i].model) + } + } else if (selectedNodes.length > 0 && selectionBoxes.length > 0) { + boxModels.push(selectionBoxes[0].model); + } + cameraControl.focusObject(boxModels, editView.camera.eulerRotation, true, false); } } @@ -857,7 +864,8 @@ Item { width: 100 height: width editCameraCtrl: cameraControl - selectedNode : viewRoot.selectedNodes.length ? selectionBoxes[0].model : null + selectedNode: viewRoot.selectedNodes.length === 1 ? viewRoot.selectionBoxes[0].model + : viewRoot.selectedNode } Text { diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditCameraController.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditCameraController.qml index 223f8ab74f0..13743094219 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditCameraController.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditCameraController.qml @@ -88,14 +88,21 @@ Item { } - function focusObject(targetObject, rotation, updateZoom, closeUp) + function focusObject(targetNodes, rotation, updateZoom, closeUp) { if (!camera) return; + // targetNodes could be a list of nodes or a single node + var nodes = []; + if (targetNodes instanceof Node) + nodes.push(targetNodes); + else + nodes = targetNodes + camera.eulerRotation = rotation; - var newLookAtAndZoom = _generalHelper.focusObjectToCamera( - camera, _defaultCameraLookAtDistance, targetObject, view3d, _zoomFactor, + var newLookAtAndZoom = _generalHelper.focusNodesToCamera( + camera, _defaultCameraLookAtDistance, nodes, view3d, _zoomFactor, updateZoom, closeUp); _lookAtPoint = newLookAtAndZoom.toVector3d(); _zoomFactor = newLookAtAndZoom.w; diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml index 18f444baa50..83e37050402 100644 --- a/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml +++ b/share/qtcreator/qml/qmlpuppet/mockfiles/qt6/EditView3D.qml @@ -176,9 +176,16 @@ Item { function fitToView() { if (editView) { - var targetNode = selectionBoxes.length > 0 - ? selectionBoxes[0].model : null; - cameraControl.focusObject(targetNode, editView.camera.eulerRotation, true, false); + var boxModels = []; + if (selectedNodes.length > 1) { + for (var i = 0; i < selectedNodes.length; ++i) { + if (selectionBoxes.length > i) + boxModels.push(selectionBoxes[i].model) + } + } else if (selectedNodes.length > 0 && selectionBoxes.length > 0) { + boxModels.push(selectionBoxes[0].model); + } + cameraControl.focusObject(boxModels, editView.camera.eulerRotation, true, false); } } @@ -857,7 +864,8 @@ Item { width: 100 height: width editCameraCtrl: cameraControl - selectedNode : viewRoot.selectedNodes.length ? selectionBoxes[0].model : null + selectedNode: viewRoot.selectedNodes.length === 1 ? viewRoot.selectionBoxes[0].model + : viewRoot.selectedNode } Text { diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp index d76d335fd7e..39ca48b8e31 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.cpp @@ -46,6 +46,8 @@ #include #include +#include + namespace QmlDesigner { namespace Internal { @@ -151,56 +153,90 @@ float GeneralHelper::zoomCamera(QQuick3DCamera *camera, float distance, float de } // Return value contains new lookAt point (xyz) and zoom factor (w) -QVector4D GeneralHelper::focusObjectToCamera(QQuick3DCamera *camera, float defaultLookAtDistance, - QQuick3DNode *targetObject, QQuick3DViewport *viewPort, - float oldZoom, bool updateZoom, bool closeUp) +QVector4D GeneralHelper::focusNodesToCamera(QQuick3DCamera *camera, float defaultLookAtDistance, + const QVariant &nodes, QQuick3DViewport *viewPort, + float oldZoom, bool updateZoom, bool closeUp) { if (!camera) return QVector4D(0.f, 0.f, 0.f, 1.f); - QVector3D lookAt = targetObject ? targetObject->scenePosition() : QVector3D(); + QList nodeList; + const QVariantList varNodes = nodes.value(); + for (const auto &varNode : varNodes) { + auto model = varNode.value(); + if (model) + nodeList.append(model); + } - // Get object bounds + // Get bounds + QVector3D totalMinBound; + QVector3D totalMaxBound; const qreal defaultExtent = 200.; - qreal maxExtent = defaultExtent; - if (auto modelNode = qobject_cast(targetObject)) { - auto targetPriv = QQuick3DObjectPrivate::get(targetObject); - if (auto renderModel = static_cast(targetPriv->spatialNode)) { - QWindow *window = static_cast(viewPort->window()); - if (window) { - QSSGRef context; + + if (!nodeList.isEmpty()) { + static const float floatMin = std::numeric_limits::lowest(); + static const float floatMax = std::numeric_limits::max(); + totalMinBound = {floatMax, floatMax, floatMax}; + totalMaxBound = {floatMin, floatMin, floatMin}; + } else { + const float halfExtent = defaultExtent / 2.f; + totalMinBound = {-halfExtent, -halfExtent, -halfExtent}; + totalMaxBound = {halfExtent, halfExtent, halfExtent}; + } + for (const auto node : qAsConst(nodeList)) { + auto model = qobject_cast(node); + qreal maxExtent = defaultExtent; + QVector3D center = node->scenePosition(); + if (model) { + auto targetPriv = QQuick3DObjectPrivate::get(model); + if (auto renderModel = static_cast(targetPriv->spatialNode)) { + QWindow *window = static_cast(viewPort->window()); + if (window) { + QSSGRef context; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - context = QSSGRenderContextInterface::getRenderContextInterface(quintptr(window)); + context = QSSGRenderContextInterface::getRenderContextInterface(quintptr(window)); #else - context = targetPriv->sceneManager->rci; + context = targetPriv->sceneManager->rci; #endif - if (!context.isNull()) { - QSSGBounds3 bounds; - auto geometry = qobject_cast(modelNode->geometry()); - if (geometry) { - bounds = geometry->bounds(); - } else { - auto bufferManager = context->bufferManager(); - bounds = renderModel->getModelBounds(bufferManager); + if (!context.isNull()) { + QSSGBounds3 bounds; + auto geometry = qobject_cast(model->geometry()); + if (geometry) { + bounds = geometry->bounds(); + } else { + auto bufferManager = context->bufferManager(); + bounds = renderModel->getModelBounds(bufferManager); + } + + center = renderModel->globalTransform.map(bounds.center()); + const QVector3D e = bounds.extents(); + const QVector3D s = model->sceneScale(); + qreal maxScale = qSqrt(qreal(s.x() * s.x() + s.y() * s.y() + s.z() * s.z())); + maxExtent = qSqrt(qreal(e.x() * e.x() + e.y() * e.y() + e.z() * e.z())); + maxExtent *= maxScale; + + if (maxExtent < 0.0001) + maxExtent = defaultExtent; } - - QVector3D center = bounds.center(); - const QVector3D e = bounds.extents(); - const QVector3D s = targetObject->sceneScale(); - qreal maxScale = qSqrt(qreal(s.x() * s.x() + s.y() * s.y() + s.z() * s.z())); - maxExtent = qSqrt(qreal(e.x() * e.x() + e.y() * e.y() + e.z() * e.z())); - maxExtent *= maxScale; - - if (maxExtent < 0.0001) - maxExtent = defaultExtent; - - // Adjust lookAt to look directly at the center of the object bounds - lookAt = renderModel->globalTransform.map(center); } } } + float halfExtent = float(maxExtent / 2.); + const QVector3D halfExtents {halfExtent, halfExtent, halfExtent}; + + const QVector3D minBound = center - halfExtents; + const QVector3D maxBound = center + halfExtents; + + for (int i = 0; i < 3; ++i) { + totalMinBound[i] = qMin(minBound[i], totalMinBound[i]); + totalMaxBound[i] = qMax(maxBound[i], totalMaxBound[i]); + } } + QVector3D extents = totalMaxBound - totalMinBound; + QVector3D lookAt = totalMinBound + (extents / 2.f); + float maxExtent = qMax(extents.x(), qMax(extents.y(), extents.z())); + // Reset camera position to default zoom QMatrix4x4 m = camera->sceneTransform(); const float *dataPtr(m.data()); @@ -210,9 +246,9 @@ QVector4D GeneralHelper::focusObjectToCamera(QQuick3DCamera *camera, float defau camera->setPosition(lookAt + newLookVector); - qreal divisor = closeUp ? 900. : 725.; + float divisor = closeUp ? 900.f : 725.f; - float newZoomFactor = updateZoom ? qBound(.01f, float(maxExtent / divisor), 100.f) : oldZoom; + float newZoomFactor = updateZoom ? qBound(.01f, maxExtent / divisor, 100.f) : oldZoom; float cameraZoomFactor = zoomCamera(camera, 0, defaultLookAtDistance, lookAt, newZoomFactor, false); return QVector4D(lookAt, cameraZoomFactor); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h index 05ec29249eb..6e888980f81 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/editor3d/generalhelper.h @@ -67,10 +67,10 @@ public: Q_INVOKABLE float zoomCamera(QQuick3DCamera *camera, float distance, float defaultLookAtDistance, const QVector3D &lookAt, float zoomFactor, bool relative); - Q_INVOKABLE QVector4D focusObjectToCamera(QQuick3DCamera *camera, float defaultLookAtDistance, - QQuick3DNode *targetObject, QQuick3DViewport *viewPort, - float oldZoom, bool updateZoom = true, - bool closeUp = false); + Q_INVOKABLE QVector4D focusNodesToCamera(QQuick3DCamera *camera, float defaultLookAtDistance, + const QVariant &nodes, QQuick3DViewport *viewPort, + float oldZoom, bool updateZoom = true, + bool closeUp = false); Q_INVOKABLE bool fuzzyCompare(double a, double b); Q_INVOKABLE void delayedPropertySet(QObject *obj, int delay, const QString &property, const QVariant& value); From 8ed5836746221a07c58e8eaa0c40aecf0e587a10 Mon Sep 17 00:00:00 2001 From: Cristian Adam Date: Mon, 28 Jun 2021 16:39:23 +0200 Subject: [PATCH 103/149] Project: Remove knowsAllBuildExecutables feature This is feature is no longer needed. Change-Id: Ia0798402fcb4c06fb4dd38225359738306211176 Reviewed-by: Qt CI Bot Reviewed-by: Eike Ziller Reviewed-by: Alexis Jeandet Reviewed-by: hjk --- src/plugins/cmakeprojectmanager/cmakeproject.cpp | 1 - .../mesonprojectmanager/project/mesonproject.cpp | 1 - src/plugins/projectexplorer/project.cpp | 11 ----------- src/plugins/projectexplorer/project.h | 5 ----- src/plugins/projectexplorer/target.cpp | 2 +- 5 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp index 4b2a5157e87..e683cca9054 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp @@ -58,7 +58,6 @@ CMakeProject::CMakeProject(const FilePath &fileName) setProjectLanguages(Core::Context(ProjectExplorer::Constants::CXX_LANGUAGE_ID)); setDisplayName(projectDirectory().fileName()); setCanBuildProducts(); - setKnowsAllBuildExecutables(true); setHasMakeInstallEquivalent(true); } diff --git a/src/plugins/mesonprojectmanager/project/mesonproject.cpp b/src/plugins/mesonprojectmanager/project/mesonproject.cpp index 027b6534125..aefc3f481d8 100644 --- a/src/plugins/mesonprojectmanager/project/mesonproject.cpp +++ b/src/plugins/mesonprojectmanager/project/mesonproject.cpp @@ -47,7 +47,6 @@ MesonProject::MesonProject(const Utils::FilePath &path) setProjectLanguages(Core::Context(ProjectExplorer::Constants::CXX_LANGUAGE_ID)); setDisplayName(projectDirectory().fileName()); setCanBuildProducts(); - setKnowsAllBuildExecutables(true); setHasMakeInstallEquivalent(true); } diff --git a/src/plugins/projectexplorer/project.cpp b/src/plugins/projectexplorer/project.cpp index 55de809be63..7064e47204a 100644 --- a/src/plugins/projectexplorer/project.cpp +++ b/src/plugins/projectexplorer/project.cpp @@ -183,7 +183,6 @@ public: Utils::Id m_id; bool m_needsInitialExpansion = false; bool m_canBuildProducts = false; - bool m_knowsAllBuildExecutables = true; bool m_hasMakeInstallEquivalent = false; bool m_needsBuildConfigurations = true; bool m_needsDeployConfigurations = true; @@ -903,11 +902,6 @@ void Project::setHasMakeInstallEquivalent(bool enabled) d->m_hasMakeInstallEquivalent = enabled; } -void Project::setKnowsAllBuildExecutables(bool value) -{ - d->m_knowsAllBuildExecutables = value; -} - void Project::setNeedsBuildConfigurations(bool value) { d->m_needsBuildConfigurations = value; @@ -976,11 +970,6 @@ void Project::configureAsExampleProject(Kit * /*kit*/) { } -bool Project::knowsAllBuildExecutables() const -{ - return d->m_knowsAllBuildExecutables; -} - bool Project::hasMakeInstallEquivalent() const { return d->m_hasMakeInstallEquivalent; diff --git a/src/plugins/projectexplorer/project.h b/src/plugins/projectexplorer/project.h index fd550b5eab4..9b783b7127c 100644 --- a/src/plugins/projectexplorer/project.h +++ b/src/plugins/projectexplorer/project.h @@ -147,10 +147,6 @@ public: virtual ProjectImporter *projectImporter() const; - // The build system is able to report all executables that can be built, independent - // of configuration. - bool knowsAllBuildExecutables() const; - virtual DeploymentKnowledge deploymentKnowledge() const { return DeploymentKnowledge::Bad; } bool hasMakeInstallEquivalent() const; virtual MakeInstallCommand makeInstallCommand(const Target *target, const QString &installRoot); @@ -228,7 +224,6 @@ protected: void removeProjectLanguage(Utils::Id id); void setHasMakeInstallEquivalent(bool enabled); - void setKnowsAllBuildExecutables(bool value); void setNeedsBuildConfigurations(bool value); void setNeedsDeployConfigurations(bool value); diff --git a/src/plugins/projectexplorer/target.cpp b/src/plugins/projectexplorer/target.cpp index 3e2e8ca40f0..51bc0149757 100644 --- a/src/plugins/projectexplorer/target.cpp +++ b/src/plugins/projectexplorer/target.cpp @@ -684,7 +684,7 @@ void Target::updateDefaultRunConfigurations() present = true; } } - if (!present && project()->knowsAllBuildExecutables()) + if (!present) toRemove.append(rc); } configuredCount -= toRemove.count(); From 92904480f0a8d696b3f0dcd46353accb64a44cee Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 1 Jul 2021 08:59:32 +0200 Subject: [PATCH 104/149] Utils: Merge FileUtils::removeRecursively() into FilePath This simplify the interface by removing a possibly wrong choice ensures it works also on remote paths. Change-Id: I01e198958900a91b99dcf2dbb491a593485493ba Reviewed-by: David Schulz --- src/libs/utils/buildablehelperlibrary.cpp | 2 +- src/libs/utils/fileutils.cpp | 22 +++++++++---------- src/libs/utils/fileutils.h | 3 +-- .../androidpackageinstallationstep.cpp | 2 +- .../clangtools/virtualfilesystemoverlay.cpp | 7 +++--- .../cmakeprojectmanager/cmakebuildsystem.cpp | 8 +++---- src/plugins/cpptools/cppheadersource_test.cpp | 2 +- 7 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/libs/utils/buildablehelperlibrary.cpp b/src/libs/utils/buildablehelperlibrary.cpp index f08e7347459..7a2f504af3d 100644 --- a/src/libs/utils/buildablehelperlibrary.cpp +++ b/src/libs/utils/buildablehelperlibrary.cpp @@ -197,7 +197,7 @@ bool BuildableHelperLibrary::copyFiles(const QString &sourcePath, QString *errorMessage) { // try remove the directory - if (!FileUtils::removeRecursively(FilePath::fromString(targetDirectory), errorMessage)) + if (!FilePath::fromString(targetDirectory).removeRecursively(errorMessage)) return false; if (!QDir().mkpath(targetDirectory)) { *errorMessage = QCoreApplication::translate("ProjectExplorer::DebuggingHelperLibrary", "The target directory %1 could not be created.").arg(targetDirectory); diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index 1f32637cbf2..374f495dfd2 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -76,14 +76,7 @@ static DeviceFileHooks s_deviceHooks; */ -/*! - Removes the directory \a filePath and its subdirectories recursively. - - \note The \a error parameter is optional. - - Returns whether the operation succeeded. -*/ -bool FileUtils::removeRecursively(const FilePath &filePath, QString *error) +static bool removeRecursivelyLocal(const FilePath &filePath, QString *error) { QTC_ASSERT(!filePath.needsDevice(), return false); QFileInfo fileInfo = filePath.toFileInfo(); @@ -111,7 +104,7 @@ bool FileUtils::removeRecursively(const FilePath &filePath, QString *error) const QStringList fileNames = dir.entryList( QDir::Files | QDir::Hidden | QDir::System | QDir::Dirs | QDir::NoDotAndDotDot); for (const QString &fileName : fileNames) { - if (!removeRecursively(filePath / fileName, error)) + if (!removeRecursivelyLocal(filePath / fileName, error)) return false; } if (!QDir::root().rmdir(dir.path())) { @@ -1443,13 +1436,20 @@ bool FilePath::removeFile() const return QFile::remove(path()); } -bool FilePath::removeRecursively() const +/*! + Removes the directory this filePath refers too and its subdirectories recursively. + + \note The \a error parameter is optional. + + Returns whether the operation succeeded. +*/ +bool FilePath::removeRecursively(QString *error) const { if (needsDevice()) { QTC_ASSERT(s_deviceHooks.removeRecursively, return false); return s_deviceHooks.removeRecursively(*this); } - return FileUtils::removeRecursively(*this); + return removeRecursivelyLocal(*this, error); } bool FilePath::copyFile(const FilePath &target) const diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index 96c27dc9d00..ae77ac507db 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -175,7 +175,7 @@ public: QDateTime lastModified() const; QFile::Permissions permissions() const; bool removeFile() const; - bool removeRecursively() const; + bool removeRecursively(QString *error = nullptr) const; bool copyFile(const FilePath &target) const; bool renameFile(const FilePath &target) const; @@ -242,7 +242,6 @@ public: }; #endif // QT_GUI_LIB - static bool removeRecursively(const FilePath &filePath, QString *error = nullptr); static bool copyRecursively(const FilePath &srcFilePath, const FilePath &tgtFilePath, QString *error = nullptr); diff --git a/src/plugins/android/androidpackageinstallationstep.cpp b/src/plugins/android/androidpackageinstallationstep.cpp index bb196834dd3..d8247de83c5 100644 --- a/src/plugins/android/androidpackageinstallationstep.cpp +++ b/src/plugins/android/androidpackageinstallationstep.cpp @@ -137,7 +137,7 @@ void AndroidPackageInstallationStep::doRun() FilePath androidDir = FilePath::fromString(dir); if (!dir.isEmpty() && androidDir.exists()) { emit addOutput(tr("Removing directory %1").arg(dir), OutputFormat::NormalMessage); - if (!FileUtils::removeRecursively(androidDir, &error)) { + if (!androidDir.removeRecursively(&error)) { emit addOutput(error, OutputFormat::Stderr); TaskHub::addTask(BuildSystemTask(Task::Error, error)); emit finished(false); diff --git a/src/plugins/clangtools/virtualfilesystemoverlay.cpp b/src/plugins/clangtools/virtualfilesystemoverlay.cpp index faf43db05bf..357f8eec40e 100644 --- a/src/plugins/clangtools/virtualfilesystemoverlay.cpp +++ b/src/plugins/clangtools/virtualfilesystemoverlay.cpp @@ -47,7 +47,7 @@ VirtualFileSystemOverlay::VirtualFileSystemOverlay(const QString &rootPattern) void VirtualFileSystemOverlay::update() { - Utils::FileUtils::removeRecursively(overlayFilePath()); + overlayFilePath().removeRecursively(); QFile overlayFile(m_overlayFilePath.toString()); if (!overlayFile.open(QFile::ReadWrite)) return; @@ -61,8 +61,7 @@ void VirtualFileSystemOverlay::update() documentRoots[doc->filePath().absolutePath()] << doc; AutoSavedPath saved = m_saved.take(document); if (saved.revision != document->document()->revision()) { - if (saved.path.exists()) - Utils::FileUtils::removeRecursively(saved.path); + saved.path.removeRecursively(); saved.revision = document->document()->revision(); QString error; saved.path = Utils::FilePath::fromString(m_root.path()) @@ -79,7 +78,7 @@ void VirtualFileSystemOverlay::update() for (const AutoSavedPath &path : qAsConst(m_saved)) { QString error; - if (!Utils::FileUtils::removeRecursively(path.path, &error)) + if (!path.path.removeRecursively(&error)) qCDebug(LOG) << error; } m_saved = newSaved; diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp index e1b38dfc86d..2ec4e68e6c3 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -513,7 +513,7 @@ void CMakeBuildSystem::clearCMakeCache() stopParsingAndClearState(); - const QList pathsToDelete = { + const FilePath pathsToDelete[] = { m_parameters.buildDirectory / "CMakeCache.txt", m_parameters.buildDirectory / "CMakeCache.txt.prev", m_parameters.buildDirectory / "CMakeFiles", @@ -521,10 +521,8 @@ void CMakeBuildSystem::clearCMakeCache() m_parameters.buildDirectory / ".cmake/api/v1/reply.prev" }; - for (const FilePath &path : pathsToDelete) { - if (path.exists()) - FileUtils::removeRecursively(path); - } + for (const FilePath &path : pathsToDelete) + path.removeRecursively(); } std::unique_ptr CMakeBuildSystem::generateProjectTree( diff --git a/src/plugins/cpptools/cppheadersource_test.cpp b/src/plugins/cpptools/cppheadersource_test.cpp index 37244f4fba2..be0bdd03c2e 100644 --- a/src/plugins/cpptools/cppheadersource_test.cpp +++ b/src/plugins/cpptools/cppheadersource_test.cpp @@ -100,7 +100,7 @@ void CppToolsPlugin::initTestCase() void CppToolsPlugin::cleanupTestCase() { - Utils::FileUtils::removeRecursively(Utils::FilePath::fromString(baseTestDir())); + Utils::FilePath::fromString(baseTestDir()).removeRecursively(); CppFileSettings *fs = fileSettings(); fs->headerSearchPaths.removeLast(); fs->headerSearchPaths.removeLast(); From a58dd22f2d445b27da5085390ada0559dac7321f Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 1 Jul 2021 09:58:48 +0200 Subject: [PATCH 105/149] Utils: Use FilePath in TemporaryDirectory API This helps to lower impedance in the using code. Even though TemporaryDirectory will very likely always stay on the local host, this is one of the entry points into path related string manipulation that we want to base on FilePath nowadays. Change-Id: I302016b8d65e54df94296659a54a93935d9e4627 Reviewed-by: David Schulz --- src/app/main.cpp | 2 +- src/libs/utils/temporarydirectory.cpp | 12 ++++++++++++ src/libs/utils/temporarydirectory.h | 5 +++++ src/plugins/android/javalanguageserver.cpp | 2 +- .../clangcodemodel/clanguiheaderondiskmanager.cpp | 3 ++- src/plugins/clangtools/clangtoolruncontrol.cpp | 2 +- src/plugins/clangtools/documentclangtoolrunner.cpp | 2 +- src/plugins/clangtools/virtualfilesystemoverlay.cpp | 5 ++--- .../cmakeprojectmanager/cmakeprojectimporter.cpp | 8 ++++---- src/plugins/coreplugin/plugininstallwizard.cpp | 6 +++--- src/plugins/cpptools/cpptoolstestcase.cpp | 4 ++-- src/plugins/cpptools/cpptoolstestcase.h | 2 +- .../designercore/instances/puppetcreator.cpp | 4 ++-- src/plugins/qtsupport/qscxmlcgenerator.cpp | 6 +++--- src/plugins/qtsupport/qtprojectimporter.cpp | 10 +++++----- 15 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/app/main.cpp b/src/app/main.cpp index b10eea891c5..77c08cbd088 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -567,7 +567,7 @@ int main(int argc, char **argv) temporaryCleanSettingsDir.reset(new Utils::TemporaryDirectory("qtc-test-settings")); if (!temporaryCleanSettingsDir->isValid()) return 1; - options.settingsPath = temporaryCleanSettingsDir->path(); + options.settingsPath = temporaryCleanSettingsDir->path().path(); } if (!options.settingsPath.isEmpty()) QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, options.settingsPath); diff --git a/src/libs/utils/temporarydirectory.cpp b/src/libs/utils/temporarydirectory.cpp index da539919e42..2c30606e9b0 100644 --- a/src/libs/utils/temporarydirectory.cpp +++ b/src/libs/utils/temporarydirectory.cpp @@ -25,6 +25,8 @@ #include "temporarydirectory.h" +#include "fileutils.h" + #include #include "qtcassert.h" @@ -64,4 +66,14 @@ QString TemporaryDirectory::masterDirectoryPath() return m_masterTemporaryDir->path(); } +FilePath TemporaryDirectory::path() const +{ + return FilePath::fromString(QTemporaryDir::path()); +} + +FilePath TemporaryDirectory::filePath(const QString &fileName) const +{ + return FilePath::fromString(QTemporaryDir::filePath(fileName)); +} + } // namespace Utils diff --git a/src/libs/utils/temporarydirectory.h b/src/libs/utils/temporarydirectory.h index 853474c24bd..42390a9a0f2 100644 --- a/src/libs/utils/temporarydirectory.h +++ b/src/libs/utils/temporarydirectory.h @@ -31,6 +31,8 @@ namespace Utils { +class FilePath; + class QTCREATOR_UTILS_EXPORT TemporaryDirectory : public QTemporaryDir { public: @@ -39,6 +41,9 @@ public: static QTemporaryDir *masterTemporaryDirectory(); static void setMasterTemporaryDirectory(const QString &pattern); static QString masterDirectoryPath(); + + FilePath path() const; + FilePath filePath(const QString &fileName) const; }; } // namespace Utils diff --git a/src/plugins/android/javalanguageserver.cpp b/src/plugins/android/javalanguageserver.cpp index 73de7c0bac2..c7c093f53c3 100644 --- a/src/plugins/android/javalanguageserver.cpp +++ b/src/plugins/android/javalanguageserver.cpp @@ -183,7 +183,7 @@ class JLSInterface : public LanguageClient::StdIOClientInterface public: JLSInterface() = default; - QString workspaceDir() const { return m_workspaceDir.path(); } + QString workspaceDir() const { return m_workspaceDir.path().path(); } private: TemporaryDirectory m_workspaceDir = TemporaryDirectory("QtCreator-jls-XXXXXX"); diff --git a/src/plugins/clangcodemodel/clanguiheaderondiskmanager.cpp b/src/plugins/clangcodemodel/clanguiheaderondiskmanager.cpp index d17f69ed50a..696ced1c6f7 100644 --- a/src/plugins/clangcodemodel/clanguiheaderondiskmanager.cpp +++ b/src/plugins/clangcodemodel/clanguiheaderondiskmanager.cpp @@ -28,6 +28,7 @@ #include #include +#include #include namespace ClangCodeModel { @@ -62,7 +63,7 @@ QString UiHeaderOnDiskManager::remove(const QString &filePath) QString UiHeaderOnDiskManager::directoryPath() const { - return m_temporaryDir.path(); + return m_temporaryDir.path().path(); } QString UiHeaderOnDiskManager::mapPath(const QString &filePath) const diff --git a/src/plugins/clangtools/clangtoolruncontrol.cpp b/src/plugins/clangtools/clangtoolruncontrol.cpp index 4a8cbcfbf4b..31c2a37655a 100644 --- a/src/plugins/clangtools/clangtoolruncontrol.cpp +++ b/src/plugins/clangtools/clangtoolruncontrol.cpp @@ -445,7 +445,7 @@ template ClangToolRunner *ClangToolRunWorker::createRunner() { auto runner = new T(m_diagnosticConfig, this); - runner->init(m_temporaryDir.path(), m_environment); + runner->init(m_temporaryDir.path().path(), m_environment); connect(runner, &ClangToolRunner::finishedWithSuccess, this, &ClangToolRunWorker::onRunnerFinishedWithSuccess); connect(runner, &ClangToolRunner::finishedWithFailure, diff --git a/src/plugins/clangtools/documentclangtoolrunner.cpp b/src/plugins/clangtools/documentclangtoolrunner.cpp index 89d76e0f559..93bcaa7746e 100644 --- a/src/plugins/clangtools/documentclangtoolrunner.cpp +++ b/src/plugins/clangtools/documentclangtoolrunner.cpp @@ -391,7 +391,7 @@ ClangToolRunner *DocumentClangToolRunner::createRunner(const CppTools::ClangDiag const Utils::Environment &env) { auto runner = new T(config, this); - runner->init(m_temporaryDir.path(), env); + runner->init(m_temporaryDir.path().path(), env); connect(runner, &ClangToolRunner::finishedWithSuccess, this, &DocumentClangToolRunner::onSuccess); connect(runner, &ClangToolRunner::finishedWithFailure, diff --git a/src/plugins/clangtools/virtualfilesystemoverlay.cpp b/src/plugins/clangtools/virtualfilesystemoverlay.cpp index 357f8eec40e..0b066ff62f6 100644 --- a/src/plugins/clangtools/virtualfilesystemoverlay.cpp +++ b/src/plugins/clangtools/virtualfilesystemoverlay.cpp @@ -42,7 +42,7 @@ namespace Internal { VirtualFileSystemOverlay::VirtualFileSystemOverlay(const QString &rootPattern) : m_root(rootPattern) - , m_overlayFilePath(Utils::FilePath::fromString(m_root.filePath("vfso.yaml"))) + , m_overlayFilePath(m_root.filePath("vfso.yaml")) { } void VirtualFileSystemOverlay::update() @@ -64,8 +64,7 @@ void VirtualFileSystemOverlay::update() saved.path.removeRecursively(); saved.revision = document->document()->revision(); QString error; - saved.path = Utils::FilePath::fromString(m_root.path()) - .pathAppended(doc->filePath().fileName() + ".auto"); + saved.path = m_root.filePath(doc->filePath().fileName() + ".auto"); while (saved.path.exists()) saved.path = saved.path + ".1"; if (!doc->save(&error, saved.path, true)) { diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp index 0b60ed75739..f60b224564f 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.cpp @@ -162,7 +162,7 @@ static FilePath qmakeFromCMakeCache(const CMakeConfig &config) // Run a CMake project that would do qmake probing TemporaryDirectory qtcQMakeProbeDir("qtc-cmake-qmake-probe-XXXXXXXX"); - QFile cmakeListTxt(qtcQMakeProbeDir.path() + "/CMakeLists.txt"); + QFile cmakeListTxt(qtcQMakeProbeDir.filePath("CMakeLists.txt").toString()); if (!cmakeListTxt.open(QIODevice::WriteOnly)) { return FilePath(); } @@ -219,9 +219,9 @@ static FilePath qmakeFromCMakeCache(const CMakeConfig &config) QStringList args; args.push_back("-S"); - args.push_back(qtcQMakeProbeDir.path()); + args.push_back(qtcQMakeProbeDir.path().path()); args.push_back("-B"); - args.push_back(qtcQMakeProbeDir.path() + "/build"); + args.push_back(qtcQMakeProbeDir.filePath("build").path()); args.push_back("-G"); args.push_back(cmakeGenerator); @@ -242,7 +242,7 @@ static FilePath qmakeFromCMakeCache(const CMakeConfig &config) cmake.setCommand({cmakeExecutable, args}); cmake.runBlocking(); - QFile qmakeLocationTxt(qtcQMakeProbeDir.path() + "/qmake-location.txt"); + QFile qmakeLocationTxt(qtcQMakeProbeDir.filePath("qmake-location.txt").path()); if (!qmakeLocationTxt.open(QIODevice::ReadOnly)) { return FilePath(); } diff --git a/src/plugins/coreplugin/plugininstallwizard.cpp b/src/plugins/coreplugin/plugininstallwizard.cpp index fd6e3ddfe9d..a97f15ef341 100644 --- a/src/plugins/coreplugin/plugininstallwizard.cpp +++ b/src/plugins/coreplugin/plugininstallwizard.cpp @@ -200,14 +200,14 @@ public: m_canceled = false; m_tempDir = std::make_unique("plugininstall"); - m_data->extractedPath = FilePath::fromString(m_tempDir->path()); + m_data->extractedPath = m_tempDir->path(); m_label->setText(PluginInstallWizard::tr("Checking archive...")); m_label->setType(InfoLabel::None); m_cancelButton->setVisible(true); m_output->clear(); - m_archive = Archive::unarchive(m_data->sourcePath, FilePath::fromString(m_tempDir->path())); + m_archive = Archive::unarchive(m_data->sourcePath, m_tempDir->path()); if (!m_archive) { m_label->setType(InfoLabel::Error); @@ -271,7 +271,7 @@ public: PluginSpec *coreplugin = CorePlugin::instance()->pluginSpec(); // look for plugin - QDirIterator it(m_tempDir->path(), + QDirIterator it(m_tempDir->path().path(), libraryNameFilter(), QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories); diff --git a/src/plugins/cpptools/cpptoolstestcase.cpp b/src/plugins/cpptools/cpptoolstestcase.cpp index c80a4a7cfff..e9a6e85d0d5 100644 --- a/src/plugins/cpptools/cpptoolstestcase.cpp +++ b/src/plugins/cpptools/cpptoolstestcase.cpp @@ -312,7 +312,7 @@ QString TemporaryDir::createFile(const QByteArray &relativePath, const QByteArra if (relativePathString.isEmpty() || QFileInfo(relativePathString).isAbsolute()) return QString(); - const QString filePath = m_temporaryDir.path() + QLatin1Char('/') + relativePathString; + const QString filePath = m_temporaryDir.filePath(relativePathString).path(); if (!TestCase::writeFile(filePath, contents)) return QString(); return filePath; @@ -368,7 +368,7 @@ TemporaryCopiedDir::TemporaryCopiedDir(const QString &sourceDirPath) QString TemporaryCopiedDir::absolutePath(const QByteArray &relativePath) const { - return m_temporaryDir.path() + QLatin1Char('/') + QString::fromUtf8(relativePath); + return m_temporaryDir.filePath(QString::fromUtf8(relativePath)).path(); } FileWriterAndRemover::FileWriterAndRemover(const QString &filePath, const QByteArray &contents) diff --git a/src/plugins/cpptools/cpptoolstestcase.h b/src/plugins/cpptools/cpptoolstestcase.h index 08ae5b7c701..d03a1b297c6 100644 --- a/src/plugins/cpptools/cpptoolstestcase.h +++ b/src/plugins/cpptools/cpptoolstestcase.h @@ -155,7 +155,7 @@ public: TemporaryDir(); bool isValid() const { return m_isValid; } - QString path() const { return m_temporaryDir.path(); } + QString path() const { return m_temporaryDir.path().path(); } QString createFile(const QByteArray &relativePath, const QByteArray &contents); diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp index d9ab3ca32d7..9a40a6a8ced 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp +++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp @@ -321,7 +321,7 @@ bool PuppetCreator::build(const QString &qmlPuppetProjectFilePath) const qmakeArguments.append("CONFIG+=release"); #endif qmakeArguments.append(qmlPuppetProjectFilePath); - buildSucceeded = startBuildProcess(buildDirectory.path(), qmakeCommand(), qmakeArguments, &progressDialog); + buildSucceeded = startBuildProcess(buildDirectory.path().path(), qmakeCommand(), qmakeArguments, &progressDialog); if (buildSucceeded) { progressDialog.show(); QString buildingCommand = buildCommand(); @@ -330,7 +330,7 @@ bool PuppetCreator::build(const QString &qmlPuppetProjectFilePath) const buildArguments.append("-j"); buildArguments.append(idealProcessCount()); } - buildSucceeded = startBuildProcess(buildDirectory.path(), buildingCommand, buildArguments, &progressDialog); + buildSucceeded = startBuildProcess(buildDirectory.path().path(), buildingCommand, buildArguments, &progressDialog); } if (!buildSucceeded) { diff --git a/src/plugins/qtsupport/qscxmlcgenerator.cpp b/src/plugins/qtsupport/qscxmlcgenerator.cpp index 3c2f0ff7cf3..98be1fd99e8 100644 --- a/src/plugins/qtsupport/qscxmlcgenerator.cpp +++ b/src/plugins/qtsupport/qscxmlcgenerator.cpp @@ -48,8 +48,8 @@ QScxmlcGenerator::QScxmlcGenerator(const Project *project, m_tmpdir("qscxmlgenerator") { QTC_ASSERT(targets.count() == 2, return); - m_header = m_tmpdir.path() + QLatin1Char('/') + targets[0].fileName(); - m_impl = m_tmpdir.path() + QLatin1Char('/') + targets[1].fileName(); + m_header = m_tmpdir.filePath(targets[0].fileName()).toString(); + m_impl = m_tmpdir.filePath(targets[1].fileName()).toString(); } Tasks QScxmlcGenerator::parseIssues(const QByteArray &processStderr) @@ -97,7 +97,7 @@ QStringList QScxmlcGenerator::arguments() const Utils::FilePath QScxmlcGenerator::workingDirectory() const { - return Utils::FilePath::fromString(m_tmpdir.path()); + return m_tmpdir.path(); } bool QScxmlcGenerator::prepareToRun(const QByteArray &sourceContents) diff --git a/src/plugins/qtsupport/qtprojectimporter.cpp b/src/plugins/qtsupport/qtprojectimporter.cpp index 4749271091c..4ee36992938 100644 --- a/src/plugins/qtsupport/qtprojectimporter.cpp +++ b/src/plugins/qtsupport/qtprojectimporter.cpp @@ -380,8 +380,8 @@ void QtSupportPlugin::testQtProjectImporter_oneProject() SysRootKitAspect::setSysRoot(kitTemplates[2], Utils::FilePath::fromString("/some/other/path")); QVector qmakePaths = {defaultQt->qmakeCommand(), - setupQmake(defaultQt, tempDir1.path()), - setupQmake(defaultQt, tempDir2.path())}; + setupQmake(defaultQt, tempDir1.path().path()), + setupQmake(defaultQt, tempDir2.path().path())}; for (int i = 1; i < qmakePaths.count(); ++i) QVERIFY(!QtVersionManager::version(Utils::equal(&BaseQtVersion::qmakeCommand, qmakePaths.at(i)))); @@ -412,7 +412,7 @@ void QtSupportPlugin::testQtProjectImporter_oneProject() // Finally set up importer: // Copy the directoryData so that importer is free to delete it later. - TestQtProjectImporter importer(Utils::FilePath::fromString(tempDir1.path()), + TestQtProjectImporter importer(tempDir1.path(), Utils::transform(testData, [](DirectoryData *i) { return static_cast(new DirectoryData(*i)); })); @@ -425,7 +425,7 @@ void QtSupportPlugin::testQtProjectImporter_oneProject() const QList buildInfo = importer.import(Utils::FilePath::fromString(appDir), true); // VALIDATE: Basic TestImporter state: - QCOMPARE(importer.projectFilePath().toString(), tempDir1.path()); + QCOMPARE(importer.projectFilePath(), tempDir1.path()); QCOMPARE(importer.allDeleted(), true); // VALIDATE: Result looks reasonable: @@ -529,7 +529,7 @@ void QtSupportPlugin::testQtProjectImporter_oneProject() QCOMPARE(newKitId, newKitIdAfterImport); // VALIDATE: Importer state - QCOMPARE(importer.projectFilePath().toString(), tempDir1.path()); + QCOMPARE(importer.projectFilePath(), tempDir1.path()); QCOMPARE(importer.allDeleted(), true); if (kitIsPersistent) { From 39707a425858faed414c2411465e4bc2f37c88a4 Mon Sep 17 00:00:00 2001 From: hjk Date: Fri, 2 Jul 2021 05:51:46 +0200 Subject: [PATCH 106/149] Docker: Add a README for the manual test Change-Id: I588d525d06be33ebb5eb1b9b927c4488aeba0120 Reviewed-by: Christian Stenger --- tests/manual/docker/README.md | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/manual/docker/README.md diff --git a/tests/manual/docker/README.md b/tests/manual/docker/README.md new file mode 100644 index 00000000000..42833b0aecc --- /dev/null +++ b/tests/manual/docker/README.md @@ -0,0 +1,55 @@ +Limitations: + +- Only Linux development hosts supported, as the docker container + contents is accessed via the local file system. + +- It currently unconditionally mounts /data and /opt, + source code has to live in either. + +- Kit items are auto-detected, but Kits themselves need to be + fixed up manually. + + +What works: + +- Qmake in path is found +- CMake in path is found +- Toolchain autodection finds gcc +- Gdb in path is found + +- Building in the container with qmake works +- Building in the container with cmake works + +- Running locally or in a compatible docker container works + + +For testing: + +- build docker containers from this directory (tests/manual/docker) by + running ./build.sh. This builds a docker image containing a Desktop Qt + build setup (including compiler etc) and second docker image container + containing a run environment without the build tools, but e.g. with gdb + for debugger testing + + - or - + + install similar docker images containing Qt, e.g. darkmattercoder/qt-build + +- Go to Tools -> Options -> Devices, 'Add', 'Apply' for both images. + Note that the Build container alone is sufficient also to run applications, + but using the Run container gives a more restricted setup closer to a + real world scenario. + +- Try to auto-detect kit items by pressing "Auto Detect Kit Items" for + the Build container (only Build, not Run) + +- Check whether the auto-detection of kit items works, i.e. this Qt version + shows up in Kits -> Qt Version, Compilers, CMake, Debugger. + +- Fix the Kit setup: There should also be an auto-detected Kit, not + necessarily with all items in a suitable state. + Select as Run device the Run container, as Build device the Build container, + and matching auto-detected compilers, cmake, gdb. + +- Create a CMake based Qt (console or widget) application, build / run / debug it. + From 8bef120f14fd4dd543a1cd8c83e0cbb4c0a32465 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Thu, 1 Jul 2021 10:22:34 +0200 Subject: [PATCH 107/149] Editor: fix duplicating color schemes Core::ICore::userResourcePath seems to have returned a string with a trailing slash before it returned a Utils::FilePath. This allowed us to just concatenate the resource path with the pattern. Use FilePath::pathAppended to make sure the styles end up in the correct directory. Fixes: QTCREATORBUG-25944 Fixes: QTCREATORBUG-25910 Change-Id: I6ac735e3746e4328b5bbae4e55d91ef642277886 Reviewed-by: Christian Stenger --- src/plugins/texteditor/fontsettingspage.cpp | 30 ++++++++++----------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/plugins/texteditor/fontsettingspage.cpp b/src/plugins/texteditor/fontsettingspage.cpp index 2487e31fff9..c7cf43ddfaa 100644 --- a/src/plugins/texteditor/fontsettingspage.cpp +++ b/src/plugins/texteditor/fontsettingspage.cpp @@ -189,32 +189,30 @@ public: } // namespace Internal -static QString customStylesPath() +static Utils::FilePath customStylesPath() { - return Core::ICore::userResourcePath("styles").toString(); + return Core::ICore::userResourcePath("styles"); } -static QString createColorSchemeFileName(const QString &pattern) +static Utils::FilePath createColorSchemeFileName(const QString &pattern) { - const QString stylesPath = customStylesPath(); - QString baseFileName = stylesPath; - baseFileName += pattern; + const Utils::FilePath stylesPath = customStylesPath(); // Find an available file name int i = 1; - QString fileName; + Utils::FilePath filePath; do { - fileName = baseFileName.arg((i == 1) ? QString() : QString::number(i)); + filePath = stylesPath.pathAppended(pattern.arg((i == 1) ? QString() : QString::number(i))); ++i; - } while (QFile::exists(fileName)); + } while (filePath.exists()); // Create the base directory when it doesn't exist - if (!QFile::exists(stylesPath) && !QDir().mkpath(stylesPath)) { + if (!stylesPath.exists() && !stylesPath.createDir()) { qWarning() << "Failed to create color scheme directory:" << stylesPath; - return QString(); + return {}; } - return fileName; + return filePath; } // ------- FormatDescription @@ -470,7 +468,7 @@ void FontSettingsPageWidget::copyColorScheme(const QString &name) QString baseFileName = QFileInfo(entry.fileName).completeBaseName(); baseFileName += QLatin1String("_copy%1.xml"); - QString fileName = createColorSchemeFileName(baseFileName); + Utils::FilePath fileName = createColorSchemeFileName(baseFileName); if (!fileName.isEmpty()) { // Ask about saving any existing modifactions @@ -481,8 +479,8 @@ void FontSettingsPageWidget::copyColorScheme(const QString &name) ColorScheme scheme = m_value.colorScheme(); scheme.setDisplayName(name); - if (scheme.save(fileName, Core::ICore::dialogParent())) - m_value.setColorSchemeFileName(fileName); + if (scheme.save(fileName.path(), Core::ICore::dialogParent())) + m_value.setColorSchemeFileName(fileName.path()); refreshColorSchemeList(); } @@ -576,7 +574,7 @@ void FontSettingsPageWidget::refreshColorSchemeList() if (colorSchemes.isEmpty()) qWarning() << "Warning: no color schemes found in path:" << styleDir.path(); - styleDir.setPath(customStylesPath()); + styleDir.setPath(customStylesPath().path()); foreach (const QString &file, styleDir.entryList()) { const QString fileName = styleDir.absoluteFilePath(file); From 18b98a85eb3f5d55e1a1b6c60b9ac3f18a914643 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Thu, 1 Jul 2021 10:55:24 +0200 Subject: [PATCH 108/149] Editor: only remove one scheme at a time Since the delete button has already the accept role the delete was triggered twice. First time by the role and second time from the connect of the button clicked signal to QDialog::accept. Change-Id: I9bd6104d36f286ec8bd0b25ee4ab6e7c8306d51a Reviewed-by: Christian Stenger --- src/plugins/texteditor/fontsettingspage.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/texteditor/fontsettingspage.cpp b/src/plugins/texteditor/fontsettingspage.cpp index c7cf43ddfaa..bcceb2c2176 100644 --- a/src/plugins/texteditor/fontsettingspage.cpp +++ b/src/plugins/texteditor/fontsettingspage.cpp @@ -508,7 +508,6 @@ void FontSettingsPageWidget::confirmDeleteColorScheme() messageBox->addButton(deleteButton, QMessageBox::AcceptRole); messageBox->setDefaultButton(deleteButton); - connect(deleteButton, &QAbstractButton::clicked, messageBox, &QDialog::accept); connect(messageBox, &QDialog::accepted, this, &FontSettingsPageWidget::deleteColorScheme); messageBox->setAttribute(Qt::WA_DeleteOnClose); messageBox->open(); From 1db9dd058e66e55163b85aecc5a63171bc3461d4 Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 30 Jun 2021 11:33:33 +0200 Subject: [PATCH 109/149] QtSupport: Use FilePaths in BaseQtVersion::isInSourceDirectory ... and isSubProject. Take the opportunity to rename them into isInQtSourceDirectory and a isQtSubProject to make its limited scope clearer. An open question is why this is only used for qmake projects. Change-Id: If36f9457583eac17e149624ec46e5de10dd4a5a5 Reviewed-by: Christian Stenger --- .../qmakebuildconfiguration.cpp | 2 +- .../qmakeprojectmanager/qmakeproject.cpp | 2 +- src/plugins/qtsupport/baseqtversion.cpp | 31 +++++++++---------- src/plugins/qtsupport/baseqtversion.h | 4 +-- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp b/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp index a4f9963a30e..b84aa0036bf 100644 --- a/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp +++ b/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp @@ -757,7 +757,7 @@ static BuildInfo createBuildInfo(const Kit *k, const FilePath &projectPath, // Leave info.buildDirectory unset; // check if this project is in the source directory: - if (version && version->isInSourceDirectory(projectPath)) { + if (version && version->isInQtSourceDirectory(projectPath)) { // assemble build directory QString projectDirectory = projectPath.toFileInfo().absolutePath(); QDir qtSourceDir = QDir(version->sourcePath().toString()); diff --git a/src/plugins/qmakeprojectmanager/qmakeproject.cpp b/src/plugins/qmakeprojectmanager/qmakeproject.cpp index 3babadd0dc7..a1ff1f21f11 100644 --- a/src/plugins/qmakeprojectmanager/qmakeproject.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeproject.cpp @@ -746,7 +746,7 @@ Tasks QmakeProject::projectIssues(const Kit *k) const // that is not the one from the current kit. const QList qtsContainingThisProject = QtVersionManager::versions([filePath = projectFilePath()](const BaseQtVersion *qt) { - return qt->isValid() && qt->isSubProject(filePath); + return qt->isValid() && qt->isQtSubProject(filePath); }); if (!qtsContainingThisProject.isEmpty() && !qtsContainingThisProject.contains(const_cast(qtFromKit))) { diff --git a/src/plugins/qtsupport/baseqtversion.cpp b/src/plugins/qtsupport/baseqtversion.cpp index a1c5331c270..0ba9d049672 100644 --- a/src/plugins/qtsupport/baseqtversion.cpp +++ b/src/plugins/qtsupport/baseqtversion.cpp @@ -1961,35 +1961,32 @@ FilePath BaseQtVersionPrivate::sourcePath(const QHash &versio return FilePath::fromUserInput(QFileInfo(sourcePath).canonicalFilePath()); } -bool BaseQtVersion::isInSourceDirectory(const FilePath &filePath) +bool BaseQtVersion::isInQtSourceDirectory(const FilePath &filePath) const { - const FilePath &source = sourcePath(); + FilePath source = sourcePath(); if (source.isEmpty()) return false; - QDir dir = QDir(source.toString()); - if (dir.dirName() == "qtbase") - dir.cdUp(); - return filePath.isChildOf(dir); + if (source.fileName() == "qtbase") + source = source.parentDir(); + return filePath.isChildOf(source); } -bool BaseQtVersion::isSubProject(const FilePath &filePath) const +bool BaseQtVersion::isQtSubProject(const FilePath &filePath) const { - const FilePath &source = sourcePath(); + FilePath source = sourcePath(); if (!source.isEmpty()) { - QDir dir = QDir(source.toString()); - if (dir.dirName() == "qtbase") - dir.cdUp(); - - if (filePath.isChildOf(dir)) + if (source.fileName() == "qtbase") + source = source.parentDir(); + if (filePath.isChildOf(source)) return true; } - const QString examples = examplesPath().toString(); - if (!examples.isEmpty() && filePath.isChildOf(QDir(examples))) + const FilePath examples = examplesPath(); + if (!examples.isEmpty() && filePath.isChildOf(examples)) return true; - const QString demos = demosPath().toString(); - if (!demos.isEmpty() && filePath.isChildOf(QDir(demos))) + const FilePath demos = demosPath(); + if (!demos.isEmpty() && filePath.isChildOf(demos)) return true; return false; diff --git a/src/plugins/qtsupport/baseqtversion.h b/src/plugins/qtsupport/baseqtversion.h index e58523f2ee5..9ea9af4b1d9 100644 --- a/src/plugins/qtsupport/baseqtversion.h +++ b/src/plugins/qtsupport/baseqtversion.h @@ -128,8 +128,8 @@ public: Utils::FilePath sourcePath() const; // returns source path for installed qt packages and empty string for self build qt Utils::FilePath qtPackageSourcePath() const; - bool isInSourceDirectory(const Utils::FilePath &filePath); - bool isSubProject(const Utils::FilePath &filePath) const; + bool isInQtSourceDirectory(const Utils::FilePath &filePath) const; + bool isQtSubProject(const Utils::FilePath &filePath) const; Utils::FilePath rccCommand() const; // used by UiCodeModelSupport From 4ba07d05c4ebed82821b4a5be4f521d0b012914c Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 1 Jul 2021 11:31:30 +0200 Subject: [PATCH 110/149] Utils: Make a FilePath's osType publicly accessible Will help with proper is{Relative,Absolute}Path implementations. Change-Id: Icad90b5a55d9cf733f6ee66dbbe273ec9682d387 Reviewed-by: David Schulz --- src/libs/utils/fileutils.cpp | 18 ++++++++++-------- src/libs/utils/fileutils.h | 1 + 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index 374f495dfd2..c1dd491bd99 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -1011,15 +1011,8 @@ FilePath FilePath::symLinkTarget() const FilePath FilePath::withExecutableSuffix() const { - OsType osType; - if (needsDevice()) { - QTC_ASSERT(s_deviceHooks.osType, return {}); - osType = s_deviceHooks.osType(*this); - } else { - osType = HostOsInfo::hostOs(); - } FilePath res = *this; - res.setPath(OsSpecificAspects::withExecutableSuffix(osType, m_data)); + res.setPath(OsSpecificAspects::withExecutableSuffix(osType(), m_data)); return res; } @@ -1427,6 +1420,15 @@ QFile::Permissions FilePath::permissions() const return toFileInfo().permissions(); } +OsType FilePath::osType() const +{ + if (needsDevice()) { + QTC_ASSERT(s_deviceHooks.osType, return {}); + return s_deviceHooks.osType(*this); + } + return HostOsInfo::hostOs(); +} + bool FilePath::removeFile() const { if (needsDevice()) { diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index ae77ac507db..b08974009fe 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -174,6 +174,7 @@ public: bool isNewerThan(const QDateTime &timeStamp) const; QDateTime lastModified() const; QFile::Permissions permissions() const; + OsType osType() const; bool removeFile() const; bool removeRecursively(QString *error = nullptr) const; bool copyFile(const FilePath &target) const; From 7bb7debb5c084d402fcc51d3efd82b9264300c5a Mon Sep 17 00:00:00 2001 From: Nodir Temirkhodjaev Date: Thu, 1 Jul 2021 17:36:07 +0300 Subject: [PATCH 111/149] styles/inkpot.xml: Add definition for "Parameter" Task-number: QTCREATORBUG-25619 Change-Id: Iebf1710f3bb75e022a8267993ab8cfa37eaff847 Reviewed-by: David Schulz --- share/qtcreator/styles/inkpot.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/share/qtcreator/styles/inkpot.xml b/share/qtcreator/styles/inkpot.xml index e5183fe9ba4..780be74d46d 100644 --- a/share/qtcreator/styles/inkpot.xml +++ b/share/qtcreator/styles/inkpot.xml @@ -27,6 +27,7 @@