diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/BundleMaterialItem.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/BundleMaterialItem.qml
new file mode 100644
index 00000000000..4b8b45be564
--- /dev/null
+++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/BundleMaterialItem.qml
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** 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 2.15
+import QtQuick.Layouts 1.15
+import QtQuickDesignerTheme 1.0
+import HelperWidgets 2.0
+import StudioTheme 1.0 as StudioTheme
+
+Item {
+ id: root
+
+ signal showContextMenu()
+
+ visible: modelData.bundleMaterialVisible
+
+ MouseArea {
+ id: mouseArea
+
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+
+ onPressed: (mouse) => {
+ if (mouse.button === Qt.LeftButton)
+ rootView.startDragBundleMaterial(modelData, mapToGlobal(mouse.x, mouse.y))
+ else if (mouse.button === Qt.RightButton)
+ root.showContextMenu()
+ }
+ }
+
+ Column {
+ anchors.fill: parent
+ spacing: 1
+
+ Item { width: 1; height: 5 } // spacer
+
+ Image {
+ id: img
+
+ width: root.width - 10
+ height: img.width
+ anchors.horizontalCenter: parent.horizontalCenter
+ source: modelData.bundleMaterialIcon
+ cache: false
+ }
+
+ TextInput {
+ id: matName
+
+ text: modelData.bundleMaterialName
+
+ width: img.width
+ clip: true
+ anchors.horizontalCenter: parent.horizontalCenter
+ horizontalAlignment: TextInput.AlignHCenter
+
+ font.pixelSize: StudioTheme.Values.myFontSize
+
+ readOnly: true
+ selectByMouse: !matName.readOnly
+
+ color: StudioTheme.Values.themeTextColor
+ selectionColor: StudioTheme.Values.themeTextSelectionColor
+ selectedTextColor: StudioTheme.Values.themeTextSelectedTextColor
+ }
+ }
+}
diff --git a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml
index a8495ffe01e..bda29dcb132 100644
--- a/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml
+++ b/share/qtcreator/qmldesigner/materialBrowserQmlSource/MaterialBrowser.qml
@@ -16,13 +16,15 @@ Item {
property var currentMaterial: null
property int currentMaterialIdx: 0
+ property var currentBundleMaterial: null
property var matSectionsModel: []
// Called also from C++ to close context menu on focus out
function closeContextMenu()
{
- contextMenu.close()
+ cxtMenu.close()
+ cxtMenuBundle.close()
}
// Called from C++ to refresh a preview material after it changes
@@ -46,10 +48,15 @@ Item {
acceptedButtons: Qt.RightButton
- onClicked: {
- if (!materialBrowserModel.hasMaterialRoot) {
+ onClicked: (mouse) => {
+ // root context-menu works only for user materials
+ var userMatsSecBottom = mapFromItem(userMaterialsSection, 0, userMaterialsSection.y).y
+ + userMaterialsSection.height;
+
+ if (!materialBrowserModel.hasMaterialRoot && (!materialBrowserBundleModel.matBundleExists
+ || mouse.y < userMatsSecBottom)) {
root.currentMaterial = null
- contextMenu.popup()
+ cxtMenu.popup()
}
}
}
@@ -68,7 +75,7 @@ Item {
}
StudioControls.Menu {
- id: contextMenu
+ id: cxtMenu
closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside
@@ -166,6 +173,32 @@ Item {
}
}
+ StudioControls.Menu {
+ id: cxtMenuBundle
+
+ closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside
+
+ StudioControls.MenuItem {
+ text: qsTr("Apply to selected (replace)")
+ enabled: root.currentBundleMaterial && materialBrowserModel.hasModelSelection
+ onTriggered: materialBrowserBundleModel.applyToSelected(root.currentBundleMaterial, false)
+ }
+
+ StudioControls.MenuItem {
+ text: qsTr("Apply to selected (add)")
+ enabled: root.currentBundleMaterial && materialBrowserModel.hasModelSelection
+ onTriggered: materialBrowserBundleModel.applyToSelected(root.currentBundleMaterial, true)
+ }
+
+ StudioControls.MenuSeparator {}
+
+ StudioControls.MenuItem {
+ text: qsTr("Add to project")
+
+ onTriggered: materialBrowserBundleModel.addMaterial(root.currentBundleMaterial)
+ }
+ }
+
Column {
id: col
y: 5
@@ -193,23 +226,12 @@ Item {
}
}
- Text {
- text: qsTr("No match found.");
- color: StudioTheme.Values.themeTextColor
- font.pixelSize: StudioTheme.Values.baseFontSize
- leftPadding: 10
- visible: materialBrowserModel.hasQuick3DImport && materialBrowserModel.isEmpty
- && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot
- }
-
Text {
text: {
if (materialBrowserModel.hasMaterialRoot)
qsTr("Material Browser is disabled inside a material component.")
else if (!materialBrowserModel.hasQuick3DImport)
qsTr("To use Material Browser, first add the QtQuick3D module in the Components view.")
- else if (materialBrowserModel.isEmpty && searchBox.isEmpty())
- qsTr("There are no materials in this project.
Select '+' to create one.")
else
""
}
@@ -230,31 +252,115 @@ Item {
width: root.width
height: root.height - searchBox.height
clip: true
+ visible: materialBrowserModel.hasQuick3DImport && !materialBrowserModel.hasMaterialRoot
- Grid {
- id: grid
+ Column {
+ Section {
+ id: userMaterialsSection
- width: scrollView.width
- leftPadding: 5
- rightPadding: 5
- bottomPadding: 5
- columns: root.width / root.cellWidth
+ width: root.width
+ caption: qsTr("User materials")
+ hideHeader: !materialBrowserBundleModel.matBundleExists
- Repeater {
- id: gridRepeater
+ Grid {
+ id: grid
- model: materialBrowserModel
- delegate: MaterialItem {
- width: root.cellWidth
- height: root.cellHeight
+ width: scrollView.width
+ leftPadding: 5
+ rightPadding: 5
+ bottomPadding: 5
+ columns: root.width / root.cellWidth
- onShowContextMenu: {
- if (searchBox.isEmpty()) {
- root.currentMaterial = model
- contextMenu.popup()
+ Repeater {
+ id: gridRepeater
+
+ model: materialBrowserModel
+ delegate: MaterialItem {
+ width: root.cellWidth
+ height: root.cellHeight
+
+ onShowContextMenu: {
+ if (searchBox.isEmpty()) {
+ root.currentMaterial = model
+ cxtMenu.popup()
+ }
+ }
}
}
}
+
+ Text {
+ text: qsTr("No match found.");
+ color: StudioTheme.Values.themeTextColor
+ font.pixelSize: StudioTheme.Values.baseFontSize
+ leftPadding: 10
+ visible: materialBrowserModel.isEmpty && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot
+ }
+
+ Text {
+ text:qsTr("There are no materials in this project.
Select '+' to create one.")
+ visible: materialBrowserModel.isEmpty && searchBox.isEmpty()
+ textFormat: Text.RichText
+ color: StudioTheme.Values.themeTextColor
+ font.pixelSize: StudioTheme.Values.mediumFontSize
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.WordWrap
+ width: root.width
+ }
+ }
+
+ Section {
+ width: root.width
+ caption: qsTr("Material Library")
+ addTopPadding: noMatchText.visible
+ visible: materialBrowserBundleModel.matBundleExists
+
+ Column {
+ Repeater {
+ model: materialBrowserBundleModel
+
+ delegate: Section {
+ width: root.width
+ caption: bundleCategory
+ addTopPadding: false
+ sectionBackgroundColor: "transparent"
+ visible: bundleCategoryVisible
+
+ Grid {
+ width: scrollView.width
+ leftPadding: 5
+ rightPadding: 5
+ bottomPadding: 5
+ columns: root.width / root.cellWidth
+
+ Repeater {
+ model: bundleMaterialsModel
+
+ delegate: BundleMaterialItem {
+ width: root.cellWidth
+ height: root.cellHeight
+
+ onShowContextMenu: {
+ if (searchBox.isEmpty()) {
+ root.currentBundleMaterial = modelData
+ cxtMenuBundle.popup()
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Text {
+ id: noMatchText
+ text: qsTr("No match found.");
+ color: StudioTheme.Values.themeTextColor
+ font.pixelSize: StudioTheme.Values.baseFontSize
+ leftPadding: 10
+ visible: materialBrowserBundleModel.isEmpty && !searchBox.isEmpty() && !materialBrowserModel.hasMaterialRoot
+ }
+ }
}
}
}
diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector2dEditorTemplate.template b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector2dEditorTemplate.template
index b339c254bac..ab2eb508d17 100644
--- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector2dEditorTemplate.template
+++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector2dEditorTemplate.template
@@ -3,40 +3,38 @@ PropertyLabel {
tooltip: "%1"
}
-ColumnLayout {
- SecondColumnLayout {
- SpinBox {
- minimumValue: -9999999
- maximumValue: 9999999
- decimals: 2
- backendValue: backendValues.%2_x
- implicitWidth: StudioTheme.Values.twoControlColumnWidth
- + StudioTheme.Values.actionIndicatorWidth
- }
-
- Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
-
- ControlLabel {
- text: "X"
- }
-
- Spacer { implicitWidth: StudioTheme.Values.controlGap }
-
- SpinBox {
- minimumValue: -9999999
- maximumValue: 9999999
- decimals: 2
- backendValue: backendValues.%2_y
- implicitWidth: StudioTheme.Values.twoControlColumnWidth
- + StudioTheme.Values.actionIndicatorWidth
- }
-
- Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
-
- ControlLabel {
- text: "Y"
- }
-
- ExpandingSpacer {}
+SecondColumnLayout {
+ SpinBox {
+ minimumValue: -9999999
+ maximumValue: 9999999
+ decimals: 2
+ backendValue: backendValues.%2_x
+ implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ + StudioTheme.Values.actionIndicatorWidth
}
+
+ Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
+
+ ControlLabel {
+ text: "X"
+ }
+
+ Spacer { implicitWidth: StudioTheme.Values.controlGap }
+
+ SpinBox {
+ minimumValue: -9999999
+ maximumValue: 9999999
+ decimals: 2
+ backendValue: backendValues.%2_y
+ implicitWidth: StudioTheme.Values.twoControlColumnWidth
+ + StudioTheme.Values.actionIndicatorWidth
+ }
+
+ Spacer { implicitWidth: StudioTheme.Values.controlLabelGap }
+
+ ControlLabel {
+ text: "Y"
+ }
+
+ ExpandingSpacer {}
}
diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector3dEditorTemplate.template b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector3dEditorTemplate.template
index 5caff585d73..c5be9374741 100644
--- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector3dEditorTemplate.template
+++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector3dEditorTemplate.template
@@ -4,6 +4,7 @@ PropertyLabel {
}
ColumnLayout {
+ spacing: StudioTheme.Values.sectionRowSpacing / 2
SecondColumnLayout {
SpinBox {
minimumValue: -9999999
diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector4dEditorTemplate.template b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector4dEditorTemplate.template
index 619f51cebe3..0fd6cb9f7f1 100644
--- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector4dEditorTemplate.template
+++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/Vector4dEditorTemplate.template
@@ -4,6 +4,7 @@ PropertyLabel {
}
ColumnLayout {
+ spacing: StudioTheme.Values.sectionRowSpacing / 2
SecondColumnLayout {
SpinBox {
minimumValue: -9999999
diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml
index 4bd99e3f68e..2a0580edf14 100644
--- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml
+++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/DynamicPropertiesSection.qml
@@ -297,7 +297,7 @@ Section {
onVecSizeChanged: updateProxyValues()
- spacing: StudioTheme.Values.sectionRowSpacing
+ spacing: StudioTheme.Values.sectionRowSpacing / 2
function isValidValue(v) {
return !(v === undefined || isNaN(v))
diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ToolTip.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ToolTip.qml
new file mode 100644
index 00000000000..0c99890e6b5
--- /dev/null
+++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/ToolTip.qml
@@ -0,0 +1,60 @@
+/****************************************************************************
+**
+** 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.Templates as T
+import StudioTheme 1.0 as StudioTheme
+
+T.ToolTip {
+ id: control
+
+ x: parent ? (parent.width - implicitWidth) / 2 : 0
+ y: -implicitHeight - 3
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ contentWidth + leftPadding + rightPadding)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ contentHeight + topPadding + bottomPadding)
+
+ margins: 6
+ padding: 4
+ delay: 1000
+ timeout: 5000
+ closePolicy: T.Popup.CloseOnEscape | T.Popup.CloseOnPressOutsideParent | T.Popup.CloseOnReleaseOutsideParent
+
+ contentItem: Text {
+ text: control.text
+ wrapMode: Text.Wrap
+ font.pixelSize: StudioTheme.Values.myFontSize
+ color: StudioTheme.Values.themeToolTipText
+ }
+
+ background: Rectangle {
+ color: StudioTheme.Values.themeToolTipBackground
+ border.width: StudioTheme.Values.border
+ border.color: StudioTheme.Values.themeToolTipOutline
+ }
+}
+
diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir
index 61c35a00490..878b2307c38 100644
--- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir
+++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioControls/qmldir
@@ -45,5 +45,6 @@ TabBar 1.0 TabBar.qml
TabButton 1.0 TabButton.qml
TextArea 1.0 TextArea.qml
TextField 1.0 TextField.qml
+ToolTip 1.0 ToolTip.qml
TranslationIndicator 1.0 TranslationIndicator.qml
VerticalScrollBar 1.0 VerticalScrollBar.qml
diff --git a/src/libs/utils/deviceshell.cpp b/src/libs/utils/deviceshell.cpp
index 9ba61c5896b..bfa33418a3d 100644
--- a/src/libs/utils/deviceshell.cpp
+++ b/src/libs/utils/deviceshell.cpp
@@ -159,12 +159,12 @@ DeviceShell::DeviceShell()
DeviceShell::~DeviceShell()
{
- m_shellProcess->deleteLater();
-
if (m_thread.isRunning()) {
m_thread.quit();
m_thread.wait();
}
+
+ QTC_CHECK(!m_shellProcess);
}
/*!
@@ -237,7 +237,7 @@ DeviceShell::RunResult DeviceShell::run(const CommandLine &cmd, const QByteArray
const int id = ++m_currentId;
const auto it = m_commandOutput.insert(id, CommandRun{{-1, {}, {}}, &waiter});
- QMetaObject::invokeMethod(m_shellProcess, [this, id, cmd, stdInData]() {
+ QMetaObject::invokeMethod(m_shellProcess.get(), [this, id, cmd, stdInData]() {
const QString command = QString("%1 \"%2\" %3\n")
.arg(id)
.arg(QString::fromLatin1(stdInData.toBase64()))
@@ -304,13 +304,12 @@ void DeviceShell::startupFailed(const CommandLine &cmdLine)
*/
bool DeviceShell::start()
{
- m_shellProcess = new QtcProcess();
- connect(m_shellProcess, &QtcProcess::done, m_shellProcess,
+ m_shellProcess = std::make_unique();
+ connect(m_shellProcess.get(), &QtcProcess::done, m_shellProcess.get(),
[this] { emit done(m_shellProcess->resultData()); });
- connect(m_shellProcess, &QObject::destroyed, this, [this] { m_shellProcess = nullptr; });
- connect(&m_thread, &QThread::finished, m_shellProcess, [this] { closeShellProcess(); });
+ connect(&m_thread, &QThread::finished, m_shellProcess.get(), [this] { closeShellProcess(); }, Qt::DirectConnection);
- setupShellProcess(m_shellProcess);
+ setupShellProcess(m_shellProcess.get());
m_shellProcess->setProcessMode(ProcessMode::Writer);
@@ -319,7 +318,7 @@ bool DeviceShell::start()
bool result = false;
QMetaObject::invokeMethod(
- m_shellProcess,
+ m_shellProcess.get(),
[this] {
qCDebug(deviceShellLog) << "Starting shell process:" << m_shellProcess->commandLine().toUserOutput();
m_shellProcess->start();
@@ -333,16 +332,21 @@ bool DeviceShell::start()
if (m_shellScriptState == State::FailedToStart)
closeShellProcess();
} else {
- connect(m_shellProcess, &QtcProcess::readyReadStandardOutput, m_shellProcess, [this] {
- onReadyRead();
- });
- connect(m_shellProcess, &QtcProcess::readyReadStandardError, m_shellProcess, [this] {
- const QByteArray stdErr = m_shellProcess->readAllStandardError();
- qCWarning(deviceShellLog) << "Received unexpected output on stderr:" << stdErr;
- });
+ connect(m_shellProcess.get(),
+ &QtcProcess::readyReadStandardOutput,
+ m_shellProcess.get(),
+ [this] { onReadyRead(); });
+ connect(m_shellProcess.get(),
+ &QtcProcess::readyReadStandardError,
+ m_shellProcess.get(),
+ [this] {
+ const QByteArray stdErr = m_shellProcess->readAllStandardError();
+ qCWarning(deviceShellLog)
+ << "Received unexpected output on stderr:" << stdErr;
+ });
}
- connect(m_shellProcess, &QtcProcess::done, m_shellProcess, [this] {
+ connect(m_shellProcess.get(), &QtcProcess::done, m_shellProcess.get(), [this] {
if (m_shellProcess->resultData().m_exitCode != EXIT_SUCCESS
|| m_shellProcess->resultData().m_exitStatus != QProcess::NormalExit) {
qCWarning(deviceShellLog) << "Shell exited with error code:"
@@ -428,6 +432,7 @@ void DeviceShell::closeShellProcess()
if (!m_shellProcess->waitForFinished(2000))
m_shellProcess->terminate();
}
+ m_shellProcess.reset();
}
}
diff --git a/src/libs/utils/deviceshell.h b/src/libs/utils/deviceshell.h
index 4b376268387..1c780997706 100644
--- a/src/libs/utils/deviceshell.h
+++ b/src/libs/utils/deviceshell.h
@@ -79,7 +79,7 @@ private:
QWaitCondition *waiter;
};
- QtcProcess *m_shellProcess = nullptr;
+ std::unique_ptr m_shellProcess;
QThread m_thread;
int m_currentId{0};
diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp
index 0301e6f2315..71b406968f2 100644
--- a/src/plugins/docker/dockerdevice.cpp
+++ b/src/plugins/docker/dockerdevice.cpp
@@ -479,7 +479,7 @@ void DockerDevicePrivate::startContainer()
return;
qCWarning(dockerDeviceLog) << "Container shell encountered error:" << resultData.m_error;
- m_shell.reset();
+ m_shell.release()->deleteLater();
DockerApi::recheckDockerDaemon();
MessageManager::writeFlashing(Tr::tr("Docker daemon appears to be not running. "
@@ -501,7 +501,7 @@ void DockerDevicePrivate::updateContainerAccess()
if (m_shell)
return;
- startContainer();
+ startContainer();
}
void DockerDevice::setMounts(const QStringList &mounts) const
diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt
index 6909ef7a405..cbffad21b31 100644
--- a/src/plugins/qmldesigner/CMakeLists.txt
+++ b/src/plugins/qmldesigner/CMakeLists.txt
@@ -408,6 +408,9 @@ extend_qtc_plugin(QmlDesigner
materialbrowserwidget.cpp materialbrowserwidget.h
materialbrowsermodel.cpp materialbrowsermodel.h
bundleimporter.cpp bundleimporter.h
+ materialbrowserbundlemodel.cpp materialbrowserbundlemodel.h
+ bundlematerial.cpp bundlematerial.h
+ bundlematerialcategory.cpp bundlematerialcategory.h
)
extend_qtc_plugin(QmlDesigner
diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp
index f0789bd6d9d..d9a0ad2824f 100644
--- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp
+++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp
@@ -1,15 +1,16 @@
// Copyright (C) 2020 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 "edit3dview.h"
+#include "backgroundcolorselection.h"
#include "edit3dactions.h"
#include "edit3dcanvas.h"
-#include "edit3dview.h"
-#include "edit3dwidget.h"
#include "edit3dviewconfig.h"
-#include "backgroundcolorselection.h"
+#include "edit3dwidget.h"
#include "metainfo.h"
-#include "seekerslider.h"
#include "nodehints.h"
+#include "seekerslider.h"
+#include "view3dactioncommand.h"
#include
#include
@@ -287,6 +288,8 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos
assignMaterialTo3dModel(modelNode, m_droppedMaterial);
});
}
+ } else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleMaterialDrop) {
+ emitCustomNotification("drop_bundle_material", {modelNode}); // To MaterialBrowserView
}
m_nodeAtPosReqType = NodeAtPosReqType::None;
}
@@ -837,4 +840,11 @@ void Edit3DView::dropMaterial(const ModelNode &matNode, const QPointF &pos)
emitView3DAction(View3DActionType::GetNodeAtPos, pos);
}
+void Edit3DView::dropBundleMaterial(const QPointF &pos)
+{
+ m_nodeAtPosReqType = NodeAtPosReqType::BundleMaterialDrop;
+ QmlDesignerPlugin::instance()->viewManager().nodeInstanceView()->view3DAction(
+ View3DActionType::GetNodeAtPos, pos);
+}
+
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h
index bd866098e5d..94c88ce5a20 100644
--- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h
+++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h
@@ -58,12 +58,14 @@ public:
void addQuick3DImport();
void startContextMenu(const QPoint &pos);
void dropMaterial(const ModelNode &matNode, const QPointF &pos);
+ void dropBundleMaterial(const QPointF &pos);
private slots:
void onEntriesChanged();
private:
enum class NodeAtPosReqType {
+ BundleMaterialDrop,
MaterialDrop,
ContextMenu,
None
diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp
index 2c497a425f3..2f02c1c620f 100644
--- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp
+++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp
@@ -309,7 +309,8 @@ void Edit3DWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent)
const DesignerActionManager &actionManager = QmlDesignerPlugin::instance()
->viewManager().designerActionManager();
if (actionManager.externalDragHasSupportedAssets(dragEnterEvent->mimeData())
- || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL)) {
+ || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_MATERIAL)
+ || dragEnterEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL)) {
dragEnterEvent->acceptProposedAction();
}
}
@@ -329,6 +330,12 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent)
return;
}
+ // handle dropping bundle materials
+ if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL)) {
+ m_view->dropBundleMaterial(dropEvent->position());
+ return;
+ }
+
// handle dropping external assets
const DesignerActionManager &actionManager = QmlDesignerPlugin::instance()
->viewManager().designerActionManager();
diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp
index 357be8db45a..422288c8b20 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp
+++ b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.cpp
@@ -33,9 +33,9 @@
#include
-#include
#include
-#include
+#include
+#include
#include
using namespace Utils;
@@ -65,20 +65,14 @@ BundleImporter::BundleImporter(const QString &bundleDir,
QString BundleImporter::importComponent(const QString &qmlFile,
const QStringList &files)
{
- FilePath bundleImportPath = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath();
+ FilePath bundleImportPath = resolveBundleImportPath();
if (bundleImportPath.isEmpty())
- return "Failed to resolve current project path";
+ return "Failed to resolve bundle import folder";
- const QString projectBundlePath = QStringLiteral("%1%2/%3").arg(
- QLatin1String(Constants::DEFAULT_ASSET_IMPORT_FOLDER),
- QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER),
- m_bundleId).mid(1); // Chop leading slash
- bundleImportPath = bundleImportPath.resolvePath(projectBundlePath);
+ bool bundleImportPathExists = bundleImportPath.exists();
- if (!bundleImportPath.exists()) {
- if (!bundleImportPath.createDir())
- return QStringLiteral("Failed to create bundle import folder: '%1'").arg(bundleImportPath.toString());
- }
+ if (!bundleImportPathExists && !bundleImportPath.createDir())
+ return QStringLiteral("Failed to create bundle import folder: '%1'").arg(bundleImportPath.toString());
for (const QString &file : qAsConst(m_sharedFiles)) {
FilePath target = bundleImportPath.resolvePath(file);
@@ -93,37 +87,24 @@ QString BundleImporter::importComponent(const QString &qmlFile,
}
FilePath qmldirPath = bundleImportPath.resolvePath(QStringLiteral("qmldir"));
- QFile qmldirFile(qmldirPath.toString());
-
- QString qmldirContent;
- if (qmldirPath.exists()) {
- if (!qmldirFile.open(QIODeviceBase::ReadOnly))
- return QStringLiteral("Failed to open qmldir file for reading: '%1'").arg(qmldirPath.toString());
- qmldirContent = QString::fromUtf8(qmldirFile.readAll());
- qmldirFile.close();
- } else {
+ QString qmldirContent = QString::fromUtf8(qmldirPath.fileContents().value_or(QByteArray()));
+ if (qmldirContent.isEmpty()) {
qmldirContent.append("module ");
qmldirContent.append(m_moduleName);
qmldirContent.append('\n');
}
- FilePath qmlSourceFile = FilePath::fromString(qmlFile);
+ FilePath qmlSourceFile = bundleImportPath.resolvePath(FilePath::fromString(qmlFile));
const bool qmlFileExists = qmlSourceFile.exists();
const QString qmlType = qmlSourceFile.baseName();
m_pendingTypes.append(QStringLiteral("%1.%2")
.arg(QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), qmlType));
if (!qmldirContent.contains(qmlFile)) {
- QSaveFile qmldirSaveFile(qmldirPath.toString());
- if (!qmldirSaveFile.open(QIODeviceBase::WriteOnly | QIODeviceBase::Truncate))
- return QStringLiteral("Failed to open qmldir file for writing: '%1'").arg(qmldirPath.toString());
-
qmldirContent.append(qmlType);
qmldirContent.append(" 1.0 ");
qmldirContent.append(qmlFile);
qmldirContent.append('\n');
-
- qmldirSaveFile.write(qmldirContent.toUtf8());
- qmldirSaveFile.commit();
+ qmldirPath.writeFileContents(qmldirContent.toUtf8());
}
QStringList allFiles;
@@ -145,6 +126,19 @@ QString BundleImporter::importComponent(const QString &qmlFile,
return QStringLiteral("Failed to copy file: '%1'").arg(source.toString());
}
+ QVariantHash assetRefMap = loadAssetRefMap(bundleImportPath);
+ bool writeAssetRefs = false;
+ for (const QString &assetFile : files) {
+ QStringList assets = assetRefMap[assetFile].toStringList();
+ if (!assets.contains(qmlFile)) {
+ assets.append(qmlFile);
+ writeAssetRefs = true;
+ }
+ assetRefMap[assetFile] = assets;
+ }
+ if (writeAssetRefs)
+ writeAssetRefMap(bundleImportPath, assetRefMap);
+
m_fullReset = !qmlFileExists;
auto doc = QmlDesignerPlugin::instance()->currentDesignDocument();
Model *model = doc ? doc->currentModel() : nullptr;
@@ -163,10 +157,12 @@ QString BundleImporter::importComponent(const QString &qmlFile,
}
} else {
// If import is not yet possible, import statement needs to be added asynchronously to
- // avoid errors, as code model update takes a while. Full reset is not necessary
- // in this case, as new import directory appearing will trigger scanning of it.
+ // avoid errors, as code model update takes a while.
m_importAddPending = true;
- m_fullReset = false;
+
+ // Full reset is not necessary if new import directory appearing will trigger scanning,
+ // but if directory existed but was not valid possible import, we need to do a reset.
+ m_fullReset = bundleImportPathExists;
}
}
m_importTimerCount = 0;
@@ -232,4 +228,114 @@ void BundleImporter::handleImportTimer()
}
}
+QVariantHash BundleImporter::loadAssetRefMap(const Utils::FilePath &bundlePath)
+{
+ FilePath assetRefPath = bundlePath.resolvePath(QLatin1String(Constants::COMPONENT_BUNDLES_ASSET_REF_FILE));
+ const std::optional content = assetRefPath.fileContents();
+ if (content) {
+ QJsonParseError error;
+ QJsonDocument bundleDataJsonDoc = QJsonDocument::fromJson(*content, &error);
+ if (bundleDataJsonDoc.isNull()) {
+ // Failure to read asset refs is not considred fatal, so just print error
+ qWarning() << "Failed to parse bundle asset ref file:" << error.errorString();
+ } else {
+ return bundleDataJsonDoc.object().toVariantHash();
+ }
+ }
+ return {};
+}
+
+void BundleImporter::writeAssetRefMap(const Utils::FilePath &bundlePath,
+ const QVariantHash &assetRefMap)
+{
+ FilePath assetRefPath = bundlePath.resolvePath(QLatin1String(Constants::COMPONENT_BUNDLES_ASSET_REF_FILE));
+ QJsonObject jsonObj = QJsonObject::fromVariantHash(assetRefMap);
+ if (!assetRefPath.writeFileContents(QJsonDocument{jsonObj}.toJson())) {
+ // Failure to write asset refs is not considred fatal, so just print error
+ qWarning() << QStringLiteral("Failed to save bundle asset ref file: '%1'").arg(assetRefPath.toString()) ;
+ }
+}
+
+QString BundleImporter::unimportComponent(const QString &qmlFile)
+{
+ FilePath bundleImportPath = resolveBundleImportPath();
+ if (bundleImportPath.isEmpty())
+ return "Failed to resolve bundle import folder";
+
+ if (!bundleImportPath.exists())
+ return {};
+
+ FilePath qmlFilePath = bundleImportPath.resolvePath(qmlFile);
+ if (!qmlFilePath.exists())
+ return {};
+
+ QStringList removedFiles;
+ removedFiles.append(qmlFile);
+
+ FilePath qmldirPath = bundleImportPath.resolvePath(QStringLiteral("qmldir"));
+ const std::optional qmldirContent = qmldirPath.fileContents();
+ QByteArray newContent;
+ if (qmldirContent) {
+ QByteArray qmlType = qmlFilePath.baseName().toUtf8();
+ int typeIndex = qmldirContent->indexOf(qmlType);
+ if (typeIndex != -1) {
+ int newLineIndex = qmldirContent->indexOf('\n', typeIndex);
+ newContent = qmldirContent->left(typeIndex);
+ if (newLineIndex != -1)
+ newContent.append(qmldirContent->mid(newLineIndex + 1));
+ }
+ if (newContent != qmldirContent) {
+ if (!qmldirPath.writeFileContents(newContent))
+ return QStringLiteral("Failed to write qmldir file: '%1'").arg(qmldirPath.toString());
+ }
+ }
+
+ QVariantHash assetRefMap = loadAssetRefMap(bundleImportPath);
+ bool writeAssetRefs = false;
+ const auto keys = assetRefMap.keys();
+ for (const QString &assetFile : keys) {
+ QStringList assets = assetRefMap[assetFile].toStringList();
+ if (assets.contains(qmlFile)) {
+ assets.removeAll(qmlFile);
+ writeAssetRefs = true;
+ }
+ if (!assets.isEmpty()) {
+ assetRefMap[assetFile] = assets;
+ } else {
+ removedFiles.append(assetFile);
+ assetRefMap.remove(assetFile);
+ writeAssetRefs = true;
+ }
+ }
+
+ for (const QString &removedFile : removedFiles) {
+ FilePath removedFilePath = bundleImportPath.resolvePath(removedFile);
+ if (removedFilePath.exists())
+ removedFilePath.removeFile();
+ }
+
+ if (writeAssetRefs)
+ writeAssetRefMap(bundleImportPath, assetRefMap);
+
+ m_fullReset = true;
+ m_importTimerCount = 0;
+ m_importTimer.start();
+
+ return {};
+}
+
+FilePath BundleImporter::resolveBundleImportPath()
+{
+ FilePath bundleImportPath = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath();
+ if (bundleImportPath.isEmpty())
+ return bundleImportPath;
+
+ const QString projectBundlePath = QStringLiteral("%1%2/%3").arg(
+ QLatin1String(Constants::DEFAULT_ASSET_IMPORT_FOLDER),
+ QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER),
+ m_bundleId).mid(1); // Chop leading slash
+
+ return bundleImportPath.resolvePath(projectBundlePath);
+}
+
} // namespace QmlDesigner::Internal
diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.h b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.h
index 840c4c672f5..b22d0edd591 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.h
+++ b/src/plugins/qmldesigner/components/materialbrowser/bundleimporter.h
@@ -30,6 +30,7 @@
#include "nodemetainfo.h"
#include
+#include
QT_BEGIN_NAMESPACE
QT_END_NAMESPACE
@@ -49,6 +50,8 @@ public:
QString importComponent(const QString &qmlFile,
const QStringList &files);
+ QString unimportComponent(const QString &qmlFile);
+
signals:
// The metaInfo parameter will be invalid if an error was encountered during
// asynchronous part of the import. In this case all remaining pending imports have been
@@ -57,6 +60,9 @@ signals:
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;
diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.cpp b/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.cpp
new file mode 100644
index 00000000000..75549945f41
--- /dev/null
+++ b/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.cpp
@@ -0,0 +1,67 @@
+/****************************************************************************
+**
+** 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.
+**
+****************************************************************************/
+
+#include "bundlematerial.h"
+
+namespace QmlDesigner {
+
+BundleMaterial::BundleMaterial(QObject *parent,
+ const QString &name,
+ const QString &qml,
+ const QUrl &icon,
+ const QStringList &files)
+ : QObject(parent), m_name(name), m_qml(qml), m_icon(icon), m_files(files) {}
+
+bool BundleMaterial::filter(const QString &searchText)
+{
+ if (m_visible != m_name.contains(searchText, Qt::CaseInsensitive)) {
+ m_visible = !m_visible;
+ emit materialVisibleChanged();
+ }
+
+ return m_visible;
+}
+
+QUrl BundleMaterial::icon() const
+{
+ return m_icon;
+}
+
+QString BundleMaterial::qml() const
+{
+ return m_qml;
+}
+
+QStringList BundleMaterial::files() const
+{
+ return m_files;
+}
+
+bool BundleMaterial::visible() const
+{
+ return m_visible;
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.h b/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.h
new file mode 100644
index 00000000000..04f4ae26568
--- /dev/null
+++ b/src/plugins/qmldesigner/components/materialbrowser/bundlematerial.h
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** 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.
+**
+****************************************************************************/
+
+#pragma once
+
+#include
+#include
+#include
+
+namespace QmlDesigner {
+
+class BundleMaterial : public QObject
+{
+ Q_OBJECT
+
+ 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)
+
+public:
+ BundleMaterial(QObject *parent,
+ const QString &name,
+ const QString &qml,
+ const QUrl &icon,
+ const QStringList &files);
+
+ bool filter(const QString &searchText);
+
+ QUrl icon() const;
+ QString qml() const;
+ QStringList files() const;
+ bool visible() const;
+
+signals:
+ void materialVisibleChanged();
+
+private:
+ QString m_name;
+ QString m_qml;
+ QUrl m_icon;
+ QStringList m_files;
+
+ bool m_visible = true;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.cpp b/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.cpp
new file mode 100644
index 00000000000..9a18ff75014
--- /dev/null
+++ b/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.cpp
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** 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.
+**
+****************************************************************************/
+
+#include "bundlematerialcategory.h"
+
+#include "bundlematerial.h"
+
+namespace QmlDesigner {
+
+BundleMaterialCategory::BundleMaterialCategory(QObject *parent, const QString &name)
+ : QObject(parent), m_name(name) {}
+
+void BundleMaterialCategory::addBundleMaterial(BundleMaterial *bundleMat)
+{
+ m_categoryMaterials.append(bundleMat);
+}
+
+bool BundleMaterialCategory::filter(const QString &searchText)
+{
+ bool visible = false;
+ for (BundleMaterial *mat : std::as_const(m_categoryMaterials))
+ visible |= mat->filter(searchText);
+
+ if (visible != m_visible) {
+ m_visible = visible;
+ emit categoryVisibleChanged();
+ return true;
+ }
+
+ return false;
+}
+
+QString BundleMaterialCategory::name() const
+{
+ return m_name;
+}
+
+bool BundleMaterialCategory::visible() const
+{
+ return m_visible;
+}
+
+QList BundleMaterialCategory::categoryMaterials() const
+{
+ return m_categoryMaterials;
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.h b/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.h
new file mode 100644
index 00000000000..0a3a0f7c028
--- /dev/null
+++ b/src/plugins/qmldesigner/components/materialbrowser/bundlematerialcategory.h
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** 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.
+**
+****************************************************************************/
+
+#pragma once
+
+#include
+
+namespace QmlDesigner {
+
+class BundleMaterial;
+
+class BundleMaterialCategory : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString categoryName MEMBER m_name CONSTANT)
+ Q_PROPERTY(bool categoryVisible MEMBER m_visible NOTIFY categoryVisibleChanged)
+
+public:
+ BundleMaterialCategory(QObject *parent, const QString &name);
+
+ void addBundleMaterial(BundleMaterial *bundleMat);
+ bool filter(const QString &searchText);
+
+ QString name() const;
+ bool visible() const;
+ QList categoryMaterials() const;
+
+signals:
+ void categoryVisibleChanged();
+
+private:
+ QString m_name;
+ bool m_visible = true;
+
+ QList m_categoryMaterials;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp
new file mode 100644
index 00000000000..1632fc14fab
--- /dev/null
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.cpp
@@ -0,0 +1,229 @@
+/****************************************************************************
+**
+** 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.
+**
+****************************************************************************/
+
+#include "materialbrowserbundlemodel.h"
+
+#include "bundleimporter.h"
+#include "bundlematerial.h"
+#include "bundlematerialcategory.h"
+#include "utils/qtcassert.h"
+
+#include
+#include
+#include
+#include
+
+namespace QmlDesigner {
+
+MaterialBrowserBundleModel::MaterialBrowserBundleModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+ loadMaterialBundle();
+}
+
+int MaterialBrowserBundleModel::rowCount(const QModelIndex &) const
+{
+ return m_bundleCategories.size();
+}
+
+QVariant MaterialBrowserBundleModel::data(const QModelIndex &index, int role) const
+{
+ QTC_ASSERT(index.isValid() && index.row() < m_bundleCategories.count(), return {});
+ QTC_ASSERT(roleNames().contains(role), return {});
+
+ QByteArray roleName = roleNames().value(role);
+ if (roleName == "bundleCategory")
+ return m_bundleCategories.at(index.row())->name();
+
+ if (roleName == "bundleCategoryVisible")
+ return m_bundleCategories.at(index.row())->visible();
+
+ if (roleName == "bundleMaterialsModel")
+ return QVariant::fromValue(m_bundleCategories.at(index.row())->categoryMaterials());
+
+ return {};
+}
+
+bool MaterialBrowserBundleModel::isValidIndex(int idx) const
+{
+ return idx > -1 && idx < rowCount();
+}
+
+QHash MaterialBrowserBundleModel::roleNames() const
+{
+ static const QHash roles {
+ {Qt::UserRole + 1, "bundleCategory"},
+ {Qt::UserRole + 2, "bundleCategoryVisible"},
+ {Qt::UserRole + 3, "bundleMaterialsModel"}
+ };
+ return roles;
+}
+
+void MaterialBrowserBundleModel::loadMaterialBundle()
+{
+ if (m_matBundleExists || m_probeMatBundleDir)
+ return;
+
+ QDir matBundleDir(qEnvironmentVariable("MATERIAL_BUNDLE_PATH"));
+
+ // search for matBundleDir from exec dir and up
+ if (matBundleDir.dirName() == ".") {
+ m_probeMatBundleDir = true; // probe only once
+
+ matBundleDir.setPath(QCoreApplication::applicationDirPath());
+ while (!matBundleDir.cd("material_bundle") && matBundleDir.cdUp())
+ ; // do nothing
+
+ if (matBundleDir.dirName() != "material_bundle") // bundlePathDir not found
+ return;
+ }
+
+ QString matBundlePath = matBundleDir.filePath("material_bundle.json");
+
+ if (m_matBundleObj.isEmpty()) {
+ QFile matPropsFile(matBundlePath);
+
+ if (!matPropsFile.open(QIODevice::ReadOnly)) {
+ qWarning("Couldn't open material_bundle.json");
+ return;
+ }
+
+ QJsonDocument matBundleJsonDoc = QJsonDocument::fromJson(matPropsFile.readAll());
+ if (matBundleJsonDoc.isNull()) {
+ qWarning("Invalid material_bundle.json file");
+ return;
+ } else {
+ m_matBundleObj = matBundleJsonDoc.object();
+ }
+ }
+
+ m_matBundleExists = true;
+
+ const QJsonObject catsObj = m_matBundleObj.value("categories").toObject();
+ const QStringList categories = catsObj.keys();
+ for (const QString &cat : categories) {
+ auto category = new BundleMaterialCategory(this, cat);
+
+ const QJsonObject matsObj = catsObj.value(cat).toObject();
+ const QStringList mats = matsObj.keys();
+ for (const QString &mat : mats) {
+ const QJsonObject matObj = matsObj.value(mat).toObject();
+
+ QStringList files;
+ const QJsonArray assetsArr = matObj.value("files").toArray();
+ for (const QJsonValueRef &asset : assetsArr)
+ files.append(asset.toString());
+
+ auto bundleMat = new BundleMaterial(category, mat, matObj.value("qml").toString(),
+ QUrl::fromLocalFile(matBundleDir.filePath(matObj.value("icon").toString())), files);
+
+ category->addBundleMaterial(bundleMat);
+ }
+ m_bundleCategories.append(category);
+ }
+
+ QStringList sharedFiles;
+ const QJsonArray sharedFilesArr = m_matBundleObj.value("sharedFiles").toArray();
+ for (const QJsonValueRef &file : sharedFilesArr)
+ sharedFiles.append(file.toString());
+
+ m_importer = new Internal::BundleImporter(matBundleDir.path(), "MaterialBundle", sharedFiles);
+ connect(m_importer, &Internal::BundleImporter::importFinished, this, [&](const QmlDesigner::NodeMetaInfo &metaInfo) {
+ if (metaInfo.isValid())
+ emit addBundleMaterialToProjectRequested(metaInfo);
+ });
+}
+
+bool MaterialBrowserBundleModel::hasQuick3DImport() const
+{
+ return m_hasQuick3DImport;
+}
+
+void MaterialBrowserBundleModel::setHasQuick3DImport(bool b)
+{
+ if (b == m_hasQuick3DImport)
+ return;
+
+ m_hasQuick3DImport = b;
+ emit hasQuick3DImportChanged();
+}
+
+bool MaterialBrowserBundleModel::hasMaterialRoot() const
+{
+ return m_hasMaterialRoot;
+}
+
+void MaterialBrowserBundleModel::setHasMaterialRoot(bool b)
+{
+ if (m_hasMaterialRoot == b)
+ return;
+
+ m_hasMaterialRoot = b;
+ emit hasMaterialRootChanged();
+}
+
+void MaterialBrowserBundleModel::setSearchText(const QString &searchText)
+{
+ QString lowerSearchText = searchText.toLower();
+
+ if (m_searchText == lowerSearchText)
+ return;
+
+ m_searchText = lowerSearchText;
+
+ bool anyCatVisible = false;
+ bool catVisibilityChanged = false;
+
+ for (BundleMaterialCategory *cat : std::as_const(m_bundleCategories)) {
+ catVisibilityChanged |= cat->filter(m_searchText);
+ anyCatVisible |= cat->visible();
+ }
+
+ if (anyCatVisible == m_isEmpty) {
+ m_isEmpty = !anyCatVisible;
+ emit isEmptyChanged();
+ }
+
+ if (catVisibilityChanged)
+ resetModel();
+}
+
+void MaterialBrowserBundleModel::resetModel()
+{
+ beginResetModel();
+ endResetModel();
+}
+
+void MaterialBrowserBundleModel::applyToSelected(BundleMaterial *mat, bool add)
+{
+ emit applyToSelectedTriggered(mat, add);
+}
+
+void MaterialBrowserBundleModel::addMaterial(BundleMaterial *mat)
+{
+ m_importer->importComponent(mat->qml(), mat->files());
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h
new file mode 100644
index 00000000000..60b48f1c9b9
--- /dev/null
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserbundlemodel.h
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** 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.
+**
+****************************************************************************/
+
+#pragma once
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+namespace QmlDesigner {
+
+class BundleMaterial;
+class BundleMaterialCategory;
+
+namespace Internal {
+class BundleImporter;
+}
+
+class MaterialBrowserBundleModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+ Q_PROPERTY(bool matBundleExists MEMBER m_matBundleExists CONSTANT)
+ 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)
+
+public:
+ MaterialBrowserBundleModel(QObject *parent = nullptr);
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash roleNames() const override;
+
+ void setSearchText(const QString &searchText);
+
+ bool hasQuick3DImport() const;
+ void setHasQuick3DImport(bool b);
+
+ bool hasMaterialRoot() const;
+ void setHasMaterialRoot(bool b);
+
+ void resetModel();
+
+ Q_INVOKABLE void applyToSelected(QmlDesigner::BundleMaterial *mat, bool add = false);
+ Q_INVOKABLE void addMaterial(QmlDesigner::BundleMaterial *mat);
+
+signals:
+ void isEmptyChanged();
+ void hasQuick3DImportChanged();
+ void hasMaterialRootChanged();
+ void materialVisibleChanged();
+ void applyToSelectedTriggered(QmlDesigner::BundleMaterial *mat, bool add = false);
+ void addBundleMaterialToProjectRequested(const QmlDesigner::NodeMetaInfo &metaInfo);
+
+private:
+ void loadMaterialBundle();
+ bool isValidIndex(int idx) const;
+
+ QString m_searchText;
+ QList m_bundleCategories;
+ QJsonObject m_matBundleObj;
+ Internal::BundleImporter *m_importer = nullptr;
+
+ bool m_isEmpty = true;
+ bool m_hasQuick3DImport = false;
+ bool m_hasMaterialRoot = false;
+ bool m_matBundleExists = false;
+ bool m_probeMatBundleDir = false;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp
index 8c0d5b345da..bbe47ec94c7 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowsermodel.cpp
@@ -213,6 +213,8 @@ void MaterialBrowserModel::setSearchText(const QString &searchText)
isEmpty = !isValidIndex(m_selectedIndex + inc)
&& !isValidIndex(m_selectedIndex - inc);
}
+ if (!isMaterialVisible(m_selectedIndex)) // handles the case of a single material
+ isEmpty = true;
}
if (isEmpty != m_isEmpty) {
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp
index c511e35488e..aaf9391d382 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp
@@ -3,8 +3,10 @@
#include "materialbrowserview.h"
#include "bindingproperty.h"
+#include "bundlematerial.h"
#include "materialbrowsermodel.h"
#include "materialbrowserwidget.h"
+#include "materialbrowserbundlemodel.h"
#include "nodeabstractproperty.h"
#include "nodemetainfo.h"
#include "qmlobjectnode.h"
@@ -14,9 +16,12 @@
#include
#include
#include
+#include
#include
#include
+#include
+#include
namespace QmlDesigner {
@@ -93,6 +98,80 @@ WidgetInfo MaterialBrowserView::widgetInfo()
}
});
});
+
+ connect(m_widget, &MaterialBrowserWidget::bundleMaterialDragStarted, this,
+ [&] (QmlDesigner::BundleMaterial *bundleMat) {
+ m_draggedBundleMaterial = bundleMat;
+ });
+
+ MaterialBrowserBundleModel *matBrowserBundleModel = m_widget->materialBrowserBundleModel().data();
+
+ connect(matBrowserBundleModel, &MaterialBrowserBundleModel::applyToSelectedTriggered, this,
+ [&] (BundleMaterial *material, bool add) {
+ if (!m_selectedModel.isValid())
+ return;
+
+ m_bundleMaterialDropTarget = m_selectedModel;
+ m_bundleMaterialAddToSelected = add;
+ m_widget->materialBrowserBundleModel()->addMaterial(material);
+ });
+
+ connect(matBrowserBundleModel, &MaterialBrowserBundleModel::addBundleMaterialToProjectRequested, this,
+ [&] (const QmlDesigner::NodeMetaInfo &metaInfo) {
+ ModelNode matLib = materialLibraryNode();
+ if (!matLib.isValid())
+ return;
+
+ executeInTransaction("MaterialBrowserView::widgetInfo", [&] {
+ 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();
+ QString newId = model()->generateIdFromName(newName, "material");
+ newMatNode.setIdWithRefactoring(newId);
+
+ VariantProperty objNameProp = newMatNode.variantProperty("objectName");
+ objNameProp.setValue(newName);
+
+ if (m_bundleMaterialDropTarget.isValid()) {
+ QmlObjectNode qmlObjNode(m_bundleMaterialDropTarget);
+ if (m_bundleMaterialAddToSelected) {
+ // 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();
+ };
+ 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_bundleMaterialDropTarget = {};
+ }
+
+ m_bundleMaterialAddToSelected = false;
+ });
+ });
}
return createWidgetInfo(m_widget.data(),
@@ -162,24 +241,24 @@ void MaterialBrowserView::modelAboutToBeDetached(Model *model)
void MaterialBrowserView::selectedNodesChanged(const QList &selectedNodeList,
[[maybe_unused]] const QList &lastSelectedNodeList)
{
- ModelNode selectedModel;
+ m_selectedModel = {};
for (const ModelNode &node : selectedNodeList) {
if (node.metaInfo().isQtQuick3DModel()) {
- selectedModel = node;
+ m_selectedModel = node;
break;
}
}
- m_widget->materialBrowserModel()->setHasModelSelection(selectedModel.isValid());
+ m_widget->materialBrowserModel()->setHasModelSelection(m_selectedModel.isValid());
if (!m_autoSelectModelMaterial)
return;
- if (selectedNodeList.size() > 1 || !selectedModel.isValid())
+ if (selectedNodeList.size() > 1 || !m_selectedModel.isValid())
return;
- QmlObjectNode qmlObjNode(selectedModel);
+ QmlObjectNode qmlObjNode(m_selectedModel);
QString matExp = qmlObjNode.expression("materials");
if (matExp.isEmpty())
return;
@@ -307,6 +386,10 @@ void MaterialBrowserView::customNotification(const AbstractView *view,
});
} else if (identifier == "delete_selected_material") {
m_widget->materialBrowserModel()->deleteSelectedMaterial();
+ } else if (identifier == "drop_bundle_material") {
+ m_bundleMaterialDropTarget = nodeList.first();
+ m_widget->materialBrowserBundleModel()->addMaterial(m_draggedBundleMaterial);
+ m_draggedBundleMaterial = nullptr;
}
}
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h
index 13bd3c36a30..6e5a45e2469 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.h
@@ -9,6 +9,7 @@
namespace QmlDesigner {
+class BundleMaterial;
class MaterialBrowserWidget;
class MaterialBrowserView : public AbstractView
@@ -43,13 +44,17 @@ public:
private:
void refreshModel(bool updateImages);
bool isMaterial(const ModelNode &node) const;
+ void loadPropertyGroups();
QPointer m_widget;
+ ModelNode m_bundleMaterialDropTarget;
+ ModelNode m_selectedModel; // first selected 3D model node
+ BundleMaterial *m_draggedBundleMaterial = nullptr;
+ bool m_bundleMaterialAddToSelected = false;
bool m_hasQuick3DImport = false;
bool m_autoSelectModelMaterial = false; // TODO: wire this to some action
bool m_puppetResetPending = false;
bool m_propertyGroupsLoaded = false;
- void loadPropertyGroups();
};
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp
index fe746926765..3f6f2f39f5e 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.cpp
@@ -2,25 +2,29 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "materialbrowserwidget.h"
+
+#include "bundlematerial.h"
+#include "materialbrowserbundlemodel.h"
#include "materialbrowsermodel.h"
#include "materialbrowserview.h"
-#include
-
#include
#include
#include
#include
#include
-#include
-#include
-#include
+#include
+
+#include
+#include
+#include
-#include
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -91,7 +95,7 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event)
if (m_materialToDrag.isValid()) {
QMouseEvent *me = static_cast(event);
- if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 10) {
+ if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 20) {
QByteArray data;
QMimeData *mimeData = new QMimeData;
QDataStream stream(&data, QIODevice::WriteOnly);
@@ -103,6 +107,17 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event)
QString::number(m_materialToDrag.internalId()), nullptr, {128, 128}));
m_materialToDrag = {};
}
+ } else if (m_bundleMaterialToDrag != nullptr) {
+ QMouseEvent *me = static_cast(event);
+ if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 20) {
+ QMimeData *mimeData = new QMimeData;
+ mimeData->setData(Constants::MIME_TYPE_BUNDLE_MATERIAL, {});
+ mimeData->removeFormat("text/plain");
+
+ model->startDrag(mimeData, m_bundleMaterialToDrag->icon().toLocalFile());
+ emit bundleMaterialDragStarted(m_bundleMaterialToDrag);
+ m_bundleMaterialToDrag = {};
+ }
}
}
@@ -112,6 +127,7 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event)
MaterialBrowserWidget::MaterialBrowserWidget(MaterialBrowserView *view)
: m_materialBrowserView(view)
, m_materialBrowserModel(new MaterialBrowserModel(this))
+ , m_materialBrowserBundleModel(new MaterialBrowserBundleModel(this))
, m_quickWidget(new QQuickWidget(this))
, m_previewImageProvider(new PreviewImageProvider())
{
@@ -130,6 +146,7 @@ MaterialBrowserWidget::MaterialBrowserWidget(MaterialBrowserView *view)
m_quickWidget->rootContext()->setContextProperties({
{"rootView", QVariant::fromValue(this)},
{"materialBrowserModel", QVariant::fromValue(m_materialBrowserModel.data())},
+ {"materialBrowserBundleModel", QVariant::fromValue(m_materialBrowserBundleModel.data())},
});
m_quickWidget->engine()->addImageProvider("materialBrowser", m_previewImageProvider);
@@ -189,6 +206,12 @@ void MaterialBrowserWidget::startDragMaterial(int index, const QPointF &mousePos
m_dragStartPoint = mousePos.toPoint();
}
+void MaterialBrowserWidget::startDragBundleMaterial(QmlDesigner::BundleMaterial *bundleMat, const QPointF &mousePos)
+{
+ m_bundleMaterialToDrag = bundleMat;
+ m_dragStartPoint = mousePos.toPoint();
+}
+
QString MaterialBrowserWidget::qmlSourcesPath()
{
#ifdef SHARE_QML_PATH
@@ -216,6 +239,7 @@ void MaterialBrowserWidget::reloadQmlSource()
void MaterialBrowserWidget::updateSearch()
{
m_materialBrowserModel->setSearchText(m_filterText);
+ m_materialBrowserBundleModel->setSearchText(m_filterText);
m_quickWidget->update();
}
@@ -229,4 +253,10 @@ QPointer MaterialBrowserWidget::materialBrowserModel() con
return m_materialBrowserModel;
}
+QPointer MaterialBrowserWidget::materialBrowserBundleModel() const
+{
+ return m_materialBrowserBundleModel;
+}
+
+
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h
index 212265f002b..1767286b49e 100644
--- a/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h
+++ b/src/plugins/qmldesigner/components/materialbrowser/materialbrowserwidget.h
@@ -11,6 +11,7 @@
#include
#include
+#include
#include
#include
#include
@@ -26,8 +27,10 @@ QT_END_NAMESPACE
namespace QmlDesigner {
+class BundleMaterial;
class MaterialBrowserView;
class MaterialBrowserModel;
+class MaterialBrowserBundleModel;
class PreviewImageProvider;
class MaterialBrowserWidget : public QFrame
@@ -45,13 +48,18 @@ public:
void clearSearchFilter();
QPointer materialBrowserModel() const;
+ QPointer materialBrowserBundleModel() const;
void updateMaterialPreview(const ModelNode &node, const QPixmap &pixmap);
Q_INVOKABLE void handleSearchfilterChanged(const QString &filterText);
Q_INVOKABLE void startDragMaterial(int index, const QPointF &mousePos);
+ Q_INVOKABLE void startDragBundleMaterial(QmlDesigner::BundleMaterial *bundleMat, const QPointF &mousePos);
QQuickWidget *quickWidget() const;
+signals:
+ void bundleMaterialDragStarted(QmlDesigner::BundleMaterial *bundleMat);
+
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
@@ -61,6 +69,7 @@ private:
QPointer m_materialBrowserView;
QPointer m_materialBrowserModel;
+ QPointer m_materialBrowserBundleModel;
QScopedPointer m_quickWidget;
QShortcut *m_qmlSourceUpdateShortcut = nullptr;
@@ -70,6 +79,7 @@ private:
QString m_filterText;
ModelNode m_materialToDrag;
+ BundleMaterial *m_bundleMaterialToDrag = nullptr;
QPoint m_dragStartPoint;
};
diff --git a/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp
index 9dd87e54c99..acc6744e2ce 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/dynamicpropertiesproxymodel.cpp
@@ -35,6 +35,7 @@
#include
#include
+#include
#include
#include
@@ -150,6 +151,12 @@ void DynamicPropertiesProxyModel::createProperty(const QString &name, const QStr
if (selectedNodes.count() == 1) {
const ModelNode modelNode = selectedNodes.constFirst();
if (modelNode.isValid()) {
+ if (modelNode.hasProperty(name.toUtf8())) {
+ Core::AsynchronousMessageBox::warning(tr("Property already exists"),
+ tr("Property '%1' already exists")
+ .arg(name));
+ return;
+ }
try {
if (Internal::DynamicPropertiesModel::isValueType(typeName)) {
QVariant value = Internal::DynamicPropertiesModel::defaultValueForType(typeName);
diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp
index 188e4f7ed7d..f56c3391c38 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp
@@ -437,6 +437,9 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q
}
);
+ contextObject()->setHasMultiSelection(
+ !qmlObjectNode.view()->singleSelectedModelNode().isValid());
+
qCInfo(propertyEditorBenchmark) << "anchors:" << time.elapsed();
qCInfo(propertyEditorBenchmark) << "context:" << time.elapsed();
diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp
index 0f0d391b816..95cab9b15ab 100644
--- a/src/plugins/qmldesigner/designercore/model/model.cpp
+++ b/src/plugins/qmldesigner/designercore/model/model.cpp
@@ -1557,6 +1557,10 @@ QString Model::generateIdFromName(const QString &name, const QString &fallbackId
newId.prepend('_');
}
+ // If the new id is not valid (e.g. qml keyword match), try fixing it by prepending underscore
+ if (!ModelNode::isValidId(newId))
+ newId.prepend("_");
+
QRegularExpression rgx("\\d+$"); // matches a number at the end of a string
while (hasId(newId)) { // id exists
QRegularExpressionMatch match = rgx.match(newId);
diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h
index 0315601379b..ee9870386bf 100644
--- a/src/plugins/qmldesigner/qmldesignerconstants.h
+++ b/src/plugins/qmldesigner/qmldesignerconstants.h
@@ -62,6 +62,7 @@ const char EDIT3D_BACKGROUND_COLOR_ACTIONS[] = "QmlDesigner.Editor3D.BackgroundC
const char QML_DESIGNER_SUBFOLDER[] = "/designer/";
const char COMPONENT_BUNDLES_FOLDER[] = "/ComponentBundles";
+const char COMPONENT_BUNDLES_ASSET_REF_FILE[] = "_asset_ref.json";
const char QUICK_3D_ASSETS_FOLDER[] = "/Quick3DAssets";
const char QUICK_3D_ASSET_LIBRARY_ICON_SUFFIX[] = "_libicon";
const char QUICK_3D_ASSET_ICON_DIR[] = "_icons";
@@ -74,6 +75,7 @@ const char MATERIAL_LIB_ID[] = "__materialLibrary__";
const char MIME_TYPE_ITEM_LIBRARY_INFO[] = "application/vnd.qtdesignstudio.itemlibraryinfo";
const char MIME_TYPE_ASSETS[] = "application/vnd.qtdesignstudio.assets";
const char MIME_TYPE_MATERIAL[] = "application/vnd.qtdesignstudio.material";
+const char MIME_TYPE_BUNDLE_MATERIAL[] = "application/vnd.qtdesignstudio.bundlematerial";
const char MIME_TYPE_ASSET_IMAGE[] = "application/vnd.qtdesignstudio.asset.image";
const char MIME_TYPE_ASSET_FONT[] = "application/vnd.qtdesignstudio.asset.font";
const char MIME_TYPE_ASSET_SHADER[] = "application/vnd.qtdesignstudio.asset.shader";
diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs
index fbb4a05f073..c6989823f6e 100644
--- a/src/plugins/qmldesigner/qmldesignerplugin.qbs
+++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs
@@ -731,6 +731,12 @@ Project {
"itemlibrary/itemlibraryiconimageprovider.h",
"materialbrowser/materialbrowsermodel.cpp",
"materialbrowser/materialbrowsermodel.h",
+ "materialbrowser/materialbrowserbundlemodel.cpp",
+ "materialbrowser/materialbrowserbundlemodel.h",
+ "materialbrowser/bundlematerial.cpp",
+ "materialbrowser/bundlematerial.h",
+ "materialbrowser/bundlematerialcategory.cpp",
+ "materialbrowser/bundlematerialcategory.h",
"materialbrowser/materialbrowserview.cpp",
"materialbrowser/materialbrowserview.h",
"materialbrowser/materialbrowserwidget.cpp",
diff --git a/src/plugins/remotelinux/linuxdevice.cpp b/src/plugins/remotelinux/linuxdevice.cpp
index 72efd9cbc61..88be06dd539 100644
--- a/src/plugins/remotelinux/linuxdevice.cpp
+++ b/src/plugins/remotelinux/linuxdevice.cpp
@@ -802,7 +802,9 @@ public:
cmd.addArg("/bin/sh");
m_shell.reset(new LinuxDeviceShell(cmd, FilePath::fromString(QString("ssh://%1/").arg(parameters.userAtHost()))));
- connect(m_shell.get(), &DeviceShell::done, this, [this] { m_shell.reset(); });
+ connect(m_shell.get(), &DeviceShell::done, this, [this] {
+ m_shell.release()->deleteLater();
+ });
return m_shell->start();
}