Merge remote-tracking branch 'origin/8.0'

Conflicts:
	src/libs/utils/deviceshell.cpp
	src/plugins/clangcodemodel/clangdclient.cpp
	src/plugins/qmldesigner/components/materialbrowser/materialbrowserview.cpp
	src/plugins/remotelinux/linuxdevice.cpp

Change-Id: Idc2edf428c98e9cbc891f1833b271ce2def9a835
This commit is contained in:
Eike Ziller
2022-09-21 10:59:59 +02:00
34 changed files with 1283 additions and 141 deletions

View File

@@ -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
}
}
}

View File

@@ -16,13 +16,15 @@ Item {
property var currentMaterial: null property var currentMaterial: null
property int currentMaterialIdx: 0 property int currentMaterialIdx: 0
property var currentBundleMaterial: null
property var matSectionsModel: [] property var matSectionsModel: []
// Called also from C++ to close context menu on focus out // Called also from C++ to close context menu on focus out
function closeContextMenu() function closeContextMenu()
{ {
contextMenu.close() cxtMenu.close()
cxtMenuBundle.close()
} }
// Called from C++ to refresh a preview material after it changes // Called from C++ to refresh a preview material after it changes
@@ -46,10 +48,15 @@ Item {
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
onClicked: { onClicked: (mouse) => {
if (!materialBrowserModel.hasMaterialRoot) { // 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 root.currentMaterial = null
contextMenu.popup() cxtMenu.popup()
} }
} }
} }
@@ -68,7 +75,7 @@ Item {
} }
StudioControls.Menu { StudioControls.Menu {
id: contextMenu id: cxtMenu
closePolicy: StudioControls.Menu.CloseOnEscape | StudioControls.Menu.CloseOnPressOutside 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 { Column {
id: col id: col
y: 5 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 {
text: { text: {
if (materialBrowserModel.hasMaterialRoot) if (materialBrowserModel.hasMaterialRoot)
qsTr("<b>Material Browser</b> is disabled inside a material component.") qsTr("<b>Material Browser</b> is disabled inside a material component.")
else if (!materialBrowserModel.hasQuick3DImport) else if (!materialBrowserModel.hasQuick3DImport)
qsTr("To use <b>Material Browser</b>, first add the QtQuick3D module in the <b>Components</b> view.") qsTr("To use <b>Material Browser</b>, first add the QtQuick3D module in the <b>Components</b> view.")
else if (materialBrowserModel.isEmpty && searchBox.isEmpty())
qsTr("There are no materials in this project.<br>Select '<b>+</b>' to create one.")
else else
"" ""
} }
@@ -230,31 +252,115 @@ Item {
width: root.width width: root.width
height: root.height - searchBox.height height: root.height - searchBox.height
clip: true clip: true
visible: materialBrowserModel.hasQuick3DImport && !materialBrowserModel.hasMaterialRoot
Grid { Column {
id: grid Section {
id: userMaterialsSection
width: scrollView.width width: root.width
leftPadding: 5 caption: qsTr("User materials")
rightPadding: 5 hideHeader: !materialBrowserBundleModel.matBundleExists
bottomPadding: 5
columns: root.width / root.cellWidth
Repeater { Grid {
id: gridRepeater id: grid
model: materialBrowserModel width: scrollView.width
delegate: MaterialItem { leftPadding: 5
width: root.cellWidth rightPadding: 5
height: root.cellHeight bottomPadding: 5
columns: root.width / root.cellWidth
onShowContextMenu: { Repeater {
if (searchBox.isEmpty()) { id: gridRepeater
root.currentMaterial = model
contextMenu.popup() 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.<br>Select '<b>+</b>' 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
}
}
} }
} }
} }

View File

@@ -3,40 +3,38 @@ PropertyLabel {
tooltip: "%1" tooltip: "%1"
} }
ColumnLayout { SecondColumnLayout {
SecondColumnLayout { SpinBox {
SpinBox { minimumValue: -9999999
minimumValue: -9999999 maximumValue: 9999999
maximumValue: 9999999 decimals: 2
decimals: 2 backendValue: backendValues.%2_x
backendValue: backendValues.%2_x implicitWidth: StudioTheme.Values.twoControlColumnWidth
implicitWidth: StudioTheme.Values.twoControlColumnWidth + StudioTheme.Values.actionIndicatorWidth
+ 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 {}
} }
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 {}
} }

View File

@@ -4,6 +4,7 @@ PropertyLabel {
} }
ColumnLayout { ColumnLayout {
spacing: StudioTheme.Values.sectionRowSpacing / 2
SecondColumnLayout { SecondColumnLayout {
SpinBox { SpinBox {
minimumValue: -9999999 minimumValue: -9999999

View File

@@ -4,6 +4,7 @@ PropertyLabel {
} }
ColumnLayout { ColumnLayout {
spacing: StudioTheme.Values.sectionRowSpacing / 2
SecondColumnLayout { SecondColumnLayout {
SpinBox { SpinBox {
minimumValue: -9999999 minimumValue: -9999999

View File

@@ -297,7 +297,7 @@ Section {
onVecSizeChanged: updateProxyValues() onVecSizeChanged: updateProxyValues()
spacing: StudioTheme.Values.sectionRowSpacing spacing: StudioTheme.Values.sectionRowSpacing / 2
function isValidValue(v) { function isValidValue(v) {
return !(v === undefined || isNaN(v)) return !(v === undefined || isNaN(v))

View File

@@ -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
}
}

View File

@@ -45,5 +45,6 @@ TabBar 1.0 TabBar.qml
TabButton 1.0 TabButton.qml TabButton 1.0 TabButton.qml
TextArea 1.0 TextArea.qml TextArea 1.0 TextArea.qml
TextField 1.0 TextField.qml TextField 1.0 TextField.qml
ToolTip 1.0 ToolTip.qml
TranslationIndicator 1.0 TranslationIndicator.qml TranslationIndicator 1.0 TranslationIndicator.qml
VerticalScrollBar 1.0 VerticalScrollBar.qml VerticalScrollBar 1.0 VerticalScrollBar.qml

View File

@@ -159,12 +159,12 @@ DeviceShell::DeviceShell()
DeviceShell::~DeviceShell() DeviceShell::~DeviceShell()
{ {
m_shellProcess->deleteLater();
if (m_thread.isRunning()) { if (m_thread.isRunning()) {
m_thread.quit(); m_thread.quit();
m_thread.wait(); 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 int id = ++m_currentId;
const auto it = m_commandOutput.insert(id, CommandRun{{-1, {}, {}}, &waiter}); 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") const QString command = QString("%1 \"%2\" %3\n")
.arg(id) .arg(id)
.arg(QString::fromLatin1(stdInData.toBase64())) .arg(QString::fromLatin1(stdInData.toBase64()))
@@ -304,13 +304,12 @@ void DeviceShell::startupFailed(const CommandLine &cmdLine)
*/ */
bool DeviceShell::start() bool DeviceShell::start()
{ {
m_shellProcess = new QtcProcess(); m_shellProcess = std::make_unique<QtcProcess>();
connect(m_shellProcess, &QtcProcess::done, m_shellProcess, connect(m_shellProcess.get(), &QtcProcess::done, m_shellProcess.get(),
[this] { emit done(m_shellProcess->resultData()); }); [this] { emit done(m_shellProcess->resultData()); });
connect(m_shellProcess, &QObject::destroyed, this, [this] { m_shellProcess = nullptr; }); connect(&m_thread, &QThread::finished, m_shellProcess.get(), [this] { closeShellProcess(); }, Qt::DirectConnection);
connect(&m_thread, &QThread::finished, m_shellProcess, [this] { closeShellProcess(); });
setupShellProcess(m_shellProcess); setupShellProcess(m_shellProcess.get());
m_shellProcess->setProcessMode(ProcessMode::Writer); m_shellProcess->setProcessMode(ProcessMode::Writer);
@@ -319,7 +318,7 @@ bool DeviceShell::start()
bool result = false; bool result = false;
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
m_shellProcess, m_shellProcess.get(),
[this] { [this] {
qCDebug(deviceShellLog) << "Starting shell process:" << m_shellProcess->commandLine().toUserOutput(); qCDebug(deviceShellLog) << "Starting shell process:" << m_shellProcess->commandLine().toUserOutput();
m_shellProcess->start(); m_shellProcess->start();
@@ -333,16 +332,21 @@ bool DeviceShell::start()
if (m_shellScriptState == State::FailedToStart) if (m_shellScriptState == State::FailedToStart)
closeShellProcess(); closeShellProcess();
} else { } else {
connect(m_shellProcess, &QtcProcess::readyReadStandardOutput, m_shellProcess, [this] { connect(m_shellProcess.get(),
onReadyRead(); &QtcProcess::readyReadStandardOutput,
}); m_shellProcess.get(),
connect(m_shellProcess, &QtcProcess::readyReadStandardError, m_shellProcess, [this] { [this] { onReadyRead(); });
const QByteArray stdErr = m_shellProcess->readAllStandardError(); connect(m_shellProcess.get(),
qCWarning(deviceShellLog) << "Received unexpected output on stderr:" << stdErr; &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 if (m_shellProcess->resultData().m_exitCode != EXIT_SUCCESS
|| m_shellProcess->resultData().m_exitStatus != QProcess::NormalExit) { || m_shellProcess->resultData().m_exitStatus != QProcess::NormalExit) {
qCWarning(deviceShellLog) << "Shell exited with error code:" qCWarning(deviceShellLog) << "Shell exited with error code:"
@@ -428,6 +432,7 @@ void DeviceShell::closeShellProcess()
if (!m_shellProcess->waitForFinished(2000)) if (!m_shellProcess->waitForFinished(2000))
m_shellProcess->terminate(); m_shellProcess->terminate();
} }
m_shellProcess.reset();
} }
} }

View File

@@ -79,7 +79,7 @@ private:
QWaitCondition *waiter; QWaitCondition *waiter;
}; };
QtcProcess *m_shellProcess = nullptr; std::unique_ptr<QtcProcess> m_shellProcess;
QThread m_thread; QThread m_thread;
int m_currentId{0}; int m_currentId{0};

View File

@@ -479,7 +479,7 @@ void DockerDevicePrivate::startContainer()
return; return;
qCWarning(dockerDeviceLog) << "Container shell encountered error:" << resultData.m_error; qCWarning(dockerDeviceLog) << "Container shell encountered error:" << resultData.m_error;
m_shell.reset(); m_shell.release()->deleteLater();
DockerApi::recheckDockerDaemon(); DockerApi::recheckDockerDaemon();
MessageManager::writeFlashing(Tr::tr("Docker daemon appears to be not running. " MessageManager::writeFlashing(Tr::tr("Docker daemon appears to be not running. "
@@ -501,7 +501,7 @@ void DockerDevicePrivate::updateContainerAccess()
if (m_shell) if (m_shell)
return; return;
startContainer(); startContainer();
} }
void DockerDevice::setMounts(const QStringList &mounts) const void DockerDevice::setMounts(const QStringList &mounts) const

View File

@@ -408,6 +408,9 @@ extend_qtc_plugin(QmlDesigner
materialbrowserwidget.cpp materialbrowserwidget.h materialbrowserwidget.cpp materialbrowserwidget.h
materialbrowsermodel.cpp materialbrowsermodel.h materialbrowsermodel.cpp materialbrowsermodel.h
bundleimporter.cpp bundleimporter.h bundleimporter.cpp bundleimporter.h
materialbrowserbundlemodel.cpp materialbrowserbundlemodel.h
bundlematerial.cpp bundlematerial.h
bundlematerialcategory.cpp bundlematerialcategory.h
) )
extend_qtc_plugin(QmlDesigner extend_qtc_plugin(QmlDesigner

View File

@@ -1,15 +1,16 @@
// Copyright (C) 2020 The Qt Company Ltd. // 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 // 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 "edit3dactions.h"
#include "edit3dcanvas.h" #include "edit3dcanvas.h"
#include "edit3dview.h"
#include "edit3dwidget.h"
#include "edit3dviewconfig.h" #include "edit3dviewconfig.h"
#include "backgroundcolorselection.h" #include "edit3dwidget.h"
#include "metainfo.h" #include "metainfo.h"
#include "seekerslider.h"
#include "nodehints.h" #include "nodehints.h"
#include "seekerslider.h"
#include "view3dactioncommand.h"
#include <auxiliarydataproperties.h> #include <auxiliarydataproperties.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
@@ -287,6 +288,8 @@ void Edit3DView::nodeAtPosReady(const ModelNode &modelNode, const QVector3D &pos
assignMaterialTo3dModel(modelNode, m_droppedMaterial); assignMaterialTo3dModel(modelNode, m_droppedMaterial);
}); });
} }
} else if (m_nodeAtPosReqType == NodeAtPosReqType::BundleMaterialDrop) {
emitCustomNotification("drop_bundle_material", {modelNode}); // To MaterialBrowserView
} }
m_nodeAtPosReqType = NodeAtPosReqType::None; m_nodeAtPosReqType = NodeAtPosReqType::None;
} }
@@ -837,4 +840,11 @@ void Edit3DView::dropMaterial(const ModelNode &matNode, const QPointF &pos)
emitView3DAction(View3DActionType::GetNodeAtPos, 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 } // namespace QmlDesigner

View File

@@ -58,12 +58,14 @@ public:
void addQuick3DImport(); void addQuick3DImport();
void startContextMenu(const QPoint &pos); void startContextMenu(const QPoint &pos);
void dropMaterial(const ModelNode &matNode, const QPointF &pos); void dropMaterial(const ModelNode &matNode, const QPointF &pos);
void dropBundleMaterial(const QPointF &pos);
private slots: private slots:
void onEntriesChanged(); void onEntriesChanged();
private: private:
enum class NodeAtPosReqType { enum class NodeAtPosReqType {
BundleMaterialDrop,
MaterialDrop, MaterialDrop,
ContextMenu, ContextMenu,
None None

View File

@@ -309,7 +309,8 @@ void Edit3DWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent)
const DesignerActionManager &actionManager = QmlDesignerPlugin::instance() const DesignerActionManager &actionManager = QmlDesignerPlugin::instance()
->viewManager().designerActionManager(); ->viewManager().designerActionManager();
if (actionManager.externalDragHasSupportedAssets(dragEnterEvent->mimeData()) 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(); dragEnterEvent->acceptProposedAction();
} }
} }
@@ -329,6 +330,12 @@ void Edit3DWidget::dropEvent(QDropEvent *dropEvent)
return; return;
} }
// handle dropping bundle materials
if (dropEvent->mimeData()->hasFormat(Constants::MIME_TYPE_BUNDLE_MATERIAL)) {
m_view->dropBundleMaterial(dropEvent->position());
return;
}
// handle dropping external assets // handle dropping external assets
const DesignerActionManager &actionManager = QmlDesignerPlugin::instance() const DesignerActionManager &actionManager = QmlDesignerPlugin::instance()
->viewManager().designerActionManager(); ->viewManager().designerActionManager();

View File

@@ -33,9 +33,9 @@
#include <qmljs/qmljsmodelmanagerinterface.h> #include <qmljs/qmljsmodelmanagerinterface.h>
#include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QSaveFile> #include <QJsonDocument>
#include <QJsonObject>
#include <QStringList> #include <QStringList>
using namespace Utils; using namespace Utils;
@@ -65,20 +65,14 @@ BundleImporter::BundleImporter(const QString &bundleDir,
QString BundleImporter::importComponent(const QString &qmlFile, QString BundleImporter::importComponent(const QString &qmlFile,
const QStringList &files) const QStringList &files)
{ {
FilePath bundleImportPath = QmlDesignerPlugin::instance()->documentManager().currentProjectDirPath(); FilePath bundleImportPath = resolveBundleImportPath();
if (bundleImportPath.isEmpty()) 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( bool bundleImportPathExists = bundleImportPath.exists();
QLatin1String(Constants::DEFAULT_ASSET_IMPORT_FOLDER),
QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER),
m_bundleId).mid(1); // Chop leading slash
bundleImportPath = bundleImportPath.resolvePath(projectBundlePath);
if (!bundleImportPath.exists()) { if (!bundleImportPathExists && !bundleImportPath.createDir())
if (!bundleImportPath.createDir()) return QStringLiteral("Failed to create bundle import folder: '%1'").arg(bundleImportPath.toString());
return QStringLiteral("Failed to create bundle import folder: '%1'").arg(bundleImportPath.toString());
}
for (const QString &file : qAsConst(m_sharedFiles)) { for (const QString &file : qAsConst(m_sharedFiles)) {
FilePath target = bundleImportPath.resolvePath(file); FilePath target = bundleImportPath.resolvePath(file);
@@ -93,37 +87,24 @@ QString BundleImporter::importComponent(const QString &qmlFile,
} }
FilePath qmldirPath = bundleImportPath.resolvePath(QStringLiteral("qmldir")); FilePath qmldirPath = bundleImportPath.resolvePath(QStringLiteral("qmldir"));
QFile qmldirFile(qmldirPath.toString()); QString qmldirContent = QString::fromUtf8(qmldirPath.fileContents().value_or(QByteArray()));
if (qmldirContent.isEmpty()) {
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 {
qmldirContent.append("module "); qmldirContent.append("module ");
qmldirContent.append(m_moduleName); qmldirContent.append(m_moduleName);
qmldirContent.append('\n'); qmldirContent.append('\n');
} }
FilePath qmlSourceFile = FilePath::fromString(qmlFile); FilePath qmlSourceFile = bundleImportPath.resolvePath(FilePath::fromString(qmlFile));
const bool qmlFileExists = qmlSourceFile.exists(); const bool qmlFileExists = qmlSourceFile.exists();
const QString qmlType = qmlSourceFile.baseName(); const QString qmlType = qmlSourceFile.baseName();
m_pendingTypes.append(QStringLiteral("%1.%2") m_pendingTypes.append(QStringLiteral("%1.%2")
.arg(QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), qmlType)); .arg(QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1), qmlType));
if (!qmldirContent.contains(qmlFile)) { 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(qmlType);
qmldirContent.append(" 1.0 "); qmldirContent.append(" 1.0 ");
qmldirContent.append(qmlFile); qmldirContent.append(qmlFile);
qmldirContent.append('\n'); qmldirContent.append('\n');
qmldirPath.writeFileContents(qmldirContent.toUtf8());
qmldirSaveFile.write(qmldirContent.toUtf8());
qmldirSaveFile.commit();
} }
QStringList allFiles; QStringList allFiles;
@@ -145,6 +126,19 @@ QString BundleImporter::importComponent(const QString &qmlFile,
return QStringLiteral("Failed to copy file: '%1'").arg(source.toString()); 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; m_fullReset = !qmlFileExists;
auto doc = QmlDesignerPlugin::instance()->currentDesignDocument(); auto doc = QmlDesignerPlugin::instance()->currentDesignDocument();
Model *model = doc ? doc->currentModel() : nullptr; Model *model = doc ? doc->currentModel() : nullptr;
@@ -163,10 +157,12 @@ QString BundleImporter::importComponent(const QString &qmlFile,
} }
} else { } else {
// If import is not yet possible, import statement needs to be added asynchronously to // 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 // avoid errors, as code model update takes a while.
// in this case, as new import directory appearing will trigger scanning of it.
m_importAddPending = true; 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; 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<QByteArray> 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<QByteArray> 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 } // namespace QmlDesigner::Internal

View File

@@ -30,6 +30,7 @@
#include "nodemetainfo.h" #include "nodemetainfo.h"
#include <QTimer> #include <QTimer>
#include <QVariantHash>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
QT_END_NAMESPACE QT_END_NAMESPACE
@@ -49,6 +50,8 @@ public:
QString importComponent(const QString &qmlFile, QString importComponent(const QString &qmlFile,
const QStringList &files); const QStringList &files);
QString unimportComponent(const QString &qmlFile);
signals: signals:
// The metaInfo parameter will be invalid if an error was encountered during // 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 // asynchronous part of the import. In this case all remaining pending imports have been
@@ -57,6 +60,9 @@ signals:
private: private:
void handleImportTimer(); void handleImportTimer();
QVariantHash loadAssetRefMap(const Utils::FilePath &bundlePath);
void writeAssetRefMap(const Utils::FilePath &bundlePath, const QVariantHash &assetRefMap);
Utils::FilePath resolveBundleImportPath();
Utils::FilePath m_bundleDir; Utils::FilePath m_bundleDir;
QString m_bundleId; QString m_bundleId;

View File

@@ -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

View File

@@ -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 <QDataStream>
#include <QObject>
#include <QUrl>
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

View File

@@ -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<BundleMaterial *> BundleMaterialCategory::categoryMaterials() const
{
return m_categoryMaterials;
}
} // namespace QmlDesigner

View File

@@ -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 <QObject>
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<BundleMaterial *> categoryMaterials() const;
signals:
void categoryVisibleChanged();
private:
QString m_name;
bool m_visible = true;
QList<BundleMaterial *> m_categoryMaterials;
};
} // namespace QmlDesigner

View File

@@ -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 <QCoreApplication>
#include <QJsonArray>
#include <QJsonDocument>
#include <QUrl>
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<int, QByteArray> MaterialBrowserBundleModel::roleNames() const
{
static const QHash<int, QByteArray> 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

View File

@@ -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 <modelnode.h>
#include <qmlobjectnode.h>
#include <QAbstractListModel>
#include <QJsonObject>
#include <QObject>
#include <QPointer>
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<int, QByteArray> 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<BundleMaterialCategory *> 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

View File

@@ -213,6 +213,8 @@ void MaterialBrowserModel::setSearchText(const QString &searchText)
isEmpty = !isValidIndex(m_selectedIndex + inc) isEmpty = !isValidIndex(m_selectedIndex + inc)
&& !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) { if (isEmpty != m_isEmpty) {

View File

@@ -3,8 +3,10 @@
#include "materialbrowserview.h" #include "materialbrowserview.h"
#include "bindingproperty.h" #include "bindingproperty.h"
#include "bundlematerial.h"
#include "materialbrowsermodel.h" #include "materialbrowsermodel.h"
#include "materialbrowserwidget.h" #include "materialbrowserwidget.h"
#include "materialbrowserbundlemodel.h"
#include "nodeabstractproperty.h" #include "nodeabstractproperty.h"
#include "nodemetainfo.h" #include "nodemetainfo.h"
#include "qmlobjectnode.h" #include "qmlobjectnode.h"
@@ -14,9 +16,12 @@
#include <designmodecontext.h> #include <designmodecontext.h>
#include <nodeinstanceview.h> #include <nodeinstanceview.h>
#include <nodemetainfo.h> #include <nodemetainfo.h>
#include <nodelistproperty.h>
#include <qmldesignerconstants.h> #include <qmldesignerconstants.h>
#include <QQuickItem> #include <QQuickItem>
#include <QRegularExpression>
#include <QTimer>
namespace QmlDesigner { 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(), return createWidgetInfo(m_widget.data(),
@@ -162,24 +241,24 @@ void MaterialBrowserView::modelAboutToBeDetached(Model *model)
void MaterialBrowserView::selectedNodesChanged(const QList<ModelNode> &selectedNodeList, void MaterialBrowserView::selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
[[maybe_unused]] const QList<ModelNode> &lastSelectedNodeList) [[maybe_unused]] const QList<ModelNode> &lastSelectedNodeList)
{ {
ModelNode selectedModel; m_selectedModel = {};
for (const ModelNode &node : selectedNodeList) { for (const ModelNode &node : selectedNodeList) {
if (node.metaInfo().isQtQuick3DModel()) { if (node.metaInfo().isQtQuick3DModel()) {
selectedModel = node; m_selectedModel = node;
break; break;
} }
} }
m_widget->materialBrowserModel()->setHasModelSelection(selectedModel.isValid()); m_widget->materialBrowserModel()->setHasModelSelection(m_selectedModel.isValid());
if (!m_autoSelectModelMaterial) if (!m_autoSelectModelMaterial)
return; return;
if (selectedNodeList.size() > 1 || !selectedModel.isValid()) if (selectedNodeList.size() > 1 || !m_selectedModel.isValid())
return; return;
QmlObjectNode qmlObjNode(selectedModel); QmlObjectNode qmlObjNode(m_selectedModel);
QString matExp = qmlObjNode.expression("materials"); QString matExp = qmlObjNode.expression("materials");
if (matExp.isEmpty()) if (matExp.isEmpty())
return; return;
@@ -307,6 +386,10 @@ void MaterialBrowserView::customNotification(const AbstractView *view,
}); });
} else if (identifier == "delete_selected_material") { } else if (identifier == "delete_selected_material") {
m_widget->materialBrowserModel()->deleteSelectedMaterial(); m_widget->materialBrowserModel()->deleteSelectedMaterial();
} else if (identifier == "drop_bundle_material") {
m_bundleMaterialDropTarget = nodeList.first();
m_widget->materialBrowserBundleModel()->addMaterial(m_draggedBundleMaterial);
m_draggedBundleMaterial = nullptr;
} }
} }

View File

@@ -9,6 +9,7 @@
namespace QmlDesigner { namespace QmlDesigner {
class BundleMaterial;
class MaterialBrowserWidget; class MaterialBrowserWidget;
class MaterialBrowserView : public AbstractView class MaterialBrowserView : public AbstractView
@@ -43,13 +44,17 @@ public:
private: private:
void refreshModel(bool updateImages); void refreshModel(bool updateImages);
bool isMaterial(const ModelNode &node) const; bool isMaterial(const ModelNode &node) const;
void loadPropertyGroups();
QPointer<MaterialBrowserWidget> m_widget; QPointer<MaterialBrowserWidget> 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_hasQuick3DImport = false;
bool m_autoSelectModelMaterial = false; // TODO: wire this to some action bool m_autoSelectModelMaterial = false; // TODO: wire this to some action
bool m_puppetResetPending = false; bool m_puppetResetPending = false;
bool m_propertyGroupsLoaded = false; bool m_propertyGroupsLoaded = false;
void loadPropertyGroups();
}; };
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -2,25 +2,29 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "materialbrowserwidget.h" #include "materialbrowserwidget.h"
#include "bundlematerial.h"
#include "materialbrowserbundlemodel.h"
#include "materialbrowsermodel.h" #include "materialbrowsermodel.h"
#include "materialbrowserview.h" #include "materialbrowserview.h"
#include <theme.h>
#include <designeractionmanager.h> #include <designeractionmanager.h>
#include <designermcumanager.h> #include <designermcumanager.h>
#include <documentmanager.h> #include <documentmanager.h>
#include <qmldesignerconstants.h> #include <qmldesignerconstants.h>
#include <qmldesignerplugin.h> #include <qmldesignerplugin.h>
#include <utils/algorithm.h> #include <theme.h>
#include <utils/stylehelper.h>
#include <utils/qtcassert.h> #include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <utils/stylehelper.h>
#include <QImageReader>
#include <QMenu> #include <QMenu>
#include <QMimeData> #include <QMimeData>
#include <QMouseEvent> #include <QMouseEvent>
#include <QJsonArray>
#include <QJsonDocument>
#include <QQmlContext> #include <QQmlContext>
#include <QQmlEngine> #include <QQmlEngine>
#include <QQuickImageProvider> #include <QQuickImageProvider>
@@ -91,7 +95,7 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event)
if (m_materialToDrag.isValid()) { if (m_materialToDrag.isValid()) {
QMouseEvent *me = static_cast<QMouseEvent *>(event); QMouseEvent *me = static_cast<QMouseEvent *>(event);
if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 10) { if ((me->globalPos() - m_dragStartPoint).manhattanLength() > 20) {
QByteArray data; QByteArray data;
QMimeData *mimeData = new QMimeData; QMimeData *mimeData = new QMimeData;
QDataStream stream(&data, QIODevice::WriteOnly); QDataStream stream(&data, QIODevice::WriteOnly);
@@ -103,6 +107,17 @@ bool MaterialBrowserWidget::eventFilter(QObject *obj, QEvent *event)
QString::number(m_materialToDrag.internalId()), nullptr, {128, 128})); QString::number(m_materialToDrag.internalId()), nullptr, {128, 128}));
m_materialToDrag = {}; m_materialToDrag = {};
} }
} else if (m_bundleMaterialToDrag != nullptr) {
QMouseEvent *me = static_cast<QMouseEvent *>(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) MaterialBrowserWidget::MaterialBrowserWidget(MaterialBrowserView *view)
: m_materialBrowserView(view) : m_materialBrowserView(view)
, m_materialBrowserModel(new MaterialBrowserModel(this)) , m_materialBrowserModel(new MaterialBrowserModel(this))
, m_materialBrowserBundleModel(new MaterialBrowserBundleModel(this))
, m_quickWidget(new QQuickWidget(this)) , m_quickWidget(new QQuickWidget(this))
, m_previewImageProvider(new PreviewImageProvider()) , m_previewImageProvider(new PreviewImageProvider())
{ {
@@ -130,6 +146,7 @@ MaterialBrowserWidget::MaterialBrowserWidget(MaterialBrowserView *view)
m_quickWidget->rootContext()->setContextProperties({ m_quickWidget->rootContext()->setContextProperties({
{"rootView", QVariant::fromValue(this)}, {"rootView", QVariant::fromValue(this)},
{"materialBrowserModel", QVariant::fromValue(m_materialBrowserModel.data())}, {"materialBrowserModel", QVariant::fromValue(m_materialBrowserModel.data())},
{"materialBrowserBundleModel", QVariant::fromValue(m_materialBrowserBundleModel.data())},
}); });
m_quickWidget->engine()->addImageProvider("materialBrowser", m_previewImageProvider); m_quickWidget->engine()->addImageProvider("materialBrowser", m_previewImageProvider);
@@ -189,6 +206,12 @@ void MaterialBrowserWidget::startDragMaterial(int index, const QPointF &mousePos
m_dragStartPoint = mousePos.toPoint(); m_dragStartPoint = mousePos.toPoint();
} }
void MaterialBrowserWidget::startDragBundleMaterial(QmlDesigner::BundleMaterial *bundleMat, const QPointF &mousePos)
{
m_bundleMaterialToDrag = bundleMat;
m_dragStartPoint = mousePos.toPoint();
}
QString MaterialBrowserWidget::qmlSourcesPath() QString MaterialBrowserWidget::qmlSourcesPath()
{ {
#ifdef SHARE_QML_PATH #ifdef SHARE_QML_PATH
@@ -216,6 +239,7 @@ void MaterialBrowserWidget::reloadQmlSource()
void MaterialBrowserWidget::updateSearch() void MaterialBrowserWidget::updateSearch()
{ {
m_materialBrowserModel->setSearchText(m_filterText); m_materialBrowserModel->setSearchText(m_filterText);
m_materialBrowserBundleModel->setSearchText(m_filterText);
m_quickWidget->update(); m_quickWidget->update();
} }
@@ -229,4 +253,10 @@ QPointer<MaterialBrowserModel> MaterialBrowserWidget::materialBrowserModel() con
return m_materialBrowserModel; return m_materialBrowserModel;
} }
QPointer<MaterialBrowserBundleModel> MaterialBrowserWidget::materialBrowserBundleModel() const
{
return m_materialBrowserBundleModel;
}
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -11,6 +11,7 @@
#include <QFileIconProvider> #include <QFileIconProvider>
#include <QFrame> #include <QFrame>
#include <QJsonObject>
#include <QPointF> #include <QPointF>
#include <QQmlPropertyMap> #include <QQmlPropertyMap>
#include <QQuickWidget> #include <QQuickWidget>
@@ -26,8 +27,10 @@ QT_END_NAMESPACE
namespace QmlDesigner { namespace QmlDesigner {
class BundleMaterial;
class MaterialBrowserView; class MaterialBrowserView;
class MaterialBrowserModel; class MaterialBrowserModel;
class MaterialBrowserBundleModel;
class PreviewImageProvider; class PreviewImageProvider;
class MaterialBrowserWidget : public QFrame class MaterialBrowserWidget : public QFrame
@@ -45,13 +48,18 @@ public:
void clearSearchFilter(); void clearSearchFilter();
QPointer<MaterialBrowserModel> materialBrowserModel() const; QPointer<MaterialBrowserModel> materialBrowserModel() const;
QPointer<MaterialBrowserBundleModel> materialBrowserBundleModel() const;
void updateMaterialPreview(const ModelNode &node, const QPixmap &pixmap); void updateMaterialPreview(const ModelNode &node, const QPixmap &pixmap);
Q_INVOKABLE void handleSearchfilterChanged(const QString &filterText); Q_INVOKABLE void handleSearchfilterChanged(const QString &filterText);
Q_INVOKABLE void startDragMaterial(int index, const QPointF &mousePos); Q_INVOKABLE void startDragMaterial(int index, const QPointF &mousePos);
Q_INVOKABLE void startDragBundleMaterial(QmlDesigner::BundleMaterial *bundleMat, const QPointF &mousePos);
QQuickWidget *quickWidget() const; QQuickWidget *quickWidget() const;
signals:
void bundleMaterialDragStarted(QmlDesigner::BundleMaterial *bundleMat);
protected: protected:
bool eventFilter(QObject *obj, QEvent *event) override; bool eventFilter(QObject *obj, QEvent *event) override;
@@ -61,6 +69,7 @@ private:
QPointer<MaterialBrowserView> m_materialBrowserView; QPointer<MaterialBrowserView> m_materialBrowserView;
QPointer<MaterialBrowserModel> m_materialBrowserModel; QPointer<MaterialBrowserModel> m_materialBrowserModel;
QPointer<MaterialBrowserBundleModel> m_materialBrowserBundleModel;
QScopedPointer<QQuickWidget> m_quickWidget; QScopedPointer<QQuickWidget> m_quickWidget;
QShortcut *m_qmlSourceUpdateShortcut = nullptr; QShortcut *m_qmlSourceUpdateShortcut = nullptr;
@@ -70,6 +79,7 @@ private:
QString m_filterText; QString m_filterText;
ModelNode m_materialToDrag; ModelNode m_materialToDrag;
BundleMaterial *m_bundleMaterialToDrag = nullptr;
QPoint m_dragStartPoint; QPoint m_dragStartPoint;
}; };

View File

@@ -35,6 +35,7 @@
#include <qmldesignerconstants.h> #include <qmldesignerconstants.h>
#include <qmldesignerplugin.h> #include <qmldesignerplugin.h>
#include <coreplugin/messagebox.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <QScopeGuard> #include <QScopeGuard>
@@ -150,6 +151,12 @@ void DynamicPropertiesProxyModel::createProperty(const QString &name, const QStr
if (selectedNodes.count() == 1) { if (selectedNodes.count() == 1) {
const ModelNode modelNode = selectedNodes.constFirst(); const ModelNode modelNode = selectedNodes.constFirst();
if (modelNode.isValid()) { if (modelNode.isValid()) {
if (modelNode.hasProperty(name.toUtf8())) {
Core::AsynchronousMessageBox::warning(tr("Property already exists"),
tr("Property '%1' already exists")
.arg(name));
return;
}
try { try {
if (Internal::DynamicPropertiesModel::isValueType(typeName)) { if (Internal::DynamicPropertiesModel::isValueType(typeName)) {
QVariant value = Internal::DynamicPropertiesModel::defaultValueForType(typeName); QVariant value = Internal::DynamicPropertiesModel::defaultValueForType(typeName);

View File

@@ -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) << "anchors:" << time.elapsed();
qCInfo(propertyEditorBenchmark) << "context:" << time.elapsed(); qCInfo(propertyEditorBenchmark) << "context:" << time.elapsed();

View File

@@ -1557,6 +1557,10 @@ QString Model::generateIdFromName(const QString &name, const QString &fallbackId
newId.prepend('_'); 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 QRegularExpression rgx("\\d+$"); // matches a number at the end of a string
while (hasId(newId)) { // id exists while (hasId(newId)) { // id exists
QRegularExpressionMatch match = rgx.match(newId); QRegularExpressionMatch match = rgx.match(newId);

View File

@@ -62,6 +62,7 @@ const char EDIT3D_BACKGROUND_COLOR_ACTIONS[] = "QmlDesigner.Editor3D.BackgroundC
const char QML_DESIGNER_SUBFOLDER[] = "/designer/"; const char QML_DESIGNER_SUBFOLDER[] = "/designer/";
const char COMPONENT_BUNDLES_FOLDER[] = "/ComponentBundles"; 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_ASSETS_FOLDER[] = "/Quick3DAssets";
const char QUICK_3D_ASSET_LIBRARY_ICON_SUFFIX[] = "_libicon"; const char QUICK_3D_ASSET_LIBRARY_ICON_SUFFIX[] = "_libicon";
const char QUICK_3D_ASSET_ICON_DIR[] = "_icons"; 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_ITEM_LIBRARY_INFO[] = "application/vnd.qtdesignstudio.itemlibraryinfo";
const char MIME_TYPE_ASSETS[] = "application/vnd.qtdesignstudio.assets"; const char MIME_TYPE_ASSETS[] = "application/vnd.qtdesignstudio.assets";
const char MIME_TYPE_MATERIAL[] = "application/vnd.qtdesignstudio.material"; 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_IMAGE[] = "application/vnd.qtdesignstudio.asset.image";
const char MIME_TYPE_ASSET_FONT[] = "application/vnd.qtdesignstudio.asset.font"; const char MIME_TYPE_ASSET_FONT[] = "application/vnd.qtdesignstudio.asset.font";
const char MIME_TYPE_ASSET_SHADER[] = "application/vnd.qtdesignstudio.asset.shader"; const char MIME_TYPE_ASSET_SHADER[] = "application/vnd.qtdesignstudio.asset.shader";

View File

@@ -731,6 +731,12 @@ Project {
"itemlibrary/itemlibraryiconimageprovider.h", "itemlibrary/itemlibraryiconimageprovider.h",
"materialbrowser/materialbrowsermodel.cpp", "materialbrowser/materialbrowsermodel.cpp",
"materialbrowser/materialbrowsermodel.h", "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.cpp",
"materialbrowser/materialbrowserview.h", "materialbrowser/materialbrowserview.h",
"materialbrowser/materialbrowserwidget.cpp", "materialbrowser/materialbrowserwidget.cpp",

View File

@@ -802,7 +802,9 @@ public:
cmd.addArg("/bin/sh"); cmd.addArg("/bin/sh");
m_shell.reset(new LinuxDeviceShell(cmd, FilePath::fromString(QString("ssh://%1/").arg(parameters.userAtHost())))); 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(); return m_shell->start();
} }