Merge branch 'dev/ImageGalleryFilter' of https://github.com/N0Ls/meshroom into N0Ls-dev/ImageGalleryFilter

This commit is contained in:
Fabien Castan 2021-09-01 11:03:55 +02:00
commit af4dae9a77
7 changed files with 549 additions and 48 deletions

View file

@ -71,25 +71,30 @@ class Attribute(BaseObject):
def root(self):
return self._root() if self._root else None
def absoluteName(self):
return '{}.{}.{}'.format(self.node.graph.name, self.node.name, self._name)
def getFullName(self):
""" Name inside the Graph: nodeName.name """
if isinstance(self.root, ListAttribute):
return '{}[{}]'.format(self.root.getFullName(), self.root.index(self))
elif isinstance(self.root, GroupAttribute):
return '{}.{}'.format(self.root.getFullName(), self._name)
return '{}.{}'.format(self.node.name, self._name)
def asLinkExpr(self):
""" Return link expression for this Attribute """
return "{" + self.getFullName() + "}"
def getName(self):
""" Attribute name """
return self._name
def getFullName(self):
""" Name inside the Graph: groupName.name """
if isinstance(self.root, ListAttribute):
return '{}[{}]'.format(self.root.getFullName(), self.root.index(self))
elif isinstance(self.root, GroupAttribute):
return '{}.{}'.format(self.root.getFullName(), self.getName())
return self.getName()
def getFullNameToNode(self):
""" Name inside the Graph: nodeName.groupName.name """
return '{}.{}'.format(self.node.name, self.getFullName())
def getFullNameToGraph(self):
""" Name inside the Graph: graphName.nodeName.groupName.name """
return '{}.{}'.format(self.node.graph.name, self.getFullNameToNode())
def asLinkExpr(self):
""" Return link expression for this Attribute """
return "{" + self.getFullNameToNode() + "}"
def getType(self):
return self.attributeDesc.__class__.__name__
@ -102,6 +107,22 @@ class Attribute(BaseObject):
def getLabel(self):
return self._label
def getFullLabel(self):
""" Full Label includes the name of all parent groups, e.g. 'groupLabel subGroupLabel Label' """
if isinstance(self.root, ListAttribute):
return self.root.getFullLabel()
elif isinstance(self.root, GroupAttribute):
return '{} {}'.format(self.root.getFullLabel(), self.getLabel())
return self.getLabel()
def getFullLabelToNode(self):
""" Label inside the Graph: nodeLabel groupLabel Label """
return '{} {}'.format(self.node.label, self.getFullLabel())
def getFullLabelToGraph(self):
""" Label inside the Graph: graphName nodeLabel groupLabel Label """
return '{} {}'.format(self.node.graph.name, self.getFullLabelToNode())
def getEnabled(self):
if isinstance(self.desc.enabled, types.FunctionType):
try:
@ -265,7 +286,12 @@ class Attribute(BaseObject):
name = Property(str, getName, constant=True)
fullName = Property(str, getFullName, constant=True)
fullNameToNode = Property(str, getFullNameToNode, constant=True)
fullNameToGraph = Property(str, getFullNameToGraph, constant=True)
label = Property(str, getLabel, constant=True)
fullLabel = Property(str, getFullLabel, constant=True)
fullLabelToNode = Property(str, getFullLabelToNode, constant=True)
fullLabelToGraph = Property(str, getFullLabelToGraph, constant=True)
type = Property(str, getType, constant=True)
baseType = Property(str, getType, constant=True)
isReadOnly = Property(bool, _isReadOnly, constant=True)

View file

