# -*- coding: utf-8 -*-

# OSD (on screen display) written in Qt
# Copyright (C) 2015 Olivier Matz <zer0@droids-corp.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Inspired from QcGauge
# Copyright (C) 2015 Hadj Tahar Berrima
# http://pytricity.com/qt-artificial-horizon-custom-widget/

import math
import argparse

from PyQt5 import QtCore
from PyQt5.QtCore import (pyqtSlot, QTimer, QRect, QPoint, Qt, QByteArray,
                          QSizeF, QRectF)
from PyQt5.QtGui import (QPainter, QColor, QPen, QBrush, QLinearGradient, QFont,
                         QPainterPath, QPolygonF)
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QAction,
                             QActionGroup, QGraphicsScene, QGraphicsView)
from PyQt5.QtMultimediaWidgets import (QGraphicsVideoItem)
from PyQt5.QtMultimedia import (QCamera, QAbstractVideoSurface,
                                QAbstractVideoBuffer)

import serialfpv
import qtosd_ui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QApplication.UnicodeUTF8
    def _translate(context, text, disambig):
        return QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QApplication.translate(context, text, disambig)

class OSDWidget(QWidget):
    def __init__(self, mode = "camera", filename = None):
        super(OSDWidget, self).__init__()
        # init parameters
        self.mode = mode
        self.fpv = None
        # parameters that will be modified by the user
        self.user_pitch = 0
        self.user_roll = 0
        self.user_yaw = 0
        self.user_speed = 0
        self.user_alt = 0
        self.user_rthome = 0
        self.left_txt = "14.8v / 34A\n1.5Ah" # XXX
        self.right_txt = "23:03 since take-off\n1.4 km to home\nRSSI 60dB" # XXX
        self.user_frame_cb = None
        # filtered parameters
        self.pitch = 0
        self.roll = 0
        self.yaw = 0
        self.speed = 0
        self.alt = 0
        self.rthome = 0
        # filtered parameters (0 = no filter, 1 = infinite filter)
        self.pitch_filter_coef = 0.8
        self.roll_filter_coef = 0.8
        self.yaw_filter_coef = 0.8
        self.speed_filter_coef = 0.8
        self.alt_filter_coef = 0.8
        self.rthome_filter_coef = 0.8
        # QRect representing the viewport
        self.dev = None
        # QRect representing the viewport, adjusted to a square
        self.adjdev = None
        self.setMinimumSize(250, 250)

        self.setStyleSheet("background-color: transparent;")

        # how many degrees between pitch reference lines
        if mode == "round":
            self.CAM_ANGLE = 90.
            self.PITCH_REFLINES_STEP_ANGLE = 10.
            self.PITCH_REFLINES_BOLD_STEP_ANGLE = 30.
            self.PITCH_REFLINES_NUM_LINES = 6
        else:
            self.CAM_ANGLE = 40.
            self.PITCH_REFLINES_STEP_ANGLE = 5.
            self.PITCH_REFLINES_BOLD_STEP_ANGLE = 10.
            self.PITCH_REFLINES_NUM_LINES = 4

        self.PITCH_REFLINES_TOTAL_ANGLE = (self.PITCH_REFLINES_STEP_ANGLE *
                                           self.PITCH_REFLINES_NUM_LINES)
        # in fraction of radius
        self.FONT_SIZE = 0.06
        # how many degrees between yaw reference lines
        self.YAW_REFLINES_STEP_ANGLE = 15.
        self.YAW_REFLINES_NUM_LINES = 12
        self.YAW_REFLINES_TOTAL_ANGLE = (self.YAW_REFLINES_STEP_ANGLE *
                                         self.YAW_REFLINES_NUM_LINES)
        self.YAW_REFLINES_SIZE_FACTOR = 0.5

        self.SPEED_REFLINES_STEP = 10.
        self.SPEED_REFLINES_BOLD_STEP = 50.
        self.SPEED_REFLINES_NUM_LINES = 10
        self.SPEED_REFLINES_TOTAL = (self.SPEED_REFLINES_STEP *
                                     self.SPEED_REFLINES_NUM_LINES)
        self.SPEED_REFLINES_SIZE_FACTOR = 0.7

        self.ALT_REFLINES_STEP = 100.
        self.ALT_REFLINES_BOLD_STEP = 500.
        self.ALT_REFLINES_NUM_LINES = 10
        self.ALT_REFLINES_TOTAL = (self.ALT_REFLINES_STEP *
                                   self.ALT_REFLINES_NUM_LINES)
        self.ALT_REFLINES_SIZE_FACTOR = 0.7

        if filename:
            self.fpv = serialfpv.SerialFPV()
            self.fpv.open_file(filename)

        self.FPS = 50.
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.frameTimerCb)
        self.timer.start(1000. / self.FPS)

    @pyqtSlot(name = "paintEvent")
    def paintEvent(self, evt):
        """Paint callback, this is the entry point for all drawings."""
        painter = QPainter()
        painter.begin(self)
        painter.setRenderHint(QPainter.Antialiasing)
        self.dev = evt.rect()
        self.min_dim = min(self.dev.right(), self.dev.bottom())
        self.max_dim = max(self.dev.right(), self.dev.bottom())
        self.adjdev = QRect(0, 0, self.min_dim, self.min_dim)
        self.adjdev.moveCenter(self.dev.center())
        self.draw(painter)
        painter.end()

    @pyqtSlot(name = "frameTimerCb")
    def frameTimerCb(self):
        # read from SerialFPV object
        if self.fpv:
            self.fpv.update_state()
            self.user_speed = self.user_speed + 1
            self.setRoll(self.fpv.roll)
            self.setPitch(self.fpv.pitch)
            self.setYaw(self.fpv.yaw)

        # avoid filter bugs when changing between 180 and -180
        self.pitch += round((self.user_pitch - self.pitch) / 360.) * 360
        self.pitch = (self.pitch * self.pitch_filter_coef +
                    self.user_pitch * (1. - self.pitch_filter_coef))
        self.roll += round((self.user_roll - self.roll) / 360.) * 360
        self.roll = (self.roll * self.roll_filter_coef +
                    self.user_roll * (1. - self.roll_filter_coef))
        self.yaw += round((self.user_yaw - self.yaw) / 360.) * 360
        self.yaw = (self.yaw * self.yaw_filter_coef +
                    self.user_yaw * (1. - self.yaw_filter_coef))
        self.speed = (self.speed * self.speed_filter_coef +
                    self.user_speed * (1. - self.speed_filter_coef))
        self.alt = (self.alt * self.alt_filter_coef +
                    self.user_alt * (1. - self.alt_filter_coef))
        self.rthome += round((self.user_rthome - self.rthome) / 360.) * 360
        self.rthome = (self.rthome * self.rthome_filter_coef +
                    self.user_rthome * (1. - self.rthome_filter_coef))
        self.update()

    def draw(self, painter):
        """Draw the widget."""
        if self.mode == "round":
            self.drawHorizonRound(painter)
        elif self.mode == "rectangle":
            self.drawHorizon(painter)
        self.drawPitchGraduation(painter)
        self.drawRollGraduation(painter)
        self.drawYaw(painter)
        self.drawCenterRef(painter)
        if self.mode != "round":
            self.drawSpeed(painter)
            self.drawAlt(painter)
            self.drawReturnToHome(painter)
            self.drawTxtInfo(painter)

    def getSkyBrush(self):
        """return the color gradient for the sky (blue)"""
        sky_gradient = QLinearGradient(self.adjdev.topLeft(),
                                       self.adjdev.bottomRight())
        color1 = Qt.blue
        color2 = Qt.darkBlue
        sky_gradient.setColorAt(0, color1)
        sky_gradient.setColorAt(.8, color2)
        return sky_gradient

    def getGroundBrush(self):
        """return the color gradient for the ground (marron)"""
        ground_gradient = QLinearGradient(self.adjdev.topLeft(),
                                          self.adjdev.bottomRight())
        color1 = QColor(140, 100, 80)
        color2 = QColor(140, 100, 40)
        ground_gradient.setColorAt(0, color1)
        ground_gradient.setColorAt(.8, color2)
        return ground_gradient

    def getCenterRadius(self):
        """Return the radius of the widget circle"""
        if self.mode == "round":
            return self.adjdev.width() / 2.
        else:
            return self.adjdev.width() / 3.5

    def drawText(self, painter, center, s):
        r = self.getCenterRadius()
        painter.save()
        pen = QPen(Qt.white, r / 150., Qt.SolidLine)
        brush = QBrush(Qt.white)
        painter.setBrush(brush)
        pen.setColor(Qt.black);
        painter.setPen(pen)
        font = QFont("Meiryo UI", 0, QFont.Bold)
        font.setPointSizeF(self.FONT_SIZE * r)
        painter.setFont(font)
        path = QPainterPath()
        for l in s.split("\n"):
            path.addText(center, font, l)
            center += QPoint(0, self.FONT_SIZE * r * 1.5)
        bounding = path.boundingRect()
        path.translate(-bounding.width() / 2., bounding.height() / 2.)
        painter.drawPath(path)
        painter.restore()

    def drawHorizonRound(self, painter):
        """Draw the horizon for round widget: the sky in blue,
           the ground in marron."""

        # set local pitch and roll
        pitch = self.pitch
        roll = self.roll
        if pitch > 90.:
            pitch = 180. - pitch
            roll += 180.
            if roll > 180.:
                roll -= 360.
        if pitch < -90.:
            pitch = -180. - pitch
            roll += 180.
            if roll > 180.:
                roll -= 360.

        # we have to draw a partial circle delimited by its chord, define
        # where the chord starts
        start_angle = math.degrees(math.asin(pitch / 90.)) - roll
        span = 2 * math.degrees(math.asin(pitch / 90.))

        # draw the sky
        painter.setBrush(self.getSkyBrush())
        # startAngle and spanAngle must be specified in 1/16th of a degree
        painter.drawChord(self.adjdev, 16 * start_angle, 16 * (180. - span))

        # draw the ground
        painter.setBrush(self.getGroundBrush())
        # startAngle and spanAngle must be specified in 1/16th of a degree
        painter.drawChord(self.adjdev, 16 * start_angle, -16 * (180. + span))

    def drawHorizon(self, painter):
        """Draw the horizon: the sky in blue, the ground in marron."""
        painter.save()
        painter.setBrush(self.getSkyBrush())
        painter.drawRect(self.dev)
        center = self.adjdev.center()
        r = self.getCenterRadius()
        # radius of the adjusted screen (same than r if roundWidget = True)
        dev_r = self.adjdev.width() / 2.
        roll = self.roll
        if self.pitch < -90.:
            pitch = -180 - self.pitch
        elif self.pitch < 90.:
            pitch = self.pitch
        else:
            pitch = 180 - self.pitch
        y_off = (pitch / self.CAM_ANGLE) * dev_r
        painter.translate(center.x(), center.y())
        painter.rotate(roll)
        ground_rect = QRect(0, 0, self.max_dim * 5., self.max_dim * 5.)
        if self.pitch < 90. and self.pitch > -90.:
            ground_rect.moveCenter(QPoint(0, -y_off + ground_rect.width()/2.))
        else:
            ground_rect.moveCenter(QPoint(0, y_off - ground_rect.width()/2.))
        painter.setBrush(self.getGroundBrush())
        painter.drawRect(ground_rect)
        painter.restore()

    def drawCenterRef(self, painter):
        """Draw the cross on the middle of the OSD"""
        center = self.adjdev.center()
        r = self.getCenterRadius()
        pen = QPen(Qt.red, r / 100., Qt.SolidLine)
        painter.setPen(pen)
        pt1 = QPoint(center.x() - 0.05 * r, center.y())
        pt2 = QPoint(center.x() + 0.05 * r, center.y())
        painter.drawLine(pt1, pt2)
        pt1 = QPoint(center.x(), center.y() + -0.025 * r)
        pt2 = QPoint(center.x(), center.y() + 0.025 * r)
        painter.drawLine(pt1, pt2)

    def drawPitchGraduation(self, painter):
        """Draw the pitch graduations."""
        # change the reference
        painter.save()
        center = self.adjdev.center()
        r = self.getCenterRadius()
        # radius of the adjusted screen (same than r if roundWidget = True)
        dev_r = self.adjdev.width() / 2.
        roll = self.roll
        x_off = (self.pitch / self.CAM_ANGLE) * dev_r * math.sin(math.radians(roll))
        y_off = (self.pitch / self.CAM_ANGLE) * dev_r * math.cos(math.radians(roll))
        painter.translate(center.x() + x_off, center.y() - y_off)
        painter.rotate(roll)

        # set font and pen
        pen = QPen(Qt.white, r / 100., Qt.SolidLine)
        painter.setPen(pen)

        # round to nearest angle that is a multiple of step
        a = self.pitch / self.PITCH_REFLINES_STEP_ANGLE
        a = round(a)
        a = int(a * self.PITCH_REFLINES_STEP_ANGLE)
        a -= self.PITCH_REFLINES_STEP_ANGLE * (self.PITCH_REFLINES_NUM_LINES / 2.)
        angles = [ a + i * self.PITCH_REFLINES_STEP_ANGLE
                   for i in range(self.PITCH_REFLINES_NUM_LINES + 1) ]
        for a in angles:
            # thin line
            if int(a) % int(self.PITCH_REFLINES_BOLD_STEP_ANGLE) != 0:
                pen = QPen(Qt.white, r / 100., Qt.SolidLine)
                painter.setPen(pen)
                pt1 = QPoint(-0.05 * r, dev_r / self.CAM_ANGLE * a)
                pt2 = QPoint(0.05 * r, dev_r / self.CAM_ANGLE * a)
                painter.drawLine(pt1, pt2)
                continue

            # bold line
            pen = QPen(Qt.white, r / 50., Qt.SolidLine)
            painter.setPen(pen)
            pt1 = QPoint(-0.2 * r, dev_r / self.CAM_ANGLE * a)
            pt2 = QPoint(0.2 * r, dev_r / self.CAM_ANGLE * a)
            painter.drawLine(pt1, pt2)

            # the left text
            disp_val = -a
            if disp_val > 90.:
                disp_val = 180. - disp_val
            if disp_val < -90.:
                disp_val = -180. - disp_val
            disp_val = str(int(disp_val))
            lefttxt_pt = pt1 - QPoint(0.2 * r, 0)
            self.drawText(painter, lefttxt_pt, disp_val)

            # flip the right text
            painter.save()
            painter.translate(pt2 + QPoint(0.2 * r, 0))
            painter.rotate(180.)
            painter.translate(-pt2 - QPoint(0.2 * r, 0))
            righttxt_pt = pt2 + QPoint(0.2 * r, 0)
            self.drawText(painter, righttxt_pt, disp_val)
            painter.restore()

        painter.restore()

    def drawOneRollGraduation(self, painter, deg, disp_text):
        # draw the graduiation
        r = self.getCenterRadius()
        center = self.adjdev.center()
        x = center.x() - math.cos(math.radians(deg)) * r
        y = center.y() - math.sin(math.radians(deg)) * r
        pt = QPoint(x, y)
        path = QPainterPath()
        path.moveTo(pt)
        path.lineTo(center)
        pt2 = path.pointAtPercent(0.075) # graduation len is 7.5% of the radius
        painter.drawLine(pt, pt2)
        # then draw the text
        if disp_text == True:
            pt_txt = path.pointAtPercent(0.2)
            disp_val = deg
            if disp_val > 90:
                disp_val = 180. - disp_val
            disp_val = str(int(disp_val))
            self.drawText(painter, pt_txt, disp_val)

    def drawRollGraduation(self, painter):
        """Draw the roll graduations."""
        center = self.adjdev.center()
        r = self.getCenterRadius()

        # draw the red reference lines (pitch 0)
        painter.save()
        painter.translate(center.x(), center.y())
        painter.rotate(self.roll)
        pen = QPen(Qt.red, r / 100., Qt.SolidLine)
        painter.setPen(pen)
        pt1 = QPoint(-0.925 * r, 0)
        pt2 = QPoint(-0.85 * r, 0)
        painter.drawLine(pt1, pt2)
        pt1 = QPoint(0.925 * r, 0)
        pt2 = QPoint(0.85 * r, 0)
        painter.drawLine(pt1, pt2)
        painter.restore()

        pen = QPen(Qt.white, r / 50., Qt.SolidLine)
        painter.setPen(pen)
        deg = 0
        while deg <= 180:
            if deg % 30 == 0:
                w = r / 50.
                disp_text = True
            else:
                w = r / 100.
                disp_text = False
            pen.setWidth(w)
            painter.setPen(pen)
            self.drawOneRollGraduation(painter, deg, disp_text)
            deg += 10

    def drawYaw(self, painter):
        center = self.adjdev.center()
        r = self.getCenterRadius()
        pen = QPen(Qt.red, r / 100., Qt.SolidLine)
        painter.setPen(pen)
        if self.mode == "round":
            y_txt = center.y() + r * 0.6
            y1 = center.y() + r * 0.7
            y2 = center.y() + r * 0.8
            y3 = center.y() + r * 0.85
        else:
            y_txt = center.y() + r * 1.
            y1 = center.y() + r * 1.1
            y2 = center.y() + r * 1.2
            y3 = center.y() + r * 1.25
        pt1 = QPoint(center.x(), y2)
        pt2 = QPoint(center.x(), y3)
        painter.drawLine(pt1, pt2)

        # round to nearest angle multiple of step
        a = self.yaw / self.YAW_REFLINES_STEP_ANGLE
        a = round(a)
        a = int(a * self.YAW_REFLINES_STEP_ANGLE)
        a -= self.YAW_REFLINES_STEP_ANGLE * (self.YAW_REFLINES_NUM_LINES / 2.)
        angles = [ a + i * self.YAW_REFLINES_STEP_ANGLE
                   for i in range(self.YAW_REFLINES_NUM_LINES + 1) ]
        for a in angles:
            # text (N, S, E, W)
            if int(a) % 90 == 0:
                disp_text = True
                pen = QPen(Qt.white, r / 50., Qt.SolidLine)
                painter.setPen(pen)
            else:
                disp_text = False
                pen = QPen(Qt.white, r / 100., Qt.SolidLine)
                painter.setPen(pen)
            # the line
            x = center.x() - ((r / (self.YAW_REFLINES_TOTAL_ANGLE / 2.)) *
                              (self.yaw - a) * self.YAW_REFLINES_SIZE_FACTOR)
            pt_txt = QPoint(x, y_txt)
            pt1 = QPoint(x, y1)
            pt2 = QPoint(x, y2)
            painter.drawLine(pt1, pt2)
            if disp_text == False:
                continue
            disp_val = ["N", "E", "S", "W"][(int(a)/90)%4]
            self.drawText(painter, pt_txt, disp_val)

    def drawSpeed(self, painter):
        center = self.adjdev.center()
        r = self.getCenterRadius()
        pen = QPen(Qt.red, r / 100., Qt.SolidLine)
        painter.setPen(pen)
        x1 =  center.x() - 1.5 * r
        x2 =  center.x() - 1.6 * r
        pt1 = QPoint(center.x() - 1.45 * r, center.y())
        pt2 = QPoint(x2, center.y())
        painter.drawLine(pt1, pt2)

        # round to nearest angle multiple of step
        s = self.speed / self.SPEED_REFLINES_STEP
        s = round(s)
        s = int(s * self.SPEED_REFLINES_STEP)
        s -= self.SPEED_REFLINES_STEP * (self.SPEED_REFLINES_NUM_LINES / 2.)
        speeds = [ s + i * self.SPEED_REFLINES_STEP
                   for i in range(self.SPEED_REFLINES_NUM_LINES + 1) ]
        for s in speeds:
            if int(s) % int(self.SPEED_REFLINES_BOLD_STEP) == 0:
                disp_text = True
                pen = QPen(Qt.white, r / 50., Qt.SolidLine)
                painter.setPen(pen)
            else:
                disp_text = False
                pen = QPen(Qt.white, r / 100., Qt.SolidLine)
                painter.setPen(pen)
            # the line
            y = center.y() + ((r / (self.SPEED_REFLINES_TOTAL/2.)) *
                              (self.speed - s) * self.SPEED_REFLINES_SIZE_FACTOR)
            pt_txt = QPoint(center.x() + r * -1.75, y)
            pt1 = QPoint(x1, y)
            pt2 = QPoint(x2, y)
            painter.drawLine(pt1, pt2)
            if disp_text == False:
                continue
            disp_val = str(int(s))
            self.drawText(painter, pt_txt, disp_val)

    def drawAlt(self, painter):
        center = self.adjdev.center()
        r = self.getCenterRadius()
        pen = QPen(Qt.red, r / 100., Qt.SolidLine)
        painter.setPen(pen)
        x1 =  center.x() + 1.5 * r
        x2 =  center.x() + 1.6 * r
        pt1 = QPoint(center.x() + 1.45 * r, center.y())
        pt2 = QPoint(x2, center.y())
        painter.drawLine(pt1, pt2)

        # round to nearest angle multiple of step
        a = self.alt / self.ALT_REFLINES_STEP
        a = round(a)
        a = int(a * self.ALT_REFLINES_STEP)
        a -= self.ALT_REFLINES_STEP * (self.ALT_REFLINES_NUM_LINES / 2.)
        alts = [ a + i * self.ALT_REFLINES_STEP
                   for i in range(self.ALT_REFLINES_NUM_LINES + 1) ]
        for a in alts:
            if int(a) % int(self.ALT_REFLINES_BOLD_STEP) == 0:
                disp_text = True
                pen = QPen(Qt.white, r / 50., Qt.SolidLine)
                painter.setPen(pen)
            else:
                disp_text = False
                pen = QPen(Qt.white, r / 100., Qt.SolidLine)
                painter.setPen(pen)
            # the line
            y = center.y() + ((r / (self.ALT_REFLINES_TOTAL / 2.)) *
                              (self.alt - a) * self.ALT_REFLINES_SIZE_FACTOR)
            pt_txt = QPoint(center.x() + r * 1.75, y)
            pt1 = QPoint(x1, y)
            pt2 = QPoint(x2, y)
            painter.drawLine(pt1, pt2)
            if disp_text == False:
                continue
            disp_val = str(int(a))
            self.drawText(painter, pt_txt, disp_val)

    def drawReturnToHome(self, painter):
        center = self.adjdev.center()
        r = self.getCenterRadius()
        dev_r = self.adjdev.width() / 2.
        painter.save()
        painter.translate(center.x(), center.y() - 0.9 * dev_r)
        painter.rotate(self.yaw) # XXX
        pen = QPen(Qt.white, r / 100., Qt.SolidLine)
        painter.setPen(pen)
        poly = QPolygonF()
        poly.append(QPoint(0., -0.08 * r))
        poly.append(QPoint(0.04 * r, 0.08 * r))
        poly.append(QPoint(-0.04 * r, 0.08 * r))
        poly.append(QPoint(0., -0.08 * r))
        path = QPainterPath()
        path.addPolygon(poly)
        brush = QBrush(Qt.darkGray)
        painter.setBrush(brush)
        painter.drawPath(path)
        painter.restore()

    def drawTxtInfo(self, painter):
        center = self.adjdev.center()
        dev_r = self.adjdev.width() / 2.
        r = self.getCenterRadius()
        pen = QPen(Qt.white, r / 100., Qt.SolidLine)
        painter.setPen(pen)

        pt_txt = QPoint(center.x() + dev_r * -0.95,
                               center.y() + dev_r * -0.95)
        self.drawText(painter, pt_txt, self.right_txt)

        pt_txt = QPoint(center.x() + dev_r * 0.95,
                               center.y() + dev_r * -0.95)
        self.drawText(painter, pt_txt, self.right_txt)

    def setPitch(self, pitch):
        """set the pitch in degrees, between -180 and 180"""
        pitch = pitch % 360.
        if pitch > 180.:
            pitch -= 360.
        self.user_pitch = pitch

    def setRoll(self, roll):
        """set the roll in degrees, between -180 and 180"""
        roll = roll % 360.
        if roll > 180.:
            roll -= 360.
        self.user_roll = roll

    def setYaw(self, yaw):
        """set the yaw in degrees, between 0 and 360"""
        yaw = yaw % 360.
        self.user_yaw = yaw

    def setSpeed(self, speed):
        """set the speed"""
        self.user_speed = speed

    def setAlt(self, alt):
        """set the alt"""
        self.user_alt = alt

    def setReturnToHomeAngle(self, angle):
        """set the left text"""
        self.user_rthome = angle

    def setLeftTxt(self, txt):
        """set the right text"""
        self.left_txt = txt

    def setRightTxt(self, txt):
        """set the left text"""
        self.right_txt = txt

