Files
qt-creator/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp
Miikka Heikkinen 2a0a1181f9 QmlDesigner: Resolve active scene id also at model attach
When coming back to previously loaded document, the sceneId will be
available at model attach time and there will not be separate changed
notification for it.

Fixes: QDS-8585
Change-Id: Ic8fcd4e2ec9123adc39d0c1cdca3bdb86d3a7924
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
2022-12-13 09:51:07 +00:00

384 lines
13 KiB
C++

// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "contentlibraryview.h"
#include "contentlibrarybundleimporter.h"
#include "contentlibrarymaterial.h"
#include "contentlibrarymaterialsmodel.h"
#include "contentlibrarytexture.h"
#include "contentlibrarytexturesmodel.h"
#include "contentlibrarywidget.h"
#include "nodelistproperty.h"
#include "qmldesignerconstants.h"
#include "qmlobjectnode.h"
#include "variantproperty.h"
#include <coreplugin/messagebox.h>
#include <enumeration.h>
#include <utils/algorithm.h>
#ifndef QMLDESIGNER_TEST
#include <projectexplorer/kit.h>
#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
#endif
namespace QmlDesigner {
ContentLibraryView::ContentLibraryView(ExternalDependenciesInterface &externalDependencies)
: AbstractView(externalDependencies)
, m_createTexture(this, true)
{}
ContentLibraryView::~ContentLibraryView()
{}
bool ContentLibraryView::hasWidget() const
{
return true;
}
WidgetInfo ContentLibraryView::widgetInfo()
{
if (m_widget.isNull()) {
m_widget = new ContentLibraryWidget();
connect(m_widget, &ContentLibraryWidget::bundleMaterialDragStarted, this,
[&] (QmlDesigner::ContentLibraryMaterial *mat) {
m_draggedBundleMaterial = mat;
});
connect(m_widget, &ContentLibraryWidget::bundleTextureDragStarted, this,
[&] (QmlDesigner::ContentLibraryTexture *tex) {
m_draggedBundleTexture = tex;
});
connect(m_widget, &ContentLibraryWidget::addTextureRequested, this,
[&] (const QString &texPath, AddTextureMode mode) {
executeInTransaction("ContentLibraryView::widgetInfo", [&]() {
m_createTexture.execute(texPath, mode, m_sceneId);
});
});
connect(m_widget, &ContentLibraryWidget::updateSceneEnvStateRequested, this, [&]() {
ModelNode activeSceneEnv = m_createTexture.resolveSceneEnv(m_sceneId);
const bool sceneEnvExists = activeSceneEnv.isValid();
m_widget->texturesModel()->setHasSceneEnv(sceneEnvExists);
m_widget->environmentsModel()->setHasSceneEnv(sceneEnvExists);
});
ContentLibraryMaterialsModel *materialsModel = m_widget->materialsModel().data();
connect(materialsModel, &ContentLibraryMaterialsModel::applyToSelectedTriggered, this,
[&] (ContentLibraryMaterial *bundleMat, bool add) {
if (m_selectedModels.isEmpty())
return;
m_bundleMaterialTargets = m_selectedModels;
m_bundleMaterialAddToSelected = add;
ModelNode defaultMat = getBundleMaterialDefaultInstance(bundleMat->type());
if (defaultMat.isValid())
applyBundleMaterialToDropTarget(defaultMat);
else
m_widget->materialsModel()->addToProject(bundleMat);
});
connect(materialsModel, &ContentLibraryMaterialsModel::bundleMaterialImported, this,
[&] (const QmlDesigner::NodeMetaInfo &metaInfo) {
applyBundleMaterialToDropTarget({}, metaInfo);
updateBundleMaterialsImportedState();
});
connect(materialsModel, &ContentLibraryMaterialsModel::bundleMaterialAboutToUnimport, this,
[&] (const QmlDesigner::TypeName &type) {
// delete instances of the bundle material that is about to be unimported
executeInTransaction("ContentLibraryView::widgetInfo", [&] {
ModelNode matLib = materialLibraryNode();
if (!matLib.isValid())
return;
Utils::reverseForeach(matLib.directSubModelNodes(), [&](const ModelNode &mat) {
if (mat.isValid() && mat.type() == type)
QmlObjectNode(mat).destroy();
});
});
});
connect(materialsModel, &ContentLibraryMaterialsModel::bundleMaterialUnimported, this,
&ContentLibraryView::updateBundleMaterialsImportedState);
}
return createWidgetInfo(m_widget.data(),
"ContentLibrary",
WidgetInfo::LeftPane,
0,
tr("Content Library"));
}
void ContentLibraryView::modelAttached(Model *model)
{
AbstractView::modelAttached(model);
m_hasQuick3DImport = model->hasImport("QtQuick3D");
updateBundleMaterialsQuick3DVersion();
updateBundleMaterialsImportedState();
const bool hasLibrary = materialLibraryNode().isValid();
m_widget->setHasMaterialLibrary(hasLibrary);
m_widget->setHasQuick3DImport(m_hasQuick3DImport);
m_sceneId = model->active3DSceneId();
}
void ContentLibraryView::modelAboutToBeDetached(Model *model)
{
m_widget->setHasMaterialLibrary(false);
m_widget->setHasQuick3DImport(false);
AbstractView::modelAboutToBeDetached(model);
}
void ContentLibraryView::importsChanged(const QList<Import> &addedImports, const QList<Import> &removedImports)
{
Q_UNUSED(addedImports)
Q_UNUSED(removedImports)
updateBundleMaterialsQuick3DVersion();
bool hasQuick3DImport = model()->hasImport("QtQuick3D");
if (hasQuick3DImport == m_hasQuick3DImport)
return;
m_hasQuick3DImport = hasQuick3DImport;
m_widget->setHasQuick3DImport(m_hasQuick3DImport);
}
void ContentLibraryView::active3DSceneChanged(qint32 sceneId)
{
m_sceneId = sceneId;
}
void ContentLibraryView::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
const QList<ModelNode> &lastSelectedNodeList)
{
Q_UNUSED(lastSelectedNodeList)
m_selectedModels = Utils::filtered(selectedNodeList, [](const ModelNode &node) {
return node.metaInfo().isQtQuick3DModel();
});
m_widget->materialsModel()->setHasModelSelection(!m_selectedModels.isEmpty());
}
void ContentLibraryView::customNotification(const AbstractView *view, const QString &identifier,
const QList<ModelNode> &nodeList, const QList<QVariant> &data)
{
Q_UNUSED(data)
if (view == this)
return;
if (identifier == "drop_bundle_material") {
ModelNode matLib = materialLibraryNode();
if (!matLib.isValid())
return;
m_bundleMaterialTargets = nodeList;
ModelNode defaultMat = getBundleMaterialDefaultInstance(m_draggedBundleMaterial->type());
if (defaultMat.isValid()) {
if (m_bundleMaterialTargets.isEmpty()) // if no drop target, create a duplicate material
createMaterial(defaultMat.metaInfo());
else
applyBundleMaterialToDropTarget(defaultMat);
} else {
m_widget->materialsModel()->addToProject(m_draggedBundleMaterial);
}
m_draggedBundleMaterial = nullptr;
} else if (identifier == "drop_bundle_texture") {
ModelNode matLib = materialLibraryNode();
if (!matLib.isValid())
return;
m_widget->addTexture(m_draggedBundleTexture);
m_draggedBundleTexture = nullptr;
}
}
void ContentLibraryView::nodeReparented(const ModelNode &node,
[[maybe_unused]] const NodeAbstractProperty &newPropertyParent,
[[maybe_unused]] const NodeAbstractProperty &oldPropertyParent,
[[maybe_unused]] PropertyChangeFlags propertyChange)
{
if (node.id() == Constants::MATERIAL_LIB_ID)
m_widget->setHasMaterialLibrary(true);
}
void ContentLibraryView::nodeAboutToBeRemoved(const ModelNode &removedNode)
{
if (removedNode.id() == Constants::MATERIAL_LIB_ID)
m_widget->setHasMaterialLibrary(false);
}
void ContentLibraryView::applyBundleMaterialToDropTarget(const ModelNode &bundleMat,
const NodeMetaInfo &metaInfo)
{
if (!bundleMat.isValid() && !metaInfo.isValid())
return;
executeInTransaction("ContentLibraryView::applyBundleMaterialToDropTarget", [&] {
ModelNode newMatNode = metaInfo.isValid() ? createMaterial(metaInfo) : bundleMat;
// TODO: unify this logic as it exist elsewhere also
auto expToList = [](const QString &exp) {
QString copy = exp;
copy = copy.remove("[").remove("]");
QStringList tmp = copy.split(',', Qt::SkipEmptyParts);
for (QString &str : tmp)
str = str.trimmed();
return tmp;
};
auto listToExp = [](QStringList &stringList) {
if (stringList.size() > 1)
return QString("[" + stringList.join(",") + "]");
if (stringList.size() == 1)
return stringList.first();
return QString();
};
for (const ModelNode &target : std::as_const(m_bundleMaterialTargets)) {
if (target.isValid() && target.metaInfo().isQtQuick3DModel()) {
QmlObjectNode qmlObjNode(target);
if (m_bundleMaterialAddToSelected) {
QStringList matList = expToList(qmlObjNode.expression("materials"));
matList.append(newMatNode.id());
QString updatedExp = listToExp(matList);
qmlObjNode.setBindingProperty("materials", updatedExp);
} else {
qmlObjNode.setBindingProperty("materials", newMatNode.id());
}
}
m_bundleMaterialTargets = {};
m_bundleMaterialAddToSelected = false;
}
});
}
ModelNode ContentLibraryView::getBundleMaterialDefaultInstance(const TypeName &type)
{
ModelNode matLib = materialLibraryNode();
if (!matLib.isValid())
return {};
const QList <ModelNode> matLibNodes = matLib.directSubModelNodes();
for (const ModelNode &mat : matLibNodes) {
if (mat.isValid() && mat.type() == type) {
bool isDefault = true;
const QList<AbstractProperty> props = mat.properties();
for (const AbstractProperty &prop : props) {
if (prop.name() != "objectName") {
isDefault = false;
break;
}
}
if (isDefault)
return mat;
}
}
return {};
}
ModelNode ContentLibraryView::createMaterial(const NodeMetaInfo &metaInfo)
{
ModelNode matLib = materialLibraryNode();
if (!matLib.isValid() || !metaInfo.isValid())
return {};
ModelNode newMatNode = createModelNode(metaInfo.typeName(), metaInfo.majorVersion(),
metaInfo.minorVersion());
matLib.defaultNodeListProperty().reparentHere(newMatNode);
static QRegularExpression rgx("([A-Z])([a-z]*)");
QString newName = QString::fromLatin1(metaInfo.simplifiedTypeName()).replace(rgx, " \\1\\2").trimmed();
if (newName.endsWith(" Material"))
newName.chop(9); // remove trailing " Material"
QString newId = model()->generateIdFromName(newName, "material");
newMatNode.setIdWithRefactoring(newId);
VariantProperty objNameProp = newMatNode.variantProperty("objectName");
objNameProp.setValue(newName);
return newMatNode;
}
void ContentLibraryView::updateBundleMaterialsImportedState()
{
using namespace Utils;
if (!m_widget->materialsModel()->bundleImporter())
return;
QStringList importedBundleMats;
FilePath materialBundlePath = m_widget->materialsModel()->bundleImporter()->resolveBundleImportPath();
if (materialBundlePath.exists()) {
importedBundleMats = transform(materialBundlePath.dirEntries({{"*.qml"}, QDir::Files}),
[](const FilePath &f) { return f.fileName().chopped(4); });
}
m_widget->materialsModel()->updateImportedState(importedBundleMats);
}
void ContentLibraryView::updateBundleMaterialsQuick3DVersion()
{
bool hasImport = false;
int major = -1;
int minor = -1;
const QString url {"QtQuick3D"};
const auto imports = model()->imports();
for (const auto &import : imports) {
if (import.url() == url) {
hasImport = true;
const int importMajor = import.majorVersion();
if (major < importMajor) {
minor = -1;
major = importMajor;
}
if (major == importMajor)
minor = qMax(minor, import.minorVersion());
}
}
#ifndef QMLDESIGNER_TEST
if (hasImport && major == -1) {
// Import without specifying version, so we take the kit version
auto target = ProjectExplorer::SessionManager::startupTarget();
if (target) {
QtSupport::QtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(target->kit());
if (qtVersion) {
major = qtVersion->qtVersion().majorVersion();
minor = qtVersion->qtVersion().minorVersion();
}
}
}
#endif
m_widget->materialsModel()->setQuick3DImportVersion(major, minor);
}
} // namespace QmlDesigner