@ -415,7 +415,7 @@ class Graph(BaseObject):
def removeNode(self, nodeName):
"""
Remove the node identified by 'nodeName' from the graph
and return in and out edges removed by this operation in two dicts {dstAttr.getFullName(), srcAttr.getFullName()}
and return in and out edges removed by this operation in two dicts {dstAttr.getFullNameToNode(), srcAttr.getFullNameToNode()}
"""
node = self.node(nodeName)
inEdges = {}
@ -425,10 +425,10 @@ class Graph(BaseObject):
with GraphModification(self):
for edge in self.nodeOutEdges(node):
self.removeEdge(edge.dst)
outEdges[edge.dst.getFullName()] = edge.src.getFullName()
outEdges[edge.dst.getFullNameToNode()] = edge.src.getFullNameToNode()
for edge in self.nodeInEdges(node):
self.removeEdge(edge.dst)
inEdges[edge.dst.getFullName()] = edge.src.getFullName()
inEdges[edge.dst.getFullNameToNode()] = edge.src.getFullNameToNode()
node.alive = False
self._nodes.remove(node)
@ -583,7 +583,7 @@ class Graph(BaseObject):
if srcAttr.node.graph != self or dstAttr.node.graph != self:
raise RuntimeError('The attributes of the edge should be part of a common graph.')
if dstAttr in self.edges.keys():
raise RuntimeError('Destination attribute "{}" is already connected.'.format(dstAttr.getFullName()))
raise RuntimeError('Destination attribute "{}" is already connected.'.format(dstAttr.getFullNameToNode()))
edge = Edge(srcAttr, dstAttr)
self.edges.add(edge)
self.markNodesDirty(dstAttr.node)
@ -600,7 +600,7 @@ class Graph(BaseObject):
@changeTopology
def removeEdge(self, dstAttr):
if dstAttr not in self.edges.keys():
raise RuntimeError('Attribute "{}" is not connected'.format(dstAttr.getFullName()))
raise RuntimeError('Attribute "{}" is not connected'.format(dstAttr.getFullNameToNode()))
edge = self.edges.pop(dstAttr)
self.markNodesDirty(dstAttr.node)
dstAttr.valueChanged.emit()
@ -1202,4 +1202,3 @@ def loadGraph(filepath):
graph.load(filepath)
graph.update()
return graph

View file

@ -197,10 +197,10 @@ class DuplicateNodesCommand(GraphCommand):
class SetAttributeCommand(GraphCommand):
def __init__(self, graph, attribute, value, parent=None):
super(SetAttributeCommand, self).__init__(graph, parent)
self.attrName = attribute.getFullName()
self.attrName = attribute.getFullNameToNode()
self.value = value
self.oldValue = attribute.getExportValue()
self.setText("Set Attribute '{}'".format(attribute.getFullName()))
self.setText("Set Attribute '{}'".format(attribute.getFullNameToNode()))
def redoImpl(self):
if self.value == self.oldValue:
@ -215,8 +215,8 @@ class SetAttributeCommand(GraphCommand):
class AddEdgeCommand(GraphCommand):
def __init__(self, graph, src, dst, parent=None):
super(AddEdgeCommand, self).__init__(graph, parent)
self.srcAttr = src.getFullName()
self.dstAttr = dst.getFullName()
self.srcAttr = src.getFullNameToNode()
self.dstAttr = dst.getFullNameToNode()
self.setText("Connect '{}'->'{}'".format(self.srcAttr, self.dstAttr))
if src.baseType != dst.baseType:
@ -233,8 +233,8 @@ class AddEdgeCommand(GraphCommand):
class RemoveEdgeCommand(GraphCommand):
def __init__(self, graph, edge, parent=None):
super(RemoveEdgeCommand, self).__init__(graph, parent)
self.srcAttr = edge.src.getFullName()
self.dstAttr = edge.dst.getFullName()
self.srcAttr = edge.src.getFullNameToNode()
self.dstAttr = edge.dst.getFullNameToNode()
self.setText("Disconnect '{}'->'{}'".format(self.srcAttr, self.dstAttr))
def redoImpl(self):
@ -250,7 +250,7 @@ class ListAttributeAppendCommand(GraphCommand):
def __init__(self, graph, listAttribute, value, parent=None):
super(ListAttributeAppendCommand, self).__init__(graph, parent)
assert isinstance(listAttribute, ListAttribute)
self.attrName = listAttribute.getFullName()
self.attrName = listAttribute.getFullNameToNode()
self.index = None
self.count = 1
self.value = value if value else None
@ -276,10 +276,10 @@ class ListAttributeRemoveCommand(GraphCommand):
super(ListAttributeRemoveCommand, self).__init__(graph, parent)
listAttribute = attribute.root
assert isinstance(listAttribute, ListAttribute)
self.listAttrName = listAttribute.getFullName()
self.listAttrName = listAttribute.getFullNameToNode()
self.index = listAttribute.index(attribute)
self.value = attribute.getExportValue()
self.setText("Remove {}".format(attribute.getFullName()))
self.setText("Remove {}".format(attribute.getFullNameToNode()))
def redoImpl(self):
listAttribute = self.graph.attribute(self.listAttrName)