class OSDGraphicsView(QGraphicsView):
    def __init__(self, parent=None):
        super(OSDGraphicsView, self).__init__(parent)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setMinimumSize(200, 150)
    def resizeEvent(self, event):
        # use item 0 to resize automatically
        self.fitInView(self.items()[0], Qt.KeepAspectRatio)
        super(OSDGraphicsView, self).resizeEvent(event)

class Ui_OSD(QMainWindow):
    def __init__(self, parent = None, mode = "camera", filename = None):
        super(Ui_OSD, self).__init__(parent)
        self.ui = qtosd_ui.Ui_MainWindow()
        self.ui.setupUi(self)

        self.mode = mode
        self.filename = filename

        self.osd = OSDWidget(mode = self.mode,
                             filename = self.filename)
        self.osd.setObjectName("osd")
        self.ui.pitchSlider.valueChanged[int].connect(self.changePitch)
        self.ui.rollSlider.valueChanged[int].connect(self.changeRoll)
        self.ui.yawSlider.valueChanged[int].connect(self.changeYaw)
        self.ui.actionExit.triggered.connect(self.close)

        self.scene = QGraphicsScene(self)
        self.graphicsView = OSDGraphicsView(self.scene)

        if self.mode == "camera":
            self.videoItem = QGraphicsVideoItem()
            self.videoItem.setSize(QSizeF(640, 480)) # XXX
            self.scene.addItem(self.videoItem)

            x = self.videoItem.boundingRect().width() / 2.0
            y = self.videoItem.boundingRect().height() / 2.0
            #self.videoItem.setTransform(
            #        QTransform().translate(x, y).rotate(70).translate(-x, -y))

            self.initCamera()

        self.scene.addWidget(self.osd)
        self.ui.gridLayout.addWidget(self.graphicsView, 0, 1)

    def initCamera(self):
        # find camera devices and add them in the menu
        cameraDevice = QByteArray()
        videoDevicesGroup = QActionGroup(self)
        videoDevicesGroup.setExclusive(True)
        for deviceName in QCamera.availableDevices():
            description = QCamera.deviceDescription(deviceName)
            videoDeviceAction = QAction(description, videoDevicesGroup)
            videoDeviceAction.setCheckable(True)
            videoDeviceAction.setData(deviceName)
            if cameraDevice.isEmpty():
                cameraDevice = deviceName
                videoDeviceAction.setChecked(True)
            self.ui.menuDevices.addAction(videoDeviceAction)
        videoDevicesGroup.triggered.connect(self.updateCameraDevice)
        # select the first camera
        self.setCamera(cameraDevice)

    def setCamera(self, cameraDevice):
        if cameraDevice.isEmpty():
            self.camera = QCamera()
        else:
            self.camera = QCamera(cameraDevice)

        self.camera.stateChanged.connect(self.updateCameraState)
        self.camera.error.connect(self.displayCameraError)

        #self.ui.exposureCompensation.valueChanged.connect(
        #        self.setExposureCompensation)

        self.camera.setCaptureMode(QCamera.CaptureViewfinder)
        self.camera.setViewfinder(self.videoItem)
        self.updateCameraState(self.camera.state())
        self.camera.start()

    #XXX stop camera? remove from scene?

    def updateCameraDevice(self, action):
        print "updateCameraDevice"
        self.setCamera(action.data())

    def updateCameraState(self, state):
        print "updateCameraState %s"%(str(state))

    def displayCameraError(self):
        print "displayCameraError"
        QMessageBox.warning(self, "Camera error", self.camera.errorString())

    def keyPressEvent(self, event):
        key = event.key()
        if key == Qt.Key_J:
            self.osd.setRoll(self.osd.user_roll + 2)
            event.accept()
        elif key == Qt.Key_L:
            self.osd.setRoll(self.osd.user_roll - 2)
            event.accept()
        elif key == Qt.Key_I:
            self.osd.setPitch(self.osd.user_pitch + 2)
            event.accept()
        elif key == Qt.Key_K:
            self.osd.setPitch(self.osd.user_pitch - 2)
            event.accept()
        elif key == Qt.Key_Q:
            self.close()
        elif not event.isAutoRepeat():
            if key == Qt.Key_CameraFocus:
                self.camera.searchAndLock()
                event.accept()
            else:
                super(Ui_OSD, self).keyPressEvent(event)

    def keyReleaseEvent(self, event):
        key = event.key()
        if event.isAutoRepeat():
            return
        if key == Qt.Key_CameraFocus:
            self.camera.unlock()
        else:
            super(Ui_OSD, self).keyReleaseEvent(event)

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))

    @pyqtSlot(int, name = "changePitch")
    def changePitch(self, value):
        self.osd.setPitch(value)

    @pyqtSlot(int, name = "changeRoll")
    def changeRoll(self, value):
        self.osd.setRoll(value)

    @pyqtSlot(int, name = "changeYaw")
    def changeYaw(self, value):
        self.osd.setYaw(value)

if __name__ == "__main__":
    import sys

    parser = argparse.ArgumentParser(description='OSD written in Qt.')
    parser.add_argument('--mode', '-m', action="store",
                        choices=["round", "rectangle", "camera"],
                        help='display the widget as a round attitude meter')
    parser.add_argument('--filename', '-f',
                        help='specify the log file')
    args = parser.parse_args()

    app = QApplication(sys.argv)
    ui = Ui_OSD(filename = args.filename, mode = args.mode)
    ui.show()
    sys.exit(app.exec_())

