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, QtGui, QtWidgets
27 from PyQt5.QtCore import pyqtSlot
33 _fromUtf8 = QtCore.QString.fromUtf8
34 except AttributeError:
39 _encoding = QtWidgets.QApplication.UnicodeUTF8
40 def _translate(context, text, disambig):
41 return QtWidgets.QApplication.translate(context, text, disambig, _encoding)
42 except AttributeError:
43 def _translate(context, text, disambig):
44 return QtWidgets.QApplication.translate(context, text, disambig)
46 class OSDWidget(QtWidgets.QWidget):
47 def __init__(self, roundWidget = False, filename = None):
48 super(OSDWidget, self).__init__()
50 self.roundWidget = roundWidget
52 # parameters that will be modified by the user
59 self.left_txt = "14.8v / 34A\n1.5Ah" # XXX
60 self.right_txt = "23:03 since take-off\n1.4 km to home\nRSSI 60dB" # XXX
61 self.user_frame_cb = None
69 # filtered parameters (0 = no filter, 1 = infinite filter)
70 self.pitch_filter_coef = 0.8
71 self.roll_filter_coef = 0.8
72 self.yaw_filter_coef = 0.8
73 self.speed_filter_coef = 0.8
74 self.alt_filter_coef = 0.8
75 self.rthome_filter_coef = 0.8
76 # QRect representing the viewport
78 # QRect representing the viewport, adjusted to a square
80 self.setMinimumSize(250, 250)
82 # how many degrees between pitch reference lines
85 self.PITCH_REFLINES_STEP_ANGLE = 10.
86 self.PITCH_REFLINES_BOLD_STEP_ANGLE = 30.
87 self.PITCH_REFLINES_NUM_LINES = 6
90 self.PITCH_REFLINES_STEP_ANGLE = 5.
91 self.PITCH_REFLINES_BOLD_STEP_ANGLE = 10.
92 self.PITCH_REFLINES_NUM_LINES = 4
94 self.PITCH_REFLINES_TOTAL_ANGLE = (self.PITCH_REFLINES_STEP_ANGLE *
95 self.PITCH_REFLINES_NUM_LINES)
96 # in fraction of radius
98 # how many degrees between yaw reference lines
99 self.YAW_REFLINES_STEP_ANGLE = 15.
100 self.YAW_REFLINES_NUM_LINES = 12
101 self.YAW_REFLINES_TOTAL_ANGLE = (self.YAW_REFLINES_STEP_ANGLE *
102 self.YAW_REFLINES_NUM_LINES)
103 self.YAW_REFLINES_SIZE_FACTOR = 0.5
105 self.SPEED_REFLINES_STEP = 10.
106 self.SPEED_REFLINES_BOLD_STEP = 50.
107 self.SPEED_REFLINES_NUM_LINES = 10
108 self.SPEED_REFLINES_TOTAL = (self.SPEED_REFLINES_STEP *
109 self.SPEED_REFLINES_NUM_LINES)
110 self.SPEED_REFLINES_SIZE_FACTOR = 0.7
112 self.ALT_REFLINES_STEP = 100.
113 self.ALT_REFLINES_BOLD_STEP = 500.
114 self.ALT_REFLINES_NUM_LINES = 10
115 self.ALT_REFLINES_TOTAL = (self.ALT_REFLINES_STEP *
116 self.ALT_REFLINES_NUM_LINES)
117 self.ALT_REFLINES_SIZE_FACTOR = 0.7
120 self.fpv = serialfpv.SerialFPV()
121 self.fpv.open_file(filename)
124 self.timer = QtCore.QTimer(self)
125 self.timer.timeout.connect(self.frameTimerCb)
126 self.timer.start(1000. / self.FPS)
128 @pyqtSlot(name = "paintEvent")
129 def paintEvent(self, evt):
130 """Paint callback, this is the entry point for all drawings."""
131 painter = QtGui.QPainter()
133 painter.setRenderHint(QtGui.QPainter.Antialiasing)
134 self.dev = evt.rect()
135 self.min_dim = min(self.dev.right(), self.dev.bottom())
136 self.max_dim = max(self.dev.right(), self.dev.bottom())
137 self.adjdev = QtCore.QRect(0, 0, self.min_dim, self.min_dim)
138 self.adjdev.moveCenter(self.dev.center())
142 @pyqtSlot(name = "frameTimerCb")
143 def frameTimerCb(self):
144 # read from SerialFPV object
146 self.fpv.update_state()
147 self.user_speed = self.user_speed + 1
148 self.setRoll(self.fpv.roll)
149 self.setPitch(self.fpv.pitch)
150 self.setYaw(self.fpv.yaw)
152 # avoid filter bugs when changing between 180 and -180
153 self.pitch += round((self.user_pitch - self.pitch) / 360.) * 360
154 self.pitch = (self.pitch * self.pitch_filter_coef +
155 self.user_pitch * (1. - self.pitch_filter_coef))
156 self.roll += round((self.user_roll - self.roll) / 360.) * 360
157 self.roll = (self.roll * self.roll_filter_coef +
158 self.user_roll * (1. - self.roll_filter_coef))
159 self.yaw += round((self.user_yaw - self.yaw) / 360.) * 360
160 self.yaw = (self.yaw * self.yaw_filter_coef +
161 self.user_yaw * (1. - self.yaw_filter_coef))
162 self.speed = (self.speed * self.speed_filter_coef +
163 self.user_speed * (1. - self.speed_filter_coef))
164 self.alt = (self.alt * self.alt_filter_coef +
165 self.user_alt * (1. - self.alt_filter_coef))
166 self.rthome += round((self.user_rthome - self.rthome) / 360.) * 360
167 self.rthome = (self.rthome * self.rthome_filter_coef +
168 self.user_rthome * (1. - self.rthome_filter_coef))
171 def draw(self, painter):
172 """Draw the widget."""
174 self.drawHorizonRound(painter)
176 self.drawHorizon(painter)
177 self.drawPitchGraduation(painter)
178 self.drawRollGraduation(painter)
179 self.drawYaw(painter)
180 self.drawCenterRef(painter)
181 if self.roundWidget == False:
182 self.drawSpeed(painter)
183 self.drawAlt(painter)
184 self.drawReturnToHome(painter)
185 self.drawTxtInfo(painter)
187 def getSkyBrush(self):
188 """return the color gradient for the sky (blue)"""
189 sky_gradient = QtGui.QLinearGradient(self.adjdev.topLeft(),
190 self.adjdev.bottomRight())
191 color1 = QtCore.Qt.blue
192 color2 = QtCore.Qt.darkBlue
193 sky_gradient.setColorAt(0, color1)
194 sky_gradient.setColorAt(.8, color2)
197 def getGroundBrush(self):
198 """return the color gradient for the ground (marron)"""
199 ground_gradient = QtGui.QLinearGradient(self.adjdev.topLeft(),
200 self.adjdev.bottomRight())
201 color1 = QtGui.QColor(140, 100, 80)
202 color2 = QtGui.QColor(140, 100, 40)
203 ground_gradient.setColorAt(0, color1)
204 ground_gradient.setColorAt(.8, color2)
205 return ground_gradient
207 def getCenterRadius(self):
208 """Return the radius of the widget circle"""
210 return self.adjdev.width() / 2.
212 return self.adjdev.width() / 3.5
214 def drawHorizonRound(self, painter):
215 """Draw the horizon for round widget: the sky in blue,
216 the ground in marron."""
218 # set local pitch and roll
227 pitch = -180. - pitch
232 # we have to draw a partial circle delimited by its chord, define
233 # where the chord starts
234 start_angle = math.degrees(math.asin(pitch / 90.)) - roll
235 span = 2 * math.degrees(math.asin(pitch / 90.))
238 painter.setBrush(self.getSkyBrush())
239 # startAngle and spanAngle must be specified in 1/16th of a degree
240 painter.drawChord(self.adjdev, 16 * start_angle, 16 * (180. - span))
243 painter.setBrush(self.getGroundBrush())
244 # startAngle and spanAngle must be specified in 1/16th of a degree
245 painter.drawChord(self.adjdev, 16 * start_angle, -16 * (180. + span))
247 def drawHorizon(self, painter):
248 """Draw the horizon: the sky in blue, the ground in marron."""
250 painter.setBrush(self.getSkyBrush())
251 painter.drawRect(self.dev)
252 center = self.adjdev.center()
253 r = self.getCenterRadius()
254 # radius of the adjusted screen (same than r if roundWidget = True)
255 dev_r = self.adjdev.width() / 2.
257 if self.pitch < -90.:
258 pitch = -180 - self.pitch
259 elif self.pitch < 90.:
262 pitch = 180 - self.pitch
263 y_off = (pitch / self.CAM_ANGLE) * dev_r
264 painter.translate(center.x(), center.y())
266 ground_rect = QtCore.QRect(0, 0, self.max_dim * 5., self.max_dim * 5.)
267 if self.pitch < 90. and self.pitch > -90.:
268 ground_rect.moveCenter(QtCore.QPoint(0, -y_off + ground_rect.width()/2.))
270 ground_rect.moveCenter(QtCore.QPoint(0, y_off - ground_rect.width()/2.))
271 painter.setBrush(self.getGroundBrush())
272 painter.drawRect(ground_rect)
275 def drawCenterRef(self, painter):
276 """Draw the cross on the middle of the OSD"""
277 center = self.adjdev.center()
278 r = self.getCenterRadius()
279 pen = QtGui.QPen(QtCore.Qt.red, r / 100., QtCore.Qt.SolidLine)
281 pt1 = QtCore.QPoint(center.x() - 0.05 * r, center.y())
282 pt2 = QtCore.QPoint(center.x() + 0.05 * r, center.y())
283 painter.drawLine(pt1, pt2)
284 pt1 = QtCore.QPoint(center.x(), center.y() + -0.025 * r)
285 pt2 = QtCore.QPoint(center.x(), center.y() + 0.025 * r)
286 painter.drawLine(pt1, pt2)
288 def drawPitchGraduation(self, painter):
289 """Draw the pitch graduations."""
290 # change the reference
292 center = self.adjdev.center()
293 r = self.getCenterRadius()
294 # radius of the adjusted screen (same than r if roundWidget = True)
295 dev_r = self.adjdev.width() / 2.
297 x_off = (self.pitch / self.CAM_ANGLE) * dev_r * math.sin(math.radians(roll))
298 y_off = (self.pitch / self.CAM_ANGLE) * dev_r * math.cos(math.radians(roll))
299 painter.translate(center.x() + x_off, center.y() - y_off)
303 pen = QtGui.QPen(QtCore.Qt.white, r / 100., QtCore.Qt.SolidLine)
305 font = QtGui.QFont("Meiryo UI", 0, QtGui.QFont.Bold)
306 font.setPointSizeF(self.FONT_SIZE * r)
307 painter.setFont(font)
309 # round to nearest angle that is a multiple of step
310 a = self.pitch / self.PITCH_REFLINES_STEP_ANGLE
312 a = int(a * self.PITCH_REFLINES_STEP_ANGLE)
313 a -= self.PITCH_REFLINES_STEP_ANGLE * (self.PITCH_REFLINES_NUM_LINES / 2.)
314 angles = [ a + i * self.PITCH_REFLINES_STEP_ANGLE
315 for i in range(self.PITCH_REFLINES_NUM_LINES + 1) ]
318 if int(a) % int(self.PITCH_REFLINES_BOLD_STEP_ANGLE) != 0:
319 pen = QtGui.QPen(QtCore.Qt.white, r / 100., QtCore.Qt.SolidLine)
321 pt1 = QtCore.QPoint(-0.05 * r, dev_r / self.CAM_ANGLE * a)
322 pt2 = QtCore.QPoint(0.05 * r, dev_r / self.CAM_ANGLE * a)
323 painter.drawLine(pt1, pt2)
327 pen = QtGui.QPen(QtCore.Qt.white, r / 50., QtCore.Qt.SolidLine)
329 pt1 = QtCore.QPoint(-0.2 * r, dev_r / self.CAM_ANGLE * a)
330 pt2 = QtCore.QPoint(0.2 * r, dev_r / self.CAM_ANGLE * a)
331 painter.drawLine(pt1, pt2)
336 disp_val = 180. - disp_val
338 disp_val = -180. - disp_val
339 disp_val = str(int(disp_val))
340 metrics = painter.fontMetrics()
341 sz = metrics.size(QtCore.Qt.TextSingleLine, disp_val)
342 lefttxt = QtCore.QRect(QtCore.QPoint(0, 0), sz)
343 lefttxt.moveCenter(pt1 - QtCore.QPoint(0.2 * r, 0))
346 #brush = QtGui.QBrush(QtCore.Qt.white)
347 #painter.setBrush(brush)
348 #pen.setColor(QtCore.Qt.black);
350 #path = QtGui.QPainterPath()
351 #path.addText(lefttxt.center(), font, disp_val)
352 #painter.drawPath(path)
353 painter.drawText(lefttxt, QtCore.Qt.TextSingleLine, disp_val)
355 # flip the right text
357 painter.translate(pt2 + QtCore.QPoint(0.2 * r, 0))
359 painter.translate(-pt2 - QtCore.QPoint(0.2 * r, 0))
360 righttxt = QtCore.QRect(QtCore.QPoint(0, 0), sz)
361 righttxt.moveCenter(pt2 + QtCore.QPoint(0.2 * r, 0))
362 #path = QtGui.QPainterPath()
363 #path.addText(righttxt.center(), font, disp_val)
364 #painter.drawPath(path)
365 painter.drawText(righttxt, QtCore.Qt.TextSingleLine, disp_val)
370 def drawOneRollGraduation(self, painter, deg, disp_text):
371 # draw the graduiation
372 r = self.getCenterRadius()
373 center = self.adjdev.center()
374 x = center.x() - math.cos(math.radians(deg)) * r
375 y = center.y() - math.sin(math.radians(deg)) * r
376 pt = QtCore.QPoint(x, y)
377 path = QtGui.QPainterPath()
380 pt2 = path.pointAtPercent(0.075) # graduation len is 7.5% of the radius
381 painter.drawLine(pt, pt2)
383 if disp_text == True:
384 pt_txt = path.pointAtPercent(0.2)
385 font = QtGui.QFont("Meiryo UI", 0, QtGui.QFont.Bold)
386 font.setPointSizeF(self.FONT_SIZE * r)
387 painter.setFont(font)
390 disp_val = 180. - disp_val
391 disp_val = str(int(disp_val))
392 metrics = painter.fontMetrics()
393 sz = metrics.size(QtCore.Qt.TextSingleLine, disp_val)
394 txt = QtCore.QRect(QtCore.QPoint(0, 0), sz)
395 txt.moveCenter(pt_txt.toPoint())
396 painter.drawText(txt, QtCore.Qt.TextSingleLine, disp_val)
398 def drawRollGraduation(self, painter):
399 """Draw the roll graduations."""
400 center = self.adjdev.center()
401 r = self.getCenterRadius()
403 # draw the red reference lines (pitch 0)
405 painter.translate(center.x(), center.y())
406 painter.rotate(self.roll)
407 pen = QtGui.QPen(QtCore.Qt.red, r / 100., QtCore.Qt.SolidLine)
409 pt1 = QtCore.QPoint(-0.925 * r, 0)
410 pt2 = QtCore.QPoint(-0.85 * r, 0)
411 painter.drawLine(pt1, pt2)
412 pt1 = QtCore.QPoint(0.925 * r, 0)
413 pt2 = QtCore.QPoint(0.85 * r, 0)
414 painter.drawLine(pt1, pt2)
417 pen = QtGui.QPen(QtCore.Qt.white, r / 50., QtCore.Qt.SolidLine)
429 self.drawOneRollGraduation(painter, deg, disp_text)
432 def drawYaw(self, painter):
433 center = self.adjdev.center()
434 r = self.getCenterRadius()
435 pen = QtGui.QPen(QtCore.Qt.red, r / 100., QtCore.Qt.SolidLine)
437 font = QtGui.QFont("Meiryo UI", 0, QtGui.QFont.Bold)
438 font.setPointSizeF(self.FONT_SIZE * r)
439 painter.setFont(font)
440 if self.roundWidget == True:
441 y_txt = center.y() + r * 0.6
442 y1 = center.y() + r * 0.7
443 y2 = center.y() + r * 0.8
444 y3 = center.y() + r * 0.85
446 y_txt = center.y() + r * 1.
447 y1 = center.y() + r * 1.1
448 y2 = center.y() + r * 1.2
449 y3 = center.y() + r * 1.25
450 pt1 = QtCore.QPoint(center.x(), y2)
451 pt2 = QtCore.QPoint(center.x(), y3)
452 painter.drawLine(pt1, pt2)
454 # round to nearest angle multiple of step
455 a = self.yaw / self.YAW_REFLINES_STEP_ANGLE
457 a = int(a * self.YAW_REFLINES_STEP_ANGLE)
458 a -= self.YAW_REFLINES_STEP_ANGLE * (self.YAW_REFLINES_NUM_LINES / 2.)
459 angles = [ a + i * self.YAW_REFLINES_STEP_ANGLE
460 for i in range(self.YAW_REFLINES_NUM_LINES + 1) ]
465 pen = QtGui.QPen(QtCore.Qt.white, r / 50., QtCore.Qt.SolidLine)
469 pen = QtGui.QPen(QtCore.Qt.white, r / 100., QtCore.Qt.SolidLine)
472 x = center.x() - ((r / (self.YAW_REFLINES_TOTAL_ANGLE / 2.)) *
473 (self.yaw - a) * self.YAW_REFLINES_SIZE_FACTOR)
474 pt_txt = QtCore.QPoint(x, y_txt)
475 pt1 = QtCore.QPoint(x, y1)
476 pt2 = QtCore.QPoint(x, y2)
477 painter.drawLine(pt1, pt2)
478 if disp_text == False:
480 disp_val = ["N", "E", "S", "W"][(int(a)/90)%4]
481 metrics = painter.fontMetrics()
482 sz = metrics.size(QtCore.Qt.TextSingleLine, disp_val)
483 txt = QtCore.QRect(QtCore.QPoint(0, 0), sz)
484 txt.moveCenter(pt_txt)
485 painter.drawText(txt, QtCore.Qt.TextSingleLine, disp_val)
487 def drawSpeed(self, painter):
488 center = self.adjdev.center()
489 r = self.getCenterRadius()
490 pen = QtGui.QPen(QtCore.Qt.red, r / 100., QtCore.Qt.SolidLine)
492 font = QtGui.QFont("Meiryo UI", 0, QtGui.QFont.Bold)
493 font.setPointSizeF(self.FONT_SIZE * r)
494 painter.setFont(font)
495 x1 = center.x() - 1.5 * r
496 x2 = center.x() - 1.6 * r
497 pt1 = QtCore.QPoint(center.x() - 1.45 * r, center.y())
498 pt2 = QtCore.QPoint(x2, center.y())
499 painter.drawLine(pt1, pt2)
501 # round to nearest angle multiple of step
502 s = self.speed / self.SPEED_REFLINES_STEP
504 s = int(s * self.SPEED_REFLINES_STEP)
505 s -= self.SPEED_REFLINES_STEP * (self.SPEED_REFLINES_NUM_LINES / 2.)
506 speeds = [ s + i * self.SPEED_REFLINES_STEP
507 for i in range(self.SPEED_REFLINES_NUM_LINES + 1) ]
509 if int(s) % int(self.SPEED_REFLINES_BOLD_STEP) == 0:
511 pen = QtGui.QPen(QtCore.Qt.white, r / 50., QtCore.Qt.SolidLine)
515 pen = QtGui.QPen(QtCore.Qt.white, r / 100., QtCore.Qt.SolidLine)
518 y = center.y() + ((r / (self.SPEED_REFLINES_TOTAL/2.)) *
519 (self.speed - s) * self.SPEED_REFLINES_SIZE_FACTOR)
520 pt_txt = QtCore.QPoint(center.x() + r * -1.75, y)
521 pt1 = QtCore.QPoint(x1, y)
522 pt2 = QtCore.QPoint(x2, y)
523 painter.drawLine(pt1, pt2)
524 if disp_text == False:
526 disp_val = str(int(s))
527 metrics = painter.fontMetrics()
528 sz = metrics.size(QtCore.Qt.TextSingleLine, disp_val)
529 txt = QtCore.QRect(QtCore.QPoint(0, 0), sz)
530 txt.moveCenter(pt_txt)
531 painter.drawText(txt, QtCore.Qt.TextSingleLine, disp_val)
533 def drawAlt(self, painter):
534 center = self.adjdev.center()
535 r = self.getCenterRadius()
536 pen = QtGui.QPen(QtCore.Qt.red, r / 100., QtCore.Qt.SolidLine)
538 font = QtGui.QFont("Meiryo UI", 0, QtGui.QFont.Bold)
539 font.setPointSizeF(self.FONT_SIZE * r)
540 painter.setFont(font)
541 x1 = center.x() + 1.5 * r
542 x2 = center.x() + 1.6 * r
543 pt1 = QtCore.QPoint(center.x() + 1.45 * r, center.y())
544 pt2 = QtCore.QPoint(x2, center.y())
545 painter.drawLine(pt1, pt2)
547 # round to nearest angle multiple of step
548 a = self.alt / self.ALT_REFLINES_STEP
550 a = int(a * self.ALT_REFLINES_STEP)
551 a -= self.ALT_REFLINES_STEP * (self.ALT_REFLINES_NUM_LINES / 2.)
552 alts = [ a + i * self.ALT_REFLINES_STEP
553 for i in range(self.ALT_REFLINES_NUM_LINES + 1) ]
555 if int(a) % int(self.ALT_REFLINES_BOLD_STEP) == 0:
557 pen = QtGui.QPen(QtCore.Qt.white, r / 50., QtCore.Qt.SolidLine)
561 pen = QtGui.QPen(QtCore.Qt.white, r / 100., QtCore.Qt.SolidLine)
564 y = center.y() + ((r / (self.ALT_REFLINES_TOTAL / 2.)) *
565 (self.alt - a) * self.ALT_REFLINES_SIZE_FACTOR)
566 pt_txt = QtCore.QPoint(center.x() + r * 1.75, y)
567 pt1 = QtCore.QPoint(x1, y)
568 pt2 = QtCore.QPoint(x2, y)
569 painter.drawLine(pt1, pt2)
570 if disp_text == False:
572 disp_val = str(int(a))
573 metrics = painter.fontMetrics()
574 sz = metrics.size(QtCore.Qt.TextSingleLine, disp_val)
575 txt = QtCore.QRect(QtCore.QPoint(0, 0), sz)
576 txt.moveCenter(pt_txt)
577 painter.drawText(txt, QtCore.Qt.TextSingleLine, disp_val)
579 def drawReturnToHome(self, painter):
580 center = self.adjdev.center()
581 r = self.getCenterRadius()
582 dev_r = self.adjdev.width() / 2.
584 painter.translate(center.x(), center.y() - 0.9 * dev_r)
585 painter.rotate(self.yaw) # XXX
586 pen = QtGui.QPen(QtCore.Qt.white, r / 100., QtCore.Qt.SolidLine)
588 poly = QtGui.QPolygonF()
589 poly.append(QtCore.QPoint(0., -0.08 * r))
590 poly.append(QtCore.QPoint(0.04 * r, 0.08 * r))
591 poly.append(QtCore.QPoint(-0.04 * r, 0.08 * r))
592 poly.append(QtCore.QPoint(0., -0.08 * r))
593 path = QtGui.QPainterPath()
594 path.addPolygon(poly)
595 brush = QtGui.QBrush(QtCore.Qt.darkGray)
596 painter.setBrush(brush)
597 painter.drawPath(path)
600 def drawTxtInfo(self, painter):
601 center = self.adjdev.center()
602 dev_r = self.adjdev.width() / 2.
603 r = self.getCenterRadius()
604 pen = QtGui.QPen(QtCore.Qt.white, r / 100., QtCore.Qt.SolidLine)
606 font = QtGui.QFont("Meiryo UI", 0, QtGui.QFont.Bold)
607 font.setPointSizeF(self.FONT_SIZE * r)
608 metrics = painter.fontMetrics()
610 sz = metrics.size(QtCore.Qt.AlignLeft, self.left_txt)
611 txt = QtCore.QRect(QtCore.QPoint(0, 0), sz)
612 pt_txt = QtCore.QPoint(center.x() + dev_r * -0.95,
613 center.y() + dev_r * -0.95)
614 txt.moveTopLeft(pt_txt)
615 painter.drawText(txt, QtCore.Qt.AlignLeft, self.left_txt)
617 sz = metrics.size(QtCore.Qt.AlignRight, self.right_txt)
618 txt = QtCore.QRect(QtCore.QPoint(0, 0), sz)
619 pt_txt = QtCore.QPoint(center.x() + dev_r * 0.95,
620 center.y() + dev_r * -0.95)
621 txt.moveTopRight(pt_txt)
622 painter.drawText(txt, QtCore.Qt.AlignRight, self.right_txt)
624 def setPitch(self, pitch):
625 """set the pitch in degrees, between -180 and 180"""
629 self.user_pitch = pitch
631 def setRoll(self, roll):
632 """set the roll in degrees, between -180 and 180"""
636 self.user_roll = roll
638 def setYaw(self, yaw):
639 """set the yaw in degrees, between 0 and 360"""
643 def setSpeed(self, speed):
645 self.user_speed = speed
647 def setAlt(self, alt):
651 def setReturnToHomeAngle(self, angle):
652 """set the left text"""
653 self.user_rthome = angle
655 def setLeftTxt(self, txt):
656 """set the right text"""
659 def setRightTxt(self, txt):
660 """set the left text"""
663 class Ui_MainWindow(QtWidgets.QMainWindow):
664 def __init__(self, parent = None, roundWidget = False, filename = None):
665 super(Ui_MainWindow, self).__init__(parent)
666 self.ui = qtosd_ui.Ui_MainWindow()
667 self.ui.setupUi(self)
669 self.roundWidget = roundWidget
670 self.filename = filename
672 self.osd = OSDWidget(roundWidget = self.roundWidget,
673 filename = self.filename)
674 self.ui.gridLayout.addWidget(self.osd, 0, 1)
675 self.ui.pitchSlider.valueChanged[int].connect(self.changePitch)
676 self.ui.rollSlider.valueChanged[int].connect(self.changeRoll)
677 self.ui.yawSlider.valueChanged[int].connect(self.changeYaw)
678 self.ui.actionExit.triggered.connect(self.close)
680 def retranslateUi(self, MainWindow):
681 MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
683 @pyqtSlot(int, name = "changePitch")
684 def changePitch(self, value):
685 self.osd.setPitch(value)
687 @pyqtSlot(int, name = "changeRoll")
688 def changeRoll(self, value):
689 self.osd.setRoll(value)
691 @pyqtSlot(int, name = "changeYaw")
692 def changeYaw(self, value):
693 self.osd.setYaw(value)
695 if __name__ == "__main__":
698 parser = argparse.ArgumentParser(description='OSD written in Qt.')
699 parser.add_argument('--round', '-r', action="store_true",
700 help='display the widget as a round attitude meter')
701 parser.add_argument('--filename', '-f',
702 help='specify the log file')
703 args = parser.parse_args()
705 app = QtWidgets.QApplication(sys.argv)
706 ui = Ui_MainWindow(filename = args.filename, roundWidget = args.round)
708 sys.exit(app.exec_())