diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml
index d3eb29563d2..94de88fc138 100644
--- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml
+++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibrary.qml
@@ -235,7 +235,6 @@ Item {
cellWidth: root.thumbnailSize
cellHeight: root.thumbnailSize + 20
numColumns: root.numColumns
- hideHorizontalScrollBar: true
searchBox: searchBox
diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryItemContextMenu.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryItemContextMenu.qml
index a2e72335e9f..1e4e177997b 100644
--- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryItemContextMenu.qml
+++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryItemContextMenu.qml
@@ -10,7 +10,8 @@ StudioControls.Menu {
id: root
property var targetItem: null
- property bool enableRemove: false // true: adds an option to remove targetItem
+ property bool showRemoveAction: false // true: adds an option to remove targetItem
+ property bool showImportAction: false // true: adds an option to import a bundle from file
readonly property bool targetAvailable: targetItem && !ContentLibraryBackend.rootView.importerRunning
@@ -18,12 +19,13 @@ StudioControls.Menu {
signal addToProject()
signal applyToSelected(bool add)
signal removeFromContentLib()
+ signal importBundle()
function popupMenu(item = null)
{
root.targetItem = item
- let isMaterial = root.targetItem.bundleId === "UserMaterials"
+ let isMaterial = item && root.targetItem.bundleId === "UserMaterials"
applyToSelectedReplace.visible = isMaterial
applyToSelectedAdd.visible = isMaterial
@@ -64,8 +66,15 @@ StudioControls.Menu {
StudioControls.MenuItem {
text: qsTr("Remove from Content Library")
- visible: root.enableRemove && root.targetAvailable
+ visible: root.showRemoveAction && root.targetAvailable
height: visible ? implicitHeight : 0
onTriggered: root.removeFromContentLib()
}
+
+ StudioControls.MenuItem {
+ text: qsTr("Import bundle")
+ visible: root.showImportAction
+ height: visible ? implicitHeight : 0
+ onTriggered: root.importBundle()
+ }
}
diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTextureContextMenu.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTextureContextMenu.qml
index b1f690f10c9..3dba67243fe 100644
--- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTextureContextMenu.qml
+++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryTextureContextMenu.qml
@@ -12,7 +12,7 @@ StudioControls.Menu {
property var targetTexture: null
property bool hasSceneEnv: false
- property bool enableRemove: false // true: adds an option to remove targetTexture
+ property bool showRemoveAction: false // true: adds an option to remove targetTexture
property bool canUse3D: targetTexture && ContentLibraryBackend.rootView.hasQuick3DImport && ContentLibraryBackend.rootView.hasMaterialLibrary
@@ -45,7 +45,7 @@ StudioControls.Menu {
StudioControls.MenuItem {
text: qsTr("Remove from Content Library")
- visible: root.targetTexture && root.enableRemove
+ visible: root.targetTexture && root.showRemoveAction
height: visible ? implicitHeight : 0
onTriggered: ContentLibraryBackend.userModel.removeTexture(root.targetTexture)
}
diff --git a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml
index f0aa94bba87..b47c01e2466 100644
--- a/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml
+++ b/share/qtcreator/qmldesigner/contentLibraryQmlSource/ContentLibraryUserView.qml
@@ -8,12 +8,10 @@ import StudioControls as StudioControls
import StudioTheme as StudioTheme
import ContentLibraryBackend
-HelperWidgets.ScrollView {
+Item {
id: root
- clip: true
- interactive: !ctxMenuItem.opened && !ctxMenuTexture.opened
- && !ContentLibraryBackend.rootView.isDragging && !HelperWidgets.Controller.contextMenuOpened
+ property alias adsFocus: scrollView.adsFocus
property real cellWidth: 100
property real cellHeight: 120
@@ -46,128 +44,152 @@ HelperWidgets.ScrollView {
}
}
- Column {
- ContentLibraryItemContextMenu {
- id: ctxMenuItem
+ ContentLibraryItemContextMenu {
+ id: ctxMenuItem
- enableRemove: true
+ showRemoveAction: true
+ showImportAction: true
- onApplyToSelected: (add) => ContentLibraryBackend.userModel.applyToSelected(ctxMenuItem.targetItem, add)
+ onApplyToSelected: (add) => ContentLibraryBackend.userModel.applyToSelected(ctxMenuItem.targetItem, add)
- onUnimport: root.unimport(ctxMenuItem.targetItem)
- onAddToProject: ContentLibraryBackend.userModel.addToProject(ctxMenuItem.targetItem)
- onRemoveFromContentLib: root.removeFromContentLib(ctxMenuItem.targetItem)
+ onUnimport: root.unimport(ctxMenuItem.targetItem)
+ onAddToProject: ContentLibraryBackend.userModel.addToProject(ctxMenuItem.targetItem)
+ onRemoveFromContentLib: root.removeFromContentLib(ctxMenuItem.targetItem)
+ onImportBundle: ContentLibraryBackend.rootView.importBundle();
+ }
+
+ ContentLibraryTextureContextMenu {
+ id: ctxMenuTexture
+
+ showRemoveAction: true
+ hasSceneEnv: ContentLibraryBackend.texturesModel.hasSceneEnv
+ }
+
+ MouseArea {
+ id: rootMouseArea
+
+ anchors.fill: parent
+ acceptedButtons: Qt.RightButton
+ enabled: infoText.text === ""
+
+ onClicked: (mouse) => {
+ ctxMenuItem.popupMenu()
}
+ }
- ContentLibraryTextureContextMenu {
- id: ctxMenuTexture
+ HelperWidgets.ScrollView {
+ id: scrollView
+ anchors.fill: parent
- enableRemove: true
- hasSceneEnv: ContentLibraryBackend.texturesModel.hasSceneEnv
- }
+ clip: true
+ interactive: !ctxMenuItem.opened && !ctxMenuTexture.opened
+ && !ContentLibraryBackend.rootView.isDragging && !HelperWidgets.Controller.contextMenuOpened
+ hideHorizontalScrollBar: true
- Repeater {
- id: categoryRepeater
+ Column {
+ Repeater {
+ id: categoryRepeater
- model: ContentLibraryBackend.userModel
+ model: ContentLibraryBackend.userModel
- delegate: HelperWidgets.Section {
- id: section
+ delegate: HelperWidgets.Section {
+ id: section
- width: root.width
- leftPadding: StudioTheme.Values.sectionPadding
- rightPadding: StudioTheme.Values.sectionPadding
- topPadding: StudioTheme.Values.sectionPadding
- bottomPadding: StudioTheme.Values.sectionPadding
+ width: root.width
+ leftPadding: StudioTheme.Values.sectionPadding
+ rightPadding: StudioTheme.Values.sectionPadding
+ topPadding: StudioTheme.Values.sectionPadding
+ bottomPadding: StudioTheme.Values.sectionPadding
- caption: categoryTitle
- visible: !categoryEmpty && infoText.text === ""
- category: "ContentLib_User"
+ caption: categoryTitle
+ visible: !categoryEmpty && infoText.text === ""
+ category: "ContentLib_User"
- function expandSection() {
- section.expanded = true
- }
+ function expandSection() {
+ section.expanded = true
+ }
- property alias count: repeater.count
+ property alias count: repeater.count
- onCountChanged: root.assignMaxCount()
+ onCountChanged: root.assignMaxCount()
- Grid {
- width: section.width - section.leftPadding - section.rightPadding
- spacing: StudioTheme.Values.sectionGridSpacing
- columns: root.numColumns
+ Grid {
+ width: section.width - section.leftPadding - section.rightPadding
+ spacing: StudioTheme.Values.sectionGridSpacing
+ columns: root.numColumns
- Repeater {
- id: repeater
- model: categoryItems
+ Repeater {
+ id: repeater
+ model: categoryItems
- delegate: DelegateChooser {
- role: "bundleId"
+ delegate: DelegateChooser {
+ role: "bundleId"
- DelegateChoice {
- roleValue: "UserMaterials"
- ContentLibraryItem {
- width: root.cellWidth
- height: root.cellHeight
+ DelegateChoice {
+ roleValue: "UserMaterials"
+ ContentLibraryItem {
+ width: root.cellWidth
+ height: root.cellHeight
- onShowContextMenu: ctxMenuItem.popupMenu(modelData)
- onAddToProject: ContentLibraryBackend.userModel.addToProject(modelData)
+ onShowContextMenu: ctxMenuItem.popupMenu(modelData)
+ onAddToProject: ContentLibraryBackend.userModel.addToProject(modelData)
+ }
+ }
+ DelegateChoice {
+ roleValue: "UserTextures"
+ delegate: ContentLibraryTexture {
+ width: root.cellWidth
+ height: root.cellWidth // for textures use a square size since there is no name row
+
+ onShowContextMenu: ctxMenuTexture.popupMenu(modelData)
+ }
+ }
+ DelegateChoice {
+ roleValue: "User3D"
+ delegate: ContentLibraryItem {
+ width: root.cellWidth
+ height: root.cellHeight
+
+ onShowContextMenu: ctxMenuItem.popupMenu(modelData)
+ onAddToProject: ContentLibraryBackend.userModel.addToProject(modelData)
+ }
}
}
- DelegateChoice {
- roleValue: "UserTextures"
- delegate: ContentLibraryTexture {
- width: root.cellWidth
- height: root.cellWidth // for textures use a square size since there is no name row
- onShowContextMenu: ctxMenuTexture.popupMenu(modelData)
- }
- }
- DelegateChoice {
- roleValue: "User3D"
- delegate: ContentLibraryItem {
- width: root.cellWidth
- height: root.cellHeight
-
- onShowContextMenu: ctxMenuItem.popupMenu(modelData)
- onAddToProject: ContentLibraryBackend.userModel.addToProject(modelData)
- }
- }
+ onCountChanged: root.assignMaxCount()
}
+ }
- onCountChanged: root.assignMaxCount()
+ Text {
+ text: qsTr("No match found.");
+ color: StudioTheme.Values.themeTextColor
+ font.pixelSize: StudioTheme.Values.baseFontSize
+ leftPadding: 10
+ visible: infoText.text === "" && !searchBox.isEmpty() && categoryNoMatch
}
}
+ }
- Text {
- text: qsTr("No match found.");
- color: StudioTheme.Values.themeTextColor
- font.pixelSize: StudioTheme.Values.baseFontSize
- leftPadding: 10
- visible: infoText.text === "" && !searchBox.isEmpty() && categoryNoMatch
+ Text {
+ id: infoText
+ text: {
+ if (!ContentLibraryBackend.rootView.isQt6Project)
+ qsTr("Content Library is not supported in Qt5 projects.")
+ else if (!ContentLibraryBackend.rootView.hasQuick3DImport)
+ qsTr("To use Content Library, first add the QtQuick3D module in the Components view.")
+ else if (!ContentLibraryBackend.rootView.hasMaterialLibrary)
+ qsTr("Content Library is disabled inside a non-visual component.")
+ else if (ContentLibraryBackend.userModel.isEmpty)
+ qsTr("There are no user assets in the Content Library.")
+ else
+ ""
}
+ color: StudioTheme.Values.themeTextColor
+ font.pixelSize: StudioTheme.Values.baseFontSize
+ topPadding: 10
+ leftPadding: 10
+ visible: infoText.text !== ""
}
}
-
- Text {
- id: infoText
- text: {
- if (!ContentLibraryBackend.rootView.isQt6Project)
- qsTr("Content Library is not supported in Qt5 projects.")
- else if (!ContentLibraryBackend.rootView.hasQuick3DImport)
- qsTr("To use Content Library, first add the QtQuick3D module in the Components view.")
- else if (!ContentLibraryBackend.rootView.hasMaterialLibrary)
- qsTr("Content Library is disabled inside a non-visual component.")
- else if (ContentLibraryBackend.userModel.isEmpty)
- qsTr("There are no user assets in the Content Library.")
- else
- ""
- }
- color: StudioTheme.Values.themeTextColor
- font.pixelSize: StudioTheme.Values.baseFontSize
- topPadding: 10
- leftPadding: 10
- visible: infoText.text !== ""
- }
}
}
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp
index 422acc19508..b4e7a802be3 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp
@@ -100,6 +100,9 @@ WidgetInfo ContentLibraryView::widgetInfo()
m_widget->environmentsModel()->setHasSceneEnv(sceneEnvExists);
});
+ connect(m_widget, &ContentLibraryWidget::importBundle, this,
+ &ContentLibraryView::importBundleToContentLib);
+
connect(m_widget->materialsModel(),
&ContentLibraryMaterialsModel::applyToSelectedTriggered,
this,
@@ -393,8 +396,8 @@ void ContentLibraryView::customNotification(const AbstractView *view,
exportLibItem(nodeList.first());
} else if (identifier == "export_material_as_bundle") {
exportLibItem(nodeList.first(), data.first().value());
- } else if (identifier == "import_bundle") {
- importBundle();
+ } else if (identifier == "import_bundle_to_project") {
+ importBundleToProject();
}
}
@@ -1023,7 +1026,7 @@ void ContentLibraryView::exportLibItem(const ModelNode &node, const QPixmap &ico
addIconAndCloseZip(iconPixmap);
}
-void ContentLibraryView::importBundle()
+void ContentLibraryView::importBundleToContentLib()
{
QString importPath = getImportPath();
if (importPath.isEmpty())
@@ -1049,6 +1052,7 @@ void ContentLibraryView::importBundle()
bool isMat = isMaterialBundle(bundleId);
QString bundleFolderName = isMat ? QLatin1String("materials") : QLatin1String("3d");
+
auto bundlePath = Utils::FilePath::fromString(QLatin1String("%1/User/%3/")
.arg(Paths::bundlesPathSetting(), bundleFolderName));
@@ -1096,6 +1100,7 @@ void ContentLibraryView::importBundle()
m_widget->userModel()->addItem(bundleId, name, qml, iconUrl, files);
}
+
m_widget->userModel()->refreshSection(bundleId);
zipReader.close();
@@ -1107,6 +1112,85 @@ void ContentLibraryView::importBundle()
QTC_ASSERT_EXPECTED(result,);
}
+void ContentLibraryView::importBundleToProject()
+{
+ QString importPath = getImportPath();
+ if (importPath.isEmpty())
+ return;
+
+ auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
+
+ ZipReader zipReader(importPath);
+
+ QByteArray bundleJsonContent = zipReader.fileData(Constants::BUNDLE_JSON_FILENAME);
+ QTC_ASSERT(!bundleJsonContent.isEmpty(), return);
+
+ const QJsonObject importedJsonObj = QJsonDocument::fromJson(bundleJsonContent).object();
+ const QJsonArray importedItemsArr = importedJsonObj.value("items").toArray();
+ QTC_ASSERT(!importedItemsArr.isEmpty(), return);
+
+ QString bundleVersion = importedJsonObj.value("version").toString();
+ bool bundleVersionOk = !bundleVersion.isEmpty() && bundleVersion == BUNDLE_VERSION;
+ if (!bundleVersionOk) {
+ QMessageBox::warning(m_widget, tr("Unsupported bundle file"),
+ tr("The chosen bundle was created with an incompatible version of Qt Design Studio"));
+ return;
+ }
+ QString bundleId = importedJsonObj.value("id").toString();
+
+ QTemporaryDir tempDir;
+ QTC_ASSERT(tempDir.isValid(), return);
+ auto bundlePath = Utils::FilePath::fromString(tempDir.path());
+
+ const QStringList existingQmls = Utils::transform(compUtils.userBundlePath(bundleId)
+ .dirEntries(QDir::Files), [](const Utils::FilePath &path) {
+ return path.fileName();
+ });
+
+ for (const QJsonValueConstRef &itemRef : importedItemsArr) {
+ QJsonObject itemObj = itemRef.toObject();
+ QString qml = itemObj.value("qml").toString();
+
+ // confirm overwrite if an item with same name exists
+ if (existingQmls.contains(qml)) {
+ QMessageBox::StandardButton reply = QMessageBox::question(m_widget, tr("Component Exists"),
+ tr("A component with the same name '%1' already "
+ "exists in the project, are you sure "
+ "you want to overwrite it?")
+ .arg(qml), QMessageBox::Yes | QMessageBox::No);
+ if (reply == QMessageBox::No)
+ continue;
+
+ // TODO: before overwriting remove old item's dependencies (not harmful but for cleanup)
+ }
+
+ // add entry to model
+ QStringList files = itemObj.value("files").toVariant().toStringList();
+ QString icon = itemObj.value("icon").toString();
+
+ // copy files
+ QStringList allFiles = files;
+ allFiles << qml << icon;
+ for (const QString &file : std::as_const(allFiles)) {
+ Utils::FilePath filePath = bundlePath.pathAppended(file);
+ filePath.parentDir().ensureWritableDir();
+ QTC_ASSERT_EXPECTED(filePath.writeFileContents(zipReader.fileData(file)),);
+ }
+
+ QString typePrefix = compUtils.userBundleType(bundleId);
+ TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1();
+
+ QString err = m_widget->importer()->importComponent(bundlePath.toFSPathString(), type, qml, files);
+
+ if (err.isEmpty())
+ m_widget->setImporterRunning(true);
+ else
+ qWarning() << __FUNCTION__ << err;
+ }
+
+ zipReader.close();
+}
+
/**
* @brief Generates an icon image from a qml component
* @param qmlPath path to the qml component file to be rendered
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h
index 724e94189b9..edd7769f5ee 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.h
@@ -66,7 +66,8 @@ private:
void exportLib3DComponent(const ModelNode &node);
void addLibItem(const ModelNode &node, const QPixmap &iconPixmap = {});
void exportLibItem(const ModelNode &node, const QPixmap &iconPixmap = {});
- void importBundle();
+ void importBundleToContentLib();
+ void importBundleToProject();
void getImageFromCache(const QString &qmlPath,
std::function successCallback);
QString getExportPath(const ModelNode &node) const;
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h
index 8e96d9d2f3b..6ead35f72a1 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibrarywidget.h
@@ -108,6 +108,7 @@ signals:
void isQt6ProjectChanged();
void importerRunningChanged();
void hasModelSelectionChanged();
+ void importBundle();
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp
index f6f514431a9..7a98b21cbd4 100644
--- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp
+++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp
@@ -373,7 +373,7 @@ void Edit3DWidget::createContextMenu()
m_importBundleAction = m_contextMenu->addAction(
contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon
tr("Import Components"), [&] {
- view()->emitCustomNotification("import_bundle"); // To ContentLibrary
+ view()->emitCustomNotification("import_bundle_to_project"); // To ContentLibrary
});
m_exportBundleAction = m_contextMenu->addAction(
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp
index 86fb13a7a33..87b24306a8a 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp
@@ -374,7 +374,7 @@ void MaterialBrowserWidget::addMaterialToContentLibrary()
void MaterialBrowserWidget::importMaterial()
{
ModelNode mat = m_materialBrowserModel->selectedMaterial();
- m_materialBrowserView->emitCustomNotification("import_bundle"); // to ContentLibrary
+ m_materialBrowserView->emitCustomNotification("import_bundle_to_project"); // to ContentLibrary
}
void MaterialBrowserWidget::exportMaterial()
{
diff --git a/src/plugins/qmldesigner/designercore/generatedcomponentutils.cpp b/src/plugins/qmldesigner/designercore/generatedcomponentutils.cpp
index 9d67d1cbf82..75f4cbf4aa2 100644
--- a/src/plugins/qmldesigner/designercore/generatedcomponentutils.cpp
+++ b/src/plugins/qmldesigner/designercore/generatedcomponentutils.cpp
@@ -131,6 +131,25 @@ Utils::FilePath GeneratedComponentUtils::effectBundlePath() const
return basePath.resolvePath(QLatin1String(Constants::COMPONENT_BUNDLES_EFFECT_BUNDLE_TYPE));
}
+Utils::FilePath GeneratedComponentUtils::userBundlePath(const QString &bundleId) const
+{
+ Utils::FilePath basePath = componentBundlesBasePath();
+ if (basePath.isEmpty())
+ return {};
+
+ if (bundleId == userMaterialsBundleId())
+ return basePath.pathAppended(Constants::COMPONENT_BUNDLES_USER_MATERIAL_BUNDLE_TYPE);
+
+ if (bundleId == userEffectsBundleId())
+ return basePath.pathAppended(Constants::COMPONENT_BUNDLES_USER_EFFECT_BUNDLE_TYPE);
+
+ if (bundleId == user3DBundleId())
+ return basePath.pathAppended(Constants::COMPONENT_BUNDLES_USER_3D_BUNDLE_TYPE);
+
+ qWarning() << __FUNCTION__ << "no bundleType for bundleId:" << bundleId;
+ return {};
+}
+
Utils::FilePath GeneratedComponentUtils::projectModulePath(bool generateIfNotExists) const
{
using Utils::FilePath;
diff --git a/src/plugins/qmldesigner/designercore/generatedcomponentutils.h b/src/plugins/qmldesigner/designercore/generatedcomponentutils.h
index df15d252cc3..f35dee554a7 100644
--- a/src/plugins/qmldesigner/designercore/generatedcomponentutils.h
+++ b/src/plugins/qmldesigner/designercore/generatedcomponentutils.h
@@ -23,6 +23,7 @@ public:
Utils::FilePath import3dBasePath() const;
Utils::FilePath materialBundlePath() const;
Utils::FilePath effectBundlePath() const;
+ Utils::FilePath userBundlePath(const QString &bundleId) const;
Utils::FilePath projectModulePath(bool generateIfNotExists = false) const;
bool isImport3dPath(const QString &path) const;