#!/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.

import pygtk
pygtk.require('2.0')
import gtk
import cairo
import gobject
import gps
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


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('expose_event', self.expose_event)
        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 expose_event(self, _unused, event, _empty=None):
        self.cr = self.window.cairo_create()
        self.cr.rectangle(
            event.area.x,
            event.area.y,
            event.area.width,
            event.area.height
        )
        self.cr.clip()
        x, y = self.get_x_y()
        width, height = self.window.get_size()
        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:
                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 + (self.cr.get_line_width() / 2)) * cos((i + z) * pi / 6.0),
                        y + (radius + (self.cr.get_line_width() / 2)) * 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 + (self.cr.get_line_width() / 2)) * cos((i - z) * pi / 6.0),
                        y + (radius + (self.cr.get_line_width() / 2)) * 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):
        Speedometer.__init__(self, speed_unit)
        self.connect('expose_event', self.expose_event)
        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)

    @staticmethod
    def polar2xy(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)'''
        return (polex + cos(angle) * radius, poley - sin(angle) * radius)

    def expose_event(self, _unused, event, _empty=None):
        self.cr = self.window.cairo_create()
        self.cr.rectangle(
            event.area.x,
            event.area.y,
            event.area.width,
            event.area.height
        )
        self.cr.clip()
        x, y = self.get_x_y()
        width, height = self.window.get_size()
        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 xrange(11):
            # draw the large ticks
            alpha = (8 - i) * pi / 6
            self.cr.move_to(*NauticalSpeedometer.polar2xy(rspeed, alpha, x, y))
            self.cr.set_line_width(radius / 100)
            self.cr.line_to(*NauticalSpeedometer.polar2xy(rspeed - s_long, alpha, x, y))
            self.cr.stroke()
            self.cr.set_line_width(radius / 200)
            xf, yf = NauticalSpeedometer.polar2xy(rspeed + 10, alpha, x, y)
            stxt = (self.maxspeed / 10) * i
            self.draw_text(xf, yf, stxt, fontsize=radius / 15)

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

            #  short tick
            for n in xrange(10):
                gamma = alpha + n * pi / 60
                self.cr.move_to(*NauticalSpeedometer.polar2xy(rspeed, gamma, x, y))
                self.cr.line_to(*NauticalSpeedometer.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 xrange(0, 4):
            label = str(n * 90)
            # self.cr.set_source_rgba(0, 1, 0)
            # radius * (1 + NauticalSpeedometer.HEADING_SAT_GAP),
            tbox_x, tbox_y = NauticalSpeedometer.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.arc(x, y, skyradius / 3, 0, 2 * pi)

        # draw the cross hair
        self.cr.move_to(x - skyradius, y)
        self.cr.line_to(x + skyradius, y)
        self.cr.move_to(x, y - skyradius)
        self.cr.line_to(x, y + skyradius)
        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 xrange(12):
            agllong = i * pi / 6
            self.cr.move_to(*NauticalSpeedometer.polar2xy(radius - long_inset, agllong, x, y))
            self.cr.line_to(*NauticalSpeedometer.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(*NauticalSpeedometer.polar2xy(radius - mid_inset, aglmid, x, y))
            self.cr.line_to(*NauticalSpeedometer.polar2xy(radius, aglmid, x, y))

            #  short tick
            for n in xrange(1, 10):
                aglshrt = agllong + n * pi / 60
                self.cr.move_to(*NauticalSpeedometer.polar2xy(radius - short_inset, aglshrt, x, y))
                self.cr.line_to(*NauticalSpeedometer.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) = NauticalSpeedometer.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 = NauticalSpeedometer.polar2xy(shiplen * 2.3, h, x, y)
        xa, ya = NauticalSpeedometer.polar2xy(shiplen * 2.2, h + pi - 0.3, x, y)
        xb, yb = NauticalSpeedometer.polar2xy(shiplen * 2.2, h + pi + 0.3, x, y)
        xc, yc = NauticalSpeedometer.polar2xy(shiplen * 1.4, h - pi / 5, x, y)
        xd, yd = NauticalSpeedometer.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 = gtk.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, azimath, SNR, draw it on the skyview
        Arg:
        satsoup: a dictionary {'el': xx, 'az': xx, 'ss': xx}
        """
        h = pi / 2 - radians(satsoup['az'])  # to xy
        self.cr.set_line_width(2)
        self.cr.set_source_rgb(0, 0, 0)

        x0, y0 = NauticalSpeedometer.polar2xy(radius * (90 - satsoup['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='2947', device=None, debug=0,
                 speed_unit=None, maxspeed=0, nautical=False):
        self.host = host
        self.port = port
        self.device = device
        self.debug = debug
        self.speed_unit = speed_unit
        self.maxspeed = maxspeed
        self.nautical = nautical
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        if not self.window.get_display():
            raise Exception("Can't open display")
        self.window.set_title('xgpsspeed')
        if self.nautical:
            self.window.set_size_request(500, 550)
            self.widget = NauticalSpeedometer(
                speed_unit=self.speed_unit,
                maxspeed=self.maxspeed)
        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(
            type=gtk.MESSAGE_ERROR,
            flags=gtk.DIALOG_DESTROY_WITH_PARENT,
            buttons=gtk.BUTTONS_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(
                type=gtk.MESSAGE_ERROR,
                flags=gtk.DIALOG_DESTROY_WITH_PARENT,
                buttons=gtk.BUTTONS_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', gtk.gdk.Event(gtk.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='localhost',
        help='The host to connect. [Default localhost]'
    )
    parser.add_option(
        '--port',
        dest='port',
        default='2947',
        help='The port to connect. [Default 2947]'
    )
    parser.add_option(
        '--device',
        dest='device',
        default=None,
        help='The device to connet. [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=False,
        action='store_true',
        help='Enable nautical-style speed and track display.'
    )
    parser.add_option(
        '--debug',
        dest='debug',
        default=0,
        action='store',
        type='int',
        help='Set level of debug. Must be integer. [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)
    Main(
        host=options.host,
        port=options.port,
        device=options.device,
        speed_unit=options.speedunits,
        maxspeed=options.maxspeed,
        nautical=options.nautical,
        debug=options.debug
    ).run()
