QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
PhotoVideoControl.qml
Go to the documentation of this file.
1import QtQuick
2import QtPositioning
3import QtQuick.Layouts
4import QtQuick.Controls
5import QtQuick.Dialogs
6
7import QGroundControl
8import QGroundControl.Controls
9import QGroundControl.FactControls
10
11Rectangle {
12 id: photoVideoControl
13 width: mainLayout.width + (_smallMargins * 2)
14 height: mainLayout.height + (_smallMargins * 2)
15 color: Qt.rgba(qgcPal.window.r, qgcPal.window.g, qgcPal.window.b, 0.5)
16 radius: _margins
17 visible: _camera.capturesVideo || _camera.capturesPhotos || _camera.hasTracking || _camera.hasVideoStream
18
19 property real _margins: ScreenTools.defaultFontPixelHeight / 2
20 property real _smallMargins: ScreenTools.defaultFontPixelWidth / 2
21 property var _activeVehicle: globals.activeVehicle
22 property var _cameraManager: _activeVehicle.cameraManager
23 property var _camera: _cameraManager.currentCameraInstance
24 property bool _cameraInPhotoMode: _camera.cameraMode === MavlinkCameraControl.CAM_MODE_PHOTO || _camera.cameraMode === MavlinkCameraControl.CAM_MODE_SURVEY
25 property bool _cameraInVideoMode: !_cameraInPhotoMode
26 property bool _videoCaptureIdle: _camera.captureVideoState === MavlinkCameraControl.CaptureVideoStateIdle
27 property bool _photoCaptureIdle: _camera.capturePhotosState === MavlinkCameraControl.CapturePhotosStateIdle
28
29 QGCPalette { id: qgcPal; colorGroupEnabled: enabled }
30
31 DeadMouseArea { anchors.fill: parent }
32
33 RowLayout {
34 id: mainLayout
35 anchors.margins: _smallMargins
36 anchors.top: parent.top
37 anchors.left: parent.left
38 spacing: _margins
39
40 ColumnLayout {
41 Layout.fillHeight: true
42 spacing: 0
43 visible: _camera.hasZoom
44
45 QGCLabel {
46 Layout.alignment: Qt.AlignHCenter
47 text: qsTr("Zoom")
48 font.pointSize: ScreenTools.smallFontPointSize
49 }
50
51 QGCSlider {
52 Layout.alignment: Qt.AlignHCenter
53 Layout.fillHeight: true
54 orientation: Qt.Vertical
55 to: 100
56 from: 0
57 value: _camera.zoomLevel
58 live: true
59 onValueChanged: _camera.zoomLevel = value
60 }
61 }
62
63 ColumnLayout {
64 spacing: _margins
65
66 // Camera name
67 QGCLabel {
68 Layout.alignment: Qt.AlignHCenter
69 text: _camera.modelName
70 visible: _cameraManager.cameras.length > 1
71 }
72
73 // Photo/Video Mode Selector
74 Rectangle {
75 Layout.alignment: Qt.AlignHCenter
76 width: ScreenTools.defaultFontPixelWidth * 10
77 height: width / 2
78 color: qgcPal.windowShadeLight
79 radius: height * 0.5
80 visible: _camera.hasModes
81
82 //-- Video Mode
83 Rectangle {
84 anchors.verticalCenter: parent.verticalCenter
85 width: parent.height
86 height: parent.height
87 color: _cameraInVideoMode ? qgcPal.window : qgcPal.windowShadeLight
88 radius: height * 0.5
89 anchors.left: parent.left
90 border.color: qgcPal.text
91 border.width: _cameraInPhotoMode ? 0 : 1
92
93 QGCColoredImage {
94 height: parent.height * 0.5
95 width: height
96 anchors.centerIn: parent
97 source: "/qmlimages/camera_video.svg"
98 fillMode: Image.PreserveAspectFit
99 sourceSize.height: height
100 color: _cameraInVideoMode ? qgcPal.colorGreen : qgcPal.text
101
102 MouseArea {
103 anchors.fill: parent
104 enabled: _cameraInPhotoMode ? _photoCaptureIdle : true
105 onClicked: _camera.setCameraModeVideo()
106 }
107 }
108 }
109
110 //-- Photo Mode
111 Rectangle {
112 anchors.verticalCenter: parent.verticalCenter
113 width: parent.height
114 height: parent.height
115 color: _cameraInPhotoMode ? qgcPal.window : qgcPal.windowShadeLight
116 radius: height * 0.5
117 anchors.right: parent.right
118 border.color: qgcPal.text
119 border.width: _cameraInPhotoMode ? 1 : 0
120
121 QGCColoredImage {
122 height: parent.height * 0.5
123 width: height
124 anchors.centerIn: parent
125 source: "/qmlimages/camera_photo.svg"
126 fillMode: Image.PreserveAspectFit
127 sourceSize.height: height
128 color: _cameraInPhotoMode ? qgcPal.colorGreen : qgcPal.text
129
130 MouseArea {
131 anchors.fill: parent
132 enabled: _cameraInVideoMode ? _videoCaptureIdle : true
133 onClicked: _camera.setCameraModePhoto()
134 }
135 }
136 }
137 }
138
139 ColumnLayout {
140 Layout.alignment: Qt.AlignHCenter
141 spacing: _smallMargins
142
143 // Start/Stop Video button
144 Rectangle {
145 id: videoCaptureButton
146 Layout.alignment: Qt.AlignHCenter
147 color: videoCaptureButtonPalette.button
148 width: ScreenTools.defaultFontPixelWidth * 6
149 height: width
150 radius: width * 0.5
151 border.width: 1
152 border.color: videoCaptureButtonPalette.buttonBorder
153 visible: (_camera.hasModes && _cameraInVideoMode) || (!_camera.hasModes && _camera.capturesVideo)
154 enabled: _camera.captureVideoState !== MavlinkCameraControl.CaptureVideoStateDisabled
155
156 QGCPalette { id: videoCaptureButtonPalette; colorGroupEnabled: videoCaptureButton.enabled }
157
158 Rectangle {
159 anchors.centerIn: parent
160 anchors.alignWhenCentered: false // Prevents anchors.centerIn from snapping to integer coordinates, which can throw off centering.
161 color: videoCaptureButtonPalette.buttonBorder
162 width: parent.width * 0.75
163 height: width
164 radius: width * 0.5
165 }
166
167 Rectangle {
168 anchors.centerIn: parent
169 anchors.alignWhenCentered: false // Prevents anchors.centerIn from snapping to integer coordinates, which can throw off centering.
170 width: parent.width * (_isCapturing ? 0.5 : 0.75)
171 height: width
172 radius: _isCapturing ? ScreenTools.defaultFontPixelWidth * 0.5 : width * 0.5
173 color: videoCaptureButtonPalette.videoCaptureButtonColor
174 border.width: 1
175 border.color: videoCaptureButtonPalette.buttonBorder
176
177 property bool _isCapturing: _camera.captureVideoState === MavlinkCameraControl.CaptureVideoStateCapturing
178 }
179
180 MouseArea {
181 anchors.fill: parent
182 onClicked: _camera.toggleVideoRecording()
183 }
184 }
185
186 QGCLabel {
187 Layout.alignment: Qt.AlignHCenter
188 text: qsTr("Video")
189 font.pointSize: ScreenTools.smallFontPointSize
190 visible: videoCaptureButton.visible && photoCaptureButton.visible
191 }
192
193 // Record time
194 Rectangle {
195 Layout.alignment: Qt.AlignHCenter
196 color: _videoCaptureIdle ? "transparent" : videoCaptureButtonPalette.videoCaptureButtonColor
197 Layout.preferredWidth: videoRecordTime.width + (_smallMargins * 2)
198 Layout.preferredHeight: videoRecordTime.height
199 radius: _smallMargins
200 visible: videoCaptureButton.visible
201
202 // Video record time
203 QGCLabel {
204 id: videoRecordTime
205 anchors.leftMargin: _smallMargins
206 anchors.left: parent.left
207 anchors.top: parent.top
208 text: _videoCaptureIdle ? "00:00:00" : _camera.recordTimeStr
209 }
210 }
211
212 Item {
213 Layout.alignment: Qt.AlignHCenter
214 width: 1
215 height: 1
216 visible: videoCaptureButton.visible && photoCaptureButton.visible
217 }
218
219 // Take Photo button
220 Rectangle {
221 id: photoCaptureButton
222 Layout.alignment: Qt.AlignHCenter
223 color: photoCaptureButtonPalette.button
224 width: ScreenTools.defaultFontPixelWidth * 6
225 height: width
226 radius: width * 0.5
227 border.width: 1
228 border.color: photoCaptureButtonPalette.buttonBorder
229 visible: (_camera.hasModes && _cameraInPhotoMode) || (!_camera.hasModes && (_camera.hasVideoStream || _camera.capturesPhotos))
230 enabled: _camera.capturePhotosState !== MavlinkCameraControl.CapturePhotosStateDisabled
231
232 QGCPalette { id: photoCaptureButtonPalette; colorGroupEnabled: photoCaptureButton.enabled }
233
234 Rectangle {
235 anchors.centerIn: parent
236 anchors.alignWhenCentered: false // Prevents anchors.centerIn from snapping to integer coordinates, which can throw off centering.
237 color: photoCaptureButtonPalette.buttonBorder
238 width: parent.width * 0.75
239 height: width
240 radius: width * 0.5
241 }
242
243 Rectangle {
244 anchors.centerIn: parent
245 anchors.alignWhenCentered: false // Prevents anchors.centerIn from snapping to integer coordinates, which can throw off centering.
246 width: parent.width * (_isCapturing ? 0.5 : 0.75)
247 height: width
248 radius: _isCapturing ? ScreenTools.defaultFontPixelWidth * 0.5 : width * 0.5
249 color: photoCaptureButtonPalette.photoCaptureButtonColor
250 border.width: 1
251 border.color: photoCaptureButtonPalette.buttonBorder
252
253 property bool _isCapturing: _camera.capturePhotosState === MavlinkCameraControl.CapturePhotosStateCapturingSinglePhoto ||
254 _camera.capturePhotosState === MavlinkCameraControl.CapturePhotosStateCapturingMultiplePhotos
255 }
256
257 MouseArea {
258 anchors.fill: parent
259 onClicked: {
260 if (_camera.capturePhotosState === MavlinkCameraControl.CapturePhotosStateCapturingMultiplePhotos) {
261 _camera.stopTakePhoto()
262 } else if (_camera.capturePhotosState === MavlinkCameraControl.CapturePhotosStateIdle) {
263 _camera.takePhoto()
264 }
265 }
266 }
267 }
268
269 QGCLabel {
270 Layout.alignment: Qt.AlignHCenter
271 text: qsTr("Photo")
272 font.pointSize: ScreenTools.smallFontPointSize
273 visible: videoCaptureButton.visible && photoCaptureButton.visible
274 }
275
276 // Capture count
277 Rectangle {
278 Layout.alignment: Qt.AlignHCenter
279 color: _photoCaptureIdle ? "transparent" : photoCaptureButtonPalette.photoCaptureButtonColor
280 Layout.preferredWidth: photoCaptureCount.width + (_smallMargins * 2)
281 Layout.preferredHeight: photoCaptureCount.height
282 radius: _smallMargins
283 visible: photoCaptureButton.visible
284
285 // Photo capture count
286 QGCLabel {
287 id: photoCaptureCount
288 anchors.leftMargin: _smallMargins
289 anchors.left: parent.left
290 anchors.top: parent.top
291 text: _activeVehicle ? ('00000' + _activeVehicle.cameraTriggerPoints.count).slice(-5) : "00000"
292 }
293 }
294 }
295
296 //-- Status Information
297 ColumnLayout {
298 Layout.alignment: Qt.AlignHCenter
299 spacing: 0
300 visible: storageStatus.visible || batteryStatus.visible
301
302 QGCLabel {
303 id: storageStatus
304 Layout.alignment: Qt.AlignHCenter
305 text: qsTr("Free: ") + _camera.storageFreeStr
306 font.pointSize: ScreenTools.defaultFontPointSize
307 visible: _camera.storageStatus === MavlinkCameraControl.STORAGE_READY
308 }
309
310 QGCLabel {
311 id: batteryStatus
312 Layout.alignment: Qt.AlignHCenter
313 text: qsTr("Battery: ") + _camera.batteryRemainingStr
314 font.pointSize: ScreenTools.defaultFontPointSize
315 visible: _camera.batteryRemaining >= 0
316 }
317 }
318
319 ColumnLayout {
320 id: trackingControls
321 Layout.alignment: Qt.AlignHCenter
322 spacing: 0
323 visible: _camera.hasTracking
324
325 Rectangle {
326 Layout.alignment: Qt.AlignHCenter
327 color: _camera.trackingEnabled ? qgcPal.colorRed : qgcPal.windowShadeLight
328 Layout.preferredWidth: ScreenTools.defaultFontPixelWidth * 6
329 Layout.preferredHeight: Layout.preferredWidth
330 border.color: qgcPal.buttonText
331 border.width: 3
332
333 QGCColoredImage {
334 height: parent.height * 0.5
335 width: height
336 anchors.centerIn: parent
337 source: "/qmlimages/TrackingIcon.svg"
338 fillMode: Image.PreserveAspectFit
339 sourceSize.height: height
340 color: qgcPal.text
341
342 MouseArea {
343 anchors.fill: parent
344 onClicked: {
345 _camera.trackingEnabled = !_camera.trackingEnabled;
346 if (!_camera.trackingEnabled) {
347 _camera.stopTracking()
348 }
349 }
350 }
351 }
352 }
353
354 QGCLabel {
355 Layout.alignment: Qt.AlignHCenter
356 text: qsTr("Camera Tracking")
357 font.pointSize: ScreenTools.smallFontPointSize
358 }
359 }
360
361 QGCColoredImage {
362 Layout.alignment: Qt.AlignHCenter
363 source: "/res/gear-black.svg"
364 mipmap: true
365 Layout.preferredHeight: ScreenTools.defaultFontPixelHeight * 1.5
366 Layout.preferredWidth: Layout.preferredHeight
367 sourceSize.height: Layout.preferredHeight
368 color: qgcPal.text
369 fillMode: Image.PreserveAspectFit
370
371 QGCMouseArea {
372 fillItem: parent
373 onClicked: settingsDialogFactory.open()
374 }
375 }
376 }
377
378 QGCPopupDialogFactory {
379 id: settingsDialogFactory
380
381 dialogComponent: settingsDialogComponent
382 }
383
384 Component {
385 id: settingsDialogComponent
386
387 QGCPopupDialog {
388 title: qsTr("Settings")
389 buttons: Dialog.Close
390
391 property bool _multipleMavlinkCameras: _cameraManager.cameras.count > 1
392 property bool _multipleMavlinkCameraStreams: _camera.streamLabels.length > 1
393 property bool _cameraStorageSupported: _camera.storageStatus !== MavlinkCameraControl.STORAGE_NOT_SUPPORTED
394 property var _videoSettings: QGroundControl.settingsManager.videoSettings
395
396 ColumnLayout {
397 spacing: _margins
398
399 GridLayout {
400 id: gridLayout
401 flow: GridLayout.TopToBottom
402 rows: dynamicRows + _camera.activeSettings.length
403
404 property int dynamicRows: 10
405
406 // First column
407 QGCLabel {
408 text: qsTr("Camera")
409 visible: _multipleMavlinkCameras
410 onVisibleChanged: gridLayout.dynamicRows += visible ? 1 : -1
411 }
412
413 QGCLabel {
414 text: qsTr("Video Stream")
415 visible: _multipleMavlinkCameraStreams
416 onVisibleChanged: gridLayout.dynamicRows += visible ? 1 : -1
417 }
418
419 QGCLabel {
420 text: qsTr("Thermal View Mode")
421 visible: _camera.thermalStreamInstance
422 onVisibleChanged: gridLayout.dynamicRows += visible ? 1 : -1
423 }
424
425 QGCLabel {
426 text: qsTr("Blend Opacity")
427 visible: _camera.thermalStreamInstance && _camera.thermalMode === MavlinkCameraControl.THERMAL_BLEND
428 onVisibleChanged: gridLayout.dynamicRows += visible ? 1 : -1
429 }
430
431 // Mavlink Camera Protocol active settings
432 Repeater {
433 model: _camera.activeSettings
434
435 QGCLabel {
436 text: _camera.getFact(modelData).shortDescription
437 }
438 }
439
440 QGCLabel {
441 text: qsTr("Photo Mode")
442 visible: _camera.capturesPhotos
443 onVisibleChanged: gridLayout.dynamicRows += visible ? 1 : -1
444 }
445
446 QGCLabel {
447 text: qsTr("Photo Interval (seconds)")
448 visible: _camera.capturesPhotos && _camera.photoCaptureMode === MavlinkCameraControl.PHOTO_CAPTURE_TIMELAPSE
449 onVisibleChanged: gridLayout.dynamicRows += visible ? 1 : -1
450 }
451
452 QGCLabel {
453 text: qsTr("Video Grid Lines")
454 visible: _camera.hasVideoStream
455 onVisibleChanged: gridLayout.dynamicRows += visible ? 1 : -1
456 }
457
458 QGCLabel {
459 text: qsTr("Video Screen Fit")
460 visible: _camera.hasVideoStream
461 onVisibleChanged: gridLayout.dynamicRows += visible ? 1 : -1
462 }
463
464 QGCLabel {
465 text: qsTr("Reset Camera Defaults")
466 onVisibleChanged: gridLayout.dynamicRows += visible ? 1 : -1
467 }
468
469 QGCLabel {
470 text: qsTr("Storage")
471 visible: _cameraStorageSupported
472 onVisibleChanged: gridLayout.dynamicRows += visible ? 1 : -1
473 }
474
475 // Second column
476 QGCComboBox {
477 Layout.fillWidth: true
478 sizeToContents: true
479 model: _cameraManager.cameraLabels
480 currentIndex: _cameraManager.currentCamera
481 visible: _multipleMavlinkCameras
482 onActivated: (index) => { _cameraManager.currentCamera = index }
483 }
484
485 QGCComboBox {
486 Layout.fillWidth: true
487 sizeToContents: true
488 model: _camera.streamLabels
489 currentIndex: _camera.currentStream
490 visible: _multipleMavlinkCameraStreams
491 onActivated: (index) => { _camera.currentStream = index }
492 }
493
494 QGCComboBox {
495 Layout.fillWidth: true
496 sizeToContents: true
497 model: [ qsTr("Off"), qsTr("Blend"), qsTr("Full"), qsTr("Picture In Picture") ]
498 currentIndex: _camera.thermalMode
499 visible: _camera.thermalStreamInstance
500 onActivated: (index) => { _camera.thermalMode = index }
501 }
502
503 QGCSlider {
504 Layout.fillWidth: true
505 to: 100
506 from: 0
507 value: _camera.thermalOpacity
508 live: true
509 visible: _camera.thermalStreamInstance && _camera.thermalMode === MavlinkCameraControl.THERMAL_BLEND
510 onValueChanged: _camera.thermalOpacity = value
511 }
512
513 // Mavlink Camera Protocol active settings
514 Repeater {
515 model: _camera.activeSettings
516
517 RowLayout {
518 Layout.fillWidth: true
519 spacing: ScreenTools.defaultFontPixelWidth
520
521 property var _fact: _camera.getFact(modelData)
522 property bool _isBool: _fact.typeIsBool
523 property bool _isCombo: !_isBool && _fact.enumStrings.length > 0
524 property bool _isSlider: _fact && !isNaN(_fact.increment)
525 property bool _isEdit: !_isBool && !_isSlider && _fact.enumStrings.length < 1
526
527 FactComboBox {
528 Layout.fillWidth: true
529 sizeToContents: true
530 fact: parent._fact
531 indexModel: false
532 visible: parent._isCombo
533 }
534 FactTextField {
535 Layout.fillWidth: true
536 fact: parent._fact
537 visible: parent._isEdit
538 }
539 QGCSlider {
540 Layout.fillWidth: true
541 to: parent._fact.max
542 from: parent._fact.min
543 stepSize: parent._fact.increment
544 visible: parent._isSlider
545 live: false
546 property bool initialized: false
547
548 onValueChanged: {
549 if (!initialized) {
550 return
551 }
552 parent._fact.value = value
553 }
554
555 Component.onCompleted: {
556 value = parent._fact.value
557 initialized = true
558 }
559 }
560 QGCCheckBoxSlider {
561 checked: parent._fact ? parent._fact.value : false
562 visible: parent._isBool
563 onClicked: parent._fact.value = checked ? 1 : 0
564 }
565 }
566 }
567
568 QGCComboBox {
569 Layout.fillWidth: true
570 sizeToContents: true
571 model: [ qsTr("Single"), qsTr("Time Lapse") ]
572 currentIndex: _camera.photoCaptureMode
573 visible: _camera.capturesPhotos
574 onActivated: (index) => { _camera.photoCaptureMode = index }
575 }
576
577 QGCSlider {
578 Layout.fillWidth: true
579 to: 60
580 from: 1
581 stepSize: 1
582 value: _camera.photoLapse
583 displayValue: true
584 live: true
585 visible: _camera.capturesPhotos && _camera.photoCaptureMode === MavlinkCameraControl.PHOTO_CAPTURE_TIMELAPSE
586 onValueChanged: _camera.photoLapse = value
587 }
588
589 QGCCheckBoxSlider {
590 checked: _videoSettings.gridLines.rawValue
591 visible: _camera.hasVideoStream
592 onClicked: _videoSettings.gridLines.rawValue = checked ? 1 : 0
593 }
594
595 FactComboBox {
596 Layout.fillWidth: true
597 sizeToContents: true
598 fact: _videoSettings.videoFit
599 indexModel: false
600 visible: _camera.hasVideoStream
601 }
602
603 QGCButton {
604 Layout.fillWidth: true
605 text: qsTr("Reset")
606 onClicked: resetPrompt.open()
607 MessageDialog {
608 id: resetPrompt
609 title: qsTr("Reset Camera to Factory Settings")
610 text: qsTr("Confirm resetting all settings?")
611 buttons: MessageDialog.Yes | MessageDialog.No
612
613 onButtonClicked: function (button, role) {
614 switch (button) {
615 case MessageDialog.Yes:
616 _camera.resetSettings()
617 resetPrompt.close()
618 break;
619 case MessageDialog.No:
620 resetPrompt.close()
621 break;
622 }
623 }
624 }
625 }
626
627 QGCButton {
628 Layout.fillWidth: true
629 text: qsTr("Format")
630 visible: _cameraStorageSupported
631 onClicked: formatPrompt.open()
632 MessageDialog {
633 id: formatPrompt
634 title: qsTr("Format Camera Storage")
635 text: qsTr("Confirm erasing all files?")
636 buttons: MessageDialog.Yes | MessageDialog.No
637
638 onButtonClicked: function (button, role) {
639 switch (button) {
640 case MessageDialog.Yes:
641 _camera.formatCard()
642 formatPrompt.close()
643 break;
644 case MessageDialog.No:
645 formatPrompt.close()
646 break;
647 }
648 }
649 }
650 }
651 }
652 }
653 }
654 }
655 }
656}