6import QGroundControl.AnalyzeView
7import QGroundControl.Controls
11 pageComponent: pageComponent
12 pageDescription: qsTr("Tag images from a survey mission with GPS coordinates from your flight log.")
14 readonly property real _margin: ScreenTools.defaultFontPixelWidth
16 QGCPalette { id: qgcPal; colorGroupEnabled: true }
27 Layout.fillWidth: true
28 Layout.preferredHeight: statusColumn.height + _margin * 2
29 color: qgcPal.windowShade
30 radius: ScreenTools.defaultFontPixelWidth / 2
31 visible: GeoTagController.inProgress || GeoTagController.errorMessage || GeoTagController.taggedCount > 0
35 anchors.left: parent.left
36 anchors.right: parent.right
37 anchors.top: parent.top
38 anchors.margins: _margin
42 Layout.fillWidth: true
44 visible: GeoTagController.inProgress
47 Layout.preferredWidth: ScreenTools.defaultFontPixelHeight * 2
48 Layout.preferredHeight: ScreenTools.defaultFontPixelHeight * 2
49 running: GeoTagController.inProgress
53 Layout.fillWidth: true
57 text: qsTr("Geotagging in progress...")
62 Layout.fillWidth: true
65 value: GeoTagController.progress
71 Layout.fillWidth: true
72 text: GeoTagController.errorMessage
73 color: qgcPal.colorRed
75 wrapMode: Text.WordWrap
76 horizontalAlignment: Text.AlignHCenter
77 visible: GeoTagController.errorMessage && !GeoTagController.inProgress
81 Layout.fillWidth: true
83 if (GeoTagController.taggedCount > 0 && !GeoTagController.inProgress) {
84 let msg = qsTr("Successfully tagged %1 images").arg(GeoTagController.taggedCount)
86 if (GeoTagController.skippedCount > 0) {
87 details.push(qsTr("%1 skipped").arg(GeoTagController.skippedCount))
89 if (GeoTagController.failedCount > 0) {
90 details.push(qsTr("%1 failed").arg(GeoTagController.failedCount))
92 if (details.length > 0) {
93 msg += " (" + details.join(", ") + ")"
99 color: GeoTagController.failedCount > 0 ? qgcPal.colorOrange : qgcPal.colorGreen
101 horizontalAlignment: Text.AlignHCenter
102 visible: GeoTagController.taggedCount > 0 && !GeoTagController.inProgress
109 Layout.fillWidth: true
110 Layout.preferredHeight: step1Column.height + _margin * 2
111 color: qgcPal.windowShade
112 radius: ScreenTools.defaultFontPixelWidth / 2
116 anchors.left: parent.left
117 anchors.right: parent.right
118 anchors.top: parent.top
119 anchors.margins: _margin
123 Layout.fillWidth: true
127 Layout.preferredWidth: ScreenTools.defaultFontPixelHeight * 1.5
128 Layout.preferredHeight: ScreenTools.defaultFontPixelHeight * 1.5
130 color: GeoTagController.logFile ? qgcPal.colorGreen : qgcPal.button
133 anchors.centerIn: parent
134 text: GeoTagController.logFile ? "\u2713" : "1"
135 color: GeoTagController.logFile ? "white" : qgcPal.buttonText
141 text: qsTr("Select Flight Log")
147 Layout.fillWidth: true
151 text: qsTr("Browse...")
152 enabled: !GeoTagController.inProgress
153 onClicked: openLogFile.openForLoad()
157 title: qsTr("Select Flight Log")
158 nameFilters: [qsTr("Flight logs (*.ulg *.bin)"), qsTr("ULog (*.ulg)"), qsTr("DataFlash (*.bin)"), qsTr("All Files (*)")]
160 onAcceptedForLoad: (file) => {
161 GeoTagController.logFile = file
168 Layout.fillWidth: true
169 text: GeoTagController.logFile ? GeoTagController.logFile : qsTr("No file selected")
170 elide: Text.ElideMiddle
171 opacity: GeoTagController.logFile ? 1.0 : 0.5
177 // Step 2: Image Directory
179 Layout.fillWidth: true
180 Layout.preferredHeight: step2Column.height + _margin * 2
181 color: qgcPal.windowShade
182 radius: ScreenTools.defaultFontPixelWidth / 2
186 anchors.left: parent.left
187 anchors.right: parent.right
188 anchors.top: parent.top
189 anchors.margins: _margin
193 Layout.fillWidth: true
197 Layout.preferredWidth: ScreenTools.defaultFontPixelHeight * 1.5
198 Layout.preferredHeight: ScreenTools.defaultFontPixelHeight * 1.5
200 color: GeoTagController.imageDirectory ? qgcPal.colorGreen : qgcPal.button
203 anchors.centerIn: parent
204 text: GeoTagController.imageDirectory ? "\u2713" : "2"
205 color: GeoTagController.imageDirectory ? "white" : qgcPal.buttonText
211 text: qsTr("Select Image Folder")
217 Layout.fillWidth: true
221 text: qsTr("Browse...")
222 enabled: !GeoTagController.inProgress
223 onClicked: selectImageDir.openForLoad()
227 title: qsTr("Select Image Folder")
229 onAcceptedForLoad: (file) => {
230 GeoTagController.imageDirectory = file
237 Layout.fillWidth: true
238 text: GeoTagController.imageDirectory ? GeoTagController.imageDirectory : qsTr("No folder selected")
239 elide: Text.ElideMiddle
240 opacity: GeoTagController.imageDirectory ? 1.0 : 0.5
246 // Step 3: Save Directory (Optional)
248 Layout.fillWidth: true
249 Layout.preferredHeight: step3Column.height + _margin * 2
250 color: qgcPal.windowShade
251 radius: ScreenTools.defaultFontPixelWidth / 2
255 anchors.left: parent.left
256 anchors.right: parent.right
257 anchors.top: parent.top
258 anchors.margins: _margin
262 Layout.fillWidth: true
266 Layout.preferredWidth: ScreenTools.defaultFontPixelHeight * 1.5
267 Layout.preferredHeight: ScreenTools.defaultFontPixelHeight * 1.5
272 anchors.centerIn: parent
274 color: qgcPal.buttonText
280 text: qsTr("Output Folder (Optional)")
286 Layout.fillWidth: true
290 text: qsTr("Browse...")
291 enabled: !GeoTagController.inProgress
292 onClicked: selectDestDir.openForLoad()
296 title: qsTr("Select Output Folder")
298 onAcceptedForLoad: (file) => {
299 GeoTagController.saveDirectory = file
306 Layout.fillWidth: true
308 if (GeoTagController.saveDirectory) {
309 return GeoTagController.saveDirectory
310 } else if (GeoTagController.imageDirectory) {
311 return GeoTagController.imageDirectory + "/TAGGED"
313 return qsTr("Default: /TAGGED subfolder")
315 elide: Text.ElideMiddle
316 opacity: GeoTagController.saveDirectory ? 1.0 : 0.5
324 Layout.fillWidth: true
325 Layout.preferredHeight: advancedColumn.height + _margin * 2
326 color: qgcPal.windowShade
327 radius: ScreenTools.defaultFontPixelWidth / 2
331 anchors.left: parent.left
332 anchors.right: parent.right
333 anchors.top: parent.top
334 anchors.margins: _margin
338 text: qsTr("Advanced Options")
343 Layout.fillWidth: true
347 text: qsTr("Time Offset (seconds):")
352 Layout.preferredWidth: ScreenTools.defaultFontPixelWidth * 10
353 text: GeoTagController.timeOffsetSecs.toFixed(1)
354 enabled: !GeoTagController.inProgress
355 inputMethodHints: Qt.ImhFormattedNumbersOnly
356 validator: DoubleValidator { bottom: -3600; top: 3600; decimals: 1 }
357 onEditingFinished: GeoTagController.timeOffsetSecs = parseFloat(text) || 0
361 Layout.fillWidth: true
362 text: qsTr("Adjust if camera clock differs from flight log")
364 font.pointSize: ScreenTools.smallFontPointSize
369 Layout.fillWidth: true
374 text: qsTr("Preview mode (don't write files)")
375 checked: GeoTagController.previewMode
376 enabled: !GeoTagController.inProgress
377 onClicked: GeoTagController.previewMode = checked
381 Layout.fillWidth: true
382 text: qsTr("Verify time offset before committing")
384 font.pointSize: ScreenTools.smallFontPointSize
392 Layout.alignment: Qt.AlignHCenter
393 Layout.preferredWidth: ScreenTools.defaultFontPixelWidth * 20
395 if (GeoTagController.inProgress) {
396 return qsTr("Cancel")
397 } else if (GeoTagController.previewMode) {
398 return qsTr("Preview")
400 return qsTr("Start Tagging")
403 enabled: (GeoTagController.logFile && GeoTagController.imageDirectory) || GeoTagController.inProgress
405 if (GeoTagController.inProgress) {
406 GeoTagController.cancelTagging()
408 GeoTagController.startTagging()
415 Layout.fillWidth: true
416 Layout.fillHeight: true
417 Layout.minimumHeight: ScreenTools.defaultFontPixelHeight * 10
418 color: qgcPal.windowShade
419 radius: ScreenTools.defaultFontPixelWidth / 2
420 visible: GeoTagController.imageModel.count > 0
424 anchors.margins: _margin
428 Layout.fillWidth: true
432 text: qsTr("Images (%1)").arg(GeoTagController.imageModel.count)
436 Item { Layout.fillWidth: true }
444 Rectangle { width: 10; height: 10; radius: 2; color: qgcPal.text; opacity: 0.5 }
445 QGCLabel { text: qsTr("Pending"); font.pointSize: ScreenTools.smallFontPointSize }
449 Rectangle { width: 10; height: 10; radius: 2; color: qgcPal.colorBlue }
450 QGCLabel { text: qsTr("Processing"); font.pointSize: ScreenTools.smallFontPointSize }
454 Rectangle { width: 10; height: 10; radius: 2; color: qgcPal.colorGreen }
455 QGCLabel { text: qsTr("Tagged"); font.pointSize: ScreenTools.smallFontPointSize }
459 Rectangle { width: 10; height: 10; radius: 2; color: qgcPal.colorOrange }
460 QGCLabel { text: qsTr("Skipped"); font.pointSize: ScreenTools.smallFontPointSize }
464 Rectangle { width: 10; height: 10; radius: 2; color: qgcPal.colorRed }
465 QGCLabel { text: qsTr("Failed"); font.pointSize: ScreenTools.smallFontPointSize }
471 Layout.fillWidth: true
479 Layout.fillWidth: true
480 Layout.fillHeight: true
481 model: GeoTagController.imageModel
483 delegate: Rectangle {
484 width: imageListView.width
485 height: ScreenTools.defaultFontPixelHeight * 2
486 color: index % 2 === 0 ? "transparent" : qgcPal.window
487 radius: ScreenTools.defaultFontPixelWidth / 4
491 anchors.leftMargin: _margin / 2
492 anchors.rightMargin: _margin / 2
497 Layout.preferredWidth: ScreenTools.defaultFontPixelHeight * 0.8
498 Layout.preferredHeight: ScreenTools.defaultFontPixelHeight * 0.8
499 radius: ScreenTools.defaultFontPixelWidth / 4
501 switch (model.status) {
502 case 0: return qgcPal.text // Pending
503 case 1: return qgcPal.colorBlue // Processing
504 case 2: return qgcPal.colorGreen // Tagged
505 case 3: return qgcPal.colorOrange // Skipped
506 case 4: return qgcPal.colorRed // Failed
507 default: return qgcPal.text
510 opacity: model.status === 0 ? 0.5 : 1.0
512 // Processing animation
513 SequentialAnimation on opacity {
514 running: model.status === 1
515 loops: Animation.Infinite
516 NumberAnimation { to: 0.3; duration: 500 }
517 NumberAnimation { to: 1.0; duration: 500 }
523 Layout.fillWidth: true
525 elide: Text.ElideMiddle
528 // Coordinate (if tagged)
530 Layout.preferredWidth: ScreenTools.defaultFontPixelWidth * 20
532 if (model.coordinate && model.coordinate.isValid) {
533 return model.coordinate.latitude.toFixed(6) + ", " + model.coordinate.longitude.toFixed(6)
537 font.pointSize: ScreenTools.smallFontPointSize
539 visible: model.status === 2
542 // Status text or error
544 Layout.preferredWidth: ScreenTools.defaultFontPixelWidth * 12
545 text: model.errorMessage ? model.errorMessage : model.statusString
546 font.pointSize: ScreenTools.smallFontPointSize
547 color: model.status === 4 ? qgcPal.colorRed : (model.status === 3 ? qgcPal.colorOrange : qgcPal.text)
548 elide: Text.ElideRight
549 horizontalAlignment: Text.AlignRight