View file

@ -618,14 +618,14 @@ class UIGraph(QObject):
@Slot(Attribute, Attribute)
def addEdge(self, src, dst):
if isinstance(dst, ListAttribute) and not isinstance(src, ListAttribute):
with self.groupedGraphModification("Insert and Add Edge on {}".format(dst.getFullName())):
with self.groupedGraphModification("Insert and Add Edge on {}".format(dst.getFullNameToNode())):
self.appendAttribute(dst)
self._addEdge(src, dst.at(-1))
else:
self._addEdge(src, dst)
def _addEdge(self, src, dst):
with self.groupedGraphModification("Connect '{}'->'{}'".format(src.getFullName(), dst.getFullName())):
with self.groupedGraphModification("Connect '{}'->'{}'".format(src.getFullNameToNode(), dst.getFullNameToNode())):
if dst in self._graph.edges.keys():
self.removeEdge(self._graph.edge(dst))
self.push(commands.AddEdgeCommand(self._graph, src, dst))
@ -633,7 +633,7 @@ class UIGraph(QObject):
@Slot(Edge)
def removeEdge(self, edge):
if isinstance(edge.dst.root, ListAttribute):
with self.groupedGraphModification("Remove Edge and Delete {}".format(edge.dst.getFullName())):
with self.groupedGraphModification("Remove Edge and Delete {}".format(edge.dst.getFullNameToNode())):
self.push(commands.RemoveEdgeCommand(self._graph, edge))
self.removeAttribute(edge.dst)
else:

View file

