add some keyboard shortcuts
[fpv.git] / qtosd / qtosd.py
1 # -*- coding: utf-8 -*-
2
3 # OSD (on screen display) written in Qt
4 # Copyright (C) 2015 Olivier Matz <zer0@droids-corp.org>
5 #
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.
10 #
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.
15 #
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/>.
18
19 # Inspired from QcGauge
20 # Copyright (C) 2015 Hadj Tahar Berrima
21 # http://pytricity.com/qt-artificial-horizon-custom-widget/
22
23 import math
24 import argparse
25
26 from PyQt5 import QtCore
27 from PyQt5.QtCore import (pyqtSlot, QTimer, QRect, QPoint, Qt)
28 from PyQt5.QtGui import (QPainter, QColor, QPen, QBrush, QLinearGradient, QFont,
29                          QPainterPath, QPolygonF)
30 from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget)
31 import serialfpv
32 import qtosd_ui
33
34 try:
35     _fromUtf8 = QtCore.QString.fromUtf8
36 except AttributeError:
37     def _fromUtf8(s):
38         return s
39
40 try:
41     _encoding = QApplication.UnicodeUTF8
42     def _translate(context, text, disambig):
43         return QApplication.translate(context, text, disambig, _encoding)
44 except AttributeError:
45     def _translate(context, text, disambig):
46         return QApplication.translate(context, text, disambig)
47
48 class OSDWidget(QWidget):
49     def __init__(self, roundWidget = False, filename = None):
50         super(OSDWidget, self).__init__()
51         # init parameters
52         self.roundWidget = roundWidget
53         self.fpv = None
54         # parameters that will be modified by the user
55         self.user_pitch = 0
56         self.user_roll = 0
57         self.user_yaw = 0
58         self.user_speed = 0
59         self.user_alt = 0
60         self.user_rthome = 0
61         self.left_txt = "14.8v / 34A\n1.5Ah" # XXX
62         self.right_txt = "23:03 since take-off\n1.4 km to home\nRSSI 60dB" # XXX
63         self.user_frame_cb = None
64         # filtered parameters
65         self.pitch = 0
66         self.roll = 0
67         self.yaw = 0
68         self.speed = 0
69         self.alt = 0
70         self.rthome = 0
71         # filtered parameters (0 = no filter, 1 = infinite filter)
72         self.pitch_filter_coef = 0.8
73         self.roll_filter_coef = 0.8
74         self.yaw_filter_coef = 0.8
75         self.speed_filter_coef = 0.8
76         self.alt_filter_coef = 0.8
77         self.rthome_filter_coef = 0.8
78         # QRect representing the viewport
79         self.dev = None
80         # QRect representing the viewport, adjusted to a square
81         self.adjdev = None
82         self.setMinimumSize(250, 250)
83
84         # how many degrees between pitch reference lines
85         if roundWidget:
86             self.CAM_ANGLE = 90.
87             self.PITCH_REFLINES_STEP_ANGLE = 10.
88             self.PITCH_REFLINES_BOLD_STEP_ANGLE = 30.
89             self.PITCH_REFLINES_NUM_LINES = 6
90         else:
91             self.CAM_ANGLE = 40.
92             self.PITCH_REFLINES_STEP_ANGLE = 5.
93             self.PITCH_REFLINES_BOLD_STEP_ANGLE = 10.
94             self.PITCH_REFLINES_NUM_LINES = 4
95
96         self.PITCH_REFLINES_TOTAL_ANGLE = (self.PITCH_REFLINES_STEP_ANGLE *
97                                            self.PITCH_REFLINES_NUM_LINES)
98         # in fraction of radius
99         self.FONT_SIZE = 0.06
100         # how many degrees between yaw reference lines
101         self.YAW_REFLINES_STEP_ANGLE = 15.
102         self.YAW_REFLINES_NUM_LINES = 12
103         self.YAW_REFLINES_TOTAL_ANGLE = (self.YAW_REFLINES_STEP_ANGLE *
104                                          self.YAW_REFLINES_NUM_LINES)
105         self.YAW_REFLINES_SIZE_FACTOR = 0.5
106
107         self.SPEED_REFLINES_STEP = 10.
108         self.SPEED_REFLINES_BOLD_STEP = 50.
109         self.SPEED_REFLINES_NUM_LINES = 10
110         self.SPEED_REFLINES_TOTAL = (self.SPEED_REFLINES_STEP *
111                                      self.SPEED_REFLINES_NUM_LINES)
112         self.SPEED_REFLINES_SIZE_FACTOR = 0.7
113
114         self.ALT_REFLINES_STEP = 100.
115         self.ALT_REFLINES_BOLD_STEP = 500.
116         self.ALT_REFLINES_NUM_LINES = 10
117         self.ALT_REFLINES_TOTAL = (self.ALT_REFLINES_STEP *
118                                    self.ALT_REFLINES_NUM_LINES)
119         self.ALT_REFLINES_SIZE_FACTOR = 0.7
120
121         if filename:
122             self.fpv = serialfpv.SerialFPV()
123             self.fpv.open_file(filename)
124
125         self.FPS = 50.
126         self.timer = QTimer(self)
127         self.timer.timeout.connect(self.frameTimerCb)
128         self.timer.start(1000. / self.FPS)
129
130     @pyqtSlot(name = "paintEvent")
131     def paintEvent(self, evt):
132         """Paint callback, this is the entry point for all drawings."""
133         painter = QPainter()
134         painter.begin(self)
135         painter.setRenderHint(QPainter.Antialiasing)
136         self.dev = evt.rect()
137         self.min_dim = min(self.dev.right(), self.dev.bottom())
138         self.max_dim = max(self.dev.right(), self.dev.bottom())
139         self.adjdev = QRect(0, 0, self.min_dim, self.min_dim)
140         self.adjdev.moveCenter(self.dev.center())
141         self.draw(painter)
142         painter.end()
143
144     @pyqtSlot(name = "frameTimerCb")
145     def frameTimerCb(self):
146         # read from SerialFPV object
147         if self.fpv:
148             self.fpv.update_state()
149             self.user_speed = self.user_speed + 1
150             self.setRoll(self.fpv.roll)
151             self.setPitch(self.fpv.pitch)
152             self.setYaw(self.fpv.yaw)
153
154         # avoid filter bugs when changing between 180 and -180
155         self.pitch += round((self.user_pitch - self.pitch) / 360.) * 360
156         self.pitch = (self.pitch * self.pitch_filter_coef +
157                     self.user_pitch * (1. - self.pitch_filter_coef))
158         self.roll += round((self.user_roll - self.roll) / 360.) * 360
159         self.roll = (self.roll * self.roll_filter_coef +
160                     self.user_roll * (1. - self.roll_filter_coef))
161         self.yaw += round((self.user_yaw - self.yaw) / 360.) * 360
162         self.yaw = (self.yaw * self.yaw_filter_coef +
163                     self.user_yaw * (1. - self.yaw_filter_coef))
164         self.speed = (self.speed * self.speed_filter_coef +
165                     self.user_speed * (1. - self.speed_filter_coef))
166         self.alt = (self.alt * self.alt_filter_coef +
167                     self.user_alt * (1. - self.alt_filter_coef))
168         self.rthome += round((self.user_rthome - self.rthome) / 360.) * 360
169         self.rthome = (self.rthome * self.rthome_filter_coef +
170                     self.user_rthome * (1. - self.rthome_filter_coef))
171         self.update()
172
173     def draw(self, painter):
174         """Draw the widget."""
175         if self.roundWidget:
176             self.drawHorizonRound(painter)
177         else:
178             self.drawHorizon(painter)
179         self.drawPitchGraduation(painter)
180         self.drawRollGraduation(painter)
181         self.drawYaw(painter)
182         self.drawCenterRef(painter)
183         if self.roundWidget == False:
184             self.drawSpeed(painter)
185             self.drawAlt(painter)
186             self.drawReturnToHome(painter)
187             self.drawTxtInfo(painter)
188
189     def getSkyBrush(self):
190         """return the color gradient for the sky (blue)"""
191         sky_gradient = QLinearGradient(self.adjdev.topLeft(),
192                                        self.adjdev.bottomRight())
193         color1 = Qt.blue
194         color2 = Qt.darkBlue
195         sky_gradient.setColorAt(0, color1)
196         sky_gradient.setColorAt(.8, color2)
197         return sky_gradient
198
199     def getGroundBrush(self):
200         """return the color gradient for the ground (marron)"""
201         ground_gradient = QLinearGradient(self.adjdev.topLeft(),
202                                           self.adjdev.bottomRight())
203         color1 = QColor(140, 100, 80)
204         color2 = QColor(140, 100, 40)
205         ground_gradient.setColorAt(0, color1)
206         ground_gradient.setColorAt(.8, color2)
207         return ground_gradient
208
209     def getCenterRadius(self):
210         """Return the radius of the widget circle"""
211         if self.roundWidget:
212             return self.adjdev.width() / 2.
213         else:
214             return self.adjdev.width() / 3.5
215
216     def drawHorizonRound(self, painter):
217         """Draw the horizon for round widget: the sky in blue,
218            the ground in marron."""
219
220         # set local pitch and roll
221         pitch = self.pitch
222         roll = self.roll
223         if pitch > 90.:
224             pitch = 180. - pitch
225             roll += 180.
226             if roll > 180.:
227                 roll -= 360.
228         if pitch < -90.:
229             pitch = -180. - pitch
230             roll += 180.
231             if roll > 180.:
232                 roll -= 360.
233
234         # we have to draw a partial circle delimited by its chord, define
235         # where the chord starts
236         start_angle = math.degrees(math.asin(pitch / 90.)) - roll
237         span = 2 * math.degrees(math.asin(pitch / 90.))
238
239         # draw the sky
240         painter.setBrush(self.getSkyBrush())
241         # startAngle and spanAngle must be specified in 1/16th of a degree
242         painter.drawChord(self.adjdev, 16 * start_angle, 16 * (180. - span))
243
244         # draw the ground
245         painter.setBrush(self.getGroundBrush())
246         # startAngle and spanAngle must be specified in 1/16th of a degree
247         painter.drawChord(self.adjdev, 16 * start_angle, -16 * (180. + span))
248
249     def drawHorizon(self, painter):
250         """Draw the horizon: the sky in blue, the ground in marron."""
251         painter.save()
252         painter.setBrush(self.getSkyBrush())
253         painter.drawRect(self.dev)
254         center = self.adjdev.center()
255         r = self.getCenterRadius()
256         # radius of the adjusted screen (same than r if roundWidget = True)
257         dev_r = self.adjdev.width() / 2.
258         roll = self.roll
259         if self.pitch < -90.:
260             pitch = -180 - self.pitch
261         elif self.pitch < 90.:
262             pitch = self.pitch
263         else:
264             pitch = 180 - self.pitch
265         y_off = (pitch / self.CAM_ANGLE) * dev_r
266         painter.translate(center.x(), center.y())
267         painter.rotate(roll)
268         ground_rect = QRect(0, 0, self.max_dim * 5., self.max_dim * 5.)
269         if self.pitch < 90. and self.pitch > -90.:
270             ground_rect.moveCenter(QPoint(0, -y_off + ground_rect.width()/2.))
271         else:
272             ground_rect.moveCenter(QPoint(0, y_off - ground_rect.width()/2.))
273         painter.setBrush(self.getGroundBrush())
274         painter.drawRect(ground_rect)
275         painter.restore()
276
277     def drawCenterRef(self, painter):
278         """Draw the cross on the middle of the OSD"""
279         center = self.adjdev.center()
280         r = self.getCenterRadius()
281         pen = QPen(Qt.red, r / 100., Qt.SolidLine)
282         painter.setPen(pen)
283         pt1 = QPoint(center.x() - 0.05 * r, center.y())
284         pt2 = QPoint(center.x() + 0.05 * r, center.y())
285         painter.drawLine(pt1, pt2)
286         pt1 = QPoint(center.x(), center.y() + -0.025 * r)
287         pt2 = QPoint(center.x(), center.y() + 0.025 * r)
288         painter.drawLine(pt1, pt2)
289
290     def drawPitchGraduation(self, painter):
291         """Draw the pitch graduations."""
292         # change the reference
293         painter.save()
294         center = self.adjdev.center()
295         r = self.getCenterRadius()
296         # radius of the adjusted screen (same than r if roundWidget = True)
297         dev_r = self.adjdev.width() / 2.
298         roll = self.roll
299         x_off = (self.pitch / self.CAM_ANGLE) * dev_r * math.sin(math.radians(roll))
300         y_off = (self.pitch / self.CAM_ANGLE) * dev_r * math.cos(math.radians(roll))
301         painter.translate(center.x() + x_off, center.y() - y_off)
302         painter.rotate(roll)
303
304         # set font and pen
305         pen = QPen(Qt.white, r / 100., Qt.SolidLine)
306         painter.setPen(pen)
307         font = QFont("Meiryo UI", 0, QFont.Bold)
308         font.setPointSizeF(self.FONT_SIZE * r)
309         painter.setFont(font)
310
311         # round to nearest angle that is a multiple of step
312         a = self.pitch / self.PITCH_REFLINES_STEP_ANGLE
313         a = round(a)
314         a = int(a * self.PITCH_REFLINES_STEP_ANGLE)
315         a -= self.PITCH_REFLINES_STEP_ANGLE * (self.PITCH_REFLINES_NUM_LINES / 2.)
316         angles = [ a + i * self.PITCH_REFLINES_STEP_ANGLE
317                    for i in range(self.PITCH_REFLINES_NUM_LINES + 1) ]
318         for a in angles:
319             # thin line
320             if int(a) % int(self.PITCH_REFLINES_BOLD_STEP_ANGLE) != 0:
321                 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
322                 painter.setPen(pen)
323                 pt1 = QPoint(-0.05 * r, dev_r / self.CAM_ANGLE * a)
324                 pt2 = QPoint(0.05 * r, dev_r / self.CAM_ANGLE * a)
325                 painter.drawLine(pt1, pt2)
326                 continue
327
328             # bold line
329             pen = QPen(Qt.white, r / 50., Qt.SolidLine)
330             painter.setPen(pen)
331             pt1 = QPoint(-0.2 * r, dev_r / self.CAM_ANGLE * a)
332             pt2 = QPoint(0.2 * r, dev_r / self.CAM_ANGLE * a)
333             painter.drawLine(pt1, pt2)
334
335             # the left text
336             disp_val = -a
337             if disp_val > 90.:
338                 disp_val = 180. - disp_val
339             if disp_val < -90.:
340                 disp_val = -180. - disp_val
341             disp_val = str(int(disp_val))
342             metrics = painter.fontMetrics()
343             sz = metrics.size(Qt.TextSingleLine, disp_val)
344             lefttxt = QRect(QPoint(0, 0), sz)
345             lefttxt.moveCenter(pt1 - QPoint(0.2 * r, 0))
346             # XXX
347             #pen.setWidth(1);
348             #brush = QBrush(Qt.white)
349             #painter.setBrush(brush)
350             #pen.setColor(Qt.black);
351             #painter.setPen(pen)
352             #path = QPainterPath()
353             #path.addText(lefttxt.center(), font, disp_val)
354             #painter.drawPath(path)
355             painter.drawText(lefttxt, Qt.TextSingleLine, disp_val)
356
357             # flip the right text
358             painter.save()
359             painter.translate(pt2 + QPoint(0.2 * r, 0))
360             painter.rotate(180.)
361             painter.translate(-pt2 - QPoint(0.2 * r, 0))
362             righttxt = QRect(QPoint(0, 0), sz)
363             righttxt.moveCenter(pt2 + QPoint(0.2 * r, 0))
364             #path = QPainterPath()
365             #path.addText(righttxt.center(), font, disp_val)
366             #painter.drawPath(path)
367             painter.drawText(righttxt, Qt.TextSingleLine, disp_val)
368             painter.restore()
369
370         painter.restore()
371
372     def drawOneRollGraduation(self, painter, deg, disp_text):
373         # draw the graduiation
374         r = self.getCenterRadius()
375         center = self.adjdev.center()
376         x = center.x() - math.cos(math.radians(deg)) * r
377         y = center.y() - math.sin(math.radians(deg)) * r
378         pt = QPoint(x, y)
379         path = QPainterPath()
380         path.moveTo(pt)
381         path.lineTo(center)
382         pt2 = path.pointAtPercent(0.075) # graduation len is 7.5% of the radius
383         painter.drawLine(pt, pt2)
384         # then draw the text
385         if disp_text == True:
386             pt_txt = path.pointAtPercent(0.2)
387             font = QFont("Meiryo UI", 0, QFont.Bold)
388             font.setPointSizeF(self.FONT_SIZE * r)
389             painter.setFont(font)
390             disp_val = deg
391             if disp_val > 90:
392                 disp_val = 180. - disp_val
393             disp_val = str(int(disp_val))
394             metrics = painter.fontMetrics()
395             sz = metrics.size(Qt.TextSingleLine, disp_val)
396             txt = QRect(QPoint(0, 0), sz)
397             txt.moveCenter(pt_txt.toPoint())
398             painter.drawText(txt, Qt.TextSingleLine, disp_val)
399
400     def drawRollGraduation(self, painter):
401         """Draw the roll graduations."""
402         center = self.adjdev.center()
403         r = self.getCenterRadius()
404
405         # draw the red reference lines (pitch 0)
406         painter.save()
407         painter.translate(center.x(), center.y())
408         painter.rotate(self.roll)
409         pen = QPen(Qt.red, r / 100., Qt.SolidLine)
410         painter.setPen(pen)
411         pt1 = QPoint(-0.925 * r, 0)
412         pt2 = QPoint(-0.85 * r, 0)
413         painter.drawLine(pt1, pt2)
414         pt1 = QPoint(0.925 * r, 0)
415         pt2 = QPoint(0.85 * r, 0)
416         painter.drawLine(pt1, pt2)
417         painter.restore()
418
419         pen = QPen(Qt.white, r / 50., Qt.SolidLine)
420         painter.setPen(pen)
421         deg = 0
422         while deg <= 180:
423             if deg % 30 == 0:
424                 w = r / 50.
425                 disp_text = True
426             else:
427                 w = r / 100.
428                 disp_text = False
429             pen.setWidth(w)
430             painter.setPen(pen)
431             self.drawOneRollGraduation(painter, deg, disp_text)
432             deg += 10
433
434     def drawYaw(self, painter):
435         center = self.adjdev.center()
436         r = self.getCenterRadius()
437         pen = QPen(Qt.red, r / 100., Qt.SolidLine)
438         painter.setPen(pen)
439         font = QFont("Meiryo UI", 0, QFont.Bold)
440         font.setPointSizeF(self.FONT_SIZE * r)
441         painter.setFont(font)
442         if self.roundWidget == True:
443             y_txt = center.y() + r * 0.6
444             y1 = center.y() + r * 0.7
445             y2 = center.y() + r * 0.8
446             y3 = center.y() + r * 0.85
447         else:
448             y_txt = center.y() + r * 1.
449             y1 = center.y() + r * 1.1
450             y2 = center.y() + r * 1.2
451             y3 = center.y() + r * 1.25
452         pt1 = QPoint(center.x(), y2)
453         pt2 = QPoint(center.x(), y3)
454         painter.drawLine(pt1, pt2)
455
456         # round to nearest angle multiple of step
457         a = self.yaw / self.YAW_REFLINES_STEP_ANGLE
458         a = round(a)
459         a = int(a * self.YAW_REFLINES_STEP_ANGLE)
460         a -= self.YAW_REFLINES_STEP_ANGLE * (self.YAW_REFLINES_NUM_LINES / 2.)
461         angles = [ a + i * self.YAW_REFLINES_STEP_ANGLE
462                    for i in range(self.YAW_REFLINES_NUM_LINES + 1) ]
463         for a in angles:
464             # text (N, S, E, W)
465             if int(a) % 90 == 0:
466                 disp_text = True
467                 pen = QPen(Qt.white, r / 50., Qt.SolidLine)
468                 painter.setPen(pen)
469             else:
470                 disp_text = False
471                 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
472                 painter.setPen(pen)
473             # the line
474             x = center.x() - ((r / (self.YAW_REFLINES_TOTAL_ANGLE / 2.)) *
475                               (self.yaw - a) * self.YAW_REFLINES_SIZE_FACTOR)
476             pt_txt = QPoint(x, y_txt)
477             pt1 = QPoint(x, y1)
478             pt2 = QPoint(x, y2)
479             painter.drawLine(pt1, pt2)
480             if disp_text == False:
481                 continue
482             disp_val = ["N", "E", "S", "W"][(int(a)/90)%4]
483             metrics = painter.fontMetrics()
484             sz = metrics.size(Qt.TextSingleLine, disp_val)
485             txt = QRect(QPoint(0, 0), sz)
486             txt.moveCenter(pt_txt)
487             painter.drawText(txt, Qt.TextSingleLine, disp_val)
488
489     def drawSpeed(self, painter):
490         center = self.adjdev.center()
491         r = self.getCenterRadius()
492         pen = QPen(Qt.red, r / 100., Qt.SolidLine)
493         painter.setPen(pen)
494         font = QFont("Meiryo UI", 0, QFont.Bold)
495         font.setPointSizeF(self.FONT_SIZE * r)
496         painter.setFont(font)
497         x1 =  center.x() - 1.5 * r
498         x2 =  center.x() - 1.6 * r
499         pt1 = QPoint(center.x() - 1.45 * r, center.y())
500         pt2 = QPoint(x2, center.y())
501         painter.drawLine(pt1, pt2)
502
503         # round to nearest angle multiple of step
504         s = self.speed / self.SPEED_REFLINES_STEP
505         s = round(s)
506         s = int(s * self.SPEED_REFLINES_STEP)
507         s -= self.SPEED_REFLINES_STEP * (self.SPEED_REFLINES_NUM_LINES / 2.)
508         speeds = [ s + i * self.SPEED_REFLINES_STEP
509                    for i in range(self.SPEED_REFLINES_NUM_LINES + 1) ]
510         for s in speeds:
511             if int(s) % int(self.SPEED_REFLINES_BOLD_STEP) == 0:
512                 disp_text = True
513                 pen = QPen(Qt.white, r / 50., Qt.SolidLine)
514                 painter.setPen(pen)
515             else:
516                 disp_text = False
517                 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
518                 painter.setPen(pen)
519             # the line
520             y = center.y() + ((r / (self.SPEED_REFLINES_TOTAL/2.)) *
521                               (self.speed - s) * self.SPEED_REFLINES_SIZE_FACTOR)
522             pt_txt = QPoint(center.x() + r * -1.75, y)
523             pt1 = QPoint(x1, y)
524             pt2 = QPoint(x2, y)
525             painter.drawLine(pt1, pt2)
526             if disp_text == False:
527                 continue
528             disp_val = str(int(s))
529             metrics = painter.fontMetrics()
530             sz = metrics.size(Qt.TextSingleLine, disp_val)
531             txt = QRect(QPoint(0, 0), sz)
532             txt.moveCenter(pt_txt)
533             painter.drawText(txt, Qt.TextSingleLine, disp_val)
534
535     def drawAlt(self, painter):
536         center = self.adjdev.center()
537         r = self.getCenterRadius()
538         pen = QPen(Qt.red, r / 100., Qt.SolidLine)
539         painter.setPen(pen)
540         font = QFont("Meiryo UI", 0, QFont.Bold)
541         font.setPointSizeF(self.FONT_SIZE * r)
542         painter.setFont(font)
543         x1 =  center.x() + 1.5 * r
544         x2 =  center.x() + 1.6 * r
545         pt1 = QPoint(center.x() + 1.45 * r, center.y())
546         pt2 = QPoint(x2, center.y())
547         painter.drawLine(pt1, pt2)
548
549         # round to nearest angle multiple of step
550         a = self.alt / self.ALT_REFLINES_STEP
551         a = round(a)
552         a = int(a * self.ALT_REFLINES_STEP)
553         a -= self.ALT_REFLINES_STEP * (self.ALT_REFLINES_NUM_LINES / 2.)
554         alts = [ a + i * self.ALT_REFLINES_STEP
555                    for i in range(self.ALT_REFLINES_NUM_LINES + 1) ]
556         for a in alts:
557             if int(a) % int(self.ALT_REFLINES_BOLD_STEP) == 0:
558                 disp_text = True
559                 pen = QPen(Qt.white, r / 50., Qt.SolidLine)
560                 painter.setPen(pen)
561             else:
562                 disp_text = False
563                 pen = QPen(Qt.white, r / 100., Qt.SolidLine)
564                 painter.setPen(pen)
565             # the line
566             y = center.y() + ((r / (self.ALT_REFLINES_TOTAL / 2.)) *
567                               (self.alt - a) * self.ALT_REFLINES_SIZE_FACTOR)
568             pt_txt = QPoint(center.x() + r * 1.75, y)
569             pt1 = QPoint(x1, y)
570             pt2 = QPoint(x2, y)
571             painter.drawLine(pt1, pt2)
572             if disp_text == False:
573                 continue
574             disp_val = str(int(a))
575             metrics = painter.fontMetrics()
576             sz = metrics.size(Qt.TextSingleLine, disp_val)
577             txt = QRect(QPoint(0, 0), sz)
578             txt.moveCenter(pt_txt)
579             painter.drawText(txt, Qt.TextSingleLine, disp_val)
580
581     def drawReturnToHome(self, painter):
582         center = self.adjdev.center()
583         r = self.getCenterRadius()
584         dev_r = self.adjdev.width() / 2.
585         painter.save()
586         painter.translate(center.x(), center.y() - 0.9 * dev_r)
587         painter.rotate(self.yaw) # XXX
588         pen = QPen(Qt.white, r / 100., Qt.SolidLine)
589         painter.setPen(pen)
590         poly = QPolygonF()
591         poly.append(QPoint(0., -0.08 * r))
592         poly.append(QPoint(0.04 * r, 0.08 * r))
593         poly.append(QPoint(-0.04 * r, 0.08 * r))
594         poly.append(QPoint(0., -0.08 * r))
595         path = QPainterPath()
596         path.addPolygon(poly)
597         brush = QBrush(Qt.darkGray)
598         painter.setBrush(brush)
599         painter.drawPath(path)
600         painter.restore()
601
602     def drawTxtInfo(self, painter):
603         center = self.adjdev.center()
604         dev_r = self.adjdev.width() / 2.
605         r = self.getCenterRadius()
606         pen = QPen(Qt.white, r / 100., Qt.SolidLine)
607         painter.setPen(pen)
608         font = QFont("Meiryo UI", 0, QFont.Bold)
609         font.setPointSizeF(self.FONT_SIZE * r)
610         metrics = painter.fontMetrics()
611
612         sz = metrics.size(Qt.AlignLeft, self.left_txt)
613         txt = QRect(QPoint(0, 0), sz)
614         pt_txt = QPoint(center.x() + dev_r * -0.95,
615                                center.y() + dev_r * -0.95)
616         txt.moveTopLeft(pt_txt)
617         painter.drawText(txt, Qt.AlignLeft, self.left_txt)
618
619         sz = metrics.size(Qt.AlignRight, self.right_txt)
620         txt = QRect(QPoint(0, 0), sz)
621         pt_txt = QPoint(center.x() + dev_r * 0.95,
622                                center.y() + dev_r * -0.95)
623         txt.moveTopRight(pt_txt)
624         painter.drawText(txt, Qt.AlignRight, self.right_txt)
625
626     def setPitch(self, pitch):
627         """set the pitch in degrees, between -180 and 180"""
628         pitch = pitch % 360.
629         if pitch > 180.:
630             pitch -= 360.
631         self.user_pitch = pitch
632
633     def setRoll(self, roll):
634         """set the roll in degrees, between -180 and 180"""
635         roll = roll % 360.
636         if roll > 180.:
637             roll -= 360.
638         self.user_roll = roll
639
640     def setYaw(self, yaw):
641         """set the yaw in degrees, between 0 and 360"""
642         yaw = yaw % 360.
643         self.user_yaw = yaw
644
645     def setSpeed(self, speed):
646         """set the speed"""
647         self.user_speed = speed
648
649     def setAlt(self, alt):
650         """set the alt"""
651         self.user_alt = alt
652
653     def setReturnToHomeAngle(self, angle):
654         """set the left text"""
655         self.user_rthome = angle
656
657     def setLeftTxt(self, txt):
658         """set the right text"""
659         self.left_txt = txt
660
661     def setRightTxt(self, txt):
662         """set the left text"""
663         self.right_txt = txt
664
665 class Ui_MainWindow(QMainWindow):
666     def __init__(self, parent = None, roundWidget = False, filename = None):
667         super(Ui_MainWindow, self).__init__(parent)
668         self.ui = qtosd_ui.Ui_MainWindow()
669         self.ui.setupUi(self)
670
671         self.roundWidget = roundWidget
672         self.filename = filename
673
674         self.osd = OSDWidget(roundWidget = self.roundWidget,
675                              filename = self.filename)
676         self.ui.gridLayout.addWidget(self.osd, 0, 1)
677         self.ui.pitchSlider.valueChanged[int].connect(self.changePitch)
678         self.ui.rollSlider.valueChanged[int].connect(self.changeRoll)
679         self.ui.yawSlider.valueChanged[int].connect(self.changeYaw)
680         self.ui.actionExit.triggered.connect(self.close)
681
682     def keyPressEvent(self, event):
683         if event.key() == Qt.Key_J:
684             self.osd.setRoll(self.osd.user_roll + 2)
685             event.accept()
686         elif event.key() == Qt.Key_L:
687             self.osd.setRoll(self.osd.user_roll - 2)
688             event.accept()
689         elif event.key() == Qt.Key_I:
690             self.osd.setPitch(self.osd.user_pitch + 2)
691             event.accept()
692         elif event.key() == Qt.Key_K:
693             self.osd.setPitch(self.osd.user_pitch - 2)
694             event.accept()
695         elif event.key() == Qt.Key_Q:
696             self.close()
697
698     def keyReleaseEvent(self, event):
699         return
700
701     def retranslateUi(self, MainWindow):
702         MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
703
704     @pyqtSlot(int, name = "changePitch")
705     def changePitch(self, value):
706         self.osd.setPitch(value)
707
708     @pyqtSlot(int, name = "changeRoll")
709     def changeRoll(self, value):
710         self.osd.setRoll(value)
711
712     @pyqtSlot(int, name = "changeYaw")
713     def changeYaw(self, value):
714         self.osd.setYaw(value)
715
716 if __name__ == "__main__":
717     import sys
718
719     parser = argparse.ArgumentParser(description='OSD written in Qt.')
720     parser.add_argument('--round', '-r', action="store_true",
721                         help='display the widget as a round attitude meter')
722     parser.add_argument('--filename', '-f',
723                         help='specify the log file')
724     args = parser.parse_args()
725
726     app = QApplication(sys.argv)
727     ui = Ui_MainWindow(filename = args.filename, roundWidget = args.round)
728     ui.show()
729     sys.exit(app.exec_())
730