add menu
[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, QtGui, QtWidgets
27 from PyQt5.QtCore import pyqtSlot
28
29 import serialfpv
30 import qtosd_ui
31
32 try:
33     _fromUtf8 = QtCore.QString.fromUtf8
34 except AttributeError:
35     def _fromUtf8(s):
36         return s
37
38 try:
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)
45
46 class OSDWidget(QtWidgets.QWidget):
47     def __init__(self, roundWidget = False, filename = None):
48         super(OSDWidget, self).__init__()
49         # init parameters
50         self.roundWidget = roundWidget
51         self.fpv = None
52         # parameters that will be modified by the user
53         self.user_pitch = 0
54         self.user_roll = 0
55         self.user_yaw = 0
56         self.user_speed = 0
57         self.user_alt = 0
58         self.user_rthome = 0
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
62         # filtered parameters
63         self.pitch = 0
64         self.roll = 0
65         self.yaw = 0
66         self.speed = 0
67         self.alt = 0
68         self.rthome = 0
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
77         self.dev = None
78         # QRect representing the viewport, adjusted to a square
79         self.adjdev = None
80         self.setMinimumSize(250, 250)
81
82         # how many degrees between pitch reference lines
83         if roundWidget:
84             self.CAM_ANGLE = 90.
85             self.PITCH_REFLINES_STEP_ANGLE = 10.
86             self.PITCH_REFLINES_BOLD_STEP_ANGLE = 30.
87             self.PITCH_REFLINES_NUM_LINES = 6
88         else:
89             self.CAM_ANGLE = 40.
90             self.PITCH_REFLINES_STEP_ANGLE = 5.
91             self.PITCH_REFLINES_BOLD_STEP_ANGLE = 10.
92             self.PITCH_REFLINES_NUM_LINES = 4
93
94         self.PITCH_REFLINES_TOTAL_ANGLE = (self.PITCH_REFLINES_STEP_ANGLE *
95                                            self.PITCH_REFLINES_NUM_LINES)
96         # in fraction of radius
97         self.FONT_SIZE = 0.06
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
104
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
111
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
118
119         if filename:
120             self.fpv = serialfpv.SerialFPV()
121             self.fpv.open_file(filename)
122
123         self.FPS = 50.
124         self.timer = QtCore.QTimer(self)
125         self.timer.timeout.connect(self.frameTimerCb)
126         self.timer.start(1000. / self.FPS)
127
128     @pyqtSlot(name = "paintEvent")
129     def paintEvent(self, evt):
130         """Paint callback, this is the entry point for all drawings."""
131         painter = QtGui.QPainter()
132         painter.begin(self)
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())
139         self.draw(painter)
140         painter.end()
141
142     @pyqtSlot(name = "frameTimerCb")
143     def frameTimerCb(self):
144         # read from SerialFPV object
145         if self.fpv:
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)
151
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))
169         self.update()
170
171     def draw(self, painter):
172         """Draw the widget."""
173         if self.roundWidget:
174             self.drawHorizonRound(painter)
175         else:
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)
186
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)
195         return sky_gradient
196
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
206
207     def getCenterRadius(self):
208         """Return the radius of the widget circle"""
209         if self.roundWidget:
210             return self.adjdev.width() / 2.
211         else:
212             return self.adjdev.width() / 3.5
213
214     def drawHorizonRound(self, painter):
215         """Draw the horizon for round widget: the sky in blue,
216            the ground in marron."""
217
218         # set local pitch and roll
219         pitch = self.pitch
220         roll = self.roll
221         if pitch > 90.:
222             pitch = 180. - pitch
223             roll += 180.
224             if roll > 180.:
225                 roll -= 360.
226         if pitch < -90.:
227             pitch = -180. - pitch
228             roll += 180.
229             if roll > 180.:
230                 roll -= 360.
231
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.))
236
237         # draw the sky
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))
241
242         # draw the ground
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))
246
247     def drawHorizon(self, painter):
248         """Draw the horizon: the sky in blue, the ground in marron."""
249         painter.save()
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.
256         roll = self.roll
257         if self.pitch < -90.:
258             pitch = -180 - self.pitch
259         elif self.pitch < 90.:
260             pitch = self.pitch
261         else:
262             pitch = 180 - self.pitch
263         y_off = (pitch / self.CAM_ANGLE) * dev_r
264         painter.translate(center.x(), center.y())
265         painter.rotate(roll)
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.))
269         else:
270             ground_rect.moveCenter(QtCore.QPoint(0, y_off - ground_rect.width()/2.))
271         painter.setBrush(self.getGroundBrush())
272         painter.drawRect(ground_rect)
273         painter.restore()
274
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)
280         painter.setPen(pen)
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)
287
288     def drawPitchGraduation(self, painter):
289         """Draw the pitch graduations."""
290         # change the reference
291         painter.save()
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.
296         roll = self.roll
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)
300         painter.rotate(roll)
301
302         # set font and pen
303         pen = QtGui.QPen(QtCore.Qt.white, r / 100., QtCore.Qt.SolidLine)
304         painter.setPen(pen)
305         font = QtGui.QFont("Meiryo UI", 0, QtGui.QFont.Bold)
306         font.setPointSizeF(self.FONT_SIZE * r)
307         painter.setFont(font)
308
309         # round to nearest angle that is a multiple of step
310         a = self.pitch / self.PITCH_REFLINES_STEP_ANGLE
311         a = round(a)
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) ]
316         for a in angles:
317             # thin line
318             if int(a) % int(self.PITCH_REFLINES_BOLD_STEP_ANGLE) != 0:
319                 pen = QtGui.QPen(QtCore.Qt.white, r / 100., QtCore.Qt.SolidLine)
320                 painter.setPen(pen)
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)
324                 continue
325
326             # bold line
327             pen = QtGui.QPen(QtCore.Qt.white, r / 50., QtCore.Qt.SolidLine)
328             painter.setPen(pen)
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)
332
333             # the left text
334             disp_val = -a
335             if disp_val > 90.:
336                 disp_val = 180. - disp_val
337             if disp_val < -90.:
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))
344             # XXX
345             #pen.setWidth(1);
346             #brush = QtGui.QBrush(QtCore.Qt.white)
347             #painter.setBrush(brush)
348             #pen.setColor(QtCore.Qt.black);
349             #painter.setPen(pen)
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)
354
355             # flip the right text
356             painter.save()
357             painter.translate(pt2 + QtCore.QPoint(0.2 * r, 0))
358             painter.rotate(180.)
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)
366             painter.restore()
367
368         painter.restore()
369
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()
378         path.moveTo(pt)
379         path.lineTo(center)
380         pt2 = path.pointAtPercent(0.075) # graduation len is 7.5% of the radius
381         painter.drawLine(pt, pt2)
382         # then draw the text
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)
388             disp_val = deg
389             if disp_val > 90:
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)
397
398     def drawRollGraduation(self, painter):
399         """Draw the roll graduations."""
400         center = self.adjdev.center()
401         r = self.getCenterRadius()
402
403         # draw the red reference lines (pitch 0)
404         painter.save()
405         painter.translate(center.x(), center.y())
406         painter.rotate(self.roll)
407         pen = QtGui.QPen(QtCore.Qt.red, r / 100., QtCore.Qt.SolidLine)
408         painter.setPen(pen)
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)
415         painter.restore()
416
417         pen = QtGui.QPen(QtCore.Qt.white, r / 50., QtCore.Qt.SolidLine)
418         painter.setPen(pen)
419         deg = 0
420         while deg <= 180:
421             if deg % 30 == 0:
422                 w = r / 50.
423                 disp_text = True
424             else:
425                 w = r / 100.
426                 disp_text = False
427             pen.setWidth(w)
428             painter.setPen(pen)
429             self.drawOneRollGraduation(painter, deg, disp_text)
430             deg += 10
431
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)
436         painter.setPen(pen)
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
445         else:
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)
453
454         # round to nearest angle multiple of step
455         a = self.yaw / self.YAW_REFLINES_STEP_ANGLE
456         a = round(a)
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) ]
461         for a in angles:
462             # text (N, S, E, W)
463             if int(a) % 90 == 0:
464                 disp_text = True
465                 pen = QtGui.QPen(QtCore.Qt.white, r / 50., QtCore.Qt.SolidLine)
466                 painter.setPen(pen)
467             else:
468                 disp_text = False
469                 pen = QtGui.QPen(QtCore.Qt.white, r / 100., QtCore.Qt.SolidLine)
470                 painter.setPen(pen)
471             # the line
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:
479                 continue
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)
486
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)
491         painter.setPen(pen)
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)
500
501         # round to nearest angle multiple of step
502         s = self.speed / self.SPEED_REFLINES_STEP
503         s = round(s)
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) ]
508         for s in speeds:
509             if int(s) % int(self.SPEED_REFLINES_BOLD_STEP) == 0:
510                 disp_text = True
511                 pen = QtGui.QPen(QtCore.Qt.white, r / 50., QtCore.Qt.SolidLine)
512                 painter.setPen(pen)
513             else:
514                 disp_text = False
515                 pen = QtGui.QPen(QtCore.Qt.white, r / 100., QtCore.Qt.SolidLine)
516                 painter.setPen(pen)
517             # the line
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:
525                 continue
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)
532
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)
537         painter.setPen(pen)
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)
546
547         # round to nearest angle multiple of step
548         a = self.alt / self.ALT_REFLINES_STEP
549         a = round(a)
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) ]
554         for a in alts:
555             if int(a) % int(self.ALT_REFLINES_BOLD_STEP) == 0:
556                 disp_text = True
557                 pen = QtGui.QPen(QtCore.Qt.white, r / 50., QtCore.Qt.SolidLine)
558                 painter.setPen(pen)
559             else:
560                 disp_text = False
561                 pen = QtGui.QPen(QtCore.Qt.white, r / 100., QtCore.Qt.SolidLine)
562                 painter.setPen(pen)
563             # the line
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:
571                 continue
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)
578
579     def drawReturnToHome(self, painter):
580         center = self.adjdev.center()
581         r = self.getCenterRadius()
582         dev_r = self.adjdev.width() / 2.
583         painter.save()
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)
587         painter.setPen(pen)
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)
598         painter.restore()
599
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)
605         painter.setPen(pen)
606         font = QtGui.QFont("Meiryo UI", 0, QtGui.QFont.Bold)
607         font.setPointSizeF(self.FONT_SIZE * r)
608         metrics = painter.fontMetrics()
609
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)
616
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)
623
624     def setPitch(self, pitch):
625         """set the pitch in degrees, between -180 and 180"""
626         pitch = pitch % 360.
627         if pitch > 180.:
628             pitch -= 360.
629         self.user_pitch = pitch
630
631     def setRoll(self, roll):
632         """set the roll in degrees, between -180 and 180"""
633         roll = roll % 360.
634         if roll > 180.:
635             roll -= 360.
636         self.user_roll = roll
637
638     def setYaw(self, yaw):
639         """set the yaw in degrees, between 0 and 360"""
640         yaw = yaw % 360.
641         self.user_yaw = yaw
642
643     def setSpeed(self, speed):
644         """set the speed"""
645         self.user_speed = speed
646
647     def setAlt(self, alt):
648         """set the alt"""
649         self.user_alt = alt
650
651     def setReturnToHomeAngle(self, angle):
652         """set the left text"""
653         self.user_rthome = angle
654
655     def setLeftTxt(self, txt):
656         """set the right text"""
657         self.left_txt = txt
658
659     def setRightTxt(self, txt):
660         """set the left text"""
661         self.right_txt = txt
662
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)
668
669         self.roundWidget = roundWidget
670         self.filename = filename
671
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)
679
680     def retranslateUi(self, MainWindow):
681         MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
682
683     @pyqtSlot(int, name = "changePitch")
684     def changePitch(self, value):
685         self.osd.setPitch(value)
686
687     @pyqtSlot(int, name = "changeRoll")
688     def changeRoll(self, value):
689         self.osd.setRoll(value)
690
691     @pyqtSlot(int, name = "changeYaw")
692     def changeYaw(self, value):
693         self.osd.setYaw(value)
694
695 if __name__ == "__main__":
696     import sys
697
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()
704
705     app = QtWidgets.QApplication(sys.argv)
706     ui = Ui_MainWindow(filename = args.filename, roundWidget = args.round)
707     ui.show()
708     sys.exit(app.exec_())
709