forked from qt-creator/qt-creator
QmlDesigner: Import bundles directly to project
Change-Id: I75b347d3fa969fe312f35e4c0c770ba03ba3cf85 Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io> Reviewed-by: Ali Kianian <ali.kianian@qt.io>
This commit is contained in:
@@ -235,7 +235,6 @@ Item {
|
|||||||
cellWidth: root.thumbnailSize
|
cellWidth: root.thumbnailSize
|
||||||
cellHeight: root.thumbnailSize + 20
|
cellHeight: root.thumbnailSize + 20
|
||||||
numColumns: root.numColumns
|
numColumns: root.numColumns
|
||||||
hideHorizontalScrollBar: true
|
|
||||||
|
|
||||||
searchBox: searchBox
|
searchBox: searchBox
|
||||||
|
|
||||||
|
@@ -10,7 +10,8 @@ StudioControls.Menu {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var targetItem: null
|
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
|
readonly property bool targetAvailable: targetItem && !ContentLibraryBackend.rootView.importerRunning
|
||||||
|
|
||||||
@@ -18,12 +19,13 @@ StudioControls.Menu {
|
|||||||
signal addToProject()
|
signal addToProject()
|
||||||
signal applyToSelected(bool add)
|
signal applyToSelected(bool add)
|
||||||
signal removeFromContentLib()
|
signal removeFromContentLib()
|
||||||
|
signal importBundle()
|
||||||
|
|
||||||
function popupMenu(item = null)
|
function popupMenu(item = null)
|
||||||
{
|
{
|
||||||
root.targetItem = item
|
root.targetItem = item
|
||||||
|
|
||||||
let isMaterial = root.targetItem.bundleId === "UserMaterials"
|
let isMaterial = item && root.targetItem.bundleId === "UserMaterials"
|
||||||
applyToSelectedReplace.visible = isMaterial
|
applyToSelectedReplace.visible = isMaterial
|
||||||
applyToSelectedAdd.visible = isMaterial
|
applyToSelectedAdd.visible = isMaterial
|
||||||
|
|
||||||
@@ -64,8 +66,15 @@ StudioControls.Menu {
|
|||||||
|
|
||||||
StudioControls.MenuItem {
|
StudioControls.MenuItem {
|
||||||
text: qsTr("Remove from Content Library")
|
text: qsTr("Remove from Content Library")
|
||||||
visible: root.enableRemove && root.targetAvailable
|
visible: root.showRemoveAction && root.targetAvailable
|
||||||
height: visible ? implicitHeight : 0
|
height: visible ? implicitHeight : 0
|
||||||
onTriggered: root.removeFromContentLib()
|
onTriggered: root.removeFromContentLib()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StudioControls.MenuItem {
|
||||||
|
text: qsTr("Import bundle")
|
||||||
|
visible: root.showImportAction
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
onTriggered: root.importBundle()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ StudioControls.Menu {
|
|||||||
|
|
||||||
property var targetTexture: null
|
property var targetTexture: null
|
||||||
property bool hasSceneEnv: false
|
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
|
property bool canUse3D: targetTexture && ContentLibraryBackend.rootView.hasQuick3DImport && ContentLibraryBackend.rootView.hasMaterialLibrary
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ StudioControls.Menu {
|
|||||||
|
|
||||||
StudioControls.MenuItem {
|
StudioControls.MenuItem {
|
||||||
text: qsTr("Remove from Content Library")
|
text: qsTr("Remove from Content Library")
|
||||||
visible: root.targetTexture && root.enableRemove
|
visible: root.targetTexture && root.showRemoveAction
|
||||||
height: visible ? implicitHeight : 0
|
height: visible ? implicitHeight : 0
|
||||||
onTriggered: ContentLibraryBackend.userModel.removeTexture(root.targetTexture)
|
onTriggered: ContentLibraryBackend.userModel.removeTexture(root.targetTexture)
|
||||||
}
|
}
|
||||||
|
@@ -8,12 +8,10 @@ import StudioControls as StudioControls
|
|||||||
import StudioTheme as StudioTheme
|
import StudioTheme as StudioTheme
|
||||||
import ContentLibraryBackend
|
import ContentLibraryBackend
|
||||||
|
|
||||||
HelperWidgets.ScrollView {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
clip: true
|
property alias adsFocus: scrollView.adsFocus
|
||||||
interactive: !ctxMenuItem.opened && !ctxMenuTexture.opened
|
|
||||||
&& !ContentLibraryBackend.rootView.isDragging && !HelperWidgets.Controller.contextMenuOpened
|
|
||||||
|
|
||||||
property real cellWidth: 100
|
property real cellWidth: 100
|
||||||
property real cellHeight: 120
|
property real cellHeight: 120
|
||||||
@@ -46,26 +44,49 @@ HelperWidgets.ScrollView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
|
||||||
ContentLibraryItemContextMenu {
|
ContentLibraryItemContextMenu {
|
||||||
id: ctxMenuItem
|
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)
|
onUnimport: root.unimport(ctxMenuItem.targetItem)
|
||||||
onAddToProject: ContentLibraryBackend.userModel.addToProject(ctxMenuItem.targetItem)
|
onAddToProject: ContentLibraryBackend.userModel.addToProject(ctxMenuItem.targetItem)
|
||||||
onRemoveFromContentLib: root.removeFromContentLib(ctxMenuItem.targetItem)
|
onRemoveFromContentLib: root.removeFromContentLib(ctxMenuItem.targetItem)
|
||||||
|
onImportBundle: ContentLibraryBackend.rootView.importBundle();
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentLibraryTextureContextMenu {
|
ContentLibraryTextureContextMenu {
|
||||||
id: ctxMenuTexture
|
id: ctxMenuTexture
|
||||||
|
|
||||||
enableRemove: true
|
showRemoveAction: true
|
||||||
hasSceneEnv: ContentLibraryBackend.texturesModel.hasSceneEnv
|
hasSceneEnv: ContentLibraryBackend.texturesModel.hasSceneEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: rootMouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
enabled: infoText.text === ""
|
||||||
|
|
||||||
|
onClicked: (mouse) => {
|
||||||
|
ctxMenuItem.popupMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperWidgets.ScrollView {
|
||||||
|
id: scrollView
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
interactive: !ctxMenuItem.opened && !ctxMenuTexture.opened
|
||||||
|
&& !ContentLibraryBackend.rootView.isDragging && !HelperWidgets.Controller.contextMenuOpened
|
||||||
|
hideHorizontalScrollBar: true
|
||||||
|
|
||||||
|
Column {
|
||||||
Repeater {
|
Repeater {
|
||||||
id: categoryRepeater
|
id: categoryRepeater
|
||||||
|
|
||||||
@@ -170,4 +191,5 @@ HelperWidgets.ScrollView {
|
|||||||
visible: infoText.text !== ""
|
visible: infoText.text !== ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -100,6 +100,9 @@ WidgetInfo ContentLibraryView::widgetInfo()
|
|||||||
m_widget->environmentsModel()->setHasSceneEnv(sceneEnvExists);
|
m_widget->environmentsModel()->setHasSceneEnv(sceneEnvExists);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(m_widget, &ContentLibraryWidget::importBundle, this,
|
||||||
|
&ContentLibraryView::importBundleToContentLib);
|
||||||
|
|
||||||
connect(m_widget->materialsModel(),
|
connect(m_widget->materialsModel(),
|
||||||
&ContentLibraryMaterialsModel::applyToSelectedTriggered,
|
&ContentLibraryMaterialsModel::applyToSelectedTriggered,
|
||||||
this,
|
this,
|
||||||
@@ -393,8 +396,8 @@ void ContentLibraryView::customNotification(const AbstractView *view,
|
|||||||
exportLibItem(nodeList.first());
|
exportLibItem(nodeList.first());
|
||||||
} else if (identifier == "export_material_as_bundle") {
|
} else if (identifier == "export_material_as_bundle") {
|
||||||
exportLibItem(nodeList.first(), data.first().value<QPixmap>());
|
exportLibItem(nodeList.first(), data.first().value<QPixmap>());
|
||||||
} else if (identifier == "import_bundle") {
|
} else if (identifier == "import_bundle_to_project") {
|
||||||
importBundle();
|
importBundleToProject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1023,7 +1026,7 @@ void ContentLibraryView::exportLibItem(const ModelNode &node, const QPixmap &ico
|
|||||||
addIconAndCloseZip(iconPixmap);
|
addIconAndCloseZip(iconPixmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentLibraryView::importBundle()
|
void ContentLibraryView::importBundleToContentLib()
|
||||||
{
|
{
|
||||||
QString importPath = getImportPath();
|
QString importPath = getImportPath();
|
||||||
if (importPath.isEmpty())
|
if (importPath.isEmpty())
|
||||||
@@ -1049,6 +1052,7 @@ void ContentLibraryView::importBundle()
|
|||||||
bool isMat = isMaterialBundle(bundleId);
|
bool isMat = isMaterialBundle(bundleId);
|
||||||
|
|
||||||
QString bundleFolderName = isMat ? QLatin1String("materials") : QLatin1String("3d");
|
QString bundleFolderName = isMat ? QLatin1String("materials") : QLatin1String("3d");
|
||||||
|
|
||||||
auto bundlePath = Utils::FilePath::fromString(QLatin1String("%1/User/%3/")
|
auto bundlePath = Utils::FilePath::fromString(QLatin1String("%1/User/%3/")
|
||||||
.arg(Paths::bundlesPathSetting(), bundleFolderName));
|
.arg(Paths::bundlesPathSetting(), bundleFolderName));
|
||||||
|
|
||||||
@@ -1096,6 +1100,7 @@ void ContentLibraryView::importBundle()
|
|||||||
|
|
||||||
m_widget->userModel()->addItem(bundleId, name, qml, iconUrl, files);
|
m_widget->userModel()->addItem(bundleId, name, qml, iconUrl, files);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_widget->userModel()->refreshSection(bundleId);
|
m_widget->userModel()->refreshSection(bundleId);
|
||||||
|
|
||||||
zipReader.close();
|
zipReader.close();
|
||||||
@@ -1107,6 +1112,85 @@ void ContentLibraryView::importBundle()
|
|||||||
QTC_ASSERT_EXPECTED(result,);
|
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
|
* @brief Generates an icon image from a qml component
|
||||||
* @param qmlPath path to the qml component file to be rendered
|
* @param qmlPath path to the qml component file to be rendered
|
||||||
|
@@ -66,7 +66,8 @@ private:
|
|||||||
void exportLib3DComponent(const ModelNode &node);
|
void exportLib3DComponent(const ModelNode &node);
|
||||||
void addLibItem(const ModelNode &node, const QPixmap &iconPixmap = {});
|
void addLibItem(const ModelNode &node, const QPixmap &iconPixmap = {});
|
||||||
void exportLibItem(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,
|
void getImageFromCache(const QString &qmlPath,
|
||||||
std::function<void(const QImage &image)> successCallback);
|
std::function<void(const QImage &image)> successCallback);
|
||||||
QString getExportPath(const ModelNode &node) const;
|
QString getExportPath(const ModelNode &node) const;
|
||||||
|
@@ -108,6 +108,7 @@ signals:
|
|||||||
void isQt6ProjectChanged();
|
void isQt6ProjectChanged();
|
||||||
void importerRunningChanged();
|
void importerRunningChanged();
|
||||||
void hasModelSelectionChanged();
|
void hasModelSelectionChanged();
|
||||||
|
void importBundle();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||||
|
@@ -373,7 +373,7 @@ void Edit3DWidget::createContextMenu()
|
|||||||
m_importBundleAction = m_contextMenu->addAction(
|
m_importBundleAction = m_contextMenu->addAction(
|
||||||
contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon
|
contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon
|
||||||
tr("Import Components"), [&] {
|
tr("Import Components"), [&] {
|
||||||
view()->emitCustomNotification("import_bundle"); // To ContentLibrary
|
view()->emitCustomNotification("import_bundle_to_project"); // To ContentLibrary
|
||||||
});
|
});
|
||||||
|
|
||||||
m_exportBundleAction = m_contextMenu->addAction(
|
m_exportBundleAction = m_contextMenu->addAction(
|
||||||
|
@@ -374,7 +374,7 @@ void MaterialBrowserWidget::addMaterialToContentLibrary()
|
|||||||
void MaterialBrowserWidget::importMaterial()
|
void MaterialBrowserWidget::importMaterial()
|
||||||
{
|
{
|
||||||
ModelNode mat = m_materialBrowserModel->selectedMaterial();
|
ModelNode mat = m_materialBrowserModel->selectedMaterial();
|
||||||
m_materialBrowserView->emitCustomNotification("import_bundle"); // to ContentLibrary
|
m_materialBrowserView->emitCustomNotification("import_bundle_to_project"); // to ContentLibrary
|
||||||
}
|
}
|
||||||
void MaterialBrowserWidget::exportMaterial()
|
void MaterialBrowserWidget::exportMaterial()
|
||||||
{
|
{
|
||||||
|
@@ -131,6 +131,25 @@ Utils::FilePath GeneratedComponentUtils::effectBundlePath() const
|
|||||||
return basePath.resolvePath(QLatin1String(Constants::COMPONENT_BUNDLES_EFFECT_BUNDLE_TYPE));
|
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
|
Utils::FilePath GeneratedComponentUtils::projectModulePath(bool generateIfNotExists) const
|
||||||
{
|
{
|
||||||
using Utils::FilePath;
|
using Utils::FilePath;
|
||||||
|
@@ -23,6 +23,7 @@ public:
|
|||||||
Utils::FilePath import3dBasePath() const;
|
Utils::FilePath import3dBasePath() const;
|
||||||
Utils::FilePath materialBundlePath() const;
|
Utils::FilePath materialBundlePath() const;
|
||||||
Utils::FilePath effectBundlePath() const;
|
Utils::FilePath effectBundlePath() const;
|
||||||
|
Utils::FilePath userBundlePath(const QString &bundleId) const;
|
||||||
Utils::FilePath projectModulePath(bool generateIfNotExists = false) const;
|
Utils::FilePath projectModulePath(bool generateIfNotExists = false) const;
|
||||||
|
|
||||||
bool isImport3dPath(const QString &path) const;
|
bool isImport3dPath(const QString &path) const;
|
||||||
|
Reference in New Issue
Block a user