diff options
Diffstat (limited to 'src/plugins')
167 files changed, 3584 insertions, 557 deletions
diff --git a/src/plugins/android/androidbuildapkstep.cpp b/src/plugins/android/androidbuildapkstep.cpp index d3331d5994c..4c33d6b5679 100644 --- a/src/plugins/android/androidbuildapkstep.cpp +++ b/src/plugins/android/androidbuildapkstep.cpp @@ -324,7 +324,7 @@ QWidget *AndroidBuildApkWidget::createAdvancedGroup() auto vbox = new QVBoxLayout(group); QtSupport::BaseQtVersion *version = QtSupport::QtKitAspect::qtVersion(m_step->kit()); if (version && version->supportsMultipleQtAbis()) { - auto buildAAB = new QCheckBox(tr("Build .aab (Android App Bundle)"), group); + auto buildAAB = new QCheckBox(tr("Build Android App Bundle (*.aab)"), group); buildAAB->setChecked(m_step->buildAAB()); connect(buildAAB, &QAbstractButton::toggled, m_step, &AndroidBuildApkStep::setBuildAAB); vbox->addWidget(buildAAB); diff --git a/src/plugins/android/androiddeployqtstep.cpp b/src/plugins/android/androiddeployqtstep.cpp index 0de5dc0d46b..3776b55c33a 100644 --- a/src/plugins/android/androiddeployqtstep.cpp +++ b/src/plugins/android/androiddeployqtstep.cpp @@ -51,6 +51,7 @@ #include <qtsupport/qtkitinformation.h> #include <utils/algorithm.h> +#include <utils/layoutbuilder.h> #include <utils/qtcassert.h> #include <utils/qtcprocess.h> #include <utils/synchronousprocess.h> @@ -86,8 +87,18 @@ AndroidDeployQtStep::AndroidDeployQtStep(BuildStepList *parent, Utils::Id id) : BuildStep(parent, id) { setImmutable(true); + + m_uninstallPreviousPackage = addAspect<BoolAspect>(); + m_uninstallPreviousPackage->setSettingsKey(UninstallPreviousPackageKey); + m_uninstallPreviousPackage->setLabel(tr("Uninstall the existing app first")); + m_uninstallPreviousPackage->setValue(false); + const QtSupport::BaseQtVersion * const qt = QtSupport::QtKitAspect::qtVersion(kit()); - m_uninstallPreviousPackage = qt && qt->qtVersion() < QtSupport::QtVersionNumber(5, 4, 0); + const bool forced = qt && qt->qtVersion() < QtSupport::QtVersionNumber(5, 4, 0); + if (forced) { + m_uninstallPreviousPackage->setValue(true); + m_uninstallPreviousPackage->setEnabled(false); + } connect(this, &AndroidDeployQtStep::askForUninstall, this, &AndroidDeployQtStep::slotAskForUninstall, @@ -167,7 +178,7 @@ bool AndroidDeployQtStep::init() emit addOutput(tr("Deploying to %1").arg(m_serialNumber), OutputFormat::Stdout); - m_uninstallPreviousPackageRun = m_uninstallPreviousPackage; + m_uninstallPreviousPackageRun = m_uninstallPreviousPackage->value(); if (m_uninstallPreviousPackageRun) m_manifestName = AndroidManager::manifestPath(target()); @@ -480,14 +491,6 @@ QWidget *AndroidDeployQtStep::createConfigWidget() setDisplayName(QString("<b>%1</b>").arg(displayName())); setSummaryText(displayName()); - auto uninstallPreviousCheckBox = new QCheckBox(widget); - uninstallPreviousCheckBox->setText(tr("Uninstall the existing app first")); - uninstallPreviousCheckBox->setChecked(uninstallPreviousPackage() > Keep); - uninstallPreviousCheckBox->setEnabled(uninstallPreviousPackage() != ForceUninstall); - - connect(uninstallPreviousCheckBox, &QAbstractButton::toggled, - this, &AndroidDeployQtStep::setUninstallPreviousPackage); - auto resetDefaultDevices = new QPushButton(widget); resetDefaultDevices->setText(tr("Reset Default Deployment Devices")); @@ -508,10 +511,10 @@ QWidget *AndroidDeployQtStep::createConfigWidget() AndroidManager::installQASIPackage(target(), packagePath); }); - auto layout = new QVBoxLayout(widget); - layout->addWidget(uninstallPreviousCheckBox); - layout->addWidget(resetDefaultDevices); - layout->addWidget(installCustomApkButton); + LayoutBuilder builder(widget); + builder.addRow(m_uninstallPreviousPackage); + builder.addRow(resetDefaultDevices); + builder.addRow(installCustomApkButton); return widget; } @@ -569,32 +572,6 @@ AndroidDeployQtStep::DeployErrorCode AndroidDeployQtStep::parseDeployErrors(QStr return errorCode; } -bool AndroidDeployQtStep::fromMap(const QVariantMap &map) -{ - m_uninstallPreviousPackage = map.value(UninstallPreviousPackageKey, m_uninstallPreviousPackage).toBool(); - return ProjectExplorer::BuildStep::fromMap(map); -} - -QVariantMap AndroidDeployQtStep::toMap() const -{ - QVariantMap map = ProjectExplorer::BuildStep::toMap(); - map.insert(UninstallPreviousPackageKey, m_uninstallPreviousPackage); - return map; -} - -void AndroidDeployQtStep::setUninstallPreviousPackage(bool uninstall) -{ - m_uninstallPreviousPackage = uninstall; -} - -AndroidDeployQtStep::UninstallType AndroidDeployQtStep::uninstallPreviousPackage() -{ - const QtSupport::BaseQtVersion * const qt = QtSupport::QtKitAspect::qtVersion(kit()); - if (qt && qt->qtVersion() < QtSupport::QtVersionNumber(5, 4, 0)) - return ForceUninstall; - return m_uninstallPreviousPackage ? Uninstall : Keep; -} - // AndroidDeployQtStepFactory AndroidDeployQtStepFactory::AndroidDeployQtStepFactory() diff --git a/src/plugins/android/androiddeployqtstep.h b/src/plugins/android/androiddeployqtstep.h index 450213de5a4..3090bbb2e8d 100644 --- a/src/plugins/android/androiddeployqtstep.h +++ b/src/plugins/android/androiddeployqtstep.h @@ -59,20 +59,8 @@ class AndroidDeployQtStep : public ProjectExplorer::BuildStep }; public: - enum UninstallType { - Keep, - Uninstall, - ForceUninstall - }; - AndroidDeployQtStep(ProjectExplorer::BuildStepList *bc, Utils::Id id); - bool fromMap(const QVariantMap &map) override; - QVariantMap toMap() const override; - - UninstallType uninstallPreviousPackage(); - void setUninstallPreviousPackage(bool uninstall); - signals: void askForUninstall(DeployErrorCode errorCode); @@ -105,7 +93,7 @@ private: QMap<QString, QString> m_filesToPull; QStringList m_androidABIs; - bool m_uninstallPreviousPackage = false; + Utils::BoolAspect *m_uninstallPreviousPackage = nullptr; bool m_uninstallPreviousPackageRun = false; bool m_useAndroiddeployqt = false; bool m_askForUninstall = false; diff --git a/src/plugins/android/androidqtversion.cpp b/src/plugins/android/androidqtversion.cpp index 32a58ffc667..2f306671eb3 100644 --- a/src/plugins/android/androidqtversion.cpp +++ b/src/plugins/android/androidqtversion.cpp @@ -86,7 +86,8 @@ QString AndroidQtVersion::invalidReason() const bool AndroidQtVersion::supportsMultipleQtAbis() const { - return qtVersion() >= QtSupport::QtVersionNumber{5, 14}; + return qtVersion() >= QtSupport::QtVersionNumber{5, 14} + && qtVersion() < QtSupport::QtVersionNumber{6, 0}; } Abis AndroidQtVersion::detectQtAbis() const diff --git a/src/plugins/autotest/boost/boosttestsettingspage.ui b/src/plugins/autotest/boost/boosttestsettingspage.ui index 1596cd7d41d..22ddef51f64 100644 --- a/src/plugins/autotest/boost/boosttestsettingspage.ui +++ b/src/plugins/autotest/boost/boosttestsettingspage.ui @@ -10,9 +10,6 @@ <height>284</height> </rect> </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <item> <layout class="QHBoxLayout" name="horizontalLayout_4"> diff --git a/src/plugins/autotest/testconfiguration.cpp b/src/plugins/autotest/testconfiguration.cpp index 10709e38a61..785abe4f753 100644 --- a/src/plugins/autotest/testconfiguration.cpp +++ b/src/plugins/autotest/testconfiguration.cpp @@ -185,8 +185,11 @@ void TestConfiguration::completeTestInformation(TestRunMode runMode) qCDebug(LOG) << " LocalExecutable" << localExecutable; qCDebug(LOG) << " DeployedExecutable" << deployedExecutable; - qCDebug(LOG) << "Iterating run configurations"; - for (RunConfiguration *runConfig : target->runConfigurations()) { + qCDebug(LOG) << "Iterating run configurations - prefer active over others"; + QList<RunConfiguration *> runConfigurations = target->runConfigurations(); + runConfigurations.removeOne(target->activeRunConfiguration()); + runConfigurations.prepend(target->activeRunConfiguration()); + for (RunConfiguration *runConfig : qAsConst(runConfigurations)) { qCDebug(LOG) << "RunConfiguration" << runConfig->id(); if (!isLocal(target)) { // TODO add device support qCDebug(LOG) << " Skipped as not being local"; diff --git a/src/plugins/clangcodemodel/test/clangbatchfileprocessor.cpp b/src/plugins/clangcodemodel/test/clangbatchfileprocessor.cpp index b1ce0cd6ebb..bb45147eb87 100644 --- a/src/plugins/clangcodemodel/test/clangbatchfileprocessor.cpp +++ b/src/plugins/clangcodemodel/test/clangbatchfileprocessor.cpp @@ -58,10 +58,11 @@ #include <QThread> using namespace ClangBackEnd; -using namespace ClangCodeModel; -using namespace ClangCodeModel::Internal; using namespace ProjectExplorer; +namespace ClangCodeModel { +namespace Internal { + static Q_LOGGING_CATEGORY(debug, "qtc.clangcodemodel.batch", QtWarningMsg); static int timeOutFromEnvironmentVariable() @@ -78,7 +79,7 @@ static int timeOutFromEnvironmentVariable() return intervalAsInt; } -static int timeOutInMs() +int timeOutInMs() { static int timeOut = timeOutFromEnvironmentVariable(); return timeOut; @@ -747,9 +748,6 @@ bool BatchFileParser::parseLine(const QString &line) } // anonymous namespace -namespace ClangCodeModel { -namespace Internal { - static QString applySubstitutions(const QString &filePath, const QString &text) { const QString dirPath = QFileInfo(filePath).absolutePath(); diff --git a/src/plugins/clangcodemodel/test/clangbatchfileprocessor.h b/src/plugins/clangcodemodel/test/clangbatchfileprocessor.h index 40f0bceee3b..8608229f985 100644 --- a/src/plugins/clangcodemodel/test/clangbatchfileprocessor.h +++ b/src/plugins/clangcodemodel/test/clangbatchfileprocessor.h @@ -30,6 +30,8 @@ namespace ClangCodeModel { namespace Internal { +int timeOutInMs(); + bool runClangBatchFile(const QString &filePath); } // namespace Internal diff --git a/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp b/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp index 0a38744a38a..e32e22d8e1f 100644 --- a/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp +++ b/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp @@ -26,6 +26,7 @@ #include "clangcodecompletion_test.h" #include "clangautomationutils.h" +#include "clangbatchfileprocessor.h" #include "../clangcompletionassistinterface.h" #include "../clangmodelmanagersupport.h" @@ -344,7 +345,7 @@ public: if (!textToInsert.isEmpty()) openEditor.editor()->insert(textToInsert); - proposal = completionResults(openEditor.editor(), includePaths, 15000); + proposal = completionResults(openEditor.editor(), includePaths, timeOutInMs()); } TextEditor::ProposalModelPtr proposal; @@ -657,7 +658,8 @@ void ClangCodeCompletionTest::testCompleteProjectDependingCode() OpenEditorAtCursorPosition openEditor(testDocument); QVERIFY(openEditor.succeeded()); - TextEditor::ProposalModelPtr proposal = completionResults(openEditor.editor()); + TextEditor::ProposalModelPtr proposal = completionResults(openEditor.editor(), {}, + timeOutInMs()); QVERIFY(hasItem(proposal, "projectConfiguration1")); } @@ -670,7 +672,8 @@ void ClangCodeCompletionTest::testCompleteProjectDependingCodeAfterChangingProje QVERIFY(openEditor.succeeded()); // Check completion without project - TextEditor::ProposalModelPtr proposal = completionResults(openEditor.editor()); + TextEditor::ProposalModelPtr proposal = completionResults(openEditor.editor(), {}, + timeOutInMs()); QVERIFY(hasItem(proposal, "noProjectConfigurationDetected")); { @@ -681,7 +684,7 @@ void ClangCodeCompletionTest::testCompleteProjectDependingCodeAfterChangingProje QVERIFY(projectLoader.load()); openEditor.waitUntilProjectPartChanged(QLatin1String("myproject.project")); - proposal = completionResults(openEditor.editor()); + proposal = completionResults(openEditor.editor(), {}, timeOutInMs()); QVERIFY(hasItem(proposal, "projectConfiguration1")); QVERIFY(!hasItem(proposal, "projectConfiguration2")); @@ -689,7 +692,7 @@ void ClangCodeCompletionTest::testCompleteProjectDependingCodeAfterChangingProje // Check completion with project configuration 2 QVERIFY(projectLoader.updateProject({{"PROJECT_CONFIGURATION_2"}})); openEditor.waitUntilBackendIsNotified(); - proposal = completionResults(openEditor.editor()); + proposal = completionResults(openEditor.editor(), {}, timeOutInMs()); QVERIFY(!hasItem(proposal, "projectConfiguration1")); QVERIFY(hasItem(proposal, "projectConfiguration2")); @@ -697,7 +700,7 @@ void ClangCodeCompletionTest::testCompleteProjectDependingCodeAfterChangingProje // Check again completion without project openEditor.waitUntilProjectPartChanged(QLatin1String("")); - proposal = completionResults(openEditor.editor()); + proposal = completionResults(openEditor.editor(), {}, timeOutInMs()); QVERIFY(hasItem(proposal, "noProjectConfigurationDetected")); } @@ -723,7 +726,8 @@ void ClangCodeCompletionTest::testCompleteProjectDependingCodeInGeneratedUiFile( QVERIFY(openSource.succeeded()); // ...and check comletions - TextEditor::ProposalModelPtr proposal = completionResults(openSource.editor()); + TextEditor::ProposalModelPtr proposal = completionResults(openSource.editor(), {}, + timeOutInMs()); QVERIFY(hasItem(proposal, "menuBar")); QVERIFY(hasItem(proposal, "statusBar")); QVERIFY(hasItem(proposal, "centralWidget")); diff --git a/src/plugins/clangpchmanager/clangindexingprojectsettingswidget.ui b/src/plugins/clangpchmanager/clangindexingprojectsettingswidget.ui index b10fd91beee..404d207d5ae 100644 --- a/src/plugins/clangpchmanager/clangindexingprojectsettingswidget.ui +++ b/src/plugins/clangpchmanager/clangindexingprojectsettingswidget.ui @@ -10,9 +10,6 @@ <height>300</height> </rect> </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> <layout class="QHBoxLayout" name="horizontalLayout"> diff --git a/src/plugins/clangtools/clangtoolsunittests.cpp b/src/plugins/clangtools/clangtoolsunittests.cpp index 1e76c577e13..512e6e71355 100644 --- a/src/plugins/clangtools/clangtoolsunittests.cpp +++ b/src/plugins/clangtools/clangtoolsunittests.cpp @@ -125,7 +125,7 @@ void ClangToolsUnitTests::testProject() ClangToolsSettings::instance()->runSettings(), diagnosticConfig); QSignalSpy waitForFinishedTool(tool, &ClangTool::finished); - QVERIFY(waitForFinishedTool.wait(90000)); + QVERIFY(waitForFinishedTool.wait(m_timeout)); // Check for errors const QString errorText = waitForFinishedTool.takeFirst().first().toString(); @@ -186,5 +186,11 @@ void ClangToolsUnitTests::addTestRow(const QByteArray &relativeFilePath, << absoluteFilePath << expectedDiagCount << diagnosticConfig; } +int ClangToolsUnitTests::getTimeout() +{ + const int t = qEnvironmentVariableIntValue("QTC_CLANGTOOLS_TEST_TIMEOUT"); + return t > 0 ? t : 480000; +} + } // namespace Internal } // namespace ClangTools diff --git a/src/plugins/clangtools/clangtoolsunittests.h b/src/plugins/clangtools/clangtoolsunittests.h index 87dd5a114e6..20da247894f 100644 --- a/src/plugins/clangtools/clangtoolsunittests.h +++ b/src/plugins/clangtools/clangtoolsunittests.h @@ -56,8 +56,11 @@ private: const CppTools::ClangDiagnosticConfig &diagnosticConfig); private: + static int getTimeout(); + CppTools::Tests::TemporaryCopiedDir *m_tmpDir = nullptr; ProjectExplorer::Kit *m_kit = nullptr; + int m_timeout = getTimeout(); }; } // namespace Internal diff --git a/src/plugins/clangtools/runsettingswidget.ui b/src/plugins/clangtools/runsettingswidget.ui index 370932c9a86..8dc283c8d0f 100644 --- a/src/plugins/clangtools/runsettingswidget.ui +++ b/src/plugins/clangtools/runsettingswidget.ui @@ -10,9 +10,6 @@ <height>125</height> </rect> </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QVBoxLayout" name="verticalLayout"> <property name="leftMargin"> <number>0</number> diff --git a/src/plugins/cppcheck/cppchecktool.cpp b/src/plugins/cppcheck/cppchecktool.cpp index 2c17247d0a9..91660020bbb 100644 --- a/src/plugins/cppcheck/cppchecktool.cpp +++ b/src/plugins/cppcheck/cppchecktool.cpp @@ -38,6 +38,7 @@ #include <utils/algorithm.h> #include <utils/macroexpander.h> #include <utils/qtcassert.h> +#include <utils/stringutils.h> #include <QThread> @@ -67,7 +68,7 @@ void CppcheckTool::updateOptions(const CppcheckOptions &options) if (trimmedPattern.isEmpty()) continue; - const QRegularExpression re(QRegularExpression::wildcardToRegularExpression(trimmedPattern)); + const QRegularExpression re(Utils::wildcardToRegularExpression(trimmedPattern)); if (re.isValid()) m_filters.push_back(re); } diff --git a/src/plugins/cppeditor/cppeditorplugin.h b/src/plugins/cppeditor/cppeditorplugin.h index 9adc6a9fc4d..a68c2edf72a 100644 --- a/src/plugins/cppeditor/cppeditorplugin.h +++ b/src/plugins/cppeditor/cppeditorplugin.h @@ -226,6 +226,8 @@ private slots: void test_quickfix_removeUsingNamespace_data(); void test_quickfix_removeUsingNamespace(); + void test_quickfix_removeUsingNamespace_simple_data(); + void test_quickfix_removeUsingNamespace_simple(); void test_quickfix_removeUsingNamespace_differentSymbols(); void test_quickfix_InsertVirtualMethods_data(); diff --git a/src/plugins/cppeditor/cppquickfix_test.cpp b/src/plugins/cppeditor/cppquickfix_test.cpp index 05b8e1b2d92..fc026a6c254 100644 --- a/src/plugins/cppeditor/cppquickfix_test.cpp +++ b/src/plugins/cppeditor/cppquickfix_test.cpp @@ -606,6 +606,32 @@ void CppEditorPlugin::test_quickfix_data() "" ); + // Checks: complete switch statement where enum is goes via a template type parameter + QTest::newRow("CompleteSwitchCaseStatement_QTCREATORBUG-24752") + << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( + "enum E {EA, EB};\n" + "template<typename T> struct S {\n" + " static T theType() { return T(); }\n" + "};\n" + "int main() {\n" + " @switch (S<E>::theType()) {\n" + " }\n" + "}\n" + ) << _( + "enum E {EA, EB};\n" + "template<typename T> struct S {\n" + " static T theType() { return T(); }\n" + "};\n" + "int main() {\n" + " switch (S<E>::theType()) {\n" + " case EA:\n" + " break;\n" + " case EB:\n" + " break;\n" + " }\n" + "}\n" + ); + // Checks: No special treatment for reference to non const. // Check: Quick fix is not triggered on a member function. @@ -7083,6 +7109,49 @@ void CppEditorPlugin::test_quickfix_removeUsingNamespace() QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation); } +void CppEditorPlugin::test_quickfix_removeUsingNamespace_simple_data() +{ + QTest::addColumn<QByteArray>("header"); + QTest::addColumn<QByteArray>("expected"); + + const QByteArray common = R"--( +namespace N{ + template<typename T> + struct vector{ + using iterator = T*; + }; + using int_vector = vector<int>; +} +)--"; + const QByteArray header = common + R"--( +using namespace N@; +int_vector ints; +int_vector::iterator intIter; +using vec = vector<int>; +vec::iterator it; +)--"; + const QByteArray expected = common + R"--( +N::int_vector ints; +N::int_vector::iterator intIter; +using vec = N::vector<int>; +vec::iterator it; +)--"; + + QTest::newRow("nested typedefs with Namespace") << header << expected; +} + +void CppEditorPlugin::test_quickfix_removeUsingNamespace_simple() +{ + QFETCH(QByteArray, header); + QFETCH(QByteArray, expected); + + QList<QuickFixTestDocument::Ptr> testDocuments; + testDocuments << QuickFixTestDocument::create("header.h", header, expected); + + RemoveUsingNamespace factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths()); +} + void CppEditorPlugin::test_quickfix_removeUsingNamespace_differentSymbols() { QByteArray header = "namespace test{\n" diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp index f5a89d4bb42..47a0575994e 100644 --- a/src/plugins/cppeditor/cppquickfixes.cpp +++ b/src/plugins/cppeditor/cppquickfixes.cpp @@ -2752,6 +2752,7 @@ Enum *conditionEnum(const CppQuickFixInterface &interface, SwitchStatementAST *s Block *block = statement->symbol; Scope *scope = interface.semanticInfo().doc->scopeAt(block->line(), block->column()); TypeOfExpression typeOfExpression; + typeOfExpression.setExpandTemplates(true); typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot()); const QList<LookupItem> results = typeOfExpression(statement->condition, interface.semanticInfo().doc, @@ -7795,6 +7796,33 @@ private: }; /** + * @brief getBaseName returns the base name of a qualified name or nullptr. + * E.g.: foo::bar => foo; bar => bar + * @param name The Name, maybe qualified + * @return The base name of the qualified name or nullptr + */ +const Identifier *getBaseName(const Name *name) +{ + class GetBaseName : public NameVisitor + { + void visit(const Identifier *name) override { baseName = name; } + void visit(const QualifiedNameId *name) override + { + if (name->base()) + accept(name->base()); + else + accept(name->name()); + } + + public: + const Identifier *baseName = nullptr; + }; + GetBaseName getter; + getter.accept(name); + return getter.baseName; +} + +/** * @brief countNames counts the parts of the Name. * E.g. if the name is std::vector, the function returns 2, if the name is variant, returns 1 * @param name The name that should be counted @@ -7993,11 +8021,24 @@ private: { if (m_start) { Scope *scope = m_file->scopeAt(ast->firstToken()); - const QList<LookupItem> lookups = m_context.lookup(ast->name->name, scope); + const Name *wantToLookup = ast->name->name; + // first check if the base name is a typedef. Consider the following example: + // using namespace std; + // using vec = std::vector<int>; + // vec::iterator it; // we have to lookup 'vec' and not iterator (would result in + // std::vector<int>::iterator => std::vec::iterator, which is wrong) + const Name *baseName = getBaseName(wantToLookup); + QList<LookupItem> typedefCandidates = m_context.lookup(baseName, scope); + if (!typedefCandidates.isEmpty()) { + if (typedefCandidates.front().declaration()->isTypedef()) + wantToLookup = baseName; + } + + const QList<LookupItem> lookups = m_context.lookup(wantToLookup, scope); if (!lookups.empty()) { QList<const Name *> fullName = m_context.fullyQualifiedName( lookups.first().declaration()); - const int currentNameCount = countNames(ast->name->name); + const int currentNameCount = countNames(wantToLookup); const bool needNamespace = needMissingNamespaces(std::move(fullName), currentNameCount); if (needNamespace) diff --git a/src/plugins/cpptools/cppmodelmanager_test.cpp b/src/plugins/cpptools/cppmodelmanager_test.cpp index a23c73f18af..78de5f1583e 100644 --- a/src/plugins/cpptools/cppmodelmanager_test.cpp +++ b/src/plugins/cpptools/cppmodelmanager_test.cpp @@ -1117,12 +1117,12 @@ void CppToolsPlugin::test_modelmanager_renameIncludesInEditor() const MyTestDataDir testDir2(_("testdata_project2")); QFile foobar2000Header(testDir2.file("foobar2000.h")); - QVERIFY(foobar2000Header.open(QFile::ReadOnly)); + QVERIFY(foobar2000Header.open(QFile::ReadOnly | QFile::Text)); const auto foobar2000HeaderContents = foobar2000Header.readAll(); foobar2000Header.close(); QFile renamedHeader(renamedHeaderWithNormalGuard); - QVERIFY(renamedHeader.open(QFile::ReadOnly)); + QVERIFY(renamedHeader.open(QFile::ReadOnly | QFile::Text)); auto renamedHeaderContents = renamedHeader.readAll(); renamedHeader.close(); QCOMPARE(renamedHeaderContents, foobar2000HeaderContents); @@ -1133,12 +1133,12 @@ void CppToolsPlugin::test_modelmanager_renameIncludesInEditor() Core::HandleIncludeGuards::Yes)); QFile foobar4000Header(testDir2.file("foobar4000.h")); - QVERIFY(foobar4000Header.open(QFile::ReadOnly)); + QVERIFY(foobar4000Header.open(QFile::ReadOnly | QFile::Text)); const auto foobar4000HeaderContents = foobar4000Header.readAll(); foobar4000Header.close(); renamedHeader.setFileName(renamedHeaderWithUnderscoredGuard); - QVERIFY(renamedHeader.open(QFile::ReadOnly)); + QVERIFY(renamedHeader.open(QFile::ReadOnly | QFile::Text)); renamedHeaderContents = renamedHeader.readAll(); renamedHeader.close(); QCOMPARE(renamedHeaderContents, foobar4000HeaderContents); @@ -1146,7 +1146,7 @@ void CppToolsPlugin::test_modelmanager_renameIncludesInEditor() // test the renaming of a header with a malformed guard to verify we do not make // accidental refactors renamedHeader.setFileName(headerWithMalformedGuard); - QVERIFY(renamedHeader.open(QFile::ReadOnly)); + QVERIFY(renamedHeader.open(QFile::ReadOnly | QFile::Text)); auto originalMalformedGuardContents = renamedHeader.readAll(); renamedHeader.close(); @@ -1154,7 +1154,7 @@ void CppToolsPlugin::test_modelmanager_renameIncludesInEditor() Core::HandleIncludeGuards::Yes)); renamedHeader.setFileName(renamedHeaderWithMalformedGuard); - QVERIFY(renamedHeader.open(QFile::ReadOnly)); + QVERIFY(renamedHeader.open(QFile::ReadOnly | QFile::Text)); renamedHeaderContents = renamedHeader.readAll(); renamedHeader.close(); QCOMPARE(renamedHeaderContents, originalMalformedGuardContents); diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index e538aa65056..3e76d5a782c 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -4166,7 +4166,7 @@ void GdbEngine::setupInferior() } if (!symbolFile.isEmpty()) { - runCommand({"-file-exec-and-symbols \"" + symbolFile + '"', + runCommand({"-file-symbol-file \"" + symbolFile + '"', CB(handleFileExecAndSymbols)}); } diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp index 593f16fc4d7..09053e77ba4 100644 --- a/src/plugins/git/gitclient.cpp +++ b/src/plugins/git/gitclient.cpp @@ -2951,14 +2951,12 @@ bool GitClient::addAndCommit(const QString &repositoryDirectory, const SynchronousProcessResponse resp = vcsSynchronousExec(repositoryDirectory, arguments, VcsCommand::NoFullySync); - const QString stdErr = resp.stdErr(); if (resp.result == SynchronousProcessResponse::Finished) { VcsOutputWindow::appendMessage(msgCommitted(amendSHA1, commitCount)); - VcsOutputWindow::appendError(stdErr); GitPlugin::updateCurrentBranch(); return true; } else { - VcsOutputWindow::appendError(tr("Cannot commit %n files: %1\n", nullptr, commitCount).arg(stdErr)); + VcsOutputWindow::appendError(tr("Cannot commit %n files\n", nullptr, commitCount)); return false; } } diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index fd39b03d374..c238ba454a3 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -325,10 +325,9 @@ void Client::sendContent(const IContent &content) QString error; if (!QTC_GUARD(content.isValid(&error))) Core::MessageManager::write(error); - LanguageClientManager::logBaseMessage(LspLogMessage::ClientMessage, - name(), - content.toBaseMessage()); - m_clientInterface->sendMessage(content.toBaseMessage()); + const BaseMessage message = content.toBaseMessage(); + LanguageClientManager::logBaseMessage(LspLogMessage::ClientMessage, name(), message); + m_clientInterface->sendMessage(message); } void Client::sendContent(const DocumentUri &uri, const IContent &content) diff --git a/src/plugins/mesonprojectmanager/project/buildoptions/mesonbuildsettingswidget.ui b/src/plugins/mesonprojectmanager/project/buildoptions/mesonbuildsettingswidget.ui index 6efac5a04f2..bedfa074fed 100644 --- a/src/plugins/mesonprojectmanager/project/buildoptions/mesonbuildsettingswidget.ui +++ b/src/plugins/mesonprojectmanager/project/buildoptions/mesonbuildsettingswidget.ui @@ -10,9 +10,6 @@ <height>300</height> </rect> </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QGridLayout" name="gridLayout"> <property name="leftMargin"> <number>0</number> diff --git a/src/plugins/mesonprojectmanager/settings/general/generalsettingswidget.ui b/src/plugins/mesonprojectmanager/settings/general/generalsettingswidget.ui index 6323bf302e6..78a3c14d771 100644 --- a/src/plugins/mesonprojectmanager/settings/general/generalsettingswidget.ui +++ b/src/plugins/mesonprojectmanager/settings/general/generalsettingswidget.ui @@ -10,9 +10,6 @@ <height>349</height> </rect> </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QWidget" name="widget" native="true"> diff --git a/src/plugins/mesonprojectmanager/settings/tools/toolitemsettings.ui b/src/plugins/mesonprojectmanager/settings/tools/toolitemsettings.ui index 5e1b2a59539..bedf7296c2b 100644 --- a/src/plugins/mesonprojectmanager/settings/tools/toolitemsettings.ui +++ b/src/plugins/mesonprojectmanager/settings/tools/toolitemsettings.ui @@ -10,9 +10,6 @@ <height>70</height> </rect> </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QFormLayout" name="formLayout"> <item row="0" column="0"> <widget class="QLabel" name="_nameLbl"> diff --git a/src/plugins/mesonprojectmanager/settings/tools/toolssettingswidget.ui b/src/plugins/mesonprojectmanager/settings/tools/toolssettingswidget.ui index ed6409ab138..137bfced8f3 100644 --- a/src/plugins/mesonprojectmanager/settings/tools/toolssettingswidget.ui +++ b/src/plugins/mesonprojectmanager/settings/tools/toolssettingswidget.ui @@ -10,9 +10,6 @@ <height>349</height> </rect> </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <layout class="QVBoxLayout" name="verticalLayout"> diff --git a/src/plugins/nim/project/nimbletaskstep.cpp b/src/plugins/nim/project/nimbletaskstep.cpp index 1cb0505089a..f41d53da909 100644 --- a/src/plugins/nim/project/nimbletaskstep.cpp +++ b/src/plugins/nim/project/nimbletaskstep.cpp @@ -124,9 +124,6 @@ QWidget *NimbleTaskStep::createConfigWidget() connect(buildSystem, &NimbleBuildSystem::tasksChanged, this, &NimbleTaskStep::updateTaskList); - connect(m_taskName, &StringAspect::changed, this, &BuildStep::recreateSummary); - connect(m_taskArgs, &StringAspect::changed, this, &BuildStep::recreateSummary); - setSummaryUpdater([this] { return QString("<b>%1:</b> nimble %2 %3") .arg(displayName(), m_taskName->value(), m_taskArgs->value()); diff --git a/src/plugins/projectexplorer/buildmanager.cpp b/src/plugins/projectexplorer/buildmanager.cpp index 16ae265bca4..9b257c00f0e 100644 --- a/src/plugins/projectexplorer/buildmanager.cpp +++ b/src/plugins/projectexplorer/buildmanager.cpp @@ -477,6 +477,7 @@ void BuildManager::finish() { const QString elapsedTime = Utils::formatElapsedTime(d->m_elapsed.elapsed()); m_instance->addToOutputWindow(elapsedTime, BuildStep::OutputFormat::NormalMessage); + d->m_outputWindow->flush(); QApplication::alert(ICore::dialogParent(), 3000); } @@ -696,7 +697,7 @@ void BuildManager::nextStep() } static const auto finishedHandler = [](bool success) { - d->m_outputWindow->outputFormatter()->flush(); + d->m_outputWindow->flush(); d->m_lastStepSucceeded = success; disconnect(d->m_currentBuildStep, nullptr, instance(), nullptr); BuildManager::nextBuildQueue(); diff --git a/src/plugins/projectexplorer/buildpropertiessettings.h b/src/plugins/projectexplorer/buildpropertiessettings.h index 17432b45e0d..63c289b5fb2 100644 --- a/src/plugins/projectexplorer/buildpropertiessettings.h +++ b/src/plugins/projectexplorer/buildpropertiessettings.h @@ -35,6 +35,7 @@ class PROJECTEXPLORER_EXPORT BuildPropertiesSettings { public: QString buildDirectoryTemplate; + QString buildDirectoryTemplateOld; // TODO: Remove in ~4.16 Utils::TriState separateDebugInfo; Utils::TriState qmlDebugging; Utils::TriState qtQuickCompiler; diff --git a/src/plugins/projectexplorer/buildstep.cpp b/src/plugins/projectexplorer/buildstep.cpp index d5aaf6ea783..6e20887d701 100644 --- a/src/plugins/projectexplorer/buildstep.cpp +++ b/src/plugins/projectexplorer/buildstep.cpp @@ -157,6 +157,26 @@ void BuildStep::cancel() doCancel(); } +QWidget *BuildStep::doCreateConfigWidget() +{ + QWidget *widget = createConfigWidget(); + + const auto recreateSummary = [this] { + if (m_summaryUpdater) + setSummaryText(m_summaryUpdater()); + }; + + for (BaseAspect *aspect : qAsConst(m_aspects)) + connect(aspect, &BaseAspect::changed, widget, recreateSummary); + + connect(buildConfiguration(), &BuildConfiguration::buildDirectoryChanged, + widget, recreateSummary); + + recreateSummary(); + + return widget; +} + QWidget *BuildStep::createConfigWidget() { auto widget = new QWidget; @@ -165,12 +185,8 @@ QWidget *BuildStep::createConfigWidget() for (BaseAspect *aspect : qAsConst(m_aspects)) { if (aspect->isVisible()) aspect->addToLayout(builder.finishRow()); - connect(aspect, &BaseAspect::changed, this, &BuildStep::recreateSummary); } - connect(buildConfiguration(), &BuildConfiguration::buildDirectoryChanged, - this, &BuildStep::recreateSummary); - if (m_addMacroExpander) VariableChooser::addSupportForChildWidgets(widget, macroExpander()); @@ -500,13 +516,6 @@ void BuildStep::setSummaryText(const QString &summaryText) void BuildStep::setSummaryUpdater(const std::function<QString()> &summaryUpdater) { m_summaryUpdater = summaryUpdater; - recreateSummary(); -} - -void BuildStep::recreateSummary() -{ - if (m_summaryUpdater) - setSummaryText(m_summaryUpdater()); } } // ProjectExplorer diff --git a/src/plugins/projectexplorer/buildstep.h b/src/plugins/projectexplorer/buildstep.h index 6fda3e7b035..78d5331034c 100644 --- a/src/plugins/projectexplorer/buildstep.h +++ b/src/plugins/projectexplorer/buildstep.h @@ -70,7 +70,6 @@ public: virtual bool init() = 0; void run(); void cancel(); - virtual QWidget *createConfigWidget(); bool fromMap(const QVariantMap &map) override; QVariantMap toMap() const override; @@ -120,7 +119,7 @@ public: QString summaryText() const; void setSummaryText(const QString &summaryText); - void recreateSummary(); + QWidget *doCreateConfigWidget(); signals: void updateSummary(); @@ -141,6 +140,8 @@ signals: void finished(bool result); protected: + virtual QWidget *createConfigWidget(); + void runInThread(const std::function<bool()> &syncImpl); std::function<bool()> cancelChecker() const; diff --git a/src/plugins/projectexplorer/buildstepspage.cpp b/src/plugins/projectexplorer/buildstepspage.cpp index cc642d0d934..f23434d6acf 100644 --- a/src/plugins/projectexplorer/buildstepspage.cpp +++ b/src/plugins/projectexplorer/buildstepspage.cpp @@ -170,7 +170,7 @@ void ToolWidget::setDownVisible(bool b) BuildStepsWidgetData::BuildStepsWidgetData(BuildStep *s) : step(s), widget(nullptr), detailsWidget(nullptr) { - widget = s->createConfigWidget(); + widget = s->doCreateConfigWidget(); Q_ASSERT(widget); detailsWidget = new DetailsWidget; diff --git a/src/plugins/projectexplorer/makestep.cpp b/src/plugins/projectexplorer/makestep.cpp index 86c1fe74a82..c411ac34c9b 100644 --- a/src/plugins/projectexplorer/makestep.cpp +++ b/src/plugins/projectexplorer/makestep.cpp @@ -416,8 +416,6 @@ QWidget *MakeStep::createConfigWidget() m_nonOverrideWarning->setVisible(makeflagsJobCountMismatch() && !jobCountOverridesMakeflags()); disableInSubDirsCheckBox->setChecked(!m_enabledForSubDirs); - - recreateSummary(); }; updateDetails(); diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp index 344f9c1766b..b10a4929706 100644 --- a/src/plugins/projectexplorer/projectexplorer.cpp +++ b/src/plugins/projectexplorer/projectexplorer.cpp @@ -259,7 +259,8 @@ const char PROJECT_OPEN_LOCATIONS_CONTEXT_MENU[] = "Project.P.OpenLocation.CtxM // Default directories: const char DEFAULT_BUILD_DIRECTORY_TEMPLATE[] = "../%{JS: Util.asciify(\"build-%{Project:Name}-%{Kit:FileSystemName}-%{BuildConfig:Name}\")}"; -const char DEFAULT_BUILD_DIRECTORY_TEMPLATE_KEY[] = "Directories/BuildDirectory.Template"; +const char DEFAULT_BUILD_DIRECTORY_TEMPLATE_KEY_OLD[] = "Directories/BuildDirectory.Template"; // TODO: Remove in ~4.16 +const char DEFAULT_BUILD_DIRECTORY_TEMPLATE_KEY[] = "Directories/BuildDirectory.TemplateV2"; const char BUILD_BEFORE_DEPLOY_SETTINGS_KEY[] = "ProjectExplorer/Settings/BuildBeforeDeploy"; const char DEPLOY_BEFORE_RUN_SETTINGS_KEY[] = "ProjectExplorer/Settings/DeployBeforeRun"; @@ -1532,8 +1533,14 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er dd->m_projectExplorerSettings.lowBuildPriority = s->value(Constants::LOW_BUILD_PRIORITY_SETTINGS_KEY, false).toBool(); + dd->m_buildPropertiesSettings.buildDirectoryTemplateOld + = s->value(Constants::DEFAULT_BUILD_DIRECTORY_TEMPLATE_KEY_OLD).toString(); dd->m_buildPropertiesSettings.buildDirectoryTemplate = s->value(Constants::DEFAULT_BUILD_DIRECTORY_TEMPLATE_KEY).toString(); + if (dd->m_buildPropertiesSettings.buildDirectoryTemplate.isEmpty()) { + dd->m_buildPropertiesSettings.buildDirectoryTemplate + = dd->m_buildPropertiesSettings.buildDirectoryTemplateOld; + } if (dd->m_buildPropertiesSettings.buildDirectoryTemplate.isEmpty()) dd->m_buildPropertiesSettings.buildDirectoryTemplate = Constants::DEFAULT_BUILD_DIRECTORY_TEMPLATE; // TODO: Remove in ~4.16 @@ -2110,6 +2117,8 @@ void ProjectExplorerPluginPrivate::savePersistentSettings() s->setValue(Constants::STOP_BEFORE_BUILD_SETTINGS_KEY, int(dd->m_projectExplorerSettings.stopBeforeBuild)); // Store this in the Core directory scope for backward compatibility! + s->setValue(Constants::DEFAULT_BUILD_DIRECTORY_TEMPLATE_KEY_OLD, + dd->m_buildPropertiesSettings.buildDirectoryTemplateOld); s->setValue(Constants::DEFAULT_BUILD_DIRECTORY_TEMPLATE_KEY, dd->m_buildPropertiesSettings.buildDirectoryTemplate); diff --git a/src/plugins/projectexplorer/target.h b/src/plugins/projectexplorer/target.h index 8de1d9168db..ce8440fa38a 100644 --- a/src/plugins/projectexplorer/target.h +++ b/src/plugins/projectexplorer/target.h @@ -53,9 +53,11 @@ class PROJECTEXPLORER_EXPORT Target : public QObject friend class SessionManager; // for setActiveBuild and setActiveDeployConfiguration Q_OBJECT - struct _constructor_tag { explicit _constructor_tag() = default; }; - public: + struct _constructor_tag + { + explicit _constructor_tag() = default; + }; Target(Project *parent, Kit *k, _constructor_tag); ~Target() override; diff --git a/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp b/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp index 3d3ec6af01d..64a12ab081e 100644 --- a/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp +++ b/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp @@ -75,6 +75,17 @@ using namespace QmakeProjectManager::Internal; namespace QmakeProjectManager { +class RunSystemAspect : public TriStateAspect +{ + Q_OBJECT +public: + RunSystemAspect() : TriStateAspect(tr("Run"), tr("Ignore"), tr("Use global setting")) + { + setSettingsKey("RunSystemFunction"); + setDisplayName(tr("qmake system() behavior when parsing:")); + } +}; + QmakeExtraBuildInfo::QmakeExtraBuildInfo() { const BuildPropertiesSettings &settings = ProjectExplorerPlugin::buildPropertiesSettings(); @@ -198,6 +209,8 @@ QmakeBuildConfiguration::QmakeBuildConfiguration(Target *target, Utils::Id id) emit qmakeBuildConfigurationChanged(); qmakeBuildSystem()->scheduleUpdateAllNowOrLater(); }); + + addAspect<RunSystemAspect>(); } QmakeBuildConfiguration::~QmakeBuildConfiguration() @@ -439,6 +452,17 @@ void QmakeBuildConfiguration::forceQtQuickCompiler(bool enable) aspect<QtQuickCompilerAspect>()->setSetting(enable ? TriState::Enabled : TriState::Disabled); } +bool QmakeBuildConfiguration::runSystemFunction() const +{ + switch (aspect<RunSystemAspect>()->value()) { + case 0: + return true; + case 1: + return false; + } + return QmakeSettings::runSystemFunction(); +} + QStringList QmakeBuildConfiguration::configCommandLineArguments() const { QStringList result; @@ -875,3 +899,5 @@ void QmakeBuildConfiguration::restrictNextBuild(const RunConfiguration *rc) } } // namespace QmakeProjectManager + +#include <qmakebuildconfiguration.moc> diff --git a/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.h b/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.h index 71f88256a9c..19fbf519a55 100644 --- a/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.h +++ b/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.h @@ -106,6 +106,8 @@ public: Utils::TriState useQtQuickCompiler() const; void forceQtQuickCompiler(bool enable); + bool runSystemFunction() const; + signals: /// emitted for setQMakeBuildConfig, not emitted for Qt version changes, even /// if those change the qmakebuildconfig diff --git a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp index 3a564fd20ba..0babec02e80 100644 --- a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp @@ -1839,15 +1839,24 @@ QStringList QmakeProFile::includePaths(QtSupport::ProFileReader *reader, const F } bool tryUnfixified = false; + + // These paths should not be checked for existence, to ensure consistent include path lists + // before and after building. + const QString mocDir = mocDirPath(reader, buildDir); + const QString uiDir = uiDirPath(reader, buildDir); + foreach (const ProFileEvaluator::SourceFile &el, reader->fixifiedValues(QLatin1String("INCLUDEPATH"), projectDir, buildDir.toString(), false)) { const QString sysrootifiedPath = sysrootify(el.fileName, sysroot.toString(), projectDir, buildDir.toString()); - if (IoUtils::isAbsolutePath(sysrootifiedPath) && IoUtils::exists(sysrootifiedPath)) + if (IoUtils::isAbsolutePath(sysrootifiedPath) + && (IoUtils::exists(sysrootifiedPath) || sysrootifiedPath == mocDir + || sysrootifiedPath == uiDir)) { paths << sysrootifiedPath; - else + } else { tryUnfixified = true; + } } // If sysrootifying a fixified path does not yield a valid path, try again with the @@ -1862,10 +1871,6 @@ QStringList QmakeProFile::includePaths(QtSupport::ProFileReader *reader, const F } } - // paths already contains moc dir and ui dir, due to corrrectly parsing uic.prf and moc.prf - // except if those directories don't exist at the time of parsing - // thus we add those directories manually (without checking for existence) - paths << mocDirPath(reader, buildDir) << uiDirPath(reader, buildDir); paths.removeDuplicates(); return paths; } diff --git a/src/plugins/qmakeprojectmanager/qmakeproject.cpp b/src/plugins/qmakeprojectmanager/qmakeproject.cpp index da0df14b706..da6dcd0eb62 100644 --- a/src/plugins/qmakeprojectmanager/qmakeproject.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeproject.cpp @@ -797,6 +797,7 @@ QtSupport::ProFileReader *QmakeBuildSystem::createProFileReader(const QmakeProFi m_qmakeGlobals->environment.insert(env.key(eit), env.expandedValueForKey(env.key(eit))); m_qmakeGlobals->setCommandLineArguments(buildDir(rootProFile()->filePath()).toString(), qmakeArgs); + m_qmakeGlobals->runSystemFunction = bc->runSystemFunction(); QtSupport::ProFileCacheManager::instance()->incRefCount(); diff --git a/src/plugins/qmakeprojectmanager/qmakesettings.cpp b/src/plugins/qmakeprojectmanager/qmakesettings.cpp index dd2fdb4e5d0..b0edf3a404d 100644 --- a/src/plugins/qmakeprojectmanager/qmakesettings.cpp +++ b/src/plugins/qmakeprojectmanager/qmakesettings.cpp @@ -38,11 +38,13 @@ namespace Internal { const char BUILD_DIR_WARNING_KEY[] = "QmakeProjectManager/WarnAgainstUnalignedBuildDir"; const char ALWAYS_RUN_QMAKE_KEY[] = "QmakeProjectManager/AlwaysRunQmake"; +const char RUN_SYSTEM_KEY[] = "QmakeProjectManager/RunSystemFunction"; static bool operator==(const QmakeSettingsData &s1, const QmakeSettingsData &s2) { return s1.warnAgainstUnalignedBuildDir == s2.warnAgainstUnalignedBuildDir - && s1.alwaysRunQmake == s2.alwaysRunQmake; + && s1.alwaysRunQmake == s2.alwaysRunQmake + && s1.runSystemFunction == s2.runSystemFunction; } static bool operator!=(const QmakeSettingsData &s1, const QmakeSettingsData &s2) { @@ -59,6 +61,11 @@ bool QmakeSettings::alwaysRunQmake() return instance().m_settings.alwaysRunQmake; } +bool QmakeSettings::runSystemFunction() +{ + return instance().m_settings.runSystemFunction; +} + QmakeSettings &QmakeSettings::instance() { static QmakeSettings theSettings; @@ -85,6 +92,7 @@ void QmakeSettings::loadSettings() m_settings.warnAgainstUnalignedBuildDir = s->value( BUILD_DIR_WARNING_KEY, Utils::HostOsInfo::isWindowsHost()).toBool(); m_settings.alwaysRunQmake = s->value(ALWAYS_RUN_QMAKE_KEY, false).toBool(); + m_settings.runSystemFunction = s->value(RUN_SYSTEM_KEY, false).toBool(); } void QmakeSettings::storeSettings() const @@ -92,6 +100,7 @@ void QmakeSettings::storeSettings() const QSettings * const s = Core::ICore::settings(); s->setValue(BUILD_DIR_WARNING_KEY, warnAgainstUnalignedBuildDir()); s->setValue(ALWAYS_RUN_QMAKE_KEY, alwaysRunQmake()); + s->setValue(RUN_SYSTEM_KEY, runSystemFunction()); } class QmakeSettingsPage::SettingsWidget : public QWidget @@ -110,9 +119,15 @@ public: m_alwaysRunQmakeCheckbox.setToolTip(tr("This option can help to prevent failures on " "incremental builds, but might slow them down unnecessarily in the general case.")); m_alwaysRunQmakeCheckbox.setChecked(QmakeSettings::alwaysRunQmake()); + m_ignoreSystemCheckbox.setText(tr("Ignore qmake's system() function " + "when parsing a project")); + m_ignoreSystemCheckbox.setToolTip(tr("Unchecking this option can help getting more exact " + "parsing results, but can have unwanted side effects.")); + m_ignoreSystemCheckbox.setChecked(!QmakeSettings::runSystemFunction()); const auto layout = new QVBoxLayout(this); layout->addWidget(&m_warnAgainstUnalignedBuildDirCheckbox); layout->addWidget(&m_alwaysRunQmakeCheckbox); + layout->addWidget(&m_ignoreSystemCheckbox); layout->addStretch(1); } @@ -121,12 +136,14 @@ public: QmakeSettingsData settings; settings.warnAgainstUnalignedBuildDir = m_warnAgainstUnalignedBuildDirCheckbox.isChecked(); settings.alwaysRunQmake = m_alwaysRunQmakeCheckbox.isChecked(); + settings.runSystemFunction = !m_ignoreSystemCheckbox.isChecked(); QmakeSettings::setSettingsData(settings); } private: QCheckBox m_warnAgainstUnalignedBuildDirCheckbox; QCheckBox m_alwaysRunQmakeCheckbox; + QCheckBox m_ignoreSystemCheckbox; }; QmakeSettingsPage::QmakeSettingsPage() diff --git a/src/plugins/qmakeprojectmanager/qmakesettings.h b/src/plugins/qmakeprojectmanager/qmakesettings.h index b2b1a75a9da..0225a00bd7d 100644 --- a/src/plugins/qmakeprojectmanager/qmakesettings.h +++ b/src/plugins/qmakeprojectmanager/qmakesettings.h @@ -37,6 +37,7 @@ class QmakeSettingsData { public: bool warnAgainstUnalignedBuildDir = false; bool alwaysRunQmake = false; + bool runSystemFunction = false; }; class QmakeSettings : public QObject @@ -46,6 +47,7 @@ public: static QmakeSettings &instance(); static bool warnAgainstUnalignedBuildDir(); static bool alwaysRunQmake(); + static bool runSystemFunction(); static void setSettingsData(const QmakeSettingsData &settings); signals: diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 11be30c80e1..ca98f2c4242 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -6,7 +6,7 @@ endif() add_qtc_plugin(QmlDesigner DEPENDS QmlJS LanguageUtils QmlEditorWidgets AdvancedDockingSystem - Qt5::QuickWidgets Qt5::CorePrivate + Qt5::QuickWidgets Qt5::CorePrivate Sqlite DEFINES DESIGNER_CORE_LIBRARY IDE_LIBRARY_BASENAME=\"${IDE_LIBRARY_BASE_PATH}\" @@ -208,6 +208,7 @@ extend_qtc_plugin(QmlDesigner modelnodecontextmenu.cpp modelnodecontextmenu.h modelnodecontextmenu_helper.cpp modelnodecontextmenu_helper.h modelnodeoperations.cpp modelnodeoperations.h + navigation2d.cpp navigation2d.h qmldesignericonprovider.cpp qmldesignericonprovider.h selectioncontext.cpp selectioncontext.h theme.cpp theme.h @@ -316,6 +317,7 @@ extend_qtc_plugin(QmlDesigner itemlibraryassetimportdialog.cpp itemlibraryassetimportdialog.h itemlibraryassetimportdialog.ui itemlibraryassetimporter.cpp itemlibraryassetimporter.h + itemlibraryiconimageprovider.cpp itemlibraryiconimageprovider.h ) find_package(Qt5 COMPONENTS Quick3DAssetImport QUIET) @@ -501,6 +503,7 @@ extend_qtc_plugin(QmlDesigner include/textmodifier.h include/variantproperty.h include/viewmanager.h + include/imagecache.h ) extend_qtc_plugin(QmlDesigner @@ -585,6 +588,21 @@ extend_qtc_plugin(QmlDesigner pluginmanager/widgetpluginmanager.cpp pluginmanager/widgetpluginmanager.h pluginmanager/widgetpluginpath.cpp pluginmanager/widgetpluginpath.h rewritertransaction.cpp rewritertransaction.h + + imagecache/imagecachecollector.h + imagecache/imagecachecollector.cpp + imagecache/imagecache.cpp + imagecache/imagecachecollectorinterface.h + imagecache/imagecacheconnectionmanager.cpp + imagecache/imagecacheconnectionmanager.h + imagecache/imagecachegenerator.cpp + imagecache/imagecachegenerator.h + imagecache/imagecachestorage.h + imagecache/imagecachegeneratorinterface.h + imagecache/imagecachestorageinterface.h + imagecache/timestampproviderinterface.h + imagecache/timestampprovider.h + imagecache/timestampprovider.cpp ) extend_qtc_plugin(QmlDesigner @@ -645,6 +663,16 @@ extend_qtc_plugin(QmlDesigner ) extend_qtc_plugin(QmlDesigner + SOURCES_PREFIX components/previewtooltip + SOURCES + previewimagetooltip.cpp + previewimagetooltip.h + previewimagetooltip.ui + previewtooltipbackend.cpp + previewtooltipbackend.h +) + +extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/richtexteditor SOURCES hyperlinkdialog.cpp hyperlinkdialog.h hyperlinkdialog.ui diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp index 5c46f76f05a..c04a6661656 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp @@ -28,12 +28,12 @@ #include "assetexportpluginconstants.h" #include "filepathmodel.h" -#include "coreplugin/fileutils.h" -#include "coreplugin/icore.h" -#include "projectexplorer/task.h" -#include "projectexplorer/taskhub.h" -#include "utils/fileutils.h" -#include "utils/outputformatter.h" +#include <coreplugin/fileutils.h> +#include <coreplugin/icore.h> +#include <projectexplorer/task.h> +#include <projectexplorer/taskhub.h> +#include <utils/fileutils.h> +#include <utils/outputformatter.h> #include <QCheckBox> #include <QPushButton> diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.cpp b/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.cpp index 4f958d59f6e..c7e1ab192c2 100644 --- a/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.cpp +++ b/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.cpp @@ -28,18 +28,34 @@ #include "richtexteditor/richtexteditor.h" +#include "QStringListModel" + namespace QmlDesigner { -AnnotationCommentTab::AnnotationCommentTab(QWidget *parent) : - QWidget(parent), - ui(new Ui::AnnotationCommentTab) +AnnotationCommentTab::AnnotationCommentTab(QWidget *parent) + : QWidget(parent) + , ui(new Ui::AnnotationCommentTab) { ui->setupUi(this); m_editor = new RichTextEditor; ui->formLayout->setWidget(3, QFormLayout::FieldRole, m_editor); - connect(ui->titleEdit, &QLineEdit::textEdited, + ui->titleEdit->setModel(new QStringListModel{QStringList{"Description", + "Display Condition", + "helper_lines" + "highlight" + "project author", + "project confirmed", + "project developer", + "project distributor", + "project modified", + "project type" + "project version", + "Screen Description" + "Section"}}); + + connect(ui->titleEdit, &QComboBox::currentTextChanged, this, &AnnotationCommentTab::commentTitleChanged); } @@ -52,7 +68,7 @@ Comment AnnotationCommentTab::currentComment() const { Comment result; - result.setTitle(ui->titleEdit->text().trimmed()); + result.setTitle(ui->titleEdit->currentText().trimmed()); result.setAuthor(ui->authorEdit->text().trimmed()); result.setText(m_editor->richText().trimmed()); @@ -77,7 +93,7 @@ void AnnotationCommentTab::setComment(const Comment &comment) void AnnotationCommentTab::resetUI() { - ui->titleEdit->setText(m_comment.title()); + ui->titleEdit->setCurrentText(m_comment.title()); ui->authorEdit->setText(m_comment.author()); m_editor->setRichText(m_comment.deescapedText()); diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.ui b/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.ui index 4a69703d57d..b3a9a85e6c9 100644 --- a/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.ui +++ b/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.ui @@ -24,7 +24,11 @@ </widget> </item> <item row="1" column="1"> - <widget class="QLineEdit" name="titleEdit"/> + <widget class="QComboBox" name="titleEdit"> + <property name="editable"> + <bool>true</bool> + </property> + </widget> </item> <item row="3" column="0"> <widget class="QLabel" name="textLabel"> diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore.pri b/src/plugins/qmldesigner/components/componentcore/componentcore.pri index 14337508ec9..decf24a5a83 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore.pri +++ b/src/plugins/qmldesigner/components/componentcore/componentcore.pri @@ -14,6 +14,7 @@ SOURCES += modelnodecontextmenu_helper.cpp SOURCES += selectioncontext.cpp SOURCES += designeractionmanager.cpp SOURCES += modelnodeoperations.cpp +SOURCES += navigation2d.cpp SOURCES += crumblebar.cpp SOURCES += qmldesignericonprovider.cpp SOURCES += zoomaction.cpp @@ -33,6 +34,7 @@ HEADERS += selectioncontext.h HEADERS += componentcore_constants.h HEADERS += designeractionmanager.h HEADERS += modelnodeoperations.h +HEADERS += navigation2d.h HEADERS += actioninterface.h HEADERS += crumblebar.h HEADERS += qmldesignericonprovider.h diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index f7487d8a3ff..ea34b091cd4 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -347,25 +347,27 @@ public: && !selectionContext().currentSingleSelectedNode().isRootNode() && selectionContext().currentSingleSelectedNode().hasParentProperty()) { - ActionTemplate *selectionAction = new ActionTemplate(QString(), &ModelNodeOperations::select); - selectionAction->setParent(menu()); - parentNode = selectionContext().currentSingleSelectedNode().parentProperty().parentModelNode(); - selectionAction->setText(QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Select parent: %1")).arg( - captionForModelNode(parentNode))); + if (!ModelNode::isThisOrAncestorLocked(parentNode)) { + ActionTemplate *selectionAction = new ActionTemplate(QString(), &ModelNodeOperations::select); + selectionAction->setParent(menu()); + selectionAction->setText(QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Select parent: %1")).arg( + captionForModelNode(parentNode))); - SelectionContext nodeSelectionContext = selectionContext(); - nodeSelectionContext.setTargetNode(parentNode); - selectionAction->setSelectionContext(nodeSelectionContext); + SelectionContext nodeSelectionContext = selectionContext(); + nodeSelectionContext.setTargetNode(parentNode); + selectionAction->setSelectionContext(nodeSelectionContext); - menu()->addAction(selectionAction); + menu()->addAction(selectionAction); + } } - foreach (const ModelNode &node, selectionContext().view()->allModelNodes()) { + for (const ModelNode &node : selectionContext().view()->allModelNodes()) { if (node != selectionContext().currentSingleSelectedNode() && node != parentNode && contains(node, selectionContext().scenePosition()) - && !node.isRootNode()) { + && !node.isRootNode() + && !ModelNode::isThisOrAncestorLocked(node)) { selectionContext().setTargetNode(node); QString what = QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Select: %1")).arg(captionForModelNode(node)); ActionTemplate *selectionAction = new ActionTemplate(what, &ModelNodeOperations::select); @@ -377,6 +379,9 @@ public: menu()->addAction(selectionAction); } } + + if (menu()->isEmpty()) + action()->setEnabled(false); } } }; @@ -574,11 +579,6 @@ bool multiSelection(const SelectionContext &context) return !singleSelection(context) && selectionNotEmpty(context); } -bool singleSelectionAndInBaseState(const SelectionContext &context) -{ - return singleSelection(context) && inBaseState(context); -} - bool multiSelectionAndInBaseState(const SelectionContext &context) { return multiSelection(context) && inBaseState(context); @@ -828,6 +828,11 @@ bool studioComponentsAvailable(const SelectionContext &context) return context.view()->model()->isImportPossible(import, true, true); } +bool studioComponentsAvailableAndSelectionCanBeLayouted(const SelectionContext &context) +{ + return selectionCanBeLayouted(context) && studioComponentsAvailable(context); +} + bool singleSelectedAndUiFile(const SelectionContext &context) { if (!singleSelection(context)) @@ -882,6 +887,12 @@ bool raiseAvailable(const SelectionContext &selectionState) return parentProperty.indexOf(modelNode) < parentProperty.count() - 1; } +bool anchorsMenuEnabled(const SelectionContext &context) +{ + return singleSelectionItemIsNotAnchoredAndSingleSelectionNotRoot(context) + || singleSelectionItemIsAnchored(context); +} + void DesignerActionManager::createDefaultDesignerActions() { using namespace SelectionContextFunctors; @@ -996,11 +1007,10 @@ void DesignerActionManager::createDefaultDesignerActions() &setVisible, &singleSelectedItem)); - addDesignerAction(new ActionGroup( - anchorsCategoryDisplayName, - anchorsCategory, - priorityAnchorsCategory, - &singleSelectionAndInBaseState)); + addDesignerAction(new ActionGroup(anchorsCategoryDisplayName, + anchorsCategory, + priorityAnchorsCategory, + &anchorsMenuEnabled)); addDesignerAction(new ModelNodeAction( anchorsFillCommandId, @@ -1042,7 +1052,7 @@ void DesignerActionManager::createDefaultDesignerActions() addDesignerAction(new ActionGroup(groupCategoryDisplayName, groupCategory, priorityGroupCategory, - &studioComponentsAvailable)); + &studioComponentsAvailableAndSelectionCanBeLayouted)); addDesignerAction(new ActionGroup( flowCategoryDisplayName, diff --git a/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp b/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp new file mode 100644 index 00000000000..94d6b5ad38f --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ +#include "navigation2d.h" + +#include <QGestureEvent> +#include <QWheelEvent> + +namespace QmlDesigner { + +Navigation2dScrollBar::Navigation2dScrollBar(QWidget *parent) + : QScrollBar(parent) +{} + +bool Navigation2dScrollBar::postEvent(QEvent *event) +{ + if (event->type() == QEvent::Wheel) { + wheelEvent(static_cast<QWheelEvent *>(event)); + return true; + } + return false; +} + +void Navigation2dScrollBar::wheelEvent(QWheelEvent *event) +{ + if (!event->angleDelta().isNull()) + QScrollBar::wheelEvent(event); +} + + +Navigation2dFilter::Navigation2dFilter(QWidget *parent, Navigation2dScrollBar *scrollbar) + : QObject(parent) + , m_scrollbar(scrollbar) +{ + if (parent) + parent->grabGesture(Qt::PinchGesture); +} + +bool Navigation2dFilter::eventFilter(QObject *, QEvent *event) +{ + if (event->type() == QEvent::Gesture) + return gestureEvent(static_cast<QGestureEvent *>(event)); + else if (event->type() == QEvent::Wheel && m_scrollbar) + return wheelEvent(static_cast<QWheelEvent *>(event)); + + return QObject::event(event); +} + +bool Navigation2dFilter::gestureEvent(QGestureEvent *event) +{ + if (QPinchGesture *pinch = static_cast<QPinchGesture *>(event->gesture(Qt::PinchGesture))) { + QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags(); + if (changeFlags & QPinchGesture::ScaleFactorChanged) { + emit zoomChanged(-(1.0 - pinch->scaleFactor()), pinch->startCenterPoint()); + event->accept(); + return true; + } + } + return false; +} + +bool Navigation2dFilter::wheelEvent(QWheelEvent *event) +{ + if (m_scrollbar->postEvent(event)) + event->ignore(); + + return false; +} + +} // End namespace QmlDesigner. diff --git a/src/plugins/qmldesigner/components/componentcore/navigation2d.h b/src/plugins/qmldesigner/components/componentcore/navigation2d.h new file mode 100644 index 00000000000..434b59055ca --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/navigation2d.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ +#pragma once + +#include <QScrollBar> + +QT_FORWARD_DECLARE_CLASS(QGestureEvent) +QT_FORWARD_DECLARE_CLASS(QWheelEvent) + +namespace QmlDesigner { + +class Navigation2dScrollBar : public QScrollBar +{ + Q_OBJECT + +public: + Navigation2dScrollBar(QWidget *parent = nullptr); + + bool postEvent(QEvent *event); + +protected: + void wheelEvent(QWheelEvent *event) override; +}; + + +class Navigation2dFilter : public QObject +{ + Q_OBJECT + +signals: + void zoomChanged(double scale, const QPointF &pos); + +public: + Navigation2dFilter(QWidget *parent = nullptr, Navigation2dScrollBar *scrollbar = nullptr); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + +private: + bool gestureEvent(QGestureEvent *event); + bool wheelEvent(QWheelEvent *event); + Navigation2dScrollBar *m_scrollbar = nullptr; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/theme.h b/src/plugins/qmldesigner/components/componentcore/theme.h index f6a1df97cc1..513965b18f4 100644 --- a/src/plugins/qmldesigner/components/componentcore/theme.h +++ b/src/plugins/qmldesigner/components/componentcore/theme.h @@ -107,6 +107,8 @@ public: idAliasOff, idAliasOn, listView, + lockOff, + lockOn, mergeCells, minus, plus, @@ -129,6 +131,8 @@ public: undo, upDownIcon, upDownSquare2, + visibilityOff, + visibilityOn, wildcard, zoomAll, zoomIn, diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp index 69e0e202f40..e325b020346 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp @@ -75,6 +75,23 @@ ConnectionModel::ConnectionModel(ConnectionView *parent) connect(this, &QStandardItemModel::dataChanged, this, &ConnectionModel::handleDataChanged); } +Qt::ItemFlags ConnectionModel::flags(const QModelIndex &modelIndex) const +{ + if (!modelIndex.isValid()) + return Qt::ItemIsEnabled; + + if (!m_connectionView || !m_connectionView->model()) + return Qt::ItemIsEnabled; + + const int internalId = data(index(modelIndex.row(), TargetModelNodeRow), UserRoles::InternalIdRole).toInt(); + ModelNode modelNode = m_connectionView->modelNodeForInternalId(internalId); + + if (modelNode.isValid() && ModelNode::isThisOrAncestorLocked(modelNode)) + return Qt::ItemIsEnabled; + + return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled; +} + void ConnectionModel::resetModel() { beginResetModel(); @@ -82,7 +99,7 @@ void ConnectionModel::resetModel() setHorizontalHeaderLabels(QStringList({ tr("Target"), tr("Signal Handler"), tr("Action") })); if (connectionView()->isAttached()) { - for (const ModelNode modelNode : connectionView()->allModelNodes()) + for (const ModelNode &modelNode : connectionView()->allModelNodes()) addModelNode(modelNode); } @@ -94,8 +111,8 @@ void ConnectionModel::resetModel() SignalHandlerProperty ConnectionModel::signalHandlerPropertyForRow(int rowNumber) const { - const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt(); - const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString(); + const int internalId = data(index(rowNumber, TargetModelNodeRow), UserRoles::InternalIdRole).toInt(); + const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), UserRoles::TargetPropertyNameRole).toString(); ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId); if (modelNode.isValid()) @@ -256,8 +273,8 @@ void ConnectionModel::updateTargetNode(int rowNumber) void ConnectionModel::updateCustomData(QStandardItem *item, const SignalHandlerProperty &signalHandlerProperty) { - item->setData(signalHandlerProperty.parentModelNode().internalId(), Qt::UserRole + 1); - item->setData(signalHandlerProperty.name(), Qt::UserRole + 2); + item->setData(signalHandlerProperty.parentModelNode().internalId(), UserRoles::InternalIdRole); + item->setData(signalHandlerProperty.name(), UserRoles::TargetPropertyNameRole); } ModelNode ConnectionModel::getTargetNodeForConnection(const ModelNode &connection) const diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h index b7d24db7190..5acf4f6402c 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h @@ -48,7 +48,14 @@ public: TargetPropertyNameRow = 1, SourceRow = 2 }; + enum UserRoles { + InternalIdRole = Qt::UserRole + 1, + TargetPropertyNameRole + }; ConnectionModel(ConnectionView *parent = nullptr); + + Qt::ItemFlags flags(const QModelIndex &modelIndex) const override; + void resetModel(); SignalHandlerProperty signalHandlerPropertyForRow(int rowNumber) const; ConnectionView *connectionView() const; diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp index 0410ccf9089..88dd6a971db 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp @@ -36,6 +36,8 @@ #include <variantproperty.h> #include <signalhandlerproperty.h> +#include <QTableView> + namespace QmlDesigner { namespace Internal { @@ -162,6 +164,33 @@ void ConnectionView::selectedNodesChanged(const QList<ModelNode> & selectedNodeL emit connectionViewWidget()->setEnabledAddButton(selectedNodeList.count() == 1); } +void ConnectionView::auxiliaryDataChanged(const ModelNode &node, + const PropertyName &name, + const QVariant &data) +{ + Q_UNUSED(node) + + // Check if the auxiliary data is actually the locked property or if it is unlocked + if (name != QmlDesigner::lockedProperty || !data.toBool()) + return; + + QItemSelectionModel *selectionModel = connectionTableView()->selectionModel(); + if (!selectionModel->hasSelection()) + return; + + QModelIndex modelIndex = selectionModel->currentIndex(); + if (!modelIndex.isValid() || !model()) + return; + + const int internalId = connectionModel()->data(connectionModel()->index(modelIndex.row(), + ConnectionModel::TargetModelNodeRow), + ConnectionModel::UserRoles::InternalIdRole).toInt(); + ModelNode modelNode = modelNodeForInternalId(internalId); + + if (modelNode.isValid() && ModelNode::isThisOrAncestorLocked(modelNode)) + selectionModel->clearSelection(); +} + void ConnectionView::importsChanged(const QList<Import> & /*addedImports*/, const QList<Import> & /*removedImports*/) { backendModel()->resetModel(); diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h index 905fa02a587..dda496263d7 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h @@ -69,6 +69,7 @@ public: void selectedNodesChanged(const QList<ModelNode> &selectedNodeList, const QList<ModelNode> &lastSelectedNodeList) override; + void auxiliaryDataChanged(const ModelNode &node, const PropertyName &name, const QVariant &data) override; void importsChanged(const QList<Import> &addedImports, const QList<Import> &removedImports) override; diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp index 36bae9be0c1..9fec8b2fc54 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp @@ -27,6 +27,7 @@ #include "axis.h" #include "curveeditormodel.h" #include "curveitem.h" +#include "navigation2d.h" #include "treeitem.h" #include "utils.h" @@ -79,6 +80,13 @@ GraphicsView::GraphicsView(CurveEditorModel *model, QWidget *parent) applyZoom(m_zoomX, m_zoomY); update(); + + QmlDesigner::Navigation2dFilter *filter = new QmlDesigner::Navigation2dFilter(this); + auto zoomChanged = &QmlDesigner::Navigation2dFilter::zoomChanged; + connect(filter, zoomChanged, [this](double scale, const QPointF &pos) { + applyZoom(m_zoomX + scale, m_zoomY, mapToGlobal(pos.toPoint())); + }); + installEventFilter(filter); } GraphicsView::~GraphicsView() diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.cpp index 891f6896eae..0fdb00ff1b0 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.cpp @@ -41,8 +41,11 @@ struct HandleGeometry handle = QRectF(topLeft, -topLeft); toKeyframe = QLineF(QPointF(0.0, 0.0), -pos); angle = -toKeyframe.angle() + 45.0; + bbox = handle.united(QRectF(-pos, QSizeF(1.0,1.0))); } + QRectF bbox; + QRectF handle; QLineF toKeyframe; @@ -97,7 +100,7 @@ HandleItem::Slot HandleItem::slot() const QRectF HandleItem::boundingRect() const { HandleGeometry geom(pos(), m_style); - return geom.handle; + return geom.bbox; } bool HandleItem::contains(const QPointF &point) const diff --git a/src/plugins/qmldesigner/components/formeditor/abstractformeditortool.cpp b/src/plugins/qmldesigner/components/formeditor/abstractformeditortool.cpp index 0ed7abaefe3..fc7e8b97c7b 100644 --- a/src/plugins/qmldesigner/components/formeditor/abstractformeditortool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/abstractformeditortool.cpp @@ -189,7 +189,7 @@ FormEditorItem *AbstractFormEditorTool::topMovableFormEditorItem(const QList<QGr FormEditorItem* AbstractFormEditorTool::nearestFormEditorItem(const QPointF &point, const QList<QGraphicsItem*> &itemList) { FormEditorItem* nearestItem = nullptr; - foreach (QGraphicsItem *item, itemList) { + for (QGraphicsItem *item : itemList) { FormEditorItem *formEditorItem = FormEditorItem::fromQGraphicsItem(item); if (formEditorItem && formEditorItem->flowHitTest(point)) @@ -201,6 +201,9 @@ FormEditorItem* AbstractFormEditorTool::nearestFormEditorItem(const QPointF &poi if (formEditorItem->parentItem() && !formEditorItem->parentItem()->isContentVisible()) continue; + if (formEditorItem && ModelNode::isThisOrAncestorLocked(formEditorItem->qmlItemNode().modelNode())) + continue; + if (!nearestItem) nearestItem = formEditorItem; else if (formEditorItem->selectionWeigth(point, 1) < nearestItem->selectionWeigth(point, 0)) diff --git a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp index 9a643edef8e..8bd2d827f5e 100644 --- a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp @@ -250,7 +250,6 @@ void DragTool::dropEvent(const QList<QGraphicsItem *> &/*itemList*/, QGraphicsSc if (m_dragNode.isValid()) view()->setSelectedModelNode(m_dragNode); - m_dragNode = QmlItemNode(); view()->changeToSelectionTool(); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp index b621dacb4a0..4cc7c027b29 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp @@ -286,6 +286,7 @@ bool FormEditorItem::flowHitTest(const QPointF & ) const void FormEditorItem::setFrameColor(const QColor &color) { m_frameColor = color; + update(); } FormEditorItem::~FormEditorItem() diff --git a/src/plugins/qmldesigner/components/formeditor/rubberbandselectionmanipulator.cpp b/src/plugins/qmldesigner/components/formeditor/rubberbandselectionmanipulator.cpp index 242b5649d27..fa399f8860a 100644 --- a/src/plugins/qmldesigner/components/formeditor/rubberbandselectionmanipulator.cpp +++ b/src/plugins/qmldesigner/components/formeditor/rubberbandselectionmanipulator.cpp @@ -90,10 +90,10 @@ void RubberBandSelectionManipulator::select(SelectionType selectionType) if (!m_beginFormEditorItem) return; - QList<QGraphicsItem*> itemList = m_editorView->scene()->items(m_selectionRectangleElement.rect(), Qt::IntersectsItemBoundingRect); + QList<QGraphicsItem *> itemList = m_editorView->scene()->items(m_selectionRectangleElement.rect(), Qt::IntersectsItemBoundingRect); QList<QmlItemNode> newNodeList; - foreach (QGraphicsItem* item, itemList) + for (QGraphicsItem *item : itemList) { FormEditorItem *formEditorItem = FormEditorItem::fromQGraphicsItem(item); @@ -137,7 +137,7 @@ void RubberBandSelectionManipulator::select(SelectionType selectionType) } -void RubberBandSelectionManipulator::setItems(const QList<FormEditorItem*> &itemList) +void RubberBandSelectionManipulator::setItems(const QList<FormEditorItem *> &itemList) { m_itemList = itemList; } diff --git a/src/plugins/qmldesigner/components/integration/designdocument.cpp b/src/plugins/qmldesigner/components/integration/designdocument.cpp index 05929fbd0ef..b572d55f0b4 100644 --- a/src/plugins/qmldesigner/components/integration/designdocument.cpp +++ b/src/plugins/qmldesigner/components/integration/designdocument.cpp @@ -49,6 +49,7 @@ #include <coreplugin/icore.h> #include <coreplugin/idocument.h> #include <coreplugin/editormanager/editormanager.h> +#include <utils/algorithm.h> #include <qmljs/qmljsmodelmanagerinterface.h> @@ -57,6 +58,7 @@ #include <QDebug> #include <QApplication> +#include <QMessageBox> #include <QPlainTextEdit> #include <QRandomGenerator> @@ -375,9 +377,41 @@ void DesignDocument::deleteSelected() if (!currentModel()) return; + QStringList lockedNodes; + for (const ModelNode &modelNode : view()->selectedModelNodes()) { + for (const ModelNode &node : modelNode.allSubModelNodesAndThisNode()) { + if (node.isValid() && !node.isRootNode() && node.locked()) + lockedNodes.push_back(node.id()); + } + } + + if (!lockedNodes.empty()) { + Utils::sort(lockedNodes); + QString detailedText = QString("<b>" + tr("Locked items:") + "</b><br>"); + + for (const auto &id : qAsConst(lockedNodes)) + detailedText.append("- " + id + "<br>"); + + detailedText.chop(QString("<br>").size()); + + QMessageBox msgBox; + msgBox.setTextFormat(Qt::RichText); + msgBox.setIcon(QMessageBox::Question); + msgBox.setWindowTitle(tr("Delete/Cut Item")); + msgBox.setText(QString(tr("Deleting or cutting this item will modify locked items.") + "<br><br>%1") + .arg(detailedText)); + msgBox.setInformativeText(tr("Do you want to continue by removing the item (Delete) or removing it and copying it to the clipboard (Cut)?")); + msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Ok); + + if (msgBox.exec() == QMessageBox::Cancel) + return; + } + rewriterView()->executeInTransaction("DesignDocument::deleteSelected", [this](){ QList<ModelNode> toDelete = view()->selectedModelNodes(); - foreach (ModelNode node, toDelete) { + + for (ModelNode node : toDelete) { if (node.isValid() && !node.isRootNode() && QmlObjectNode::isValidQmlObjectNode(node)) QmlObjectNode(node).destroy(); } @@ -545,7 +579,7 @@ void DesignDocument::paste() } view.setSelectedModelNodes({pastedNode}); }); - NodeMetaInfo::clearCache(); + view.model()->clearMetaInfoCache(); } } diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri b/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri index 0e6219dd9ac..8eaa9ce983f 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri @@ -7,6 +7,7 @@ qtHaveModule(quick3dassetimport) { # Input HEADERS += itemlibraryview.h \ + $$PWD/itemlibraryiconimageprovider.h \ itemlibrarywidget.h \ itemlibrarymodel.h \ itemlibraryresourceview.h \ @@ -19,6 +20,7 @@ HEADERS += itemlibraryview.h \ customfilesystemmodel.h SOURCES += itemlibraryview.cpp \ + $$PWD/itemlibraryiconimageprovider.cpp \ itemlibrarywidget.cpp \ itemlibrarymodel.cpp \ itemlibraryresourceview.cpp \ diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp index 1fc817cd01c..ca00b66f462 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp @@ -502,7 +502,6 @@ bool ItemLibraryAssetImporter::generateComponentIcon(int size, const QString &ic QProcessUniquePointer process = puppetCreator.createPuppetProcess( "custom", {}, - this, std::function<void()>(), [&](int exitCode, QProcess::ExitStatus exitStatus) { processFinished(exitCode, exitStatus); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryiconimageprovider.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryiconimageprovider.cpp new file mode 100644 index 00000000000..e0254111a9f --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryiconimageprovider.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "itemlibraryiconimageprovider.h" + +#include <projectexplorer/target.h> +#include <utils/stylehelper.h> + +#include <QMetaObject> +#include <QQuickImageResponse> + +namespace QmlDesigner { + +class ImageRespose : public QQuickImageResponse +{ +public: + QQuickTextureFactory *textureFactory() const override + { + return QQuickTextureFactory::textureFactoryForImage(m_image); + } + + void setImage(const QImage &image) + { + m_image = image; + + emit finished(); + } + + void abort() + { + m_image = QImage{ + Utils::StyleHelper::dpiSpecificImageFile(":/ItemLibrary/images/item-default-icon.png")}; + + emit finished(); + } + +private: + QImage m_image; +}; + + +QQuickImageResponse *ItemLibraryIconImageProvider::requestImageResponse(const QString &id, + const QSize &) +{ + auto response = std::make_unique<ImageRespose>(); + + m_cache.requestIcon( + id, + [response = QPointer<ImageRespose>(response.get())](const QImage &image) { + QMetaObject::invokeMethod( + response, + [response, image] { + if (response) + response->setImage(image); + }, + Qt::QueuedConnection); + }, + [response = QPointer<ImageRespose>(response.get())] { + QMetaObject::invokeMethod( + response, + [response] { + if (response) + response->abort(); + }, + Qt::QueuedConnection); + }); + + return response.release(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryiconimageprovider.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryiconimageprovider.h new file mode 100644 index 00000000000..9e60246d4d6 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryiconimageprovider.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include <nodeinstanceview.h> +#include <rewriterview.h> + +#include <coreplugin/icore.h> +#include <imagecache.h> +#include <imagecache/imagecachecollector.h> +#include <imagecache/imagecacheconnectionmanager.h> +#include <imagecache/imagecachegenerator.h> +#include <imagecache/imagecachestorage.h> +#include <imagecache/timestampprovider.h> + +#include <sqlitedatabase.h> + +#include <QQuickAsyncImageProvider> + +namespace QmlDesigner { + +class ItemLibraryIconImageProvider : public QQuickAsyncImageProvider +{ +public: + ItemLibraryIconImageProvider(ImageCache &imageCache) + : m_cache{imageCache} + {} + + QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override; + +private: + ImageCache &m_cache; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.cpp index d67631531ce..0d46140f12e 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.cpp @@ -47,8 +47,18 @@ QString ItemLibraryItem::typeName() const QString ItemLibraryItem::itemLibraryIconPath() const { - //Prepend image provider prefix - return QStringLiteral("image://qmldesigner_itemlibrary/") + m_itemLibraryEntry.libraryEntryIconPath(); + if (m_itemLibraryEntry.customComponentSource().isEmpty()) { + return QStringLiteral("image://qmldesigner_itemlibrary/") + + m_itemLibraryEntry.libraryEntryIconPath(); + } else { + return QStringLiteral("image://itemlibrary_preview/") + + m_itemLibraryEntry.customComponentSource(); + } +} + +QString ItemLibraryItem::componentPath() const +{ + return m_itemLibraryEntry.customComponentSource(); } bool ItemLibraryItem::setVisible(bool isVisible) diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h index fa1fa922572..859cbe51dd0 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h @@ -42,6 +42,7 @@ class ItemLibraryItem: public QObject { Q_PROPERTY(QString itemName READ itemName FINAL) Q_PROPERTY(QString itemLibraryIconPath READ itemLibraryIconPath FINAL) Q_PROPERTY(bool itemVisible READ isVisible NOTIFY visibilityChanged FINAL) + Q_PROPERTY(QString componentPath READ componentPath FINAL) public: ItemLibraryItem(QObject *parent); @@ -50,6 +51,7 @@ public: QString itemName() const; QString typeName() const; QString itemLibraryIconPath() const; + QString componentPath() const; bool setVisible(bool isVisible); bool isVisible() const; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp index a01ce38fc72..19506042bb9 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp @@ -29,6 +29,8 @@ #include "itemlibraryitem.h" #include "itemlibrarysection.h" +#include <components/previewtooltip/previewtooltipbackend.h> + #include <model.h> #include <nodehints.h> #include <nodemetainfo.h> diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp index 5ffece61cd9..79d559c0a0f 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp @@ -25,24 +25,47 @@ #include "itemlibraryview.h" #include "itemlibrarywidget.h" +#include "metainfo.h" +#include <bindingproperty.h> +#include <coreplugin/icore.h> +#include <imagecache.h> +#include <imagecache/imagecachecollector.h> +#include <imagecache/imagecacheconnectionmanager.h> +#include <imagecache/imagecachegenerator.h> +#include <imagecache/imagecachestorage.h> +#include <imagecache/timestampprovider.h> #include <import.h> #include <importmanagerview.h> -#include <qmlitemnode.h> -#include <rewriterview.h> -#include <bindingproperty.h> #include <nodelistproperty.h> +#include <projectexplorer/kit.h> +#include <projectexplorer/target.h> +#include <rewriterview.h> +#include <sqlitedatabase.h> #include <utils/algorithm.h> #include <qmldesignerplugin.h> -#include "metainfo.h" +#include <qmlitemnode.h> namespace QmlDesigner { +class ImageCacheData +{ +public: + Sqlite::Database database{ + Utils::PathString{Core::ICore::cacheResourcePath() + "/imagecache-v1.db"}}; + ImageCacheStorage<Sqlite::Database> storage{database}; + ImageCacheConnectionManager connectionManager; + ImageCacheCollector collector{connectionManager}; + ImageCacheGenerator generator{collector, storage}; + TimeStampProvider timeStampProvider; + ImageCache cache{storage, generator, timeStampProvider}; +}; + ItemLibraryView::ItemLibraryView(QObject* parent) : AbstractView(parent), m_importManagerView(new ImportManagerView(this)) { - + m_imageCacheData = std::make_unique<ImageCacheData>(); } ItemLibraryView::~ItemLibraryView() = default; @@ -55,7 +78,7 @@ bool ItemLibraryView::hasWidget() const WidgetInfo ItemLibraryView::widgetInfo() { if (m_widget.isNull()) { - m_widget = new ItemLibraryWidget; + m_widget = new ItemLibraryWidget{m_imageCacheData->cache}; m_widget->setImportsWidget(m_importManagerView->widgetInfo().widget); } @@ -70,6 +93,16 @@ WidgetInfo ItemLibraryView::widgetInfo() void ItemLibraryView::modelAttached(Model *model) { AbstractView::modelAttached(model); + auto target = QmlDesignerPlugin::instance()->currentDesignDocument()->currentTarget(); + m_imageCacheData->cache.clean(); + + if (target) { + auto clonedTarget = std::make_unique<ProjectExplorer::Target>( + target->project(), target->kit()->clone(), ProjectExplorer::Target::_constructor_tag{}); + + m_imageCacheData->collector.setTarget(std::move(clonedTarget)); + } + m_widget->clearSearchFilter(); m_widget->setModel(model); updateImports(); @@ -83,6 +116,8 @@ void ItemLibraryView::modelAboutToBeDetached(Model *model) { model->detachView(m_importManagerView); + m_imageCacheData->collector.setTarget({}); + AbstractView::modelAboutToBeDetached(model); m_widget->setModel(nullptr); @@ -124,7 +159,7 @@ void ItemLibraryView::importsChanged(const QList<Import> &addedImports, const QL void ItemLibraryView::setResourcePath(const QString &resourcePath) { if (m_widget.isNull()) - m_widget = new ItemLibraryWidget; + m_widget = new ItemLibraryWidget{m_imageCacheData->cache}; m_widget->setResourcePath(resourcePath); } @@ -142,4 +177,4 @@ void ItemLibraryView::updateImports() m_widget->delayedUpdateModel(); } -} //QmlDesigner +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.h index 09535c32302..3d4af5d21c9 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.h @@ -34,6 +34,7 @@ namespace QmlDesigner { class ItemLibraryWidget; class ImportManagerView; +class ImageCacheData; class ItemLibraryView : public AbstractView { @@ -58,6 +59,7 @@ protected: void updateImports(); private: + std::unique_ptr<ImageCacheData> m_imageCacheData; QPointer<ItemLibraryWidget> m_widget; ImportManagerView *m_importManagerView; bool m_hasErrors = false; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp index 45e56011351..10a253f5b6e 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp @@ -27,19 +27,21 @@ #include "customfilesystemmodel.h" #include "itemlibraryassetimportdialog.h" +#include "itemlibraryiconimageprovider.h" #include <theme.h> -#include <itemlibrarymodel.h> +#include <designeractionmanager.h> +#include <designermcumanager.h> #include <itemlibraryimageprovider.h> #include <itemlibraryinfo.h> +#include <itemlibrarymodel.h> #include <metainfo.h> #include <model.h> +#include <previewtooltip/previewtooltipbackend.h> #include <rewritingexception.h> -#include <qmldesignerplugin.h> #include <qmldesignerconstants.h> -#include <designeractionmanager.h> -#include <designermcumanager.h> +#include <qmldesignerplugin.h> #include <utils/algorithm.h> #include <utils/flowlayout.h> @@ -81,14 +83,14 @@ static QString propertyEditorResourcesPath() { return Core::ICore::resourcePath() + QStringLiteral("/qmldesigner/propertyEditorQmlSources"); } -ItemLibraryWidget::ItemLibraryWidget(QWidget *parent) : - QFrame(parent), - m_itemIconSize(24, 24), - m_itemViewQuickWidget(new QQuickWidget(this)), - m_resourcesView(new ItemLibraryResourceView(this)), - m_importTagsWidget(new QWidget(this)), - m_addResourcesWidget(new QWidget(this)), - m_filterFlag(QtBasic) +ItemLibraryWidget::ItemLibraryWidget(ImageCache &imageCache) + : m_itemIconSize(24, 24) + , m_itemViewQuickWidget(new QQuickWidget(this)) + , m_resourcesView(new ItemLibraryResourceView(this)) + , m_importTagsWidget(new QWidget(this)) + , m_addResourcesWidget(new QWidget(this)) + , m_imageCache{imageCache} + , m_filterFlag(QtBasic) { m_compressionTimer.setInterval(200); m_compressionTimer.setSingleShot(true); @@ -102,16 +104,20 @@ ItemLibraryWidget::ItemLibraryWidget(QWidget *parent) : m_itemViewQuickWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); m_itemLibraryModel = new ItemLibraryModel(this); - m_itemViewQuickWidget->rootContext()->setContextProperties( - QVector<QQmlContext::PropertyPair>{ - {{"itemLibraryModel"}, QVariant::fromValue(m_itemLibraryModel.data())}, - {{"itemLibraryIconWidth"}, m_itemIconSize.width()}, - {{"itemLibraryIconHeight"}, m_itemIconSize.height()}, - {{"rootView"}, QVariant::fromValue(this)}, - {{"highlightColor"}, Utils::StyleHelper::notTooBrightHighlightColor()} - } - ); - m_itemViewQuickWidget->setClearColor(Theme::getColor(Theme::Color::QmlDesigner_BackgroundColorDarkAlternate)); + m_itemViewQuickWidget->rootContext()->setContextProperties(QVector<QQmlContext::PropertyPair>{ + {{"itemLibraryModel"}, QVariant::fromValue(m_itemLibraryModel.data())}, + {{"itemLibraryIconWidth"}, m_itemIconSize.width()}, + {{"itemLibraryIconHeight"}, m_itemIconSize.height()}, + {{"rootView"}, QVariant::fromValue(this)}, + {{"highlightColor"}, Utils::StyleHelper::notTooBrightHighlightColor()}, + }); + + m_previewTooltipBackend = std::make_unique<PreviewTooltipBackend>(m_imageCache); + m_itemViewQuickWidget->rootContext()->setContextProperty("tooltipBackend", + m_previewTooltipBackend.get()); + + m_itemViewQuickWidget->setClearColor( + Theme::getColor(Theme::Color::QmlDesigner_BackgroundColorDarkAlternate)); /* create Resources view and its model */ m_resourcesFileSystemModel = new CustomFileSystemModel(this); @@ -119,6 +125,7 @@ ItemLibraryWidget::ItemLibraryWidget(QWidget *parent) : /* create image provider for loading item icons */ m_itemViewQuickWidget->engine()->addImageProvider(QStringLiteral("qmldesigner_itemlibrary"), new Internal::ItemLibraryImageProvider); + Theme::setupTheme(m_itemViewQuickWidget->engine()); /* other widgets */ @@ -243,6 +250,8 @@ ItemLibraryWidget::ItemLibraryWidget(QWidget *parent) : reloadQmlSource(); } +ItemLibraryWidget::~ItemLibraryWidget() = default; + void ItemLibraryWidget::setItemLibraryInfo(ItemLibraryInfo *itemLibraryInfo) { if (m_itemLibraryInfo.data() == itemLibraryInfo) @@ -306,9 +315,14 @@ void ItemLibraryWidget::delayedUpdateModel() void ItemLibraryWidget::setModel(Model *model) { + m_itemViewQuickWidget->engine()->removeImageProvider("itemlibrary_preview"); m_model = model; if (!model) return; + + m_itemViewQuickWidget->engine()->addImageProvider("itemlibrary_preview", + new ItemLibraryIconImageProvider{m_imageCache}); + setItemLibraryInfo(model->metaInfo().itemLibraryInfo()); } @@ -318,7 +332,8 @@ void ItemLibraryWidget::setCurrentIndexOfStackedWidget(int index) m_filterLineEdit->setVisible(false); m_importTagsWidget->setVisible(true); m_addResourcesWidget->setVisible(false); - } if (index == 1) { + } + if (index == 1) { m_filterLineEdit->setVisible(true); m_importTagsWidget->setVisible(false); m_addResourcesWidget->setVisible(true); @@ -564,5 +579,4 @@ void ItemLibraryWidget::addResources() } } } - -} +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h index 11dea7d0c1a..bfe9106a23b 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h @@ -37,6 +37,8 @@ #include <QQmlPropertyMap> #include <QTimer> +#include <memory> + QT_BEGIN_NAMESPACE class QStackedWidget; class QShortcut; @@ -52,6 +54,9 @@ class CustomFileSystemModel; class ItemLibraryModel; class ItemLibraryResourceView; +class PreviewTooltipBackend; +class ImageCache; +class ImageCacheCollector; class ItemLibraryWidget : public QFrame { @@ -63,7 +68,8 @@ class ItemLibraryWidget : public QFrame }; public: - ItemLibraryWidget(QWidget *parent = nullptr); + ItemLibraryWidget(ImageCache &imageCache); + ~ItemLibraryWidget(); void setItemLibraryInfo(ItemLibraryInfo *itemLibraryInfo); QList<QToolButton *> createToolBarWidgets(); @@ -115,9 +121,10 @@ private: QScopedPointer<ItemLibraryResourceView> m_resourcesView; QScopedPointer<QWidget> m_importTagsWidget; QScopedPointer<QWidget> m_addResourcesWidget; + std::unique_ptr<PreviewTooltipBackend> m_previewTooltipBackend; QShortcut *m_qmlSourceUpdateShortcut; - + ImageCache &m_imageCache; QPointer<Model> m_model; FilterChangeFlag m_filterFlag; ItemLibraryEntry m_currentitemLibraryEntry; diff --git a/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp b/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp index 0371d0f1bba..5cd5721812d 100644 --- a/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp +++ b/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp @@ -82,6 +82,12 @@ void IconCheckboxItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &styleOption, const QModelIndex &modelIndex) const { + bool isVisibilityIcon = modelIndex.column() != NavigatorTreeModel::ColumnType::Visibility; + // We need to invert the check status if visibility icon + bool checked = isVisibilityIcon ? isChecked(modelIndex) : !isChecked(modelIndex); + if (!(styleOption.state & QStyle::State_MouseOver) && !checked) + return; + if (rowIsPropertyRole(modelIndex.model(), modelIndex)) return; //Do not paint icons for property rows diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index 5538a9cb8d2..602a0f73164 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -200,10 +200,10 @@ QVariant NavigatorTreeModel::data(const QModelIndex &index, int role) const if (!modelNode.isValid()) return QVariant(); - if (role == ItemIsVisibleRole) //independent of column + if (role == ItemIsVisibleRole) // independent of column return m_view->isNodeInvisible(modelNode) ? Qt::Unchecked : Qt::Checked; - if (index.column() == 0) { + if (index.column() == ColumnType::Name) { if (role == Qt::DisplayRole) { return modelNode.displayName(); } else if (role == Qt::DecorationRole) { @@ -240,18 +240,24 @@ QVariant NavigatorTreeModel::data(const QModelIndex &index, int role) const } else if (role == ModelNodeRole) { return QVariant::fromValue<ModelNode>(modelNode); } - } else if (index.column() == 1) { //export + } else if (index.column() == ColumnType::Alias) { // export if (role == Qt::CheckStateRole) - return currentQmlObjectNode.isAliasExported() ? Qt::Checked : Qt::Unchecked; + return currentQmlObjectNode.isAliasExported() ? Qt::Checked : Qt::Unchecked; else if (role == Qt::ToolTipRole) return tr("Toggles whether this item is exported as an " "alias property of the root item."); - } else if (index.column() == 2) { //visible + } else if (index.column() == ColumnType::Visibility) { // visible if (role == Qt::CheckStateRole) return m_view->isNodeInvisible(modelNode) ? Qt::Unchecked : Qt::Checked; else if (role == Qt::ToolTipRole) return tr("Toggles the visibility of this item in the form editor.\n" "This is independent of the visibility property in QML."); + } else if (index.column() == ColumnType::Lock) { // lock + if (role == Qt::CheckStateRole) + return modelNode.locked() ? Qt::Checked : Qt::Unchecked; + else if (role == Qt::ToolTipRole) + return tr("Toggles whether this item is locked.\n" + "Locked items can't be modified or selected."); } return QVariant(); @@ -259,7 +265,16 @@ QVariant NavigatorTreeModel::data(const QModelIndex &index, int role) const Qt::ItemFlags NavigatorTreeModel::flags(const QModelIndex &index) const { - if (index.column() == 0) + if (index.column() == ColumnType::Alias + || index.column() == ColumnType::Visibility + || index.column() == ColumnType::Lock) + return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemNeverHasChildren; + + const ModelNode modelNode = modelNodeForIndex(index); + if (ModelNode::isThisOrAncestorLocked(modelNode)) + return Qt::NoItemFlags; + + if (index.column() == ColumnType::Name) return Qt::ItemIsEditable | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled; return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable @@ -378,7 +393,7 @@ int NavigatorTreeModel::columnCount(const QModelIndex &parent) const if (parent.column() > 0) return 0; - return 3; + return ColumnType::Count; } ModelNode NavigatorTreeModel::modelNodeForIndex(const QModelIndex &index) const @@ -755,7 +770,8 @@ void NavigatorTreeModel::moveNodesInteractive(NodeAbstractProperty &parentProper auto doMoveNodesInteractive = [&parentProperty, modelNodes, targetIndex](){ const TypeName propertyQmlType = parentProperty.parentModelNode().metaInfo().propertyTypeName(parentProperty.name()); - foreach (const ModelNode &modelNode, modelNodes) { + int idx = targetIndex; + for (const ModelNode &modelNode : modelNodes) { if (modelNode.isValid() && modelNode != parentProperty.parentModelNode() && !modelNode.isAncestorOf(parentProperty.parentModelNode()) @@ -764,10 +780,9 @@ void NavigatorTreeModel::moveNodesInteractive(NodeAbstractProperty &parentProper //once the MetaInfo is part of instances we can do this right bool nodeCanBeMovedToParentProperty = removeModelNodeFromNodeProperty(parentProperty, modelNode); - if (nodeCanBeMovedToParentProperty) { reparentModelNodeToNodeProperty(parentProperty, modelNode); - slideModelNodeInList(parentProperty, modelNode, targetIndex); + slideModelNodeInList(parentProperty, modelNode, idx++); } } } @@ -792,11 +807,13 @@ Qt::DropActions NavigatorTreeModel::supportedDragActions() const bool NavigatorTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { ModelNode modelNode = modelNodeForIndex(index); - if (index.column() == 1 && role == Qt::CheckStateRole) { + if (index.column() == ColumnType::Alias && role == Qt::CheckStateRole) { QTC_ASSERT(m_view, return false); m_view->handleChangedExport(modelNode, value.toInt() != 0); - } else if (index.column() == 2 && role == Qt::CheckStateRole) { + } else if (index.column() == ColumnType::Visibility && role == Qt::CheckStateRole) { QmlVisualNode(modelNode).setVisibilityOverride(value.toInt() == 0); + } else if (index.column() == ColumnType::Lock && role == Qt::CheckStateRole) { + modelNode.setLocked(value.toInt() != 0); } return true; @@ -806,7 +823,7 @@ void NavigatorTreeModel::notifyDataChanged(const ModelNode &modelNode) { const QModelIndex index = indexForModelNode(modelNode); const QAbstractItemModel *model = index.model(); - const QModelIndex sibling = model ? model->sibling(index.row(), 2, index) : QModelIndex(); + const QModelIndex sibling = model ? model->sibling(index.row(), ColumnType::Count - 1, index) : QModelIndex(); emit dataChanged(index, sibling); } diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h index d474ee98b9c..bb2712148df 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h @@ -49,6 +49,14 @@ class NavigatorTreeModel : public QAbstractItemModel, public NavigatorModelInter public: + enum ColumnType { + Name = 0, + Alias, + Visibility, + Lock, + Count + }; + explicit NavigatorTreeModel(QObject *parent = nullptr); ~NavigatorTreeModel() override; diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreeview.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreeview.cpp index ddf8862c895..8be526278c6 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreeview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreeview.cpp @@ -168,6 +168,7 @@ NavigatorTreeView::NavigatorTreeView(QWidget *parent) setMinimumWidth(240); setRootIsDecorated(false); setIndentation(indentation() * 0.5); + viewport()->setAttribute(Qt::WA_Hover); m_toolTipHideTimer.setSingleShot(true); connect(&m_toolTipHideTimer, &QTimer::timeout, [this]() { diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp index 87952613b1c..e1702bdac46 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp @@ -41,6 +41,7 @@ #include <qmlitemnode.h> #include <rewritingexception.h> #include <nodeinstanceview.h> +#include <theme.h> #include <coreplugin/editormanager/editormanager.h> #include <coreplugin/icore.h> @@ -48,6 +49,7 @@ #include <utils/algorithm.h> #include <utils/icon.h> #include <utils/utilsicons.h> +#include <utils/stylehelper.h> #include <QHeaderView> #include <QTimer> @@ -138,13 +140,14 @@ void NavigatorView::modelAttached(Model *model) QTreeView *treeView = treeWidget(); - treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch); - treeView->header()->resizeSection(1,26); + treeView->header()->setSectionResizeMode(NavigatorTreeModel::ColumnType::Name, QHeaderView::Stretch); + treeView->header()->resizeSection(NavigatorTreeModel::ColumnType::Alias, 26); + treeView->header()->resizeSection(NavigatorTreeModel::ColumnType::Visibility, 26); + treeView->header()->resizeSection(NavigatorTreeModel::ColumnType::Lock, 26); treeView->setIndentation(20); m_currentModelInterface->setFilter(false); - QTimer::singleShot(0, this, [this, treeView]() { m_currentModelInterface->setFilter( DesignerSettings::getValue(DesignerSettingsKey::NAVIGATOR_SHOW_ONLY_VISIBLE_ITEMS).toBool()); @@ -166,10 +169,6 @@ void NavigatorView::modelAttached(Model *model) } } }); - -#ifdef _LOCK_ITEMS_ - treeView->header()->resizeSection(2,20); -#endif } void NavigatorView::modelAboutToBeDetached(Model *model) @@ -304,7 +303,7 @@ void NavigatorView::nodeIdChanged(const ModelNode& modelNode, const QString & /* m_currentModelInterface->notifyDataChanged(modelNode); } -void NavigatorView::propertiesAboutToBeRemoved(const QList<AbstractProperty>& /*propertyList*/) +void NavigatorView::propertiesAboutToBeRemoved(const QList<AbstractProperty> &/*propertyList*/) { } @@ -321,7 +320,7 @@ void NavigatorView::propertiesRemoved(const QList<AbstractProperty> &propertyLis m_currentModelInterface->notifyModelNodesRemoved(modelNodes); } -void NavigatorView::rootNodeTypeChanged(const QString & /*type*/, int /*majorVersion*/, int /*minorVersion*/) +void NavigatorView::rootNodeTypeChanged(const QString &/*type*/, int /*majorVersion*/, int /*minorVersion*/) { m_currentModelInterface->notifyDataChanged(rootModelNode()); } @@ -332,9 +331,12 @@ void NavigatorView::nodeTypeChanged(const ModelNode &modelNode, const TypeName & } void NavigatorView::auxiliaryDataChanged(const ModelNode &modelNode, - const PropertyName & /*name*/, - const QVariant & /*data*/) + const PropertyName &name, + const QVariant &data) { + Q_UNUSED(name) + Q_UNUSED(data) + m_currentModelInterface->notifyDataChanged(modelNode); } @@ -344,8 +346,8 @@ void NavigatorView::instanceErrorChanged(const QVector<ModelNode> &errorNodeList m_currentModelInterface->notifyDataChanged(modelNode); } -void NavigatorView::nodeOrderChanged(const NodeListProperty & listProperty, - const ModelNode & /*node*/, +void NavigatorView::nodeOrderChanged(const NodeListProperty &listProperty, + const ModelNode &/*node*/, int /*oldIndex*/) { m_currentModelInterface->notifyModelNodesMoved(listProperty.directSubNodes()); @@ -613,33 +615,50 @@ void NavigatorView::setupWidget() connect(m_widget.data(), &NavigatorWidget::reverseOrderToggled, this, &NavigatorView::reverseOrderToggled); #ifndef QMLDESIGNER_TEST + const QString fontName = "qtds_propertyIconFont.ttf"; + + const QIcon visibilityOnIcon = + Utils::StyleHelper::getIconFromIconFont(fontName, + Theme::getIconUnicode(Theme::Icon::visibilityOn), + 28, 28, QColor(Qt::white)); + const QIcon visibilityOffIcon = + Utils::StyleHelper::getIconFromIconFont(fontName, + Theme::getIconUnicode(Theme::Icon::visibilityOff), + 28, 28, QColor(Qt::white)); + + const QIcon aliasOnIcon = + Utils::StyleHelper::getIconFromIconFont(fontName, + Theme::getIconUnicode(Theme::Icon::idAliasOn), + 28, 28, QColor(Qt::red)); + const QIcon aliasOffIcon = + Utils::StyleHelper::getIconFromIconFont(fontName, + Theme::getIconUnicode(Theme::Icon::idAliasOff), + 28, 28, QColor(Qt::white)); + + const QIcon lockOnIcon = + Utils::StyleHelper::getIconFromIconFont(fontName, + Theme::getIconUnicode(Theme::Icon::lockOn), + 28, 28, QColor(Qt::white)); + const QIcon lockOffIcon = + Utils::StyleHelper::getIconFromIconFont(fontName, + Theme::getIconUnicode(Theme::Icon::lockOff), + 28, 28, QColor(Qt::white)); + auto idDelegate = new NameItemDelegate(this); - IconCheckboxItemDelegate *showDelegate = - new IconCheckboxItemDelegate(this, - Utils::Icons::EYE_OPEN_TOOLBAR.icon(), - Utils::Icons::EYE_CLOSED_TOOLBAR.icon()); - IconCheckboxItemDelegate *exportDelegate = - new IconCheckboxItemDelegate(this, - Icons::EXPORT_CHECKED.icon(), - Icons::EXPORT_UNCHECKED.icon()); + IconCheckboxItemDelegate *visibilityDelegate = + new IconCheckboxItemDelegate(this, visibilityOnIcon, visibilityOffIcon); -#ifdef _LOCK_ITEMS_ - IconCheckboxItemDelegate *lockDelegate = - new IconCheckboxItemDelegate(this, - Utils::Icons::LOCKED_TOOLBAR.icon(), - Utils::Icons::UNLOCKED_TOOLBAR.icon()); -#endif + IconCheckboxItemDelegate *aliasDelegate = + new IconCheckboxItemDelegate(this, aliasOnIcon, aliasOffIcon); + IconCheckboxItemDelegate *lockDelegate = + new IconCheckboxItemDelegate(this, lockOnIcon, lockOffIcon); - treeWidget()->setItemDelegateForColumn(0, idDelegate); -#ifdef _LOCK_ITEMS_ - treeWidget()->setItemDelegateForColumn(1,lockDelegate); - treeWidget()->setItemDelegateForColumn(2,showDelegate); -#else - treeWidget()->setItemDelegateForColumn(1, exportDelegate); - treeWidget()->setItemDelegateForColumn(2, showDelegate); -#endif + treeWidget()->setItemDelegateForColumn(NavigatorTreeModel::ColumnType::Name, idDelegate); + treeWidget()->setItemDelegateForColumn(NavigatorTreeModel::ColumnType::Alias, aliasDelegate); + treeWidget()->setItemDelegateForColumn(NavigatorTreeModel::ColumnType::Visibility, visibilityDelegate); + treeWidget()->setItemDelegateForColumn(NavigatorTreeModel::ColumnType::Lock, lockDelegate); #endif //QMLDESIGNER_TEST } diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.h b/src/plugins/qmldesigner/components/navigator/navigatorview.h index 67042634e90..6189b24559a 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorview.h +++ b/src/plugins/qmldesigner/components/navigator/navigatorview.h @@ -82,7 +82,7 @@ public: void propertiesRemoved(const QList<AbstractProperty>& propertyList) override; void selectedNodesChanged(const QList<ModelNode> &selectedNodeList , - const QList<ModelNode> &lastSelectedNodeList) override; + const QList<ModelNode> &lastSelectedNodeList) override; void auxiliaryDataChanged(const ModelNode &node, const PropertyName &name, const QVariant &data) override; void instanceErrorChanged(const QVector<ModelNode> &errorNodeList) override; diff --git a/src/plugins/qmldesigner/components/navigator/navigatorwidget.cpp b/src/plugins/qmldesigner/components/navigator/navigatorwidget.cpp index 3813881f550..649b53b5ffa 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorwidget.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorwidget.cpp @@ -78,7 +78,7 @@ NavigatorWidget::NavigatorWidget(NavigatorView *view) #endif } -void NavigatorWidget::setTreeModel(QAbstractItemModel* model) +void NavigatorWidget::setTreeModel(QAbstractItemModel *model) { m_treeView->setModel(model); } @@ -92,7 +92,6 @@ QList<QToolButton *> NavigatorWidget::createToolBarWidgets() { QList<QToolButton *> buttons; - auto button = new QToolButton(); button->setIcon(Icons::ARROW_LEFT.icon()); button->setToolTip(tr("Become last sibling of parent (CTRL + Left).")); @@ -180,7 +179,6 @@ void NavigatorWidget::enableNavigator() m_treeView->setEnabled(true); } - NavigatorView *NavigatorWidget::navigatorView() const { return m_navigatorView.data(); diff --git a/src/plugins/qmldesigner/components/navigator/previewtooltip.ui b/src/plugins/qmldesigner/components/navigator/previewtooltip.ui index ccfcb0a6c46..65e7cb01cee 100644 --- a/src/plugins/qmldesigner/components/navigator/previewtooltip.ui +++ b/src/plugins/qmldesigner/components/navigator/previewtooltip.ui @@ -82,12 +82,6 @@ </property> <item> <widget class="QLabel" name="imageLabel"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> <property name="minimumSize"> <size> <width>150</width> @@ -100,9 +94,6 @@ <property name="frameShadow"> <enum>QFrame::Plain</enum> </property> - <property name="text"> - <string notr="true"><image></string> - </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> diff --git a/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.cpp b/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.cpp new file mode 100644 index 00000000000..d3c972c2176 --- /dev/null +++ b/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.cpp @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "previewimagetooltip.h" +#include "ui_previewimagetooltip.h" + +#include <utils/theme/theme.h> + +#include <QtGui/qpixmap.h> + +namespace QmlDesigner { + +PreviewImageTooltip::PreviewImageTooltip(QWidget *parent) + : QWidget(parent) + , m_ui(std::make_unique<Ui::PreviewImageTooltip>()) +{ + // setAttribute(Qt::WA_TransparentForMouseEvents); + setWindowFlags(Qt::ToolTip); + m_ui->setupUi(this); + setStyleSheet(QString("QWidget { background-color: %1 }").arg(Utils::creatorTheme()->color(Utils::Theme::BackgroundColorNormal).name())); +} + +PreviewImageTooltip::~PreviewImageTooltip() = default; + +void PreviewImageTooltip::setComponentPath(const QString &path) +{ + m_ui->componentPathLabel->setText(path); +} + +void PreviewImageTooltip::setComponentName(const QString &name) +{ + m_ui->componentNameLabel->setText(name); +} + +void PreviewImageTooltip::setImage(const QImage &image) +{ + resize(image.width() + 20 + m_ui->componentNameLabel->width(), + std::max(image.height() + 20, height())); + m_ui->imageLabel->setPixmap(QPixmap::fromImage({image})); +} +} diff --git a/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.h b/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.h new file mode 100644 index 00000000000..e05b8a07277 --- /dev/null +++ b/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include <QtWidgets/qwidget.h> +#include <QtGui/qpixmap.h> + +#include <memory> + +namespace QmlDesigner { +namespace Ui { +class PreviewImageTooltip; +} + +class PreviewImageTooltip : public QWidget +{ + Q_OBJECT + +public: + explicit PreviewImageTooltip(QWidget *parent = {}); + ~PreviewImageTooltip(); + + void setComponentPath(const QString &path); + void setComponentName(const QString &name); + void setImage(const QImage &pixmap); + +private: + std::unique_ptr<Ui::PreviewImageTooltip> m_ui; +}; +} diff --git a/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.ui b/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.ui new file mode 100644 index 00000000000..16f34fae07d --- /dev/null +++ b/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.ui @@ -0,0 +1,158 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QmlDesigner::PreviewImageTooltip</class> + <widget class="QWidget" name="QmlDesigner::PreviewImageTooltip"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>200</width> + <height>200</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>1000</width> + <height>1000</height> + </size> + </property> + <property name="windowTitle"> + <string/> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="sizeGripEnabled" stdset="0"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>1</number> + </property> + <property name="topMargin"> + <number>1</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QFrame" name="frame"> + <property name="frameShape"> + <enum>QFrame::Box</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <property name="lineWidth"> + <number>1</number> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="1"> + <widget class="QLabel" name="componentPathLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string notr="true"/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::NoTextInteraction</set> + </property> + </widget> + </item> + <item row="0" column="0" rowspan="2"> + <widget class="QLabel" name="imageLabel"> + <property name="frameShape"> + <enum>QFrame::Box</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="Utils::ElidingLabel" name="componentNameLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>12</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string notr="true"/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::NoTextInteraction</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>Utils::ElidingLabel</class> + <extends>QLabel</extends> + <header location="global">utils/elidinglabel.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.cpp b/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.cpp new file mode 100644 index 00000000000..559cd5c17b9 --- /dev/null +++ b/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.cpp @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "previewtooltipbackend.h" + +#include "previewimagetooltip.h" + +#include <coreplugin/icore.h> +#include <imagecache.h> + +#include <QApplication> +#include <QDesktopWidget> +#include <QMetaObject> + +namespace QmlDesigner { + +PreviewTooltipBackend::PreviewTooltipBackend(ImageCache &cache) + : m_cache{cache} +{} + +PreviewTooltipBackend::~PreviewTooltipBackend() +{ + hideTooltip(); +} + +void PreviewTooltipBackend::showTooltip() +{ + if (m_componentPath.isEmpty()) + return; + + m_tooltip = std::make_unique<PreviewImageTooltip>(); + + m_tooltip->setComponentName(m_componentName); + m_tooltip->setComponentPath(m_componentPath); + + m_cache.requestImage( + m_componentPath, + [tooltip = QPointer<PreviewImageTooltip>(m_tooltip.get())](const QImage &image) { + QMetaObject::invokeMethod(tooltip, [tooltip, image] { + if (tooltip) + tooltip->setImage(image); + }); + }, + [] {}); + + auto desktopWidget = QApplication::desktop(); + auto mousePosition = desktopWidget->cursor().pos(); + + mousePosition += {20, 20}; + m_tooltip->move(mousePosition); + m_tooltip->show(); +} + +void PreviewTooltipBackend::hideTooltip() +{ + if (m_tooltip) + m_tooltip->hide(); + + m_tooltip.reset(); +} + +QString QmlDesigner::PreviewTooltipBackend::componentPath() const +{ + return m_componentPath; +} + +void QmlDesigner::PreviewTooltipBackend::setComponentPath(const QString &path) +{ + m_componentPath = path; + + if (m_componentPath != path) + emit componentPathChanged(); +} + +QString QmlDesigner::PreviewTooltipBackend::componentName() const +{ + return m_componentName; +} + +void QmlDesigner::PreviewTooltipBackend::setComponentName(const QString &name) +{ + m_componentName = name; + + if (m_componentName != name) + emit componentNameChanged(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.h b/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.h new file mode 100644 index 00000000000..b5f777662b9 --- /dev/null +++ b/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QObject> +#include <QQmlEngine> + +#include <memory> + +namespace QmlDesigner { + +class PreviewImageTooltip; +class ImageCache; + +class PreviewTooltipBackend : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString componentPath READ componentPath WRITE setComponentPath NOTIFY componentPathChanged) + Q_PROPERTY(QString componentName READ componentName WRITE setComponentName NOTIFY componentNameChanged) + +public: + PreviewTooltipBackend(ImageCache &cache); + ~PreviewTooltipBackend(); + + Q_INVOKABLE void showTooltip(); + Q_INVOKABLE void hideTooltip(); + + QString componentPath() const; + void setComponentPath(const QString &path); + + QString componentName() const; + void setComponentName(const QString &path); + +signals: + void componentPathChanged(); + void componentNameChanged(); + +private: + QString m_componentPath; + QString m_componentName; + std::unique_ptr<PreviewImageTooltip> m_tooltip; + ImageCache &m_cache; +}; + +} + +QML_DECLARE_TYPE(QmlDesigner::PreviewTooltipBackend) diff --git a/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.pri b/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.pri new file mode 100644 index 00000000000..a33c5d98538 --- /dev/null +++ b/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.pri @@ -0,0 +1,11 @@ +HEADERS += \ + $$PWD/previewtooltipbackend.h \ + $$PWD/previewimagetooltip.h + +SOURCES += \ + $$PWD/previewtooltipbackend.cpp \ + $$PWD/previewimagetooltip.cpp + +FORMS += $$PWD/previewimagetooltip.ui + + diff --git a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp index a42db492d9e..71e2c996277 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp @@ -29,15 +29,21 @@ #include <model.h> +#include <utils/algorithm.h> + #include <QFileDialog> #include <QDirIterator> #include <qmlmodelnodeproxy.h> static QString s_lastBrowserPath; -FileResourcesModel::FileResourcesModel(QObject *parent) : - QObject(parent), m_filter(QLatin1String("(*.*)")), m_lock(false) +FileResourcesModel::FileResourcesModel(QObject *parent) + : QObject(parent) + , m_filter(QLatin1String("(*.*)")) + , m_fileSystemWatcher(new Utils::FileSystemWatcher(this)) { + connect(m_fileSystemWatcher, &Utils::FileSystemWatcher::directoryChanged, + this, &FileResourcesModel::refreshModel); } void FileResourcesModel::setModelNodeBackend(const QVariant &modelNodeBackend) @@ -163,7 +169,6 @@ QVariant FileResourcesModel::modelNodeBackend() const bool filterMetaIcons(const QString &fileName) { - QFileInfo info(fileName); if (info.dir().path().split('/').contains("designer")) { @@ -189,12 +194,20 @@ bool filterMetaIcons(const QString &fileName) void FileResourcesModel::setupModel() { - m_lock = true; + m_dirPath = QFileInfo(m_path.toLocalFile()).dir(); + + refreshModel(); + + m_fileSystemWatcher->removeDirectories(m_fileSystemWatcher->directories()); + m_fileSystemWatcher->addDirectory(m_dirPath.absolutePath(), + Utils::FileSystemWatcher::WatchAllChanges); +} + +void FileResourcesModel::refreshModel() +{ m_fullPathModel.clear(); m_fileNameModel.clear(); - m_dirPath = QFileInfo(m_path.toLocalFile()).dir(); - QStringList filterList = m_filter.split(QLatin1Char(' ')); QDirIterator it(m_dirPath.absolutePath(), filterList, QDir::Files, QDirIterator::Subdirectories); @@ -203,11 +216,15 @@ void FileResourcesModel::setupModel() if (filterMetaIcons(absolutePath)) { QString filePath = m_dirPath.relativeFilePath(absolutePath); m_fullPathModel.append(filePath); - m_fileNameModel.append(filePath.mid(filePath.lastIndexOf('/') + 1)); } } - m_lock = false; + Utils::sort(m_fullPathModel, [](const QString &s1, const QString &s2) { + return s1.mid(s1.lastIndexOf('/') + 1).toLower() < s2.mid(s2.lastIndexOf('/') + 1).toLower(); + }); + + for (const QString &fullPath : qAsConst(m_fullPathModel)) + m_fileNameModel.append(fullPath.mid(fullPath.lastIndexOf('/') + 1)); emit fullPathModelChanged(); emit fileNameModelChanged(); diff --git a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h index e0a19643479..226421f441f 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h +++ b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h @@ -27,6 +27,8 @@ #include <qmlitemnode.h> +#include <utils/filesystemwatcher.h> + #include <QDir> #include <QObject> #include <QStringList> @@ -60,6 +62,7 @@ public: QStringList fullPathModel() const; QStringList fileNameModel() const; void setupModel(); + void refreshModel(); Q_INVOKABLE void openFileDialog(); @@ -79,12 +82,11 @@ private: QUrl m_path; QDir m_dirPath; QString m_filter; - bool m_lock; QString m_currentPath; QString m_lastModelPath; QStringList m_fullPathModel; QStringList m_fileNameModel; - + Utils::FileSystemWatcher *m_fileSystemWatcher; }; QML_DECLARE_TYPE(FileResourcesModel) diff --git a/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp b/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp index 3a152836633..a8792ef2041 100644 --- a/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp +++ b/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp @@ -30,6 +30,7 @@ #include <QDebug> #include <QRegularExpression> +#include <QMessageBox> #include <cmath> #include <memory> @@ -42,6 +43,7 @@ #include <qmlitemnode.h> #include <qmlstate.h> #include <annotationeditor/annotationeditor.h> +#include <utils/algorithm.h> namespace QmlDesigner { @@ -92,11 +94,46 @@ void StatesEditorView::removeState(int nodeId) if (nodeId > 0 && hasModelNodeForInternalId(nodeId)) { ModelNode stateNode(modelNodeForInternalId(nodeId)); Q_ASSERT(stateNode.metaInfo().isSubclassOf("QtQuick.State")); + + QmlModelState modelState(stateNode); + if (modelState.isValid()) { + QStringList lockedTargets; + const auto propertyChanges = modelState.propertyChanges(); + for (const QmlPropertyChanges &change : propertyChanges) { + const ModelNode target = change.target(); + if (target.locked()) + lockedTargets.push_back(target.id()); + } + + if (!lockedTargets.empty()) { + Utils::sort(lockedTargets); + QString detailedText = QString("<b>" + tr("Locked items:") + "</b><br>"); + + for (const auto &id : qAsConst(lockedTargets)) + detailedText.append("- " + id + "<br>"); + + detailedText.chop(QString("<br>").size()); + + QMessageBox msgBox; + msgBox.setTextFormat(Qt::RichText); + msgBox.setIcon(QMessageBox::Question); + msgBox.setWindowTitle(tr("Remove State")); + msgBox.setText(QString(tr("Removing this state will modify locked items.") + "<br><br>%1") + .arg(detailedText)); + msgBox.setInformativeText(tr("Do you want to continue by removing the state?")); + msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Ok); + + if (msgBox.exec() == QMessageBox::Cancel) + return; + } + } + NodeListProperty parentProperty = stateNode.parentProperty().toNodeListProperty(); if (parentProperty.count() <= 1) { setCurrentState(baseState()); - } else if (parentProperty.isValid()){ + } else if (parentProperty.isValid()) { int index = parentProperty.indexOf(stateNode); if (index == 0) setCurrentState(parentProperty.at(1)); @@ -104,7 +141,6 @@ void StatesEditorView::removeState(int nodeId) setCurrentState(parentProperty.at(index - 1)); } - stateNode.destroy(); } } catch (const RewritingException &e) { diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.cpp index a0f0eedf292..007e8ae571a 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.cpp @@ -63,6 +63,11 @@ TimelineGraphicsLayout::TimelineGraphicsLayout(TimelineGraphicsScene *scene, Tim TimelineGraphicsLayout::~TimelineGraphicsLayout() = default; +int TimelineGraphicsLayout::zoom() const +{ + return m_rulerItem->zoom(); +} + double TimelineGraphicsLayout::rulerWidth() const { return m_rulerItem->preferredWidth(); @@ -133,12 +138,12 @@ void TimelineGraphicsLayout::setTimeline(const QmlTimeline &timeline) if (auto *scene = timelineScene()) if (auto *view = scene->timelineView()) if (!timeline.isValid() && view->isAttached()) - emit scaleFactorChanged(0); + emit zoomChanged(0); } -void TimelineGraphicsLayout::setRulerScaleFactor(int factor) +void TimelineGraphicsLayout::setZoom(int factor) { - m_rulerItem->setRulerScaleFactor(factor); + m_rulerItem->setZoom(factor); } void TimelineGraphicsLayout::invalidate() diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.h b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.h index f4d2ae58364..ce2423dfe97 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.h @@ -44,7 +44,7 @@ class TimelineGraphicsLayout : public TimelineItem signals: void rulerClicked(const QPointF &pos); - void scaleFactorChanged(int factor); + void zoomChanged(int factor); public: TimelineGraphicsLayout(TimelineGraphicsScene *scene, TimelineItem *parent = nullptr); @@ -52,6 +52,8 @@ public: ~TimelineGraphicsLayout() override; public: + int zoom() const; + double rulerWidth() const; double rulerScaling() const; @@ -66,7 +68,7 @@ public: void setTimeline(const QmlTimeline &timeline); - void setRulerScaleFactor(int factor); + void setZoom(int factor); void invalidate(); diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp index 3b87f22c517..5c2621300fb 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp @@ -123,9 +123,9 @@ TimelineGraphicsScene::TimelineGraphicsScene(TimelineWidget *parent) auto changeScale = [this](int factor) { timelineWidget()->changeScaleFactor(factor); - setRulerScaling(qreal(factor)); + setZoom(factor); }; - connect(m_layout, &TimelineGraphicsLayout::scaleFactorChanged, changeScale); + connect(m_layout, &TimelineGraphicsLayout::zoomChanged, changeScale); } TimelineGraphicsScene::~TimelineGraphicsScene() @@ -144,7 +144,7 @@ void TimelineGraphicsScene::onShow() setCurrentFrame(cf); } - emit m_layout->scaleFactorChanged(0); + emit m_layout->zoomChanged(0); } } @@ -271,6 +271,11 @@ void TimelineGraphicsScene::setEndFrame(int frame) timeline.modelNode().variantProperty("endFrame").setValue(frame); } +int TimelineGraphicsScene::zoom() const +{ + return m_layout->zoom(); +} + qreal TimelineGraphicsScene::rulerScaling() const { return m_layout->rulerScaling(); @@ -332,15 +337,20 @@ QVector<qreal> TimelineGraphicsScene::keyframePositions(const QmlTimelineKeyfram return positions; } -void TimelineGraphicsScene::setRulerScaling(int scaleFactor) +void TimelineGraphicsScene::setZoom(int scaleFactor) +{ + setZoom(scaleFactor, currentFramePosition()); +} + +void TimelineGraphicsScene::setZoom(int scaleFactor, double pivot) { const qreal oldOffset = scrollOffset(); const qreal oldScaling = m_layout->rulerScaling(); - const qreal oldPosition = mapToScene(currentFramePosition()); - m_layout->setRulerScaleFactor(scaleFactor); + const qreal oldPosition = mapToScene(pivot); + m_layout->setZoom(scaleFactor); const qreal newScaling = m_layout->rulerScaling(); - const qreal newPosition = mapToScene(currentFramePosition()); + const qreal newPosition = mapToScene(pivot); const qreal newOffset = oldOffset + (newPosition - oldPosition); @@ -428,6 +438,18 @@ void TimelineGraphicsScene::invalidateKeyframesForTarget(const ModelNode &target TimelineSectionItem::updateFramesForTarget(child, target); } +void TimelineGraphicsScene::invalidateHeightForTarget(const ModelNode &target) +{ + if (!target.isValid()) + return; + + const auto children = m_layout->childItems(); + for (auto child : children) + TimelineSectionItem::updateHeightForTarget(child, target); + + invalidateLayout(); +} + void TimelineGraphicsScene::invalidateScene() { ModelNode node = timelineView()->modelNodeForId( @@ -502,7 +524,7 @@ QRectF AbstractScrollGraphicsScene::selectionBounds() const } void AbstractScrollGraphicsScene::selectKeyframes(const SelectionMode &mode, - const QList<TimelineKeyframeItem *> &items) + const QList<TimelineKeyframeItem *> &items) { if (mode == SelectionMode::Remove || mode == SelectionMode::Toggle) { for (auto *item : items) { @@ -731,7 +753,7 @@ void TimelineGraphicsScene::deleteKeyframeGroup(const ModelNode &group) if (!QmlTimelineKeyframeGroup::isValidQmlTimelineKeyframeGroup(group)) return; - timelineView()->executeInTransaction("TimelineGraphicsScene::handleKeyframeGroupDeletion", [group](){ + timelineView()->executeInTransaction("TimelineGraphicsScene::handleKeyframeGroupDeletion", [group]() { ModelNode nonConst = group; nonConst.destroy(); }); @@ -739,7 +761,7 @@ void TimelineGraphicsScene::deleteKeyframeGroup(const ModelNode &group) void TimelineGraphicsScene::deleteKeyframes(const QList<ModelNode> &frames) { - timelineView()->executeInTransaction("TimelineGraphicsScene::handleKeyframeDeletion", [frames](){ + timelineView()->executeInTransaction("TimelineGraphicsScene::handleKeyframeDeletion", [frames]() { for (auto keyframe : frames) { if (keyframe.isValid()) { ModelNode frame = keyframe; @@ -764,7 +786,7 @@ AbstractView *TimelineGraphicsScene::abstractView() const int AbstractScrollGraphicsScene::getScrollOffset(QGraphicsScene *scene) { - auto scrollScene = qobject_cast<AbstractScrollGraphicsScene*>(scene); + auto scrollScene = qobject_cast<AbstractScrollGraphicsScene *>(scene); if (scrollScene) return scrollScene->scrollOffset(); return 0; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h index 7413cb1dbb2..fc4da39f322 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h @@ -58,7 +58,6 @@ class AbstractScrollGraphicsScene : public QGraphicsScene public: AbstractScrollGraphicsScene(QWidget *parent); - ; int scrollOffset() const; void setScrollOffset(int offset); @@ -74,6 +73,7 @@ public: bool isKeyframeSelected(TimelineKeyframeItem *keyframe) const; bool multipleKeyframesSelected() const; + virtual int zoom() const = 0; virtual qreal rulerScaling() const = 0; virtual int rulerWidth() const = 0; virtual qreal rulerDuration() const = 0; @@ -134,6 +134,7 @@ public: TimelineWidget *timelineWidget() const; TimelineToolBar *toolBar() const; + int zoom() const override; qreal rulerScaling() const override; int rulerWidth() const override; qreal rulerDuration() const override; @@ -152,12 +153,14 @@ public: qreal snap(qreal frame, bool snapToPlayhead = true) override; - void setRulerScaling(int scaling); + void setZoom(int scaling); + void setZoom(int scaling, double pivot); void commitCurrentFrame(qreal frame); void invalidateSectionForTarget(const ModelNode &modelNode); void invalidateKeyframesForTarget(const ModelNode &modelNode); + void invalidateHeightForTarget(const ModelNode &modelNode); void invalidateScene(); void invalidateScrollbar() override; @@ -203,7 +206,6 @@ private: QList<QGraphicsItem *> itemsAt(const QPointF &pos); private: - TimelineWidget *m_parent = nullptr; TimelineGraphicsLayout *m_layout = nullptr; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp index 12345a404e4..1b0b2cb7e53 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp @@ -156,4 +156,9 @@ TimelineFrameHandle *TimelineMovableAbstractItem::asTimelineFrameHandle() return nullptr; } +bool TimelineMovableAbstractItem::isLocked() const +{ + return false; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h index 199a78ad997..d79101b4d9d 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h @@ -69,6 +69,8 @@ public: virtual TimelineKeyframeItem *asTimelineKeyframeItem(); virtual TimelineFrameHandle *asTimelineFrameHandle(); + virtual bool isLocked() const; + protected: int scrollOffset() const; void mousePressEvent(QGraphicsSceneMouseEvent *event) override; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp index 5dc52bbc9b8..0508606c11f 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp @@ -70,6 +70,9 @@ void TimelineMoveTool::mousePressEvent(TimelineMovableAbstractItem *item, { Q_UNUSED(item) + if (currentItem() && currentItem()->isLocked()) + return; + if (auto *current = currentItem()->asTimelineKeyframeItem()) { const qreal sourceFrame = qRound(current->mapFromSceneToFrame(current->rect().center().x())); const qreal targetFrame = qRound(current->mapFromSceneToFrame(event->scenePos().x())); @@ -85,6 +88,9 @@ void TimelineMoveTool::mouseMoveEvent(TimelineMovableAbstractItem *item, if (!currentItem()) return; + if (currentItem()->isLocked()) + return; + if (auto *current = currentItem()->asTimelineKeyframeItem()) { // prevent dragging if deselecting a keyframe (Ctrl+click and drag a selected keyframe) if (!current->highlighted()) diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp index c77d466585f..1cff8012353 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp @@ -171,6 +171,17 @@ void TimelineSectionItem::updateFramesForTarget(QGraphicsItem *item, const Model } } +void TimelineSectionItem::updateHeightForTarget(QGraphicsItem *item, const ModelNode &target) +{ + if (!target.isValid()) + return; + + if (auto sectionItem = qgraphicsitem_cast<TimelineSectionItem *>(item)) { + if (sectionItem->targetNode() == target) + sectionItem->updateHeight(); + } +} + void TimelineSectionItem::moveAllFrames(qreal offset) { if (m_timeline.isValid()) @@ -313,7 +324,8 @@ void TimelineSectionItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) if (event->button() == Qt::LeftButton) { event->accept(); - toggleCollapsed(); + if (!ModelNode::isThisOrAncestorLocked(m_targetNode)) + toggleCollapsed(); } } @@ -345,7 +357,8 @@ void TimelineSectionItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) if (m_targetNode.isValid()) m_targetNode.view()->setSelectedModelNode(m_targetNode); } else { - toggleCollapsed(); + if (!ModelNode::isThisOrAncestorLocked(m_targetNode)) + toggleCollapsed(); } update(); } @@ -414,6 +427,12 @@ void TimelineSectionItem::updateFrames() update(); } +void TimelineSectionItem::updateHeight() +{ + invalidateHeight(); + update(); +} + void TimelineSectionItem::invalidateHeight() { int height = 0; @@ -464,7 +483,8 @@ void TimelineSectionItem::invalidateFrames() bool TimelineSectionItem::collapsed() const { - return m_targetNode.isValid() && !m_targetNode.hasAuxiliaryData("timeline_expanded"); + return m_targetNode.isValid() + && (!m_targetNode.hasAuxiliaryData("timeline_expanded") || m_targetNode.locked()); } void TimelineSectionItem::createPropertyItems() @@ -549,9 +569,9 @@ void TimelineRulerSectionItem::invalidateRulerSize(const qreal length) m_end = length; } -void TimelineRulerSectionItem::setRulerScaleFactor(int scaling) +void TimelineRulerSectionItem::setZoom(int zoom) { - qreal blend = qreal(scaling) / 100.0; + qreal blend = qreal(zoom) / 100.0; qreal width = size().width() - qreal(TimelineConstants::sectionWidth); qreal duration = rulerDuration(); @@ -572,7 +592,7 @@ void TimelineRulerSectionItem::setRulerScaleFactor(int scaling) update(); } -int TimelineRulerSectionItem::getRulerScaleFactor() const +int TimelineRulerSectionItem::zoom() const { qreal width = size().width() - qreal(TimelineConstants::sectionWidth); qreal duration = rulerDuration(); @@ -589,7 +609,7 @@ int TimelineRulerSectionItem::getRulerScaleFactor() const qreal rcount = width / m_scaling; qreal rblend = TimelineUtils::reverseLerp(rcount, minCount, maxCount); - int rfactor = std::round(rblend * 100); + int rfactor = static_cast<int>(std::round(rblend * 100)); return TimelineUtils::clamp(rfactor, 0, 100); } @@ -766,7 +786,7 @@ void TimelineRulerSectionItem::resizeEvent(QGraphicsSceneResizeEvent *event) { QGraphicsWidget::resizeEvent(event); - auto factor = getRulerScaleFactor(); + auto factor = zoom(); if (factor < 0) { if (event->oldSize().width() < event->newSize().width()) @@ -775,7 +795,7 @@ void TimelineRulerSectionItem::resizeEvent(QGraphicsSceneResizeEvent *event) factor = 100; } - emit scaleFactorChanged(factor); + emit zoomChanged(factor); } void TimelineRulerSectionItem::setSizeHints(int width) @@ -845,6 +865,11 @@ void TimelineBarItem::commitPosition(const QPointF & /*point*/) m_oldRect = QRectF(); } +bool TimelineBarItem::isLocked() const +{ + return sectionItem()->targetNode().isValid() && sectionItem()->targetNode().locked(); +} + void TimelineBarItem::scrollOffsetChanged() { sectionItem()->invalidateBar(); @@ -904,7 +929,9 @@ void TimelineBarItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) const auto p = event->pos(); QRectF left, right; - if (handleRects(rect(), left, right)) { + if (isLocked() && rect().contains(p)) { + setCursor(QCursor(Qt::ForbiddenCursor)); + } else if (handleRects(rect(), left, right)) { if (left.contains(p) || right.contains(p)) { if (cursor().shape() != Qt::SizeHorCursor) setCursor(QCursor(Qt::SizeHorCursor)); @@ -920,6 +947,9 @@ void TimelineBarItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) void TimelineBarItem::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { + if (isLocked()) + return; + QMenu menu; QAction* overrideColor = menu.addAction(tr("Override Color")); diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h index e5403bcb74f..956ef31ef78 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h @@ -50,6 +50,8 @@ public: void itemMoved(const QPointF &start, const QPointF &end) override; void commitPosition(const QPointF &point) override; + bool isLocked() const override; + protected: void scrollOffsetChanged() override; void paint(QPainter *painter, @@ -100,6 +102,7 @@ public: static void updateData(QGraphicsItem *item); static void updateDataForTarget(QGraphicsItem *item, const ModelNode &target, bool *b); static void updateFramesForTarget(QGraphicsItem *item, const ModelNode &target); + static void updateHeightForTarget(QGraphicsItem *item, const ModelNode &target); void moveAllFrames(qreal offset); void scaleAllFrames(qreal scale); @@ -121,6 +124,7 @@ protected: private: void updateData(); void updateFrames(); + void updateHeight(); void invalidateHeight(); void invalidateProperties(); void invalidateFrames(); @@ -145,7 +149,7 @@ class TimelineRulerSectionItem : public TimelineItem signals: void rulerClicked(const QPointF &pos); - void scaleFactorChanged(int scale); + void zoomChanged(int zoom); public: static TimelineRulerSectionItem *create(QGraphicsScene *parentScene, TimelineItem *parent); @@ -153,9 +157,9 @@ public: void invalidateRulerSize(const QmlTimeline &timeline); void invalidateRulerSize(const qreal length); - void setRulerScaleFactor(int scaling); + void setZoom(int zoom); - int getRulerScaleFactor() const; + int zoom() const; qreal getFrameTick() const; qreal rulerScaling() const; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp index d0679c80bb6..dab580a1469 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp @@ -236,6 +236,18 @@ void TimelineView::selectedNodesChanged(const QList<ModelNode> & /*selectedNodeL m_timelineWidget->graphicsScene()->update(); } +void TimelineView::auxiliaryDataChanged(const ModelNode &modelNode, + const PropertyName &name, + const QVariant &data) +{ + if (name == QmlDesigner::lockedProperty && data.toBool() && modelNode.isValid()) { + for (const auto &node : modelNode.allSubModelNodesAndThisNode()) { + if (node.hasAuxiliaryData("timeline_expanded")) + m_timelineWidget->graphicsScene()->invalidateHeightForTarget(node); + } + } +} + void TimelineView::propertiesAboutToBeRemoved(const QList<AbstractProperty> &propertyList) { for (const auto &property : propertyList) { diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineview.h b/src/plugins/qmldesigner/components/timelineeditor/timelineview.h index fe3f5903ff3..f1f39b6a35f 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineview.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineview.h @@ -62,6 +62,9 @@ public: PropertyChangeFlags propertyChange) override; void selectedNodesChanged(const QList<ModelNode> &selectedNodeList, const QList<ModelNode> &lastSelectedNodeList) override; + void auxiliaryDataChanged(const ModelNode &node, + const PropertyName &name, + const QVariant &data) override; void propertiesAboutToBeRemoved(const QList<AbstractProperty> &propertyList) override; void propertiesRemoved(const QList<AbstractProperty> &propertyList) override; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp index e740fb924ec..6c7c15451de 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp @@ -34,6 +34,7 @@ #include "timelinepropertyitem.h" #include "timelinetoolbar.h" #include "timelineview.h" +#include "navigation2d.h" #include <qmldesignerplugin.h> #include <qmlstate.h> @@ -60,6 +61,8 @@ #include <QtGlobal> #include <QSpacerItem> +#include <cmath> + namespace QmlDesigner { class Eventfilter : public QObject @@ -114,7 +117,7 @@ TimelineWidget::TimelineWidget(TimelineView *view) , m_toolbar(new TimelineToolBar(this)) , m_rulerView(new QGraphicsView(this)) , m_graphicsView(new QGraphicsView(this)) - , m_scrollbar(new QScrollBar(this)) + , m_scrollbar(new Navigation2dScrollBar(this)) , m_statusBar(new QLabel(this)) , m_timelineView(view) , m_graphicsScene(new TimelineGraphicsScene(this)) @@ -153,6 +156,7 @@ TimelineWidget::TimelineWidget(TimelineView *view) m_graphicsView->setFrameShape(QFrame::NoFrame); m_graphicsView->setFrameShadow(QFrame::Plain); m_graphicsView->setLineWidth(0); + m_graphicsView->setVerticalScrollBar(new Navigation2dScrollBar); m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); m_graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -247,6 +251,14 @@ TimelineWidget::TimelineWidget(TimelineView *view) connect(m_addButton, &QPushButton::clicked, this, [this]() { m_timelineView->addNewTimelineDialog(); }); + + Navigation2dFilter *filter = new Navigation2dFilter(this, m_scrollbar); + connect(filter, &Navigation2dFilter::zoomChanged, [this](double scale, const QPointF& pos) { + int s = static_cast<int>(std::round(scale*100.)); + double ps = m_graphicsScene->mapFromScene(pos.x()); + m_graphicsScene->setZoom(std::clamp(m_graphicsScene->zoom() + s, 0, 100), ps); + }); + installEventFilter(filter); } void TimelineWidget::connectToolbar() @@ -258,8 +270,8 @@ void TimelineWidget::connectToolbar() connect(graphicsScene(), &TimelineGraphicsScene::scroll, this, &TimelineWidget::scroll); - auto setRulerScaling = [this](int val) { m_graphicsScene->setRulerScaling(val); }; - connect(m_toolbar, &TimelineToolBar::scaleFactorChanged, setRulerScaling); + auto setZoomFactor = [this](int val) { m_graphicsScene->setZoom(val); }; + connect(m_toolbar, &TimelineToolBar::scaleFactorChanged, setZoomFactor); auto setToFirstFrame = [this]() { graphicsScene()->setCurrentFrame(graphicsScene()->startFrame()); @@ -428,7 +440,7 @@ void TimelineWidget::init() // setScaleFactor uses QSignalBlocker. m_toolbar->setScaleFactor(0); - m_graphicsScene->setRulerScaling(0); + m_graphicsScene->setZoom(0); } void TimelineWidget::reset() diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h index b546452e560..b1e24caf937 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h @@ -36,7 +36,6 @@ QT_FORWARD_DECLARE_CLASS(QComboBox) QT_FORWARD_DECLARE_CLASS(QGraphicsView) QT_FORWARD_DECLARE_CLASS(QLabel) QT_FORWARD_DECLARE_CLASS(QResizeEvent) -QT_FORWARD_DECLARE_CLASS(QScrollBar) QT_FORWARD_DECLARE_CLASS(QShowEvent) QT_FORWARD_DECLARE_CLASS(QString) QT_FORWARD_DECLARE_CLASS(QPushButton) @@ -47,6 +46,7 @@ class TimelineToolBar; class TimelineView; class TimelineGraphicsScene; class QmlTimeline; +class Navigation2dScrollBar; class TimelineWidget : public QWidget { @@ -94,7 +94,7 @@ private: QGraphicsView *m_graphicsView = nullptr; - QScrollBar *m_scrollbar = nullptr; + Navigation2dScrollBar *m_scrollbar = nullptr; QLabel *m_statusBar = nullptr; diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp index 02e1258dfd5..5abff4d6116 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp @@ -65,6 +65,11 @@ TransitionEditorGraphicsLayout::TransitionEditorGraphicsLayout(QGraphicsScene *s TransitionEditorGraphicsLayout::~TransitionEditorGraphicsLayout() = default; +int TransitionEditorGraphicsLayout::zoom() const +{ + return m_rulerItem->zoom(); +} + double TransitionEditorGraphicsLayout::rulerWidth() const { return m_rulerItem->preferredWidth(); @@ -133,7 +138,7 @@ void TransitionEditorGraphicsLayout::setTransition(const ModelNode &transition) if (auto *scene = timelineScene()) if (auto *view = scene->timelineView()) if (!transition.isValid() && view->isAttached()) - emit scaleFactorChanged(0); + emit zoomChanged(0); } void TransitionEditorGraphicsLayout::setDuration(qreal duration) @@ -141,9 +146,9 @@ void TransitionEditorGraphicsLayout::setDuration(qreal duration) m_rulerItem->invalidateRulerSize(duration); } -void TransitionEditorGraphicsLayout::setRulerScaleFactor(int factor) +void TransitionEditorGraphicsLayout::setZoom(int factor) { - m_rulerItem->setRulerScaleFactor(factor); + m_rulerItem->setZoom(factor); } void TransitionEditorGraphicsLayout::invalidate() diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h index 9362abffdfc..67495db20d8 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h @@ -44,7 +44,7 @@ class TransitionEditorGraphicsLayout : public TimelineItem signals: void rulerClicked(const QPointF &pos); - void scaleFactorChanged(int factor); + void zoomChanged(int factor); public: TransitionEditorGraphicsLayout(QGraphicsScene *scene, TimelineItem *parent = nullptr); @@ -52,6 +52,8 @@ public: ~TransitionEditorGraphicsLayout() override; public: + int zoom() const; + double rulerWidth() const; double rulerScaling() const; @@ -66,7 +68,7 @@ public: void setDuration(qreal duration); - void setRulerScaleFactor(int factor); + void setZoom(int factor); void invalidate(); diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp index 036fe173f5c..c080a49314e 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp @@ -104,9 +104,9 @@ TransitionEditorGraphicsScene::TransitionEditorGraphicsScene(TransitionEditorWid auto changeScale = [this](int factor) { transitionEditorWidget()->changeScaleFactor(factor); - setRulerScaling(qreal(factor)); + setZoom(factor); }; - connect(m_layout, &TransitionEditorGraphicsLayout::scaleFactorChanged, changeScale); + connect(m_layout, &TransitionEditorGraphicsLayout::zoomChanged, changeScale); } TransitionEditorGraphicsScene::~TransitionEditorGraphicsScene() @@ -125,7 +125,7 @@ void TransitionEditorGraphicsScene::invalidateScrollbar() void TransitionEditorGraphicsScene::onShow() { - emit m_layout->scaleFactorChanged(0); + emit m_layout->zoomChanged(0); } void TransitionEditorGraphicsScene::setTransition(const ModelNode &transition) @@ -157,7 +157,12 @@ void TransitionEditorGraphicsScene::setDuration(int duration) m_transition.setAuxiliaryData("transitionDuration", duration); m_layout->setDuration(duration); qreal scaling = m_layout->rulerScaling(); - setRulerScaling(scaling); + setZoom(scaling); +} + +int TransitionEditorGraphicsScene::zoom() const +{ + return m_layout->zoom(); } qreal TransitionEditorGraphicsScene::rulerScaling() const @@ -199,9 +204,9 @@ qreal TransitionEditorGraphicsScene::mapFromScene(qreal x) const return xPosOffset / rulerScaling() + startFrame(); } -void TransitionEditorGraphicsScene::setRulerScaling(int scaleFactor) +void TransitionEditorGraphicsScene::setZoom(int scaleFactor) { - m_layout->setRulerScaleFactor(scaleFactor); + m_layout->setZoom(scaleFactor); setScrollOffset(0); invalidateSections(); @@ -209,6 +214,35 @@ void TransitionEditorGraphicsScene::setRulerScaling(int scaleFactor) update(); } +void TransitionEditorGraphicsScene::setZoom(int scaling, double pivot) +{ + const qreal oldOffset = scrollOffset(); + const qreal oldScaling = m_layout->rulerScaling(); + const qreal oldPosition = mapToScene(pivot); + m_layout->setZoom(scaling); + + const qreal newScaling = m_layout->rulerScaling(); + const qreal newPosition = mapToScene(pivot); + + const qreal newOffset = oldOffset + (newPosition - oldPosition); + + if (std::isinf(oldScaling) || std::isinf(newScaling)) + setScrollOffset(0); + else { + setScrollOffset(std::round(newOffset)); + + const qreal start = mapToScene(startFrame()); + const qreal head = TimelineConstants::sectionWidth + TimelineConstants::timelineLeftOffset; + + if (start - head > 0) + setScrollOffset(0); + } + + invalidateSections(); + invalidateScrollbar(); + update(); +} + void TransitionEditorGraphicsScene::invalidateSectionForTarget(const ModelNode &target) { if (!target.isValid()) @@ -218,7 +252,7 @@ void TransitionEditorGraphicsScene::invalidateSectionForTarget(const ModelNode & const QList<QGraphicsItem *> items = m_layout->childItems(); for (auto child : items) - TimelineSectionItem::updateDataForTarget(child, target, &found); + TransitionEditorSectionItem::updateDataForTarget(child, target, &found); if (!found) invalidateScene(); @@ -227,6 +261,18 @@ void TransitionEditorGraphicsScene::invalidateSectionForTarget(const ModelNode & invalidateLayout(); } +void TransitionEditorGraphicsScene::invalidateHeightForTarget(const ModelNode &target) +{ + if (!target.isValid()) + return; + + const auto children = m_layout->childItems(); + for (auto child : children) + TransitionEditorSectionItem::updateHeightForTarget(child, target); + + invalidateLayout(); +} + void TransitionEditorGraphicsScene::invalidateScene() { invalidateScrollbar(); diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h index 2f04c5b7298..d0ade7064ca 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h @@ -81,6 +81,7 @@ public: TransitionEditorWidget *transitionEditorWidget() const; TransitionEditorToolBar *toolBar() const; + int zoom() const override; qreal rulerScaling() const override; int rulerWidth() const override; qreal rulerDuration() const override; @@ -90,9 +91,11 @@ public: qreal mapToScene(qreal x) const; qreal mapFromScene(qreal x) const; - void setRulerScaling(int scaling); + void setZoom(int scaling); + void setZoom(int scaling, double pivot); void invalidateSectionForTarget(const ModelNode &modelNode); + void invalidateHeightForTarget(const ModelNode &modelNode); void invalidateScene(); void invalidateCurrentValues(); diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp index 86442059d9c..7172465e0d5 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp @@ -196,6 +196,17 @@ void TransitionEditorSectionItem::updateData(QGraphicsItem *item) sectionItem->updateData(); } +void TransitionEditorSectionItem::updateHeightForTarget(QGraphicsItem *item, const ModelNode &target) +{ + if (!target.isValid()) + return; + + if (auto sectionItem = qgraphicsitem_cast<TransitionEditorSectionItem *>(item)) { + if (sectionItem->targetNode() == target) + sectionItem->updateHeight(); + } +} + void TransitionEditorSectionItem::invalidateBar(QGraphicsItem *item) { if (auto sectionItem = qgraphicsitem_cast<TransitionEditorSectionItem *>(item)) @@ -360,7 +371,8 @@ void TransitionEditorSectionItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent if (event->button() == Qt::LeftButton) { event->accept(); - toggleCollapsed(); + if (!ModelNode::isThisOrAncestorLocked(m_targetNode)) + toggleCollapsed(); } } @@ -392,7 +404,8 @@ void TransitionEditorSectionItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *ev if (m_targetNode.isValid()) m_targetNode.view()->setSelectedModelNode(m_targetNode); } else { - toggleCollapsed(); + if (!ModelNode::isThisOrAncestorLocked(m_targetNode)) + toggleCollapsed(); } update(); } @@ -417,6 +430,12 @@ void TransitionEditorSectionItem::updateData() update(); } +void TransitionEditorSectionItem::updateHeight() +{ + invalidateHeight(); + update(); +} + const QList<QGraphicsItem *> TransitionEditorSectionItem::propertyItems() const { QList<QGraphicsItem *> list; @@ -488,7 +507,8 @@ void TransitionEditorSectionItem::invalidateProperties() bool TransitionEditorSectionItem::collapsed() const { - return m_targetNode.isValid() && !m_targetNode.hasAuxiliaryData("timeline_expanded"); + return m_targetNode.isValid() + && (!m_targetNode.hasAuxiliaryData("transition_expanded") || m_targetNode.locked()); } qreal TransitionEditorSectionItem::rulerWidth() const @@ -501,9 +521,9 @@ void TransitionEditorSectionItem::toggleCollapsed() QTC_ASSERT(m_targetNode.isValid(), return ); if (collapsed()) - m_targetNode.setAuxiliaryData("timeline_expanded", true); + m_targetNode.setAuxiliaryData("transition_expanded", true); else - m_targetNode.removeAuxiliaryData("timeline_expanded"); + m_targetNode.removeAuxiliaryData("transition_expanded"); invalidateHeight(); } @@ -592,6 +612,11 @@ void TransitionEditorBarItem::commitPosition(const QPointF & /*point*/) scrollOffsetChanged(); } +bool TransitionEditorBarItem::isLocked() const +{ + return sectionItem() && sectionItem()->targetNode().isValid() && sectionItem()->targetNode().locked(); +} + void TransitionEditorBarItem::scrollOffsetChanged() { if (sectionItem()) @@ -637,7 +662,9 @@ void TransitionEditorBarItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) const auto p = event->pos(); QRectF left, right; - if (handleRects(rect(), left, right)) { + if (isLocked() && rect().contains(p)) { + setCursor(QCursor(Qt::ForbiddenCursor)); + } else if (handleRects(rect(), left, right)) { if (left.contains(p) || right.contains(p)) { if (cursor().shape() != Qt::SizeHorCursor) setCursor(QCursor(Qt::SizeHorCursor)); diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h index d7ce78f56c2..63828b52f54 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h @@ -54,6 +54,8 @@ public: void itemMoved(const QPointF &start, const QPointF &end) override; void commitPosition(const QPointF &point) override; + bool isLocked() const override; + protected: void scrollOffsetChanged() override; void paint(QPainter *painter, @@ -106,6 +108,7 @@ public: static void updateData(QGraphicsItem *item); static void invalidateBar(QGraphicsItem *item); static void updateDataForTarget(QGraphicsItem *item, const ModelNode &target, bool *b); + static void updateHeightForTarget(QGraphicsItem *item, const ModelNode &target); void moveAllDurations(qreal offset); void scaleAllDurations(qreal scale); @@ -125,6 +128,7 @@ protected: void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; private: + void updateHeight(); void invalidateHeight(); void invalidateProperties(); bool collapsed() const; diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp index ef83b17d90a..e9cfb3fd729 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp @@ -142,6 +142,18 @@ void TransitionEditorView::selectedNodesChanged(const QList<ModelNode> & /*selec } +void TransitionEditorView::auxiliaryDataChanged(const ModelNode &modelNode, + const PropertyName &name, + const QVariant &data) +{ + if (name == QmlDesigner::lockedProperty && data.toBool() && modelNode.isValid()) { + for (const auto &node : modelNode.allSubModelNodesAndThisNode()) { + if (node.hasAuxiliaryData("transition_expanded")) + m_transitionEditorWidget->graphicsScene()->invalidateHeightForTarget(node); + } + } +} + void TransitionEditorView::propertiesAboutToBeRemoved( const QList<AbstractProperty> & /*propertyList */) { @@ -217,7 +229,7 @@ ModelNode TransitionEditorView::addNewTransition() QStringList newlist = idPropertyList.value(targetId); for (const QString &str :locList) if (!newlist.contains(str)) - newlist.append(str); + newlist.append(str); idPropertyList.insert(targetId, newlist); } else { if (!locList.isEmpty()) diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h index 1476a07353d..857467e3cd9 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h @@ -60,6 +60,9 @@ public: PropertyChangeFlags propertyChange) override; void selectedNodesChanged(const QList<ModelNode> &selectedNodeList, const QList<ModelNode> &lastSelectedNodeList) override; + void auxiliaryDataChanged(const ModelNode &node, + const PropertyName &name, + const QVariant &data) override; void propertiesAboutToBeRemoved(const QList<AbstractProperty> &propertyList) override; void propertiesRemoved(const QList<AbstractProperty> &propertyList) override; diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp index 3d7e4ec4216..b2cb67c988d 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp @@ -29,6 +29,7 @@ #include "transitioneditorpropertyitem.h" #include "transitioneditortoolbar.h" #include "transitioneditorview.h" +#include "navigation2d.h" #include <timelineeditor/easingcurvedialog.h> #include <timelineeditor/timelineconstants.h> @@ -63,6 +64,8 @@ #include <QVBoxLayout> #include <QtGlobal> +#include <cmath> + namespace QmlDesigner { class Eventfilter : public QObject @@ -87,7 +90,7 @@ TransitionEditorWidget::TransitionEditorWidget(TransitionEditorView *view) , m_toolbar(new TransitionEditorToolBar(this)) , m_rulerView(new QGraphicsView(this)) , m_graphicsView(new QGraphicsView(this)) - , m_scrollbar(new QScrollBar(this)) + , m_scrollbar(new Navigation2dScrollBar(this)) , m_statusBar(new QLabel(this)) , m_transitionEditorView(view) , m_graphicsScene(new TransitionEditorGraphicsScene(this)) @@ -126,6 +129,7 @@ TransitionEditorWidget::TransitionEditorWidget(TransitionEditorView *view) m_graphicsView->setFrameShape(QFrame::NoFrame); m_graphicsView->setFrameShadow(QFrame::Plain); m_graphicsView->setLineWidth(0); + m_graphicsView->setVerticalScrollBar(new Navigation2dScrollBar); m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); m_graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -218,6 +222,14 @@ TransitionEditorWidget::TransitionEditorWidget(TransitionEditorView *view) connect(m_addButton, &QPushButton::clicked, this, [this]() { m_transitionEditorView->addNewTransition(); }); + + Navigation2dFilter *filter = new Navigation2dFilter(this, m_scrollbar); + connect(filter, &Navigation2dFilter::zoomChanged, [this](double scale, const QPointF& pos) { + int s = static_cast<int>(std::round(scale*100.)); + double ps = m_graphicsScene->mapFromScene(pos.x()); + m_graphicsScene->setZoom(std::clamp(m_graphicsScene->zoom() + s, 0, 100), ps); + }); + installEventFilter(filter); } void TransitionEditorWidget::setTransitionActive(bool b) @@ -258,7 +270,7 @@ void TransitionEditorWidget::connectToolbar() this, &TransitionEditorWidget::scroll); - auto setRulerScaling = [this](int val) { m_graphicsScene->setRulerScaling(val); }; + auto setRulerScaling = [this](int val) { m_graphicsScene->setZoom(val); }; connect(m_toolbar, &TransitionEditorToolBar::scaleFactorChanged, setRulerScaling); auto setDuration = [this](int end) { graphicsScene()->setDuration(end); }; @@ -335,7 +347,7 @@ void TransitionEditorWidget::init() m_toolbar->setDuration(duration); - m_graphicsScene->setRulerScaling(40); + m_graphicsScene->setZoom(40); } void TransitionEditorWidget::updateData(const ModelNode &transition) diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h index dbbe87a1ffd..f1c4174418a 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h @@ -37,7 +37,6 @@ QT_FORWARD_DECLARE_CLASS(QComboBox) QT_FORWARD_DECLARE_CLASS(QGraphicsView) QT_FORWARD_DECLARE_CLASS(QLabel) QT_FORWARD_DECLARE_CLASS(QResizeEvent) -QT_FORWARD_DECLARE_CLASS(QScrollBar) QT_FORWARD_DECLARE_CLASS(QShowEvent) QT_FORWARD_DECLARE_CLASS(QString) QT_FORWARD_DECLARE_CLASS(QPushButton) @@ -48,6 +47,7 @@ class TransitionEditorView; class TransitionEditorToolBar; class TransitionEditorGraphicsScene; class ModelNode; +class Navigation2dScrollBar; class TransitionEditorWidget : public QWidget { @@ -88,7 +88,7 @@ private: QGraphicsView *m_graphicsView = nullptr; - QScrollBar *m_scrollbar = nullptr; + Navigation2dScrollBar *m_scrollbar = nullptr; QLabel *m_statusBar = nullptr; diff --git a/src/plugins/qmldesigner/designercore/designercore-lib.pri b/src/plugins/qmldesigner/designercore/designercore-lib.pri index 08842527eec..981025d1fb5 100644 --- a/src/plugins/qmldesigner/designercore/designercore-lib.pri +++ b/src/plugins/qmldesigner/designercore/designercore-lib.pri @@ -14,6 +14,7 @@ include (../../../../share/qtcreator/qml/qmlpuppet/container/container.pri) include (../../../../share/qtcreator/qml/qmlpuppet/types/types.pri) SOURCES += $$PWD/model/abstractview.cpp \ + $$PWD/imagecache/imagecachecollector.cpp \ $$PWD/model/rewriterview.cpp \ $$PWD/model/documentmessage.cpp \ $$PWD/metainfo/metainfo.cpp \ @@ -84,9 +85,15 @@ SOURCES += $$PWD/model/abstractview.cpp \ $$PWD/model/qmltimeline.cpp \ $$PWD/model/qmltimelinekeyframegroup.cpp \ $$PWD/model/annotation.cpp \ - $$PWD/model/stylesheetmerger.cpp + $$PWD/model/stylesheetmerger.cpp \ + $$PWD/imagecache/imagecache.cpp \ + $$PWD/imagecache/imagecacheconnectionmanager.cpp \ + $$PWD/imagecache/imagecachegenerator.cpp \ + $$PWD/imagecache/timestampprovider.cpp + HEADERS += $$PWD/include/qmldesignercorelib_global.h \ + $$PWD/imagecache/imagecachecollector.h \ $$PWD/include/abstractview.h \ $$PWD/include/nodeinstanceview.h \ $$PWD/include/rewriterview.h \ @@ -162,7 +169,17 @@ HEADERS += $$PWD/include/qmldesignercorelib_global.h \ $$PWD/include/qmltimeline.h \ $$PWD/include/qmltimelinekeyframegroup.h \ $$PWD/include/annotation.h \ - $$PWD/include/stylesheetmerger.h + $$PWD/include/stylesheetmerger.h \ + $$PWD/include/imagecache.h \ + $$PWD/imagecache/imagecachecollectorinterface.h \ + $$PWD/imagecache/imagecacheconnectionmanager.h \ + $$PWD/imagecache/imagecachegeneratorinterface.h \ + $$PWD/imagecache/imagecachestorageinterface.h \ + $$PWD/imagecache/imagecachegenerator.h \ + $$PWD/imagecache/imagecachestorage.h \ + $$PWD/imagecache/timestampprovider.h \ + $$PWD/imagecache/timestampproviderinterface.h + FORMS += \ $$PWD/instances/puppetdialog.ui diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecache.cpp b/src/plugins/qmldesigner/designercore/imagecache/imagecache.cpp new file mode 100644 index 00000000000..20409eb7fa2 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecache.cpp @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "imagecache.h" + +#include "imagecachegenerator.h" +#include "imagecachestorage.h" +#include "timestampprovider.h" + +#include <thread> + +namespace QmlDesigner { + +ImageCache::ImageCache(ImageCacheStorageInterface &storage, + ImageCacheGeneratorInterface &generator, + TimeStampProviderInterface &timeStampProvider) + : m_storage(storage) + , m_generator(generator) + , m_timeStampProvider(timeStampProvider) +{ + m_backgroundThread = std::thread{[this] { + while (isRunning()) { + if (auto [hasEntry, entry] = getEntry(); hasEntry) { + request(entry.name, + entry.requestType, + std::move(entry.captureCallback), + std::move(entry.abortCallback), + m_storage, + m_generator, + m_timeStampProvider); + } + + waitForEntries(); + } + }}; +} + +ImageCache::~ImageCache() +{ + clean(); + stopThread(); + m_condition.notify_all(); + if (m_backgroundThread.joinable()) + m_backgroundThread.join(); +} + +void ImageCache::request(Utils::SmallStringView name, + ImageCache::RequestType requestType, + ImageCache::CaptureCallback captureCallback, + ImageCache::AbortCallback abortCallback, + ImageCacheStorageInterface &storage, + ImageCacheGeneratorInterface &generator, + TimeStampProviderInterface &timeStampProvider) +{ + const auto timeStamp = timeStampProvider.timeStamp(name); + const auto entry = requestType == RequestType::Image ? storage.fetchImage(name, timeStamp) + : storage.fetchIcon(name, timeStamp); + + if (entry.hasEntry) { + if (entry.image.isNull()) + abortCallback(); + else + captureCallback(entry.image); + } else { + generator.generateImage(name, timeStamp, std::move(captureCallback), std::move(abortCallback)); + } +} + +void ImageCache::requestImage(Utils::PathString name, + ImageCache::CaptureCallback captureCallback, + AbortCallback abortCallback) +{ + addEntry(std::move(name), std::move(captureCallback), std::move(abortCallback), RequestType::Image); + m_condition.notify_all(); +} + +void ImageCache::requestIcon(Utils::PathString name, + ImageCache::CaptureCallback captureCallback, + ImageCache::AbortCallback abortCallback) +{ + addEntry(std::move(name), std::move(captureCallback), std::move(abortCallback), RequestType::Icon); + m_condition.notify_all(); +} + +void ImageCache::clean() +{ + clearEntries(); + m_generator.clean(); +} + +std::tuple<bool, ImageCache::Entry> ImageCache::getEntry() +{ + std::unique_lock lock{m_mutex}; + + if (m_entries.empty()) + return {false, Entry{}}; + + Entry entry = m_entries.back(); + m_entries.pop_back(); + + return {true, entry}; +} + +void ImageCache::addEntry(Utils::PathString &&name, + ImageCache::CaptureCallback &&captureCallback, + AbortCallback &&abortCallback, + RequestType requestType) +{ + std::unique_lock lock{m_mutex}; + + m_entries.emplace_back(std::move(name), + std::move(captureCallback), + std::move(abortCallback), + requestType); +} + +void ImageCache::clearEntries() +{ + std::unique_lock lock{m_mutex}; + for (Entry &entry : m_entries) + entry.abortCallback(); + m_entries.clear(); +} + +void ImageCache::waitForEntries() +{ + std::unique_lock lock{m_mutex}; + if (m_entries.empty()) + m_condition.wait(lock, [&] { return m_entries.size() || m_finishing; }); +} + +void ImageCache::stopThread() +{ + std::unique_lock lock{m_mutex}; + m_finishing = true; +} + +bool ImageCache::isRunning() +{ + std::unique_lock lock{m_mutex}; + return !m_finishing; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp new file mode 100644 index 00000000000..1bb7262d17a --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "imagecachecollector.h" +#include "imagecacheconnectionmanager.h" + +#include <metainfo.h> +#include <model.h> +#include <nodeinstanceview.h> +#include <plaintexteditmodifier.h> +#include <rewriterview.h> + +#include <projectexplorer/project.h> +#include <projectexplorer/target.h> +#include <utils/fileutils.h> + +#include <QPlainTextEdit> + +namespace QmlDesigner { + +namespace { + +QByteArray fileToByteArray(QString const &filename) +{ + QFile file(filename); + QFileInfo fleInfo(file); + + if (fleInfo.exists() && file.open(QFile::ReadOnly)) + return file.readAll(); + + return {}; +} + +QString fileToString(const QString &filename) +{ + return QString::fromUtf8(fileToByteArray(filename)); +} + +} // namespace + +ImageCacheCollector::ImageCacheCollector(ImageCacheConnectionManager &connectionManager) + : m_connectionManager{connectionManager} +{} + +ImageCacheCollector::~ImageCacheCollector() = default; + +void ImageCacheCollector::start(Utils::SmallStringView name, + CaptureCallback captureCallback, + AbortCallback abortCallback) +{ + RewriterView rewriterView{RewriterView::Amend, nullptr}; + NodeInstanceView nodeInstanceView{m_connectionManager}; + + const QString filePath{name}; + std::unique_ptr<Model> model{QmlDesigner::Model::create("QtQuick/Item", 2, 1)}; + model->setFileUrl(QUrl::fromLocalFile(filePath)); + + auto textDocument = std::make_unique<QTextDocument>(fileToString(filePath)); + + auto modifier = std::make_unique<NotIndentingTextEditModifier>(textDocument.get(), + QTextCursor{textDocument.get()}); + + rewriterView.setTextModifier(modifier.get()); + + model->setRewriterView(&rewriterView); + + if (rewriterView.inErrorState() || !rewriterView.rootModelNode().metaInfo().isGraphicalItem()) { + abortCallback(); + return; + } + + m_connectionManager.setCallback(std::move(captureCallback)); + + nodeInstanceView.setTarget(m_target.get()); + nodeInstanceView.setCrashCallback(abortCallback); + model->setNodeInstanceView(&nodeInstanceView); + + bool capturedDataArrived = m_connectionManager.waitForCapturedData(); + + m_connectionManager.setCallback({}); + m_connectionManager.setCrashCallback({}); + + model->setNodeInstanceView({}); + model->setRewriterView({}); + + if (!capturedDataArrived) + abortCallback(); +} + +void ImageCacheCollector::setTarget(std::unique_ptr<ProjectExplorer::Target> target) +{ + m_target = std::move(target); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.h new file mode 100644 index 00000000000..e39f95f5732 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include "imagecachecollectorinterface.h" + +#include <memory> + +QT_BEGIN_NAMESPACE +class QTextDocument; +QT_END_NAMESPACE + +namespace ProjectExplorer { +class Target; +} + +namespace QmlDesigner { + +class Model; +class NotIndentingTextEditModifier; +class ImageCacheConnectionManager; +class RewriterView; +class NodeInstanceView; + +class ImageCacheCollector final : public ImageCacheCollectorInterface +{ +public: + ImageCacheCollector(ImageCacheConnectionManager &connectionManager); + + ~ImageCacheCollector(); + + void start(Utils::SmallStringView filePath, + CaptureCallback captureCallback, + AbortCallback abortCallback) override; + + void setTarget(std::unique_ptr<ProjectExplorer::Target> target); + +private: + ImageCacheConnectionManager &m_connectionManager; + std::unique_ptr<ProjectExplorer::Target> m_target; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollectorinterface.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollectorinterface.h new file mode 100644 index 00000000000..e6528f2ec37 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollectorinterface.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include <utils/smallstringview.h> + +#include <QImage> + +namespace QmlDesigner { + +class ImageCacheCollectorInterface +{ +public: + using CaptureCallback = std::function<void(QImage &&image)>; + using AbortCallback = std::function<void()>; + + virtual void start(Utils::SmallStringView filePath, + CaptureCallback captureCallback, + AbortCallback abortCallback) + = 0; + +protected: + ~ImageCacheCollectorInterface() = default; +}; +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecacheconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/imagecache/imagecacheconnectionmanager.cpp new file mode 100644 index 00000000000..2c7982bd1f6 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecacheconnectionmanager.cpp @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#include "imagecacheconnectionmanager.h" + +#include <captureddatacommand.h> + +#include <QLocalSocket> + +namespace QmlDesigner { + +ImageCacheConnectionManager::ImageCacheConnectionManager() +{ + connections().emplace_back("Capture icon", "captureiconmode"); +} + +void ImageCacheConnectionManager::setCallback(ImageCacheConnectionManager::Callback callback) +{ + m_captureCallback = std::move(callback); +} + +bool ImageCacheConnectionManager::waitForCapturedData() +{ + if (connections().empty()) + return false; + + disconnect(connections().front().socket.get(), &QIODevice::readyRead, nullptr, nullptr); + + while (!m_capturedDataArrived) { + bool dataArrived = connections().front().socket->waitForReadyRead(600000); + + if (!dataArrived) + return false; + + readDataStream(connections().front()); + } + + m_capturedDataArrived = false; + + return true; +} + +void ImageCacheConnectionManager::dispatchCommand(const QVariant &command, + ConnectionManagerInterface::Connection &) +{ + static const int capturedDataCommandType = QMetaType::type("CapturedDataCommand"); + + if (command.userType() == capturedDataCommandType) { + m_captureCallback(command.value<CapturedDataCommand>().image); + m_capturedDataArrived = true; + } +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecacheconnectionmanager.h b/src/plugins/qmldesigner/designercore/imagecache/imagecacheconnectionmanager.h new file mode 100644 index 00000000000..5788f6f31d2 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecacheconnectionmanager.h @@ -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. +** +****************************************************************************/ + +#pragma once + +#include <designercore/instances/connectionmanager.h> + +namespace QmlDesigner { + +class CapturedDataCommand; + +class ImageCacheConnectionManager : public ConnectionManager +{ +public: + using Callback = std::function<void(QImage &&)>; + + ImageCacheConnectionManager(); + + void setCallback(Callback captureCallback); + + bool waitForCapturedData(); + +protected: + void dispatchCommand(const QVariant &command, Connection &connection) override; + +private: + Callback m_captureCallback; + bool m_capturedDataArrived = false; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.cpp b/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.cpp new file mode 100644 index 00000000000..a6783fbf489 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.cpp @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "imagecachegenerator.h" + +#include "imagecachecollectorinterface.h" +#include "imagecachestorage.h" + +#include <QThread> + +namespace QmlDesigner { + +ImageCacheGenerator::~ImageCacheGenerator() +{ + std::lock_guard threadLock{*m_threadMutex.get()}; + + if (m_backgroundThread) + m_backgroundThread->wait(); + + clean(); +} + +void ImageCacheGenerator::generateImage(Utils::SmallStringView name, + Sqlite::TimeStamp timeStamp, + ImageCacheGeneratorInterface::CaptureCallback &&captureCallback, + AbortCallback &&abortCallback) +{ + { + std::lock_guard lock{m_dataMutex}; + m_tasks.emplace_back(name, timeStamp, std::move(captureCallback), std::move(abortCallback)); + } + + startGenerationAsynchronously(); +} + +void ImageCacheGenerator::clean() +{ + std::lock_guard dataLock{m_dataMutex}; + m_tasks.clear(); +} + +class ReleaseProcessing +{ +public: + ReleaseProcessing(std::atomic_flag &processing) + : m_processing(processing) + { + m_processing.test_and_set(std::memory_order_acquire); + } + + ~ReleaseProcessing() { m_processing.clear(std::memory_order_release); } + +private: + std::atomic_flag &m_processing; +}; + +void ImageCacheGenerator::startGeneration(std::shared_ptr<std::mutex> threadMutex) +{ + ReleaseProcessing guard(m_processing); + + while (true) { + Task task; + + { + std::unique_lock threadLock{*threadMutex.get(), std::defer_lock_t{}}; + + if (!threadLock.try_lock()) + return; + + std::lock_guard dataLock{m_dataMutex}; + + if (m_tasks.empty()) { + m_storage.walCheckpointFull(); + return; + } + + task = std::move(m_tasks.back()); + + m_tasks.pop_back(); + } + + m_collector.start( + task.filePath, + [this, threadMutex, task](QImage &&image) { + std::unique_lock lock{*threadMutex.get(), std::defer_lock_t{}}; + + if (!lock.try_lock()) + return; + + if (threadMutex.use_count() == 1) + return; + + if (image.isNull()) + task.abortCallback(); + else + task.captureCallback(image); + + m_storage.storeImage(std::move(task.filePath), task.timeStamp, image); + }, + [this, threadMutex, task] { + std::unique_lock lock{*threadMutex.get(), std::defer_lock_t{}}; + + if (!lock.try_lock()) + return; + + if (threadMutex.use_count() == 1) + return; + + task.abortCallback(); + m_storage.storeImage(std::move(task.filePath), task.timeStamp, {}); + }); + } +} + +void ImageCacheGenerator::startGenerationAsynchronously() +{ + if (m_processing.test_and_set(std::memory_order_acquire)) + return; + + std::unique_lock lock{*m_threadMutex.get(), std::defer_lock_t{}}; + + if (!lock.try_lock()) + return; + + if (m_backgroundThread) + m_backgroundThread->wait(); + + m_backgroundThread.reset(QThread::create( + [this](std::shared_ptr<std::mutex> threadMutex) { startGeneration(threadMutex); }, + m_threadMutex)); + m_backgroundThread->start(); + // m_backgroundThread = std::thread( + // [this](std::shared_ptr<std::mutex> threadMutex) { startGeneration(threadMutex); }, + // m_threadMutex); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.h new file mode 100644 index 00000000000..207622714b6 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include "imagecachegeneratorinterface.h" + +#include <utils/smallstring.h> + +#include <QThread> + +#include <memory> +#include <mutex> + +QT_BEGIN_NAMESPACE +class QPlainTextEdit; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class ImageCacheCollectorInterface; +class ImageCacheStorageInterface; + +class ImageCacheGenerator final : public ImageCacheGeneratorInterface +{ +public: + ImageCacheGenerator(ImageCacheCollectorInterface &collector, ImageCacheStorageInterface &storage) + : m_collector{collector} + , m_storage(storage) + {} + + ~ImageCacheGenerator(); + + void generateImage(Utils::SmallStringView filePath, + Sqlite::TimeStamp timeStamp, + CaptureCallback &&captureCallback, + AbortCallback &&abortCallback) override; + void clean() override; + +private: + struct Task + { + Task() = default; + Task(Utils::SmallStringView filePath, + Sqlite::TimeStamp timeStamp, + CaptureCallback &&captureCallback, + AbortCallback &&abortCallback) + : filePath(filePath) + , captureCallback(std::move(captureCallback)) + , abortCallback(std::move(abortCallback)) + , timeStamp(timeStamp) + {} + + Utils::PathString filePath; + CaptureCallback captureCallback; + AbortCallback abortCallback; + Sqlite::TimeStamp timeStamp; + }; + + void startGeneration(std::shared_ptr<std::mutex> threadMutex); + void startGenerationAsynchronously(); + +private: + std::unique_ptr<QThread> m_backgroundThread; + std::mutex m_dataMutex; + std::shared_ptr<std::mutex> m_threadMutex{std::make_shared<std::mutex>()}; + std::vector<Task> m_tasks; + ImageCacheCollectorInterface &m_collector; + ImageCacheStorageInterface &m_storage; + std::atomic_flag m_processing = ATOMIC_FLAG_INIT; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachegeneratorinterface.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachegeneratorinterface.h new file mode 100644 index 00000000000..26b96219951 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachegeneratorinterface.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include <sqlitetimestamp.h> +#include <utils/smallstringview.h> + +#include <QImage> + +namespace QmlDesigner { + +class ImageCacheGeneratorInterface +{ +public: + using CaptureCallback = std::function<void(const QImage &image)>; + using AbortCallback = std::function<void()>; + + virtual void generateImage(Utils::SmallStringView name, + Sqlite::TimeStamp timeStamp, + CaptureCallback &&captureCallback, + AbortCallback &&abortCallback) + = 0; + + virtual void clean() = 0; + +protected: + ~ImageCacheGeneratorInterface() = default; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachestorage.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachestorage.h new file mode 100644 index 00000000000..90900cf19e0 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachestorage.h @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "imagecachestorageinterface.h" + +#include <createtablesqlstatementbuilder.h> + +#include <sqliteblob.h> +#include <sqlitereadstatement.h> +#include <sqlitetable.h> +#include <sqlitetransaction.h> +#include <sqlitewritestatement.h> + +#include <QBuffer> +#include <QImageReader> +#include <QImageWriter> + +namespace QmlDesigner { + +template<typename DatabaseType> +class ImageCacheStorage : public ImageCacheStorageInterface +{ +public: + using ReadStatement = typename DatabaseType::ReadStatement; + using WriteStatement = typename DatabaseType::WriteStatement; + + ImageCacheStorage(DatabaseType &database) + : database(database) + { + transaction.commit(); + } + + Entry fetchImage(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp) const override + { + try { + Sqlite::DeferredTransaction transaction{database}; + + auto optionalBlob = selectImageStatement.template value<Sqlite::ByteArrayBlob>( + name, minimumTimeStamp.value); + + transaction.commit(); + + if (optionalBlob) { + QBuffer buffer{&optionalBlob->byteArray}; + QImageReader reader{&buffer, "PNG"}; + + return Entry{reader.read(), true}; + } + + return {}; + + } catch (const Sqlite::StatementIsBusy &) { + return fetchImage(name, minimumTimeStamp); + } + } + + Entry fetchIcon(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp) const override + { + try { + Sqlite::DeferredTransaction transaction{database}; + + auto optionalBlob = selectIconStatement.template value<Sqlite::ByteArrayBlob>( + name, minimumTimeStamp.value); + + transaction.commit(); + + if (optionalBlob) { + QBuffer buffer{&optionalBlob->byteArray}; + QImageReader reader{&buffer, "PNG"}; + + return Entry{reader.read(), true}; + } + + return {}; + + } catch (const Sqlite::StatementIsBusy &) { + return fetchIcon(name, minimumTimeStamp); + } + } + + void storeImage(Utils::SmallStringView name, Sqlite::TimeStamp newTimeStamp, const QImage &image) override + { + try { + Sqlite::ImmediateTransaction transaction{database}; + + if (image.isNull()) { + upsertImageStatement.write(name, + newTimeStamp.value, + Sqlite::NullValue{}, + Sqlite::NullValue{}); + } else { + QSize iconSize = image.size().scaled(QSize{96, 96}.boundedTo(image.size()), + Qt::KeepAspectRatio); + QImage icon = image.scaled(iconSize); + upsertImageStatement.write(name, + newTimeStamp.value, + Sqlite::BlobView{createImageBuffer(image)->data()}, + Sqlite::BlobView{createImageBuffer(icon)->data()}); + } + transaction.commit(); + + } catch (const Sqlite::StatementIsBusy &) { + return storeImage(name, newTimeStamp, image); + } + } + + void walCheckpointFull() + { + try { + database.walCheckpointFull(); + } catch (const Sqlite::StatementIsBusy &) { + return walCheckpointFull(); + } + } + +private: + class Initializer + { + public: + Initializer(DatabaseType &database) + { + if (!database.isInitialized()) { + Sqlite::ExclusiveTransaction transaction{database}; + + createImagesTable(database); + + transaction.commit(); + + database.setIsInitialized(true); + + database.walCheckpointFull(); + } + } + + void createImagesTable(DatabaseType &database) + { + Sqlite::Table table; + table.setUseIfNotExists(true); + table.setName("images"); + table.addColumn("id", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}}); + table.addColumn("name", Sqlite::ColumnType::Text, {Sqlite::NotNull{}, Sqlite::Unique{}}); + table.addColumn("mtime", Sqlite::ColumnType::Integer); + table.addColumn("image", Sqlite::ColumnType::Blob); + table.addColumn("icon", Sqlite::ColumnType::Blob); + + table.initialize(database); + } + }; + + std::unique_ptr<QBuffer> createImageBuffer(const QImage &image) + { + auto buffer = std::make_unique<QBuffer>(); + buffer->open(QIODevice::WriteOnly); + QImageWriter writer{buffer.get(), "PNG"}; + writer.write(image); + + return buffer; + } + +public: + DatabaseType &database; + Initializer initializer{database}; + Sqlite::ImmediateNonThrowingDestructorTransaction transaction{database}; + mutable ReadStatement selectImageStatement{ + "SELECT image FROM images WHERE name=?1 AND mtime >= ?2", database}; + mutable ReadStatement selectIconStatement{ + "SELECT icon FROM images WHERE name=?1 AND mtime >= ?2", database}; + WriteStatement upsertImageStatement{ + "INSERT INTO images(name, mtime, image, icon) VALUES (?1, ?2, ?3, ?4) ON " + "CONFLICT(name) DO UPDATE SET mtime=excluded.mtime, image=excluded.image, " + "icon=excluded.icon", + database}; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachestorageinterface.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachestorageinterface.h new file mode 100644 index 00000000000..97ace6efe6d --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachestorageinterface.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include <QImage> + +#include <sqlitetimestamp.h> +#include <utils/smallstringview.h> + +namespace QmlDesigner { +namespace Internal { +class ImageCacheStorageEntry +{ + public: + QImage image; + bool hasEntry = false; +}; + +} // namespace Internal + +class ImageCacheStorageInterface +{ +public: + using Entry = Internal::ImageCacheStorageEntry; + + virtual Entry fetchImage(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp) const = 0; + virtual Entry fetchIcon(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp) const = 0; + virtual void storeImage(Utils::SmallStringView name, Sqlite::TimeStamp newTimeStamp, const QImage &image) = 0; + virtual void walCheckpointFull() = 0; + +protected: + ~ImageCacheStorageInterface() = default; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/timestampprovider.cpp b/src/plugins/qmldesigner/designercore/imagecache/timestampprovider.cpp new file mode 100644 index 00000000000..99573f175fb --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/timestampprovider.cpp @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timestampprovider.h" + +#include <QDateTime> +#include <QFileInfo> + +namespace QmlDesigner { + +Sqlite::TimeStamp TimeStampProvider::timeStamp(Utils::SmallStringView name) const +{ + return QFileInfo{QString{name}}.lastModified().toSecsSinceEpoch(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/timestampprovider.h b/src/plugins/qmldesigner/designercore/imagecache/timestampprovider.h new file mode 100644 index 00000000000..8acc5fcb588 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/timestampprovider.h @@ -0,0 +1,39 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include "timestampproviderinterface.h" + +namespace QmlDesigner { + +class TimeStampProvider : public TimeStampProviderInterface +{ +public: + Sqlite::TimeStamp timeStamp(Utils::SmallStringView name) const override; +}; + +} // namespace QmlDesigner + diff --git a/src/plugins/qmldesigner/designercore/imagecache/timestampproviderinterface.h b/src/plugins/qmldesigner/designercore/imagecache/timestampproviderinterface.h new file mode 100644 index 00000000000..33cffc9b49c --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/timestampproviderinterface.h @@ -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. +** +****************************************************************************/ + +#pragma once + +#include <sqlitetimestamp.h> +#include <utils/smallstringview.h> + +#include <QImage> + +namespace QmlDesigner { + +class TimeStampProviderInterface +{ +public: + virtual Sqlite::TimeStamp timeStamp(Utils::SmallStringView name) const = 0; + +protected: + ~TimeStampProviderInterface() = default; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/include/abstractview.h b/src/plugins/qmldesigner/designercore/include/abstractview.h index 12cb4575717..6b0276c8b15 100644 --- a/src/plugins/qmldesigner/designercore/include/abstractview.h +++ b/src/plugins/qmldesigner/designercore/include/abstractview.h @@ -147,6 +147,7 @@ public: void setSelectedModelNodes(const QList<ModelNode> &selectedNodeList); void setSelectedModelNode(const ModelNode &modelNode); + void selectModelNode(const ModelNode &node); void deselectModelNode(const ModelNode &node); void clearSelectedModelNodes(); diff --git a/src/plugins/qmldesigner/designercore/include/basetexteditmodifier.h b/src/plugins/qmldesigner/designercore/include/basetexteditmodifier.h index e20566724bf..640ff367bc1 100644 --- a/src/plugins/qmldesigner/designercore/include/basetexteditmodifier.h +++ b/src/plugins/qmldesigner/designercore/include/basetexteditmodifier.h @@ -49,6 +49,9 @@ public: bool renameId(const QString &oldId, const QString &newId) override; bool moveToComponent(int nodeOffset) override; QStringList autoComplete(QTextDocument *textDocument, int position, bool explicitComplete) override; + +private: + TextEditor::TextEditorWidget *m_textEdit; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/include/filesystemfacadeinterface.h b/src/plugins/qmldesigner/designercore/include/filesystemfacadeinterface.h new file mode 100644 index 00000000000..c0ba644fddc --- /dev/null +++ b/src/plugins/qmldesigner/designercore/include/filesystemfacadeinterface.h @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +namespace QmlDesigner { + +class FileSystemFacadeInterface +{ +public: +protected: + ~FileSystemFacadeInterface() = default; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/include/imagecache.h b/src/plugins/qmldesigner/designercore/include/imagecache.h new file mode 100644 index 00000000000..4ac360c2a50 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/include/imagecache.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include <utils/smallstring.h> + +#include <condition_variable> +#include <functional> +#include <mutex> +#include <thread> + +#include <QImage> + +namespace QmlDesigner { + +class TimeStampProviderInterface; +class ImageCacheStorageInterface; +class ImageCacheGeneratorInterface; + +class ImageCache +{ +public: + using CaptureCallback = std::function<void(const QImage &)>; + using AbortCallback = std::function<void()>; + + ~ImageCache(); + + ImageCache(ImageCacheStorageInterface &storage, + ImageCacheGeneratorInterface &generator, + TimeStampProviderInterface &timeStampProvider); + + void requestImage(Utils::PathString name, + CaptureCallback captureCallback, + AbortCallback abortCallback); + void requestIcon(Utils::PathString name, + CaptureCallback captureCallback, + AbortCallback abortCallback); + + void clean(); + +private: + enum class RequestType { Image, Icon }; + struct Entry + { + Entry() = default; + Entry(Utils::PathString name, + CaptureCallback &&captureCallback, + AbortCallback &&abortCallback, + RequestType requestType) + : name{std::move(name)} + , captureCallback{std::move(captureCallback)} + , abortCallback{std::move(abortCallback)} + , requestType{requestType} + {} + + Utils::PathString name; + CaptureCallback captureCallback; + AbortCallback abortCallback; + RequestType requestType = RequestType::Image; + }; + + std::tuple<bool, Entry> getEntry(); + void addEntry(Utils::PathString &&name, + CaptureCallback &&captureCallback, + AbortCallback &&abortCallback, + RequestType requestType); + void clearEntries(); + void waitForEntries(); + void stopThread(); + bool isRunning(); + static void request(Utils::SmallStringView name, + ImageCache::RequestType requestType, + ImageCache::CaptureCallback captureCallback, + ImageCache::AbortCallback abortCallback, + ImageCacheStorageInterface &storage, + ImageCacheGeneratorInterface &generator, + TimeStampProviderInterface &timeStampProvider); + +private: + std::vector<Entry> m_entries; + mutable std::mutex m_mutex; + std::condition_variable m_condition; + std::thread m_backgroundThread; + ImageCacheStorageInterface &m_storage; + ImageCacheGeneratorInterface &m_generator; + TimeStampProviderInterface &m_timeStampProvider; + bool m_finishing{false}; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/include/model.h b/src/plugins/qmldesigner/designercore/include/model.h index 4d97eb8694e..20611736e90 100644 --- a/src/plugins/qmldesigner/designercore/include/model.h +++ b/src/plugins/qmldesigner/designercore/include/model.h @@ -43,6 +43,7 @@ namespace QmlDesigner { namespace Internal { class ModelPrivate; class WriteLocker; +class NodeMetaInfoPrivate; } //Internal class AnchorLine; @@ -68,6 +69,7 @@ class QMLDESIGNERCORE_EXPORT Model : public QObject friend class QmlDesigner::AbstractView; friend class Internal::ModelPrivate; friend class Internal::WriteLocker; + friend class QmlDesigner::Internal::NodeMetaInfoPrivate; Q_OBJECT @@ -118,6 +120,8 @@ public: QList<ModelNode> selectedNodes(AbstractView *view) const; + void clearMetaInfoCache(); + protected: Model(); diff --git a/src/plugins/qmldesigner/designercore/include/modelnode.h b/src/plugins/qmldesigner/designercore/include/modelnode.h index 3197e746ab5..19bed05a255 100644 --- a/src/plugins/qmldesigner/designercore/include/modelnode.h +++ b/src/plugins/qmldesigner/designercore/include/modelnode.h @@ -65,7 +65,9 @@ QMLDESIGNERCORE_EXPORT QList<Internal::InternalNodePointer> toInternalNodeList(c using PropertyListType = QList<QPair<PropertyName, QVariant> >; -class QMLDESIGNERCORE_EXPORT ModelNode +static const PropertyName lockedProperty = {("locked")}; + +class QMLDESIGNERCORE_EXPORT ModelNode { friend QMLDESIGNERCORE_EXPORT bool operator ==(const ModelNode &firstNode, const ModelNode &secondNode); friend QMLDESIGNERCORE_EXPORT bool operator !=(const ModelNode &firstNode, const ModelNode &secondNode); @@ -216,6 +218,11 @@ public: void setGlobalStatus(const GlobalAnnotationStatus &status); void removeGlobalStatus(); + bool locked() const; + void setLocked(bool value); + + static bool isThisOrAncestorLocked(const ModelNode &node); + qint32 internalId() const; void setNodeSource(const QString&); @@ -241,6 +248,8 @@ public: private: // functions Internal::InternalNodePointer internalNode() const; + void removeLocked(); + bool hasLocked() const; private: // variables Internal::InternalNodePointer m_internalNode; diff --git a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h index a570a0e690d..0e86ad11ee7 100644 --- a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h +++ b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h @@ -146,6 +146,11 @@ public: QVariant previewImageDataForGenericNode(const ModelNode &modelNode, const ModelNode &renderNode); QVariant previewImageDataForImageNode(const ModelNode &modelNode); + void setCrashCallback(std::function<void()> crashCallback) + { + m_crashCallback = std::move(crashCallback); + } + protected: void timerEvent(QTimerEvent *event) override; @@ -231,6 +236,7 @@ private: // key: fileUrl value: (key: instance qml id, value: related tool states) QHash<QUrl, QHash<QString, QVariantMap>> m_edit3DToolStates; + std::function<void()> m_crashCallback{[this] { handleCrash(); }}; }; } // namespace ProxyNodeInstanceView diff --git a/src/plugins/qmldesigner/designercore/include/nodemetainfo.h b/src/plugins/qmldesigner/designercore/include/nodemetainfo.h index b5e2e7626ca..869d327f5f2 100644 --- a/src/plugins/qmldesigner/designercore/include/nodemetainfo.h +++ b/src/plugins/qmldesigner/designercore/include/nodemetainfo.h @@ -107,8 +107,6 @@ public: QString importDirectoryPath() const; - static void clearCache(); - private: QSharedPointer<Internal::NodeMetaInfoPrivate> m_privateData; }; diff --git a/src/plugins/qmldesigner/designercore/include/plaintexteditmodifier.h b/src/plugins/qmldesigner/designercore/include/plaintexteditmodifier.h index 500bffd6fd6..c18b4d8cfb4 100644 --- a/src/plugins/qmldesigner/designercore/include/plaintexteditmodifier.h +++ b/src/plugins/qmldesigner/designercore/include/plaintexteditmodifier.h @@ -47,6 +47,7 @@ private: public: PlainTextEditModifier(QPlainTextEdit *textEdit); + PlainTextEditModifier(QTextDocument *document, const QTextCursor &textCursor); ~PlainTextEditModifier() override; QTextDocument *textDocument() const override; @@ -76,20 +77,17 @@ public: bool moveToComponent(int /* nodeOffset */) override { return false; } -protected: - QPlainTextEdit *plainTextEdit() const - { return m_textEdit; } - private: void textEditChanged(); void runRewriting(Utils::ChangeSet *writer); private: - Utils::ChangeSet *m_changeSet; - QPlainTextEdit *m_textEdit; - bool m_changeSignalsEnabled; - bool m_pendingChangeSignal; - bool m_ongoingTextChange; + Utils::ChangeSet *m_changeSet = nullptr; + QTextDocument *m_textDocument; + QTextCursor m_textCursor; + bool m_changeSignalsEnabled{true}; + bool m_pendingChangeSignal{false}; + bool m_ongoingTextChange{false}; }; class QMLDESIGNERCORE_EXPORT NotIndentingTextEditModifier: public PlainTextEditModifier @@ -99,6 +97,10 @@ public: : PlainTextEditModifier(textEdit) {} + NotIndentingTextEditModifier(QTextDocument *document, const QTextCursor &textCursor) + : PlainTextEditModifier{document, textCursor} + {} + void indent(int /*offset*/, int /*length*/) override {} void indentLines(int /*offset*/, int /*length*/) override diff --git a/src/plugins/qmldesigner/designercore/include/rewriterview.h b/src/plugins/qmldesigner/designercore/include/rewriterview.h index 0601da3acf4..5eae2f221fa 100644 --- a/src/plugins/qmldesigner/designercore/include/rewriterview.h +++ b/src/plugins/qmldesigner/designercore/include/rewriterview.h @@ -190,7 +190,6 @@ protected: // functions private: //variables ModelNode nodeAtTextCursorPositionHelper(const ModelNode &root, int cursorPosition) const; void setupCanonicalHashes() const; - void handleLibraryInfoUpdate(); TextModifier *m_textModifier = nullptr; int transactionLevel = 0; @@ -211,7 +210,6 @@ private: //variables std::function<void(bool)> m_setWidgetStatusCallback; bool m_hasIncompleteTypeInformation = false; bool m_restoringAuxData = false; - bool m_modelAttachPending = false; mutable QHash<int, ModelNode> m_canonicalIntModelNode; mutable QHash<ModelNode, int> m_canonicalModelNodeInt; diff --git a/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp index b7a2cc282e5..76d641fe30f 100644 --- a/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp +++ b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp @@ -33,11 +33,12 @@ namespace QmlDesigner { -void BaseConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, +void BaseConnectionManager::setUp(NodeInstanceServerInterface *nodeInstanceServer, const QString &, - ProjectExplorer::Target *) + ProjectExplorer::Target *, + AbstractView *view) { - m_nodeInstanceServerProxy = nodeInstanceServerProxy; + m_nodeInstanceServer = nodeInstanceServer; m_isActive = true; } @@ -47,7 +48,14 @@ void BaseConnectionManager::shutDown() writeCommand(QVariant::fromValue(EndPuppetCommand())); - m_nodeInstanceServerProxy = nullptr; + m_nodeInstanceServer = nullptr; +} + +void BaseConnectionManager::setCrashCallback(std::function<void()> callback) +{ + std::lock_guard<std::mutex> lock{m_callbackMutex}; + + m_crashCallback = std::move(callback); } bool BaseConnectionManager::isActive() const @@ -85,7 +93,7 @@ void BaseConnectionManager::dispatchCommand(const QVariant &command, Connection if (!isActive()) return; - m_nodeInstanceServerProxy->dispatchCommand(command); + m_nodeInstanceServer->dispatchCommand(command); } void BaseConnectionManager::readDataStream(Connection &connection) @@ -123,5 +131,12 @@ void BaseConnectionManager::readDataStream(Connection &connection) for (const QVariant &command : commandList) dispatchCommand(command, connection); } + +void BaseConnectionManager::callCrashCallback() +{ + std::lock_guard<std::mutex> lock{m_callbackMutex}; + + m_crashCallback(); +} } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.h b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.h index 83a41a2bd89..fca035682fc 100644 --- a/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.h +++ b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.h @@ -29,6 +29,8 @@ #include <QProcess> +#include <mutex> + QT_BEGIN_NAMESPACE class QLocalSocket; QT_END_NAMESPACE @@ -40,7 +42,6 @@ class Target; namespace QmlDesigner { class AbstractView; -class NodeInstanceServerProxy; class QMLDESIGNERCORE_EXPORT BaseConnectionManager : public QObject, public ConnectionManagerInterface { @@ -49,11 +50,14 @@ class QMLDESIGNERCORE_EXPORT BaseConnectionManager : public QObject, public Conn public: BaseConnectionManager() = default; - void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + void setUp(NodeInstanceServerInterface *nodeInstanceServer, const QString &qrcMappingString, - ProjectExplorer::Target *target) override; + ProjectExplorer::Target *target, + AbstractView *view) override; void shutDown() override; + void setCrashCallback(std::function<void()> callback) override; + bool isActive() const; protected: @@ -61,15 +65,19 @@ protected: virtual void showCannotConnectToPuppetWarningAndSwitchToEditMode(); using ConnectionManagerInterface::processFinished; void processFinished(); - void writeCommandToIODevice(const QVariant &command, - QIODevice *ioDevice, - unsigned int commandCounter); + static void writeCommandToIODevice(const QVariant &command, + QIODevice *ioDevice, + unsigned int commandCounter); void readDataStream(Connection &connection); - NodeInstanceServerProxy *nodeInstanceServerProxy() const { return m_nodeInstanceServerProxy; } + NodeInstanceServerInterface *nodeInstanceServer() const { return m_nodeInstanceServer; } + + void callCrashCallback(); private: - NodeInstanceServerProxy *m_nodeInstanceServerProxy{}; + std::mutex m_callbackMutex; + std::function<void()> m_crashCallback; + NodeInstanceServerInterface *m_nodeInstanceServer{}; bool m_isActive = false; }; diff --git a/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp index adf395d874b..90f4226217d 100644 --- a/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp +++ b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp @@ -34,11 +34,12 @@ namespace QmlDesigner { -void CapturingConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, +void CapturingConnectionManager::setUp(NodeInstanceServerInterface *nodeInstanceServer, const QString &qrcMappingString, - ProjectExplorer::Target *target) + ProjectExplorer::Target *target, + AbstractView *view) { - InteractiveConnectionManager::setUp(nodeInstanceServerProxy, qrcMappingString, target); + InteractiveConnectionManager::setUp(nodeInstanceServer, qrcMappingString, target, view); int indexOfCapturePuppetStream = QCoreApplication::arguments().indexOf( "-capture-puppet-stream"); @@ -72,7 +73,7 @@ void CapturingConnectionManager::writeCommand(const QVariant &command) if (m_captureFileForTest.isWritable()) { qDebug() << "command name: " << QMetaType::typeName(command.userType()); - writeCommandToIODevice(command, &m_captureFileForTest, m_writeCommandCounter); + writeCommandToIODevice(command, &m_captureFileForTest, writeCommandCounter()); qDebug() << "\tcatpure file offset: " << m_captureFileForTest.pos(); } } diff --git a/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.h b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.h index 1bedef440b3..e13d2e254aa 100644 --- a/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.h +++ b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.h @@ -32,9 +32,10 @@ namespace QmlDesigner { class QMLDESIGNERCORE_EXPORT CapturingConnectionManager : public InteractiveConnectionManager { public: - void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + void setUp(NodeInstanceServerInterface *nodeInstanceServer, const QString &qrcMappingString, - ProjectExplorer::Target *target) override; + ProjectExplorer::Target *target, + AbstractView *view) override; void processFinished(int exitCode, QProcess::ExitStatus exitStatus) override; diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp index fa8528579d0..77ea8706bf1 100644 --- a/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp @@ -46,19 +46,19 @@ ConnectionManager::ConnectionManager() = default; ConnectionManager::~ConnectionManager() = default; -void ConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, +void ConnectionManager::setUp(NodeInstanceServerInterface *nodeInstanceServerProxy, const QString &qrcMappingString, - ProjectExplorer::Target *target) + ProjectExplorer::Target *target, + AbstractView *view) { - BaseConnectionManager::setUp(nodeInstanceServerProxy, qrcMappingString, target); + BaseConnectionManager::setUp(nodeInstanceServerProxy, qrcMappingString, target, view); m_localServer = std::make_unique<QLocalServer>(); QString socketToken(QUuid::createUuid().toString()); m_localServer->listen(socketToken); m_localServer->setMaxPendingConnections(3); - NodeInstanceView *nodeInstanceView = nodeInstanceServerProxy->nodeInstanceView(); - PuppetCreator puppetCreator(target, nodeInstanceView->model()); + PuppetCreator puppetCreator(target, view->model()); puppetCreator.setQrcMappingString(qrcMappingString); puppetCreator.createQml2PuppetExecutableIfMissing(); @@ -67,7 +67,6 @@ void ConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, connection.qmlPuppetProcess = puppetCreator.createPuppetProcess( connection.mode, socketToken, - nodeInstanceView, [&] { printProcessOutput(connection.qmlPuppetProcess.get(), connection.name); }, [&](int exitCode, QProcess::ExitStatus exitStatus) { processFinished(exitCode, exitStatus); @@ -90,7 +89,7 @@ void ConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, if (connectedToPuppet) { connection.socket.reset(m_localServer->nextPendingConnection()); - QObject::connect(connection.socket.get(), &QIODevice::readyRead, [&] { + QObject::connect(connection.socket.get(), &QIODevice::readyRead, this, [&] { readDataStream(connection); }); } else { @@ -101,11 +100,6 @@ void ConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, } m_localServer->close(); - - connect(this, - &ConnectionManager::processCrashed, - nodeInstanceServerProxy, - &NodeInstanceServerProxy::processCrashed); } void ConnectionManager::shutDown() @@ -143,7 +137,7 @@ void ConnectionManager::processFinished(int exitCode, QProcess::ExitStatus exitS closeSocketsAndKillProcesses(); if (exitStatus == QProcess::CrashExit) - emit processCrashed(); + callCrashCallback(); } void ConnectionManager::closeSocketsAndKillProcesses() diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanager.h b/src/plugins/qmldesigner/designercore/instances/connectionmanager.h index 3e8ef26744f..c3c2c34afbf 100644 --- a/src/plugins/qmldesigner/designercore/instances/connectionmanager.h +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanager.h @@ -48,19 +48,20 @@ public: ~ConnectionManager() override; enum PuppetStreamType { FirstPuppetStream, SecondPuppetStream, ThirdPuppetStream }; - void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + void setUp(NodeInstanceServerInterface *nodeInstanceServerProxy, const QString &qrcMappingString, - ProjectExplorer::Target *target) override; + ProjectExplorer::Target *target, + AbstractView *view) override; void shutDown() override; void writeCommand(const QVariant &command) override; -signals: - void processCrashed(); - protected: using BaseConnectionManager::processFinished; void processFinished(int exitCode, QProcess::ExitStatus exitStatus) override; + std::vector<Connection> &connections() { return m_connections; } + + quint32 &writeCommandCounter() { return m_writeCommandCounter; } private: void printProcessOutput(QProcess *process, const QString &connectionName); @@ -69,7 +70,6 @@ private: private: std::unique_ptr<QLocalServer> m_localServer; -protected: std::vector<Connection> m_connections; quint32 m_writeCommandCounter = 0; }; diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h b/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h index 92d7449bc0b..2fc75c61c22 100644 --- a/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h @@ -38,7 +38,8 @@ class Target; namespace QmlDesigner { -class NodeInstanceServerProxy; +class NodeInstanceServerInterface; +class AbstractView; class QMLDESIGNERCORE_EXPORT ConnectionManagerInterface { @@ -65,12 +66,15 @@ public: virtual ~ConnectionManagerInterface(); - virtual void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + virtual void setUp(NodeInstanceServerInterface *nodeInstanceServer, const QString &qrcMappingString, - ProjectExplorer::Target *target) + ProjectExplorer::Target *target, + AbstractView *view) = 0; virtual void shutDown() = 0; + virtual void setCrashCallback(std::function<void()> callback) = 0; + virtual void writeCommand(const QVariant &command) = 0; protected: diff --git a/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp index 6da44603df8..cd9b5fc5cfa 100644 --- a/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp +++ b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp @@ -38,20 +38,21 @@ namespace QmlDesigner { InteractiveConnectionManager::InteractiveConnectionManager() { - m_connections.emplace_back("Editor", "editormode"); - m_connections.emplace_back("Render", "rendermode"); - m_connections.emplace_back("Preview", "previewmode"); + connections().emplace_back("Editor", "editormode"); + connections().emplace_back("Render", "rendermode"); + connections().emplace_back("Preview", "previewmode"); } -void InteractiveConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, +void InteractiveConnectionManager::setUp(NodeInstanceServerInterface *nodeInstanceServer, const QString &qrcMappingString, - ProjectExplorer::Target *target) + ProjectExplorer::Target *target, + AbstractView *view) { - ConnectionManager::setUp(nodeInstanceServerProxy, qrcMappingString, target); + ConnectionManager::setUp(nodeInstanceServer, qrcMappingString, target, view); DesignerSettings settings = QmlDesignerPlugin::instance()->settings(); int timeOutTime = settings.value(DesignerSettingsKey::PUPPET_KILL_TIMEOUT).toInt(); - for (Connection &connection : m_connections) + for (Connection &connection : connections()) connection.timer->setInterval(timeOutTime); if (QmlDesignerPlugin::instance() @@ -59,7 +60,7 @@ void InteractiveConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceSe .value(DesignerSettingsKey::DEBUG_PUPPET) .toString() .isEmpty()) { - for (Connection &connection : m_connections) { + for (Connection &connection : connections()) { QObject::connect(connection.timer.get(), &QTimer::timeout, [&]() { puppetTimeout(connection); }); @@ -67,6 +68,12 @@ void InteractiveConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceSe } } +void InteractiveConnectionManager::shutDown() +{ + m_view = {}; + ConnectionManager::shutDown(); +} + void InteractiveConnectionManager::showCannotConnectToPuppetWarningAndSwitchToEditMode() { Core::AsynchronousMessageBox::warning( @@ -75,8 +82,8 @@ void InteractiveConnectionManager::showCannotConnectToPuppetWarningAndSwitchToEd "Switching to another kit might help.")); QmlDesignerPlugin::instance()->switchToTextModeDeferred(); - nodeInstanceServerProxy()->nodeInstanceView()->emitDocumentMessage( - tr("Cannot Connect to QML Emulation Layer (QML Puppet)")); + if (m_view) + m_view->emitDocumentMessage(tr("Cannot Connect to QML Emulation Layer (QML Puppet)")); } void InteractiveConnectionManager::dispatchCommand(const QVariant &command, Connection &connection) diff --git a/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.h b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.h index 1946620a432..03be103ad61 100644 --- a/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.h +++ b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.h @@ -34,9 +34,12 @@ class InteractiveConnectionManager : public ConnectionManager public: InteractiveConnectionManager(); - void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + void setUp(NodeInstanceServerInterface *nodeInstanceServer, const QString &qrcMappingString, - ProjectExplorer::Target *target) override; + ProjectExplorer::Target *target, + AbstractView *view) override; + + void shutDown() override; void showCannotConnectToPuppetWarningAndSwitchToEditMode() override; @@ -46,6 +49,9 @@ protected: private: void puppetTimeout(Connection &connection); void puppetAlive(Connection &connection); + +private: + AbstractView *m_view{}; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.cpp index 173adc1b001..e04d725b38a 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.cpp @@ -100,7 +100,7 @@ NodeInstanceServerProxy::NodeInstanceServerProxy(NodeInstanceView *nodeInstanceV if (instanceViewBenchmark().isInfoEnabled()) m_benchmarkTimer.start(); - m_connectionManager.setUp(this, qrcMappingString(), target); + m_connectionManager.setUp(this, qrcMappingString(), target, nodeInstanceView); qCInfo(instanceViewBenchmark) << "puppets setup:" << m_benchmarkTimer.elapsed(); } diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.h b/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.h index 0177bd6a14a..e4edeb6725a 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.h +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.h @@ -84,6 +84,7 @@ public: void requestModelNodePreviewImage(const RequestModelNodePreviewImageCommand &command) override; void changeLanguage(const ChangeLanguageCommand &command) override; void changePreviewImageSize(const ChangePreviewImageSizeCommand &command) override; + void dispatchCommand(const QVariant &command) override; NodeInstanceView *nodeInstanceView() const { return m_nodeInstanceView; } @@ -91,12 +92,8 @@ public: protected: void writeCommand(const QVariant &command); - void dispatchCommand(const QVariant &command); NodeInstanceClientInterface *nodeInstanceClient() const; -signals: - void processCrashed(); - private: NodeInstanceView *m_nodeInstanceView{}; QElapsedTimer m_benchmarkTimer; diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 2f85a9e1a47..3f0b1f8a5f3 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -196,10 +196,7 @@ void NodeInstanceView::modelAttached(Model *model) AbstractView::modelAttached(model); m_nodeInstanceServer = createNodeInstanceServerProxy(); m_lastCrashTime.start(); - connect(m_nodeInstanceServer.get(), - &NodeInstanceServerProxy::processCrashed, - this, - &NodeInstanceView::handleCrash); + m_connectionManager.setCrashCallback(m_crashCallback); if (!isSkippedRootNode(rootModelNode())) { m_nodeInstanceServer->createScene(createCreateSceneCommand()); @@ -215,6 +212,8 @@ void NodeInstanceView::modelAttached(Model *model) void NodeInstanceView::modelAboutToBeDetached(Model * model) { + m_connectionManager.setCrashCallback({}); + removeAllInstanceNodeRelationships(); if (m_nodeInstanceServer) { m_nodeInstanceServer->clearScene(createClearSceneCommand()); @@ -281,11 +280,6 @@ void NodeInstanceView::restartProcess() m_nodeInstanceServer.reset(); m_nodeInstanceServer = createNodeInstanceServerProxy(); - connect(m_nodeInstanceServer.get(), - &NodeInstanceServerProxy::processCrashed, - this, - &NodeInstanceView::handleCrash); - if (!isSkippedRootNode(rootModelNode())) { m_nodeInstanceServer->createScene(createCreateSceneCommand()); m_nodeInstanceServer->changeSelection( @@ -534,11 +528,11 @@ void NodeInstanceView::auxiliaryDataChanged(const ModelNode &node, const PropertyName &name, const QVariant &value) { - if (((node.isRootNode() && (name == "width" || name == "height")) || name == "invisible") + if (((node.isRootNode() && (name == "width" || name == "height")) || name == "invisible" || name == "locked") || name.endsWith(PropertyName("@NodeInstance"))) { if (hasInstanceForModelNode(node)) { NodeInstance instance = instanceForModelNode(node); - if (value.isValid() || name == "invisible") { + if (value.isValid() || name == "invisible" || name == "locked") { PropertyValueContainer container{instance.instanceId(), name, value, TypeName()}; m_nodeInstanceServer->changeAuxiliaryValues({{container}}); } else { @@ -1496,7 +1490,15 @@ void NodeInstanceView::handlePuppetToCreatorCommand(const PuppetToCreatorCommand if (hasModelNodeForInternalId(container.instanceId()) && !image.isNull()) { auto node = modelNodeForInternalId(container.instanceId()); if (node.isValid()) { - image.setDevicePixelRatio(2.); +#ifndef QMLDESIGNER_TEST + const double ratio = QmlDesignerPlugin::formEditorDevicePixelRatio(); +#else + const double ratio = 1; +#endif + const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio; + if (image.height() != dim || image.width() != dim) + image = image.scaled(dim, dim, Qt::KeepAspectRatio); + image.setDevicePixelRatio(ratio); updatePreviewImageForNode(node, image); } } @@ -1540,12 +1542,15 @@ void NodeInstanceView::requestModelNodePreviewImage(const ModelNode &node, const } else if (node.isComponent()) { componentPath = node.metaInfo().componentFileName(); } +#ifndef QMLDESIGNER_TEST + const double ratio = QmlDesignerPlugin::formEditorDevicePixelRatio(); +#else + const double ratio = 1; +#endif + const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio; m_nodeInstanceServer->requestModelNodePreviewImage( - RequestModelNodePreviewImageCommand( - instance.instanceId(), - QSize(Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS, - Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS), - componentPath, renderItemId)); + RequestModelNodePreviewImageCommand(instance.instanceId(), QSize(dim, dim), + componentPath, renderItemId)); } } } @@ -1587,6 +1592,11 @@ QVariant NodeInstanceView::previewImageDataForImageNode(const ModelNode &modelNo ModelNodePreviewImageData imageData; imageData.id = modelNode.id(); imageData.type = QString::fromLatin1(modelNode.type()); +#ifndef QMLDESIGNER_TEST + const double ratio = QmlDesignerPlugin::formEditorDevicePixelRatio(); +#else + const double ratio = 1; +#endif if (imageSource.isEmpty() && modelNode.isSubclassOf("QtQuick3D.Texture")) { // Texture node may have sourceItem instead @@ -1601,11 +1611,10 @@ QVariant NodeInstanceView::previewImageDataForImageNode(const ModelNode &modelNo return previewImageDataForGenericNode(modelNode, boundNode); } else { QmlItemNode itemNode(boundNode); - imageData.pixmap = itemNode.instanceRenderPixmap().scaled( - Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * 2, - Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * 2, - Qt::KeepAspectRatio); - imageData.pixmap.setDevicePixelRatio(2.); + const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio; + imageData.pixmap = itemNode.instanceRenderPixmap().scaled(dim, dim, Qt::KeepAspectRatio); + imageData.pixmap.setDevicePixelRatio(ratio); + } imageData.info = QObject::tr("Source item: %1").arg(boundNode.id()); } @@ -1629,10 +1638,9 @@ QVariant NodeInstanceView::previewImageDataForImageNode(const ModelNode &modelNo QPixmap originalPixmap; originalPixmap.load(imageSource); if (!originalPixmap.isNull()) { - imageData.pixmap = originalPixmap.scaled(Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * 2, - Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * 2, - Qt::KeepAspectRatio); - imageData.pixmap.setDevicePixelRatio(2.); + const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio; + imageData.pixmap = originalPixmap.scaled(dim, dim, Qt::KeepAspectRatio); + imageData.pixmap.setDevicePixelRatio(ratio); double imgSize = double(imageFi.size()); static QStringList units({QObject::tr("B"), QObject::tr("KB"), QObject::tr("MB"), QObject::tr("GB")}); diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp index 5f691bc5a02..df609f4cb09 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp +++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp @@ -181,7 +181,6 @@ PuppetCreator::PuppetCreator(ProjectExplorer::Target *target, const Model *model QProcessUniquePointer PuppetCreator::createPuppetProcess( const QString &puppetMode, const QString &socketToken, - QObject *handlerObject, std::function<void()> processOutputCallback, std::function<void(int, QProcess::ExitStatus)> processFinishCallback, const QStringList &customOptions) const @@ -190,7 +189,6 @@ QProcessUniquePointer PuppetCreator::createPuppetProcess( qmlPuppetDirectory(m_availablePuppetType), puppetMode, socketToken, - handlerObject, processOutputCallback, processFinishCallback, customOptions); @@ -201,7 +199,6 @@ QProcessUniquePointer PuppetCreator::puppetProcess( const QString &workingDirectory, const QString &puppetMode, const QString &socketToken, - QObject *handlerObject, std::function<void()> processOutputCallback, std::function<void(int, QProcess::ExitStatus)> processFinishCallback, const QStringList &customOptions) const @@ -216,7 +213,6 @@ QProcessUniquePointer PuppetCreator::puppetProcess( &QProcess::kill); QObject::connect(puppetProcess.get(), static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), - handlerObject, processFinishCallback); #ifndef QMLDESIGNER_TEST @@ -227,7 +223,7 @@ QProcessUniquePointer PuppetCreator::puppetProcess( #endif if (forwardOutput == puppetMode || forwardOutput == "all") { puppetProcess->setProcessChannelMode(QProcess::MergedChannels); - QObject::connect(puppetProcess.get(), &QProcess::readyRead, handlerObject, processOutputCallback); + QObject::connect(puppetProcess.get(), &QProcess::readyRead, processOutputCallback); } puppetProcess->setWorkingDirectory(workingDirectory); diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.h b/src/plugins/qmldesigner/designercore/instances/puppetcreator.h index 69c66688fe9..e001b9d0c10 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.h +++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.h @@ -58,7 +58,6 @@ public: QProcessUniquePointer createPuppetProcess( const QString &puppetMode, const QString &socketToken, - QObject *handlerObject, std::function<void()> processOutputCallback, std::function<void(int, QProcess::ExitStatus)> processFinishCallback, const QStringList &customOptions = {}) const; @@ -89,7 +88,6 @@ protected: const QString &workingDirectory, const QString &puppetMode, const QString &socketToken, - QObject *handlerObject, std::function<void()> processOutputCallback, std::function<void(int, QProcess::ExitStatus)> processFinishCallback, const QStringList &customOptions) const; diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp index afd2aaa9a59..e6a61800e67 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp @@ -25,6 +25,7 @@ #include "nodemetainfo.h" #include "model.h" +#include "model/model_p.h" #include "metainfo.h" #include <enumeration.h> @@ -620,8 +621,6 @@ public: QSet<QByteArray> &prototypeCachePositives(); QSet<QByteArray> &prototypeCacheNegatives(); - static void clearCache(); - private: NodeMetaInfoPrivate(Model *model, TypeName type, int maj = -1, int min = -1); @@ -657,13 +656,10 @@ private: const Document *document() const; QPointer<Model> m_model; - static QHash<TypeName, Pointer> m_nodeMetaInfoCache; const ObjectValue *m_objectValue = nullptr; bool m_propertiesSetup = false; }; -QHash<TypeName, NodeMetaInfoPrivate::Pointer> NodeMetaInfoPrivate::m_nodeMetaInfoCache; - bool NodeMetaInfoPrivate::isFileComponent() const { return m_isFileComponent; @@ -705,11 +701,6 @@ QSet<QByteArray> &NodeMetaInfoPrivate::prototypeCacheNegatives() return m_prototypeCacheNegatives; } -void NodeMetaInfoPrivate::clearCache() -{ - m_nodeMetaInfoCache.clear(); -} - PropertyName NodeMetaInfoPrivate::defaultPropertyName() const { if (!m_defaultPropertyName.isEmpty()) @@ -724,17 +715,12 @@ static inline TypeName stringIdentifier( const TypeName &type, int maj, int min) NodeMetaInfoPrivate::Pointer NodeMetaInfoPrivate::create(Model *model, const TypeName &type, int major, int minor) { - if (m_nodeMetaInfoCache.contains(stringIdentifier(type, major, minor))) { - const Pointer &info = m_nodeMetaInfoCache.value(stringIdentifier(type, major, minor)); - if (info->model() == model) - return info; - else - m_nodeMetaInfoCache.clear(); - } + if (model->d->m_nodeMetaInfoCache.contains(stringIdentifier(type, major, minor))) + return model->d->m_nodeMetaInfoCache.value(stringIdentifier(type, major, minor)); Pointer newData(new NodeMetaInfoPrivate(model, type, major, minor)); if (newData->isValid()) - m_nodeMetaInfoCache.insert(stringIdentifier(type, major, minor), newData); + model->d->m_nodeMetaInfoCache.insert(stringIdentifier(type, major, minor), newData); return newData; } @@ -1671,11 +1657,6 @@ bool NodeMetaInfo::isQmlItem() const || isSubclassOf("QtQml.QtObject"); } -void NodeMetaInfo::clearCache() -{ - Internal::NodeMetaInfoPrivate::clearCache(); -} - bool NodeMetaInfo::isLayoutable() const { if (isSubclassOf("<cpp>.QDeclarativeBasePositioner")) diff --git a/src/plugins/qmldesigner/designercore/model/abstractview.cpp b/src/plugins/qmldesigner/designercore/model/abstractview.cpp index d7d512334d9..6a1a8d4897b 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractview.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractview.cpp @@ -35,6 +35,7 @@ #ifndef QMLDESIGNER_TEST #include <qmldesignerplugin.h> #include <viewmanager.h> +#include <nodeabstractproperty.h> #endif #include <coreplugin/helpmanager.h> @@ -397,7 +398,7 @@ QList<ModelNode> AbstractView::toModelNodeList(const QList<Internal::InternalNod QList<ModelNode> toModelNodeList(const QList<Internal::InternalNode::Pointer> &nodeList, AbstractView *view) { QList<ModelNode> newNodeList; - foreach (const Internal::InternalNode::Pointer &node, nodeList) + for (const Internal::InternalNode::Pointer &node : nodeList) newNodeList.append(ModelNode(node, view->model(), view)); return newNodeList; @@ -406,7 +407,7 @@ QList<ModelNode> toModelNodeList(const QList<Internal::InternalNode::Pointer> &n QList<Internal::InternalNode::Pointer> toInternalNodeList(const QList<ModelNode> &nodeList) { QList<Internal::InternalNode::Pointer> newNodeList; - foreach (const ModelNode &node, nodeList) + for (const ModelNode &node : nodeList) newNodeList.append(node.internalNode()); return newNodeList; @@ -414,15 +415,26 @@ QList<Internal::InternalNode::Pointer> toInternalNodeList(const QList<ModelNode> /*! Sets the list of nodes to the actual selected nodes specified by - \a selectedNodeList. + \a selectedNodeList if the node or its ancestors are not locked. */ void AbstractView::setSelectedModelNodes(const QList<ModelNode> &selectedNodeList) { - model()->d->setSelectedNodes(toInternalNodeList(selectedNodeList)); + QList<ModelNode> unlockedNodes; + + for (const auto &modelNode : selectedNodeList) { + if (!ModelNode::isThisOrAncestorLocked(modelNode)) + unlockedNodes.push_back(modelNode); + } + + model()->d->setSelectedNodes(toInternalNodeList(unlockedNodes)); } void AbstractView::setSelectedModelNode(const ModelNode &modelNode) { + if (ModelNode::isThisOrAncestorLocked(modelNode)) { + clearSelectedModelNodes(); + return; + } setSelectedModelNodes({modelNode}); } diff --git a/src/plugins/qmldesigner/designercore/model/basetexteditmodifier.cpp b/src/plugins/qmldesigner/designercore/model/basetexteditmodifier.cpp index 03239faedc5..8113e628468 100644 --- a/src/plugins/qmldesigner/designercore/model/basetexteditmodifier.cpp +++ b/src/plugins/qmldesigner/designercore/model/basetexteditmodifier.cpp @@ -38,8 +38,9 @@ using namespace QmlDesigner; -BaseTextEditModifier::BaseTextEditModifier(TextEditor::TextEditorWidget *textEdit): - PlainTextEditModifier(textEdit) +BaseTextEditModifier::BaseTextEditModifier(TextEditor::TextEditorWidget *textEdit) + : PlainTextEditModifier(textEdit) + , m_textEdit{textEdit} { } @@ -47,21 +48,20 @@ void BaseTextEditModifier::indentLines(int startLine, int endLine) { if (startLine < 0) return; - auto baseTextEditorWidget = qobject_cast<TextEditor::TextEditorWidget*>(plainTextEdit()); - if (!baseTextEditorWidget) + + if (!m_textEdit) return; - QTextDocument *textDocument = plainTextEdit()->document(); - TextEditor::TextDocument *baseTextEditorDocument = baseTextEditorWidget->textDocument(); + TextEditor::TextDocument *baseTextEditorDocument = m_textEdit->textDocument(); TextEditor::TabSettings tabSettings = baseTextEditorDocument->tabSettings(); - QTextCursor tc(textDocument); + QTextCursor tc(textDocument()); tc.beginEditBlock(); for (int i = startLine; i <= endLine; i++) { - QTextBlock start = textDocument->findBlockByNumber(i); + QTextBlock start = textDocument()->findBlockByNumber(i); if (start.isValid()) { - QmlJSEditor::Internal::Indenter indenter(textDocument); + QmlJSEditor::Internal::Indenter indenter(textDocument()); indenter.indentBlock(start, QChar::Null, tabSettings); } } @@ -82,22 +82,23 @@ void BaseTextEditModifier::indent(int offset, int length) int BaseTextEditModifier::indentDepth() const { - if (auto bte = qobject_cast<TextEditor::TextEditorWidget*>(plainTextEdit())) - return bte->textDocument()->tabSettings().m_indentSize; + if (m_textEdit) + return m_textEdit->textDocument()->tabSettings().m_indentSize; else return 0; } bool BaseTextEditModifier::renameId(const QString &oldId, const QString &newId) { - if (auto bte = qobject_cast<TextEditor::TextEditorWidget*>(plainTextEdit())) { - if (auto document = qobject_cast<QmlJSEditor::QmlJSEditorDocument *>(bte->textDocument())) { + if (m_textEdit) { + if (auto document = qobject_cast<QmlJSEditor::QmlJSEditorDocument *>( + m_textEdit->textDocument())) { Utils::ChangeSet changeSet; foreach (const QmlJS::SourceLocation &loc, document->semanticInfo().idLocations.value(oldId)) { changeSet.replace(loc.begin(), loc.end(), newId); } - QTextCursor tc = bte->textCursor(); + QTextCursor tc = textCursor(); changeSet.apply(&tc); return true; } @@ -120,10 +121,9 @@ static QmlJS::AST::UiObjectDefinition *getObjectDefinition(const QList<QmlJS::AS bool BaseTextEditModifier::moveToComponent(int nodeOffset) { - if (auto bte = qobject_cast<TextEditor::TextEditorWidget*>(plainTextEdit())) { - if (auto document - = qobject_cast<QmlJSEditor::QmlJSEditorDocument *>(bte->textDocument())) { - + if (m_textEdit) { + if (auto document = qobject_cast<QmlJSEditor::QmlJSEditorDocument *>( + m_textEdit->textDocument())) { auto qualifiedId = QmlJS::AST::cast<QmlJS::AST::UiQualifiedId *>(document->semanticInfo().astNodeAt(nodeOffset)); QList<QmlJS::AST::Node *> path = document->semanticInfo().rangePath(nodeOffset); QmlJS::AST::UiObjectDefinition *object = getObjectDefinition(path, qualifiedId); @@ -140,9 +140,9 @@ bool BaseTextEditModifier::moveToComponent(int nodeOffset) QStringList BaseTextEditModifier::autoComplete(QTextDocument *textDocument, int position, bool explicitComplete) { - if (auto bte = qobject_cast<TextEditor::TextEditorWidget*>(plainTextEdit())) - if (auto document - = qobject_cast<QmlJSEditor::QmlJSEditorDocument *>(bte->textDocument())) + if (m_textEdit) + if (auto document = qobject_cast<QmlJSEditor::QmlJSEditorDocument *>( + m_textEdit->textDocument())) return QmlJSEditor::qmlJSAutoComplete(textDocument, position, document->filePath(), diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index f31aaeecdcf..aed9ae788c4 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -161,7 +161,7 @@ void ModelPrivate::notifyImportsChanged(const QList<Import> &addedImports, const resetModel = true; } - NodeMetaInfo::clearCache(); + m_nodeMetaInfoCache.clear(); if (nodeInstanceView()) nodeInstanceView()->importsChanged(addedImports, removedImports); @@ -2080,6 +2080,11 @@ QList<ModelNode> Model::selectedNodes(AbstractView *view) const return d->toModelNodeList(d->selectedNodes(), view); } +void Model::clearMetaInfoCache() +{ + d->m_nodeMetaInfoCache.clear(); +} + /*! \brief Returns the URL against which relative URLs within the model should be resolved. \return The base URL. diff --git a/src/plugins/qmldesigner/designercore/model/model_p.h b/src/plugins/qmldesigner/designercore/model/model_p.h index 5a8dced5127..4874959759b 100644 --- a/src/plugins/qmldesigner/designercore/model/model_p.h +++ b/src/plugins/qmldesigner/designercore/model/model_p.h @@ -85,6 +85,7 @@ class ModelPrivate : public QObject { friend class QmlDesigner::Model; friend class QmlDesigner::Internal::WriteLocker; + friend class QmlDesigner::Internal::NodeMetaInfoPrivate; public: ModelPrivate(Model *model); @@ -268,6 +269,7 @@ private: QPointer<NodeInstanceView> m_nodeInstanceView; QPointer<TextModifier> m_textModifier; QPointer<Model> m_metaInfoProxyModel; + QHash<TypeName, QSharedPointer<NodeMetaInfoPrivate>> m_nodeMetaInfoCache; bool m_writeLock; qint32 m_internalIdCounter; }; diff --git a/src/plugins/qmldesigner/designercore/model/modelnode.cpp b/src/plugins/qmldesigner/designercore/model/modelnode.cpp index 5c1ffce639a..688dae2d074 100644 --- a/src/plugins/qmldesigner/designercore/model/modelnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/modelnode.cpp @@ -1228,6 +1228,53 @@ void ModelNode::removeGlobalStatus() } } +bool ModelNode::locked() const +{ + if (hasLocked()) + return auxiliaryData(lockedProperty).toBool(); + + return false; +} + +bool ModelNode::hasLocked() const +{ + return hasAuxiliaryData(lockedProperty); +} + +void ModelNode::setLocked(bool value) +{ + setAuxiliaryData(lockedProperty, value); + + if (value) { + // Remove newly locked node and all its descendants from potential selection + for (ModelNode node : allSubModelNodesAndThisNode()) { + node.deselectNode(); + node.removeAuxiliaryData("timeline_expanded"); + node.removeAuxiliaryData("transition_expanded"); + } + } +} + +void ModelNode::removeLocked() +{ + if (hasLocked()) + removeAuxiliaryData(lockedProperty); +} + +bool ModelNode::isThisOrAncestorLocked(const ModelNode &node) +{ + if (!node.isValid()) + return false; + + if (node.locked()) + return true; + + if (node.isRootNode() || !node.hasParentProperty()) + return false; + + return isThisOrAncestorLocked(node.parentProperty().parentModelNode()); +} + void ModelNode::setScriptFunctions(const QStringList &scriptFunctionList) { model()->d->setScriptFunctions(internalNode(), scriptFunctionList); diff --git a/src/plugins/qmldesigner/designercore/model/plaintexteditmodifier.cpp b/src/plugins/qmldesigner/designercore/model/plaintexteditmodifier.cpp index f87dc73a2c1..782ff3794bf 100644 --- a/src/plugins/qmldesigner/designercore/model/plaintexteditmodifier.cpp +++ b/src/plugins/qmldesigner/designercore/model/plaintexteditmodifier.cpp @@ -35,19 +35,17 @@ using namespace Utils; using namespace QmlDesigner; -PlainTextEditModifier::PlainTextEditModifier(QPlainTextEdit *textEdit): - m_changeSet(nullptr), - m_textEdit(textEdit), - m_changeSignalsEnabled(true), - m_pendingChangeSignal(false), - m_ongoingTextChange(false) +PlainTextEditModifier::PlainTextEditModifier(QPlainTextEdit *textEdit) + : PlainTextEditModifier(textEdit->document(), textEdit->textCursor()) { - Q_ASSERT(textEdit); - - connect(m_textEdit, &QPlainTextEdit::textChanged, - this, &PlainTextEditModifier::textEditChanged); + connect(textEdit, &QPlainTextEdit::textChanged, this, &PlainTextEditModifier::textEditChanged); } +PlainTextEditModifier::PlainTextEditModifier(QTextDocument *document, const QTextCursor &textCursor) + : m_textDocument{document} + , m_textCursor{textCursor} +{} + PlainTextEditModifier::~PlainTextEditModifier() = default; void PlainTextEditModifier::replace(int offset, int length, const QString &replacement) @@ -158,17 +156,17 @@ void PlainTextEditModifier::runRewriting(ChangeSet *changeSet) QTextDocument *PlainTextEditModifier::textDocument() const { - return m_textEdit->document(); + return m_textDocument; } QString PlainTextEditModifier::text() const { - return m_textEdit->toPlainText(); + return m_textDocument->toPlainText(); } QTextCursor PlainTextEditModifier::textCursor() const { - return m_textEdit->textCursor(); + return m_textCursor; } void PlainTextEditModifier::deactivateChangeSignals() diff --git a/src/plugins/qmldesigner/designercore/model/rewriteaction.cpp b/src/plugins/qmldesigner/designercore/model/rewriteaction.cpp index 106341043db..2b12f03b2b5 100644 --- a/src/plugins/qmldesigner/designercore/model/rewriteaction.cpp +++ b/src/plugins/qmldesigner/designercore/model/rewriteaction.cpp @@ -181,8 +181,10 @@ bool ChangePropertyRewriteAction::execute(QmlRefactoring &refactoring, ModelNode { if (m_sheduledInHierarchy) { const int nodeLocation = positionStore.nodeOffset(m_property.parentModelNode()); - if (nodeLocation < 0) + if (nodeLocation < 0) { + qWarning() << "*** ChangePropertyRewriteAction::execute ignored. Invalid node location"; return true; + } bool result = false; if (m_property.isDefaultProperty()) { diff --git a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp index f7a1254035c..86b561548ea 100644 --- a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp +++ b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp @@ -72,12 +72,7 @@ RewriterView::RewriterView(DifferenceHandling differenceHandling, QObject *paren m_textToModelMerger(new Internal::TextToModelMerger(this)) { m_amendTimer.setSingleShot(true); - m_amendTimer.setInterval(400); connect(&m_amendTimer, &QTimer::timeout, this, &RewriterView::amendQmlText); - - QmlJS::ModelManagerInterface *modelManager = QmlJS::ModelManagerInterface::instance(); - connect(modelManager, &QmlJS::ModelManagerInterface::libraryInfoUpdated, - this, &RewriterView::handleLibraryInfoUpdate, Qt::QueuedConnection); } RewriterView::~RewriterView() = default; @@ -94,8 +89,6 @@ Internal::TextToModelMerger *RewriterView::textToModelMerger() const void RewriterView::modelAttached(Model *model) { - m_modelAttachPending = false; - if (model && model->textModifier()) setTextModifier(model->textModifier()); @@ -109,12 +102,10 @@ void RewriterView::modelAttached(Model *model) if (!(m_errors.isEmpty() && m_warnings.isEmpty())) notifyErrorsAndWarnings(m_errors); - if (hasIncompleteTypeInformation()) { - m_modelAttachPending = true; + if (hasIncompleteTypeInformation()) QTimer::singleShot(1000, this, [this, model](){ modelAttached(model); }); - } } void RewriterView::modelAboutToBeDetached(Model * /*model*/) @@ -812,13 +803,6 @@ void RewriterView::setupCanonicalHashes() const } } -void RewriterView::handleLibraryInfoUpdate() -{ - // Trigger dummy amend to reload document when library info changes - if (isAttached() && !m_modelAttachPending) - m_amendTimer.start(); -} - ModelNode RewriterView::nodeAtTextCursorPosition(int cursorPosition) const { return nodeAtTextCursorPositionHelper(rootModelNode(), cursorPosition); @@ -1021,7 +1005,7 @@ void RewriterView::qmlTextChanged() auto &viewManager = QmlDesignerPlugin::instance()->viewManager(); if (viewManager.usesRewriterView(this)) { QmlDesignerPlugin::instance()->viewManager().disableWidgets(); - m_amendTimer.start(); + m_amendTimer.start(400); } #else /*Keep test synchronous*/ diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp index 61f8d5e0c3e..708d120ba53 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp @@ -989,7 +989,7 @@ bool TextToModelMerger::load(const QString &data, DifferenceHandler &differenceH m_rewriterView->setIncompleteTypeInformation(false); // maybe the project environment (kit, ...) changed, so we need to clean old caches - NodeMetaInfo::clearCache(); + m_rewriterView->model()->clearMetaInfoCache(); try { Snapshot snapshot = m_rewriterView->textModifier()->qmljsSnapshot(); @@ -1119,8 +1119,15 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, differenceHandler.typeDiffers(isRootNode, modelNode, typeName, majorVersion, minorVersion, astNode, context); - if (!isRootNode) + + if (!modelNode.isValid()) + return; + + if (!isRootNode && modelNode.majorVersion() != -1 && modelNode.minorVersion() != -1) { + qWarning() << "Preempting Node sync. Type differs" << modelNode << + modelNode.majorVersion() << modelNode.minorVersion(); return; // the difference handler will create a new node, so we're done. + } } if (isComponentType(typeName) || isImplicitComponent) diff --git a/src/plugins/qmldesigner/qmldesigner_dependencies.pri b/src/plugins/qmldesigner/qmldesigner_dependencies.pri index 321d2c2b071..473692aa5db 100644 --- a/src/plugins/qmldesigner/qmldesigner_dependencies.pri +++ b/src/plugins/qmldesigner/qmldesigner_dependencies.pri @@ -3,7 +3,8 @@ QTC_LIB_DEPENDS += \ utils \ qmljs \ qmleditorwidgets \ - advanceddockingsystem + advanceddockingsystem \ + sqlite QTC_PLUGIN_DEPENDS += \ coreplugin \ texteditor \ diff --git a/src/plugins/qmldesigner/qmldesignerplugin.pro b/src/plugins/qmldesigner/qmldesignerplugin.pro index 6590dcb0462..8f34f42d0a6 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.pro +++ b/src/plugins/qmldesigner/qmldesignerplugin.pro @@ -35,6 +35,7 @@ include(components/annotationeditor/annotationeditor.pri) include(components/richtexteditor/richtexteditor.pri) include(components/transitioneditor/transitioneditor.pri) include(components/listmodeleditor/listmodeleditor.pri) +include(components/previewtooltip/previewtooltipbackend.pri) BUILD_PUPPET_IN_CREATOR_BINPATH = $$(BUILD_PUPPET_IN_CREATOR_BINPATH) !isEmpty(BUILD_PUPPET_IN_CREATOR_BINPATH) { diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index ea57599bd08..8dc09339034 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -27,6 +27,7 @@ Project { Depends { name: "LanguageUtils" } Depends { name: "QtSupport" } Depends { name: "app_version_header" } + Depends { name: "Sqlite" } cpp.defines: base.concat([ "DESIGNER_CORE_LIBRARY", @@ -411,6 +412,21 @@ Project { "pluginmanager/widgetpluginmanager.h", "pluginmanager/widgetpluginpath.cpp", "pluginmanager/widgetpluginpath.h", + "include/imagecache.h", + "imagecache/imagecachecollector.cpp", + "imagecache/imagecachecollector.h", + "imagecache/imagecache.cpp", + "imagecache/imagecachecollectorinterface.h", + "imagecache/imagecacheconnectionmanager.cpp", + "imagecache/imagecacheconnectionmanager.h", + "imagecache/imagecachegeneratorinterface.h", + "imagecache/imagecachegenerator.cpp", + "imagecache/imagecachegenerator.h", + "imagecache/imagecachestorageinterface.h", + "imagecache/imagecachestorage.h", + "imagecache/timestampproviderinterface.h", + "imagecache/timestampprovider.h", + "imagecache/timestampprovider.cpp", ] } @@ -446,6 +462,8 @@ Project { "componentcore/modelnodecontextmenu_helper.h", "componentcore/modelnodeoperations.cpp", "componentcore/modelnodeoperations.h", + "componentcore/navigation2d.cpp", + "componentcore/navigation2d.h", "componentcore/selectioncontext.cpp", "componentcore/selectioncontext.h", "componentcore/qmldesignericonprovider.cpp", @@ -600,6 +618,8 @@ Project { "itemlibrary/itemlibrarywidget.h", "itemlibrary/customfilesystemmodel.cpp", "itemlibrary/customfilesystemmodel.h", + "itemlibrary/itemlibraryiconimageprovider.cpp", + "itemlibrary/itemlibraryiconimageprovider.h", "navigator/iconcheckboxitemdelegate.cpp", "navigator/iconcheckboxitemdelegate.h", "navigator/nameitemdelegate.cpp", @@ -791,6 +811,11 @@ Project { "pathtool/pathtool.h", "pathtool/pathtoolview.cpp", "pathtool/pathtoolview.h", + "previewtooltip/previewimagetooltip.cpp", + "previewtooltip/previewimagetooltip.h", + "previewtooltip/previewimagetooltip.ui", + "previewtooltip/previewtooltipbackend.cpp", + "previewtooltip/previewtooltipbackend.h", "richtexteditor/hyperlinkdialog.cpp", "richtexteditor/hyperlinkdialog.h", "richtexteditor/hyperlinkdialog.ui", diff --git a/src/plugins/qmldesigner/qmldesignerunittestfiles.pri b/src/plugins/qmldesigner/qmldesignerunittestfiles.pri index cd4d52e8d3a..b5ae93f2f1c 100644 --- a/src/plugins/qmldesigner/qmldesignerunittestfiles.pri +++ b/src/plugins/qmldesigner/qmldesignerunittestfiles.pri @@ -1,5 +1,6 @@ INCLUDEPATH += $$PWD INCLUDEPATH += $$PWD/designercore/include +INCLUDEPATH += $$PWD/designercore/imagecache INCLUDEPATH += $$PWD/designercore INCLUDEPATH += $$PWD/../../../share/qtcreator/qml/qmlpuppet/interfaces INCLUDEPATH += $$PWD/../../../share/qtcreator/qml/qmlpuppet/types @@ -30,9 +31,18 @@ SOURCES += \ $$PWD/designercore/model/variantproperty.cpp\ $$PWD/designercore/model/annotation.cpp \ $$PWD/designercore/rewritertransaction.cpp \ - $$PWD/components/listmodeleditor/listmodeleditormodel.cpp + $$PWD/components/listmodeleditor/listmodeleditormodel.cpp \ + $$PWD/designercore/imagecache/imagecache.cpp \ + $$PWD/designercore/imagecache/imagecachegenerator.cpp HEADERS += \ + $$PWD/designercore/imagecache/imagecachecollectorinterface.h \ + $$PWD/designercore/imagecache/imagecachestorage.h \ + $$PWD/designercore/imagecache/imagecachegenerator.h \ + $$PWD/designercore/imagecache/imagecachestorageinterface.h \ + $$PWD/designercore/imagecache/imagecachegeneratorinterface.h \ + $$PWD/designercore/imagecache/timestampproviderinterface.h \ + $$PWD/designercore/include/imagecache.h \ $$PWD/designercore/include/modelnode.h \ $$PWD/designercore/include/model.h \ $$PWD/../../../share/qtcreator/qml/qmlpuppet/interfaces/commondefines.h \ diff --git a/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp b/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp index d1c7c9f2891..086ef82fc99 100644 --- a/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp +++ b/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp @@ -38,6 +38,7 @@ #include <utils/outputformatter.h> #include <utils/utilsicons.h> #include <utils/fileutils.h> +#include <utils/qtcolorbutton.h> #include <extensionsystem/pluginmanager.h> #include <extensionsystem/pluginspec.h> @@ -94,6 +95,11 @@ namespace QmlPreview { QmlDebugTranslationWidget::QmlDebugTranslationWidget(QWidget *parent, TestLanguageGetter languagesGetterMethod) : QWidget(parent) , m_testLanguagesGetter(languagesGetterMethod) + , m_warningColor(Qt::red) + //, m_foundTrColor(Qt::green) // invalid color -> init without the frame + , m_lastWarningColor(m_warningColor) + , m_lastfoundTrColor(Qt::green) + { auto mainLayout = new QVBoxLayout(this); @@ -121,12 +127,54 @@ QmlDebugTranslationWidget::QmlDebugTranslationWidget(QWidget *parent, TestLangua m_selectLanguageLayout = new QHBoxLayout; mainLayout->addLayout(m_selectLanguageLayout); + auto settingsLayout = new QHBoxLayout(); + mainLayout->addLayout(settingsLayout); + auto elideWarningCheckBox = new QCheckBox(tr("Enable elide warning")); - layout()->addWidget(elideWarningCheckBox); connect(elideWarningCheckBox, &QCheckBox::stateChanged, [this] (int state) { m_elideWarning = (state == Qt::Checked); - }); + settingsLayout->addWidget(elideWarningCheckBox); + + auto warningColorCheckbox = new QCheckBox(tr("select Warning color: ")); + settingsLayout->addWidget(warningColorCheckbox); + auto warningColorButton = new Utils::QtColorButton(); + connect(warningColorCheckbox, &QCheckBox::stateChanged, [warningColorButton, this] (int state) { + if (state == Qt::Checked) { + warningColorButton->setColor(m_lastWarningColor); + warningColorButton->setEnabled(true); + } else { + m_lastWarningColor = warningColorButton->color(); + warningColorButton->setColor({}); + warningColorButton->setEnabled(false); + } + }); + connect(warningColorButton, &Utils::QtColorButton::colorChanged, [this](const QColor &color) { + m_warningColor = color; + }); + warningColorCheckbox->setCheckState(Qt::Checked); + settingsLayout->addWidget(warningColorButton); + + auto foundTrColorCheckbox = new QCheckBox(tr("select found 'tr' color: ")); + settingsLayout->addWidget(foundTrColorCheckbox); + auto foundTrColorButton = new Utils::QtColorButton(); + foundTrColorButton->setDisabled(true); + connect(foundTrColorCheckbox, &QCheckBox::stateChanged, [foundTrColorButton, this] (int state) { + if (state == Qt::Checked) { + foundTrColorButton->setColor(m_lastfoundTrColor); + foundTrColorButton->setEnabled(true); + } else { + m_lastfoundTrColor = foundTrColorButton->color(); + foundTrColorButton->setColor({}); + foundTrColorButton->setEnabled(false); + } + }); + connect(foundTrColorButton, &Utils::QtColorButton::colorChanged, [this](const QColor &color) { + m_foundTrColor = color; + }); + settingsLayout->addWidget(foundTrColorButton); + + settingsLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); auto controlLayout = new QHBoxLayout; mainLayout->addLayout(controlLayout); @@ -232,6 +280,16 @@ void QmlDebugTranslationWidget::updateStartupProjectTranslations() updateCurrentTranslations(ProjectExplorer::SessionManager::startupProject()); } +QColor QmlDebugTranslationWidget::warningColor() +{ + return m_warningColor; +} + +QColor QmlDebugTranslationWidget::foundTrColor() +{ + return m_foundTrColor; +} + void QmlDebugTranslationWidget::updateCurrentTranslations(ProjectExplorer::Project *project) { m_testLanguages.clear(); diff --git a/src/plugins/qmlpreview/qmldebugtranslationwidget.h b/src/plugins/qmlpreview/qmldebugtranslationwidget.h index 7ea0760ac0f..de0c7ba3624 100644 --- a/src/plugins/qmlpreview/qmldebugtranslationwidget.h +++ b/src/plugins/qmlpreview/qmldebugtranslationwidget.h @@ -63,6 +63,9 @@ public: void setCurrentFile(const Utils::FilePath &filepath); void setFiles(const Utils::FilePaths &filePathes); void updateStartupProjectTranslations(); + + QColor warningColor(); + QColor foundTrColor(); private: void updateCurrentEditor(const Core::IEditor *editor); void updateCurrentTranslations(ProjectExplorer::Project *project); @@ -98,6 +101,10 @@ private: QHBoxLayout *m_selectLanguageLayout; TestLanguageGetter m_testLanguagesGetter; + QColor m_warningColor; + QColor m_foundTrColor; + QColor m_lastWarningColor; + QColor m_lastfoundTrColor; }; } // namespace QmlPreview diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 0fdd28182cc..516b82ac724 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -4745,7 +4745,8 @@ void TextEditorWidgetPrivate::setupSelections(const PaintEventData &data, o.start = ts.positionAtColumn(text, m_blockSelection.firstVisualColumn()); o.length = ts.positionAtColumn(text, m_blockSelection.lastVisualColumn()) - o.start; } - if (data.textCursor.hasSelection() && data.textCursor == range.cursor) { + if (data.textCursor.hasSelection() && data.textCursor == range.cursor + && data.textCursor.anchor() == range.cursor.anchor()) { const QTextCharFormat selectionFormat = data.fontSettings.toTextCharFormat(C_SELECTION); if (selectionFormat.background().style() != Qt::NoBrush) o.format.setBackground(selectionFormat.background()); |