]> git.street.me.uk Git - andy/viking.git/blob - src/vikgoto.c
Refactor: TrackWaypoint sublayer menu callback functions.
[andy/viking.git] / src / vikgoto.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) 2009, Guilhem Bonnefille <guilhem.bonnefille@gmail.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <glib.h>
29 #include <glib/gstdio.h>
30 #include <glib/gprintf.h>
31 #include <glib/gi18n.h>
32
33 #include "viking.h"
34 #include "util.h"
35 #include "vikgototool.h"
36 #include "vikgoto.h"
37
38 /* Compatibility */
39 #if ! GLIB_CHECK_VERSION(2,22,0)
40 #define g_mapped_file_unref g_mapped_file_free
41 #endif
42
43 static gchar *last_goto_str = NULL;
44 static VikCoord *last_coord = NULL;
45 static gchar *last_successful_goto_str = NULL;
46
47 static GList *goto_tools_list = NULL;
48
49 #define VIK_SETTINGS_GOTO_PROVIDER "goto_provider"
50 int last_goto_tool = -1;
51
52 void vik_goto_register ( VikGotoTool *tool )
53 {
54   if ( IS_VIK_GOTO_TOOL( tool ) )
55     goto_tools_list = g_list_append ( goto_tools_list, g_object_ref ( tool ) );
56 }
57
58 void vik_goto_unregister_all ()
59 {
60   g_list_foreach ( goto_tools_list, (GFunc) g_object_unref, NULL );
61 }
62
63 gchar * a_vik_goto_get_search_string_for_this_place(VikWindow *vw)
64 {
65   if (!last_coord)
66     return NULL;
67
68   VikViewport *vvp = vik_window_viewport(vw);
69   const VikCoord *cur_center = vik_viewport_get_center(vvp);
70   if (vik_coord_equals(cur_center, last_coord)) {
71     return(last_successful_goto_str);
72   }
73   else
74     return NULL;
75 }
76
77 static void display_no_tool(VikWindow *vw)
78 {
79   GtkWidget *dialog = NULL;
80
81   dialog = gtk_message_dialog_new ( GTK_WINDOW(vw), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("No goto tool available.") );
82
83   gtk_dialog_run ( GTK_DIALOG(dialog) );
84
85   gtk_widget_destroy(dialog);
86 }
87
88 static gboolean prompt_try_again(VikWindow *vw)
89 {
90   GtkWidget *dialog = NULL;
91   gboolean ret = TRUE;
92
93   dialog = gtk_dialog_new_with_buttons ( "", GTK_WINDOW(vw), 0, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL );
94   gtk_window_set_title(GTK_WINDOW(dialog), _("goto"));
95
96   GtkWidget *goto_label = gtk_label_new(_("I don't know that place. Do you want another goto?"));
97   gtk_box_pack_start ( GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), goto_label, FALSE, FALSE, 5 );
98   gtk_dialog_set_default_response ( GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT );
99   gtk_widget_show_all(dialog);
100
101   if ( gtk_dialog_run ( GTK_DIALOG(dialog) ) != GTK_RESPONSE_ACCEPT )
102     ret = FALSE;
103
104   gtk_widget_destroy(dialog);
105   return ret;
106 }
107
108 static gint find_entry = -1;
109 static gint wanted_entry = -1;
110
111 static void find_provider (gpointer elem, gpointer user_data)
112 {
113   const gchar *name = vik_goto_tool_get_label (elem);
114   const gchar *provider = user_data;
115   find_entry++;
116   if (!strcmp(name, provider)) {
117     wanted_entry = find_entry;
118   }
119 }
120
121 /**
122  * Setup last_goto_tool value
123  */
124 static void get_provider ()
125 {
126   // Use setting for the provider if available
127   if ( last_goto_tool < 0 ) {
128     find_entry = -1;
129     wanted_entry = -1;
130     gchar *provider = NULL;
131     if ( a_settings_get_string ( VIK_SETTINGS_GOTO_PROVIDER, &provider ) ) {
132       // Use setting
133       if ( provider )
134         g_list_foreach (goto_tools_list, find_provider, provider);
135       // If not found set it to the first entry, otherwise use the entry
136       last_goto_tool = ( wanted_entry < 0 ) ? 0 : wanted_entry;
137     }
138     else
139       last_goto_tool = 0;
140   }
141 }
142
143 static gchar *a_prompt_for_goto_string(VikWindow *vw)
144 {
145   GtkWidget *dialog = NULL;
146
147   dialog = gtk_dialog_new_with_buttons ( "", GTK_WINDOW(vw), 0, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL );
148   gtk_window_set_title(GTK_WINDOW(dialog), _("goto"));
149
150   GtkWidget *tool_label = gtk_label_new(_("goto provider:"));
151   GtkWidget *tool_list = vik_combo_box_text_new ();
152   GList *current = g_list_first (goto_tools_list);
153   while (current != NULL)
154   {
155     char *label = NULL;
156     VikGotoTool *tool = current->data;
157     label = vik_goto_tool_get_label (tool);
158     vik_combo_box_text_append ( tool_list, label );
159     current = g_list_next (current);
160   }
161
162   get_provider ();
163   gtk_combo_box_set_active ( GTK_COMBO_BOX( tool_list ), last_goto_tool );
164
165   GtkWidget *goto_label = gtk_label_new(_("Enter address or place name:"));
166   GtkWidget *goto_entry = gtk_entry_new();
167   if (last_goto_str)
168     gtk_entry_set_text(GTK_ENTRY(goto_entry), last_goto_str);
169
170   // 'ok' when press return in the entry
171   g_signal_connect_swapped (goto_entry, "activate", G_CALLBACK(a_dialog_response_accept), dialog);
172
173   gtk_box_pack_start ( GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), tool_label, FALSE, FALSE, 5 );
174   gtk_box_pack_start ( GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), tool_list, FALSE, FALSE, 5 );
175   gtk_box_pack_start ( GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), goto_label, FALSE, FALSE, 5 );
176   gtk_box_pack_start ( GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), goto_entry, FALSE, FALSE, 5 );
177   gtk_dialog_set_default_response ( GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT );
178   gtk_widget_show_all(dialog);
179
180   // Ensure the text field has focus so we can start typing straight away
181   gtk_widget_grab_focus ( goto_entry );
182
183   if ( gtk_dialog_run ( GTK_DIALOG(dialog) ) != GTK_RESPONSE_ACCEPT ) {
184     gtk_widget_destroy(dialog);
185     return NULL;
186   }
187   
188   // TODO check if list is empty
189   last_goto_tool = gtk_combo_box_get_active ( GTK_COMBO_BOX(tool_list) );
190   gchar *provider = vik_goto_tool_get_label ( g_list_nth_data (goto_tools_list, last_goto_tool) );
191   a_settings_set_string ( VIK_SETTINGS_GOTO_PROVIDER, provider );
192
193   gchar *goto_str = g_strdup ( gtk_entry_get_text ( GTK_ENTRY(goto_entry) ) );
194
195   gtk_widget_destroy(dialog);
196
197   if (goto_str[0] != '\0') {
198     if (last_goto_str)
199       g_free(last_goto_str);
200     last_goto_str = g_strdup(goto_str);
201   }
202
203   return(goto_str);   /* goto_str needs to be freed by caller */
204 }
205
206 /**
207  * Goto a place when we already have a string to search on
208  *
209  * Returns: %TRUE if a successful lookup
210  */
211 static gboolean vik_goto_place ( VikWindow *vw, VikViewport *vvp, gchar* name, VikCoord *vcoord )
212 {
213   // Ensure last_goto_tool is given a value
214   get_provider ();
215
216   if ( goto_tools_list ) {
217     VikGotoTool *gototool = g_list_nth_data ( goto_tools_list, last_goto_tool );
218     if ( gototool ) {
219       if ( vik_goto_tool_get_coord ( gototool, vw, vvp, name, vcoord ) == 0 )
220         return TRUE;
221     }
222   }
223   return FALSE;
224 }
225
226 void a_vik_goto(VikWindow *vw, VikViewport *vvp)
227 {
228   VikCoord new_center;
229   gchar *s_str;
230   gboolean more = TRUE;
231
232   if (goto_tools_list == NULL)
233   {
234     /* Empty list */
235     display_no_tool(vw);
236     return;
237   }
238
239   do {
240     s_str = a_prompt_for_goto_string(vw);
241     if ((!s_str) || (s_str[0] == 0)) {
242       more = FALSE;
243     }
244
245     else if (!vik_goto_tool_get_coord(g_list_nth_data (goto_tools_list, last_goto_tool), vw, vvp, s_str, &new_center)) {
246       if (last_coord)
247         g_free(last_coord);
248       last_coord = g_malloc(sizeof(VikCoord));
249       *last_coord = new_center;
250       if (last_successful_goto_str)
251         g_free(last_successful_goto_str);
252       last_successful_goto_str = g_strdup(last_goto_str);
253       vik_viewport_set_center_coord(vvp, &new_center);
254       more = FALSE;
255     }
256     else if (!prompt_try_again(vw))
257         more = FALSE;
258     g_free(s_str);
259   } while (more);
260 }
261
262 #define HOSTIP_LATITUDE_PATTERN "\"lat\":\""
263 #define HOSTIP_LONGITUDE_PATTERN "\"lng\":\""
264 #define HOSTIP_CITY_PATTERN "\"city\":\""
265 #define HOSTIP_COUNTRY_PATTERN "\"country_name\":\""
266
267 /**
268  * Automatic attempt to find out where you are using:
269  *   1. http://www.hostip.info ++
270  *   2. if not specific enough fallback to using the default goto tool with a country name
271  * ++ Using returned JSON information
272  *  c.f. with googlesearch.c - similar implementation is used here
273  *
274  * returns:
275  *   0 if failed to locate anything
276  *   1 if exact latitude/longitude found
277  *   2 if position only as precise as a city
278  *   3 if position only as precise as a country
279  * @name: Contains the name of place found. Free this string after use.
280  */
281 gint a_vik_goto_where_am_i ( VikViewport *vvp, struct LatLon *ll, gchar **name )
282 {
283   gint result = 0;
284   *name = NULL;
285
286   gchar *tmpname = a_download_uri_to_tmp_file ( "http://api.hostip.info/get_json.php?position=true", NULL );
287   //gchar *tmpname = g_strdup ("../test/hostip2.json");
288   if (!tmpname) {
289     return result;
290   }
291
292   ll->lat = 0.0;
293   ll->lon = 0.0;
294
295   gchar *pat;
296   GMappedFile *mf;
297   gchar *ss;
298   gint fragment_len;
299
300   gchar lat_buf[32], lon_buf[32];
301   lat_buf[0] = lon_buf[0] = '\0';
302   gchar *country = NULL;
303   gchar *city = NULL;
304
305   if ((mf = g_mapped_file_new(tmpname, FALSE, NULL)) == NULL) {
306     g_critical(_("couldn't map temp file"));
307     goto tidy;
308   }
309
310   gsize len = g_mapped_file_get_length(mf);
311   gchar *text = g_mapped_file_get_contents(mf);
312
313   if ((pat = g_strstr_len(text, len, HOSTIP_COUNTRY_PATTERN))) {
314     pat += strlen(HOSTIP_COUNTRY_PATTERN);
315     fragment_len = 0;
316     ss = pat;
317     while (*pat != '"') {
318       fragment_len++;
319       pat++;
320     }
321     country = g_strndup(ss, fragment_len);
322   }
323
324   if ((pat = g_strstr_len(text, len, HOSTIP_CITY_PATTERN))) {
325     pat += strlen(HOSTIP_CITY_PATTERN);
326     fragment_len = 0;
327     ss = pat;
328     while (*pat != '"') {
329       fragment_len++;
330       pat++;
331     }
332     city = g_strndup(ss, fragment_len);
333   }
334
335   if ((pat = g_strstr_len(text, len, HOSTIP_LATITUDE_PATTERN))) {
336     pat += strlen(HOSTIP_LATITUDE_PATTERN);
337     ss = lat_buf;
338     if (*pat == '-')
339       *ss++ = *pat++;
340     while ((ss < (lat_buf + sizeof(lat_buf))) && (pat < (text + len)) &&
341            (g_ascii_isdigit(*pat) || (*pat == '.')))
342       *ss++ = *pat++;
343     *ss = '\0';
344     ll->lat = g_ascii_strtod(lat_buf, NULL);
345   }
346
347   if ((pat = g_strstr_len(text, len, HOSTIP_LONGITUDE_PATTERN))) {
348     pat += strlen(HOSTIP_LONGITUDE_PATTERN);
349     ss = lon_buf;
350     if (*pat == '-')
351       *ss++ = *pat++;
352     while ((ss < (lon_buf + sizeof(lon_buf))) && (pat < (text + len)) &&
353            (g_ascii_isdigit(*pat) || (*pat == '.')))
354       *ss++ = *pat++;
355     *ss = '\0';
356     ll->lon = g_ascii_strtod(lon_buf, NULL);
357   }
358
359   if ( ll->lat != 0.0 && ll->lon != 0.0 ) {
360     if ( ll->lat > -90.0 && ll->lat < 90.0 && ll->lon > -180.0 && ll->lon < 180.0 ) {
361       // Found a 'sensible' & 'precise' location
362       result = 1;
363       *name = g_strdup ( _("Locality") ); //Albeit maybe not known by an actual name!
364     }
365   }
366   else {
367     // Hopefully city name is unique enough to lookup position on
368     // Maybe for American places where hostip appends the State code on the end
369     // But if the country code is not appended if could easily get confused
370     //  e.g. 'Portsmouth' could be at least
371     //   Portsmouth, Hampshire, UK or
372     //   Portsmouth, Viginia, USA.
373
374     // Try city name lookup
375     if ( city ) {
376       g_debug ( "%s: found city %s", __FUNCTION__, city );
377       if ( strcmp ( city, "(Unknown city)" ) != 0 ) {
378         VikCoord new_center;
379         if ( vik_goto_place ( NULL, vvp, city, &new_center ) ) {
380           // Got something
381           vik_coord_to_latlon ( &new_center, ll );
382           result = 2;
383           *name = city;
384           goto tidy;
385         }
386       }
387     }
388
389     // Try country name lookup
390     if ( country ) {
391       g_debug ( "%s: found country %s", __FUNCTION__, country );
392       if ( strcmp ( country, "(Unknown Country)" ) != 0 ) {
393         VikCoord new_center;
394         if ( vik_goto_place ( NULL, vvp, country, &new_center ) ) {
395           // Finally got something
396           vik_coord_to_latlon ( &new_center, ll );
397           result = 3;
398           *name = country;
399           goto tidy;
400         }
401       }
402     }
403   }
404   
405  tidy:
406   g_mapped_file_unref ( mf );
407   g_remove ( tmpname );
408   g_free ( tmpname );
409   return result;
410 }