mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-05-22 13:36:31 +02:00
253 lines
9.2 KiB
QML
253 lines
9.2 KiB
QML
import QtQuick 2.7
|
|
import QtQuick.Controls 2.3
|
|
import QtQuick.Layouts 1.3
|
|
import MaterialIcons 2.2
|
|
import QtQml.Models 2.2
|
|
|
|
import Utils 1.0
|
|
|
|
/**
|
|
* ImageGallery displays as a grid of Images a model containing Viewpoints objects.
|
|
* It manages a model of multiple CameraInit nodes as individual groups.
|
|
*/
|
|
Panel {
|
|
id: root
|
|
|
|
property variant cameraInits
|
|
property variant cameraInit
|
|
readonly property alias currentItem: grid.currentItem
|
|
readonly property string currentItemSource: grid.currentItem ? grid.currentItem.source : ""
|
|
readonly property var currentItemMetadata: grid.currentItem ? grid.currentItem.metadata : undefined
|
|
signal removeImageRequest(var attribute)
|
|
property int defaultCellSize: 160
|
|
|
|
implicitWidth: 100
|
|
implicitHeight: 300
|
|
title: "Images"
|
|
property int currentIndex: 0
|
|
readonly property variant viewpoints: cameraInit.attribute('viewpoints').value
|
|
signal filesDropped(var drop, var augmentSfm)
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
spacing: 4
|
|
|
|
GridView {
|
|
id: grid
|
|
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
|
|
ScrollBar.vertical: ScrollBar {}
|
|
|
|
focus: true
|
|
clip: true
|
|
cellWidth: thumbnailSizeSlider.value
|
|
cellHeight: cellWidth
|
|
highlightFollowsCurrentItem: true
|
|
keyNavigationEnabled: true
|
|
|
|
model: SortFilterDelegateModel {
|
|
id: sortedModel
|
|
model: _reconstruction.viewpoints
|
|
sortRole: "path"
|
|
// TODO: provide filtering on reconstruction status
|
|
// filterRole: _reconstruction.sfmReport ? "reconstructed" : ""
|
|
// filterValue: true / false
|
|
// 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(roleName == sortRole)
|
|
return Filepath.basename(item.model.object.value.get(roleName).value)
|
|
}
|
|
|
|
delegate: ImageDelegate {
|
|
|
|
viewpoint: object.value
|
|
width: grid.cellWidth
|
|
height: grid.cellHeight
|
|
|
|
isCurrentItem: GridView.isCurrentItem
|
|
onPressed: {
|
|
grid.currentIndex = DelegateModel.filteredIndex
|
|
if(mouse.button == Qt.LeftButton)
|
|
grid.forceActiveFocus()
|
|
}
|
|
onRemoveRequest: removeImageRequest(object)
|
|
Keys.onDeletePressed: removeImageRequest(object)
|
|
|
|
// Reconstruction status indicator
|
|
Label {
|
|
id: statusIndicator
|
|
|
|
// object can be evaluated to null at some point during creation/deletion
|
|
property bool inViews: Qt.isQtObject(object) && _reconstruction.sfmReport && _reconstruction.isInViews(object)
|
|
property bool reconstructed: inViews && _reconstruction.isReconstructed(model.object)
|
|
|
|
font.family: MaterialIcons.fontFamily
|
|
text: reconstructed ? MaterialIcons.check_circle : MaterialIcons.remove_circle
|
|
color: reconstructed ? "#4CAF50" : "#F44336"
|
|
anchors.top: parent.top
|
|
anchors.right: parent.right
|
|
anchors.margins: 10
|
|
font.pointSize: 10
|
|
visible: inViews
|
|
}
|
|
}
|
|
}
|
|
|
|
// Keyboard shortcut to change current image group
|
|
Keys.priority: Keys.BeforeItem
|
|
Keys.onPressed: {
|
|
if(event.modifiers & Qt.AltModifier)
|
|
{
|
|
event.accepted = true
|
|
if(event.key == Qt.Key_Right)
|
|
root.currentIndex = Math.min(root.cameraInits.count - 1, root.currentIndex + 1)
|
|
else if(event.key == Qt.Key_Left)
|
|
root.currentIndex = Math.max(0, root.currentIndex - 1)
|
|
}
|
|
}
|
|
|
|
// Explanatory placeholder when no image has been added yet
|
|
Column {
|
|
anchors.centerIn: parent
|
|
visible: grid.model.count == 0
|
|
spacing: 4
|
|
Label {
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
text: MaterialIcons.photo_library
|
|
font.pointSize: 24
|
|
font.family: MaterialIcons.fontFamily
|
|
}
|
|
Label {
|
|
text: "Drop Image Files / Folders"
|
|
}
|
|
}
|
|
|
|
DropArea {
|
|
id: dropArea
|
|
anchors.fill: parent
|
|
enabled: !root.readOnly
|
|
keys: ["text/uri-list"]
|
|
// TODO: onEntered: call specific method to filter files based on extension
|
|
onDropped: {
|
|
var augmentSfm = augmentArea.hovered
|
|
root.filesDropped(drop, augmentSfm)
|
|
}
|
|
|
|
// Background opacifier
|
|
Rectangle {
|
|
visible: dropArea.containsDrag
|
|
anchors.fill: parent
|
|
color: palette.window
|
|
opacity: 0.8
|
|
}
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
visible: dropArea.containsDrag
|
|
spacing: 1
|
|
Label {
|
|
id: addArea
|
|
property bool hovered: dropArea.drag.y < height
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
text: "Add Images"
|
|
font.bold: true
|
|
background: Rectangle {
|
|
color: parent.hovered ? palette.highlight : palette.window
|
|
opacity: 0.8
|
|
border.color: palette.highlight
|
|
}
|
|
}
|
|
|
|
// DropArea overlay
|
|
Label {
|
|
id: augmentArea
|
|
property bool hovered: visible && dropArea.drag.y >= y
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: parent.height * 0.3
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
text: "Augment Reconstruction"
|
|
font.bold: true
|
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
|
visible: viewpoints.count > 0
|
|
background: Rectangle {
|
|
color: parent.hovered ? palette.highlight : palette.window
|
|
opacity: 0.8
|
|
border.color: palette.highlight
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillHeight: false
|
|
visible: root.cameraInits.count > 1
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
spacing: 2
|
|
|
|
ToolButton {
|
|
text: MaterialIcons.navigate_before
|
|
font.family: MaterialIcons.fontFamily
|
|
ToolTip.text: "Previous Group (Alt+Left)"
|
|
ToolTip.visible: hovered
|
|
enabled: nodesCB.currentIndex > 0
|
|
onClicked: nodesCB.decrementCurrentIndex()
|
|
}
|
|
Label { text: "Group " }
|
|
ComboBox {
|
|
id: nodesCB
|
|
model: root.cameraInits.count
|
|
implicitWidth: 40
|
|
currentIndex: root.currentIndex
|
|
onActivated: root.currentIndex = currentIndex
|
|
|
|
}
|
|
Label { text: "/ " + (root.cameraInits.count - 1) }
|
|
ToolButton {
|
|
text: MaterialIcons.navigate_next
|
|
font.family: MaterialIcons.fontFamily
|
|
ToolTip.text: "Next Group (Alt+Right)"
|
|
ToolTip.visible: hovered
|
|
enabled: root.currentIndex < root.cameraInits.count - 1
|
|
onClicked: nodesCB.incrementCurrentIndex()
|
|
}
|
|
}
|
|
}
|
|
|
|
footerContent: RowLayout {
|
|
anchors.fill: parent
|
|
|
|
// Image count
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: grid.model.count + " image" + (grid.model.count > 1 ? "s" : "")
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
// Thumbnail size icon and slider
|
|
Label {
|
|
text: MaterialIcons.photo_size_select_large
|
|
font.family: MaterialIcons.fontFamily
|
|
font.pixelSize: 13
|
|
}
|
|
Slider {
|
|
id: thumbnailSizeSlider
|
|
from: 70
|
|
value: defaultCellSize
|
|
to: 250
|
|
implicitWidth: 70
|
|
height: parent.height
|
|
}
|
|
}
|
|
|
|
}
|