#!/usr/bin/env python
#
# by
# Robin Wittler <real@the-real.org> (speedometer mode)
# and
# Chen Wei <weichen302@gmx.com> (nautical mode)
#
# BSD terms apply: see the file COPYING in the distribution root for details.

# This code runs compatibly under Python 2 and 3.x for x >= 2.
# Preserve this property!
from __future__ import absolute_import, print_function, division

from math import pi
from math import cos
from math import sin
from math import sqrt
from math import radians
from socket import error as SocketError

import gps

import cairo

# Gtk3 imports.  Gtk3 requires the require_version(), which then causes
# pylint to complain about the subsequent "non-top" imports.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk  # pylint: disable=wrong-import-position
from gi.repository import Gdk  # pylint: disable=wrong-import-position
from gi.repository import GObject  # pylint: disable=wrong-import-position


class Speedometer(Gtk.DrawingArea):
    def __init__(self, speed_unit=None):
        Gtk.DrawingArea.__init__(self)
        self.MPH_UNIT_LABEL = 'mph'
        self.KPH_UNIT_LABEL = 'kmh'
        self.KNOTS_UNIT_LABEL = 'knots'
        self.conversions = {
            self.MPH_UNIT_LABEL: gps.MPS_TO_MPH,
            self.KPH_UNIT_LABEL: gps.MPS_TO_KPH,
            self.KNOTS_UNIT_LABEL: gps.MPS_TO_KNOTS
        }
        self.speed_unit = speed_unit or self.MPH_UNIT_LABEL
        if self.speed_unit not in self.conversions:
            raise TypeError(
                '%s is not a valid speed unit'
                % (repr(speed_unit))
            )


