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 drawHorizonRound(self, painter):
225 """Draw the horizon for round widget: the sky in blue,
226 the ground in marron."""
228 # set local pitch and roll
237 pitch = -180. - pitch
242 # we have to draw a partial circle delimited by its chord, define
243 # where the chord starts
244 start_angle = math.degrees(math.asin(pitch / 90.)) - roll
245 span = 2 * math.degrees(math.asin(pitch / 90.))
248 painter.setBrush(self.getSkyBrush())
249 # startAngle and spanAngle must be specified in 1/16th of a degree
250 painter.drawChord(self.adjdev, 16 * start_angle, 16 * (180. - span))
253 painter.setBrush(self.getGroundBrush())
254 # startAngle and spanAngle must be specified in 1/16th of a degree
255 painter.drawChord(self.adjdev, 16 * start_angle, -16 * (180. + span))
257 def drawHorizon(self, painter):
258 """Draw the horizon: the sky in blue, the ground in marron."""
260 painter.setBrush(self.getSkyBrush())
261 painter.drawRect(self.dev)
262 center = self.adjdev.center()
263 r = self.getCenterRadius()
264 # radius of the adjusted screen (same than r if roundWidget = True)
265 dev_r = self.adjdev.width() / 2.
267 if self.pitch < -90.:
268 pitch = -180 - self.pitch
269 elif self.pitch < 90.:
272 pitch = 180 - self.pitch
273 y_off = (pitch / self.CAM_ANGLE) * dev_r
274 painter.translate(center.x(), center.y())
276 ground_rect = QRect(0, 0, self.max_dim * 5., self.max_dim * 5.)
277 if self.pitch < 90. and self.pitch > -90.:
278 ground_rect.moveCenter(QPoint(0, -y_off + ground_rect.width()/2.))
280 ground_rect.moveCenter(QPoint(0, y_off - ground_rect.width()/2.))
281 painter.setBrush(self.getGroundBrush())
282 painter.drawRect(ground_rect)
285 def drawCenterRef(self, painter):
286 """Draw the cross on the middle of the OSD"""
287 center = self.adjdev.center()
288 r = self.getCenterRadius()
289 pen = QPen(Qt.red, r / 100., Qt.SolidLine)
291 pt1 = QPoint(center.x() - 0.05 * r, center.y())
292 pt2 = QPoint(center.x() + 0.05 * r, center.y())
293 painter.drawLine(pt1, pt2)
294 pt1 = QPoint(center.x(), center.y() + -0.025 * r)
295 pt2 = QPoint(center.x(), center.y() + 0.025 * r)
296 painter.drawLine(pt1, pt2)
298 def drawPitchGraduation(self, painter):
299 """Draw the pitch graduations."""
300 # change the reference
302 center = self.adjdev.center()
303 r = self.getCenterRadius()
304 # radius of the adjusted screen (same than r if roundWidget = True)
305 dev_r = self.adjdev.width() / 2.
307 x_off = (self.pitch / self.CAM_ANGLE) * dev_r * math.sin(math.radians(roll))
308 y_off = (self.pitch / self.CAM_ANGLE) * dev_r * math.cos(math.radians(roll))
309 painter.translate(center.x() + x_off, center.y() - y_off)
313 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
315 font = QFont("Meiryo UI", 0, QFont.Bold)
316 font.setPointSizeF(self.FONT_SIZE * r)
317 painter.setFont(font)
319 # round to nearest angle that is a multiple of step
320 a = self.pitch / self.PITCH_REFLINES_STEP_ANGLE
322 a = int(a * self.PITCH_REFLINES_STEP_ANGLE)
323 a -= self.PITCH_REFLINES_STEP_ANGLE * (self.PITCH_REFLINES_NUM_LINES / 2.)
324 angles = [ a + i * self.PITCH_REFLINES_STEP_ANGLE
325 for i in range(self.PITCH_REFLINES_NUM_LINES + 1) ]
328 if int(a) % int(self.PITCH_REFLINES_BOLD_STEP_ANGLE) != 0:
329 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
331 pt1 = QPoint(-0.05 * r, dev_r / self.CAM_ANGLE * a)
332 pt2 = QPoint(0.05 * r, dev_r / self.CAM_ANGLE * a)
333 painter.drawLine(pt1, pt2)
337 pen = QPen(Qt.white, r / 50., Qt.SolidLine)
339 pt1 = QPoint(-0.2 * r, dev_r / self.CAM_ANGLE * a)
340 pt2 = QPoint(0.2 * r, dev_r / self.CAM_ANGLE * a)
341 painter.drawLine(pt1, pt2)
346 disp_val = 180. - disp_val
348 disp_val = -180. - disp_val
349 disp_val = str(int(disp_val))
350 metrics = painter.fontMetrics()
351 sz = metrics.size(Qt.TextSingleLine, disp_val)
352 lefttxt = QRect(QPoint(0, 0), sz)
353 lefttxt.moveCenter(pt1 - QPoint(0.2 * r, 0))
356 #brush = QBrush(Qt.white)
357 #painter.setBrush(brush)
358 #pen.setColor(Qt.black);
360 #path = QPainterPath()
361 #path.addText(lefttxt.center(), font, disp_val)
362 #painter.drawPath(path)
363 painter.drawText(lefttxt, Qt.TextSingleLine, disp_val)
365 # flip the right text
367 painter.translate(pt2 + QPoint(0.2 * r, 0))
369 painter.translate(-pt2 - QPoint(0.2 * r, 0))
370 righttxt = QRect(QPoint(0, 0), sz)
371 righttxt.moveCenter(pt2 + QPoint(0.2 * r, 0))
372 #path = QPainterPath()
373 #path.addText(righttxt.center(), font, disp_val)
374 #painter.drawPath(path)
375 painter.drawText(righttxt, Qt.TextSingleLine, disp_val)
380 def drawOneRollGraduation(self, painter, deg, disp_text):
381 # draw the graduiation
382 r = self.getCenterRadius()
383 center = self.adjdev.center()
384 x = center.x() - math.cos(math.radians(deg)) * r
385 y = center.y() - math.sin(math.radians(deg)) * r
387 path = QPainterPath()
390 pt2 = path.pointAtPercent(0.075) # graduation len is 7.5% of the radius
391 painter.drawLine(pt, pt2)
393 if disp_text == True:
394 pt_txt = path.pointAtPercent(0.2)
395 font = QFont("Meiryo UI", 0, QFont.Bold)
396 font.setPointSizeF(self.FONT_SIZE * r)
397 painter.setFont(font)
400 disp_val = 180. - disp_val
401 disp_val = str(int(disp_val))
402 metrics = painter.fontMetrics()
403 sz = metrics.size(Qt.TextSingleLine, disp_val)
404 txt = QRect(QPoint(0, 0), sz)
405 txt.moveCenter(pt_txt.toPoint())
406 painter.drawText(txt, Qt.TextSingleLine, disp_val)
408 def drawRollGraduation(self, painter):
409 """Draw the roll graduations."""
410 center = self.adjdev.center()
411 r = self.getCenterRadius()
413 # draw the red reference lines (pitch 0)
415 painter.translate(center.x(), center.y())
416 painter.rotate(self.roll)
417 pen = QPen(Qt.red, r / 100., Qt.SolidLine)
419 pt1 = QPoint(-0.925 * r, 0)
420 pt2 = QPoint(-0.85 * r, 0)
421 painter.drawLine(pt1, pt2)
422 pt1 = QPoint(0.925 * r, 0)
423 pt2 = QPoint(0.85 * r, 0)
424 painter.drawLine(pt1, pt2)
427 pen = QPen(Qt.white, r / 50., Qt.SolidLine)
439 self.drawOneRollGraduation(painter, deg, disp_text)
442 def drawYaw(self, painter):
443 center = self.adjdev.center()
444 r = self.getCenterRadius()
445 pen = QPen(Qt.red, r / 100., Qt.SolidLine)
447 font = QFont("Meiryo UI", 0, QFont.Bold)
448 font.setPointSizeF(self.FONT_SIZE * r)
449 painter.setFont(font)
450 if self.mode == "round":
451 y_txt = center.y() + r * 0.6
452 y1 = center.y() + r * 0.7
453 y2 = center.y() + r * 0.8
454 y3 = center.y() + r * 0.85
456 y_txt = center.y() + r * 1.
457 y1 = center.y() + r * 1.1
458 y2 = center.y() + r * 1.2
459 y3 = center.y() + r * 1.25
460 pt1 = QPoint(center.x(), y2)
461 pt2 = QPoint(center.x(), y3)
462 painter.drawLine(pt1, pt2)
464 # round to nearest angle multiple of step
465 a = self.yaw / self.YAW_REFLINES_STEP_ANGLE
467 a = int(a * self.YAW_REFLINES_STEP_ANGLE)
468 a -= self.YAW_REFLINES_STEP_ANGLE * (self.YAW_REFLINES_NUM_LINES / 2.)
469 angles = [ a + i * self.YAW_REFLINES_STEP_ANGLE
470 for i in range(self.YAW_REFLINES_NUM_LINES + 1) ]
475 pen = QPen(Qt.white, r / 50., Qt.SolidLine)
479 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
482 x = center.x() - ((r / (self.YAW_REFLINES_TOTAL_ANGLE / 2.)) *
483 (self.yaw - a) * self.YAW_REFLINES_SIZE_FACTOR)
484 pt_txt = QPoint(x, y_txt)
487 painter.drawLine(pt1, pt2)
488 if disp_text == False:
490 disp_val = ["N", "E", "S", "W"][(int(a)/90)%4]
491 metrics = painter.fontMetrics()
492 sz = metrics.size(Qt.TextSingleLine, disp_val)
493 txt = QRect(QPoint(0, 0), sz)
494 txt.moveCenter(pt_txt)
495 painter.drawText(txt, Qt.TextSingleLine, disp_val)
497 def drawSpeed(self, painter):
498 center = self.adjdev.center()
499 r = self.getCenterRadius()
500 pen = QPen(Qt.red, r / 100., Qt.SolidLine)
502 font = QFont("Meiryo UI", 0, QFont.Bold)
503 font.setPointSizeF(self.FONT_SIZE * r)
504 painter.setFont(font)
505 x1 = center.x() - 1.5 * r
506 x2 = center.x() - 1.6 * r
507 pt1 = QPoint(center.x() - 1.45 * r, center.y())
508 pt2 = QPoint(x2, center.y())
509 painter.drawLine(pt1, pt2)
511 # round to nearest angle multiple of step
512 s = self.speed / self.SPEED_REFLINES_STEP
514 s = int(s * self.SPEED_REFLINES_STEP)
515 s -= self.SPEED_REFLINES_STEP * (self.SPEED_REFLINES_NUM_LINES / 2.)
516 speeds = [ s + i * self.SPEED_REFLINES_STEP
517 for i in range(self.SPEED_REFLINES_NUM_LINES + 1) ]
519 if int(s) % int(self.SPEED_REFLINES_BOLD_STEP) == 0:
521 pen = QPen(Qt.white, r / 50., Qt.SolidLine)
525 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
528 y = center.y() + ((r / (self.SPEED_REFLINES_TOTAL/2.)) *
529 (self.speed - s) * self.SPEED_REFLINES_SIZE_FACTOR)
530 pt_txt = QPoint(center.x() + r * -1.75, y)
533 painter.drawLine(pt1, pt2)
534 if disp_text == False:
536 disp_val = str(int(s))
537 metrics = painter.fontMetrics()
538 sz = metrics.size(Qt.TextSingleLine, disp_val)
539 txt = QRect(QPoint(0, 0), sz)
540 txt.moveCenter(pt_txt)
541 painter.drawText(txt, Qt.TextSingleLine, disp_val)
543 def drawAlt(self, painter):
544 center = self.adjdev.center()
545 r = self.getCenterRadius()
546 pen = QPen(Qt.red, r / 100., Qt.SolidLine)
548 font = QFont("Meiryo UI", 0, QFont.Bold)
549 font.setPointSizeF(self.FONT_SIZE * r)
550 painter.setFont(font)
551 x1 = center.x() + 1.5 * r
552 x2 = center.x() + 1.6 * r
553 pt1 = QPoint(center.x() + 1.45 * r, center.y())
554 pt2 = QPoint(x2, center.y())
555 painter.drawLine(pt1, pt2)
557 # round to nearest angle multiple of step
558 a = self.alt / self.ALT_REFLINES_STEP
560 a = int(a * self.ALT_REFLINES_STEP)
561 a -= self.ALT_REFLINES_STEP * (self.ALT_REFLINES_NUM_LINES / 2.)
562 alts = [ a + i * self.ALT_REFLINES_STEP
563 for i in range(self.ALT_REFLINES_NUM_LINES + 1) ]
565 if int(a) % int(self.ALT_REFLINES_BOLD_STEP) == 0:
567 pen = QPen(Qt.white, r / 50., Qt.SolidLine)
571 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
574 y = center.y() + ((r / (self.ALT_REFLINES_TOTAL / 2.)) *
575 (self.alt - a) * self.ALT_REFLINES_SIZE_FACTOR)
576 pt_txt = QPoint(center.x() + r * 1.75, y)
579 painter.drawLine(pt1, pt2)
580 if disp_text == False:
582 disp_val = str(int(a))
583 metrics = painter.fontMetrics()
584 sz = metrics.size(Qt.TextSingleLine, disp_val)
585 txt = QRect(QPoint(0, 0), sz)
586 txt.moveCenter(pt_txt)
587 painter.drawText(txt, Qt.TextSingleLine, disp_val)
589 def drawReturnToHome(self, painter):
590 center = self.adjdev.center()
591 r = self.getCenterRadius()
592 dev_r = self.adjdev.width() / 2.
594 painter.translate(center.x(), center.y() - 0.9 * dev_r)
595 painter.rotate(self.yaw) # XXX
596 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
599 poly.append(QPoint(0., -0.08 * r))
600 poly.append(QPoint(0.04 * r, 0.08 * r))
601 poly.append(QPoint(-0.04 * r, 0.08 * r))
602 poly.append(QPoint(0., -0.08 * r))
603 path = QPainterPath()
604 path.addPolygon(poly)
605 brush = QBrush(Qt.darkGray)
606 painter.setBrush(brush)
607 painter.drawPath(path)
610 def drawTxtInfo(self, painter):
611 center = self.adjdev.center()
612 dev_r = self.adjdev.width() / 2.
613 r = self.getCenterRadius()
614 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
616 font = QFont("Meiryo UI", 0, QFont.Bold)
617 font.setPointSizeF(self.FONT_SIZE * r)
618 metrics = painter.fontMetrics()
620 sz = metrics.size(Qt.AlignLeft, self.left_txt)
621 txt = QRect(QPoint(0, 0), sz)
622 pt_txt = QPoint(center.x() + dev_r * -0.95,
623 center.y() + dev_r * -0.95)
624 txt.moveTopLeft(pt_txt)
625 painter.drawText(txt, Qt.AlignLeft, self.left_txt)
627 sz = metrics.size(Qt.AlignRight, self.right_txt)
628 txt = QRect(QPoint(0, 0), sz)
629 pt_txt = QPoint(center.x() + dev_r * 0.95,
630 center.y() + dev_r * -0.95)
631 txt.moveTopRight(pt_txt)
632 painter.drawText(txt, Qt.AlignRight, self.right_txt)
634 def setPitch(self, pitch):
635 """set the pitch in degrees, between -180 and 180"""
639 self.user_pitch = pitch
641 def setRoll(self, roll):
642 """set the roll in degrees, between -180 and 180"""
646 self.user_roll = roll
648 def setYaw(self, yaw):
649 """set the yaw in degrees, between 0 and 360"""
653 def setSpeed(self, speed):
655 self.user_speed = speed
657 def setAlt(self, alt):
661 def setReturnToHomeAngle(self, angle):
662 """set the left text"""
663 self.user_rthome = angle
665 def setLeftTxt(self, txt):
666 """set the right text"""
669 def setRightTxt(self, txt):
670 """set the left text"""
673 class OSDGraphicsView(QGraphicsView):
674 def __init__(self, parent=None):
675 super(OSDGraphicsView, self).__init__(parent)
676 self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
677 self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
678 self.setMinimumSize(200, 150)
679 def resizeEvent(self, event):
680 # use item 0 to resize automatically
681 self.fitInView(self.items()[0], Qt.KeepAspectRatio)
682 super(OSDGraphicsView, self).resizeEvent(event)
684 class Ui_OSD(QMainWindow):
685 def __init__(self, parent = None, mode = "camera", filename = None):
686 super(Ui_OSD, self).__init__(parent)
687 self.ui = qtosd_ui.Ui_MainWindow()
688 self.ui.setupUi(self)
691 self.filename = filename
693 self.osd = OSDWidget(mode = self.mode,
694 filename = self.filename)
695 self.osd.setObjectName("osd")
696 self.ui.pitchSlider.valueChanged[int].connect(self.changePitch)
697 self.ui.rollSlider.valueChanged[int].connect(self.changeRoll)
698 self.ui.yawSlider.valueChanged[int].connect(self.changeYaw)
699 self.ui.actionExit.triggered.connect(self.close)
701 self.scene = QGraphicsScene(self)
702 self.graphicsView = OSDGraphicsView(self.scene)
704 if self.mode == "camera":
705 self.videoItem = QGraphicsVideoItem()
706 self.videoItem.setSize(QSizeF(640, 480)) # XXX
707 self.scene.addItem(self.videoItem)
709 x = self.videoItem.boundingRect().width() / 2.0
710 y = self.videoItem.boundingRect().height() / 2.0
711 #self.videoItem.setTransform(
712 # QTransform().translate(x, y).rotate(70).translate(-x, -y))
716 self.scene.addWidget(self.osd)
717 self.ui.gridLayout.addWidget(self.graphicsView, 0, 1)
719 def initCamera(self):
720 # find camera devices and add them in the menu
721 cameraDevice = QByteArray()
722 videoDevicesGroup = QActionGroup(self)
723 videoDevicesGroup.setExclusive(True)
724 for deviceName in QCamera.availableDevices():
725 description = QCamera.deviceDescription(deviceName)
726 videoDeviceAction = QAction(description, videoDevicesGroup)
727 videoDeviceAction.setCheckable(True)
728 videoDeviceAction.setData(deviceName)
729 if cameraDevice.isEmpty():
730 cameraDevice = deviceName
731 videoDeviceAction.setChecked(True)
732 self.ui.menuDevices.addAction(videoDeviceAction)
733 videoDevicesGroup.triggered.connect(self.updateCameraDevice)
734 # select the first camera
735 self.setCamera(cameraDevice)
737 def setCamera(self, cameraDevice):
738 if cameraDevice.isEmpty():
739 self.camera = QCamera()
741 self.camera = QCamera(cameraDevice)
743 self.camera.stateChanged.connect(self.updateCameraState)
744 self.camera.error.connect(self.displayCameraError)
746 #self.ui.exposureCompensation.valueChanged.connect(
747 # self.setExposureCompensation)
749 self.camera.setCaptureMode(QCamera.CaptureViewfinder)
750 self.camera.setViewfinder(self.videoItem)
751 self.updateCameraState(self.camera.state())
754 #XXX stop camera? remove from scene?
756 def updateCameraDevice(self, action):
757 print "updateCameraDevice"
758 self.setCamera(action.data())
760 def updateCameraState(self, state):
761 print "updateCameraState %s"%(str(state))
763 def displayCameraError(self):
764 print "displayCameraError"
765 QMessageBox.warning(self, "Camera error", self.camera.errorString())
767 def keyPressEvent(self, event):
770 self.osd.setRoll(self.osd.user_roll + 2)
772 elif key == Qt.Key_L:
773 self.osd.setRoll(self.osd.user_roll - 2)
775 elif key == Qt.Key_I:
776 self.osd.setPitch(self.osd.user_pitch + 2)
778 elif key == Qt.Key_K:
779 self.osd.setPitch(self.osd.user_pitch - 2)
781 elif key == Qt.Key_Q:
783 elif not event.isAutoRepeat():
784 if key == Qt.Key_CameraFocus:
785 self.camera.searchAndLock()
788 super(Ui_OSD, self).keyPressEvent(event)
790 def keyReleaseEvent(self, event):
792 if event.isAutoRepeat():
794 if key == Qt.Key_CameraFocus:
797 super(Ui_OSD, self).keyReleaseEvent(event)
799 def retranslateUi(self, MainWindow):
800 MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
802 @pyqtSlot(int, name = "changePitch")
803 def changePitch(self, value):
804 self.osd.setPitch(value)
806 @pyqtSlot(int, name = "changeRoll")
807 def changeRoll(self, value):
808 self.osd.setRoll(value)
810 @pyqtSlot(int, name = "changeYaw")
811 def changeYaw(self, value):
812 self.osd.setYaw(value)
814 if __name__ == "__main__":
817 parser = argparse.ArgumentParser(description='OSD written in Qt.')
818 parser.add_argument('--mode', '-m', action="store",
819 choices=["round", "rectangle", "camera"],
820 help='display the widget as a round attitude meter')
821 parser.add_argument('--filename', '-f',
822 help='specify the log file')
823 args = parser.parse_args()
825 app = QApplication(sys.argv)
826 ui = Ui_OSD(filename = args.filename, mode = args.mode)
828 sys.exit(app.exec_())