QmlDesigner: Add control for material management

- Create EditableListView for material management
 - Add id list functionality to PropertyEditorValue

Task-number: QDS-1258
Task-number: QDS-1256
Change-Id: I1694648b36845b22f4773ecbd578d77c22c934e2
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Henning Gruendl
2019-12-05 10:53:57 +01:00
committed by Henning Gründl
parent c6ab1d8b4f
commit f4d6300b0b
5 changed files with 382 additions and 2 deletions

View File

@@ -0,0 +1,213 @@
/****************************************************************************
**
** Copyright (C) 2019 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.12
import QtQuick.Layouts 1.12
import StudioControls 1.0 as StudioControls
import StudioTheme 1.0 as StudioTheme
Rectangle {
id: editableListView
property variant backendValue
ExtendedFunctionLogic {
id: extFuncLogic
backendValue: editableListView.backendValue
}
property var model
onModelChanged: myRepeater.updateModel()
signal add(string value)
signal remove(int idx)
signal replace(int idx, string value)
property alias actionIndicator: actionIndicator
property alias actionIndicatorVisible: actionIndicator.visible
property real __actionIndicatorWidth: StudioTheme.Values.squareComponentWidth
property real __actionIndicatorHeight: StudioTheme.Values.height
color: "transparent"
border.color: StudioTheme.Values.themeControlOutline
border.width: StudioTheme.Values.border
property int numVisibleItems: myRepeater.count
Layout.preferredWidth: StudioTheme.Values.height * 10
Layout.preferredHeight: myColumn.height
Component {
id: myDelegate
ListViewComboBox {
id: itemFilterComboBox
property int myIndex: index
property bool empty: itemFilterComboBox.initialModelData === ""
validator: RegExpValidator { regExp: /(^[a-z_]\w*|^[A-Z]\w*\.{1}([a-z_]\w*\.?)+)/ }
actionIndicatorVisible: false
typeFilter: "QtQuick3D.Material"
editText: modelData
initialModelData: modelData
width: editableListView.width
onFocusChanged: {
if (itemFilterComboBox.focus) {
myColumn.currentIndex = index
}
if (itemFilterComboBox.empty && itemFilterComboBox.editText !== "") {
myRepeater.dirty = false
editableListView.add(itemFilterComboBox.editText)
}
}
onCompressedActivated: {
if (itemFilterComboBox.empty && itemFilterComboBox.editText !== "") {
myRepeater.dirty = false
editableListView.add(itemFilterComboBox.editText)
} else {
editableListView.replace(myIndex, itemFilterComboBox.editText)
}
}
}
}
Rectangle {
id: highlightRect
color: "transparent"
border.width: StudioTheme.Values.border
border.color: StudioTheme.Values.themeInteraction
x: myColumn.currentItem ? myColumn.currentItem.x : 0
y: myColumn.currentItem ? myColumn.currentItem.y : 0
z: 10
width: myColumn.currentItem ? myColumn.currentItem.width : 0
height: myColumn.currentItem ? myColumn.currentItem.height : 0
}
Column {
id: myColumn
property int currentIndex: -1
property Item currentItem
spacing: -1
onCurrentIndexChanged: myColumn.currentItem = myRepeater.itemAt(myColumn.currentIndex)
Repeater {
id: myRepeater
property bool dirty: false
property var localModel: []
delegate: myDelegate
onItemAdded: function(index, item) {
if (index === myColumn.currentIndex)
myColumn.currentItem = item
}
function updateModel() {
var lastIndex = myColumn.currentIndex
myColumn.currentIndex = -1
myRepeater.localModel = []
editableListView.model.forEach(function(item) {
myRepeater.localModel.push(item)
});
// if list view is still dirty, then last state had an unfinished/empty ComboBox
if (myRepeater.dirty)
myRepeater.localModel.push("")
myRepeater.model = myRepeater.localModel // trigger on change handler
if (lastIndex < 0 && myRepeater.localModel.length > 0)
myColumn.currentIndex = 0
else if (myRepeater.localModel.length > lastIndex)
myColumn.currentIndex = lastIndex
else
myColumn.currentIndex = myRepeater.localModel.length - 1
}
}
Row {
id: row
spacing: -StudioTheme.Values.border
StudioControls.ActionIndicator {
id: actionIndicator
width: actionIndicator.visible ? __actionIndicatorWidth : 0
height: actionIndicator.visible ? __actionIndicatorHeight : 0
showBackground: true
icon.color: extFuncLogic.color
icon.text: extFuncLogic.glyph
onClicked: extFuncLogic.show()
}
StudioControls.AbstractButton {
buttonIcon: "+"
iconFont: StudioTheme.Constants.font
enabled: !myRepeater.dirty
onClicked: {
var idx = myRepeater.localModel.push("") - 1
myRepeater.model = myRepeater.localModel // trigger on change handler
myRepeater.dirty = true
myColumn.currentIndex = idx
myColumn.currentItem.forceActiveFocus()
}
}
StudioControls.AbstractButton {
buttonIcon: "-"
iconFont: StudioTheme.Constants.font
enabled: myRepeater.model.length
onClicked: {
var lastItem = myColumn.currentIndex === myRepeater.localModel.length - 1
if (myColumn.currentItem.initialModelData === "") {
myRepeater.localModel.pop()
myRepeater.dirty = false
myRepeater.model = myRepeater.localModel // trigger on change handler
} else {
editableListView.remove(myColumn.currentIndex)
}
if (!lastItem)
myColumn.currentIndex = myColumn.currentIndex - 1
}
}
Rectangle {
color: StudioTheme.Values.themeControlBackground
border.width: StudioTheme.Values.border
border.color: StudioTheme.Values.themeControlOutline
height: StudioTheme.Values.height
width: editableListView.width - (StudioTheme.Values.height - StudioTheme.Values.border) * (actionIndicatorVisible ? 3 : 2)
}
}
}
}

View File

@@ -0,0 +1,60 @@
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Quick 3D.
**
** $QT_BEGIN_LICENSE:GPL$
** 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 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** 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.
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.12
import HelperWidgets 2.0 as HelperWidgets
import StudioControls 1.0 as StudioControls
StudioControls.ComboBox {
id: comboBox
property alias typeFilter: itemFilterModel.typeFilter
property var initialModelData
property bool __isCompleted: false
editable: true
model: itemFilterModel.itemModel
HelperWidgets.ItemFilterModel {
id: itemFilterModel
modelNodeBackendProperty: modelNodeBackend
}
Component.onCompleted: {
comboBox.__isCompleted = true
// Workaround for proper initialization. Use the initial modelData value and search for it
// in the model. If nothing was found, set the editText to the initial modelData.
comboBox.currentIndex = comboBox.find(comboBox.initialModelData)
if (comboBox.currentIndex === -1)
comboBox.editText = comboBox.initialModelData
}
}

View File

@@ -17,6 +17,7 @@ ComboBox 2.0 ComboBox.qml
CustomCheckBoxStyle 2.0 CustomCheckBoxStyle.qml CustomCheckBoxStyle 2.0 CustomCheckBoxStyle.qml
CustomComboBoxStyle 2.0 CustomComboBoxStyle.qml CustomComboBoxStyle 2.0 CustomComboBoxStyle.qml
CustomSpinBoxStyle 2.0 CustomSpinBoxStyle.qml CustomSpinBoxStyle 2.0 CustomSpinBoxStyle.qml
EditableListView 2.0 EditableListView.qml
ExpandingSpacer 2.0 ExpandingSpacer.qml ExpandingSpacer 2.0 ExpandingSpacer.qml
ExtendedFunctionButton 2.0 ExtendedFunctionButton.qml ExtendedFunctionButton 2.0 ExtendedFunctionButton.qml
ExtendedFunctionLogic 2.0 ExtendedFunctionLogic.qml ExtendedFunctionLogic 2.0 ExtendedFunctionLogic.qml
@@ -30,6 +31,7 @@ GradientPresetTabContent 2.0 GradientPresetTabContent.qml
GroupBox 2.0 GroupBox.qml GroupBox 2.0 GroupBox.qml
HueSlider 2.0 HueSlider.qml HueSlider 2.0 HueSlider.qml
IconLabel 2.0 IconLabel.qml IconLabel 2.0 IconLabel.qml
ListViewComboBox 2.0 ListViewComboBox.qml
Label 2.0 Label.qml Label 2.0 Label.qml
LineEdit 2.0 LineEdit.qml LineEdit 2.0 LineEdit.qml
OriginControl 2.0 OriginControl.qml OriginControl 2.0 OriginControl.qml

View File

@@ -34,6 +34,7 @@
#include <nodemetainfo.h> #include <nodemetainfo.h>
#include <qmlobjectnode.h> #include <qmlobjectnode.h>
#include <bindingproperty.h> #include <bindingproperty.h>
#include <utils/qtcassert.h>
//using namespace QmlDesigner; //using namespace QmlDesigner;
@@ -348,6 +349,99 @@ QString PropertyEditorValue::getTranslationContext() const
return QString(); return QString();
} }
bool PropertyEditorValue::isIdList() const
{
if (modelNode().isValid() && modelNode().metaInfo().isValid() && modelNode().metaInfo().hasProperty(name())) {
const QmlDesigner::QmlObjectNode objectNode(modelNode());
if (objectNode.isValid() && objectNode.hasBindingProperty(name())) {
static const QRegExp rx("^[a-z_]\\w*|^[A-Z]\\w*\\.{1}([a-z_]\\w*\\.?)+");
const QString exp = objectNode.propertyAffectedByCurrentState(name()) ? expression() : modelNode().bindingProperty(name()).expression();
for (const auto &str : generateStringList(exp))
{
if (!rx.exactMatch(str))
return false;
}
return true;
}
return false;
}
return false;
}
QStringList PropertyEditorValue::getExpressionAsList() const
{
return generateStringList(expression());
}
bool PropertyEditorValue::idListAdd(const QString &value)
{
QTC_ASSERT(isIdList(), return false);
static const QRegExp rx("^[a-z_]\\w*|^[A-Z]\\w*\\.{1}([a-z_]\\w*\\.?)+");
if (!rx.exactMatch(value))
return false;
auto stringList = generateStringList(expression());
stringList.append(value);
setExpressionWithEmit(generateString(stringList));
return true;
}
bool PropertyEditorValue::idListRemove(int idx)
{
QTC_ASSERT(isIdList(), return false);
auto stringList = generateStringList(expression());
if (idx < 0 || idx >= stringList.size())
return false;
stringList.removeAt(idx);
setExpressionWithEmit(generateString(stringList));
return true;
}
bool PropertyEditorValue::idListReplace(int idx, const QString &value)
{
QTC_ASSERT(isIdList(), return false);
static const QRegExp rx("^[a-z_]\\w*|^[A-Z]\\w*\\.{1}([a-z_]\\w*\\.?)+");
if (!rx.exactMatch(value))
return false;
auto stringList = generateStringList(expression());
if (idx < 0 || idx >= stringList.size())
return false;
stringList.replace(idx, value);
setExpressionWithEmit(generateString(stringList));
return true;
}
QStringList PropertyEditorValue::generateStringList(const QString &string) const
{
QString copy = string;
copy = copy.remove("[").remove("]");
QStringList tmp = copy.split(",", QString::SkipEmptyParts);
for (QString &str : tmp)
str = str.trimmed();
return tmp;
}
QString PropertyEditorValue::generateString(const QStringList &stringList) const
{
if (stringList.size() > 1)
return "[" + stringList.join(",") + "]";
else
return stringList.first();
}
void PropertyEditorValue::registerDeclarativeTypes() void PropertyEditorValue::registerDeclarativeTypes()
{ {
qmlRegisterType<PropertyEditorValue>("HelperWidgets",2,0,"PropertyEditorValue"); qmlRegisterType<PropertyEditorValue>("HelperWidgets",2,0,"PropertyEditorValue");
@@ -490,4 +584,3 @@ void PropertyEditorNodeWrapper::update()
emit typeChanged(); emit typeChanged();
} }
} }

View File

@@ -83,6 +83,8 @@ class PropertyEditorValue : public QObject
Q_PROPERTY(bool isValid READ isValid NOTIFY isValidChanged FINAL) Q_PROPERTY(bool isValid READ isValid NOTIFY isValidChanged FINAL)
Q_PROPERTY(bool isTranslated READ isTranslated NOTIFY expressionChanged FINAL) Q_PROPERTY(bool isTranslated READ isTranslated NOTIFY expressionChanged FINAL)
Q_PROPERTY(QStringList expressionAsList READ getExpressionAsList NOTIFY expressionChanged FINAL)
Q_PROPERTY(QString name READ nameAsQString FINAL) Q_PROPERTY(QString name READ nameAsQString FINAL)
Q_PROPERTY(PropertyEditorNodeWrapper* complexNode READ complexNode NOTIFY complexNodeChanged FINAL) Q_PROPERTY(PropertyEditorNodeWrapper* complexNode READ complexNode NOTIFY complexNodeChanged FINAL)
@@ -130,6 +132,13 @@ public:
Q_INVOKABLE QString getTranslationContext() const; Q_INVOKABLE QString getTranslationContext() const;
bool isIdList() const;
Q_INVOKABLE QStringList getExpressionAsList() const;
Q_INVOKABLE bool idListAdd(const QString &value);
Q_INVOKABLE bool idListRemove(int idx);
Q_INVOKABLE bool idListReplace(int idx, const QString &value);
public slots: public slots:
void resetValue(); void resetValue();
void setEnumeration(const QString &scope, const QString &name); void setEnumeration(const QString &scope, const QString &name);
@@ -149,7 +158,10 @@ signals:
void isValidChanged(); void isValidChanged();
void isExplicitChanged(); void isExplicitChanged();
private: //variables private:
QStringList generateStringList(const QString &string) const;
QString generateString(const QStringList &stringList) const;
QmlDesigner::ModelNode m_modelNode; QmlDesigner::ModelNode m_modelNode;
QVariant m_value; QVariant m_value;
QString m_expression; QString m_expression;