class LandSpeedometer(Speedometer):
    def __init__(self, speed_unit=None):
        Speedometer.__init__(self, speed_unit)
        self.connect('size-allocate', self.on_size_allocate)
        self.width = self.height = 0
        self.connect('draw', self.draw_s)
        self.long_ticks = (2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8)
        self.short_ticks = (0.1, 0.2, 0.3, 0.4, 0.6, 0.7, 0.8, 0.9)
        self.long_inset = lambda x: 0.1 * x
        self.middle_inset = lambda x: self.long_inset(x) / 1.5
        self.short_inset = lambda x: self.long_inset(x) / 3
        self.res_div = 10.0
        self.res_div_mul = 1
        self.last_speed = 0
        self.nums = {
            -8: 0,
            -7: 10,
            -6: 20,
            -5: 30,
            -4: 40,
            -3: 50,
            -2: 60,
            -1: 70,
            0: 80,
            1: 90,
            2: 100
        }

    def on_size_allocate(self, _unused, allocation):
        self.width = allocation.width
        self.height = allocation.height

    def draw_s(self, _unused, _event, _empty=None):
        self.cr = self.get_window().cairo_create()
        self.cr.rectangle(0, 0, self.width, self.height)
        self.cr.clip()
        x, y = self.get_x_y()
        width, height = self.get_window().get_geometry()[2:4]
        radius = self.get_radius(width, height)
        self.cr.set_line_width(radius / 100)
        self.draw_arc_and_ticks(width, height, radius, x, y)
        self.draw_needle(self.last_speed, radius, x, y)
        self.draw_speed_text(self.last_speed, radius, x, y)

    def draw_arc_and_ticks(self, width, height, radius, x, y):
        self.cr.set_source_rgb(1.0, 1.0, 1.0)
        self.cr.rectangle(0, 0, width, height)
        self.cr.fill()
        self.cr.set_source_rgb(0.0, 0.0, 0.0)

        # draw the speedometer arc
        self.cr.arc_negative(x, y, radius, radians(60), radians(120))
        self.cr.stroke()
        long_inset = self.long_inset(radius)
        middle_inset = self.middle_inset(radius)
        short_inset = self.short_inset(radius)

        # draw the ticks
        for i in self.long_ticks:
            self.cr.move_to(
                x + (radius - long_inset) * cos(i * pi / 6.0),
                y + (radius - long_inset) * sin(i * pi / 6.0)
            )
            self.cr.line_to(
                (x + (radius + (self.cr.get_line_width() / 2))
                 * cos(i * pi / 6.0)),
                (y + (radius + (self.cr.get_line_width() / 2))
                 * sin(i * pi / 6.0))
            )
            self.cr.select_font_face(
                'Georgia',
                cairo.FONT_SLANT_NORMAL,
            )
            self.cr.set_font_size(radius / 10)
            self.cr.save()
            _num = str(self.nums.get(i) * self.res_div_mul)
            (
                _x_bearing,
                _y_bearing,
                t_width,
                t_height,
                _x_advance,
                _y_advance
            ) = self.cr.text_extents(_num)

            if i in (-8, -7, -6, -5, -4):
                self.cr.move_to(
                    (x + (radius - long_inset - (t_width / 2))
                     * cos(i * pi / 6.0)),
                    (y + (radius - long_inset - (t_height * 2))
                     * sin(i * pi / 6.0))
                )
            elif i in (-2, -1, 0, 2, 1):
                self.cr.move_to(
                    (x + (radius - long_inset - (t_width * 1.5))
                     * cos(i * pi / 6.0)),
                    (y + (radius - long_inset - (t_height * 2))
                     * sin(i * pi / 6.0))
                )
            elif i in (-3,):
                self.cr.move_to(
                    (x - t_width / 2),
                    (y - radius + self.long_inset(radius) * 2 + t_height)
                )
            self.cr.show_text(_num)
            self.cr.restore()

            if i != self.long_ticks[0]:
                self.cr.move_to(
                    x + (radius - middle_inset) * cos((i + 0.5) * pi / 6.0),
                    y + (radius - middle_inset) * sin((i + 0.5) * pi / 6.0)
                )
                self.cr.line_to(
                    x + (radius + (self.cr.get_line_width() / 2)) *
                    cos((i + 0.5) * pi / 6.0),
                    y + (radius + (self.cr.get_line_width() / 2)) *
                    sin((i + 0.5) * pi / 6.0)
                )

            for z in self.short_ticks:
                w_half = self.cr.get_line_width() / 2
                if i < 0:
                    self.cr.move_to(
                        x + (radius - short_inset) * cos((i + z) * pi / 6.0),
                        y + (radius - short_inset) * sin((i + z) * pi / 6.0)
                    )
                    self.cr.line_to(
                        x + (radius + w_half) * cos((i + z) * pi / 6.0),
                        y + (radius + w_half) * sin((i + z) * pi / 6.0)
                    )
                else:
                    self.cr.move_to(
                        x + (radius - short_inset) * cos((i - z) * pi / 6.0),
                        y + (radius - short_inset) * sin((i - z) * pi / 6.0)
                    )
                    self.cr.line_to(
                        x + (radius + w_half) * cos((i - z) * pi / 6.0),
                        y + (radius + w_half) * sin((i - z) * pi / 6.0)
                    )
            self.cr.stroke()

    def draw_needle(self, speed, radius, x, y):
        self.cr.save()
        inset = self.long_inset(radius)
        speed = speed * self.conversions.get(self.speed_unit)
        speed = speed / (self.res_div * self.res_div_mul)
        actual = self.long_ticks[-1] + speed
        if actual > self.long_ticks[0]:
            self.res_div_mul += 1
            speed = speed / (self.res_div * self.res_div_mul)
            actual = self.long_ticks[-1] + speed
        self.cr.move_to(x, y)
        self.cr.line_to(
            x + (radius - (2 * inset)) * cos(actual * pi / 6.0),
            y + (radius - (2 * inset)) * sin(actual * pi / 6.0)
        )
        self.cr.stroke()
        self.cr.restore()

    def draw_speed_text(self, speed, radius, x, y):
        self.cr.save()
        speed = '%.2f %s' % (
                speed * self.conversions.get(self.speed_unit),
                self.speed_unit
        )
        self.cr.select_font_face(
            'Georgia',
            cairo.FONT_SLANT_NORMAL,
            # cairo.FONT_WEIGHT_BOLD
        )
        self.cr.set_font_size(radius / 10)
        _x_bearing, _y_bearing, t_width, _t_height = \
            self.cr.text_extents(speed)[:4]
        self.cr.move_to((x - t_width / 2),
                        (y + radius) - self.long_inset(radius))
        self.cr.show_text(speed)
        self.cr.restore()

    def get_x_y(self):
        rect = self.get_allocation()
        x = (rect.x + rect.width / 2.0)
        y = (rect.y + rect.height / 2.0) - 20
        return x, y

    def get_radius(self, width, height):
        return min(width / 2.0, height / 2.0) - 20


