forked from qt-creator/qt-creator
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:
committed by
Henning Gründl
parent
c6ab1d8b4f
commit
f4d6300b0b
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
|
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
Reference in New Issue
Block a user