]> Pileus Git - aweather/blob - src/plugins/gps-plugin.c
Switch to newer API for gps input sources
[aweather] / src / plugins / gps-plugin.c
1 /*
2  * Copyright (C) 2012 Adam Boggs <boggs@aircrafter.org>
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 /* TODO:
19  *    If gpsd connection fails, try to connect again periodically.
20  *    If gps stops sending data there should be an indication that it's stale.
21  */
22
23 #define _XOPEN_SOURCE
24 #include <config.h>
25
26 #include <stdio.h>
27 #include <fcntl.h>
28 #include <errno.h>
29 #include <time.h>
30 #include <string.h>
31 #include <glib/gstdio.h>
32 #include <gtk/gtk.h>
33 #include <gio/gio.h>
34 #include <math.h>
35
36 #include <grits.h>
37 #include <gps.h>
38
39 #include "gps-plugin.h"
40 #include "level2.h"
41 #include "../aweather-location.h"
42
43 /* interval to update map with new gps data in seconds. */
44 #define GPS_UPDATE_INTERVAL     (2)
45
46 /* Filename and search path to use for gps marker, should be configurable */
47 #define GPS_MARKER_ICON_PATH    ".:" PKGDATADIR
48 #define GPS_MARKER_ICON         "arrow.png"
49
50 /* number of track points per group and number of groups to maintain */
51 #define GPS_TRACK_POINTS        (256)
52 #define GPS_TRACK_GROUPS        (16)
53 #define GPS_TRACK_POINTS_FACTOR (1.5)
54
55 /* interval to update log file in seconds (default value for slider) */
56 #define GPS_LOG_DEFAULT_UPDATE_INTERVAL (30)
57 #define GPS_LOG_EXT             "csv"
58
59 /* For updating the status bar conveniently */
60 #define GPS_STATUSBAR_CONTEXT   "GPS"
61
62 #if 0
63 #define GPS_STATUS(gps, format, args...) \
64         do { \
65                 gchar *buf = g_strdup_printf(format, ##args); \
66                 gtk_statusbar_push(GTK_STATUSBAR(gps->status_bar), \
67                     gtk_statusbar_get_context_id( \
68                       GTK_STATUSBAR(gps->status_bar), \
69                       GPS_STATUSBAR_CONTEXT), \
70                 buf); \
71         } while (0)
72 #endif
73
74 #define GPS_STATUS(gps, format, args...) \
75         do { \
76                 gchar *buf = g_strdup_printf(format, ##args); \
77                 g_debug("STATUS: %s", buf); \
78         } while (0)
79
80
81 /********************
82  * Helper functions *
83  ********************/
84
85 /* Find a readable file in a colon delimeted path */
86 static gchar *find_path(const gchar *path, const gchar *filename)
87 {
88         gchar *end_ptr, *fullpath;
89
90         end_ptr = (gchar *)path;
91
92         /* find first : */
93         while (*end_ptr != ':' && *end_ptr != '\0')
94                 end_ptr++;
95         fullpath = g_strdup_printf("%.*s/%s", (int)(end_ptr-path), path,
96                                filename);
97         g_debug("GritsPluginGps: find_path - searching %s", fullpath);
98         if (access(fullpath, R_OK) == 0) {
99                 g_debug("GritsPluginGps: find_path - found %s", fullpath);
100                 return fullpath;        /* caller frees */
101         }
102
103         g_free(fullpath);
104         /* recurse */
105         if (*end_ptr == '\0') {
106                 return NULL;
107         } else {
108                 return find_path(end_ptr + 1, filename);
109         }
110 }
111
112
113 /********************
114  * GPS Status Table *
115  ********************/
116
117 static gchar *gps_get_status(struct gps_data_t *gps_data)
118 {
119         gchar *status_color;
120         gchar *status_text;
121
122         switch (gps_data->fix.mode) {
123         case MODE_NOT_SEEN:
124                 status_color = "red";
125                 status_text = "No Signal";
126                 break;
127         case MODE_NO_FIX:
128                 status_color = "red";
129                 status_text = "No Fix";
130                 break;
131         case MODE_2D:
132                 status_color = "yellow";
133                 status_text = "2D Mode";
134                 break;
135         case MODE_3D:
136                 status_color = "green";
137                 status_text = "3D Mode";
138                 break;
139         default:
140                 status_color = "black";
141                 status_text = "Unknown";
142                 break;
143         }
144         return g_strdup_printf("<span foreground=\"%s\">%s</span>",
145                                 status_color, status_text);
146 }
147
148 #if 0
149 static gchar *gps_get_online(struct gps_data_t *gps_data)
150 {
151         gchar *status_str;
152         gchar *online_str;
153
154         if (gps_data->online == -1.0) {
155                 online_str = "Offline";
156         } else {
157                 online_str = "Online";
158         }
159
160         switch (gps_data->status) {
161         case 0:
162                 status_str = "No Fix";
163                 break;
164         case 1:
165                 status_str = "Fix Acquired";
166                 break;
167         case 2:
168                 status_str = "DGPS Fix";
169                 break;
170         default:
171                 status_str = "Unknown Status";
172                 break;
173         }
174
175         return g_strdup_printf("%lf,%s,%s", gps_data->online, online_str, status_str);
176 }
177 #endif
178
179 static gchar *gps_get_latitude(struct gps_data_t *gps_data)
180 {
181         return g_strdup_printf("%10.4f", gps_data->fix.latitude);
182 }
183
184 static gchar *gps_get_longitude(struct gps_data_t *gps_data)
185 {
186         return g_strdup_printf("%10.4f", gps_data->fix.longitude);
187 }
188
189 static gchar *gps_get_elevation(struct gps_data_t *gps_data)
190 {
191         /* XXX Make units (m/ft) settable */
192         return g_strdup_printf("%5.0lf %4s",
193                     (gps_data->fix.altitude * METERS_TO_FEET), "ft");
194 }
195
196 static gchar *gps_get_heading(struct gps_data_t *gps_data)
197 {
198         /* XXX Make units (m/ft) settable */
199         return g_strdup_printf("%5.0lf %4s", gps_data->fix.track, "deg");
200 }
201
202 static gchar *gps_get_speed(struct gps_data_t *gps_data)
203 {
204         /* XXX Make units (m/ft) settable */
205         return g_strdup_printf("%5.0f %4s",
206                 (gps_data->fix.speed*3600.0*METERS_TO_FEET/5280.0), "mph");
207 }
208
209 struct {
210         const gchar *label;
211         const gchar *initial_val;
212         gchar     *(*get_data)(struct gps_data_t *);
213         guint        font_size;
214         GtkWidget   *label_widget;
215         GtkWidget   *value_widget;
216 } gps_table[] = {
217         {"Status:",    "No Data", gps_get_status,    14, NULL, NULL},
218 //      {"Online:",    "No Data", gps_get_online,    14, NULL, NULL},
219         {"Latitude:",  "No Data", gps_get_latitude,  14, NULL, NULL},
220         {"Longitude:", "No Data", gps_get_longitude, 14, NULL, NULL},
221         {"Elevation:", "No Data", gps_get_elevation, 14, NULL, NULL},
222         {"Heading:",   "No Data", gps_get_heading,   14, NULL, NULL},
223         {"Speed:",     "No Data", gps_get_speed,     14, NULL, NULL},
224 };
225
226
227 /******************
228  * Track handling *
229  ******************/
230
231 static void gps_track_init(GpsTrack *track)
232 {
233         /* Save a spot at the end for the NULL termination */
234         track->points = (gpointer)g_new0(double*, GPS_TRACK_GROUPS + 1);
235         track->cur_point  = 0;
236         track->cur_group  = 0;
237         track->num_points = 1;  /* starts at 1 so realloc logic works */
238         track->line = NULL;
239 }
240
241 static void gps_track_clear(GpsTrack *track)
242 {
243         gint pi;
244         for (pi = 0; pi < GPS_TRACK_GROUPS; pi++) {
245                 if (track->points[pi] != NULL) {
246                         g_free(track->points[pi]);
247                         track->points[pi] = NULL;
248                 }
249         }
250         track->cur_point  = 0;
251         track->cur_group  = 0;
252         track->num_points = 1;  /* starts at 1 so realloc logic works */
253 }
254
255 static void gps_track_free(GpsTrack *track)
256 {
257         gps_track_clear(track);
258         g_free(track->points);
259 }
260
261 /* add a new track group (points in a track group are connected, and
262  * separated from points in other track groups).  */
263 static void gps_track_group_incr(GpsTrack *track)
264 {
265         gdouble (**points)[3] = track->points; /* for simplicity */
266
267         /* Just return if they increment it again before any points have
268          * been added.
269          */
270         if (points[track->cur_group] == NULL) {
271                 return;
272         }
273
274         g_debug("GritsPluginGps: track_group_incr - track group %u->%u.",
275                 track->cur_group, track->cur_group + 1);
276
277         track->cur_group++;
278         track->cur_point  = 0;
279         track->num_points = 1;  /* starts at 1 so realloc logic works */
280
281         if (track->cur_group >= GPS_TRACK_GROUPS) {
282                 g_debug("GritsPluginGps: track_group_incr - track group %u "
283                         "is at max %u, shifting groups",
284                         track->cur_group, GPS_TRACK_GROUPS);
285
286                 /* Free the oldest one which falls off the end */
287                 g_free(points[0]);
288
289                 /* shift the rest down, last one should be NULL already */
290                 /* note we alloc GPS_TRACK_GROUPS+1 */
291                 for (int pi = 0; pi < GPS_TRACK_GROUPS; pi++) {
292                         points[pi] = points[pi+1];
293                 }
294
295                 /* always write into the last group */
296                 track->cur_group = GPS_TRACK_GROUPS - 1;
297         }
298 }
299
300 static void gps_track_add_point(GpsTrack *track,
301     gdouble lat, gdouble lon, gdouble elevation)
302 {
303         gdouble (**points)[3] = track->points; /* for simplicity */
304
305         g_debug("GritsPluginGps: track_add_point");
306
307         g_assert(track->cur_group < GPS_TRACK_GROUPS &&
308                 (track->cur_point <= track->num_points));
309
310         /* resize/allocate the point group if the current one is full */
311         if (track->cur_point >= track->num_points - 1) {
312                 guint new_size = track->num_points == 1 ?
313                             GPS_TRACK_POINTS :
314                             track->num_points * GPS_TRACK_POINTS_FACTOR;
315                 g_debug("GritsPluginGps: track_add_point - reallocating points "
316                         "array from %u points to %u points.\n",
317                         track->num_points, new_size);
318                 points[track->cur_group] = (gpointer)g_renew(gdouble,
319                             points[track->cur_group], 3*(new_size+1));
320                 track->num_points = new_size;
321         }
322
323         g_assert(points[track->cur_group] != NULL);
324
325         /* Add the coordinate */
326         lle2xyz(lat, lon, elevation,
327             &points[track->cur_group][track->cur_point][0],
328             &points[track->cur_group][track->cur_point][1],
329             &points[track->cur_group][track->cur_point][2]);
330
331         track->cur_point++;
332
333         /* make sure last point is always 0s so the line drawing stops. */
334         points[track->cur_group][track->cur_point][0] = 0.0;
335         points[track->cur_group][track->cur_point][1] = 0.0;
336         points[track->cur_group][track->cur_point][2] = 0.0;
337 }
338
339
340 /*****************
341  * Track Logging *
342  *****************/
343
344 static gchar *gps_get_date_string(double gps_time)
345 {
346         static gchar    buf[256];
347         time_t int_time = (time_t)gps_time;
348         struct tm       tm_time;
349
350         gmtime_r(&int_time, &tm_time);
351
352         snprintf(buf, sizeof(buf), "%04d-%02d-%02d",
353                  tm_time.tm_year+1900, tm_time.tm_mon+1, tm_time.tm_mday);
354
355         return buf;
356 }
357
358 static gchar *gps_get_time_string(time_t gps_time)
359 {
360         static gchar buf[256];
361         time_t int_time = (time_t)gps_time;
362         struct tm tm_time;
363
364         gmtime_r(&int_time, &tm_time);
365
366         snprintf(buf, sizeof(buf), "%02d:%02d:%02dZ",
367                  tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
368
369         return buf;
370 }
371
372 static gboolean gps_write_log(gpointer data)
373 {
374         GritsPluginGps *gps = (GritsPluginGps *)data;
375         struct gps_data_t *gps_data = &gps->gps_data;
376         gchar buf[256];
377         gchar filename[256];
378         gint fd;
379         gboolean new_file = FALSE;
380
381         if (gps_data == NULL) {
382                 g_warning("Skipped write to GPS log file: "
383                           "can not get GPS coordinates.");
384                 GPS_STATUS(gps, "Skipped write to GPS log file: "
385                           "can not get GPS coordinates.");
386                 return TRUE;
387         }
388
389         /* get filename from text entry box.  If empty, generate a name from
390          * the date and time and set it. */
391         if (strlen(gtk_entry_get_text(
392                       GTK_ENTRY(gps->ui.gps_log_filename_entry))) == 0) {
393                 snprintf(filename, sizeof(filename),
394                             "%sT%s.%s",
395                             gps_get_date_string(gps->gps_data.fix.time),
396                             gps_get_time_string(gps->gps_data.fix.time),
397                             GPS_LOG_EXT);
398                 gtk_entry_set_text(GTK_ENTRY(gps->ui.gps_log_filename_entry),
399                             filename);
400         }
401
402         strncpy(filename,
403             gtk_entry_get_text(GTK_ENTRY(gps->ui.gps_log_filename_entry)),
404             sizeof(filename));
405
406         if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
407                 new_file = TRUE;
408         }
409
410         if ((fd = open(filename, O_CREAT|O_APPEND|O_WRONLY, 0644)) == -1) {
411                 g_warning("Error opening log file %s: %s",
412                                 filename, strerror(errno));
413                 return FALSE;
414         }
415
416         if (new_file) {
417                 /* write header and reset record counter */
418                 snprintf(buf, sizeof(buf),
419                         "No,Date,Time,Lat,Lon,Ele,Head,Speed,RTR\n");
420                 if (write(fd, buf, strlen(buf)) == -1) {
421                     g_warning("Error writing header to log file %s: %s",
422                                     filename, strerror(errno));
423                 }
424                 gps->ui.gps_log_number = 1;
425         }
426
427         /* Write log entry.  Make sure this matches the header */
428         /* "No,Date,Time,Lat,Lon,Ele,Head,Speed,Fix,RTR\n" */
429         /* RTR values: T=time, B=button push, S=speed, D=distance */
430         snprintf(buf, sizeof(buf), "%d,%s,%s,%lf,%lf,%lf,%lf,%lf,%c\n",
431                         gps->ui.gps_log_number++,
432                         gps_get_date_string(gps->gps_data.fix.time),
433                         gps_get_time_string(gps->gps_data.fix.time),
434                         //gps_data->fix.time,
435                         gps_data->fix.latitude,
436                         gps_data->fix.longitude,
437                         gps_data->fix.altitude * METERS_TO_FEET,
438                         gps_data->fix.track,
439                         gps_data->fix.speed * METERS_TO_FEET,
440                         'T'); /* position due to timer expired  */
441
442         if (write(fd, buf, strlen(buf)) == -1) {
443                 g_warning("Could not write log number %d to log file %s: %s",
444                                 gps->ui.gps_log_number-1, filename, strerror(errno));
445         }
446         close(fd);
447
448         GPS_STATUS(gps, "Updated GPS log file %s.", filename);
449
450         /* reschedule */
451         return TRUE;
452 }
453
454
455 /****************
456  * Main drawing *
457  ****************/
458
459 static gboolean gps_data_is_valid(struct gps_data_t *gps_data)
460 {
461         if (gps_data != NULL && gps_data->online != -1.0 &&
462                 gps_data->fix.mode >= MODE_2D &&
463                 gps_data->status > STATUS_NO_FIX) {
464                 return TRUE;
465         }
466
467         return FALSE;
468 }
469
470 static void gps_update_status(GritsPluginGps *gps)
471 {
472         struct gps_data_t *gps_data = &gps->gps_data;
473
474         /* gps table update */
475         for (gint i = 0; i < G_N_ELEMENTS(gps_table); i++) {
476                 gchar *str = gps_table[i].get_data(gps_data);
477                 gtk_label_set_markup(GTK_LABEL(gps_table[i].value_widget), str);
478                 g_free(str);
479         }
480 }
481
482 /* external interface to update UI from latest GPS data. */
483 gboolean gps_redraw_all(gpointer data)
484 {
485         GritsPluginGps *gps = (GritsPluginGps *)data;
486         g_assert(gps);
487
488         struct gps_data_t *gps_data = &gps->gps_data;
489
490         g_debug("GritsPluginGps: redraw_all");
491
492         g_assert(gps_data);
493         if (!gps_data_is_valid(gps_data)) {
494                 g_debug("GritsPluginGps: redraw_all - gps_data is not valid.");
495                 /* XXX Change marker to indicate data is not valid */
496                 return TRUE;
497         }
498
499         /* update position labels */
500         gps_update_status(gps);
501
502         /* Update track and marker position */
503         if (gps_data_is_valid(gps_data) && gps->track.active) {
504                 g_debug("GritsPluginGps: redraw_all - updating track group %u "
505                         "point %u at lat = %f, long = %f, track = %f",
506                         gps->track.cur_group,
507                         gps->track.cur_point,
508                         gps_data->fix.latitude,
509                         gps_data->fix.longitude,
510                         gps_data->fix.track);
511
512                 gps_track_add_point(&gps->track,
513                           gps_data->fix.latitude, gps_data->fix.longitude, 0.0);
514
515                 if (gps->track.line) {
516                         grits_viewer_remove(gps->viewer,
517                             GRITS_OBJECT(gps->track.line));
518                         gps->track.line = NULL;
519                 }
520
521                 gps->track.line = grits_line_new(gps->track.points);
522                 gps->track.line->color[0]  = 1.0;
523                 gps->track.line->color[1]  = 0;
524                 gps->track.line->color[2]  = 0.1;
525                 gps->track.line->color[3]  = 1.0;
526                 gps->track.line->width     = 3;
527
528                 grits_viewer_add(gps->viewer, GRITS_OBJECT(gps->track.line),
529                             GRITS_LEVEL_OVERLAY, FALSE);
530                 grits_object_queue_draw(GRITS_OBJECT(gps->track.line));
531         }
532
533         if (gps_data_is_valid(gps_data)) {
534                 if (gps->marker) {
535                         grits_viewer_remove(gps->viewer,
536                             GRITS_OBJECT(gps->marker));
537                         gps->marker = NULL;
538                 }
539
540                 gchar *path = find_path(GPS_MARKER_ICON_PATH, GPS_MARKER_ICON);
541                 if (path) {
542                         gps->marker = grits_marker_icon_new("GPS", path,
543                                       gps_data->fix.track, TRUE, GRITS_MARKER_DMASK_ICON);
544                         g_free(path);
545                 } else {
546                         /* if icon not found just use a point */
547                         g_warning("Could not find GPS marker icon %s in path %s.",
548                                   GPS_MARKER_ICON, GPS_MARKER_ICON_PATH);
549                         gps->marker = grits_marker_icon_new("GPS", NULL,
550                                       gps_data->fix.track, FALSE,
551                                       GRITS_MARKER_DMASK_POINT |
552                                       GRITS_MARKER_DMASK_LABEL);
553                 }
554
555                 GRITS_OBJECT(gps->marker)->center.lat  = gps_data->fix.latitude;
556                 GRITS_OBJECT(gps->marker)->center.lon  = gps_data->fix.longitude;
557                 GRITS_OBJECT(gps->marker)->center.elev = 0.0;
558                 GRITS_OBJECT(gps->marker)->lod         = EARTH_R * 5;
559                 GRITS_MARKER(gps->marker)->ortho       = FALSE;
560
561                 grits_viewer_add(gps->viewer, GRITS_OBJECT(gps->marker),
562                         GRITS_LEVEL_OVERLAY+1, FALSE);
563                 grits_object_queue_draw(GRITS_OBJECT(gps->marker));
564         }
565
566         if (gps->follow_gps && gps_data_is_valid(gps_data)) {
567                 /* Center map at current GPS position. */
568                 g_debug("GritsPluginGps: redraw_all - centering map at "
569                         "lat = %f, long = %f, track = %f",
570                             gps_data->fix.latitude,
571                             gps_data->fix.longitude,
572                             gps_data->fix.track);
573
574                 double lat, lon, elev;
575                 grits_viewer_get_location(gps->viewer, &lat, &lon, &elev);
576                 grits_viewer_set_location(gps->viewer, gps_data->fix.latitude,
577                                           gps_data->fix.longitude, elev);
578                 //grits_viewer_set_rotation(gps->viewer, 0, 0, 0);
579         }
580
581         /* reschedule */
582         return TRUE;
583 }
584
585
586 /***************
587  * Config Area *
588  ***************/
589
590 /* GPS Data Frame */
591 static void gps_init_status_info(GritsPluginGps *gps, GtkWidget *gbox)
592 {
593         gps->ui.gps_status_frame = gtk_frame_new("<b>GPS Data</b>");
594         GtkWidget *label = gtk_frame_get_label_widget(
595                            GTK_FRAME(gps->ui.gps_status_frame));
596         gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
597         gtk_frame_set_shadow_type(GTK_FRAME(gps->ui.gps_status_frame),
598                            GTK_SHADOW_NONE);
599         gps->ui.gps_status_table = gtk_table_new(5, 2, FALSE);
600         gtk_container_add(GTK_CONTAINER(gps->ui.gps_status_frame),
601                    gps->ui.gps_status_table);
602
603
604         /* gps data table setup */
605         gint i;
606         for (i = 0; i < G_N_ELEMENTS(gps_table); i++) {
607                 gps_table[i].label_widget = gtk_label_new(gps_table[i].label);
608                 gps_table[i].value_widget = gtk_label_new(gps_table[i].initial_val);
609
610                 gtk_misc_set_alignment(GTK_MISC(gps_table[i].label_widget), 0, 0);
611                 gtk_misc_set_alignment(GTK_MISC(gps_table[i].value_widget), 1, 0);
612
613                 gtk_table_attach(GTK_TABLE(gps->ui.gps_status_table),
614                                 gps_table[i].label_widget, 0, 1, i, i+1,
615                                 GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0);
616                 gtk_table_attach(GTK_TABLE(gps->ui.gps_status_table),
617                                 gps_table[i].value_widget, 1, 2, i, i+1,
618                                 GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0);
619
620                 PangoFontDescription *roman = pango_font_description_new();
621                 PangoFontDescription *mono  = pango_font_description_from_string("monospace");
622                 pango_font_description_set_size(roman, gps_table[i].font_size*PANGO_SCALE);
623                 pango_font_description_set_size(mono,  gps_table[i].font_size*PANGO_SCALE);
624                 gtk_widget_modify_font(gps_table[i].label_widget, roman);
625                 gtk_widget_modify_font(gps_table[i].value_widget, mono);
626                 pango_font_description_free(roman);
627                 pango_font_description_free(mono);
628         }
629         gtk_box_pack_start(GTK_BOX(gbox), gps->ui.gps_status_frame,
630                             FALSE, FALSE, 0);
631
632         /* Start UI refresh task, which will reschedule itgps. */
633         gps_redraw_all(gps);
634         gps->gps_update_timeout_id = g_timeout_add(
635                     GPS_UPDATE_INTERVAL*1000,
636                     gps_redraw_all, gps);
637
638 }
639
640 /* GPS Control Frame */
641 static gboolean on_gps_follow_clicked_event(GtkWidget *widget, gpointer user_data)
642 {
643         GritsPluginGps *gps = (GritsPluginGps *)user_data;
644
645         g_debug("GritsPluginGps: follow_clicked_event - button status %d",
646                 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)));
647         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
648                 gps->follow_gps = TRUE;
649         } else {
650                 gps->follow_gps = FALSE;
651         }
652
653         return FALSE;
654 }
655
656 static gboolean on_gps_track_enable_clicked_event(GtkWidget *widget, gpointer user_data)
657 {
658         GritsPluginGps *gps = (GritsPluginGps *)user_data;
659
660         g_debug("GritsPluginGps: track_enable_clicked_event");
661
662         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
663                 /* start logging trip history */
664                 GPS_STATUS(gps, "Enabled GPS track.");
665                 gps->track.active = TRUE;
666         } else {
667                 /* stop logging trip history */
668                 GPS_STATUS(gps, "Disabled GPS track.");
669                 gps->track.active = FALSE;
670                 /* advance to the next track group, moving everything down if
671                  * it's full. */
672                 gps_track_group_incr(&gps->track);
673         }
674
675         return FALSE;
676 }
677
678 static gboolean on_gps_track_clear_clicked_event(GtkWidget *widget, gpointer user_data)
679 {
680         GritsPluginGps *gps = (GritsPluginGps *)user_data;
681
682         g_debug("GritsPluginGps: track_clear_clicked_event");
683         GPS_STATUS(gps, "Cleared GPS track.");
684         gps_track_clear(&gps->track);
685
686         return FALSE;
687 }
688
689 static void gps_init_control_frame(GritsPluginGps *gps, GtkWidget *gbox)
690 {
691         /* Control checkboxes */
692         GtkWidget *gps_control_frame = gtk_frame_new("<b>GPS Control</b>");
693         GtkWidget *label = gtk_frame_get_label_widget(
694                            GTK_FRAME(gps_control_frame));
695         gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
696         gtk_frame_set_shadow_type(GTK_FRAME(gps_control_frame),
697                            GTK_SHADOW_NONE);
698         GtkWidget *cbox = gtk_vbox_new(FALSE, 2);
699         gtk_container_add(GTK_CONTAINER(gps_control_frame), cbox);
700         gtk_box_pack_start(GTK_BOX(gbox), gps_control_frame, FALSE, FALSE, 0);
701
702         gps->ui.gps_follow_checkbox =
703                       gtk_check_button_new_with_label("Follow GPS");
704         g_signal_connect(G_OBJECT(gps->ui.gps_follow_checkbox), "clicked",
705                       G_CALLBACK(on_gps_follow_clicked_event),
706                       (gpointer)gps);
707         gtk_box_pack_start(GTK_BOX(cbox), gps->ui.gps_follow_checkbox,
708                        FALSE, FALSE, 0);
709
710         gps->ui.gps_track_checkbox =
711                        gtk_check_button_new_with_label("Record Track");
712         g_signal_connect(G_OBJECT(gps->ui.gps_track_checkbox), "clicked",
713                        G_CALLBACK(on_gps_track_enable_clicked_event),
714                        (gpointer)gps);
715         gtk_box_pack_start(GTK_BOX(cbox), gps->ui.gps_track_checkbox,
716                        FALSE, FALSE, 0);
717
718         gps->ui.gps_clear_button = gtk_button_new_with_label("Clear Track");
719         g_signal_connect(G_OBJECT(gps->ui.gps_clear_button), "clicked",
720                       G_CALLBACK(on_gps_track_clear_clicked_event),
721                       (gpointer)gps);
722         gtk_box_pack_start(GTK_BOX(cbox), gps->ui.gps_clear_button,
723                        FALSE, FALSE, 0);
724 }
725
726 /* Track Log Frame */
727 static gboolean on_gps_log_clicked_event(GtkWidget *widget, gpointer user_data)
728 {
729         GritsPluginGps *gps = (GritsPluginGps *)user_data;
730
731         g_debug("GritsPluginGps: log_clicked_event");
732
733         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON (widget)))  {
734                 gps_write_log(gps);
735
736                 /* Schedule log file write */
737                 gps->ui.gps_log_timeout_id = g_timeout_add(
738                       gtk_range_get_value(
739                         GTK_RANGE(gps->ui.gps_log_interval_slider))*1000,
740                         gps_write_log, gps);
741         } else {
742                 /* button unchecked */
743                 g_source_remove(gps->ui.gps_log_timeout_id);
744                 gps->ui.gps_log_timeout_id = 0;
745                 g_debug("GritsPluginGps: log_clicked_event - closed log file.");
746         }
747
748         return FALSE;
749 }
750
751 static gboolean on_gps_log_interval_changed_event(GtkWidget *widget, gpointer user_data)
752 {
753         GritsPluginGps *gps = (GritsPluginGps *)user_data;
754
755         g_assert(gps);
756
757         g_debug("GritsPluginGps: log_interval_changed_event - value = %f",
758         gtk_range_get_value(GTK_RANGE(widget)));
759
760         if (gtk_toggle_button_get_active(
761                         GTK_TOGGLE_BUTTON(gps->ui.gps_log_checkbox))) {
762                 g_assert(gps->ui.gps_log_timeout_id != 0);
763
764                 /* disable old timeout */
765                 g_source_remove(gps->ui.gps_log_timeout_id);
766                 gps->ui.gps_log_timeout_id = 0;
767
768                 /* Schedule new log file write */
769                 gps->ui.gps_log_timeout_id = g_timeout_add(
770                          gtk_range_get_value(GTK_RANGE(widget))*1000,
771                          gps_write_log, gps);
772                 gps_write_log(gps);
773         }
774
775         return FALSE;
776 }
777
778 static void gps_init_track_log_frame(GritsPluginGps *gps, GtkWidget *gbox)
779 {
780         /* Track log box with enable checkbox and filename entry */
781         GtkWidget *gps_log_frame = gtk_frame_new("<b>Track Log</b>");
782         GtkWidget *label = gtk_frame_get_label_widget(GTK_FRAME(gps_log_frame));
783         gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
784         gtk_frame_set_shadow_type(GTK_FRAME(gps_log_frame), GTK_SHADOW_NONE);
785         GtkWidget *lbox = gtk_vbox_new(FALSE, 2);
786         gtk_container_add(GTK_CONTAINER(gps_log_frame), lbox);
787         gtk_box_pack_start(GTK_BOX(gbox), gps_log_frame,
788                         FALSE, FALSE, 0);
789
790         gps->ui.gps_log_checkbox =
791                gtk_check_button_new_with_label("Log Position to File");
792         g_signal_connect(G_OBJECT(gps->ui.gps_log_checkbox), "clicked",
793                G_CALLBACK(on_gps_log_clicked_event),
794                (gpointer)gps);
795         gtk_box_pack_start(GTK_BOX(lbox), gps->ui.gps_log_checkbox,
796                FALSE, FALSE, 0);
797
798         /* Set up filename entry box */
799         GtkWidget *fbox = gtk_hbox_new(FALSE, 2);
800         GtkWidget *filename_label = gtk_label_new("Filename:");
801         gtk_box_pack_start(GTK_BOX(fbox), filename_label, FALSE, FALSE, 0);
802         gps->ui.gps_log_filename_entry = gtk_entry_new();
803         gtk_box_pack_start(GTK_BOX(fbox), gps->ui.gps_log_filename_entry,
804                TRUE, TRUE, 0);
805         gtk_box_pack_start(GTK_BOX(lbox), fbox, FALSE, FALSE, 0);
806
807         /* set up gps log interval slider */
808         GtkWidget *ubox = gtk_hbox_new(FALSE, 4);
809         GtkWidget *interval_label = gtk_label_new("Update Interval:");
810         gtk_box_pack_start(GTK_BOX(ubox), interval_label, FALSE, FALSE, 0);
811         gps->ui.gps_log_interval_slider =
812                     gtk_hscale_new_with_range(1.0, 600.0, 30.0);
813         gtk_range_set_value(GTK_RANGE(gps->ui.gps_log_interval_slider),
814                     GPS_LOG_DEFAULT_UPDATE_INTERVAL);
815         g_signal_connect(G_OBJECT(gps->ui.gps_log_interval_slider),
816                     "value-changed",
817                     G_CALLBACK(on_gps_log_interval_changed_event),
818                     (gpointer)gps);
819         gtk_range_set_increments(
820                     GTK_RANGE(gps->ui.gps_log_interval_slider),
821                     10.0 /* step */, 30.0 /* page up/down */);
822         gtk_range_set_update_policy(
823                     GTK_RANGE(gps->ui.gps_log_interval_slider),
824                     GTK_UPDATE_DELAYED);
825         gtk_box_pack_start(GTK_BOX(ubox), gps->ui.gps_log_interval_slider,
826                     TRUE, TRUE, 0);
827         gtk_box_pack_start(GTK_BOX(lbox), ubox, FALSE, FALSE, 0);
828 }
829
830
831 /*******************
832  * GPSD interfaces *
833  *******************/
834
835 static gboolean process_gps(GIOChannel *source, GIOCondition condition, gpointer data)
836 {
837         struct gps_data_t *gps_data = (struct gps_data_t *)data;
838
839         g_debug("GritsPluginGps: process_gps");
840
841         /* Process any data from the gps and call the hook function */
842         if (gps_data != NULL) {
843                 gdouble track = gps_data->fix.track;
844                 gint result = gps_read(gps_data);
845                 if (isnan(gps_data->fix.track))
846                         gps_data->fix.track = track;
847                 g_debug("GritsPluginGps: process_gps - gps_read returned %d, "
848                         "position %f, %f.", result,
849                         gps_data->fix.latitude, gps_data->fix.longitude);
850         } else {
851                 g_warning("GritsPluginGps: process_gps - gps_data == NULL.");
852         }
853         return TRUE;
854 }
855
856 static gint initialize_gpsd(char *server, gchar *port, struct gps_data_t *gps_data)
857 {
858 #if GPSD_API_MAJOR_VERSION < 5
859 #error "GPSD protocol version 5 or greater required."
860 #endif
861         gint result = gps_open(server, port, gps_data);
862         if (result > 0) {
863                 g_warning("Unable to open gpsd connection to %s:%s: %d, %d, %s",
864                                 server, port, result, errno, gps_errstr(errno));
865                 return 0;
866         }
867
868         (void)gps_stream(gps_data, WATCH_ENABLE|WATCH_JSON, NULL);
869         g_debug("GritsPluginGps: initialize_gpsd - gpsd fd %u.", gps_data->gps_fd);
870         GIOChannel *channel = g_io_channel_unix_new(gps_data->gps_fd);
871         gint input_tag = g_io_add_watch(channel, G_IO_IN, process_gps, gps_data);
872         g_io_channel_unref(channel);
873         return input_tag;
874 }
875
876
877 /**********************
878  * GPS Plugin Methods *
879  **********************/
880
881 /* Methods */
882 GritsPluginGps *grits_plugin_gps_new(GritsViewer *viewer, GritsPrefs *prefs)
883 {
884         /* TODO: move to constructor if possible */
885         g_debug("GritsPluginGps: new");
886         GritsPluginGps *gps = g_object_new(GRITS_TYPE_PLUGIN_GPS, NULL);
887         gps->viewer  = g_object_ref(viewer);
888         gps->prefs   = g_object_ref(prefs);
889
890         gps->input_tag = initialize_gpsd("localhost", DEFAULT_GPSD_PORT, &gps->gps_data);
891         gps->follow_gps = FALSE;
892
893         gps_track_init(&gps->track);
894         gps_init_status_info(gps, gps->config);
895         gps_init_control_frame(gps, gps->config);
896         gps_init_track_log_frame(gps, gps->config);
897
898         return gps;
899 }
900
901 static GtkWidget *grits_plugin_gps_get_config(GritsPlugin *_gps)
902 {
903         GritsPluginGps *gps = GRITS_PLUGIN_GPS(_gps);
904         return gps->config;
905 }
906
907 /* GObject code */
908 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface);
909 G_DEFINE_TYPE_WITH_CODE(GritsPluginGps, grits_plugin_gps, G_TYPE_OBJECT,
910                 G_IMPLEMENT_INTERFACE(GRITS_TYPE_PLUGIN,
911                         grits_plugin_gps_plugin_init));
912
913 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface)
914 {
915         g_debug("GritsPluginGps: plugin_init");
916         /* Add methods to the interface */
917         iface->get_config = grits_plugin_gps_get_config;
918 }
919
920 static void grits_plugin_gps_init(GritsPluginGps *gps)
921 {
922         g_debug("GritsPluginGps: gps_init");
923
924         gps->config     = gtk_hbox_new(FALSE, 15);
925 }
926
927 static void grits_plugin_gps_dispose(GObject *gobject)
928 {
929         GritsPluginGps *gps = GRITS_PLUGIN_GPS(gobject);
930
931         g_debug("GritsPluginGps: dispose");
932
933         if (gps->gps_update_timeout_id) {
934                 g_source_remove(gps->gps_update_timeout_id);
935                 gps->gps_update_timeout_id = 0;
936         }
937         if (gps->ui.gps_log_timeout_id) {
938                 g_source_remove(gps->ui.gps_log_timeout_id);
939                 gps->ui.gps_log_timeout_id = 0;
940         }
941         if (gps->input_tag) {
942                 g_source_remove(gps->input_tag);
943                 gps->input_tag = 0;
944         }
945         if (gps->viewer) {
946                 GritsViewer *viewer = gps->viewer;
947                 gps->viewer = NULL;
948                 if (gps->marker)
949                         grits_viewer_remove(viewer,
950                                GRITS_OBJECT(gps->marker));
951                 g_object_unref(gps->prefs);
952                 g_object_unref(viewer);
953         }
954
955         /* Drop references */
956         G_OBJECT_CLASS(grits_plugin_gps_parent_class)->dispose(gobject);
957 }
958
959 static void grits_plugin_gps_finalize(GObject *gobject)
960 {
961         GritsPluginGps *gps = GRITS_PLUGIN_GPS(gobject);
962
963         g_debug("GritsPluginGps: finalize");
964
965         /* Free data */
966         gps_track_free(&gps->track);
967         G_OBJECT_CLASS(grits_plugin_gps_parent_class)->finalize(gobject);
968 }
969
970 static void grits_plugin_gps_class_init(GritsPluginGpsClass *klass)
971 {
972         g_debug("GritsPluginGps: class_init");
973         GObjectClass *gobject_class = (GObjectClass*)klass;
974         gobject_class->dispose  = grits_plugin_gps_dispose;
975         gobject_class->finalize = grits_plugin_gps_finalize;
976 }