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