QmlDesigner: Implement unimporting bundle materials

Fixes: QDS-7904
Change-Id: I08642c25a2844547d0104a7b3d9fda6afe47cd38
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
This commit is contained in:
Mahmoud Badri
2022-10-12 20:46:17 +03:00
parent 7a967d385e
commit b983b8aa52
12 changed files with 287 additions and 21 deletions

View File

@@ -27,6 +27,8 @@ import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuickDesignerTheme 1.0
import HelperWidgets 2.0
import QtQuick.Controls
import StudioTheme 1.0 as StudioTheme
Item {
@@ -43,7 +45,7 @@ Item {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: (mouse) => {
if (mouse.button === Qt.LeftButton)
if (mouse.button === Qt.LeftButton && !materialBrowserBundleModel.importerRunning)
rootView.startDragBundleMaterial(modelData, mapToGlobal(mouse.x, mouse.y))
else if (mouse.button === Qt.RightButton)
root.showContextMenu()
@@ -64,6 +66,48 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
source: modelData.bundleMaterialIcon
cache: false
Rectangle { // circular indicator for imported bundle materials
width: 10
height: 10
radius: 5
anchors.right: img.right
anchors.top: img.top
anchors.margins: 5
color: "#00ff00"
border.color: "#555555"
border.width: 1
visible: modelData.bundleMaterialImported
ToolTip {
visible: indicatorMouseArea.containsMouse
text: qsTr("Material is imported to project")
delay: 1000
}
MouseArea {
id: indicatorMouseArea
anchors.fill: parent
hoverEnabled: true
}
}
IconButton {
icon: StudioTheme.Constants.plus
tooltip: qsTr("Add an instance to project")
buttonSize: 22
property color c: StudioTheme.Values.themeIconColor
normalColor: Qt.hsla(c.hslHue, c.hslSaturation, c.hslLightness, .2)
hoverColor: Qt.hsla(c.hslHue, c.hslSaturation, c.hslLightness, .3)
pressColor: Qt.hsla(c.hslHue, c.hslSaturation, c.hslLightness, .4)
anchors.right: img.right
anchors.bottom: img.bottom
enabled: !materialBrowserBundleModel.importerRunning
onClicked: {
materialBrowserBundleModel.addToProject(modelData)
}
}
}
TextInput {

View File

@@ -46,8 +46,8 @@ Item {
// Called also from C++ to close context menu on focus out
function closeContextMenu()
{
cxtMenu.close()
cxtMenuBundle.close()
ctxMenu.close()
ctxMenuBundle.close()
}
// Called from C++ to refresh a preview material after it changes
@@ -79,7 +79,7 @@ Item {
if (!materialBrowserModel.hasMaterialRoot && (!materialBrowserBundleModel.matBundleExists
|| mouse.y < userMatsSecBottom)) {
root.currentMaterial = null
cxtMenu.popup()
ctxMenu.popup()
}
}
}
@@ -97,8 +97,12 @@ Item {
}
}
UnimportBundleMaterialDialog {
id: unimportBundleMaterialDialog
}
StudioControls.Menu {
id: cxtMenu
id: ctxMenu
closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside
@@ -205,7 +209,7 @@ Item {
}
StudioControls.Menu {
id: cxtMenuBundle
id: ctxMenuBundle
closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside
@@ -224,9 +228,22 @@ Item {
StudioControls.MenuSeparator {}
StudioControls.MenuItem {
text: qsTr("Add to project")
enabled: !materialBrowserBundleModel.importerRunning
text: qsTr("Add an instance to project")
onTriggered: materialBrowserBundleModel.addMaterial(root.currentBundleMaterial)
onTriggered: {
materialBrowserBundleModel.addToProject(root.currentBundleMaterial)
}
}
StudioControls.MenuItem {
enabled: !materialBrowserBundleModel.importerRunning && root.currentBundleMaterial.bundleMaterialImported
text: qsTr("Remove from project")
onTriggered: {
unimportBundleMaterialDialog.targetBundleMaterial = root.currentBundleMaterial
unimportBundleMaterialDialog.open()
}
}
}
@@ -301,13 +318,14 @@ Item {
height: root.height - searchBox.height
clip: true
visible: materialBrowserModel.hasQuick3DImport && !materialBrowserModel.hasMaterialRoot
interactive: !ctxMenu.opened && !ctxMenuBundle.opened
Column {
Section {
id: userMaterialsSection
width: root.width
caption: qsTr("User materials")
caption: qsTr("Materials")
hideHeader: !materialBrowserBundleModel.matBundleExists
Grid {
@@ -329,7 +347,7 @@ Item {
onShowContextMenu: {
root.currentMaterial = model
cxtMenu.popup()
ctxMenu.popup()
}
}
}
@@ -392,7 +410,7 @@ Item {
onShowContextMenu: {
root.currentBundleMaterial = modelData
cxtMenuBundle.popup()
ctxMenuBundle.popup()
}
}
}

View File

@@ -0,0 +1,85 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuickDesignerTheme
import HelperWidgets
import StudioControls as StudioControls
import StudioTheme as StudioTheme
Dialog {
id: root
title: qsTr("Bundle material might be in use")
anchors.centerIn: parent
closePolicy: Popup.CloseOnEscape
implicitWidth: 300
modal: true
property var targetBundleMaterial
contentItem: Column {
spacing: 20
width: parent.width
Text {
id: folderNotEmpty
text: qsTr("If the material you are removing is in use, it might cause the project to malfunction.\n\nAre you sure you want to remove the material?")
color: StudioTheme.Values.themeTextColor
wrapMode: Text.WordWrap
anchors.right: parent.right
anchors.left: parent.left
leftPadding: 10
rightPadding: 10
Keys.onEnterPressed: btnRemove.onClicked()
Keys.onReturnPressed: btnRemove.onClicked()
}
Row {
anchors.right: parent.right
Button {
id: btnRemove
text: qsTr("Remove")
onClicked: {
materialBrowserBundleModel.removeFromProject(root.targetBundleMaterial)
root.accept()
}
}
Button {
text: qsTr("Cancel")
onClicked: root.reject()
}
}
}
onOpened: folderNotEmpty.forceActiveFocus()
}

View File

@@ -51,6 +51,7 @@ public:
QString importComponent(const QString &qmlFile,
const QStringList &files);
QString unimportComponent(const QString &qmlFile);
Utils::FilePath resolveBundleImportPath();
signals:
// The metaInfo parameter will be invalid if an error was encountered during
@@ -63,7 +64,6 @@ private:
void handleImportTimer();
QVariantHash loadAssetRefMap(const Utils::FilePath &bundlePath);
void writeAssetRefMap(const Utils::FilePath &bundlePath, const QVariantHash &assetRefMap);
Utils::FilePath resolveBundleImportPath();
Utils::FilePath m_bundleDir;
QString m_bundleId;

View File

@@ -70,4 +70,20 @@ bool BundleMaterial::visible() const
return m_visible;
}
bool BundleMaterial::setImported(bool imported)
{
if (m_imported != imported) {
m_imported = imported;
emit materialImportedChanged();
return true;
}
return false;
}
bool BundleMaterial::imported() const
{
return m_imported;
}
} // namespace QmlDesigner

View File

@@ -40,6 +40,7 @@ class BundleMaterial : public QObject
Q_PROPERTY(QString bundleMaterialName MEMBER m_name CONSTANT)
Q_PROPERTY(QUrl bundleMaterialIcon MEMBER m_icon CONSTANT)
Q_PROPERTY(bool bundleMaterialVisible MEMBER m_visible NOTIFY materialVisibleChanged)
Q_PROPERTY(bool bundleMaterialImported READ imported WRITE setImported NOTIFY materialImportedChanged)
public:
BundleMaterial(QObject *parent,
@@ -57,8 +58,12 @@ public:
QStringList files() const;
bool visible() const;
bool setImported(bool imported);
bool imported() const;
signals:
void materialVisibleChanged();
void materialImportedChanged();
private:
QString m_name;
@@ -68,6 +73,7 @@ private:
QStringList m_files;
bool m_visible = true;
bool m_imported = false;
};
} // namespace QmlDesigner

View File

@@ -37,6 +37,16 @@ void BundleMaterialCategory::addBundleMaterial(BundleMaterial *bundleMat)
m_categoryMaterials.append(bundleMat);
}
bool BundleMaterialCategory::updateImportedState(const QStringList &importedMats)
{
bool changed = false;
for (BundleMaterial *mat : std::as_const(m_categoryMaterials))
changed |= mat->setImported(importedMats.contains(mat->qml().chopped(4)));
return changed;
}
bool BundleMaterialCategory::filter(const QString &searchText)
{
bool visible = false;

View File

@@ -42,6 +42,7 @@ public:
BundleMaterialCategory(QObject *parent, const QString &name);
void addBundleMaterial(BundleMaterial *bundleMat);
bool updateImportedState(const QStringList &importedMats);
bool filter(const QString &searchText);
QString name() const;

View File

@@ -122,7 +122,7 @@ void MaterialBrowserBundleModel::loadMaterialBundle()
m_matBundleExists = true;
const QString bundleId = m_matBundleObj.value("id").toString();
QString bundleId = m_matBundleObj.value("id").toString();
const QJsonObject catsObj = m_matBundleObj.value("categories").toObject();
const QStringList categories = catsObj.keys();
@@ -160,8 +160,17 @@ void MaterialBrowserBundleModel::loadMaterialBundle()
m_importer = new Internal::BundleImporter(matBundleDir.path(), bundleId, sharedFiles);
connect(m_importer, &Internal::BundleImporter::importFinished, this, [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
m_importerRunning = false;
emit importerRunningChanged();
if (metaInfo.isValid())
emit addBundleMaterialToProjectRequested(metaInfo);
emit bundleMaterialImported(metaInfo);
});
connect(m_importer, &Internal::BundleImporter::unimportFinished, this, [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
Q_UNUSED(metaInfo)
m_importerRunning = false;
emit importerRunningChanged();
emit bundleMaterialUnimported(metaInfo);
});
}
@@ -193,6 +202,11 @@ void MaterialBrowserBundleModel::setHasMaterialRoot(bool b)
emit hasMaterialRootChanged();
}
Internal::BundleImporter *MaterialBrowserBundleModel::bundleImporter() const
{
return m_importer;
}
void MaterialBrowserBundleModel::setSearchText(const QString &searchText)
{
QString lowerSearchText = searchText.toLower();
@@ -219,6 +233,16 @@ void MaterialBrowserBundleModel::setSearchText(const QString &searchText)
resetModel();
}
void MaterialBrowserBundleModel::updateImportedState(const QStringList &importedMats)
{
bool changed = false;
for (BundleMaterialCategory *cat : std::as_const(m_bundleCategories))
changed |= cat->updateImportedState(importedMats);
if (changed)
resetModel();
}
void MaterialBrowserBundleModel::resetModel()
{
beginResetModel();
@@ -230,12 +254,30 @@ void MaterialBrowserBundleModel::applyToSelected(BundleMaterial *mat, bool add)
emit applyToSelectedTriggered(mat, add);
}
void MaterialBrowserBundleModel::addMaterial(BundleMaterial *mat)
void MaterialBrowserBundleModel::addToProject(BundleMaterial *mat)
{
QString err = m_importer->importComponent(mat->qml(), mat->files());
if (!err.isEmpty())
if (err.isEmpty()) {
m_importerRunning = true;
emit importerRunningChanged();
} else {
qWarning() << __FUNCTION__ << err;
}
}
void MaterialBrowserBundleModel::removeFromProject(BundleMaterial *mat)
{
emit bundleMaterialAboutToUnimport(mat->type());
QString err = m_importer->unimportComponent(mat->qml());
if (err.isEmpty()) {
m_importerRunning = true;
emit importerRunningChanged();
} else {
qWarning() << __FUNCTION__ << err;
}
}
} // namespace QmlDesigner

