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
|
||||
cellHeight: root.thumbnailSize + 20
|
||||
numColumns: root.numColumns
|
||||
hideHorizontalScrollBar: true
|
||||
|
||||
searchBox: searchBox
|
||||
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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,26 +44,49 @@ HelperWidgets.ScrollView {
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
ContentLibraryItemContextMenu {
|
||||
id: ctxMenuItem
|
||||
|
||||
enableRemove: true
|
||||
showRemoveAction: true
|
||||
showImportAction: true
|
||||
|
||||
onApplyToSelected: (add) => ContentLibraryBackend.userModel.applyToSelected(ctxMenuItem.targetItem, add)
|
||||
|
||||
onUnimport: root.unimport(ctxMenuItem.targetItem)
|
||||
onAddToProject: ContentLibraryBackend.userModel.addToProject(ctxMenuItem.targetItem)
|
||||
onRemoveFromContentLib: root.removeFromContentLib(ctxMenuItem.targetItem)
|
||||
onImportBundle: ContentLibraryBackend.rootView.importBundle();
|
||||
}
|
||||
|
||||
ContentLibraryTextureContextMenu {
|
||||
id: ctxMenuTexture
|
||||
|
||||
enableRemove: true
|
||||
showRemoveAction: true
|
||||
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 {
|
||||
id: categoryRepeater
|
||||
|
||||
@@ -170,4 +191,5 @@ HelperWidgets.ScrollView {
|
||||
visible: infoText.text !== ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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<QPixmap>());
|
||||
} 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
|
||||
|
@@ -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<void(const QImage &image)> successCallback);
|
||||
QString getExportPath(const ModelNode &node) const;
|
||||
|
@@ -108,6 +108,7 @@ signals:
|
||||
void isQt6ProjectChanged();
|
||||
void importerRunningChanged();
|
||||
void hasModelSelectionChanged();
|
||||
void importBundle();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
|
@@ -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(
|
||||
|
@@ -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()
|
||||
{
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user