mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-08-03 16:58:24 +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 str(self.value)
|
||||
|
||||
def isDefault(self):
|
||||
return self._value == self.desc.value
|
||||
def defaultValue(self):
|
||||
return self.desc.value
|
||||
|
||||
def _isDefault(self):
|
||||
return self._value == self.defaultValue()
|
||||
|
||||
def getPrimitiveValue(self, exportDefault=True):
|
||||
return self._value
|
||||
|
@ -273,6 +276,7 @@ class Attribute(BaseObject):
|
|||
isOutput = Property(bool, isOutput.fget, constant=True)
|
||||
isLinkChanged = Signal()
|
||||
isLink = Property(bool, isLink.fget, notify=isLinkChanged)
|
||||
isDefault = Property(bool, _isDefault, notify=valueChanged)
|
||||
|
||||
|
||||
class ListAttribute(Attribute):
|
||||
|
@ -289,8 +293,7 @@ class ListAttribute(Attribute):
|
|||
|
||||
def _set_value(self, value):
|
||||
self._value.clear()
|
||||
if value:
|
||||
self.extend(value)
|
||||
self.extend(value)
|
||||
self.requestGraphUpdate()
|
||||
|
||||
def append(self, value):
|
||||
|
@ -300,6 +303,7 @@ class ListAttribute(Attribute):
|
|||
values = value if isinstance(value, list) else [value]
|
||||
attrs = [attribute_factory(self.attributeDesc.elementDesc, v, self.isOutput, self.node, self) for v in values]
|
||||
self._value.insert(index, attrs)
|
||||
self.valueChanged.emit()
|
||||
self._applyExpr()
|
||||
self.requestGraphUpdate()
|
||||
|
||||
|
@ -317,6 +321,7 @@ class ListAttribute(Attribute):
|
|||
self.node.graph.removeEdge(attr) # delete edge if the attribute is linked
|
||||
self._value.removeAt(index, count)
|
||||
self.requestGraphUpdate()
|
||||
self.valueChanged.emit()
|
||||
|
||||
def uid(self, uidIndex):
|
||||
uids = []
|
||||
|
@ -334,20 +339,24 @@ class ListAttribute(Attribute):
|
|||
def getExportValue(self):
|
||||
return [attr.getExportValue() for attr in self._value]
|
||||
|
||||
def isDefault(self):
|
||||
return bool(self._value)
|
||||
def defaultValue(self):
|
||||
return []
|
||||
|
||||
def _isDefault(self):
|
||||
return len(self._value) == 0
|
||||
|
||||
def getPrimitiveValue(self, exportDefault=True):
|
||||
if exportDefault:
|
||||
return [attr.getPrimitiveValue(exportDefault=exportDefault) for attr in self._value]
|
||||
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):
|
||||
return self.attributeDesc.joinChar.join([v.getValueStr() for v in self._value])
|
||||
|
||||
# Override value property setter
|
||||
value = Property(Variant, Attribute._get_value, _set_value, notify=Attribute.valueChanged)
|
||||
isDefault = Property(bool, _isDefault, notify=Attribute.valueChanged)
|
||||
|
||||
|
||||
class GroupAttribute(Attribute):
|
||||
|
@ -360,6 +369,7 @@ class GroupAttribute(Attribute):
|
|||
for subAttrDesc in self.attributeDesc.groupDesc:
|
||||
childAttr = attribute_factory(subAttrDesc, None, self.isOutput, self.node, self)
|
||||
subAttributes.append(childAttr)
|
||||
childAttr.valueChanged.connect(self.valueChanged)
|
||||
|
||||
self._value.reset(subAttributes)
|
||||
|
||||
|
@ -391,20 +401,24 @@ class GroupAttribute(Attribute):
|
|||
def getExportValue(self):
|
||||
return {key: attr.getExportValue() for key, attr in self._value.objects.items()}
|
||||
|
||||
def isDefault(self):
|
||||
return len(self._value) == 0
|
||||
def _isDefault(self):
|
||||
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):
|
||||
if exportDefault:
|
||||
return {name: attr.getPrimitiveValue(exportDefault=exportDefault) for name, attr in self._value.items()}
|
||||
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):
|
||||
return self.attributeDesc.joinChar.join([v.getValueStr() for v in self._value.objects.values()])
|
||||
|
||||
# Override value property
|
||||
value = Property(Variant, Attribute._get_value, _set_value, notify=Attribute.valueChanged)
|
||||
isDefault = Property(bool, _isDefault, notify=Attribute.valueChanged)
|
||||
|
||||
|
||||
class Edge(BaseObject):
|
||||
|
|
|
@ -54,7 +54,13 @@ class FeatureMatching(desc.CommandLineNode):
|
|||
desc.ChoiceParam(
|
||||
name='photometricMatchingMethod',
|
||||
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',
|
||||
values=('BRUTE_FORCE_L2', 'ANN_L2', 'CASCADE_HASHING_L2', 'FAST_CASCADE_HASHING_L2', 'BRUTE_FORCE_HAMMING'),
|
||||
exclusive=True,
|
||||
|
@ -73,7 +79,7 @@ class FeatureMatching(desc.CommandLineNode):
|
|||
name='savePutativeMatches',
|
||||
label='Save Putative Matches',
|
||||
description='''putative matches.''',
|
||||
value='',
|
||||
value=False,
|
||||
uid=[0],
|
||||
),
|
||||
desc.BoolParam(
|
||||
|
|
|
@ -276,6 +276,11 @@ class UIGraph(QObject):
|
|||
def setAttribute(self, 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)
|
||||
def appendAttribute(self, attribute, value=QJsonValue()):
|
||||
if isinstance(value, QJsonValue):
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick 2.9
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.2
|
||||
import MaterialIcons 2.2
|
||||
|
||||
/**
|
||||
A component to display and edit a Node's attributes.
|
||||
|
@ -31,8 +32,11 @@ ColumnLayout {
|
|||
}
|
||||
|
||||
ToolButton {
|
||||
text: "⚙"
|
||||
text: MaterialIcons.settings
|
||||
font.family: MaterialIcons.fontFamily
|
||||
onClicked: settingsMenu.popup()
|
||||
checkable: true
|
||||
checked: settingsMenu.visible
|
||||
}
|
||||
}
|
||||
Menu {
|
||||
|
@ -64,42 +68,25 @@ ColumnLayout {
|
|||
id: attributesListView
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 6
|
||||
anchors.margins: 4
|
||||
|
||||
clip: true
|
||||
spacing: 4
|
||||
spacing: 1
|
||||
ScrollBar.vertical: ScrollBar { id: scrollBar }
|
||||
|
||||
model: node ? node.attributes : undefined
|
||||
|
||||
delegate: RowLayout {
|
||||
delegate: AttributeItemDelegate {
|
||||
labelWidth: 180
|
||||
width: attributesListView.width
|
||||
spacing: 4
|
||||
|
||||
Label {
|
||||
id: parameterLabel
|
||||
text: object.label
|
||||
Layout.preferredWidth: 180
|
||||
color: object.isOutput ? "orange" : palette.text
|
||||
elide: Label.ElideRight
|
||||
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
|
||||
}
|
||||
attribute: object
|
||||
}
|
||||
// Helper MouseArea to lose edit/activeFocus
|
||||
// when clicking on the background
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.forceActiveFocus()
|
||||
z: -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,216 +1,338 @@
|
|||
import QtQuick 2.9
|
||||
import QtQuick.Layouts 1.3
|
||||
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.
|
||||
*/
|
||||
Loader {
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
spacing: 4
|
||||
|
||||
function setTextFieldAttribute(attribute, value)
|
||||
{
|
||||
// editingFinished called even when TextField is readonly
|
||||
if(editable)
|
||||
_reconstruction.setAttribute(attribute, value.trim())
|
||||
}
|
||||
Label {
|
||||
id: parameterLabel
|
||||
|
||||
Component {
|
||||
id: textField_component
|
||||
TextField {
|
||||
readOnly: !root.editable
|
||||
text: attribute.value
|
||||
selectByMouse: true
|
||||
onEditingFinished: setTextFieldAttribute(attribute, text)
|
||||
onAccepted: setTextFieldAttribute(attribute, text)
|
||||
}
|
||||
}
|
||||
Layout.preferredWidth: labelWidth || implicitWidth
|
||||
Layout.fillHeight: true
|
||||
horizontalAlignment: attribute.isOutput ? Qt.AlignRight : Qt.AlignLeft
|
||||
elide: Label.ElideRight
|
||||
padding: 5
|
||||
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
|
||||
|
||||
Component {
|
||||
id: comboBox_component
|
||||
ComboBox {
|
||||
enabled: root.editable
|
||||
model: attribute.desc.values
|
||||
Component.onCompleted: currentIndex = find(attribute.value)
|
||||
onActivated: _reconstruction.setAttribute(attribute, currentText)
|
||||
Connections {
|
||||
target: attribute
|
||||
onValueChanged: currentIndex = find(attribute.value)
|
||||
text: attribute.label
|
||||
|
||||
// Tooltip hint with attribute's description
|
||||
ToolTip.text: object.desc.description
|
||||
ToolTip.visible: parameterMA.containsMouse && object.desc.description
|
||||
ToolTip.delay: 800
|
||||
|
||||
// make label bold if attribute's value is not the default one
|
||||
font.bold: !object.isOutput && !object.isDefault
|
||||
|
||||
// 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 {
|
||||
id: multiChoice_component
|
||||
Flow {
|
||||
Repeater {
|
||||
id: checkbox_repeater
|
||||
model: attribute.desc.values
|
||||
delegate: CheckBox {
|
||||
function setTextFieldAttribute(value)
|
||||
{
|
||||
// editingFinished called even when TextField is readonly
|
||||
if(!editable)
|
||||
return
|
||||
switch(attribute.type)
|
||||
{
|
||||
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
|
||||
text: modelData
|
||||
checked: attribute.value.indexOf(modelData) >= 0
|
||||
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)
|
||||
anchors.fill: parent
|
||||
onDropped: {
|
||||
if(drop.hasUrls)
|
||||
setTextFieldAttribute(drop.urls[0].toLocaleString())
|
||||
else if(drop.hasText && drop.text != '')
|
||||
setTextFieldAttribute(drop.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: slider_component
|
||||
RowLayout {
|
||||
TextField {
|
||||
IntValidator {
|
||||
id: intValidator
|
||||
}
|
||||
DoubleValidator {
|
||||
id: doubleValidator
|
||||
}
|
||||
implicitWidth: 70
|
||||
Component {
|
||||
id: comboBox_component
|
||||
ComboBox {
|
||||
enabled: root.editable
|
||||
text: s.pressed ? s.value : attribute.value
|
||||
selectByMouse: true
|
||||
validator: attribute.type == "FloatParam" ? doubleValidator : intValidator
|
||||
onEditingFinished: setTextFieldAttribute(attribute, 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)
|
||||
model: attribute.desc.values
|
||||
Component.onCompleted: currentIndex = find(attribute.value)
|
||||
onActivated: _reconstruction.setAttribute(attribute, currentText)
|
||||
Connections {
|
||||
target: attribute
|
||||
onValueChanged: currentIndex = find(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
|
||||
Row {
|
||||
spacing: 2
|
||||
ToolButton {
|
||||
text: listAttribute_layout.expanded ? "▾" : "▸"
|
||||
onClicked: listAttribute_layout.expanded = !listAttribute_layout.expanded
|
||||
Component {
|
||||
id: multiChoice_component
|
||||
Flow {
|
||||
Repeater {
|
||||
id: checkbox_repeater
|
||||
model: attribute.desc.values
|
||||
delegate: CheckBox {
|
||||
enabled: root.editable
|
||||
text: modelData
|
||||
checked: attribute.value.indexOf(modelData) >= 0
|
||||
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"
|
||||
}
|
||||
ToolButton {
|
||||
text: "+"
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: slider_component
|
||||
RowLayout {
|
||||
TextField {
|
||||
IntValidator {
|
||||
id: intValidator
|
||||
}
|
||||
DoubleValidator {
|
||||
id: doubleValidator
|
||||
}
|
||||
implicitWidth: 70
|
||||
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 {
|
||||
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: 10
|
||||
id: chilrenListView
|
||||
model: attribute.value
|
||||
implicitWidth: parent.width
|
||||
implicitHeight: childrenRect.height
|
||||
onCountChanged: forceLayout()
|
||||
spacing: 2
|
||||
|
||||
ScrollBar.vertical: ScrollBar { id: sb }
|
||||
|
||||
delegate: RowLayout {
|
||||
id: item
|
||||
delegate: RowLayout {
|
||||
id: row
|
||||
width: chilrenListView.width
|
||||
property var childAttrib: object
|
||||
layoutDirection: Qt.RightToLeft
|
||||
width: lv.width - sb.width
|
||||
Component.onCompleted: {
|
||||
|
||||
Component.onCompleted: {
|
||||
var cpt = Qt.createComponent("AttributeItemDelegate.qml")
|
||||
var obj = cpt.createObject(item,
|
||||
{'attribute': Qt.binding(function() { return item.childAttrib }),
|
||||
var obj = cpt.createObject(row,
|
||||
{'attribute': Qt.binding(function() { return row.childAttrib }),
|
||||
'readOnly': Qt.binding(function() { return root.readOnly })
|
||||
})
|
||||
obj.Layout.fillWidth = true
|
||||
}
|
||||
ToolButton {
|
||||
enabled: root.editable
|
||||
text: "∅"
|
||||
ToolTip.text: "Remove Element"
|
||||
ToolTip.visible: hovered
|
||||
onClicked: _reconstruction.removeAttribute(item.childAttrib)
|
||||
obj.labelWidth = 100 // reduce label width for children (space gain)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: {
|
||||
drag.target = undefined // stop drag
|
||||
root.forceActiveFocus()
|
||||
workspaceClicked()
|
||||
}
|
||||
onPositionChanged: {
|
||||
|
|
|
@ -15,3 +15,10 @@ function extension(path) {
|
|||
var dot_pos = path.lastIndexOf('.');
|
||||
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
|
||||
orientation: Qt.Vertical
|
||||
|
||||
// Setup global tooltip style
|
||||
ToolTip.toolTip.background: Rectangle { color: palette.base; border.color: palette.mid }
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import os
|
||||
import time
|
||||
|
||||
from PySide2.QtCore import QFileSystemWatcher, QUrl, Slot
|
||||
from PySide2.QtCore import QFileSystemWatcher, QUrl, Slot, QTimer
|
||||
from PySide2.QtQml import QQmlApplicationEngine
|
||||
|
||||
|
||||
|
@ -29,6 +29,8 @@ class QmlInstantEngine(QQmlApplicationEngine):
|
|||
self._rootItem = None
|
||||
|
||||
def onObjectCreated(root, url):
|
||||
if not root:
|
||||
return
|
||||
# Restore root item geometry
|
||||
if self._rootItem:
|
||||
root.setGeometry(self._rootItem.geometry())
|
||||
|
@ -160,6 +162,11 @@ class QmlInstantEngine(QQmlApplicationEngine):
|
|||
@Slot(str)
|
||||
def onFileChanged(self, filepath):
|
||||
""" 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:
|
||||
print("Source file changed : ", filepath)
|
||||
# Clear the QQuickEngine cache
|
||||
|
@ -177,9 +184,10 @@ class QmlInstantEngine(QQmlApplicationEngine):
|
|||
|
||||
self.reload()
|
||||
|
||||
# Finally, read the modified file to the watch system
|
||||
self.addFile(filepath)
|
||||
# Finally, re-add the modified file to the watch system
|
||||
# after a short cooldown to avoid multiple consecutive reloads
|
||||
QTimer.singleShot(200, lambda: self.addFile(filepath))
|
||||
|
||||
def reload(self):
|
||||
print("Reloading ", self._sourceFile)
|
||||
print("Reloading {}".format(self._sourceFile))
|
||||
self.load(self._sourceFile)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue