mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-08-06 10:18:42 +02:00
Merge pull request #89 from alicevision/dev_attributeEditor
Improve AttributeEditor UI and features
This commit is contained in:
commit
4a2d8cdab8
9 changed files with 367 additions and 214 deletions
|
@ -258,8 +258,11 @@ class Attribute(BaseObject):
|
||||||
return '"{}"'.format(self.value)
|
return '"{}"'.format(self.value)
|
||||||
return str(self.value)
|
return str(self.value)
|
||||||
|
|
||||||
def isDefault(self):
|
def defaultValue(self):
|
||||||
return self._value == self.desc.value
|
return self.desc.value
|
||||||
|
|
||||||
|
def _isDefault(self):
|
||||||
|
return self._value == self.defaultValue()
|
||||||
|
|
||||||
def getPrimitiveValue(self, exportDefault=True):
|
def getPrimitiveValue(self, exportDefault=True):
|
||||||
return self._value
|
return self._value
|
||||||
|
@ -273,6 +276,7 @@ class Attribute(BaseObject):
|
||||||
isOutput = Property(bool, isOutput.fget, constant=True)
|
isOutput = Property(bool, isOutput.fget, constant=True)
|
||||||
isLinkChanged = Signal()
|
isLinkChanged = Signal()
|
||||||
isLink = Property(bool, isLink.fget, notify=isLinkChanged)
|
isLink = Property(bool, isLink.fget, notify=isLinkChanged)
|
||||||
|
isDefault = Property(bool, _isDefault, notify=valueChanged)
|
||||||
|
|
||||||
|
|
||||||
class ListAttribute(Attribute):
|
class ListAttribute(Attribute):
|
||||||
|
@ -289,8 +293,7 @@ class ListAttribute(Attribute):
|
||||||
|
|
||||||
def _set_value(self, value):
|
def _set_value(self, value):
|
||||||
self._value.clear()
|
self._value.clear()
|
||||||
if value:
|
self.extend(value)
|
||||||
self.extend(value)
|
|
||||||
self.requestGraphUpdate()
|
self.requestGraphUpdate()
|
||||||
|
|
||||||
def append(self, value):
|
def append(self, value):
|
||||||
|
@ -300,6 +303,7 @@ class ListAttribute(Attribute):
|
||||||
values = value if isinstance(value, list) else [value]
|
values = value if isinstance(value, list) else [value]
|
||||||
attrs = [attribute_factory(self.attributeDesc.elementDesc, v, self.isOutput, self.node, self) for v in values]
|
attrs = [attribute_factory(self.attributeDesc.elementDesc, v, self.isOutput, self.node, self) for v in values]
|
||||||
self._value.insert(index, attrs)
|
self._value.insert(index, attrs)
|
||||||
|
self.valueChanged.emit()
|
||||||
self._applyExpr()
|
self._applyExpr()
|
||||||
self.requestGraphUpdate()
|
self.requestGraphUpdate()
|
||||||
|
|
||||||
|
@ -317,6 +321,7 @@ class ListAttribute(Attribute):
|
||||||
self.node.graph.removeEdge(attr) # delete edge if the attribute is linked
|
self.node.graph.removeEdge(attr) # delete edge if the attribute is linked
|
||||||
self._value.removeAt(index, count)
|
self._value.removeAt(index, count)
|
||||||
self.requestGraphUpdate()
|
self.requestGraphUpdate()
|
||||||
|
self.valueChanged.emit()
|
||||||
|
|
||||||
def uid(self, uidIndex):
|
def uid(self, uidIndex):
|
||||||
uids = []
|
uids = []
|
||||||
|
@ -334,20 +339,24 @@ class ListAttribute(Attribute):
|
||||||
def getExportValue(self):
|
def getExportValue(self):
|
||||||
return [attr.getExportValue() for attr in self._value]
|
return [attr.getExportValue() for attr in self._value]
|
||||||
|
|
||||||
def isDefault(self):
|
def defaultValue(self):
|
||||||
return bool(self._value)
|
return []
|
||||||
|
|
||||||
|
def _isDefault(self):
|
||||||
|
return len(self._value) == 0
|
||||||
|
|
||||||
def getPrimitiveValue(self, exportDefault=True):
|
def getPrimitiveValue(self, exportDefault=True):
|
||||||
if exportDefault:
|
if exportDefault:
|
||||||
return [attr.getPrimitiveValue(exportDefault=exportDefault) for attr in self._value]
|
return [attr.getPrimitiveValue(exportDefault=exportDefault) for attr in self._value]
|
||||||
else:
|
else:
|
||||||
return [attr.getPrimitiveValue(exportDefault=exportDefault) for attr in self._value if not attr.isDefault()]
|
return [attr.getPrimitiveValue(exportDefault=exportDefault) for attr in self._value if not attr.isDefault]
|
||||||
|
|
||||||
def getValueStr(self):
|
def getValueStr(self):
|
||||||
return self.attributeDesc.joinChar.join([v.getValueStr() for v in self._value])
|
return self.attributeDesc.joinChar.join([v.getValueStr() for v in self._value])
|
||||||
|
|
||||||
# Override value property setter
|
# Override value property setter
|
||||||
value = Property(Variant, Attribute._get_value, _set_value, notify=Attribute.valueChanged)
|
value = Property(Variant, Attribute._get_value, _set_value, notify=Attribute.valueChanged)
|
||||||
|
isDefault = Property(bool, _isDefault, notify=Attribute.valueChanged)
|
||||||
|
|
||||||
|
|
||||||
class GroupAttribute(Attribute):
|
class GroupAttribute(Attribute):
|
||||||
|
@ -360,6 +369,7 @@ class GroupAttribute(Attribute):
|
||||||
for subAttrDesc in self.attributeDesc.groupDesc:
|
for subAttrDesc in self.attributeDesc.groupDesc:
|
||||||
childAttr = attribute_factory(subAttrDesc, None, self.isOutput, self.node, self)
|
childAttr = attribute_factory(subAttrDesc, None, self.isOutput, self.node, self)
|
||||||
subAttributes.append(childAttr)
|
subAttributes.append(childAttr)
|
||||||
|
childAttr.valueChanged.connect(self.valueChanged)
|
||||||
|
|
||||||
self._value.reset(subAttributes)
|
self._value.reset(subAttributes)
|
||||||
|
|
||||||
|
@ -391,20 +401,24 @@ class GroupAttribute(Attribute):
|
||||||
def getExportValue(self):
|
def getExportValue(self):
|
||||||
return {key: attr.getExportValue() for key, attr in self._value.objects.items()}
|
return {key: attr.getExportValue() for key, attr in self._value.objects.items()}
|
||||||
|
|
||||||
def isDefault(self):
|
def _isDefault(self):
|
||||||
return len(self._value) == 0
|
return all(v.isDefault for v in self._value)
|
||||||
|
|
||||||
|
def defaultValue(self):
|
||||||
|
return {key: attr.defaultValue() for key, attr in self._value.items()}
|
||||||
|
|
||||||
def getPrimitiveValue(self, exportDefault=True):
|
def getPrimitiveValue(self, exportDefault=True):
|
||||||
if exportDefault:
|
if exportDefault:
|
||||||
return {name: attr.getPrimitiveValue(exportDefault=exportDefault) for name, attr in self._value.items()}
|
return {name: attr.getPrimitiveValue(exportDefault=exportDefault) for name, attr in self._value.items()}
|
||||||
else:
|
else:
|
||||||
return {name: attr.getPrimitiveValue(exportDefault=exportDefault) for name, attr in self._value.items() if not attr.isDefault()}
|
return {name: attr.getPrimitiveValue(exportDefault=exportDefault) for name, attr in self._value.items() if not attr.isDefault}
|
||||||
|
|
||||||
def getValueStr(self):
|
def getValueStr(self):
|
||||||
return self.attributeDesc.joinChar.join([v.getValueStr() for v in self._value.objects.values()])
|
return self.attributeDesc.joinChar.join([v.getValueStr() for v in self._value.objects.values()])
|
||||||
|
|
||||||
# Override value property
|
# Override value property
|
||||||
value = Property(Variant, Attribute._get_value, _set_value, notify=Attribute.valueChanged)
|
value = Property(Variant, Attribute._get_value, _set_value, notify=Attribute.valueChanged)
|
||||||
|
isDefault = Property(bool, _isDefault, notify=Attribute.valueChanged)
|
||||||
|
|
||||||
|
|
||||||
class Edge(BaseObject):
|
class Edge(BaseObject):
|
||||||
|
|
|
@ -54,7 +54,13 @@ class FeatureMatching(desc.CommandLineNode):
|
||||||
desc.ChoiceParam(
|
desc.ChoiceParam(
|
||||||
name='photometricMatchingMethod',
|
name='photometricMatchingMethod',
|
||||||
label='Photometric Matching Method',
|
label='Photometric Matching Method',
|
||||||
description='''For Scalar based regions descriptor: * BRUTE_FORCE_L2: L2 BruteForce matching * ANN_L2: L2 Approximate Nearest Neighbor matching * CASCADE_HASHING_L2: L2 Cascade Hashing matching * FAST_CASCADE_HASHING_L2: L2 Cascade Hashing with precomputed hashed regions (faster than CASCADE_HASHING_L2 but use more memory) For Binary based descriptor: * BRUTE_FORCE_HAMMING: BruteForce Hamming matching''',
|
description='For Scalar based regions descriptor\n'
|
||||||
|
' * BRUTE_FORCE_L2: L2 BruteForce matching\n'
|
||||||
|
' * ANN_L2: L2 Approximate Nearest Neighbor matching\n'
|
||||||
|
' * CASCADE_HASHING_L2: L2 Cascade Hashing matching\n'
|
||||||
|
' * FAST_CASCADE_HASHING_L2: L2 Cascade Hashing with precomputed hashed regions (faster than CASCADE_HASHING_L2 but use more memory) \n'
|
||||||
|
'For Binary based descriptor\n'
|
||||||
|
' * BRUTE_FORCE_HAMMING: BruteForce Hamming matching',
|
||||||
value='ANN_L2',
|
value='ANN_L2',
|
||||||
values=('BRUTE_FORCE_L2', 'ANN_L2', 'CASCADE_HASHING_L2', 'FAST_CASCADE_HASHING_L2', 'BRUTE_FORCE_HAMMING'),
|
values=('BRUTE_FORCE_L2', 'ANN_L2', 'CASCADE_HASHING_L2', 'FAST_CASCADE_HASHING_L2', 'BRUTE_FORCE_HAMMING'),
|
||||||
exclusive=True,
|
exclusive=True,
|
||||||
|
@ -73,7 +79,7 @@ class FeatureMatching(desc.CommandLineNode):
|
||||||
name='savePutativeMatches',
|
name='savePutativeMatches',
|
||||||
label='Save Putative Matches',
|
label='Save Putative Matches',
|
||||||
description='''putative matches.''',
|
description='''putative matches.''',
|
||||||
value='',
|
value=False,
|
||||||
uid=[0],
|
uid=[0],
|
||||||
),
|
),
|
||||||
desc.BoolParam(
|
desc.BoolParam(
|
||||||
|
|
|
@ -276,6 +276,11 @@ class UIGraph(QObject):
|
||||||
def setAttribute(self, attribute, value):
|
def setAttribute(self, attribute, value):
|
||||||
self.push(commands.SetAttributeCommand(self._graph, attribute, value))
|
self.push(commands.SetAttributeCommand(self._graph, attribute, value))
|
||||||
|
|
||||||
|
@Slot(graph.Attribute)
|
||||||
|
def resetAttribute(self, attribute):
|
||||||
|
""" Reset 'attribute' to its default value """
|
||||||
|
self.push(commands.SetAttributeCommand(self._graph, attribute, attribute.defaultValue()))
|
||||||
|
|
||||||
@Slot(graph.Attribute, QJsonValue)
|
@Slot(graph.Attribute, QJsonValue)
|
||||||
def appendAttribute(self, attribute, value=QJsonValue()):
|
def appendAttribute(self, attribute, value=QJsonValue()):
|
||||||
if isinstance(value, QJsonValue):
|
if isinstance(value, QJsonValue):
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
|
import MaterialIcons 2.2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A component to display and edit a Node's attributes.
|
A component to display and edit a Node's attributes.
|
||||||
|
@ -31,8 +32,11 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolButton {
|
ToolButton {
|
||||||
text: "⚙"
|
text: MaterialIcons.settings
|
||||||
|
font.family: MaterialIcons.fontFamily
|
||||||
onClicked: settingsMenu.popup()
|
onClicked: settingsMenu.popup()
|
||||||
|
checkable: true
|
||||||
|
checked: settingsMenu.visible
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Menu {
|
Menu {
|
||||||
|
@ -64,42 +68,25 @@ ColumnLayout {
|
||||||
id: attributesListView
|
id: attributesListView
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 6
|
anchors.margins: 4
|
||||||
|
|
||||||
clip: true
|
clip: true
|
||||||
spacing: 4
|
spacing: 1
|
||||||
ScrollBar.vertical: ScrollBar { id: scrollBar }
|
ScrollBar.vertical: ScrollBar { id: scrollBar }
|
||||||
|
|
||||||
model: node ? node.attributes : undefined
|
model: node ? node.attributes : undefined
|
||||||
|
|
||||||
delegate: RowLayout {
|
delegate: AttributeItemDelegate {
|
||||||
|
labelWidth: 180
|
||||||
width: attributesListView.width
|
width: attributesListView.width
|
||||||
spacing: 4
|
attribute: object
|
||||||
|
}
|
||||||
Label {
|
// Helper MouseArea to lose edit/activeFocus
|
||||||
id: parameterLabel
|
// when clicking on the background
|
||||||
text: object.label
|
MouseArea {
|
||||||
Layout.preferredWidth: 180
|
anchors.fill: parent
|
||||||
color: object.isOutput ? "orange" : palette.text
|
onClicked: root.forceActiveFocus()
|
||||||
elide: Label.ElideRight
|
z: -1
|
||||||
ToolTip.text: object.desc.description
|
|
||||||
ToolTip.visible: parameterMA.containsMouse && object.desc.description
|
|
||||||
ToolTip.delay: 200
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: parameterMA
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AttributeItemDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.rightMargin: scrollBar.width
|
|
||||||
height: childrenRect.height
|
|
||||||
attribute: object
|
|
||||||
readOnly: root.readOnly
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,216 +1,338 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
|
import MaterialIcons 2.2
|
||||||
|
import "../filepath.js" as Filepath
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Instantiate a control to visualize and edit an Attribute based on its type.
|
Instantiate a control to visualize and edit an Attribute based on its type.
|
||||||
*/
|
*/
|
||||||
Loader {
|
RowLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property variant attribute: null
|
property variant attribute: null
|
||||||
property bool readOnly: false
|
property bool readOnly: false // whether the attribute's value can be modified
|
||||||
|
|
||||||
|
property alias label: parameterLabel // accessor to the internal Label (attribute's name)
|
||||||
|
property int labelWidth // shortcut to set the fixed size of the Label
|
||||||
|
|
||||||
readonly property bool editable: !attribute.isOutput && !attribute.isLink && !readOnly
|
readonly property bool editable: !attribute.isOutput && !attribute.isLink && !readOnly
|
||||||
|
|
||||||
sourceComponent: {
|
spacing: 4
|
||||||
switch(attribute.type)
|
|
||||||
{
|
|
||||||
case "ChoiceParam": return attribute.desc.exclusive ? comboBox_component : multiChoice_component
|
|
||||||
case "IntParam": return slider_component
|
|
||||||
case "FloatParam": return slider_component
|
|
||||||
case "BoolParam": return checkbox_component
|
|
||||||
case "ListAttribute": return listAttribute_component
|
|
||||||
case "GroupAttribute": return groupAttribute_component
|
|
||||||
default: return textField_component
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTextFieldAttribute(attribute, value)
|
Label {
|
||||||
{
|
id: parameterLabel
|
||||||
// editingFinished called even when TextField is readonly
|
|
||||||
if(editable)
|
|
||||||
_reconstruction.setAttribute(attribute, value.trim())
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
Layout.preferredWidth: labelWidth || implicitWidth
|
||||||
id: textField_component
|
Layout.fillHeight: true
|
||||||
TextField {
|
horizontalAlignment: attribute.isOutput ? Qt.AlignRight : Qt.AlignLeft
|
||||||
readOnly: !root.editable
|
elide: Label.ElideRight
|
||||||
text: attribute.value
|
padding: 5
|
||||||
selectByMouse: true
|
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
|
||||||
onEditingFinished: setTextFieldAttribute(attribute, text)
|
|
||||||
onAccepted: setTextFieldAttribute(attribute, text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
text: attribute.label
|
||||||
id: comboBox_component
|
|
||||||
ComboBox {
|
// Tooltip hint with attribute's description
|
||||||
enabled: root.editable
|
ToolTip.text: object.desc.description
|
||||||
model: attribute.desc.values
|
ToolTip.visible: parameterMA.containsMouse && object.desc.description
|
||||||
Component.onCompleted: currentIndex = find(attribute.value)
|
ToolTip.delay: 800
|
||||||
onActivated: _reconstruction.setAttribute(attribute, currentText)
|
|
||||||
Connections {
|
// make label bold if attribute's value is not the default one
|
||||||
target: attribute
|
font.bold: !object.isOutput && !object.isDefault
|
||||||
onValueChanged: currentIndex = find(attribute.value)
|
|
||||||
|
// make label italic if attribute is a link
|
||||||
|
font.italic: object.isLink
|
||||||
|
|
||||||
|
background: Rectangle { color: Qt.darker(palette.window, 1.2) }
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: parameterMA
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.AllButtons
|
||||||
|
|
||||||
|
property Component menuComp: Menu {
|
||||||
|
id: paramMenu
|
||||||
|
|
||||||
|
property bool isFileAttribute: attribute.type == "File"
|
||||||
|
property bool isFilepath: isFileAttribute && Filepath.isFile(attribute.value)
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "Reset To Default Value"
|
||||||
|
enabled: !attribute.isOutput && !attribute.isLink && !attribute.isDefault
|
||||||
|
onTriggered: _reconstruction.resetAttribute(attribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuSeparator {
|
||||||
|
visible: paramMenu.isFileAttribute
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
visible: paramMenu.isFileAttribute
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
text: paramMenu.isFilepath ? "Open Containing Folder" : "Open Folder"
|
||||||
|
onClicked: paramMenu.isFilepath ? Qt.openUrlExternally(Filepath.dirname(attribute.value)) :
|
||||||
|
Qt.openUrlExternally(attribute.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
visible: paramMenu.isFilepath
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
text: "Open File"
|
||||||
|
onClicked: Qt.openUrlExternally(attribute.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
forceActiveFocus()
|
||||||
|
if(mouse.button == Qt.RightButton)
|
||||||
|
{
|
||||||
|
var menu = menuComp.createObject(parameterLabel)
|
||||||
|
menu.parent = parameterLabel
|
||||||
|
menu.popup()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
function setTextFieldAttribute(value)
|
||||||
id: multiChoice_component
|
{
|
||||||
Flow {
|
// editingFinished called even when TextField is readonly
|
||||||
Repeater {
|
if(!editable)
|
||||||
id: checkbox_repeater
|
return
|
||||||
model: attribute.desc.values
|
switch(attribute.type)
|
||||||
delegate: CheckBox {
|
{
|
||||||
|
case "IntParam":
|
||||||
|
case "FloatParam":
|
||||||
|
_reconstruction.setAttribute(root.attribute, Number(value))
|
||||||
|
break;
|
||||||
|
case "File":
|
||||||
|
_reconstruction.setAttribute(root.attribute, value.replace("file://", "").trim())
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_reconstruction.setAttribute(root.attribute, value.trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
sourceComponent: {
|
||||||
|
switch(attribute.type)
|
||||||
|
{
|
||||||
|
case "ChoiceParam": return attribute.desc.exclusive ? comboBox_component : multiChoice_component
|
||||||
|
case "IntParam": return slider_component
|
||||||
|
case "FloatParam": return slider_component
|
||||||
|
case "BoolParam": return checkbox_component
|
||||||
|
case "ListAttribute": return listAttribute_component
|
||||||
|
case "GroupAttribute": return groupAttribute_component
|
||||||
|
default: return textField_component
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: textField_component
|
||||||
|
TextField {
|
||||||
|
readOnly: !root.editable
|
||||||
|
text: attribute.value
|
||||||
|
selectByMouse: true
|
||||||
|
onEditingFinished: setTextFieldAttribute(text)
|
||||||
|
onAccepted: {
|
||||||
|
setTextFieldAttribute(text)
|
||||||
|
root.forceActiveFocus()
|
||||||
|
}
|
||||||
|
DropArea {
|
||||||
enabled: root.editable
|
enabled: root.editable
|
||||||
text: modelData
|
anchors.fill: parent
|
||||||
checked: attribute.value.indexOf(modelData) >= 0
|
onDropped: {
|
||||||
onToggled: {
|
if(drop.hasUrls)
|
||||||
var t = attribute.value
|
setTextFieldAttribute(drop.urls[0].toLocaleString())
|
||||||
if(!checked) { t.splice(t.indexOf(modelData), 1) } // remove element
|
else if(drop.hasText && drop.text != '')
|
||||||
else { t.push(modelData) } // add element
|
setTextFieldAttribute(drop.text)
|
||||||
_reconstruction.setAttribute(attribute, t)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: slider_component
|
id: comboBox_component
|
||||||
RowLayout {
|
ComboBox {
|
||||||
TextField {
|
|
||||||
IntValidator {
|
|
||||||
id: intValidator
|
|
||||||
}
|
|
||||||
DoubleValidator {
|
|
||||||
id: doubleValidator
|
|
||||||
}
|
|
||||||
implicitWidth: 70
|
|
||||||
enabled: root.editable
|
enabled: root.editable
|
||||||
text: s.pressed ? s.value : attribute.value
|
model: attribute.desc.values
|
||||||
selectByMouse: true
|
Component.onCompleted: currentIndex = find(attribute.value)
|
||||||
validator: attribute.type == "FloatParam" ? doubleValidator : intValidator
|
onActivated: _reconstruction.setAttribute(attribute, currentText)
|
||||||
onEditingFinished: setTextFieldAttribute(attribute, text)
|
Connections {
|
||||||
}
|
target: attribute
|
||||||
Slider {
|
onValueChanged: currentIndex = find(attribute.value)
|
||||||
id: s
|
|
||||||
Layout.fillWidth: true
|
|
||||||
enabled: root.editable
|
|
||||||
value: attribute.value
|
|
||||||
from: attribute.desc.range[0]
|
|
||||||
to: attribute.desc.range[1]
|
|
||||||
stepSize: attribute.desc.range[2]
|
|
||||||
snapMode: Slider.SnapAlways
|
|
||||||
|
|
||||||
onPressedChanged: {
|
|
||||||
if(!pressed)
|
|
||||||
_reconstruction.setAttribute(attribute, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: checkbox_component
|
|
||||||
Row {
|
|
||||||
CheckBox {
|
|
||||||
enabled: root.editable
|
|
||||||
checked: attribute.value
|
|
||||||
onToggled: _reconstruction.setAttribute(attribute, !attribute.value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: listAttribute_component
|
id: multiChoice_component
|
||||||
ColumnLayout {
|
Flow {
|
||||||
id: listAttribute_layout
|
Repeater {
|
||||||
width: parent.width
|
id: checkbox_repeater
|
||||||
property bool expanded: false
|
model: attribute.desc.values
|
||||||
Row {
|
delegate: CheckBox {
|
||||||
spacing: 2
|
enabled: root.editable
|
||||||
ToolButton {
|
text: modelData
|
||||||
text: listAttribute_layout.expanded ? "▾" : "▸"
|
checked: attribute.value.indexOf(modelData) >= 0
|
||||||
onClicked: listAttribute_layout.expanded = !listAttribute_layout.expanded
|
onToggled: {
|
||||||
|
var t = attribute.value
|
||||||
|
if(!checked) { t.splice(t.indexOf(modelData), 1) } // remove element
|
||||||
|
else { t.push(modelData) } // add element
|
||||||
|
_reconstruction.setAttribute(attribute, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Label {
|
}
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
}
|
||||||
text: attribute.value.count + " elements"
|
|
||||||
}
|
Component {
|
||||||
ToolButton {
|
id: slider_component
|
||||||
text: "+"
|
RowLayout {
|
||||||
|
TextField {
|
||||||
|
IntValidator {
|
||||||
|
id: intValidator
|
||||||
|
}
|
||||||
|
DoubleValidator {
|
||||||
|
id: doubleValidator
|
||||||
|
}
|
||||||
|
implicitWidth: 70
|
||||||
enabled: root.editable
|
enabled: root.editable
|
||||||
onClicked: _reconstruction.appendAttribute(attribute, undefined)
|
text: s.pressed ? s.value : attribute.value
|
||||||
|
selectByMouse: true
|
||||||
|
validator: attribute.type == "FloatParam" ? doubleValidator : intValidator
|
||||||
|
onEditingFinished: setTextFieldAttribute(text)
|
||||||
|
onAccepted: setTextFieldAttribute(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
Slider {
|
||||||
|
id: s
|
||||||
|
Layout.fillWidth: true
|
||||||
|
enabled: root.editable
|
||||||
|
value: attribute.value
|
||||||
|
from: attribute.desc.range[0]
|
||||||
|
to: attribute.desc.range[1]
|
||||||
|
stepSize: attribute.desc.range[2]
|
||||||
|
snapMode: Slider.SnapAlways
|
||||||
|
|
||||||
|
onPressedChanged: {
|
||||||
|
if(!pressed)
|
||||||
|
_reconstruction.setAttribute(attribute, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: checkbox_component
|
||||||
|
Row {
|
||||||
|
CheckBox {
|
||||||
|
enabled: root.editable
|
||||||
|
checked: attribute.value
|
||||||
|
onToggled: _reconstruction.setAttribute(attribute, !attribute.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: listAttribute_component
|
||||||
|
ColumnLayout {
|
||||||
|
id: listAttribute_layout
|
||||||
|
width: parent.width
|
||||||
|
property bool expanded: false
|
||||||
|
RowLayout {
|
||||||
|
spacing: 4
|
||||||
|
ToolButton {
|
||||||
|
text: listAttribute_layout.expanded ? "▾" : "▸"
|
||||||
|
onClicked: listAttribute_layout.expanded = !listAttribute_layout.expanded
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: attribute.value.count + " elements"
|
||||||
|
}
|
||||||
|
ToolButton {
|
||||||
|
text: MaterialIcons.add_circle_outline
|
||||||
|
font.family: MaterialIcons.fontFamily
|
||||||
|
font.pointSize: 11
|
||||||
|
padding: 2
|
||||||
|
enabled: root.editable
|
||||||
|
onClicked: _reconstruction.appendAttribute(attribute, undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ListView {
|
||||||
|
id: lv
|
||||||
|
model: listAttribute_layout.expanded ? attribute.value : undefined
|
||||||
|
visible: model != undefined && count > 0
|
||||||
|
implicitHeight: Math.min(childrenRect.height, 300)
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: 4
|
||||||
|
clip: true
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar { id: sb }
|
||||||
|
|
||||||
|
delegate: RowLayout {
|
||||||
|
id: item
|
||||||
|
property var childAttrib: object
|
||||||
|
layoutDirection: Qt.RightToLeft
|
||||||
|
width: lv.width - sb.width
|
||||||
|
Component.onCompleted: {
|
||||||
|
var cpt = Qt.createComponent("AttributeItemDelegate.qml")
|
||||||
|
var obj = cpt.createObject(item,
|
||||||
|
{'attribute': Qt.binding(function() { return item.childAttrib }),
|
||||||
|
'readOnly': Qt.binding(function() { return root.readOnly })
|
||||||
|
})
|
||||||
|
obj.Layout.fillWidth = true
|
||||||
|
obj.label.text = index
|
||||||
|
obj.label.horizontalAlignment = Text.AlignHCenter
|
||||||
|
obj.label.verticalAlignment = Text.AlignVCenter
|
||||||
|
}
|
||||||
|
ToolButton {
|
||||||
|
enabled: root.editable
|
||||||
|
text: MaterialIcons.remove_circle_outline
|
||||||
|
font.family: MaterialIcons.fontFamily
|
||||||
|
font.pointSize: 11
|
||||||
|
padding: 2
|
||||||
|
ToolTip.text: "Remove Element"
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
onClicked: _reconstruction.removeAttribute(item.childAttrib)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: groupAttribute_component
|
||||||
ListView {
|
ListView {
|
||||||
id: lv
|
id: chilrenListView
|
||||||
model: listAttribute_layout.expanded ? attribute.value : undefined
|
model: attribute.value
|
||||||
visible: model != undefined && count > 0
|
implicitWidth: parent.width
|
||||||
implicitHeight: Math.min(childrenRect.height, 300)
|
implicitHeight: childrenRect.height
|
||||||
Layout.fillWidth: true
|
onCountChanged: forceLayout()
|
||||||
Layout.margins: 4
|
spacing: 2
|
||||||
clip: true
|
|
||||||
spacing: 10
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar { id: sb }
|
delegate: RowLayout {
|
||||||
|
id: row
|
||||||
delegate: RowLayout {
|
width: chilrenListView.width
|
||||||
id: item
|
|
||||||
property var childAttrib: object
|
property var childAttrib: object
|
||||||
layoutDirection: Qt.RightToLeft
|
|
||||||
width: lv.width - sb.width
|
Component.onCompleted: {
|
||||||
Component.onCompleted: {
|
|
||||||
var cpt = Qt.createComponent("AttributeItemDelegate.qml")
|
var cpt = Qt.createComponent("AttributeItemDelegate.qml")
|
||||||
var obj = cpt.createObject(item,
|
var obj = cpt.createObject(row,
|
||||||
{'attribute': Qt.binding(function() { return item.childAttrib }),
|
{'attribute': Qt.binding(function() { return row.childAttrib }),
|
||||||
'readOnly': Qt.binding(function() { return root.readOnly })
|
'readOnly': Qt.binding(function() { return root.readOnly })
|
||||||
})
|
})
|
||||||
obj.Layout.fillWidth = true
|
obj.Layout.fillWidth = true
|
||||||
}
|
obj.labelWidth = 100 // reduce label width for children (space gain)
|
||||||
ToolButton {
|
|
||||||
enabled: root.editable
|
|
||||||
text: "∅"
|
|
||||||
ToolTip.text: "Remove Element"
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
onClicked: _reconstruction.removeAttribute(item.childAttrib)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
|
||||||
id: groupAttribute_component
|
|
||||||
ListView {
|
|
||||||
id: someview
|
|
||||||
model: attribute.value
|
|
||||||
implicitWidth: parent.width
|
|
||||||
implicitHeight: childrenRect.height
|
|
||||||
onCountChanged: forceLayout()
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
delegate: RowLayout {
|
|
||||||
id: row
|
|
||||||
width: someview.width
|
|
||||||
property var childAttrib: object
|
|
||||||
|
|
||||||
Label { text: childAttrib.name }
|
|
||||||
Component.onCompleted: {
|
|
||||||
var cpt = Qt.createComponent("AttributeItemDelegate.qml")
|
|
||||||
var obj = cpt.createObject(row,
|
|
||||||
{'attribute': Qt.binding(function() { return row.childAttrib }),
|
|
||||||
'readOnly': Qt.binding(function() { return root.readOnly })
|
|
||||||
})
|
|
||||||
obj.Layout.fillWidth = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,7 @@ Item {
|
||||||
}
|
}
|
||||||
onReleased: {
|
onReleased: {
|
||||||
drag.target = undefined // stop drag
|
drag.target = undefined // stop drag
|
||||||
|
root.forceActiveFocus()
|
||||||
workspaceClicked()
|
workspaceClicked()
|
||||||
}
|
}
|
||||||
onPositionChanged: {
|
onPositionChanged: {
|
||||||
|
|
|
@ -15,3 +15,10 @@ function extension(path) {
|
||||||
var dot_pos = path.lastIndexOf('.');
|
var dot_pos = path.lastIndexOf('.');
|
||||||
return dot_pos > -1 ? path.substring(dot_pos, path.length) : ""
|
return dot_pos > -1 ? path.substring(dot_pos, path.length) : ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return whether the given path is a path to a file.
|
||||||
|
/// (only based on the fact that the last portion of the path
|
||||||
|
/// matches the 'basename.extension' pattern)
|
||||||
|
function isFile(path) {
|
||||||
|
return extension(path) !== ""
|
||||||
|
}
|
||||||
|
|
|
@ -290,6 +290,9 @@ ApplicationWindow {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
orientation: Qt.Vertical
|
orientation: Qt.Vertical
|
||||||
|
|
||||||
|
// Setup global tooltip style
|
||||||
|
ToolTip.toolTip.background: Rectangle { color: palette.base; border.color: palette.mid }
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from PySide2.QtCore import QFileSystemWatcher, QUrl, Slot
|
from PySide2.QtCore import QFileSystemWatcher, QUrl, Slot, QTimer
|
||||||
from PySide2.QtQml import QQmlApplicationEngine
|
from PySide2.QtQml import QQmlApplicationEngine
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,6 +29,8 @@ class QmlInstantEngine(QQmlApplicationEngine):
|
||||||
self._rootItem = None
|
self._rootItem = None
|
||||||
|
|
||||||
def onObjectCreated(root, url):
|
def onObjectCreated(root, url):
|
||||||
|
if not root:
|
||||||
|
return
|
||||||
# Restore root item geometry
|
# Restore root item geometry
|
||||||
if self._rootItem:
|
if self._rootItem:
|
||||||
root.setGeometry(self._rootItem.geometry())
|
root.setGeometry(self._rootItem.geometry())
|
||||||
|
@ -160,6 +162,11 @@ class QmlInstantEngine(QQmlApplicationEngine):
|
||||||
@Slot(str)
|
@Slot(str)
|
||||||
def onFileChanged(self, filepath):
|
def onFileChanged(self, filepath):
|
||||||
""" Handle changes in a watched file. """
|
""" Handle changes in a watched file. """
|
||||||
|
if filepath not in self._watchedFiles:
|
||||||
|
# could happen if a file has just been reloaded
|
||||||
|
# and has not been re-added yet to the watched files
|
||||||
|
return
|
||||||
|
|
||||||
if self._verbose:
|
if self._verbose:
|
||||||
print("Source file changed : ", filepath)
|
print("Source file changed : ", filepath)
|
||||||
# Clear the QQuickEngine cache
|
# Clear the QQuickEngine cache
|
||||||
|
@ -177,9 +184,10 @@ class QmlInstantEngine(QQmlApplicationEngine):
|
||||||
|
|
||||||
self.reload()
|
self.reload()
|
||||||
|
|
||||||
# Finally, read the modified file to the watch system
|
# Finally, re-add the modified file to the watch system
|
||||||
self.addFile(filepath)
|
# after a short cooldown to avoid multiple consecutive reloads
|
||||||
|
QTimer.singleShot(200, lambda: self.addFile(filepath))
|
||||||
|
|
||||||
def reload(self):
|
def reload(self):
|
||||||
print("Reloading ", self._sourceFile)
|
print("Reloading {}".format(self._sourceFile))
|
||||||
self.load(self._sourceFile)
|
self.load(self._sourceFile)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue