6import QGroundControl.Controls
10 pageComponent: pageComponent
11 pageDescription: qsTr("Tag images from a survey mission with GPS coordinates from your flight log.")
13 readonly property real _margin: ScreenTools.defaultFontPixelWidth
15 QGCPalette { id: qgcPal; colorGroupEnabled: true }
26 Layout.fillWidth: true
27 Layout.preferredHeight: statusColumn.height + _margin * 2
28 color: qgcPal.windowShade
29 radius: ScreenTools.defaultFontPixelWidth / 2
30 visible: geoController.inProgress || geoController.errorMessage || geoController.taggedCount > 0
34 anchors.left: parent.left
35 anchors.right: parent.right
36 anchors.top: parent.top
37 anchors.margins: _margin
41 Layout.fillWidth: true
43 visible: geoController.inProgress
46 Layout.preferredWidth: ScreenTools.defaultFontPixelHeight * 2
47 Layout.preferredHeight: ScreenTools.defaultFontPixelHeight * 2
48 running: geoController.inProgress
52 Layout.fillWidth: true
56 text: qsTr("Geotagging in progress...")
61 Layout.fillWidth: true
64 value: geoController.progress
70 Layout.fillWidth: true
71 text: geoController.errorMessage
72 color: qgcPal.colorRed
74 wrapMode: Text.WordWrap
75 horizontalAlignment: Text.AlignHCenter
76 visible: geoController.errorMessage && !geoController.inProgress
80 Layout.fillWidth: true
82 if (geoController.taggedCount > 0 && !geoController.inProgress) {
83 let msg = qsTr("Successfully tagged %1 images").arg(geoController.taggedCount)
85 if (geoController.skippedCount > 0) {
86 details.push(qsTr("%1 skipped").arg(geoController.skippedCount))
88 if (geoController.failedCount > 0) {
89 details.push(qsTr("%1 failed").arg(geoController.failedCount))
91 if (details.length > 0) {
92 msg += " (" + details.join(", ") + ")"
98 color: geoController.failedCount > 0 ? qgcPal.colorOrange : qgcPal.colorGreen
100 horizontalAlignment: Text.AlignHCenter
101 visible: geoController.taggedCount > 0 && !geoController.inProgress
108 Layout.fillWidth: true
109 Layout.preferredHeight: step1Column.height + _margin * 2
110 color: qgcPal.windowShade
111 radius: ScreenTools.defaultFontPixelWidth / 2
115 anchors.left: parent.left
116 anchors.right: parent.right
117 anchors.top: parent.top
118 anchors.margins: _margin
122 Layout.fillWidth: true
126 Layout.preferredWidth: ScreenTools.defaultFontPixelHeight * 1.5
127 Layout.preferredHeight: ScreenTools.defaultFontPixelHeight * 1.5
129 color: geoController.logFile ? qgcPal.colorGreen : qgcPal.button
132 anchors.centerIn: parent
133 text: geoController.logFile ? "\u2713" : "1"
134 color: geoController.logFile ? "white" : qgcPal.buttonText
140 text: qsTr("Select Flight Log")
146 Layout.fillWidth: true
150 text: qsTr("Browse...")
151 enabled: !geoController.inProgress
152 onClicked: openLogFile.openForLoad()
156 title: qsTr("Select Flight Log")
157 nameFilters: [qsTr("Flight logs (*.ulg *.bin)"), qsTr("ULog (*.ulg)"), qsTr("DataFlash (*.bin)"), qsTr("All Files (*)")]
159 onAcceptedForLoad: (file) => {
160 geoController.logFile = file
167 Layout.fillWidth: true
168 text: geoController.logFile ? geoController.logFile : qsTr("No file selected")
169 elide: Text.ElideMiddle
170 opacity: geoController.logFile ? 1.0 : 0.5
176 // Step 2: Image Directory
178 Layout.fillWidth: true
179 Layout.preferredHeight: step2Column.height + _margin * 2
180 color: qgcPal.windowShade
181 radius: ScreenTools.defaultFontPixelWidth / 2
185 anchors.left: parent.left
186 anchors.right: parent.right
187 anchors.top: parent.top
188 anchors.margins: _margin
192 Layout.fillWidth: true
196 Layout.preferredWidth: ScreenTools.defaultFontPixelHeight * 1.5
197 Layout.preferredHeight: ScreenTools.defaultFontPixelHeight * 1.5
199 color: geoController.imageDirectory ? qgcPal.colorGreen : qgcPal.button
202 anchors.centerIn: parent
203 text: geoController.imageDirectory ? "\u2713" : "2"
204 color: geoController.imageDirectory ? "white" : qgcPal.buttonText
210 text: qsTr("Select Image Folder")
216 Layout.fillWidth: true
220 text: qsTr("Browse...")
221 enabled: !geoController.inProgress
222 onClicked: selectImageDir.openForLoad()
226 title: qsTr("Select Image Folder")
228 onAcceptedForLoad: (file) => {
229 geoController.imageDirectory = file
236 Layout.fillWidth: true
237 text: geoController.imageDirectory ? geoController.imageDirectory : qsTr("No folder selected")
238 elide: Text.ElideMiddle
239 opacity: geoController.imageDirectory ? 1.0 : 0.5
245 // Step 3: Save Directory (Optional)
247 Layout.fillWidth: true
248 Layout.preferredHeight: step3Column.height + _margin * 2
249 color: qgcPal.windowShade
250 radius: ScreenTools.defaultFontPixelWidth / 2
254 anchors.left: parent.left
255 anchors.right: parent.right
256 anchors.top: parent.top
257 anchors.margins: _margin
261 Layout.fillWidth: true
265 Layout.preferredWidth: ScreenTools.defaultFontPixelHeight * 1.5
266 Layout.preferredHeight: ScreenTools.defaultFontPixelHeight * 1.5
271 anchors.centerIn: parent
273 color: qgcPal.buttonText
279 text: qsTr("Output Folder (Optional)")
285 Layout.fillWidth: true
289 text: qsTr("Browse...")
290 enabled: !geoController.inProgress
291 onClicked: selectDestDir.openForLoad()
295 title: qsTr("Select Output Folder")
297 onAcceptedForLoad: (file) => {
298 geoController.saveDirectory = file
305 Layout.fillWidth: true
307 if (geoController.saveDirectory) {
308 return geoController.saveDirectory
309 } else if (geoController.imageDirectory) {
310 return geoController.imageDirectory + "/TAGGED"
312 return qsTr("Default: /TAGGED subfolder")
314 elide: Text.ElideMiddle
315 opacity: geoController.saveDirectory ? 1.0 : 0.5
323 Layout.fillWidth: true
324 Layout.preferredHeight: advancedColumn.height + _margin * 2
325 color: qgcPal.windowShade
326 radius: ScreenTools.defaultFontPixelWidth / 2
330 anchors.left: parent.left
331 anchors.right: parent.right
332 anchors.top: parent.top
333 anchors.margins: _margin
337 text: qsTr("Advanced Options")
342 Layout.fillWidth: true
346 text: qsTr("Time Offset (seconds):")
351 Layout.preferredWidth: ScreenTools.defaultFontPixelWidth * 10
352 text: geoController.timeOffsetSecs.toFixed(1)
353 enabled: !geoController.inProgress
354 inputMethodHints: Qt.ImhFormattedNumbersOnly
355 validator: DoubleValidator { bottom: -3600; top: 3600; decimals: 1 }
356 onEditingFinished: geoController.timeOffsetSecs = parseFloat(text) || 0
360 Layout.fillWidth: true
361 text: qsTr("Adjust if camera clock differs from flight log")
363 font.pointSize: ScreenTools.smallFontPointSize
368 Layout.fillWidth: true
373 text: qsTr("Preview mode (don't write files)")
374 checked: geoController.previewMode
375 enabled: !geoController.inProgress
376 onClicked: geoController.previewMode = checked
380 Layout.fillWidth: true
381 text: qsTr("Verify time offset before committing")
383 font.pointSize: ScreenTools.smallFontPointSize
391 Layout.alignment: Qt.AlignHCenter
392 Layout.preferredWidth: ScreenTools.defaultFontPixelWidth * 20
394 if (geoController.inProgress) {
395 return qsTr("Cancel")
396 } else if (geoController.previewMode) {
397 return qsTr("Preview")
399 return qsTr("Start Tagging")
402 enabled: (geoController.logFile && geoController.imageDirectory) || geoController.inProgress
404 if (geoController.inProgress) {
405 geoController.cancelTagging()
407 geoController.startTagging()
414 Layout.fillWidth: true
415 Layout.fillHeight: true
416 Layout.minimumHeight: ScreenTools.defaultFontPixelHeight * 10
417 color: qgcPal.windowShade
418 radius: ScreenTools.defaultFontPixelWidth / 2
419 visible: geoController.imageModel.count > 0
423 anchors.margins: _margin
427 Layout.fillWidth: true
431 text: qsTr("Images (%1)").arg(geoController.imageModel.count)
435 Item { Layout.fillWidth: true }
443 Rectangle { width: 10; height: 10; radius: 2; color: qgcPal.text; opacity: 0.5 }
444 QGCLabel { text: qsTr("Pending"); font.pointSize: ScreenTools.smallFontPointSize }
448 Rectangle { width: 10; height: 10; radius: 2; color: qgcPal.colorBlue }
449 QGCLabel { text: qsTr("Processing"); font.pointSize: ScreenTools.smallFontPointSize }
453 Rectangle { width: 10; height: 10; radius: 2; color: qgcPal.colorGreen }
454 QGCLabel { text: qsTr("Tagged"); font.pointSize: ScreenTools.smallFontPointSize }
458 Rectangle { width: 10; height: 10; radius: 2; color: qgcPal.colorOrange }
459 QGCLabel { text: qsTr("Skipped"); font.pointSize: ScreenTools.smallFontPointSize }
463 Rectangle { width: 10; height: 10; radius: 2; color: qgcPal.colorRed }
464 QGCLabel { text: qsTr("Failed"); font.pointSize: ScreenTools.smallFontPointSize }
470 Layout.fillWidth: true
478 Layout.fillWidth: true
479 Layout.fillHeight: true
480 model: geoController.imageModel
482 delegate: Rectangle {
483 width: imageListView.width
484 height: ScreenTools.defaultFontPixelHeight * 2
485 color: index % 2 === 0 ? "transparent" : qgcPal.window
486 radius: ScreenTools.defaultFontPixelWidth / 4
490 anchors.leftMargin: _margin / 2
491 anchors.rightMargin: _margin / 2
496 Layout.preferredWidth: ScreenTools.defaultFontPixelHeight * 0.8
497 Layout.preferredHeight: ScreenTools.defaultFontPixelHeight * 0.8
498 radius: ScreenTools.defaultFontPixelWidth / 4
500 switch (model.status) {
501 case 0: return qgcPal.text // Pending
502 case 1: return qgcPal.colorBlue // Processing
503 case 2: return qgcPal.colorGreen // Tagged
504 case 3: return qgcPal.colorOrange // Skipped
505 case 4: return qgcPal.colorRed // Failed
506 default: return qgcPal.text
509 opacity: model.status === 0 ? 0.5 : 1.0
511 // Processing animation
512 SequentialAnimation on opacity {
513 running: model.status === 1
514 loops: Animation.Infinite
515 NumberAnimation { to: 0.3; duration: 500 }
516 NumberAnimation { to: 1.0; duration: 500 }
522 Layout.fillWidth: true
524 elide: Text.ElideMiddle
527 // Coordinate (if tagged)
529 Layout.preferredWidth: ScreenTools.defaultFontPixelWidth * 20
531 if (model.coordinate && model.coordinate.isValid) {
532 return model.coordinate.latitude.toFixed(6) + ", " + model.coordinate.longitude.toFixed(6)
536 font.pointSize: ScreenTools.smallFontPointSize
538 visible: model.status === 2
541 // Status text or error
543 Layout.preferredWidth: ScreenTools.defaultFontPixelWidth * 12
544 text: model.errorMessage ? model.errorMessage : model.statusString
545 font.pointSize: ScreenTools.smallFontPointSize
546 color: model.status === 4 ? qgcPal.colorRed : (model.status === 3 ? qgcPal.colorOrange : qgcPal.text)
547 elide: Text.ElideRight
548 horizontalAlignment: Text.AlignRight