class NauticalSpeedometer(Speedometer):
    HEADING_SAT_GAP = 0.8
    SAT_SIZE = 10  # radius of the satellite circle in skyview

    def __init__(self, speed_unit=None, maxspeed=100, rotate=0.0):
        Speedometer.__init__(self, speed_unit)
        self.connect('size-allocate', self.on_size_allocate)
        self.width = self.height = 0
        self.connect('draw', self.draw_s)
        self.long_inset = lambda x: 0.05 * x
        self.mid_inset = lambda x: self.long_inset(x) / 1.5
        self.short_inset = lambda x: self.long_inset(x) / 3
        self.last_speed = 0
        self.satellites = []
        self.last_heading = 0
        self.maxspeed = int(maxspeed)
        self.rotate = radians(rotate)

    def polar2xy(self, radius, angle, polex, poley):
        '''convert Polar coordinate to Cartesian coordinate system
        the y axis in pygtk points downward
        Args:
           radius:
           angle: azimuth from from Polar coordinate system, in radian
           polex and poley are the Cartesian coordinate of the pole
        return a tuple contains (x, y)'''
        angle = (angle + self.rotate) % (pi * 2)  # Note reversed sense
        return (polex + cos(angle) * radius, poley - sin(angle) * radius)

    def on_size_allocate(self, _unused, allocation):
        self.width = allocation.width
        self.height = allocation.height

    def draw_s(self, _unused, _event, _empty=None):
        self.cr = self.get_window().cairo_create()
        self.cr.rectangle(0, 0, self.width, self.height)
        self.cr.clip()
        x, y = self.get_x_y()
        width, height = self.get_window().get_geometry()[2:4]
        radius = self.get_radius(width, height)
        self.cr.set_line_width(radius / 100)
        self.draw_arc_and_ticks(width, height, radius, x, y)
        self.draw_heading(20, self.last_heading, radius, x, y)
        for sat in self.satellites:
            self.draw_sat(sat, radius * NauticalSpeedometer.HEADING_SAT_GAP,
                          x, y)
        self.draw_speed(radius, x, y)

    def draw_text(self, x, y, text, fontsize=10):
        '''draw text at given location
        Args:
            x, y is the center of textbox'''
        txt = str(text)
        self.cr.new_sub_path()
        self.cr.set_source_rgba(0, 0, 0)
        self.cr.select_font_face('Sans',
                                 cairo.FONT_SLANT_NORMAL,
                                 cairo.FONT_WEIGHT_BOLD)
        self.cr.set_font_size(fontsize)
        (_x_bearing, _y_bearing,
         t_width, t_height) = self.cr.text_extents(txt)[:4]
        # set the center of textbox
        self.cr.move_to(x - t_width / 2, y + t_height / 2)
        self.cr.show_text(txt)

    def draw_arc_and_ticks(self, width, height, radius, x, y):
        '''Draw a serial of circle, with ticks in outmost circle'''

        self.cr.set_source_rgb(1.0, 1.0, 1.0)
        self.cr.rectangle(0, 0, width, height)
        self.cr.fill()
        self.cr.set_source_rgba(0, 0, 0)

        # draw the speedmeter arc
        rspeed = radius + 50
        self.cr.arc(x, y, rspeed, 2 * pi / 3, 7 * pi / 3)
        self.cr.set_source_rgba(0, 0, 0, 1.0)
        self.cr.stroke()
        s_long = self.long_inset(rspeed)
        s_middle = self.mid_inset(radius)
        s_short = self.short_inset(radius)
        for i in range(11):
            # draw the large ticks
            alpha = (8 - i) * pi / 6
            self.cr.move_to(*self.polar2xy(rspeed, alpha, x, y))
            self.cr.set_line_width(radius / 100)
            self.cr.line_to(*self.polar2xy(rspeed - s_long, alpha, x, y))
            self.cr.stroke()
            self.cr.set_line_width(radius / 200)
            xf, yf = self.polar2xy(rspeed + 10, alpha, x, y)
            stxt = (self.maxspeed // 10) * i
            self.draw_text(xf, yf, stxt, fontsize=radius / 15)

        for i in range(1, 11):
            # middle tick
            alpha = (8 - i) * pi / 6
            beta = (17 - 2 * i) * pi / 12
            self.cr.move_to(*self.polar2xy(rspeed, beta, x, y))
            self.cr.line_to(*self.polar2xy(rspeed - s_middle, beta, x, y))

            #  short tick
            for n in range(10):
                gamma = alpha + n * pi / 60
                self.cr.move_to(*self.polar2xy(rspeed, gamma, x, y))
                self.cr.line_to(*self.polar2xy(rspeed - s_short, gamma, x, y))

        # draw the heading arc
        self.cr.new_sub_path()
        self.cr.arc(x, y, radius, 0, 2 * pi)
        self.cr.stroke()
        self.cr.arc(x, y, radius - 20, 0, 2 * pi)
        self.cr.set_source_rgba(0, 0, 0, 0.20)
        self.cr.fill()
        self.cr.set_source_rgba(0, 0, 0)

        # heading label 90/180/270
        for n in range(0, 4):
            label = str(n * 90)
            # self.cr.set_source_rgba(0, 1, 0)
            # radius * (1 + NauticalSpeedometer.HEADING_SAT_GAP),
            tbox_x, tbox_y = self.polar2xy(
                radius * 0.88,
                (1 - n) * pi / 2,
                x, y)
            self.draw_text(tbox_x, tbox_y,
                           label, fontsize=radius / 20)

        # draw the satellite arcs
        skyradius = radius * NauticalSpeedometer.HEADING_SAT_GAP
        self.cr.set_line_width(radius / 200)
        self.cr.set_source_rgba(0, 0, 0)
        self.cr.arc(x, y, skyradius, 0, 2 * pi)
        self.cr.set_source_rgba(1, 1, 1)
        self.cr.fill()
        self.cr.set_source_rgba(0, 0, 0)

        self.cr.arc(x, y, skyradius * 2 / 3, 0, 2 * pi)
        self.cr.move_to(x + skyradius / 3, y)  # Avoid line connecting circles
        self.cr.arc(x, y, skyradius / 3, 0, 2 * pi)

        # draw the cross hair
        self.cr.move_to(*self.polar2xy(skyradius, 1.5 * pi, x, y))
        self.cr.line_to(*self.polar2xy(skyradius, 0.5 * pi, x, y))
        self.cr.move_to(*self.polar2xy(skyradius, 0.0, x, y))
        self.cr.line_to(*self.polar2xy(skyradius, pi, x, y))
        self.cr.set_line_width(radius / 200)
        self.cr.stroke()

        long_inset = self.long_inset(radius)
        mid_inset = self.mid_inset(radius)
        short_inset = self.short_inset(radius)

        # draw the large ticks
        for i in range(12):
            agllong = i * pi / 6
            self.cr.move_to(*self.polar2xy(radius - long_inset, agllong, x, y))
            self.cr.line_to(*self.polar2xy(radius, agllong, x, y))
            self.cr.set_line_width(radius / 100)
            self.cr.stroke()
            self.cr.set_line_width(radius / 200)

            # middle tick
            aglmid = (i + 0.5) * pi / 6
            self.cr.move_to(*self.polar2xy(radius - mid_inset, aglmid, x, y))
            self.cr.line_to(*self.polar2xy(radius, aglmid, x, y))

            #  short tick
            for n in range(1, 10):
                aglshrt = agllong + n * pi / 60
                self.cr.move_to(*self.polar2xy(radius - short_inset,
                                aglshrt, x, y))
                self.cr.line_to(*self.polar2xy(radius, aglshrt, x, y))
            self.cr.stroke()

    def draw_heading(self, trig_height, heading, radius, x, y):
        hypo = trig_height * 2 / sqrt(3)
        h = pi / 2 - radians(heading)  # to xyz
        self.cr.set_line_width(2)
        self.cr.set_source_rgba(0, 0.3, 0.2, 0.8)

        # the triangle pointer
        x0 = x + radius * cos(h)
        y0 = y - radius * sin(h)

        x1 = x0 + hypo * cos(7 * pi / 6 + h)
        y1 = y0 - hypo * sin(7 * pi / 6 + h)

        x2 = x0 + hypo * cos(5 * pi / 6 + h)
        y2 = y0 - hypo * sin(5 * pi / 6 + h)

        self.cr.move_to(x0, y0)
        self.cr.line_to(x1, y1)
        self.cr.line_to(x2, y2)
        self.cr.line_to(x0, y0)
        self.cr.close_path()
        self.cr.fill()
        self.cr.stroke()

        # heading text
        (tbox_x, tbox_y) = self.polar2xy(radius * 1.1, h, x, y)
        self.draw_text(tbox_x, tbox_y, int(heading), fontsize=radius / 15)

        # the ship shape, based on test and try
        shiplen = radius * NauticalSpeedometer.HEADING_SAT_GAP / 4
        xh, yh = self.polar2xy(shiplen * 2.3, h, x, y)
        xa, ya = self.polar2xy(shiplen * 2.2, h + pi - 0.3, x, y)
        xb, yb = self.polar2xy(shiplen * 2.2, h + pi + 0.3, x, y)
        xc, yc = self.polar2xy(shiplen * 1.4, h - pi / 5, x, y)
        xd, yd = self.polar2xy(shiplen * 1.4, h + pi / 5, x, y)

        self.cr.set_source_rgba(0, 0.3, 0.2, 0.5)
        self.cr.move_to(xa, ya)
        self.cr.line_to(xb, yb)
        self.cr.line_to(xc, yc)
        self.cr.line_to(xh, yh)
        self.cr.line_to(xd, yd)
        self.cr.close_path()
        self.cr.fill()
        # self.cr.stroke()

    def set_color(self, spec):
        '''Set foreground color for drawing.'''
        gdkcolor = Gdk.color_parse(spec)
        r = gdkcolor.red / 65535.0
        g = gdkcolor.green / 65535.0
        b = gdkcolor.blue / 65535.0
        self.cr.set_source_rgb(r, g, b)

    def draw_sat(self, satsoup, radius, x, y):
        """Given a sat's elevation, azimuth, SNR, draw it on the skyview
        Arg:
        satsoup: a dictionary {'el': xx, 'az': xx, 'ss': xx}
        """
        el, az = satsoup['el'], satsoup['az']
        if el == 0 and az == 0:
            return  # Skip satellites with unknown position
        h = pi / 2 - radians(az)  # to xy
        self.cr.set_line_width(2)
        self.cr.set_source_rgb(0, 0, 0)

        x0, y0 = self.polar2xy(radius * (90 - el) // 90, h, x, y)

        self.cr.new_sub_path()
        if gps.is_sbas(satsoup['PRN']):
            self.cr.rectangle(x0 - NauticalSpeedometer.SAT_SIZE,
                              y0 - NauticalSpeedometer.SAT_SIZE,
                              NauticalSpeedometer.SAT_SIZE * 2,
                              NauticalSpeedometer.SAT_SIZE * 2)
        else:
            self.cr.arc(x0, y0, NauticalSpeedometer.SAT_SIZE, 0, pi * 2.0)

        if satsoup['ss'] < 10:
            self.set_color('Gray')
        elif satsoup['ss'] < 30:
            self.set_color('Red')
        elif satsoup['ss'] < 35:
            self.set_color('Yellow')
        elif satsoup['ss'] < 40:
            self.set_color('Green3')
        else:
            self.set_color('Green1')

        if satsoup['used']:
            self.cr.fill()
        else:
            self.cr.stroke()
        self.draw_text(x0, y0, satsoup['PRN'], fontsize=15)

    def draw_speed(self, radius, x, y):
        self.cr.new_sub_path()
        self.cr.set_line_width(20)
        self.cr.set_source_rgba(0, 0, 0, 0.5)
        speed = self.last_speed * self.conversions.get(self.speed_unit)
        # cariol arc angle start at polar 0, going clockwise
        alpha = 4 * pi / 3
        beta = 2 * pi - alpha
        theta = 5 * pi * speed / (self.maxspeed * 3)

        self.cr.arc(x, y, radius + 40, beta, beta + theta)
        self.cr.stroke()
        # self.cr.close_path()
        # self.cr.fill()
        label = '%.2f %s' % (speed, self.speed_unit)
        self.draw_text(x, y + radius + 40, label, fontsize=20)

    def get_x_y(self):
        rect = self.get_allocation()
        x = (rect.x + rect.width / 2.0)
        y = (rect.y + rect.height / 2.0) - 20
        return x, y

    def get_radius(self, width, height):
        return min(width / 2.0, height / 2.0) - 70


class Main(object):
    def __init__(self, host='localhost', port=gps.GPSD_PORT, device=None,
                 debug=0, speed_unit=None, maxspeed=0, nautical=False,
                 rotate=0.0, target=""):
        self.host = host
        self.port = port
        self.device = device
        self.debug = debug
        self.speed_unit = speed_unit
        self.maxspeed = maxspeed
        self.nautical = nautical
        self.rotate = rotate
        self.window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
        if not self.window.get_display():
            raise Exception("Can't open display")
        if len(target):
            target = " " + target
        self.window.set_title('xgpsspeed' + target)
        if self.nautical:
            self.window.set_size_request(500, 550)
            self.widget = NauticalSpeedometer(
                speed_unit=self.speed_unit,
                maxspeed=self.maxspeed,
                rotate=self.rotate)
        else:
            self.widget = LandSpeedometer(speed_unit=self.speed_unit)
        self.window.connect('delete-event', self.delete_event)
        self.window.connect('destroy', self.destroy)
        self.widget.show()
        vbox = Gtk.VBox(False, 0)
        self.window.add(vbox)

        self.window.present()
        self.uimanager = Gtk.UIManager()
        self.accelgroup = self.uimanager.get_accel_group()
        self.window.add_accel_group(self.accelgroup)
        self.actiongroup = Gtk.ActionGroup('gpsspeed-ng')
        self.actiongroup.add_actions(
            [
                ('Quit', Gtk.STOCK_QUIT, '_Quit', None,
                    'Quit the Program', lambda unused: Gtk.main_quit()),
                ('File', None, '_File'),
                ('Units', None, '_Units')]
        )
        self.actiongroup.add_radio_actions(
            [
                ('Imperial', None, '_Imperial', '<Control>i',
                    'Imperial Units', 0),
                ('Metric', None, '_Metric', '<Control>m',
                    'Metrical Units', 1),
                ('Nautical', None, '_Nautical', '<Control>n',
                    'Nautical Units', 2)
            ],
            0, lambda a, unused: setattr(
                self.widget, 'speed_unit',
                ['mph', 'kmh', 'knots'][a.get_current_value()])
        )

        self.uimanager.insert_action_group(self.actiongroup, 0)
        self.uimanager.add_ui_from_string('''
<ui>
    <menubar name='MenuBar'>
        <menu action='File'>
            <menuitem action='Quit'/>
        </menu>
        <menu action='Units'>
            <menuitem action='Imperial'/>
            <menuitem action='Metric'/>
            <menuitem action='Nautical'/>
        </menu>
    </menubar>
</ui>
''')
        self.active_unit_map = {
            'mph': '/MenuBar/Units/Imperial',
            'kmh': '/MenuBar/Units/Metric',
            'knots': '/MenuBar/Units/Nautical'
        }
        menubar = self.uimanager.get_widget('/MenuBar')
        self.uimanager.get_widget(
            self.active_unit_map.get(self.speed_unit)
        ).set_active(True)
        vbox.pack_start(menubar, False, False, 0)
        vbox.add(self.widget)
        self.window.show_all()

    def watch(self, daemon, device):
        self.daemon = daemon
        self.device = device
        GObject.io_add_watch(daemon.sock, GObject.IO_IN, self.handle_response)
        GObject.io_add_watch(daemon.sock, GObject.IO_ERR, self.handle_hangup)
        GObject.io_add_watch(daemon.sock, GObject.IO_HUP, self.handle_hangup)
        return True

    def handle_response(self, source, condition):
        if self.daemon.read() == -1:
            self.handle_hangup(source, condition)
        if self.daemon.data['class'] == 'TPV':
            self.update_speed(self.daemon.data)
        if self.nautical and self.daemon.data['class'] == 'SKY':
            self.update_skyview(self.daemon.data)

        return True

    def handle_hangup(self, _dummy, _unused):
        w = Gtk.MessageDialog(
            parent=self.window,
            type=Gtk.MessageType.ERROR,
            flags=Gtk.DialogFlags.DESTROY_WITH_PARENT,
            buttons=Gtk.ButtonsType.OK
        )
        w.connect("destroy", lambda unused: Gtk.main_quit())
        w.set_title('gpsd error')
        w.set_markup("gpsd has stopped sending data.")
        w.run()
        Gtk.main_quit()
        return True

    def update_speed(self, data):
        if hasattr(data, 'speed'):
            self.widget.last_speed = data.speed
            self.widget.queue_draw()
        if self.nautical and hasattr(data, 'track'):
            self.widget.last_heading = data.track
            self.widget.queue_draw()

    # Used for NauticalSpeedometer only
    def update_skyview(self, data):
        "Update the satellite list and skyview."
        if hasattr(data, 'satellites'):
            self.widget.satellites = data.satellites
            self.widget.queue_draw()

    def delete_event(self, _widget, _event, _data=None):
        # Someday, handle all cleanup operations here
        return False

    def destroy(self, _unused, _empty=None):
        Gtk.main_quit()

    def run(self):
        try:
            daemon = gps.gps(
                host=self.host,
                port=self.port,
                mode=gps.WATCH_ENABLE | gps.WATCH_JSON | gps.WATCH_SCALED,
                verbose=self.debug
            )
            self.watch(daemon, self.device)
            Gtk.main()
        except SocketError:
            w = Gtk.MessageDialog(
                parent=self.window,
                type=Gtk.MessageType.ERROR,
                flags=Gtk.DialogFlags.DESTROY_WITH_PARENT,
                buttons=Gtk.ButtonsType.OK
            )
            w.set_title('socket error')
            w.set_markup(
                "could not connect to gpsd socket. make sure gpsd is running."
            )
            w.run()
            w.destroy()
        except KeyboardInterrupt:
            self.window.emit('delete_event', Gdk.Event(Gdk.NOTHING))

if __name__ == '__main__':
    import sys
    from os.path import basename
    from optparse import OptionParser
    prog = basename(sys.argv[0])
    usage = ('%s [-V|--version] [-h|--help] [--debug] [--host] ' +
             '[--port] [--device] [--speedunits {[mph] [kmh] [knots]}] ' +
             '[host [:port [:device]]]') % (prog)
    epilog = 'BSD terms apply: see the file COPYING in the distribution root' \
             ' for details.'

    parser = OptionParser(usage=usage, epilog=epilog)
    parser.add_option(
        '--host',
        dest='host',
        default=None,
        help='The host to connect. [Default localhost]'
    )
    parser.add_option(
        '--port',
        dest='port',
        default=None,
        help='The port to connect. [Default %s]' % gps.GPSD_PORT
    )
    parser.add_option(
        '--device',
        dest='device',
        default=None,
        help='The device to connect. [Default None]'
    )
    parser.add_option(
        '--speedunits',
        dest='speedunits',
        default='mph',
        help='The unit of speed. Possible units are: mph, kmh, knots. '
             '[Default mph]'
    )
    parser.add_option(
        '--maxspeed',
        dest='maxspeed',
        default='50',
        help='Max speed of the speedmeter [Default 50]'
    )
    parser.add_option(
        '--nautical',
        dest='nautical',
        default=True,
        action='store_true',
        help='Enable nautical-style speed and track display.'
    )
    parser.add_option(
        '--landspeed',
        dest='nautical',
        default=True,
        action='store_false',
        help='Enable dashboard-style speedometer.'
    )
    parser.add_option(
        '--debug',
        dest='debug',
        default=0,
        action='store',
        type='int',
        help='Set level of debug. Must be integer. [Default 0]'
    )
    parser.add_option(
        '--rotate',
        dest='rotate',
        default=0,
        action='store',
        type='float',
        help='Rotation of skyview ("up" direction) in degrees. [Default 0]'
    )
    (options, args) = parser.parse_args()
    if args:
        arg = args[0].split(':')
        len_arg = len(arg)
        if len_arg == 1:
            (options.host,) = arg
        elif len_arg == 2:
            (options.host, options.port) = arg
        elif len_arg == 3:
            (options.host, options.port, options.device) = arg
        else:
            parser.print_help()
            sys.exit(0)
        target = ':'.join(args[0:])
    elif options.host or options.port or options.device:
        ltarget = [options.host or 'localhost']
        if options.port or options.device:
            ltarget += [options.port or '']
        if options.device:
            ltarget += [options.device]
        target = ':'.join(ltarget)
    else:
        target = ""
    Main(
        host=options.host or 'localhost',
        port=options.port or gps.GPSD_PORT,
        device=options.device,
        speed_unit=options.speedunits,
        maxspeed=options.maxspeed,
        nautical=options.nautical,
        debug=options.debug,
        rotate=options.rotate,
        target=target,
    ).run()
