]> git.street.me.uk Git - andy/viking.git/blob - src/datasource_gps.c
Add method to return the type of Viking data held in the clipboard
[andy/viking.git] / src / datasource_gps.c
1 /*
2  * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
3  *
4  * Copyright (C) 2003-2005, Evan Battaglia <gtoevan@gmx.net>
5  * Copyright (C) 2006, Alex Foobarian <foobarian@gmail.com>
6  * Copyright (C) 2012, Rob Norris <rw_norris@hotmail.com>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 #include <string.h>
27 #ifdef HAVE_UNISTD_H
28 #include <unistd.h>
29 #endif
30
31 #include <glib/gstdio.h>
32 #include <glib/gprintf.h>
33 #include <glib/gi18n.h>
34
35 #include "datasource_gps.h"
36 #include "viking.h"
37 #include "babel.h"
38 #include "gpx.h"
39 #include "acquire.h"
40
41 static gboolean gps_acquire_in_progress = FALSE;
42
43 static gint last_active = -1;
44 static gboolean last_get_tracks = TRUE;
45 static gboolean last_get_waypoints = TRUE;
46
47 static gpointer datasource_gps_init_func ( );
48 static void datasource_gps_get_cmd_string ( gpointer add_widgets_data_not_used, gchar **babelargs, gchar **input_file );
49 static void datasource_gps_cleanup ( gpointer user_data );
50 static void datasource_gps_progress ( BabelProgressCode c, gpointer data, acq_dialog_widgets_t *w );
51 static void datasource_gps_add_setup_widgets ( GtkWidget *dialog, VikViewport *vvp, gpointer user_data );
52 static void datasource_gps_add_progress_widgets ( GtkWidget *dialog, gpointer user_data );
53 static void datasource_gps_off ( gpointer add_widgets_data_not_used, gchar **babelargs, gchar **input_file );
54
55 VikDataSourceInterface vik_datasource_gps_interface = {
56   N_("Acquire from GPS"),
57   N_("Acquired from GPS"),
58   VIK_DATASOURCE_GPSBABEL_DIRECT,
59   VIK_DATASOURCE_CREATENEWLAYER,
60   VIK_DATASOURCE_INPUTTYPE_NONE,
61   TRUE,
62   TRUE,
63   (VikDataSourceInitFunc)               datasource_gps_init_func,
64   (VikDataSourceCheckExistenceFunc)     NULL,
65   (VikDataSourceAddSetupWidgetsFunc)    datasource_gps_add_setup_widgets,
66   (VikDataSourceGetCmdStringFunc)       datasource_gps_get_cmd_string,
67   (VikDataSourceProcessFunc)            NULL,
68   (VikDataSourceProgressFunc)           datasource_gps_progress,
69   (VikDataSourceAddProgressWidgetsFunc) datasource_gps_add_progress_widgets,
70   (VikDataSourceCleanupFunc)            datasource_gps_cleanup,
71   (VikDataSourceOffFunc)                datasource_gps_off
72 };
73
74 /*********************************************************
75  * Definitions and routines for acquiring data from GPS
76  *********************************************************/
77
78 /* widgets in setup dialog specific to GPS */
79 /* widgets in progress dialog specific to GPS */
80 /* also counts needed for progress */
81 typedef struct {
82   /* setup dialog */
83   GtkWidget *proto_l;
84   GtkComboBox *proto_b;
85   GtkWidget *ser_l;
86   GtkComboBox *ser_b;
87   GtkWidget *off_request_l;
88   GtkCheckButton *off_request_b;
89   GtkWidget *get_tracks_l;
90   GtkCheckButton *get_tracks_b;
91   GtkWidget *get_waypoints_l;
92   GtkCheckButton *get_waypoints_b;
93
94   /* progress dialog */
95   GtkWidget *gps_label;
96   GtkWidget *ver_label;
97   GtkWidget *id_label;
98   GtkWidget *wp_label;
99   GtkWidget *trk_label;
100   GtkWidget *progress_label;
101
102   /* state */
103   int total_count;
104   int count;
105 } gps_user_data_t;
106
107 static gpointer datasource_gps_init_func ()
108 {
109   return g_malloc (sizeof(gps_user_data_t));
110 }
111
112 /**
113  * datasource_gps_get_protocol:
114  *
115  * Method to get the communication protocol of the GPS device from the widget structure
116  */
117 gchar* datasource_gps_get_protocol ( gpointer user_data )
118 {
119   // Uses the list of supported devices
120   gps_user_data_t *w = (gps_user_data_t *)user_data;
121   last_active = gtk_combo_box_get_active(GTK_COMBO_BOX(w->proto_b));
122   if (a_babel_device_list)
123     return ((BabelDevice*)g_list_nth_data(a_babel_device_list, last_active))->name;
124
125   return NULL;
126 }
127
128 /**
129  * datasource_gps_get_descriptor:
130  *
131  * Method to get the descriptor from the widget structure
132  * "Everything is a file"
133  * Could actually be normal file or a serial port
134  */
135 gchar* datasource_gps_get_descriptor ( gpointer user_data )
136 {
137   gps_user_data_t *w = (gps_user_data_t *)user_data;
138   return gtk_combo_box_get_active_text(GTK_COMBO_BOX(w->ser_b));
139 }
140
141 /**
142  * datasource_gps_get_do_tracks:
143  *
144  * Method to get the track handling behaviour from the widget structure
145  */
146 gboolean datasource_gps_get_do_tracks ( gpointer user_data )
147 {
148   gps_user_data_t *w = (gps_user_data_t *)user_data;
149   last_get_tracks = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w->get_tracks_b));
150   return last_get_tracks;
151 }
152
153 /**
154  * datasource_gps_get_do_waypoints:
155  *
156  * Method to get the waypoint handling behaviour from the widget structure
157  */
158 gboolean datasource_gps_get_do_waypoints ( gpointer user_data )
159 {
160   gps_user_data_t *w = (gps_user_data_t *)user_data;
161   last_get_waypoints = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w->get_waypoints_b));
162   return last_get_waypoints;
163 }
164
165 static void datasource_gps_get_cmd_string ( gpointer user_data, gchar **babelargs, gchar **input_file )
166 {
167   char *device = NULL;
168   char *tracks = NULL;
169   char *waypoints = NULL;
170
171   if (gps_acquire_in_progress) {
172     *babelargs = *input_file = NULL;
173   }
174   
175   gps_acquire_in_progress = TRUE;
176
177   device = datasource_gps_get_protocol ( user_data );
178
179   if ( datasource_gps_get_do_tracks ( user_data ) )
180     tracks = "-t";
181   else
182     tracks = "";
183
184   if ( datasource_gps_get_do_waypoints ( user_data ) )
185     waypoints = "-w";
186   else
187     waypoints = "";
188
189   *babelargs = g_strdup_printf("-D 9 %s %s -i %s", tracks, waypoints, device);
190   /* device points to static content => no free */
191   device = NULL;
192   tracks = NULL;
193   waypoints = NULL;
194
195   *input_file = g_strdup(datasource_gps_get_descriptor(user_data));
196
197   g_debug(_("using cmdline '%s' and file '%s'\n"), *babelargs, *input_file);
198 }
199
200 /**
201  * datasource_gps_get_off:
202  *
203  * Method to get the off behaviour from the widget structure
204  */
205 gboolean datasource_gps_get_off ( gpointer user_data )
206 {
207   gps_user_data_t *w = (gps_user_data_t *)user_data;
208   return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w->off_request_b));
209 }
210
211 static void datasource_gps_off ( gpointer user_data, gchar **babelargs, gchar **input_file )
212 {
213   char *ser = NULL;
214   char *device = NULL;
215   gps_user_data_t *w = (gps_user_data_t *)user_data;
216
217   if (gps_acquire_in_progress) {
218     *babelargs = *input_file = NULL;
219   }
220
221   /* See if we should turn off the device */
222   if (!datasource_gps_get_off ( user_data )){
223     return;
224   }
225   
226   if (!a_babel_device_list)
227     return;
228   last_active = gtk_combo_box_get_active(GTK_COMBO_BOX(w->proto_b));
229   device = ((BabelDevice*)g_list_nth_data(a_babel_device_list, last_active))->name;
230   if (!strcmp(device, "garmin")) {
231     device = "garmin,power_off";
232   }
233   else if (!strcmp(device, "navilink")) {
234     device = "navilink,power_off";
235   }
236   else {
237     return;
238   }
239
240   *babelargs = g_strdup_printf("-i %s", device);
241   /* device points to static content => no free */
242   device = NULL;
243   
244   ser = gtk_combo_box_get_active_text(GTK_COMBO_BOX(w->ser_b));
245   *input_file = g_strdup(ser);
246 }
247
248
249 static void datasource_gps_cleanup ( gpointer user_data )
250 {
251   g_free ( user_data );
252   gps_acquire_in_progress = FALSE;
253 }
254
255 /**
256  * datasource_gps_clean_up:
257  *
258  * External method to tidy up
259  */
260 void datasource_gps_clean_up ( gpointer user_data )
261 {
262   datasource_gps_cleanup ( user_data );
263 }
264
265 static void set_total_count(gint cnt, acq_dialog_widgets_t *w)
266 {
267   gchar *s = NULL;
268   gdk_threads_enter();
269   if (w->ok) {
270     gps_user_data_t *gps_data = (gps_user_data_t *)w->user_data;
271     const gchar *tmp_str;
272     if (gps_data->progress_label == gps_data->wp_label)
273       tmp_str = ngettext("Downloading %d waypoint...", "Downloading %d waypoints...", cnt);
274     else
275       tmp_str = ngettext("Downloading %d trackpoint...", "Downloading %d trackpoints...", cnt);
276     s = g_strdup_printf(tmp_str, cnt);
277     gtk_label_set_text ( GTK_LABEL(gps_data->progress_label), s );
278     gtk_widget_show ( gps_data->progress_label );
279     gps_data->total_count = cnt;
280   }
281   g_free(s); s = NULL;
282   gdk_threads_leave();
283 }
284
285 static void set_current_count(gint cnt, acq_dialog_widgets_t *w)
286 {
287   gchar *s = NULL;
288   gdk_threads_enter();
289   if (w->ok) {
290     gps_user_data_t *gps_data = (gps_user_data_t *)w->user_data;
291
292     if (cnt < gps_data->total_count) {
293       s = g_strdup_printf(_("Downloaded %d out of %d %s..."), cnt, gps_data->total_count, (gps_data->progress_label == gps_data->wp_label) ? "waypoints" : "trackpoints");
294     } else {
295       s = g_strdup_printf(_("Downloaded %d %s."), cnt, (gps_data->progress_label == gps_data->wp_label) ? "waypoints" : "trackpoints");
296     }     
297     gtk_label_set_text ( GTK_LABEL(gps_data->progress_label), s );
298   }
299   g_free(s); s = NULL;
300   gdk_threads_leave();
301 }
302
303 static void set_gps_info(const gchar *info, acq_dialog_widgets_t *w)
304 {
305   gchar *s = NULL;
306   gdk_threads_enter();
307   if (w->ok) {
308     s = g_strdup_printf(_("GPS Device: %s"), info);
309     gtk_label_set_text ( GTK_LABEL(((gps_user_data_t *)w->user_data)->gps_label), s );
310   }
311   g_free(s); s = NULL;
312   gdk_threads_leave();
313 }
314
315 /* 
316  * This routine relies on gpsbabel's diagnostic output to display the progress information. 
317  * These outputs differ when different GPS devices are used, so we will need to test
318  * them on several and add the corresponding support.
319  */
320 static void datasource_gps_progress ( BabelProgressCode c, gpointer data, acq_dialog_widgets_t *w )
321 {
322   gchar *line;
323   gps_user_data_t *gps_data = (gps_user_data_t *)w->user_data;
324
325   switch(c) {
326   case BABEL_DIAG_OUTPUT:
327     line = (gchar *)data;
328
329     gdk_threads_enter();
330     if (w->ok) {
331       gtk_label_set_text ( GTK_LABEL(w->status), _("Status: Working...") );
332     }
333     gdk_threads_leave();
334
335     /* tells us how many items there will be */
336     if (strstr(line, "Xfer Wpt")) { 
337       gps_data->progress_label = gps_data->wp_label;
338     }
339     if (strstr(line, "Xfer Trk")) { 
340       gps_data->progress_label = gps_data->trk_label;
341     }
342     if (strstr(line, "PRDDAT")) {
343       gchar **tokens = g_strsplit(line, " ", 0);
344       gchar info[128];
345       int ilen = 0;
346       int i;
347       int n_tokens = 0;
348
349       while (tokens[n_tokens])
350         n_tokens++;
351
352       if (n_tokens > 8) {
353         for (i=8; tokens[i] && ilen < sizeof(info)-2 && strcmp(tokens[i], "00"); i++) {
354           guint ch;
355           sscanf(tokens[i], "%x", &ch);
356           info[ilen++] = ch;
357         }
358         info[ilen++] = 0;
359         set_gps_info(info, w);
360       }
361       g_strfreev(tokens);
362     }
363     /* eg: "Unit:\teTrex Legend HCx Software Version 2.90\n" */
364     if (strstr(line, "Unit:")) {
365       gchar **tokens = g_strsplit(line, "\t", 0);
366       int n_tokens = 0;
367       while (tokens[n_tokens])
368         n_tokens++;
369
370       if (n_tokens > 1) {
371         set_gps_info(tokens[1], w);
372       }
373       g_strfreev(tokens);
374     }
375     if (strstr(line, "RECORD")) { 
376       int lsb, msb, cnt;
377
378       if (strlen(line) > 20) {
379        sscanf(line+17, "%x", &lsb); 
380        sscanf(line+20, "%x", &msb);
381        cnt = lsb + msb * 256;
382        set_total_count(cnt, w);
383        gps_data->count = 0;
384       }
385     }
386     if ( strstr(line, "WPTDAT") || strstr(line, "TRKHDR") || strstr(line, "TRKDAT") ) {
387       gps_data->count++;
388       set_current_count(gps_data->count, w);
389     }
390     break;
391   case BABEL_DONE:
392     break;
393   default:
394     break;
395   }
396 }
397
398 void append_element (gpointer elem, gpointer user_data)
399 {
400   GtkComboBox *combo = GTK_COMBO_BOX (user_data);
401   const gchar *text = ((BabelDevice*)elem)->label;
402   gtk_combo_box_append_text (combo, text);
403 }
404
405 static gint find_entry = -1;
406 static gint garmin_entry = -1;
407
408 static void find_garmin (gpointer elem, gpointer user_data)
409 {
410   const gchar *name = ((BabelDevice*)elem)->name;
411   find_entry++;
412   if (!strcmp(name, "garmin")) {
413     garmin_entry = find_entry;
414   }
415 }
416
417 static void datasource_gps_add_setup_widgets ( GtkWidget *dialog, VikViewport *vvp, gpointer user_data )
418 {
419   gps_user_data_t *w = (gps_user_data_t *)user_data;
420   GtkTable *box, *data_type_box;
421
422   w->proto_l = gtk_label_new (_("GPS Protocol:"));
423   w->proto_b = GTK_COMBO_BOX(gtk_combo_box_new_text ());
424   g_list_foreach (a_babel_device_list, append_element, w->proto_b);
425
426   // Maintain default to Garmin devices (assumed most popular/numerous device)
427   if ( last_active < 0 ) {
428     find_entry = -1;
429     g_list_foreach (a_babel_device_list, find_garmin, NULL);
430     if ( garmin_entry < 0 )
431       // Not found - so set it to the first entry
432       last_active = 0;
433     else
434       // Found
435       last_active = garmin_entry;
436   }
437
438   gtk_combo_box_set_active (w->proto_b, last_active);
439   g_object_ref(w->proto_b);
440
441   w->ser_l = gtk_label_new (_("Serial Port:"));
442   w->ser_b = GTK_COMBO_BOX(gtk_combo_box_entry_new_text ());
443 #ifdef WINDOWS
444   gtk_combo_box_append_text (w->ser_b, "com1");
445 #else
446   /* Here just try to see if the device is available which gets passed onto gpsbabel
447      List USB devices first as these will generally only be present if autogenerated by udev or similar
448      User is still able to set their own free text entry */
449   if (g_access ("/dev/ttyUSB0", R_OK) == 0)
450     gtk_combo_box_append_text (w->ser_b, "/dev/ttyUSB0");
451   if (g_access ("/dev/ttyUSB1", R_OK) == 0)
452     gtk_combo_box_append_text (w->ser_b, "/dev/ttyUSB1");
453   if (g_access ("/dev/ttyS0", R_OK) == 0)
454     gtk_combo_box_append_text (w->ser_b, "/dev/ttyS0");
455   if (g_access ("/dev/ttyS1", R_OK) == 0)
456     gtk_combo_box_append_text (w->ser_b, "/dev/ttyS1");
457 #endif
458   gtk_combo_box_append_text (w->ser_b, "usb:");
459   gtk_combo_box_set_active (w->ser_b, 0);
460   g_object_ref(w->ser_b);
461
462   w->off_request_l = gtk_label_new (_("Turn Off After Transfer\n(Garmin/NAViLink Only)"));
463   w->off_request_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
464
465   w->get_tracks_l = gtk_label_new (_("Tracks:"));
466   w->get_tracks_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
467   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w->get_tracks_b), last_get_tracks);
468
469   w->get_waypoints_l = gtk_label_new (_("Waypoints:"));
470   w->get_waypoints_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
471   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w->get_waypoints_b), last_get_waypoints);
472
473   box = GTK_TABLE(gtk_table_new(2, 4, FALSE));
474   data_type_box = GTK_TABLE(gtk_table_new(4, 1, FALSE));
475
476   gtk_table_attach_defaults(box, GTK_WIDGET(w->proto_l), 0, 1, 0, 1);
477   gtk_table_attach_defaults(box, GTK_WIDGET(w->proto_b), 1, 2, 0, 1);
478   gtk_table_attach_defaults(box, GTK_WIDGET(w->ser_l), 0, 1, 1, 2);
479   gtk_table_attach_defaults(box, GTK_WIDGET(w->ser_b), 1, 2, 1, 2);
480   gtk_table_attach_defaults(data_type_box, GTK_WIDGET(w->get_tracks_l), 0, 1, 0, 1);
481   gtk_table_attach_defaults(data_type_box, GTK_WIDGET(w->get_tracks_b), 1, 2, 0, 1);
482   gtk_table_attach_defaults(data_type_box, GTK_WIDGET(w->get_waypoints_l), 2, 3, 0, 1);
483   gtk_table_attach_defaults(data_type_box, GTK_WIDGET(w->get_waypoints_b), 3, 4, 0, 1);
484   gtk_table_attach_defaults(box, GTK_WIDGET(data_type_box), 0, 2, 2, 3);
485   gtk_table_attach_defaults(box, GTK_WIDGET(w->off_request_l), 0, 1, 3, 4);
486   gtk_table_attach_defaults(box, GTK_WIDGET(w->off_request_b), 1, 3, 3, 4);
487   gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(dialog)->vbox), GTK_WIDGET(box), FALSE, FALSE, 5 );
488
489   gtk_widget_show_all ( dialog );
490 }
491
492 /**
493  * datasource_gps_setup:
494  * @dialog: The GTK dialog. The caller is responsible for managing the dialog creation/deletion
495  * @only_tracks: When only tracks are specified, waypoints will be disabled.
496  *
497  * Returns: A gpointer to the private structure for GPS progress/information widgets
498  *          Pass this pointer back into the other exposed datasource_gps_X functions
499  */
500 gpointer datasource_gps_setup ( GtkWidget *dialog, gboolean only_tracks )
501 {
502   gps_user_data_t *w_gps = (gps_user_data_t *)datasource_gps_init_func();
503   datasource_gps_add_setup_widgets ( dialog, NULL, w_gps );
504
505   if ( only_tracks ) {
506     // Indicate tracks enabled (although no option to turn off):
507     gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(w_gps->get_tracks_b), TRUE);
508     gtk_widget_set_sensitive ( GTK_WIDGET(w_gps->get_tracks_b), FALSE );
509     // Disable waypoints
510     gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(w_gps->get_waypoints_b), FALSE);
511     gtk_widget_set_sensitive ( GTK_WIDGET(w_gps->get_waypoints_l), FALSE );
512     gtk_widget_set_sensitive ( GTK_WIDGET(w_gps->get_waypoints_b), FALSE );
513   }
514   return (gpointer)w_gps;
515 }
516
517 void datasource_gps_add_progress_widgets ( GtkWidget *dialog, gpointer user_data )
518 {
519   GtkWidget *gpslabel, *verlabel, *idlabel, *wplabel, *trklabel;
520
521   gps_user_data_t *w_gps = (gps_user_data_t *)user_data;
522
523   gpslabel = gtk_label_new (_("GPS device: N/A"));
524   verlabel = gtk_label_new ("");
525   idlabel = gtk_label_new ("");
526   wplabel = gtk_label_new ("");
527   trklabel = gtk_label_new ("");
528
529   gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(dialog)->vbox), gpslabel, FALSE, FALSE, 5 );
530   gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(dialog)->vbox), wplabel, FALSE, FALSE, 5 );
531   gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(dialog)->vbox), trklabel, FALSE, FALSE, 5 );
532
533   gtk_widget_show_all ( dialog );
534
535   w_gps->gps_label = gpslabel;
536   w_gps->id_label = idlabel;
537   w_gps->ver_label = verlabel;
538   w_gps->progress_label = w_gps->wp_label = wplabel;
539   w_gps->trk_label = trklabel;
540   w_gps->total_count = -1;
541 }