]> Pileus Git - aweather/blob - src/plugins/gps-plugin.c
Added support for new icon marker interface. Added car.png icon.
[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 <stdio.h>
25 #include <fcntl.h>
26 #include <errno.h>
27 #include <time.h>
28 #include <config.h>
29 #include <assert.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 /* interval to update log file in seconds (default value for slider) */
47 #define GPS_LOG_DEFAULT_UPDATE_INTERVAL (30)
48 #define GPS_LOG_EXT "csv"
49
50 /* For updating the status bar conveniently */
51 #define GPS_STATUSBAR_CONTEXT "GPS"
52 #if 0
53 #define GPS_STATUS(gps_state, format, args...) \
54     do { \
55         char *buf = g_strdup_printf(format, ##args); \
56         gtk_statusbar_push(GTK_STATUSBAR(gps_state->status_bar), \
57             gtk_statusbar_get_context_id( \
58                 GTK_STATUSBAR(gps_state->status_bar), \
59                 GPS_STATUSBAR_CONTEXT), \
60             buf); \
61     } while (0)
62 #endif
63
64 #define GPS_STATUS(gps_state, format, args...) \
65     do { \
66         char *buf = g_strdup_printf(format, ##args); \
67         g_warning("STATUS: %s", buf); \
68     } while (0)
69
70
71 static gboolean gps_data_is_valid(struct gps_data_t *gps_data);
72 static char *gps_get_time_string(time_t gps_time);
73 static char *gps_get_date_string(double gps_time);
74 static void process_gps( gpointer, gint, GdkInputCondition);
75
76 #ifdef GPS_RANGE_RINGS
77 static void gps_init_range_rings(GritsPluginGPS *gps_state,
78             GtkWidget *gbox);
79 static gboolean on_gps_rangering_clicked_event (GtkWidget *widget, gpointer user_data);
80 #endif
81
82 static void gps_init_status_info(GritsPluginGPS *gps_state,
83             GtkWidget *gbox);
84 static void gps_init_control_frame(GritsPluginGPS *gps_state,
85             GtkWidget *gbox);
86 static gboolean on_gps_follow_clicked_event (GtkWidget *widget, gpointer user_data);
87
88 /* GPS logging support */
89 static void gps_init_track_log_frame(GritsPluginGPS *gps_state,
90             GtkWidget *gbox);
91 static gboolean on_gps_log_clicked_event (GtkWidget *widget, gpointer user_data);
92 static gboolean on_gps_track_clicked_event (GtkWidget *widget, gpointer user_data);
93 static gboolean gps_write_log(gpointer data);
94
95 static char *gps_get_status(struct gps_data_t *);
96 static char *gps_get_latitude(struct gps_data_t *);
97 static char *gps_get_longitude(struct gps_data_t *);
98 static char *gps_get_elevation(struct gps_data_t *);
99 static char *gps_get_heading(struct gps_data_t *);
100 static char *gps_get_speed(struct gps_data_t *);
101
102 /* Describes a line in the gps table */
103 struct gps_status_info {
104     char *label;
105     char *initial_val;
106     char *(*get_data)(struct gps_data_t *);
107     unsigned int font_size;
108     GtkWidget *label_widget;
109     GtkWidget *value_widget;
110 };
111
112 struct gps_status_info gps_table[] = {
113     {"Status:", "No Data", gps_get_status, 14, NULL, NULL},
114 //    {"Online:", "No Data", gps_get_online, 14, NULL, NULL},
115     {"Latitude:", "No Data", gps_get_latitude, 14, NULL, NULL},
116     {"Longitude:", "No Data", gps_get_longitude, 14, NULL, NULL},
117     {"Elevation:", "No Data", gps_get_elevation, 14, NULL, NULL},
118     {"Heading:", "No Data", gps_get_heading, 14, NULL, NULL},
119     {"Speed:", "No Data", gps_get_speed, 14, NULL, NULL},
120 };
121
122 static
123 gboolean gps_data_is_valid(struct gps_data_t *gps_data)
124 {
125     if (gps_data != NULL && gps_data->online != -1.0 &&
126         gps_data->fix.mode >= MODE_2D &&
127         gps_data->status > STATUS_NO_FIX) {
128         return TRUE;
129     }
130
131     return FALSE;
132 }
133
134 static char *
135 gps_get_date_string(double gps_time)
136 {
137     static char buf[256];
138     time_t      int_time = (time_t)gps_time;
139     struct tm   tm_time;
140
141     gmtime_r(&int_time, &tm_time);
142
143     snprintf(buf, sizeof(buf), "%04d-%02d-%02d",
144                  tm_time.tm_year+1900, tm_time.tm_mon+1, tm_time.tm_mday);
145
146
147     return buf;
148 }
149
150 static char *
151 gps_get_time_string(time_t gps_time)
152 {
153     static char buf[256];
154     time_t      int_time = (time_t)gps_time;
155     struct tm   tm_time;
156
157     gmtime_r(&int_time, &tm_time);
158
159     snprintf(buf, sizeof(buf), "%02d:%02d:%02dZ",
160                  tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
161
162     return buf;
163 }
164
165
166 static void
167 update_gps_status(GritsPluginGPS *gps_state)
168 {
169     struct gps_data_t *gps_data = &gps_state->gps_data;
170
171     /* gps table update */
172     int i;
173     gchar *str;
174     for (i = 0; i < sizeof(gps_table)/sizeof(*gps_table); i++) {
175         gtk_label_set_markup (GTK_LABEL(gps_table[i].value_widget),
176                 (str = gps_table[i].get_data(gps_data)));
177         g_free(str);
178     }
179 }
180
181 static void
182 gps_init_control_frame(GritsPluginGPS *gps_state, GtkWidget *gbox)
183 {
184     /* Control checkboxes */
185     GtkWidget *gps_control_frame = gtk_frame_new ("GPS Control");
186     GtkWidget *cbox = gtk_vbox_new (FALSE, 2);
187     gtk_container_add (GTK_CONTAINER (gps_control_frame), cbox);
188     gtk_box_pack_start (GTK_BOX(gbox), gps_control_frame, FALSE, FALSE, 0);
189
190     gps_state->ui.gps_follow_checkbox = gtk_check_button_new_with_label("Follow GPS");
191     g_signal_connect (G_OBJECT (gps_state->ui.gps_follow_checkbox), "clicked",
192                       G_CALLBACK (on_gps_follow_clicked_event),
193                       (gpointer)gps_state);
194     gtk_box_pack_start (GTK_BOX(cbox), gps_state->ui.gps_follow_checkbox,
195                         FALSE, FALSE, 0);
196     gps_state->ui.gps_track_checkbox = gtk_check_button_new_with_label("Show Track");
197     g_signal_connect (G_OBJECT (gps_state->ui.gps_track_checkbox), "clicked",
198                       G_CALLBACK (on_gps_track_clicked_event),
199                       (gpointer)gps_state);
200     gtk_box_pack_start (GTK_BOX(cbox), gps_state->ui.gps_track_checkbox,
201                         FALSE, FALSE, 0);
202 }
203
204 static gboolean
205 on_gps_track_clicked_event (GtkWidget *widget, gpointer user_data)
206 {
207     g_debug("on_gps_track_clicked_event called!");
208     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
209         /* XXX start logging trip history */
210         GPS_STATUS(gps_state, "Enabled GPS track.");
211     } else {
212         /* XXX stop logging trip history */
213         GPS_STATUS(gps_state, "Disabled GPS track.");
214     }
215
216     return FALSE;
217 }
218
219 static gboolean
220 on_gps_log_interval_changed_event(GtkWidget *widget, gpointer user_data)
221 {
222     GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
223
224     assert(gps_state);
225
226     g_debug("gps interval changed, value = %f",
227         gtk_range_get_value(GTK_RANGE(widget)));
228
229     if (gtk_toggle_button_get_active(
230                         GTK_TOGGLE_BUTTON(gps_state->ui.gps_log_checkbox))) {
231         assert(gps_state->ui.gps_log_timeout_id != 0);
232
233         /* disable old timeout */
234         g_source_remove(gps_state->ui.gps_log_timeout_id);
235         gps_state->ui.gps_log_timeout_id = 0;
236
237         /* Schedule new log file write */
238         gps_state->ui.gps_log_timeout_id = g_timeout_add(
239                                 gtk_range_get_value(GTK_RANGE(widget))*1000,
240                                 gps_write_log, gps_state);
241         gps_write_log(gps_state);
242     }
243
244     return FALSE;
245 }
246
247 static gboolean
248 gps_write_log(gpointer data)
249 {
250     GritsPluginGPS *gps_state = (GritsPluginGPS *)data;
251     struct gps_data_t *gps_data = &gps_state->gps_data;
252     char buf[256];
253     char filename[256];
254     int fd;
255     gboolean new_file = FALSE;
256
257     if (gps_data == NULL) {
258         g_warning("Skipped write to GPS log file: "
259                 "can not get GPS coordinates.");
260         GPS_STATUS(gps_state, "Skipped write to GPS log file: "
261                 "can not get GPS coordinates.");
262         return TRUE;
263     }
264
265     /* get filename from text entry box.  If empty, generate a name from
266      * the date and time and set it.
267      */
268     if (strlen(gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry)))
269                                                                     == 0) {
270         snprintf(filename, sizeof(filename),
271                             "%sT%s.%s",
272                             gps_get_date_string(gps_state->gps_data.fix.time),
273                             gps_get_time_string(gps_state->gps_data.fix.time),
274                             GPS_LOG_EXT);
275         gtk_entry_set_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry),
276                             filename);
277     }
278
279     strncpy(filename,
280             gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry)),
281             sizeof (filename));
282
283     if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
284         new_file = TRUE;
285     }
286
287     if ((fd = open(filename, O_CREAT|O_APPEND|O_WRONLY, 0644)) == -1) {
288         g_warning("Error opening log file %s: %s",
289                         filename, strerror(errno));
290         return FALSE;
291     }
292
293     if (new_file) {
294         /* write header and reset record counter */
295         snprintf(buf, sizeof(buf),
296                 "No,Date,Time,Lat,Lon,Ele,Head,Speed,RTR\n");
297         if (write(fd, buf, strlen(buf)) == -1) {
298             g_warning("Error writing header to log file %s: %s",
299                             filename, strerror(errno));
300         }
301         gps_state->ui.gps_log_number = 1;
302     }
303
304     /* Write log entry.  Make sure this matches the header */
305     /* "No,Date,Time,Lat,Lon,Ele,Head,Speed,Fix,RTR\n" */
306     /* RTR values: T=time, B=button push, S=speed, D=distance */
307     snprintf(buf, sizeof(buf), "%d,%s,%s,%lf,%lf,%lf,%lf,%lf,%c\n",
308         gps_state->ui.gps_log_number++,
309         gps_get_date_string(gps_state->gps_data.fix.time),
310         gps_get_time_string(gps_state->gps_data.fix.time),
311 //      gps_data->fix.time,
312         gps_data->fix.latitude,
313         gps_data->fix.longitude,
314         gps_data->fix.altitude * METERS_TO_FEET,
315         gps_data->fix.track,
316         gps_data->fix.speed * METERS_TO_FEET,
317         'T'); /* position due to timer expired  */
318
319     if (write(fd, buf, strlen(buf)) == -1) {
320         g_warning("Could not write log number %d to log file %s: %s",
321             gps_state->ui.gps_log_number-1, filename, strerror(errno));
322     }
323     close(fd);
324
325     GPS_STATUS(gps_state, "Updated GPS log file %s.", filename);
326
327     /* reschedule */
328     return TRUE;
329 }
330
331 static gboolean
332 on_gps_follow_clicked_event (GtkWidget *widget, gpointer user_data)
333 {
334     GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
335
336     g_debug("on_gps_follow_clicked_event called!, button status %d",
337         gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)));
338     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
339         gps_state->follow_gps = TRUE;
340     else
341         gps_state->follow_gps = FALSE;
342
343     return FALSE;
344 }
345
346
347 static void
348 gps_init_track_log_frame(GritsPluginGPS *gps_state, GtkWidget *gbox)
349 {
350     /* Track log box with enable checkbox and filename entry */
351     GtkWidget *gps_log_frame = gtk_frame_new ("Track Log");
352     GtkWidget *lbox = gtk_vbox_new (FALSE, 2);
353     gtk_container_add (GTK_CONTAINER (gps_log_frame), lbox);
354     gtk_box_pack_start (GTK_BOX(gbox), gps_log_frame,
355                         FALSE, FALSE, 0);
356
357     gps_state->ui.gps_log_checkbox = gtk_check_button_new_with_label("Log Position to File");
358     g_signal_connect (G_OBJECT (gps_state->ui.gps_log_checkbox), "clicked",
359                       G_CALLBACK (on_gps_log_clicked_event),
360                       (gpointer)gps_state);
361     gtk_box_pack_start (GTK_BOX(lbox), gps_state->ui.gps_log_checkbox,
362                         FALSE, FALSE, 0);
363
364     /* Set up filename entry box */
365     GtkWidget *fbox = gtk_hbox_new (FALSE, 2);
366     GtkWidget *filename_label = gtk_label_new ("Filename:");
367     gtk_box_pack_start (GTK_BOX(fbox), filename_label, FALSE, FALSE, 0);
368     gps_state->ui.gps_log_filename_entry = gtk_entry_new();
369     gtk_box_pack_start (GTK_BOX(fbox), gps_state->ui.gps_log_filename_entry,
370                         TRUE, TRUE, 0);
371     gtk_box_pack_start (GTK_BOX(lbox), fbox, FALSE, FALSE, 0);
372
373     /* set up gps log interval slider */
374     GtkWidget *ubox = gtk_hbox_new (FALSE, 4);
375     GtkWidget *interval_label = gtk_label_new ("Update Interval:");
376     gtk_box_pack_start (GTK_BOX(ubox), interval_label, FALSE, FALSE, 0);
377     gps_state->ui.gps_log_interval_slider =
378                     gtk_hscale_new_with_range(1.0, 600.0, 30.0);
379     gtk_range_set_value (GTK_RANGE(gps_state->ui.gps_log_interval_slider),
380                     GPS_LOG_DEFAULT_UPDATE_INTERVAL);
381     g_signal_connect (G_OBJECT (gps_state->ui.gps_log_interval_slider),
382                     "value-changed",
383                     G_CALLBACK(on_gps_log_interval_changed_event),
384                     (gpointer)gps_state);
385     gtk_range_set_increments (GTK_RANGE(gps_state->ui.gps_log_interval_slider),
386                     10.0 /* step */, 30.0 /* page up/down */);
387     gtk_range_set_update_policy (GTK_RANGE(gps_state->ui.gps_log_interval_slider),
388                     GTK_UPDATE_DELAYED);
389     gtk_box_pack_start (GTK_BOX(ubox), gps_state->ui.gps_log_interval_slider,
390                     TRUE, TRUE, 0);
391     gtk_box_pack_start (GTK_BOX(lbox), ubox, FALSE, FALSE, 0);
392 }
393
394 static gboolean
395 on_gps_log_clicked_event (GtkWidget *widget, gpointer user_data)
396 {
397     GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
398
399     g_debug("on_gps_log_clicked_event called!");
400
401     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))  {
402         gps_write_log(gps_state);
403
404         /* Schedule log file write */
405         gps_state->ui.gps_log_timeout_id = g_timeout_add(
406                     gtk_range_get_value(
407                         GTK_RANGE(gps_state->ui.gps_log_interval_slider))*1000,
408                         gps_write_log, gps_state);
409     } else {
410         /* button unchecked */
411         g_source_remove(gps_state->ui.gps_log_timeout_id);
412         gps_state->ui.gps_log_timeout_id = 0;
413         g_debug("Closed log file.");
414     }
415
416     return FALSE;
417 }
418
419
420 static void
421 gps_init_status_info(GritsPluginGPS *gps_state, GtkWidget *gbox)
422 {
423     gps_state->ui.gps_status_frame = gtk_frame_new ("GPS Data");
424     gps_state->ui.gps_status_table = gtk_table_new (5, 2, TRUE);
425     gtk_container_add (GTK_CONTAINER (gps_state->ui.gps_status_frame),
426                         gps_state->ui.gps_status_table);
427
428     /* gps data table setup */
429     int i;
430     for (i = 0; i < sizeof(gps_table)/sizeof(*gps_table); i++) {
431         gps_table[i].label_widget = gtk_label_new (gps_table[i].label);
432         gtk_label_set_justify(GTK_LABEL(gps_table[i].label_widget),
433                                         GTK_JUSTIFY_LEFT);
434         gtk_table_attach( GTK_TABLE(gps_state->ui.gps_status_table),
435                         gps_table[i].label_widget, 0, 1, i, i+1, 0, 0, 0, 0);
436         gps_table[i].value_widget = gtk_label_new (gps_table[i].initial_val);
437         gtk_table_attach( GTK_TABLE(gps_state->ui.gps_status_table),
438                         gps_table[i].value_widget, 1, 2, i, i+1, 0, 0, 0, 0);
439
440         PangoFontDescription *font_desc = pango_font_description_new ();
441         pango_font_description_set_size (font_desc,
442                         gps_table[i].font_size*PANGO_SCALE);
443         gtk_widget_modify_font (gps_table[i].label_widget, font_desc);
444         gtk_widget_modify_font (gps_table[i].value_widget, font_desc);
445         pango_font_description_free (font_desc);
446     }
447     gtk_box_pack_start (GTK_BOX(gbox), gps_state->ui.gps_status_frame,
448                         FALSE, FALSE, 0);
449
450     /* Start UI refresh task, which will reschedule itself periodically. */
451     gps_redraw_all(gps_state);
452     gps_state->gps_update_timeout_id = g_timeout_add(
453                     GPS_UPDATE_INTERVAL*1000,
454                     gps_redraw_all, gps_state);
455
456 }
457
458
459 #ifdef GPS_RANGE_RINGS
460 static void
461 gps_init_range_rings(GritsPluginGPS *gps_state, GtkWidget *gbox)
462 {
463     GtkWidget *gps_range_ring_frame = gtk_frame_new ("Range Rings");
464     GtkWidget *cbox = gtk_vbox_new (FALSE, 2);
465     gtk_container_add (GTK_CONTAINER (gps_range_ring_frame), cbox);
466     gtk_box_pack_start (GTK_BOX(gbox), gps_range_ring_frame, FALSE, FALSE, 0);
467
468     gps_state->ui.gps_rangering_checkbox = gtk_check_button_new_with_label("Enable Range Rings");
469     g_signal_connect (G_OBJECT (gps_state->ui.gps_rangering_checkbox), "clicked",
470                       G_CALLBACK (on_gps_rangering_clicked_event),
471                       (gpointer)gps_state);
472     gtk_box_pack_start (GTK_BOX(cbox), gps_state->ui.gps_rangering_checkbox,
473                         FALSE, FALSE, 0);
474 }
475
476 static gboolean
477 on_gps_rangering_clicked_event (GtkWidget *widget, gpointer user_data)
478 {
479     GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
480
481     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))  {
482         gps_state->gps_rangering_active = TRUE;
483     } else {
484         gps_state->gps_rangering_active = FALSE;
485     }
486
487     /* XXX force a redraw */
488
489     return FALSE;
490 }
491 #endif /* GPS_RANGE_RINGS */
492
493 /* external interface to update UI from latest GPS data. */
494 gboolean gps_redraw_all(gpointer data)
495 {
496     GritsPluginGPS *gps_state = (GritsPluginGPS *)data;
497     assert(gps_state);
498
499     struct gps_data_t *gps_data = &gps_state->gps_data;
500
501     g_debug("gps_redraw_all called");
502
503     assert(gps_data);
504     if (!gps_data_is_valid(gps_data)) {
505         g_debug("gps_data is not valid.");
506         /* XXX Change marker to indicate data is not valid */
507         return TRUE;
508     }
509
510     /* update position labels */
511     update_gps_status(gps_state);
512
513     /* Update track and marker position */
514     if (gps_data_is_valid(gps_data)) {
515         g_debug("Updating track at lat = %f, long = %f, track = %f",
516                         gps_data->fix.latitude,
517                         gps_data->fix.longitude,
518                         gps_data->fix.track);
519
520         if (gps_state->marker) {
521             grits_viewer_remove(gps_state->viewer,
522                     GRITS_OBJECT(gps_state->marker));
523             gps_state->marker = NULL;
524         }
525
526         gps_state->marker = grits_marker_icon_new("GPS", "car.png",
527                 gps_data->fix.track, TRUE);
528                 
529         GRITS_OBJECT(gps_state->marker)->center.lat  = gps_data->fix.latitude;
530         GRITS_OBJECT(gps_state->marker)->center.lon  = gps_data->fix.longitude;
531         GRITS_OBJECT(gps_state->marker)->center.elev =   0.0;
532         GRITS_OBJECT(gps_state->marker)->lod         = EARTH_R;
533
534         grits_viewer_add(gps_state->viewer, GRITS_OBJECT(gps_state->marker),
535                         GRITS_LEVEL_OVERLAY, TRUE);
536         grits_viewer_refresh(gps_state->viewer);
537     }
538
539     if (gps_state->follow_gps && gps_data_is_valid(gps_data)) {
540         /* Center map at current GPS position. */
541         g_debug("Centering map at lat = %f, long = %f, track = %f",
542                         gps_data->fix.latitude,
543                         gps_data->fix.longitude,
544                         gps_data->fix.track);
545
546         double lat, lon, elev;
547         grits_viewer_get_location(gps_state->viewer, &lat, &lon, &elev);
548         grits_viewer_set_location(gps_state->viewer, gps_data->fix.latitude,
549                                           gps_data->fix.longitude, elev);
550         //grits_viewer_set_rotation(gps_state->viewer, 0, 0, 0);
551     }
552
553     /* reschedule */
554     return TRUE;
555 }
556
557 static
558 char *gps_get_status(struct gps_data_t *gps_data)
559 {
560     gchar *status_color;
561     gchar *status_text;
562
563     switch (gps_data->fix.mode) {
564         case MODE_NOT_SEEN:
565             status_color = "red";
566             status_text = "No Signal";
567             break;
568         case MODE_NO_FIX:
569             status_color = "red";
570             status_text = "No Fix";
571             break;
572         case MODE_2D:
573             status_color = "yellow";
574             status_text = "2D Mode";
575             break;
576         case MODE_3D:
577             status_color = "green";
578             status_text = "3D Mode";
579             break;
580         default:
581             status_color = "black";
582             status_text = "Unknown";
583             break;
584     }
585     return g_strdup_printf("<span foreground=\"%s\">%s</span>",
586                                 status_color, status_text);
587 }
588
589 #if 0
590 static char *gps_get_online(struct gps_data_t *);
591
592 static
593 char *gps_get_online(struct gps_data_t *gps_data)
594 {
595     char *status_str;
596     char *online_str;
597
598     if (gps_data->online == -1.0) {
599         online_str = "Offline";
600     } else {
601         online_str = "Online";
602     }
603
604     switch (gps_data->status) {
605     case 0:
606         status_str = "No Fix";
607         break;
608     case 1:
609         status_str = "Fix Acquired";
610         break;
611     case 2:
612         status_str = "DGPS Fix";
613         break;
614     default:
615         status_str = "Unknown Status";
616         break;
617     }
618
619     return g_strdup_printf("%lf,%s,%s", gps_data->online, online_str, status_str);
620 }
621 #endif
622
623
624
625 static
626 char *gps_get_latitude(struct gps_data_t *gps_data)
627 {
628     return g_strdup_printf("%3.4f", gps_data->fix.latitude);
629 }
630
631 static
632 char *gps_get_longitude(struct gps_data_t *gps_data)
633 {
634     return g_strdup_printf("%3.4f", gps_data->fix.longitude);
635 }
636
637 static
638 char *gps_get_elevation(struct gps_data_t *gps_data)
639 {
640     /* XXX Make units (m/ft) settable */
641     return g_strdup_printf("%.1lf %s",
642                     (gps_data->fix.altitude * METERS_TO_FEET), "ft");
643 }
644
645 static
646 char *gps_get_heading(struct gps_data_t *gps_data)
647 {
648     /* XXX Make units (m/ft) settable */
649     return g_strdup_printf("%03.0lf", gps_data->fix.track);
650 }
651
652 static
653 char *gps_get_speed(struct gps_data_t *gps_data)
654 {
655     /* XXX Make units (m/ft) settable */
656     return g_strdup_printf("%1.1f %s",
657         (gps_data->fix.speed*3600.0*METERS_TO_FEET/5280.0), "mph");
658 }
659
660
661 static
662 gint initialize_gpsd(char *server, char *port,
663         struct gps_data_t *gps_data)
664 {
665 #if GPSD_API_MAJOR_VERSION < 5
666 #error "GPSD protocol version 5 or greater required."
667 #endif
668     int result;
669
670     if ((result = gps_open(server, port, gps_data)) != 0) {
671         g_warning("Unable to open gpsd connection to %s:%s: %d, %d, %s",
672         server, port, result, errno, gps_errstr(errno));
673     } else {
674         (void)gps_stream(gps_data, WATCH_ENABLE|WATCH_JSON, NULL);
675         g_debug("initialize_gpsd(): gpsd fd %u.", gps_data->gps_fd);
676         gdk_input_add(gps_data->gps_fd, GDK_INPUT_READ, process_gps, gps_data);
677     }
678
679     return result;
680 }
681
682 static void
683 process_gps(gpointer data, gint source, GdkInputCondition condition)
684 {
685     struct gps_data_t *gps_data = (struct gps_data_t *)data;
686
687     /* Process any data from the gps and call the hook function */
688     g_debug("In process_gps()");
689     if (gps_data != NULL) {
690         int result = gps_read(gps_data);
691         g_debug("In process_gps(), gps_read returned %d, position %f, %f.", result, gps_data->fix.latitude, gps_data->fix.longitude);
692     } else {
693         g_debug("process_gps: gps_data == NULL.");
694     }
695 }
696
697 /************************** GPS Object Methods *************************/
698
699 /* Methods */
700 GritsPluginGPS *grits_plugin_gps_new(GritsViewer *viewer, GritsPrefs *prefs)
701 {
702         /* TODO: move to constructor if possible */
703         g_debug("GritsPluginGPS: new");
704         GritsPluginGPS *self = g_object_new(GRITS_TYPE_PLUGIN_GPS, NULL);
705         self->viewer = viewer;
706         self->prefs  = prefs;
707
708         g_debug("grits_plugin_gps_new()");
709
710         initialize_gpsd("localhost", DEFAULT_GPSD_PORT, &self->gps_data);
711         self->follow_gps = FALSE;
712
713         gps_init_status_info(self, self->hbox);
714         gps_init_control_frame(self, self->hbox);
715         gps_init_track_log_frame(self, self->hbox);
716 #ifdef GPS_RANGE_RINGS
717         gps_init_range_rings(self, self->hbox);
718 #endif
719
720         return self;
721 }
722
723 static GtkWidget *grits_plugin_gps_get_config(GritsPlugin *_self)
724 {
725         GritsPluginGPS *self = GRITS_PLUGIN_GPS(_self);
726         return self->config;
727 }
728
729 /* GObject code */
730 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface);
731 G_DEFINE_TYPE_WITH_CODE(GritsPluginGPS, grits_plugin_gps, G_TYPE_OBJECT,
732                 G_IMPLEMENT_INTERFACE(GRITS_TYPE_PLUGIN,
733                         grits_plugin_gps_plugin_init));
734
735 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface)
736 {
737         g_debug("GritsPluginGPS: plugin_init");
738         /* Add methods to the interface */
739         iface->get_config = grits_plugin_gps_get_config;
740 }
741
742 static void grits_plugin_gps_init(GritsPluginGPS *self)
743 {
744         g_debug("GritsPluginGPS: in gps_init()");
745
746         self->config     = gtk_notebook_new();
747
748          self->hbox = gtk_hbox_new(FALSE, 2);
749          gtk_notebook_insert_page(GTK_NOTEBOOK(self->config),
750                                 GTK_WIDGET(self->hbox),
751                                 gtk_label_new("GPS"), 0);
752         /* Need to position on the top because of Win32 bug */
753         gtk_notebook_set_tab_pos(GTK_NOTEBOOK(self->config), GTK_POS_LEFT);
754 }
755
756 static void grits_plugin_gps_dispose(GObject *gobject)
757 {
758         GritsPluginGPS *self = GRITS_PLUGIN_GPS(gobject);
759
760         g_debug("GritsPluginGPS: dispose");
761
762         if (self->viewer) {
763                 if (self->marker) {
764                     grits_viewer_remove(self->viewer,
765                                         GRITS_OBJECT(self->marker));
766                 }
767                 g_object_unref(self->viewer);
768                 self->viewer = NULL;
769         }
770
771         /* Drop references */
772         G_OBJECT_CLASS(grits_plugin_gps_parent_class)->dispose(gobject);
773 }
774
775 static void grits_plugin_gps_finalize(GObject *gobject)
776 {
777         GritsPluginGPS *self = GRITS_PLUGIN_GPS(gobject);
778
779         g_debug("GritsPluginGPS: finalize");
780
781         /* Free data */
782         gtk_widget_destroy(self->config);
783         G_OBJECT_CLASS(grits_plugin_gps_parent_class)->finalize(gobject);
784 }
785
786 static void grits_plugin_gps_class_init(GritsPluginGPSClass *klass)
787 {
788         g_debug("GritsPluginGPS: class_init");
789         GObjectClass *gobject_class = (GObjectClass*)klass;
790         gobject_class->dispose  = grits_plugin_gps_dispose;
791         gobject_class->finalize = grits_plugin_gps_finalize;
792 }