@ -1,8 +1,9 @@
import QtQuick 2.7
import QtQuick 2.14
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import MaterialIcons 2.2
import QtQml.Models 2.2
import Qt.labs.qmlmodels 1.0
import Controls 1.0
import Utils 1.0
@ -26,6 +27,9 @@ Panel {
property int currentIndex: 0
property bool readOnly: false
property string filter: ""
property bool filterValue: true
signal removeImageRequest(var attribute)
signal filesDropped(var drop, var augmentSfm)
@ -40,9 +44,66 @@ Panel {
id: m
property variant currentCameraInit: _reconstruction.tempCameraInit ? _reconstruction.tempCameraInit : root.cameraInit
property variant viewpoints: currentCameraInit ? currentCameraInit.attribute('viewpoints').value : undefined
property variant intrinsics: currentCameraInit ? currentCameraInit.attribute('intrinsics').value : undefined
property bool readOnly: root.readOnly || displayHDR.checked
}
property variant parsedIntrinsic
property int numberOfIntrinsics : m.intrinsics ? m.intrinsics.count : 0
onNumberOfIntrinsicsChanged: {
parseIntr()
}
function populate_model()
{
intrinsicModel.clear()
for (var intr in parsedIntrinsic) {
intrinsicModel.appendRow(parsedIntrinsic[intr])
}
}
function parseIntr(){
parsedIntrinsic = []
if(!m.intrinsics)
{
return
}
//Loop through all intrinsics
for(var i = 0; i < m.intrinsics.count; ++i){
var intrinsic = {}
//Loop through all attributes
for(var j=0; j < m.intrinsics.at(i).value.count; ++j){
var currentAttribute = m.intrinsics.at(i).value.at(j)
if(currentAttribute.type === "GroupAttribute"){
for(var k=0; k < currentAttribute.value.count; ++k){
intrinsic[currentAttribute.name + "." + currentAttribute.value.at(k).name] = currentAttribute.value.at(k)
}
}
else if(currentAttribute.type === "ListAttribute"){
// not needed for now
}
else{
intrinsic[currentAttribute.name] = currentAttribute
}
}
// Table Model needs to contain an entry for each column.
// In case of old file formats, some intrinsic keys that we display may not exist in the model.
// So, here we create an empty entry to enforce that the key exists in the model.
for(var n = 0; n < intrinsicModel.columnNames.length; ++n)
{
var name = intrinsicModel.columnNames[n]
if(!(name in intrinsic)) {
intrinsic[name] = {}
}
}
parsedIntrinsic[i] = intrinsic
}
populate_model()
}
headerBar: RowLayout {
MaterialToolButton {
text: MaterialIcons.more_vert
@ -91,7 +152,14 @@ Panel {
Layout.fillWidth: true
Layout.fillHeight: true
ScrollBar.vertical: ScrollBar { minimumSize: 0.05 }
interactive: !intrinsicsFilterButton.checked
ScrollBar.vertical: ScrollBar {
minimumSize: 0.05
active : !intrinsicsFilterButton.checked
visible: !intrinsicsFilterButton.checked
}
focus: true
clip: true
@ -115,14 +183,16 @@ Panel {
model: m.viewpoints
sortRole: "path"
// TODO: provide filtering on reconstruction status
// filterRole: _reconstruction.sfmReport ? "reconstructed" : ""
// filterValue: true / false
filterRole: _reconstruction.sfmReport ? root.filter : ""
filterValue: root.filterValue
// in modelData:
// if(filterRole == roleName)
// return _reconstruction.isReconstructed(item.model.object)
// override modelData to return basename of viewpoint's path for sorting
function modelData(item, roleName) {
if(filterRole == roleName)
return _reconstruction.isReconstructed(item.model.object)
var value = item.model.object.childAttribute(roleName).value
if(roleName == sortRole)
return Filepath.basename(value)
@ -138,6 +208,7 @@ Panel {
height: grid.cellHeight
readOnly: m.readOnly
displayViewId: displayViewIdsAction.checked
visible: !intrinsicsFilterButton.checked
isCurrentItem: GridView.isCurrentItem
@ -233,8 +304,9 @@ Panel {
// Explanatory placeholder when no image has been added yet
Column {
id: dropImagePlaceholder
anchors.centerIn: parent
visible: grid.model.count == 0
visible: (m.viewpoints ? m.viewpoints.count == 0 : true) && !intrinsicsFilterButton.checked
spacing: 4
Label {
anchors.horizontalCenter: parent.horizontalCenter
@ -246,11 +318,106 @@ Panel {
text: "Drop Image Files / Folders"
}
}
// Placeholder when the filtered images list is empty
Column {
id: noImageImagePlaceholder
anchors.centerIn: parent
visible: (m.viewpoints ? m.viewpoints.count != 0 : false) && !dropImagePlaceholder.visible && grid.model.count == 0 && !intrinsicsFilterButton.checked
spacing: 4
Label {
anchors.horizontalCenter: parent.horizontalCenter
text: MaterialIcons.filter_none
font.pointSize: 24
font.family: MaterialIcons.fontFamily
}
Label {
text: "No images in this filtered view"
}
}
RowLayout{
anchors.fill: parent
Layout.fillWidth: true
Layout.fillHeight: true
TapHandler {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onTapped: {
intrinsicTable.focus = false
}
}
TableView{
id : intrinsicTable
visible: intrinsicsFilterButton.checked
Layout.fillHeight: true
Layout.fillWidth: true
interactive : !focus
boundsMovement : Flickable.StopAtBounds
//Provide width for column
//Note no size provided for the last column (bool comp) so it uses its automated size
columnWidthProvider: function (column) { return intrinsicModel.columnWidths[column] }
model: intrinsicModel
delegate: IntrinsicDisplayDelegate{}
ScrollBar.horizontal: ScrollBar { id: sb }
ScrollBar.vertical : ScrollBar { id: sbv }
}
TableModel{
id : intrinsicModel
// Hardcoded default width per column
property var columnWidths: [105, 75, 75, 75, 125, 60, 60, 45, 45, 200, 60, 60]
property var columnNames: [
"intrinsicId",
"pxInitialFocalLength",
"pxFocalLength.x",
"pxFocalLength.y",
"type",
"width",
"height",
"sensorWidth",
"sensorHeight",
"serialNumber",
"principalPoint.x",
"principalPoint.y",
"locked"]
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[0]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[1]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[2]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[3]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[4]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[5]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[6]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[7]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[8]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[9]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[10]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[11]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[12]]} }
//https://doc.qt.io/qt-5/qml-qt-labs-qmlmodels-tablemodel.html#appendRow-method
}
//CODE FOR HEADERS
//UNCOMMENT WHEN COMPATIBLE WITH THE RIGHT QT VERSION
// HorizontalHeaderView {
// id: horizontalHeader
// syncView: tableView
// anchors.left: tableView.left
// }
}
DropArea {
id: dropArea
anchors.fill: parent
enabled: !m.readOnly
enabled: !m.readOnly && !intrinsicsFilterButton.checked
keys: ["text/uri-list"]
// TODO: onEntered: call specific method to filter files based on extension
onDropped: {
@ -344,22 +511,123 @@ Panel {
footerContent: RowLayout {
// Images count
MaterialToolLabel {
id: footer
function resetButtons(){
inputImagesFilterButton.checked = false
estimatedCamerasFilterButton.checked = false
nonEstimatedCamerasFilterButton.checked = false
}
MaterialToolLabelButton {
id : inputImagesFilterButton
Layout.minimumWidth: childrenRect.width
ToolTip.text: grid.model.count + " Input Images"
iconText: MaterialIcons.image
label: grid.model.count.toString()
// enabled: grid.model.count > 0
// margin: 4
label: (m.viewpoints ? m.viewpoints.count : 0)
padding: 3
checkable: true
checked: true
onCheckedChanged:{
if(checked) {
root.filter = ""
root.filterValue = true
estimatedCamerasFilterButton.checked = false
nonEstimatedCamerasFilterButton.checked = false
intrinsicsFilterButton.checked = false;
}
// cameras count
MaterialToolLabel {
}
}
// Estimated cameras count
MaterialToolLabelButton {
id : estimatedCamerasFilterButton
Layout.minimumWidth: childrenRect.width
ToolTip.text: label + " Estimated Cameras"
iconText: MaterialIcons.videocam
label: _reconstruction ? _reconstruction.nbCameras.toString() : "0"
// margin: 4
// enabled: _reconstruction.cameraInit && _reconstruction.nbCameras
label: _reconstruction.nbCameras ? _reconstruction.nbCameras.toString() : "-"
padding: 3
enabled: _reconstruction.cameraInit && _reconstruction.nbCameras
checkable: true
checked: false
onCheckedChanged:{
if(checked) {
root.filter = "viewId"
root.filterValue = true
inputImagesFilterButton.checked = false
nonEstimatedCamerasFilterButton.checked = false
intrinsicsFilterButton.checked = false;
}
}
onEnabledChanged:{
if(!enabled) {
if(checked) inputImagesFilterButton.checked = true;
checked = false
}
}
}
// Non estimated cameras count
MaterialToolLabelButton {
id : nonEstimatedCamerasFilterButton
Layout.minimumWidth: childrenRect.width
ToolTip.text: label + " Non Estimated Cameras"
iconText: MaterialIcons.videocam_off
label: _reconstruction.nbCameras ? ((m.viewpoints ? m.viewpoints.count : 0) - _reconstruction.nbCameras.toString()).toString() : "-"
padding: 3
enabled: _reconstruction.cameraInit && _reconstruction.nbCameras
checkable: true
checked: false
onCheckedChanged:{
if(checked) {
filter = "viewId"
root.filterValue = false
inputImagesFilterButton.checked = false
estimatedCamerasFilterButton.checked = false
intrinsicsFilterButton.checked = false;
}
}
onEnabledChanged:{
if(!enabled) {
if(checked) inputImagesFilterButton.checked = true;
checked = false
}
}
}
MaterialToolLabelButton {
id : intrinsicsFilterButton
Layout.minimumWidth: childrenRect.width
ToolTip.text: label + " Number of intrinsics"
iconText: MaterialIcons.camera
label: _reconstruction ? (m.intrinsics ? m.intrinsics.count : 0) : "0"
padding: 3
enabled: m.intrinsics ? m.intrinsics.count > 0 : false
checkable: true
checked: false
onCheckedChanged:{
if(checked) {
inputImagesFilterButton.checked = false
estimatedCamerasFilterButton.checked = false
nonEstimatedCamerasFilterButton.checked = false
}
}
onEnabledChanged:{
if(!enabled) {
if(checked) inputImagesFilterButton.checked = true;
checked = false
}
}
}
Item { Layout.fillHeight: true; Layout.fillWidth: true }

View file

@ -0,0 +1,205 @@
import QtQuick 2.9
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.2
import MaterialIcons 2.2
import Utils 1.0
RowLayout {
id: root
Layout.fillWidth: true
property variant attribute: model.display
property int rowIndex: model.row
property int columnIndex: model.column
property bool readOnly: false
property string toolTipText: {
if(!attribute || Object.keys(attribute).length === 0)
return ""
return attribute.fullLabel
}
Pane {
Layout.minimumWidth: loaderComponent.width
Layout.minimumHeight: loaderComponent.height
Layout.fillWidth: true
padding: 0
hoverEnabled: true
// Tooltip to replace headers for now (header incompatible atm)
ToolTip.delay: 10
ToolTip.timeout: 5000
ToolTip.visible: hovered
ToolTip.text: toolTipText
Rectangle {
width: parent.width
height: loaderComponent.height
color: rowIndex % 2 ? palette.window : Qt.darker(palette.window, 1.1)
border.width: 2
border.color: Qt.darker(palette.window, 1.2)
clip: true
Loader {
id: loaderComponent
active: !!model.display // convert to bool with "!!"
sourceComponent: {
if(!model.display)
return undefined
switch(model.display.type)
{
case "ChoiceParam": return choice_component
case "IntParam": return int_component
case "FloatParam": return float_component
case "BoolParam": return bool_component
case "StringParam": return textField_component
default: return undefined
}
}
}
}
}
Component {
id: textField_component
TextInput{
text: model.display.value
width: intrinsicModel.columnWidths[columnIndex]
horizontalAlignment: TextInput.AlignRight
color: 'white'
padding: 12
selectByMouse: true
selectionColor: 'white'
selectedTextColor: Qt.darker(palette.window, 1.1)
onEditingFinished: _reconstruction.setAttribute(attribute, text)
onAccepted: {
_reconstruction.setAttribute(attribute, text)
}
Component.onDestruction: {
if(activeFocus)
_reconstruction.setAttribute(attribute, text)
}
}
}
Component {
id: int_component
TextInput{
text: model.display.value
width: intrinsicModel.columnWidths[columnIndex]
horizontalAlignment: TextInput.AlignRight
color: 'white'
padding: 12
selectByMouse: true
selectionColor: 'white'
selectedTextColor: Qt.darker(palette.window, 1.1)
IntValidator {
id: intValidator
}
validator: intValidator
onEditingFinished: _reconstruction.setAttribute(attribute, Number(text))
onAccepted: {
_reconstruction.setAttribute(attribute, Number(text))
}
Component.onDestruction: {
if(activeFocus)
_reconstruction.setAttribute(attribute, Number(text))
}
}
}
Component {
id: choice_component
ComboBox {
id: combo
model: attribute.desc.values
width: intrinsicModel.columnWidths[columnIndex]
flat : true
topInset: 7
leftInset: 6
rightInset: 6
bottomInset: 7
Component.onCompleted: currentIndex = find(attribute.value)
onActivated: _reconstruction.setAttribute(attribute, currentText)
Connections {
target: attribute
onValueChanged: combo.currentIndex = combo.find(attribute.value)
}
}
}
Component {
id: bool_component
CheckBox {
checked: attribute ? attribute.value : false
padding: 12
onToggled: _reconstruction.setAttribute(attribute, !attribute.value)
}
}
Component {
id: float_component
TextInput{
readonly property real formattedValue: model.display.value.toFixed(2)
property string displayValue: String(formattedValue)
text: displayValue
width: intrinsicModel.columnWidths[columnIndex]
horizontalAlignment: TextInput.AlignRight
color: 'white'
padding: 12
selectByMouse: true
selectionColor: 'white'
selectedTextColor: Qt.darker(palette.window, 1.1)
enabled: !readOnly
clip: true;
autoScroll: activeFocus
//Use this function to ensure the left part is visible
//while keeping the trick for formatting the text
//Timing issues otherwise
onActiveFocusChanged: {
if(activeFocus) text = String(model.display.value)
else text = String(formattedValue)
cursorPosition = 0
}
DoubleValidator {
id: doubleValidator
locale: 'C' // use '.' decimal separator disregarding the system locale
}
validator: doubleValidator
onEditingFinished: _reconstruction.setAttribute(attribute, Number(text))
onAccepted: {
_reconstruction.setAttribute(attribute, Number(text))
}
Component.onDestruction: {
if(activeFocus)
_reconstruction.setAttribute(attribute, Number(text))
}
}
}
}

View file

@ -2,3 +2,6 @@ module ImageGallery
ImageGallery 1.0 ImageGallery.qml
ImageDelegate 1.0 ImageDelegate.qml
ImageIntrinsicDelegate 1.0 ImageIntrinsicDelegate.qml
ImageIntrinsicViewer 1.0 ImageIntrinsicViewer.qml
IntrinsicDisplayDelegate 1.0 IntrinsicDisplayDelegate.qml