]> git.street.me.uk Git - andy/viking.git/blob - src/vikwebtool_datasource.c
[QA] Fix clang warning: format string is not a string literal
[andy/viking.git] / src / vikwebtool_datasource.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
4  *
5  * Copyright (C) 2013, Rob Norris <rw_norris@hotmail.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
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include "vikwebtool_datasource.h"
28 #include <ctype.h>
29 #include <string.h>
30
31 #include <glib.h>
32 #include <glib/gi18n.h>
33
34 #include "globals.h"
35 #include "acquire.h"
36 #include "maputils.h"
37 #include "dialog.h"
38
39 static GObjectClass *parent_class;
40 static GHashTable *last_user_strings = NULL;
41
42 static void webtool_datasource_finalize ( GObject *gob );
43
44 static gchar *webtool_datasource_get_url ( VikWebtool *self, VikWindow *vw );
45
46 static gboolean webtool_needs_user_string ( VikWebtool *self );
47
48 typedef struct _VikWebtoolDatasourcePrivate VikWebtoolDatasourcePrivate;
49
50 struct _VikWebtoolDatasourcePrivate
51 {
52         gchar *url;
53         gchar *url_format_code;
54         gchar *file_type;
55         gchar *babel_filter_args;
56     gchar *input_label;
57         gchar *user_string;
58 };
59
60 #define WEBTOOL_DATASOURCE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
61                                            VIK_WEBTOOL_DATASOURCE_TYPE,      \
62                                            VikWebtoolDatasourcePrivate))
63
64 G_DEFINE_TYPE (VikWebtoolDatasource, vik_webtool_datasource, VIK_WEBTOOL_TYPE)
65
66 enum
67 {
68         PROP_0,
69         PROP_URL,
70         PROP_URL_FORMAT_CODE,
71         PROP_FILE_TYPE,
72         PROP_BABEL_FILTER_ARGS,
73     PROP_INPUT_LABEL
74 };
75
76 static void webtool_datasource_set_property (GObject      *object,
77                                              guint         property_id,
78                                              const GValue *value,
79                                              GParamSpec   *pspec)
80 {
81         VikWebtoolDatasource *self = VIK_WEBTOOL_DATASOURCE ( object );
82         VikWebtoolDatasourcePrivate *priv = WEBTOOL_DATASOURCE_GET_PRIVATE ( self );
83
84         switch ( property_id ) {
85         
86     case PROP_URL:
87                 g_free ( priv->url );
88                 priv->url = g_value_dup_string ( value );
89                 g_debug ( "VikWebtoolDatasource.url: %s", priv->url );
90                 break;
91
92         case PROP_URL_FORMAT_CODE:
93                 g_free ( priv->url_format_code );
94                 priv->url_format_code = g_value_dup_string ( value );
95                 g_debug ( "VikWebtoolDatasource.url_format_code: %s", priv->url_format_code );
96                 break;
97
98         case PROP_FILE_TYPE:
99                 g_free ( priv->file_type );
100                 priv->file_type = g_value_dup_string ( value );
101                 g_debug ( "VikWebtoolDatasource.file_type: %s", priv->url_format_code );
102                 break;
103
104         case PROP_BABEL_FILTER_ARGS:
105                 g_free ( priv->babel_filter_args );
106                 priv->babel_filter_args = g_value_dup_string ( value );
107                 g_debug ( "VikWebtoolDatasource.babel_filter_args: %s", priv->babel_filter_args );
108                 break;
109
110     case PROP_INPUT_LABEL:
111                 g_free ( priv->input_label );
112                 priv->input_label = g_value_dup_string ( value );
113                 g_debug ( "VikWebtoolDatasource.input_label: %s", priv->input_label );
114                 break;
115
116         default:
117                 /* We don't have any other property... */
118                 G_OBJECT_WARN_INVALID_PROPERTY_ID ( object, property_id, pspec );
119                 break;
120         }
121 }
122
123 static void webtool_datasource_get_property (GObject    *object,
124                                              guint       property_id,
125                                              GValue     *value,
126                                              GParamSpec *pspec)
127 {
128         VikWebtoolDatasource *self = VIK_WEBTOOL_DATASOURCE ( object );
129         VikWebtoolDatasourcePrivate *priv = WEBTOOL_DATASOURCE_GET_PRIVATE ( self );
130
131         switch ( property_id ) {
132
133         case PROP_URL:               g_value_set_string ( value, priv->url ); break;
134         case PROP_URL_FORMAT_CODE:       g_value_set_string ( value, priv->url_format_code ); break;
135         case PROP_FILE_TYPE:         g_value_set_string ( value, priv->url ); break;
136         case PROP_BABEL_FILTER_ARGS: g_value_set_string ( value, priv->babel_filter_args ); break;
137         case PROP_INPUT_LABEL:       g_value_set_string ( value, priv->input_label ); break;
138
139         default:
140                 /* We don't have any other property... */
141                 G_OBJECT_WARN_INVALID_PROPERTY_ID ( object, property_id, pspec );
142                 break;
143         }
144 }
145
146 typedef struct {
147         VikExtTool *self;
148         VikWindow *vw;
149         VikViewport *vvp;
150         GtkWidget *user_string;
151 } datasource_t;
152
153
154 static void ensure_last_user_strings_hash() {
155     if ( last_user_strings == NULL ) {
156         last_user_strings = g_hash_table_new_full ( g_str_hash, 
157                                                     g_str_equal,
158                                                     g_free,
159                                                     g_free ); 
160     }
161 }
162
163
164 static gchar *get_last_user_string ( const datasource_t *source ) {
165     ensure_last_user_strings_hash();
166     gchar *label = vik_ext_tool_get_label ( source->self );
167     gchar *last_str = g_hash_table_lookup ( last_user_strings, label );
168     g_free( label );
169     return last_str;
170 }
171
172
173 static void set_last_user_string ( const datasource_t *source, const gchar *s ) {
174     ensure_last_user_strings_hash();
175     g_hash_table_insert ( last_user_strings, 
176                           vik_ext_tool_get_label ( source->self ), 
177                           g_strdup ( s ) );
178 }
179
180 static gpointer datasource_init ( acq_vik_t *avt )
181 {
182         datasource_t *data = g_malloc(sizeof(*data));
183         data->self = avt->userdata;
184         data->vw = avt->vw;
185         data->vvp = avt->vvp;
186         data->user_string = NULL;
187         return data;
188 }
189
190 static void datasource_add_setup_widgets ( GtkWidget *dialog, VikViewport *vvp, gpointer user_data )
191 {
192         datasource_t *widgets = (datasource_t *)user_data;
193         VikWebtoolDatasourcePrivate *priv = WEBTOOL_DATASOURCE_GET_PRIVATE ( widgets->self );
194         GtkWidget *user_string_label;
195     gchar *label = g_strdup_printf( "%s:", priv->input_label );
196         user_string_label = gtk_label_new ( label );
197         widgets->user_string = gtk_entry_new ( );
198
199     gchar *last_str = get_last_user_string ( widgets );
200     if ( last_str )
201         gtk_entry_set_text( GTK_ENTRY( widgets->user_string ), last_str );
202
203         // 'ok' when press return in the entry
204         g_signal_connect_swapped (widgets->user_string, "activate", G_CALLBACK(a_dialog_response_accept), dialog);
205
206         /* Packing all widgets */
207         GtkBox *box = GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog)));
208         gtk_box_pack_start ( box, user_string_label, FALSE, FALSE, 5 );
209         gtk_box_pack_start ( box, widgets->user_string, FALSE, FALSE, 5 );
210         gtk_widget_show_all ( dialog );
211         gtk_dialog_set_default_response ( GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT );
212         // NB presently the focus is overridden later on by the acquire.c code.
213         gtk_widget_grab_focus ( widgets->user_string );
214
215     g_free ( label );
216 }
217
218
219
220 static void datasource_get_cmd_string ( gpointer user_data, gchar **cmd, gchar **extra, DownloadMapOptions *options )
221 {
222         datasource_t *data = (datasource_t*) user_data;
223
224         VikWebtool *vwd = VIK_WEBTOOL ( data->self );
225         VikWebtoolDatasourcePrivate *priv = WEBTOOL_DATASOURCE_GET_PRIVATE ( data->self );
226
227         if ( webtool_needs_user_string ( vwd ) ) {
228                 priv->user_string = g_strdup ( gtk_entry_get_text ( GTK_ENTRY ( data->user_string ) ) );
229
230         if ( priv->user_string[0] != '\0' ) {
231             set_last_user_string ( data, priv->user_string );
232         }
233     }
234
235         gchar *url = vik_webtool_get_url ( vwd, data->vw );
236         g_debug ("%s: %s", __FUNCTION__, url );
237
238         *cmd = g_strdup ( url );
239
240         // Only use first section of the file_type string
241         // One can't use values like 'kml -x transform,rte=wpt' in order to do fancy things
242         //  since it won't be in the right order for the overall GPSBabel command
243         // So prevent any potentially dangerous behaviour
244         gchar **parts = NULL;
245         if ( priv->file_type )
246                 parts = g_strsplit ( priv->file_type, " ", 0);
247         if ( parts )
248                 *extra = g_strdup ( parts[0] );
249         else
250                 *extra = NULL;
251         g_strfreev ( parts );
252
253         options = NULL;
254 }
255
256 static gboolean datasource_process ( VikTrwLayer *vtl, const gchar *cmd, const gchar *extra, BabelStatusFunc status_cb, acq_dialog_widgets_t *adw, DownloadMapOptions *options )
257 {
258         datasource_t *data = (datasource_t *)adw->user_data;
259         VikWebtoolDatasourcePrivate *priv = WEBTOOL_DATASOURCE_GET_PRIVATE ( data->self );
260         // Dependent on the ExtTool / what extra has been set to...
261         // When extra is NULL - then it interprets results as a GPX
262         gboolean result = a_babel_convert_from_url_filter ( vtl, cmd, extra, priv->babel_filter_args, status_cb, adw, options);
263         return result;
264 }
265
266 static void cleanup ( gpointer data )
267 {
268         g_free ( data );
269 }
270
271 static void webtool_datasource_open ( VikExtTool *self, VikWindow *vw )
272 {
273         gboolean search = webtool_needs_user_string ( VIK_WEBTOOL ( self ) );
274
275         // Use VikDataSourceInterface to give thready goodness controls of downloading stuff (i.e. can cancel the request)
276
277         // Can now create a 'VikDataSourceInterface' on the fly...
278         VikDataSourceInterface *vik_datasource_interface = g_malloc(sizeof(VikDataSourceInterface));
279
280         // An 'easy' way of assigning values
281         VikDataSourceInterface data = {
282                 vik_ext_tool_get_label (self),
283                 vik_ext_tool_get_label (self),
284                 VIK_DATASOURCE_ADDTOLAYER,
285                 VIK_DATASOURCE_INPUTTYPE_NONE,
286                 FALSE, // Maintain current view - rather than setting it to the acquired points
287                 TRUE,
288                 TRUE,
289                 (VikDataSourceInitFunc)               datasource_init,
290                 (VikDataSourceCheckExistenceFunc)     NULL,
291                 (VikDataSourceAddSetupWidgetsFunc)    (search ? datasource_add_setup_widgets : NULL),
292                 (VikDataSourceGetCmdStringFunc)       datasource_get_cmd_string,
293                 (VikDataSourceProcessFunc)            datasource_process,
294                 (VikDataSourceProgressFunc)           NULL,
295                 (VikDataSourceAddProgressWidgetsFunc) NULL,
296                 (VikDataSourceCleanupFunc)            cleanup,
297                 (VikDataSourceOffFunc)                NULL,
298                 NULL,
299                 0,
300                 NULL,
301                 NULL,
302                 0
303         };
304         memcpy ( vik_datasource_interface, &data, sizeof(VikDataSourceInterface) );
305
306         a_acquire ( vw, vik_window_layers_panel(vw), vik_window_viewport (vw), data.mode, vik_datasource_interface, self, cleanup );
307 }
308
309 static void vik_webtool_datasource_class_init ( VikWebtoolDatasourceClass *klass )
310 {
311         GObjectClass *gobject_class;
312         VikWebtoolClass *base_class;
313         GParamSpec *pspec;
314
315         gobject_class = G_OBJECT_CLASS (klass);
316
317         gobject_class->finalize = webtool_datasource_finalize;
318         gobject_class->set_property = webtool_datasource_set_property;
319         gobject_class->get_property = webtool_datasource_get_property;
320
321         pspec = g_param_spec_string ("url",
322                                      "Template URL",
323                                      "Set the template URL",
324                                      VIKING_URL /* default value */,
325                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
326         g_object_class_install_property (gobject_class,
327                                          PROP_URL,
328                                          pspec);
329
330         pspec = g_param_spec_string ("url_format_code",
331                                      "Template URL Format Code",
332                                      "Set the template URL format code",
333                                      "LRBT", // default value
334                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
335         g_object_class_install_property (gobject_class,
336                                          PROP_URL_FORMAT_CODE,
337                                          pspec);
338
339         pspec = g_param_spec_string ("file_type",
340                                      "The file type expected",
341                                      "Set the file type",
342                                      NULL, // default value ~ equates to internal GPX reading
343                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
344         g_object_class_install_property (gobject_class,
345                                          PROP_FILE_TYPE,
346                                          pspec);
347
348         pspec = g_param_spec_string ("babel_filter_args",
349                                      "The command line filter options to pass to gpsbabel",
350                                      "Set the command line filter options for gpsbabel",
351                                      NULL, // default value 
352                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
353         g_object_class_install_property (gobject_class,
354                                          PROP_BABEL_FILTER_ARGS,
355                                          pspec);
356
357         pspec = g_param_spec_string ("input_label",
358                                      "The label for the user input box if input is required.",
359                                      "Set the label to be shown next to the user input box if an input term is required",
360                                      _("Search Term"),
361                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
362         g_object_class_install_property (gobject_class,
363                                          PROP_INPUT_LABEL,
364                                          pspec);
365
366         parent_class = g_type_class_peek_parent (klass);
367
368         base_class = VIK_WEBTOOL_CLASS ( klass );
369         base_class->get_url = webtool_datasource_get_url;
370
371         // Override default open function here:
372         VikExtToolClass *ext_tool_class = VIK_EXT_TOOL_CLASS ( klass );
373         ext_tool_class->open = webtool_datasource_open;
374
375         g_type_class_add_private (klass, sizeof (VikWebtoolDatasourcePrivate));
376 }
377
378 VikWebtoolDatasource *vik_webtool_datasource_new ()
379 {
380         return VIK_WEBTOOL_DATASOURCE ( g_object_new ( VIK_WEBTOOL_DATASOURCE_TYPE, NULL ) );
381 }
382
383 VikWebtoolDatasource *vik_webtool_datasource_new_with_members ( const gchar *label,
384                                                                 const gchar *url,
385                                                                 const gchar *url_format_code,
386                                                                 const gchar *file_type,
387                                                                 const gchar *babel_filter_args,
388                                                                 const gchar *input_label )
389 {
390         VikWebtoolDatasource *result = VIK_WEBTOOL_DATASOURCE ( g_object_new ( VIK_WEBTOOL_DATASOURCE_TYPE,
391                                                                 "label", label,
392                                                                 "url", url,
393                                                                 "url_format_code", url_format_code,
394                                                                 "file_type", file_type,
395                                                                 "babel_filter_args", babel_filter_args,
396                                                             "input_label", input_label,
397                                                                 NULL ) );
398
399         return result;
400 }
401
402 static void vik_webtool_datasource_init ( VikWebtoolDatasource *self )
403 {
404         VikWebtoolDatasourcePrivate *priv = WEBTOOL_DATASOURCE_GET_PRIVATE (self);
405         priv->url = NULL;
406         priv->url_format_code = NULL;
407         priv->file_type = NULL;
408         priv->babel_filter_args = NULL;
409     priv->input_label = NULL;
410         priv->user_string = NULL;
411 }
412
413 static void webtool_datasource_finalize ( GObject *gob )
414 {
415         VikWebtoolDatasourcePrivate *priv = WEBTOOL_DATASOURCE_GET_PRIVATE ( gob );
416         g_free ( priv->url ); priv->url = NULL;
417         g_free ( priv->url_format_code ); priv->url_format_code = NULL;
418         g_free ( priv->file_type ); priv->file_type = NULL;
419         g_free ( priv->babel_filter_args ); priv->babel_filter_args = NULL;
420         g_free ( priv->input_label ); priv->input_label = NULL;
421         g_free ( priv->user_string); priv->user_string = NULL;
422         G_OBJECT_CLASS(parent_class)->finalize(gob);
423 }
424
425 #define MAX_NUMBER_CODES 7
426
427 /**
428  * Calculate individual elements (similarly to the VikWebtool Bounds & Center) for *all* potential values
429  * Then only values specified by the URL format are used in parameterizing the URL
430  */
431 static gchar *webtool_datasource_get_url ( VikWebtool *self, VikWindow *vw )
432 {
433         VikWebtoolDatasourcePrivate *priv = WEBTOOL_DATASOURCE_GET_PRIVATE ( self );
434         VikViewport *viewport = vik_window_viewport ( vw );
435
436         // Get top left and bottom right lat/lon pairs from the viewport
437         gdouble min_lat, max_lat, min_lon, max_lon;
438         gchar sminlon[G_ASCII_DTOSTR_BUF_SIZE];
439         gchar smaxlon[G_ASCII_DTOSTR_BUF_SIZE];
440         gchar sminlat[G_ASCII_DTOSTR_BUF_SIZE];
441         gchar smaxlat[G_ASCII_DTOSTR_BUF_SIZE];
442         vik_viewport_get_min_max_lat_lon ( viewport, &min_lat, &max_lat, &min_lon, &max_lon );
443
444         // Cannot simply use g_strdup_printf and gdouble due to locale.
445         // As we compute an URL, we have to think in C locale.
446         g_ascii_dtostr (sminlon, G_ASCII_DTOSTR_BUF_SIZE, min_lon);
447         g_ascii_dtostr (smaxlon, G_ASCII_DTOSTR_BUF_SIZE, max_lon);
448         g_ascii_dtostr (sminlat, G_ASCII_DTOSTR_BUF_SIZE, min_lat);
449         g_ascii_dtostr (smaxlat, G_ASCII_DTOSTR_BUF_SIZE, max_lat);
450
451         // Center values
452         const VikCoord *coord = vik_viewport_get_center ( viewport );
453         struct LatLon ll;
454         vik_coord_to_latlon ( coord, &ll );
455
456         gchar scenterlat[G_ASCII_DTOSTR_BUF_SIZE];
457         gchar scenterlon[G_ASCII_DTOSTR_BUF_SIZE];
458         g_ascii_dtostr (scenterlat, G_ASCII_DTOSTR_BUF_SIZE, ll.lat);
459         g_ascii_dtostr (scenterlon, G_ASCII_DTOSTR_BUF_SIZE, ll.lon);
460
461         guint8 zoom = 17; // A zoomed in default
462         // zoom - ideally x & y factors need to be the same otherwise use the default
463         if ( vik_viewport_get_xmpp ( viewport ) == vik_viewport_get_ympp ( viewport ) )
464                 zoom = map_utils_mpp_to_zoom_level ( vik_viewport_get_zoom ( viewport ) );
465
466         gchar szoom[G_ASCII_DTOSTR_BUF_SIZE];
467         g_snprintf ( szoom, G_ASCII_DTOSTR_BUF_SIZE, "%d", zoom );
468
469         gint len = 0;
470         if ( priv->url_format_code )
471                 len = strlen ( priv->url_format_code );
472         if ( len > MAX_NUMBER_CODES )
473                 len = MAX_NUMBER_CODES;
474
475         gchar* values[MAX_NUMBER_CODES];
476         int i;
477         for ( i = 0; i < MAX_NUMBER_CODES; i++ ) {
478                 values[i] = '\0';
479         }
480
481         for ( i = 0; i < len; i++ ) {
482                 switch ( g_ascii_toupper ( priv->url_format_code[i] ) ) {
483                 case 'L': values[i] = g_strdup ( sminlon ); break;
484                 case 'R': values[i] = g_strdup ( smaxlon ); break;
485                 case 'B': values[i] = g_strdup ( sminlat ); break;
486                 case 'T': values[i] = g_strdup ( smaxlat ); break;
487                 case 'A': values[i] = g_strdup ( scenterlat ); break;
488                 case 'O': values[i] = g_strdup ( scenterlon ); break;
489                 case 'Z': values[i] = g_strdup ( szoom ); break;
490                 case 'S': values[i] = g_strdup ( priv->user_string ); break;
491                 default: break;
492                 }
493         }
494
495         gchar *url = g_strdup_printf ( priv->url, values[0], values[1], values[2], values[3], values[4], values[5], values[6] );
496
497         for ( i = 0; i < MAX_NUMBER_CODES; i++ ) {
498                 if ( values[i] != '\0' )
499                         g_free ( values[i] );
500         }
501         
502         return url;
503 }
504
505 // NB Only works for ascii strings
506 char* strcasestr2(const char *dst, const char *src)
507 {
508         if ( !dst || !src )
509                 return NULL;
510
511         if(src[0] == '\0')
512                 return (char*)dst;
513
514         int len = strlen(src) - 1;
515         char sc = tolower(src[0]);
516         for(char dc = *dst; (dc = *dst); dst++) {
517                 dc = tolower(dc);
518                 if(sc == dc && (len == 0 || !strncasecmp(dst+1, src+1, len)))
519                         return (char*)dst;
520         }
521
522         return NULL;
523 }
524
525 /**
526  * Returns true if the URL format contains 'S' -- that is, a search term entry
527  * box needs to be displayed
528  */
529 static gboolean webtool_needs_user_string ( VikWebtool *self )
530 {
531         VikWebtoolDatasourcePrivate *priv = WEBTOOL_DATASOURCE_GET_PRIVATE ( self );
532         // For some reason (my) Windows build gets built with -D_GNU_SOURCE
533 #if (_GNU_SOURCE && !WINDOWS)
534         return (strcasestr(priv->url_format_code, "S") != NULL);
535 #else
536         return (strcasestr2(priv->url_format_code, "S") != NULL);
537 #endif
538 }