Meshroom/meshroom/ui/qml/Viewer/ImageMetadataView.qml

307 lines
9.8 KiB
QML

import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import MaterialIcons 2.2
import QtPositioning 5.8
import QtLocation 5.9
import Controls 1.0
import Utils 1.0
/**
* ImageMetadataView displays a JSON model representing an image"s metadata as a ListView.
*/
FloatingPane {
id: root
property alias metadata: metadataModel.metadata
property var coordinates: QtPositioning.coordinate()
clip: true
padding: 4
anchors.rightMargin: 0
/**
* Convert GPS metadata to degree coordinates.
*
* GPS coordinates in metadata can be store in 3 forms:
* (degrees), (degrees, minutes), (degrees, minutes, seconds)
*/
function gpsMetadataToCoordinates(value, ref)
{
var values = value.split(",")
var result = 0
for(var i=0; i < values.length; ++i)
{
// divide each component by the corresponding power of 60
// 1 for degree, 60 for minutes, 3600 for seconds
result += Number(values[i]) / Math.pow(60, i)
}
// handle opposite reference: South (latitude) or West (longitude)
return (ref === "S" || ref === "W") ? -result : result
}
/// Try to get GPS coordinates from metadata
function getGPSCoordinates(metadata)
{
// GPS data available
if(metadata && metadata["GPS:Longitude"] != undefined && metadata["GPS:Latitude"] != undefined)
{
var latitude = gpsMetadataToCoordinates(metadata["GPS:Latitude"], metadata["GPS:LatitudeRef"])
var longitude = gpsMetadataToCoordinates(metadata["GPS:Longitude"], metadata["GPS:LongitudeRef"])
var altitude = metadata["GPS:Altitude"] || 0
return QtPositioning.coordinate(latitude, longitude, altitude)
}
// GPS data unavailable: reset coordinates to default value
else
{
return QtPositioning.coordinate()
}
}
// Metadata model
// Available roles for child items:
// - group: metadata group if any, "-" otherwise
// - key: metadata key
// - value: metadata value
// - raw: a sortable/filterable representation of the metadata as "group:key=value"
ListModel {
id: metadataModel
property var metadata: ({})
// reset model when metadata changes
onMetadataChanged: {
metadataModel.clear()
var entries = []
// prepare data to populate the model from the input metadata object
for(var key in metadata)
{
var entry = {}
// split on ":" to get group and key
var i = key.lastIndexOf(":")
if(i == -1)
{
i = key.lastIndexOf("/")
}
if(i != -1)
{
entry["group"] = key.substr(0, i)
entry["key"] = key.substr(i+1)
}
else
{
// set default group to something convenient for sorting
entry["group"] = "-"
entry["key"] = key
}
entry["value"] = metadata[key]
entry["raw"] = entry["group"] + ":" + entry["key"] + "=" + entry["value"]
entries.push(entry)
}
// reset the model with prepared data (limit to one update event)
metadataModel.append(entries)
coordinates = getGPSCoordinates(metadata)
}
}
// Button {
// onClicked: {
// if(sortedMetadataModel.sortOrder == Qt.DescendingOrder)
// sortedMetadataModel.sortOrder = Qt.AscendingOrder
// else
// sortedMetadataModel.sortOrder = Qt.DescendingOrder
// }
// }
// Background WheelEvent grabber
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.MiddleButton
onWheel: wheel.accepted = true
}
// Main Layout
ColumnLayout {
anchors.fill: parent
SearchBar {
id: searchBar
Layout.fillWidth: true
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
Label {
font.family: MaterialIcons.fontFamily
text: MaterialIcons.shutter_speed
}
Label {
id: exposureLabel
text: {
if(metadata["ExposureTime"] === undefined)
return "";
var expStr = metadata["ExposureTime"];
var exp = parseFloat(expStr);
if(exp < 1.0)
{
var invExp = 1.0 / exp;
return "1/" + invExp.toFixed(0);
}
return expStr;
}
elide: Text.ElideRight
horizontalAlignment: Text.AlignHLeft
}
Item { width: 4 }
Label {
font.family: MaterialIcons.fontFamily
text: MaterialIcons.camera
}
Label {
id: fnumberLabel
text: (metadata["FNumber"] !== undefined) ? ("f/" + metadata["FNumber"]) : ""
elide: Text.ElideRight
horizontalAlignment: Text.AlignHLeft
}
Item { width: 4 }
Label {
font.family: MaterialIcons.fontFamily
text: MaterialIcons.iso
}
Label {
id: isoLabel
text: metadata["Exif:ISOSpeedRatings"] || ""
elide: Text.ElideRight
horizontalAlignment: Text.AlignHLeft
}
}
// Metadata ListView
ListView {
id: metadataView
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 3
clip: true
// SortFilter delegate over the metadataModel
model: SortFilterDelegateModel {
id: sortedMetadataModel
model: metadataModel
sortRole: "raw"
filterRole: "raw"
filterValue: searchBar.text
delegate: RowLayout {
width: parent.width
Label {
text: key
leftPadding: 6
rightPadding: 4
Layout.preferredWidth: sizeHandle.x
elide: Text.ElideRight
}
Label {
text: value
Layout.fillWidth: true
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
}
}
}
// Categories resize handle
Rectangle {
id: sizeHandle
height: parent.contentHeight
width: 1
color: root.palette.mid
x: parent.width * 0.4
MouseArea {
anchors.fill: parent
anchors.margins: -4
cursorShape: Qt.SizeHorCursor
drag {
target: parent
axis: Drag.XAxis
threshold: 0
minimumX: metadataView.width * 0.2
maximumX: metadataView.width * 0.8
}
}
}
// Display section based on metadata group
section.property: "group"
section.delegate: Pane {
width: parent.width
padding: 3
background: null
Label {
width: parent.width
padding: 2
background: Rectangle { color: parent.palette.mid }
text: section
}
}
ScrollBar.vertical: ScrollBar{}
}
// Display map if GPS coordinates are available
Loader {
Layout.fillWidth: true
Layout.preferredHeight: coordinates.isValid ? 160 : 0
active: coordinates.isValid
Plugin {
id: osmPlugin
name: "osm"
}
sourceComponent: Map {
id: map
plugin: osmPlugin
center: coordinates
function recenter() {
center = coordinates
}
Connections {
target: root
onCoordinatesChanged: recenter()
}
zoomLevel: 16
// Coordinates visual indicator
MapQuickItem {
coordinate: coordinates
anchorPoint.x: circle.paintedWidth / 2
anchorPoint.y: circle.paintedHeight
sourceItem: Text {
id: circle
color: root.palette.highlight
font.pointSize: 18
font.family: MaterialIcons.fontFamily
text: MaterialIcons.location_on
}
}
// Reset map center
FloatingPane {
anchors.right: parent.right
anchors.top: parent.top
padding: 2
visible: map.center != coordinates
ToolButton {
font.family: MaterialIcons.fontFamily
text: MaterialIcons.my_location
ToolTip.visible: hovered
ToolTip.text: "Recenter"
padding: 0
onClicked: recenter()
}
}
}
}
}
}