1 # -*- coding: utf-8 -*-
3 # OSD (on screen display) written in Qt
4 # Copyright (C) 2015 Olivier Matz <zer0@droids-corp.org>
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 # Inspired from QcGauge
20 # Copyright (C) 2015 Hadj Tahar Berrima
21 # http://pytricity.com/qt-artificial-horizon-custom-widget/
26 from PyQt5 import QtCore
27 from PyQt5.QtCore import (pyqtSlot, QTimer, QRect, QPoint, Qt, QByteArray,
29 from PyQt5.QtGui import (QPainter, QColor, QPen, QBrush, QLinearGradient, QFont,
30 QPainterPath, QPolygonF)
31 from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QAction,
32 QActionGroup, QGraphicsScene, QGraphicsView)
33 from PyQt5.QtMultimediaWidgets import (QGraphicsVideoItem)
34 from PyQt5.QtMultimedia import (QCamera, QAbstractVideoSurface,
41 _fromUtf8 = QtCore.QString.fromUtf8
42 except AttributeError:
47 _encoding = QApplication.UnicodeUTF8
48 def _translate(context, text, disambig):
49 return QApplication.translate(context, text, disambig, _encoding)
50 except AttributeError:
51 def _translate(context, text, disambig):
52 return QApplication.translate(context, text, disambig)
54 class OSDWidget(QWidget):
55 def __init__(self, mode = "camera", filename = None):
56 super(OSDWidget, self).__init__()
60 # parameters that will be modified by the user
67 self.left_txt = "14.8v / 34A\n1.5Ah" # XXX
68 self.right_txt = "23:03 since take-off\n1.4 km to home\nRSSI 60dB" # XXX
69 self.user_frame_cb = None
77 # filtered parameters (0 = no filter, 1 = infinite filter)
78 self.pitch_filter_coef = 0.8
79 self.roll_filter_coef = 0.8
80 self.yaw_filter_coef = 0.8
81 self.speed_filter_coef = 0.8
82 self.alt_filter_coef = 0.8
83 self.rthome_filter_coef = 0.8
84 # QRect representing the viewport
86 # QRect representing the viewport, adjusted to a square
88 self.setMinimumSize(250, 250)
90 self.setStyleSheet("background-color: transparent;")
92 # how many degrees between pitch reference lines
95 self.PITCH_REFLINES_STEP_ANGLE = 10.
96 self.PITCH_REFLINES_BOLD_STEP_ANGLE = 30.
97 self.PITCH_REFLINES_NUM_LINES = 6
100 self.PITCH_REFLINES_STEP_ANGLE = 5.
101 self.PITCH_REFLINES_BOLD_STEP_ANGLE = 10.
102 self.PITCH_REFLINES_NUM_LINES = 4
104 self.PITCH_REFLINES_TOTAL_ANGLE = (self.PITCH_REFLINES_STEP_ANGLE *
105 self.PITCH_REFLINES_NUM_LINES)
106 # in fraction of radius
107 self.FONT_SIZE = 0.06
108 # how many degrees between yaw reference lines
109 self.YAW_REFLINES_STEP_ANGLE = 15.
110 self.YAW_REFLINES_NUM_LINES = 12
111 self.YAW_REFLINES_TOTAL_ANGLE = (self.YAW_REFLINES_STEP_ANGLE *
112 self.YAW_REFLINES_NUM_LINES)
113 self.YAW_REFLINES_SIZE_FACTOR = 0.5
115 self.SPEED_REFLINES_STEP = 10.
116 self.SPEED_REFLINES_BOLD_STEP = 50.
117 self.SPEED_REFLINES_NUM_LINES = 10
118 self.SPEED_REFLINES_TOTAL = (self.SPEED_REFLINES_STEP *
119 self.SPEED_REFLINES_NUM_LINES)
120 self.SPEED_REFLINES_SIZE_FACTOR = 0.7
122 self.ALT_REFLINES_STEP = 100.
123 self.ALT_REFLINES_BOLD_STEP = 500.
124 self.ALT_REFLINES_NUM_LINES = 10
125 self.ALT_REFLINES_TOTAL = (self.ALT_REFLINES_STEP *
126 self.ALT_REFLINES_NUM_LINES)
127 self.ALT_REFLINES_SIZE_FACTOR = 0.7
130 self.fpv = serialfpv.SerialFPV()
131 self.fpv.open_file(filename)
134 self.timer = QTimer(self)
135 self.timer.timeout.connect(self.frameTimerCb)
136 self.timer.start(1000. / self.FPS)
138 @pyqtSlot(name = "paintEvent")
139 def paintEvent(self, evt):
140 """Paint callback, this is the entry point for all drawings."""
143 painter.setRenderHint(QPainter.Antialiasing)
144 self.dev = evt.rect()
145 self.min_dim = min(self.dev.right(), self.dev.bottom())
146 self.max_dim = max(self.dev.right(), self.dev.bottom())
147 self.adjdev = QRect(0, 0, self.min_dim, self.min_dim)
148 self.adjdev.moveCenter(self.dev.center())
152 @pyqtSlot(name = "frameTimerCb")
153 def frameTimerCb(self):
154 # read from SerialFPV object
156 self.fpv.update_state()
157 self.user_speed = self.user_speed + 1
158 self.setRoll(self.fpv.roll)
159 self.setPitch(self.fpv.pitch)
160 self.setYaw(self.fpv.yaw)
162 # avoid filter bugs when changing between 180 and -180
163 self.pitch += round((self.user_pitch - self.pitch) / 360.) * 360
164 self.pitch = (self.pitch * self.pitch_filter_coef +
165 self.user_pitch * (1. - self.pitch_filter_coef))
166 self.roll += round((self.user_roll - self.roll) / 360.) * 360
167 self.roll = (self.roll * self.roll_filter_coef +
168 self.user_roll * (1. - self.roll_filter_coef))
169 self.yaw += round((self.user_yaw - self.yaw) / 360.) * 360
170 self.yaw = (self.yaw * self.yaw_filter_coef +
171 self.user_yaw * (1. - self.yaw_filter_coef))
172 self.speed = (self.speed * self.speed_filter_coef +
173 self.user_speed * (1. - self.speed_filter_coef))
174 self.alt = (self.alt * self.alt_filter_coef +
175 self.user_alt * (1. - self.alt_filter_coef))
176 self.rthome += round((self.user_rthome - self.rthome) / 360.) * 360
177 self.rthome = (self.rthome * self.rthome_filter_coef +
178 self.user_rthome * (1. - self.rthome_filter_coef))
181 def draw(self, painter):
182 """Draw the widget."""
183 if self.mode == "round":
184 self.drawHorizonRound(painter)
185 elif self.mode == "rectangle":
186 self.drawHorizon(painter)
187 self.drawPitchGraduation(painter)
188 self.drawRollGraduation(painter)
189 self.drawYaw(painter)
190 self.drawCenterRef(painter)
191 if self.mode != "round":
192 self.drawSpeed(painter)
193 self.drawAlt(painter)
194 self.drawReturnToHome(painter)
195 self.drawTxtInfo(painter)
197 def getSkyBrush(self):
198 """return the color gradient for the sky (blue)"""
199 sky_gradient = QLinearGradient(self.adjdev.topLeft(),
200 self.adjdev.bottomRight())
203 sky_gradient.setColorAt(0, color1)
204 sky_gradient.setColorAt(.8, color2)
207 def getGroundBrush(self):
208 """return the color gradient for the ground (marron)"""
209 ground_gradient = QLinearGradient(self.adjdev.topLeft(),
210 self.adjdev.bottomRight())
211 color1 = QColor(140, 100, 80)
212 color2 = QColor(140, 100, 40)
213 ground_gradient.setColorAt(0, color1)
214 ground_gradient.setColorAt(.8, color2)
215 return ground_gradient
217 def getCenterRadius(self):
218 """Return the radius of the widget circle"""
219 if self.mode == "round":
220 return self.adjdev.width() / 2.
222 return self.adjdev.width() / 3.5
224 def drawText(self, painter, center, s):
225 r = self.getCenterRadius()
227 pen = QPen(Qt.white, r / 150., Qt.SolidLine)
228 brush = QBrush(Qt.white)
229 painter.setBrush(brush)
230 pen.setColor(Qt.black);
232 font = QFont("Meiryo UI", 0, QFont.Bold)
233 font.setPointSizeF(self.FONT_SIZE * r)
234 painter.setFont(font)
235 path = QPainterPath()
236 for l in s.split("\n"):
237 path.addText(center, font, l)
238 center += QPoint(0, self.FONT_SIZE * r * 1.5)
239 bounding = path.boundingRect()
240 path.translate(-bounding.width() / 2., bounding.height() / 2.)
241 painter.drawPath(path)
244 def drawHorizonRound(self, painter):
245 """Draw the horizon for round widget: the sky in blue,
246 the ground in marron."""
248 # set local pitch and roll
257 pitch = -180. - pitch
262 # we have to draw a partial circle delimited by its chord, define
263 # where the chord starts
264 start_angle = math.degrees(math.asin(pitch / 90.)) - roll
265 span = 2 * math.degrees(math.asin(pitch / 90.))
268 painter.setBrush(self.getSkyBrush())
269 # startAngle and spanAngle must be specified in 1/16th of a degree
270 painter.drawChord(self.adjdev, 16 * start_angle, 16 * (180. - span))
273 painter.setBrush(self.getGroundBrush())
274 # startAngle and spanAngle must be specified in 1/16th of a degree
275 painter.drawChord(self.adjdev, 16 * start_angle, -16 * (180. + span))
277 def drawHorizon(self, painter):
278 """Draw the horizon: the sky in blue, the ground in marron."""
280 painter.setBrush(self.getSkyBrush())
281 painter.drawRect(self.dev)
282 center = self.adjdev.center()
283 r = self.getCenterRadius()
284 # radius of the adjusted screen (same than r if roundWidget = True)
285 dev_r = self.adjdev.width() / 2.
287 if self.pitch < -90.:
288 pitch = -180 - self.pitch
289 elif self.pitch < 90.:
292 pitch = 180 - self.pitch
293 y_off = (pitch / self.CAM_ANGLE) * dev_r
294 painter.translate(center.x(), center.y())
296 ground_rect = QRect(0, 0, self.max_dim * 5., self.max_dim * 5.)
297 if self.pitch < 90. and self.pitch > -90.:
298 ground_rect.moveCenter(QPoint(0, -y_off + ground_rect.width()/2.))
300 ground_rect.moveCenter(QPoint(0, y_off - ground_rect.width()/2.))
301 painter.setBrush(self.getGroundBrush())
302 painter.drawRect(ground_rect)
305 def drawCenterRef(self, painter):
306 """Draw the cross on the middle of the OSD"""
307 center = self.adjdev.center()
308 r = self.getCenterRadius()
309 pen = QPen(Qt.red, r / 100., Qt.SolidLine)
311 pt1 = QPoint(center.x() - 0.05 * r, center.y())
312 pt2 = QPoint(center.x() + 0.05 * r, center.y())
313 painter.drawLine(pt1, pt2)
314 pt1 = QPoint(center.x(), center.y() + -0.025 * r)
315 pt2 = QPoint(center.x(), center.y() + 0.025 * r)
316 painter.drawLine(pt1, pt2)
318 def drawPitchGraduation(self, painter):
319 """Draw the pitch graduations."""
320 # change the reference
322 center = self.adjdev.center()
323 r = self.getCenterRadius()
324 # radius of the adjusted screen (same than r if roundWidget = True)
325 dev_r = self.adjdev.width() / 2.
327 x_off = (self.pitch / self.CAM_ANGLE) * dev_r * math.sin(math.radians(roll))
328 y_off = (self.pitch / self.CAM_ANGLE) * dev_r * math.cos(math.radians(roll))
329 painter.translate(center.x() + x_off, center.y() - y_off)
333 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
336 # round to nearest angle that is a multiple of step
337 a = self.pitch / self.PITCH_REFLINES_STEP_ANGLE
339 a = int(a * self.PITCH_REFLINES_STEP_ANGLE)
340 a -= self.PITCH_REFLINES_STEP_ANGLE * (self.PITCH_REFLINES_NUM_LINES / 2.)
341 angles = [ a + i * self.PITCH_REFLINES_STEP_ANGLE
342 for i in range(self.PITCH_REFLINES_NUM_LINES + 1) ]
345 if int(a) % int(self.PITCH_REFLINES_BOLD_STEP_ANGLE) != 0:
346 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
348 pt1 = QPoint(-0.05 * r, dev_r / self.CAM_ANGLE * a)
349 pt2 = QPoint(0.05 * r, dev_r / self.CAM_ANGLE * a)
350 painter.drawLine(pt1, pt2)
354 pen = QPen(Qt.white, r / 50., Qt.SolidLine)
356 pt1 = QPoint(-0.2 * r, dev_r / self.CAM_ANGLE * a)
357 pt2 = QPoint(0.2 * r, dev_r / self.CAM_ANGLE * a)
358 painter.drawLine(pt1, pt2)
363 disp_val = 180. - disp_val
365 disp_val = -180. - disp_val
366 disp_val = str(int(disp_val))
367 lefttxt_pt = pt1 - QPoint(0.2 * r, 0)
368 self.drawText(painter, lefttxt_pt, disp_val)
370 # flip the right text
372 painter.translate(pt2 + QPoint(0.2 * r, 0))
374 painter.translate(-pt2 - QPoint(0.2 * r, 0))
375 righttxt_pt = pt2 + QPoint(0.2 * r, 0)
376 self.drawText(painter, righttxt_pt, disp_val)
381 def drawOneRollGraduation(self, painter, deg, disp_text):
382 # draw the graduiation
383 r = self.getCenterRadius()
384 center = self.adjdev.center()
385 x = center.x() - math.cos(math.radians(deg)) * r
386 y = center.y() - math.sin(math.radians(deg)) * r
388 path = QPainterPath()
391 pt2 = path.pointAtPercent(0.075) # graduation len is 7.5% of the radius
392 painter.drawLine(pt, pt2)
394 if disp_text == True:
395 pt_txt = path.pointAtPercent(0.2)
398 disp_val = 180. - disp_val
399 disp_val = str(int(disp_val))
400 self.drawText(painter, pt_txt, disp_val)
402 def drawRollGraduation(self, painter):
403 """Draw the roll graduations."""
404 center = self.adjdev.center()
405 r = self.getCenterRadius()
407 # draw the red reference lines (pitch 0)
409 painter.translate(center.x(), center.y())
410 painter.rotate(self.roll)
411 pen = QPen(Qt.red, r / 100., Qt.SolidLine)
413 pt1 = QPoint(-0.925 * r, 0)
414 pt2 = QPoint(-0.85 * r, 0)
415 painter.drawLine(pt1, pt2)
416 pt1 = QPoint(0.925 * r, 0)
417 pt2 = QPoint(0.85 * r, 0)
418 painter.drawLine(pt1, pt2)
421 pen = QPen(Qt.white, r / 50., Qt.SolidLine)
433 self.drawOneRollGraduation(painter, deg, disp_text)
436 def drawYaw(self, painter):
437 center = self.adjdev.center()
438 r = self.getCenterRadius()
439 pen = QPen(Qt.red, r / 100., Qt.SolidLine)
441 if self.mode == "round":
442 y_txt = center.y() + r * 0.6
443 y1 = center.y() + r * 0.7
444 y2 = center.y() + r * 0.8
445 y3 = center.y() + r * 0.85
447 y_txt = center.y() + r * 1.
448 y1 = center.y() + r * 1.1
449 y2 = center.y() + r * 1.2
450 y3 = center.y() + r * 1.25
451 pt1 = QPoint(center.x(), y2)
452 pt2 = QPoint(center.x(), y3)
453 painter.drawLine(pt1, pt2)
455 # round to nearest angle multiple of step
456 a = self.yaw / self.YAW_REFLINES_STEP_ANGLE
458 a = int(a * self.YAW_REFLINES_STEP_ANGLE)
459 a -= self.YAW_REFLINES_STEP_ANGLE * (self.YAW_REFLINES_NUM_LINES / 2.)
460 angles = [ a + i * self.YAW_REFLINES_STEP_ANGLE
461 for i in range(self.YAW_REFLINES_NUM_LINES + 1) ]
466 pen = QPen(Qt.white, r / 50., Qt.SolidLine)
470 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
473 x = center.x() - ((r / (self.YAW_REFLINES_TOTAL_ANGLE / 2.)) *
474 (self.yaw - a) * self.YAW_REFLINES_SIZE_FACTOR)
475 pt_txt = QPoint(x, y_txt)
478 painter.drawLine(pt1, pt2)
479 if disp_text == False:
481 disp_val = ["N", "E", "S", "W"][(int(a)/90)%4]
482 self.drawText(painter, pt_txt, disp_val)
484 def drawSpeed(self, painter):
485 center = self.adjdev.center()
486 r = self.getCenterRadius()
487 pen = QPen(Qt.red, r / 100., Qt.SolidLine)
489 x1 = center.x() - 1.5 * r
490 x2 = center.x() - 1.6 * r
491 pt1 = QPoint(center.x() - 1.45 * r, center.y())
492 pt2 = QPoint(x2, center.y())
493 painter.drawLine(pt1, pt2)
495 # round to nearest angle multiple of step
496 s = self.speed / self.SPEED_REFLINES_STEP
498 s = int(s * self.SPEED_REFLINES_STEP)
499 s -= self.SPEED_REFLINES_STEP * (self.SPEED_REFLINES_NUM_LINES / 2.)
500 speeds = [ s + i * self.SPEED_REFLINES_STEP
501 for i in range(self.SPEED_REFLINES_NUM_LINES + 1) ]
503 if int(s) % int(self.SPEED_REFLINES_BOLD_STEP) == 0:
505 pen = QPen(Qt.white, r / 50., Qt.SolidLine)
509 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
512 y = center.y() + ((r / (self.SPEED_REFLINES_TOTAL/2.)) *
513 (self.speed - s) * self.SPEED_REFLINES_SIZE_FACTOR)
514 pt_txt = QPoint(center.x() + r * -1.75, y)
517 painter.drawLine(pt1, pt2)
518 if disp_text == False:
520 disp_val = str(int(s))
521 self.drawText(painter, pt_txt, disp_val)
523 def drawAlt(self, painter):
524 center = self.adjdev.center()
525 r = self.getCenterRadius()
526 pen = QPen(Qt.red, r / 100., Qt.SolidLine)
528 x1 = center.x() + 1.5 * r
529 x2 = center.x() + 1.6 * r
530 pt1 = QPoint(center.x() + 1.45 * r, center.y())
531 pt2 = QPoint(x2, center.y())
532 painter.drawLine(pt1, pt2)
534 # round to nearest angle multiple of step
535 a = self.alt / self.ALT_REFLINES_STEP
537 a = int(a * self.ALT_REFLINES_STEP)
538 a -= self.ALT_REFLINES_STEP * (self.ALT_REFLINES_NUM_LINES / 2.)
539 alts = [ a + i * self.ALT_REFLINES_STEP
540 for i in range(self.ALT_REFLINES_NUM_LINES + 1) ]
542 if int(a) % int(self.ALT_REFLINES_BOLD_STEP) == 0:
544 pen = QPen(Qt.white, r / 50., Qt.SolidLine)
548 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
551 y = center.y() + ((r / (self.ALT_REFLINES_TOTAL / 2.)) *
552 (self.alt - a) * self.ALT_REFLINES_SIZE_FACTOR)
553 pt_txt = QPoint(center.x() + r * 1.75, y)
556 painter.drawLine(pt1, pt2)
557 if disp_text == False:
559 disp_val = str(int(a))
560 self.drawText(painter, pt_txt, disp_val)
562 def drawReturnToHome(self, painter):
563 center = self.adjdev.center()
564 r = self.getCenterRadius()
565 dev_r = self.adjdev.width() / 2.
567 painter.translate(center.x(), center.y() - 0.9 * dev_r)
568 painter.rotate(self.yaw) # XXX
569 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
572 poly.append(QPoint(0., -0.08 * r))
573 poly.append(QPoint(0.04 * r, 0.08 * r))
574 poly.append(QPoint(-0.04 * r, 0.08 * r))
575 poly.append(QPoint(0., -0.08 * r))
576 path = QPainterPath()
577 path.addPolygon(poly)
578 brush = QBrush(Qt.darkGray)
579 painter.setBrush(brush)
580 painter.drawPath(path)
583 def drawTxtInfo(self, painter):
584 center = self.adjdev.center()
585 dev_r = self.adjdev.width() / 2.
586 r = self.getCenterRadius()
587 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
590 pt_txt = QPoint(center.x() + dev_r * -0.95,
591 center.y() + dev_r * -0.95)
592 self.drawText(painter, pt_txt, self.right_txt)
594 pt_txt = QPoint(center.x() + dev_r * 0.95,
595 center.y() + dev_r * -0.95)
596 self.drawText(painter, pt_txt, self.right_txt)
598 def setPitch(self, pitch):
599 """set the pitch in degrees, between -180 and 180"""
603 self.user_pitch = pitch
605 def setRoll(self, roll):
606 """set the roll in degrees, between -180 and 180"""
610 self.user_roll = roll
612 def setYaw(self, yaw):
613 """set the yaw in degrees, between 0 and 360"""
617 def setSpeed(self, speed):
619 self.user_speed = speed
621 def setAlt(self, alt):
625 def setReturnToHomeAngle(self, angle):
626 """set the left text"""
627 self.user_rthome = angle
629 def setLeftTxt(self, txt):
630 """set the right text"""
633 def setRightTxt(self, txt):
634 """set the left text"""
637 class OSDGraphicsView(QGraphicsView):
638 def __init__(self, parent=None):
639 super(OSDGraphicsView, self).__init__(parent)
640 self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
641 self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
642 self.setMinimumSize(200, 150)
643 def resizeEvent(self, event):
644 # use item 0 to resize automatically
645 self.fitInView(self.items()[0], Qt.KeepAspectRatio)
646 super(OSDGraphicsView, self).resizeEvent(event)
648 class Ui_OSD(QMainWindow):
649 def __init__(self, parent = None, mode = "camera", filename = None):
650 super(Ui_OSD, self).__init__(parent)
651 self.ui = qtosd_ui.Ui_MainWindow()
652 self.ui.setupUi(self)
655 self.filename = filename
657 self.osd = OSDWidget(mode = self.mode,
658 filename = self.filename)
659 self.osd.setObjectName("osd")
660 self.ui.pitchSlider.valueChanged[int].connect(self.changePitch)
661 self.ui.rollSlider.valueChanged[int].connect(self.changeRoll)
662 self.ui.yawSlider.valueChanged[int].connect(self.changeYaw)
663 self.ui.actionExit.triggered.connect(self.close)
665 self.scene = QGraphicsScene(self)
666 self.graphicsView = OSDGraphicsView(self.scene)
668 if self.mode == "camera":
669 self.videoItem = QGraphicsVideoItem()
670 self.videoItem.setSize(QSizeF(640, 480)) # XXX
671 self.scene.addItem(self.videoItem)
673 x = self.videoItem.boundingRect().width() / 2.0
674 y = self.videoItem.boundingRect().height() / 2.0
675 #self.videoItem.setTransform(
676 # QTransform().translate(x, y).rotate(70).translate(-x, -y))
680 self.scene.addWidget(self.osd)
681 self.ui.gridLayout.addWidget(self.graphicsView, 0, 1)
683 def initCamera(self):
684 # find camera devices and add them in the menu
685 cameraDevice = QByteArray()
686 videoDevicesGroup = QActionGroup(self)
687 videoDevicesGroup.setExclusive(True)
688 for deviceName in QCamera.availableDevices():
689 description = QCamera.deviceDescription(deviceName)
690 videoDeviceAction = QAction(description, videoDevicesGroup)
691 videoDeviceAction.setCheckable(True)
692 videoDeviceAction.setData(deviceName)
693 if cameraDevice.isEmpty():
694 cameraDevice = deviceName
695 videoDeviceAction.setChecked(True)
696 self.ui.menuDevices.addAction(videoDeviceAction)
697 videoDevicesGroup.triggered.connect(self.updateCameraDevice)
698 # select the first camera
699 self.setCamera(cameraDevice)
701 def setCamera(self, cameraDevice):
702 if cameraDevice.isEmpty():
703 self.camera = QCamera()
705 self.camera = QCamera(cameraDevice)
707 self.camera.stateChanged.connect(self.updateCameraState)
708 self.camera.error.connect(self.displayCameraError)
710 #self.ui.exposureCompensation.valueChanged.connect(
711 # self.setExposureCompensation)
713 self.camera.setCaptureMode(QCamera.CaptureViewfinder)
714 self.camera.setViewfinder(self.videoItem)
715 self.updateCameraState(self.camera.state())
718 #XXX stop camera? remove from scene?
720 def updateCameraDevice(self, action):
721 print "updateCameraDevice"
722 self.setCamera(action.data())
724 def updateCameraState(self, state):
725 print "updateCameraState %s"%(str(state))
727 def displayCameraError(self):
728 print "displayCameraError"
729 QMessageBox.warning(self, "Camera error", self.camera.errorString())
731 def keyPressEvent(self, event):
734 self.osd.setRoll(self.osd.user_roll + 2)
736 elif key == Qt.Key_L:
737 self.osd.setRoll(self.osd.user_roll - 2)
739 elif key == Qt.Key_I:
740 self.osd.setPitch(self.osd.user_pitch + 2)
742 elif key == Qt.Key_K:
743 self.osd.setPitch(self.osd.user_pitch - 2)
745 elif key == Qt.Key_Q:
747 elif not event.isAutoRepeat():
748 if key == Qt.Key_CameraFocus:
749 self.camera.searchAndLock()
752 super(Ui_OSD, self).keyPressEvent(event)
754 def keyReleaseEvent(self, event):
756 if event.isAutoRepeat():
758 if key == Qt.Key_CameraFocus:
761 super(Ui_OSD, self).keyReleaseEvent(event)
763 def retranslateUi(self, MainWindow):
764 MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
766 @pyqtSlot(int, name = "changePitch")
767 def changePitch(self, value):
768 self.osd.setPitch(value)
770 @pyqtSlot(int, name = "changeRoll")
771 def changeRoll(self, value):
772 self.osd.setRoll(value)
774 @pyqtSlot(int, name = "changeYaw")
775 def changeYaw(self, value):
776 self.osd.setYaw(value)
778 if __name__ == "__main__":
781 parser = argparse.ArgumentParser(description='OSD written in Qt.')
782 parser.add_argument('--mode', '-m', action="store",
783 choices=["round", "rectangle", "camera"],
784 help='display the widget as a round attitude meter')
785 parser.add_argument('--filename', '-f',
786 help='specify the log file')
787 args = parser.parse_args()
789 app = QApplication(sys.argv)
790 ui = Ui_OSD(filename = args.filename, mode = args.mode)
792 sys.exit(app.exec_())