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
CustomComboBoxStyle 2.0 CustomComboBoxStyle.qml
CustomSpinBoxStyle 2.0 CustomSpinBoxStyle.qml
EditableListView 2.0 EditableListView.qml
ExpandingSpacer 2.0 ExpandingSpacer.qml
ExtendedFunctionButton 2.0 ExtendedFunctionButton.qml
ExtendedFunctionLogic 2.0 ExtendedFunctionLogic.qml
@@ -30,6 +31,7 @@ GradientPresetTabContent 2.0 GradientPresetTabContent.qml
GroupBox 2.0 GroupBox.qml
HueSlider 2.0 HueSlider.qml
IconLabel 2.0 IconLabel.qml
ListViewComboBox 2.0 ListViewComboBox.qml
Label 2.0 Label.qml
LineEdit 2.0 LineEdit.qml
OriginControl 2.0 OriginControl.qml

View File

@@ -34,6 +34,7 @@
#include <nodemetainfo.h>
#include <qmlobjectnode.h>
#include <bindingproperty.h>
#include <utils/qtcassert.h>
//using namespace QmlDesigner;
@@ -348,6 +349,99 @@ QString PropertyEditorValue::getTranslationContext() const
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()
{
qmlRegisterType<PropertyEditorValue>("HelperWidgets",2,0,"PropertyEditorValue");
@@ -490,4 +584,3 @@ void PropertyEditorNodeWrapper::update()
emit typeChanged();
}
}

View File

@@ -83,6 +83,8 @@ class PropertyEditorValue : public QObject
Q_PROPERTY(bool isValid READ isValid NOTIFY isValidChanged 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(PropertyEditorNodeWrapper* complexNode READ complexNode NOTIFY complexNodeChanged FINAL)
@@ -130,6 +132,13 @@ public:
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:
void resetValue();
void setEnumeration(const QString &scope, const QString &name);
@@ -149,7 +158,10 @@ signals:
void isValidChanged();
void isExplicitChanged();
private: //variables
private:
QStringList generateStringList(const QString &string) const;
QString generateString(const QStringList &stringList) const;
QmlDesigner::ModelNode m_modelNode;
QVariant m_value;
QString m_expression;