View File

@@ -50,6 +50,7 @@ class MaterialBrowserBundleModel : public QAbstractListModel
Q_PROPERTY(bool isEmpty MEMBER m_isEmpty NOTIFY isEmptyChanged)
Q_PROPERTY(bool hasQuick3DImport READ hasQuick3DImport WRITE setHasQuick3DImport NOTIFY hasQuick3DImportChanged)
Q_PROPERTY(bool hasMaterialRoot READ hasMaterialRoot WRITE setHasMaterialRoot NOTIFY hasMaterialRootChanged)
Q_PROPERTY(bool importerRunning MEMBER m_importerRunning NOTIFY importerRunningChanged)
public:
MaterialBrowserBundleModel(QObject *parent = nullptr);
@@ -59,6 +60,7 @@ public:
QHash<int, QByteArray> roleNames() const override;
void setSearchText(const QString &searchText);
void updateImportedState(const QStringList &importedMats);
bool hasQuick3DImport() const;
void setHasQuick3DImport(bool b);
@@ -66,10 +68,13 @@ public:
bool hasMaterialRoot() const;
void setHasMaterialRoot(bool b);
Internal::BundleImporter *bundleImporter() const;
void resetModel();
Q_INVOKABLE void applyToSelected(QmlDesigner::BundleMaterial *mat, bool add = false);
Q_INVOKABLE void addMaterial(QmlDesigner::BundleMaterial *mat);
Q_INVOKABLE void addToProject(QmlDesigner::BundleMaterial *mat);
Q_INVOKABLE void removeFromProject(QmlDesigner::BundleMaterial *mat);
signals:
void isEmptyChanged();
@@ -77,7 +82,10 @@ signals:
void hasMaterialRootChanged();
void materialVisibleChanged();
void applyToSelectedTriggered(QmlDesigner::BundleMaterial *mat, bool add = false);
void addBundleMaterialToProjectRequested(const QmlDesigner::NodeMetaInfo &metaInfo);
void bundleMaterialImported(const QmlDesigner::NodeMetaInfo &metaInfo);
void bundleMaterialAboutToUnimport(const QmlDesigner::TypeName &type);
void bundleMaterialUnimported(const QmlDesigner::NodeMetaInfo &metaInfo);
void importerRunningChanged();
private:
void loadMaterialBundle();
@@ -93,6 +101,7 @@ private:
bool m_hasMaterialRoot = false;
bool m_matBundleExists = false;
bool m_probeMatBundleDir = false;
bool m_importerRunning = false;
};
} // namespace QmlDesigner

View File

@@ -27,6 +27,7 @@
#include "bindingproperty.h"
#include "bundlematerial.h"
#include "bundleimporter.h"
#include "materialbrowserwidget.h"
#include "materialbrowsermodel.h"
#include "materialbrowserbundlemodel.h"
@@ -178,13 +179,28 @@ WidgetInfo MaterialBrowserView::widgetInfo()
if (defaultMat.isValid())
applyBundleMaterialToDropTarget(defaultMat);
else
m_widget->materialBrowserBundleModel()->addMaterial(bundleMat);
m_widget->materialBrowserBundleModel()->addToProject(bundleMat);
});
connect(matBrowserBundleModel, &MaterialBrowserBundleModel::addBundleMaterialToProjectRequested, this,
connect(matBrowserBundleModel, &MaterialBrowserBundleModel::bundleMaterialImported, this,
[&] (const QmlDesigner::NodeMetaInfo &metaInfo) {
applyBundleMaterialToDropTarget({}, metaInfo);
updateBundleMaterialsImportedState();
});
connect(matBrowserBundleModel, &MaterialBrowserBundleModel::bundleMaterialAboutToUnimport, this,
[&] (const QmlDesigner::TypeName &type) {
// delete instances of the bundle material that is about to be unimported
executeInTransaction("MaterialBrowserView::widgetInfo", [&] {
Utils::reverseForeach(m_widget->materialBrowserModel()->materials(), [&](const ModelNode &mat) {
if (mat.isValid() && mat.type() == type)
QmlObjectNode(mat).destroy();
});
});
});
connect(matBrowserBundleModel, &MaterialBrowserBundleModel::bundleMaterialUnimported, this,
&MaterialBrowserView::updateBundleMaterialsImportedState);
}
return createWidgetInfo(m_widget.data(),
@@ -273,6 +289,7 @@ void MaterialBrowserView::modelAttached(Model *model)
m_widget->materialBrowserModel()->setHasMaterialRoot(rootModelNode().isSubclassOf("QtQuick3D.Material"));
m_hasQuick3DImport = model->hasImport("QtQuick3D");
updateBundleMaterialsImportedState();
// Project load is already very busy and may even trigger puppet reset, so let's wait a moment
// before refreshing the model
@@ -440,6 +457,22 @@ void QmlDesigner::MaterialBrowserView::loadPropertyGroups()
m_propertyGroupsLoaded = m_widget->materialBrowserModel()->loadPropertyGroups(matPropsPath);
}
void MaterialBrowserView::updateBundleMaterialsImportedState()
{
using namespace Utils;
QStringList importedBundleMats;
FilePath materialBundlePath = m_widget->materialBrowserBundleModel()->bundleImporter()->resolveBundleImportPath();
if (materialBundlePath.exists()) {
importedBundleMats = transform(materialBundlePath.dirEntries({{"*.qml"}, QDir::Files}),
[](const FilePath &f) { return f.fileName().chopped(4); });
}
m_widget->materialBrowserBundleModel()->updateImportedState(importedBundleMats);
}
ModelNode MaterialBrowserView::getBundleMaterialDefaultInstance(const TypeName &type)
{
const QList<ModelNode> materials = m_widget->materialBrowserModel()->materials();
@@ -505,7 +538,7 @@ void MaterialBrowserView::customNotification(const AbstractView *view, const QSt
if (defaultMat.isValid())
applyBundleMaterialToDropTarget(defaultMat);
else
m_widget->materialBrowserBundleModel()->addMaterial(m_draggedBundleMaterial);
m_widget->materialBrowserBundleModel()->addToProject(m_draggedBundleMaterial);
m_draggedBundleMaterial = nullptr;
}

View File

@@ -68,6 +68,7 @@ private:
void refreshModel(bool updateImages);
bool isMaterial(const ModelNode &node) const;
void loadPropertyGroups();
void updateBundleMaterialsImportedState();
void applyBundleMaterialToDropTarget(const ModelNode &bundleMat, const NodeMetaInfo &metaInfo = {});
ModelNode getBundleMaterialDefaultInstance(const TypeName &type);
@@ -75,6 +76,7 @@ private:
QList<ModelNode> m_bundleMaterialTargets;
QList<ModelNode> m_selectedModels; // selected 3D model nodes
BundleMaterial *m_draggedBundleMaterial = nullptr;
bool m_bundleMaterialAddToSelected = false;
bool m_hasQuick3DImport = false;
bool m_autoSelectModelMaterial = false; // TODO: wire this to some action