]> git.street.me.uk Git - andy/viking.git/blob - src/vikwindow.c
Fix background thread statusbar update instability.
[andy/viking.git] / src / vikwindow.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) 2005-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
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27
28 #include "viking.h"
29 #include "background.h"
30 #include "acquire.h"
31 #include "datasources.h"
32 #include "vikgoto.h"
33 #include "dems.h"
34 #include "mapcache.h"
35 #include "print.h"
36 #include "preferences.h"
37 #include "viklayer_defaults.h"
38 #include "icons/icons.h"
39 #include "vikexttools.h"
40 #include "vikexttool_datasources.h"
41 #include "garminsymbols.h"
42 #include "vikmapslayer.h"
43 #include "geonamessearch.h"
44
45 #ifdef HAVE_STDLIB_H
46 #include <stdlib.h>
47 #endif
48 #ifdef HAVE_MATH_H
49 #include <math.h>
50 #endif
51 #ifdef HAVE_STRING_H
52 #include <string.h>
53 #endif
54 #include <ctype.h>
55 #include <glib.h>
56 #include <glib/gstdio.h>
57 #include <glib/gprintf.h>
58 #include <glib/gi18n.h>
59 #include <gio/gio.h>
60 #include <gdk/gdkkeysyms.h>
61
62 // This seems rather arbitary, quite large and pointless
63 //  I mean, if you have a thousand windows open;
64 //   why not be allowed to open a thousand more...
65 #define MAX_WINDOWS 1024
66 static guint window_count = 0;
67 static GSList *window_list = NULL;
68
69 #define VIKING_WINDOW_WIDTH      1000
70 #define VIKING_WINDOW_HEIGHT     800
71 #define DRAW_IMAGE_DEFAULT_WIDTH 1280
72 #define DRAW_IMAGE_DEFAULT_HEIGHT 1024
73 #define DRAW_IMAGE_DEFAULT_SAVE_AS_PNG TRUE
74
75 static void window_finalize ( GObject *gob );
76 static GObjectClass *parent_class;
77
78 static void window_set_filename ( VikWindow *vw, const gchar *filename );
79 static const gchar *window_get_filename ( VikWindow *vw );
80
81 static VikWindow *window_new ();
82
83 static void draw_update ( VikWindow *vw );
84
85 static void newwindow_cb ( GtkAction *a, VikWindow *vw );
86
87 // Signals
88 static void open_window ( VikWindow *vw, GSList *files );
89 static void destroy_window ( GtkWidget *widget,
90                              gpointer   data );
91
92 /* Drawing & stuff */
93
94 static gboolean delete_event( VikWindow *vw );
95
96 static gboolean key_press_event( VikWindow *vw, GdkEventKey *event, gpointer data );
97
98 static void window_configure_event ( VikWindow *vw );
99 static void draw_sync ( VikWindow *vw );
100 static void draw_redraw ( VikWindow *vw );
101 static void draw_scroll  ( VikWindow *vw, GdkEventScroll *event );
102 static void draw_click  ( VikWindow *vw, GdkEventButton *event );
103 static void draw_release ( VikWindow *vw, GdkEventButton *event );
104 static void draw_mouse_motion ( VikWindow *vw, GdkEventMotion *event );
105 static void draw_zoom_cb ( GtkAction *a, VikWindow *vw );
106 static void draw_goto_cb ( GtkAction *a, VikWindow *vw );
107 static void draw_refresh_cb ( GtkAction *a, VikWindow *vw );
108
109 static void draw_status ( VikWindow *vw );
110
111 /* End Drawing Functions */
112
113 static void menu_addlayer_cb ( GtkAction *a, VikWindow *vw );
114 static void menu_properties_cb ( GtkAction *a, VikWindow *vw );
115 static void menu_delete_layer_cb ( GtkAction *a, VikWindow *vw );
116
117 /* tool management */
118 typedef struct {
119   VikToolInterface ti;
120   gpointer state;
121   gint layer_type;
122 } toolbox_tool_t;
123 #define TOOL_LAYER_TYPE_NONE -1
124
125 typedef struct {
126   int                   active_tool;
127   int                   n_tools;
128   toolbox_tool_t        *tools;
129   VikWindow *vw;
130 } toolbox_tools_t;
131
132 static void menu_tool_cb ( GtkAction *old, GtkAction *a, VikWindow *vw );
133 static toolbox_tools_t* toolbox_create(VikWindow *vw);
134 static void toolbox_add_tool(toolbox_tools_t *vt, VikToolInterface *vti, gint layer_type );
135 static int toolbox_get_tool(toolbox_tools_t *vt, const gchar *tool_name);
136 static void toolbox_activate(toolbox_tools_t *vt, const gchar *tool_name);
137 static const GdkCursor *toolbox_get_cursor(toolbox_tools_t *vt, const gchar *tool_name);
138 static void toolbox_click (toolbox_tools_t *vt, GdkEventButton *event);
139 static void toolbox_move (toolbox_tools_t *vt, GdkEventMotion *event);
140 static void toolbox_release (toolbox_tools_t *vt, GdkEventButton *event);
141
142
143 /* ui creation */
144 static void window_create_ui( VikWindow *window );
145 static void register_vik_icons (GtkIconFactory *icon_factory);
146
147 /* i/o */
148 static void load_file ( GtkAction *a, VikWindow *vw );
149 static gboolean save_file_as ( GtkAction *a, VikWindow *vw );
150 static gboolean save_file ( GtkAction *a, VikWindow *vw );
151 static gboolean save_file_and_exit ( GtkAction *a, VikWindow *vw );
152 static gboolean window_save ( VikWindow *vw );
153
154 struct _VikWindow {
155   GtkWindow gtkwindow;
156   VikViewport *viking_vvp;
157   VikLayersPanel *viking_vlp;
158   VikStatusbar *viking_vs;
159
160   GtkToolbar *toolbar;
161
162   GdkCursor *busy_cursor;
163   GdkCursor *viewport_cursor; // only a reference
164
165   /* tool management state */
166   guint current_tool;
167   toolbox_tools_t *vt;
168   guint16 tool_layer_id;
169   guint16 tool_tool_id;
170
171   GtkActionGroup *action_group;
172
173   gboolean pan_move;
174   gint pan_x, pan_y;
175
176   guint draw_image_width, draw_image_height;
177   gboolean draw_image_save_as_png;
178
179   gchar *filename;
180   gboolean modified;
181
182   GtkWidget *open_dia, *save_dia;
183   GtkWidget *save_img_dia, *save_img_dir_dia;
184
185   gboolean only_updating_coord_mode_ui; /* hack for a bug in GTK */
186   GtkUIManager *uim;
187
188   GThread  *thread;
189   /* half-drawn update */
190   VikLayer *trigger;
191   VikCoord trigger_center;
192
193   /* Store at this level for highlighted selection drawing since it applies to the viewport and the layers panel */
194   /* Only one of these items can be selected at the same time */
195   gpointer selected_vtl; /* notionally VikTrwLayer */
196   GHashTable *selected_tracks;
197   gpointer selected_track; /* notionally VikTrack */
198   GHashTable *selected_waypoints;
199   gpointer selected_waypoint; /* notionally VikWaypoint */
200   /* only use for individual track or waypoint */
201   /* For track(s) & waypoint(s) it is the layer they are in - this helps refering to the individual item easier */
202   gpointer containing_vtl; /* notionally VikTrwLayer */
203 };
204
205 enum {
206  TOOL_PAN = 0,
207  TOOL_ZOOM,
208  TOOL_RULER,
209  TOOL_SELECT,
210  TOOL_LAYER,
211  NUMBER_OF_TOOLS
212 };
213
214 enum {
215   VW_NEWWINDOW_SIGNAL,
216   VW_OPENWINDOW_SIGNAL,
217   VW_LAST_SIGNAL
218 };
219
220 static guint window_signals[VW_LAST_SIGNAL] = { 0 };
221
222 // TODO get rid of this as this is unnecessary duplication...
223 static gchar *tool_names[NUMBER_OF_TOOLS] = { N_("Pan"), N_("Zoom"), N_("Ruler"), N_("Select") };
224
225 G_DEFINE_TYPE (VikWindow, vik_window, GTK_TYPE_WINDOW)
226
227 VikViewport * vik_window_viewport(VikWindow *vw)
228 {
229   return(vw->viking_vvp);
230 }
231
232 VikLayersPanel * vik_window_layers_panel(VikWindow *vw)
233 {
234   return(vw->viking_vlp);
235 }
236
237 /**
238  *  Returns the statusbar for the window
239  */
240 VikStatusbar * vik_window_get_statusbar ( VikWindow *vw )
241 {
242   return vw->viking_vs;
243 }
244
245 typedef struct {
246   VikStatusbar *vs;
247   vik_statusbar_type_t vs_type;
248   gchar* message; // Always make a copy of this data
249 } statusbar_idle_data;
250
251 /**
252  * For the actual statusbar update!
253  */
254 static gboolean statusbar_idle_update ( statusbar_idle_data *sid )
255 {
256   vik_statusbar_set_message ( sid->vs, sid->vs_type, sid->message );
257   g_free ( sid->message );
258   g_free ( sid );
259   return FALSE;
260 }
261
262 /**
263  * vik_window_statusbar_update:
264  * @vw:      The main window in which the statusbar will be updated.
265  * @message: The string to be displayed. This is copied.
266  * @vs_type: The part of the statusbar to be updated.
267  *
268  * This updates any part of the statusbar with the new string.
269  * It handles calling from the main thread or any background thread
270  * ATM this mostly used from background threads - as from the main thread
271  *  one may use the vik_statusbar_set_message() directly.
272  */
273 void vik_window_statusbar_update ( VikWindow *vw, const gchar* message, vik_statusbar_type_t vs_type )
274 {
275   statusbar_idle_data *sid = g_malloc ( sizeof (statusbar_idle_data) );
276   sid->vs = vw->viking_vs;
277   sid->vs_type = vs_type;
278   sid->message = g_strdup ( message );
279
280   if ( g_thread_self() == vik_window_get_thread ( vw ) ) {
281     g_idle_add ( (GSourceFunc) statusbar_idle_update, sid );
282   }
283   else {
284     // From a background thread
285     gdk_threads_add_idle ( (GSourceFunc) statusbar_idle_update, sid );
286   }
287 }
288
289 // Actual signal handlers
290 static void destroy_window ( GtkWidget *widget,
291                              gpointer   data )
292 {
293     if ( ! --window_count )
294       gtk_main_quit ();
295 }
296
297 VikWindow *vik_window_new_window ()
298 {
299   if ( window_count < MAX_WINDOWS )
300   {
301     VikWindow *vw = window_new ();
302
303     g_signal_connect (G_OBJECT (vw), "destroy",
304                       G_CALLBACK (destroy_window), NULL);
305     g_signal_connect (G_OBJECT (vw), "newwindow",
306                       G_CALLBACK (vik_window_new_window), NULL);
307     g_signal_connect (G_OBJECT (vw), "openwindow",
308                       G_CALLBACK (open_window), NULL);
309
310     gtk_widget_show_all ( GTK_WIDGET(vw) );
311
312     window_count++;
313
314     return vw;
315   }
316   return NULL;
317 }
318
319 static void open_window ( VikWindow *vw, GSList *files )
320 {
321   gboolean change_fn = (g_slist_length(files) == 1); /* only change fn if one file */
322   GSList *cur_file = files;
323   while ( cur_file ) {
324     // Only open a new window if a viking file
325     gchar *file_name = cur_file->data;
326     if (vw != NULL && check_file_magic_vik ( file_name ) ) {
327       VikWindow *newvw = vik_window_new_window ();
328       if (newvw)
329         vik_window_open_file ( newvw, file_name, TRUE );
330     }
331     else {
332       vik_window_open_file ( vw, file_name, change_fn );
333     }
334     g_free (file_name);
335     cur_file = g_slist_next (cur_file);
336   }
337   g_slist_free (files);
338 }
339 // End signals
340
341 void vik_window_selected_layer(VikWindow *vw, VikLayer *vl)
342 {
343   int i, j, tool_count;
344   VikLayerInterface *layer_interface;
345
346   if (!vw->action_group) return;
347
348   for (i=0; i<VIK_LAYER_NUM_TYPES; i++) {
349     GtkAction *action;
350     layer_interface = vik_layer_get_interface(i);
351     tool_count = layer_interface->tools_count;
352
353     for (j = 0; j < tool_count; j++) {
354       action = gtk_action_group_get_action(vw->action_group,
355                                            layer_interface->tools[j].radioActionEntry.name);
356       g_object_set(action, "sensitive", i == vl->type, NULL);
357     }
358   }
359 }
360
361 static void window_finalize ( GObject *gob )
362 {
363   VikWindow *vw = VIK_WINDOW(gob);
364   g_return_if_fail ( vw != NULL );
365
366   a_background_remove_window ( vw );
367
368   window_list = g_slist_remove ( window_list, vw );
369
370   gdk_cursor_unref ( vw->busy_cursor );
371
372   G_OBJECT_CLASS(parent_class)->finalize(gob);
373 }
374
375
376 static void vik_window_class_init ( VikWindowClass *klass )
377 {
378   /* destructor */
379   GObjectClass *object_class;
380
381   window_signals[VW_NEWWINDOW_SIGNAL] = g_signal_new ( "newwindow", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (VikWindowClass, newwindow), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
382   window_signals[VW_OPENWINDOW_SIGNAL] = g_signal_new ( "openwindow", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (VikWindowClass, openwindow), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
383
384   object_class = G_OBJECT_CLASS (klass);
385
386   object_class->finalize = window_finalize;
387
388   parent_class = g_type_class_peek_parent (klass);
389
390 }
391
392 static void zoom_changed (GtkMenuShell *menushell,
393               gpointer      user_data)
394 {
395   VikWindow *vw = VIK_WINDOW (user_data);
396
397   GtkWidget *aw = gtk_menu_get_active ( GTK_MENU (menushell) );
398   gint active = GPOINTER_TO_INT(g_object_get_data ( G_OBJECT (aw), "position" ));
399
400   gdouble zoom_request = pow (2, active-2 );
401
402   // But has it really changed?
403   gdouble current_zoom = vik_viewport_get_zoom ( vw->viking_vvp );
404   if ( current_zoom != 0.0 && zoom_request != current_zoom ) {
405     vik_viewport_set_zoom ( vw->viking_vvp, zoom_request );
406     // Force drawing update
407     draw_update ( vw );
408   }
409 }
410
411 /**
412  * @mpp: The initial zoom level
413  */
414 static GtkWidget *create_zoom_menu_all_levels ( gdouble mpp )
415 {
416   GtkWidget *menu = gtk_menu_new ();
417   char *itemLabels[] = { "0.25", "0.5", "1", "2", "4", "8", "16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192", "16384", "32768" };
418
419   int i;
420   for (i = 0 ; i < G_N_ELEMENTS(itemLabels) ; i++)
421     {
422       GtkWidget *item = gtk_menu_item_new_with_label (itemLabels[i]);
423       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
424       gtk_widget_show (item);
425       g_object_set_data (G_OBJECT (item), "position", GINT_TO_POINTER(i));
426     }
427
428   gint active = 2 + round ( log (mpp) / log (2) );
429   // Ensure value derived from mpp is in bounds of the menu
430   if ( active >= G_N_ELEMENTS(itemLabels) )
431     active = G_N_ELEMENTS(itemLabels) - 1;
432   if ( active < 0 )
433     active = 0;
434   gtk_menu_set_active ( GTK_MENU(menu), active );
435
436   return menu;
437 }
438
439 static GtkWidget *create_zoom_combo_all_levels ()
440 {
441   GtkWidget *combo = vik_combo_box_text_new();
442   vik_combo_box_text_append ( combo, "0.25");
443   vik_combo_box_text_append ( combo, "0.5");
444   vik_combo_box_text_append ( combo, "1");
445   vik_combo_box_text_append ( combo, "2");
446   vik_combo_box_text_append ( combo, "4");
447   vik_combo_box_text_append ( combo, "8");
448   vik_combo_box_text_append ( combo, "16");
449   vik_combo_box_text_append ( combo, "32");
450   vik_combo_box_text_append ( combo, "64");
451   vik_combo_box_text_append ( combo, "128");
452   vik_combo_box_text_append ( combo, "256");
453   vik_combo_box_text_append ( combo, "512");
454   vik_combo_box_text_append ( combo, "1024");
455   vik_combo_box_text_append ( combo, "2048");
456   vik_combo_box_text_append ( combo, "4096");
457   vik_combo_box_text_append ( combo, "8192");
458   vik_combo_box_text_append ( combo, "16384");
459   vik_combo_box_text_append ( combo, "32768");
460   /* Create tooltip */
461   gtk_widget_set_tooltip_text (combo, _("Select zoom level"));
462   return combo;
463 }
464
465 static gint zoom_popup_handler (GtkWidget *widget)
466 {
467   GtkMenu *menu;
468
469   g_return_val_if_fail (widget != NULL, FALSE);
470   g_return_val_if_fail (GTK_IS_MENU (widget), FALSE);
471
472   /* The "widget" is the menu that was supplied when
473    * g_signal_connect_swapped() was called.
474    */
475   menu = GTK_MENU (widget);
476
477   gtk_menu_popup (menu, NULL, NULL, NULL, NULL,
478                   1, gtk_get_current_event_time());
479   return TRUE;
480 }
481
482 static void vik_window_init ( VikWindow *vw )
483 {
484   GtkWidget *main_vbox;
485   GtkWidget *hpaned;
486
487   vw->action_group = NULL;
488
489   vw->viking_vvp = vik_viewport_new();
490   vw->viking_vlp = vik_layers_panel_new();
491   vik_layers_panel_set_viewport ( vw->viking_vlp, vw->viking_vvp );
492   vw->viking_vs = vik_statusbar_new();
493
494   vw->vt = toolbox_create(vw);
495   window_create_ui(vw);
496   window_set_filename (vw, NULL);
497   vw->toolbar = GTK_TOOLBAR(gtk_ui_manager_get_widget (vw->uim, "/MainToolbar"));
498
499   vw->busy_cursor = gdk_cursor_new ( GDK_WATCH );
500
501   // Set the default tool
502   gtk_action_activate ( gtk_action_group_get_action ( vw->action_group, "Pan" ) );
503
504   vw->filename = NULL;
505
506   vw->modified = FALSE;
507   vw->only_updating_coord_mode_ui = FALSE;
508  
509   vw->pan_move = FALSE; 
510   vw->pan_x = vw->pan_y = -1;
511   vw->draw_image_width = DRAW_IMAGE_DEFAULT_WIDTH;
512   vw->draw_image_height = DRAW_IMAGE_DEFAULT_HEIGHT;
513   vw->draw_image_save_as_png = DRAW_IMAGE_DEFAULT_SAVE_AS_PNG;
514
515   main_vbox = gtk_vbox_new(FALSE, 1);
516   gtk_container_add (GTK_CONTAINER (vw), main_vbox);
517
518   gtk_box_pack_start (GTK_BOX(main_vbox), gtk_ui_manager_get_widget (vw->uim, "/MainMenu"), FALSE, TRUE, 0);
519   gtk_box_pack_start (GTK_BOX(main_vbox), GTK_WIDGET(vw->toolbar), FALSE, TRUE, 0);
520   gtk_toolbar_set_icon_size (vw->toolbar, GTK_ICON_SIZE_SMALL_TOOLBAR);
521   gtk_toolbar_set_style (vw->toolbar, GTK_TOOLBAR_ICONS);
522
523   vik_ext_tool_datasources_add_menu_items ( vw, vw->uim );
524
525   GtkWidget * zoom_levels = gtk_ui_manager_get_widget (vw->uim, "/MainMenu/View/SetZoom");
526   GtkWidget * zoom_levels_menu = create_zoom_menu_all_levels ( vik_viewport_get_zoom(vw->viking_vvp) );
527   gtk_menu_item_set_submenu (GTK_MENU_ITEM (zoom_levels), zoom_levels_menu);
528   g_signal_connect ( G_OBJECT(zoom_levels_menu), "selection-done", G_CALLBACK(zoom_changed), vw);
529   g_signal_connect_swapped ( G_OBJECT(vw->viking_vs), "clicked", G_CALLBACK(zoom_popup_handler), zoom_levels_menu );
530
531   g_signal_connect (G_OBJECT (vw), "delete_event", G_CALLBACK (delete_event), NULL);
532
533   g_signal_connect_swapped (G_OBJECT(vw->viking_vvp), "expose_event", G_CALLBACK(draw_sync), vw);
534   g_signal_connect_swapped (G_OBJECT(vw->viking_vvp), "configure_event", G_CALLBACK(window_configure_event), vw);
535   gtk_widget_add_events ( GTK_WIDGET(vw->viking_vvp), GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK );
536   g_signal_connect_swapped (G_OBJECT(vw->viking_vvp), "scroll_event", G_CALLBACK(draw_scroll), vw);
537   g_signal_connect_swapped (G_OBJECT(vw->viking_vvp), "button_press_event", G_CALLBACK(draw_click), vw);
538   g_signal_connect_swapped (G_OBJECT(vw->viking_vvp), "button_release_event", G_CALLBACK(draw_release), vw);
539   g_signal_connect_swapped (G_OBJECT(vw->viking_vvp), "motion_notify_event", G_CALLBACK(draw_mouse_motion), vw);
540   g_signal_connect_swapped (G_OBJECT(vw->viking_vlp), "update", G_CALLBACK(draw_update), vw);
541
542   // Allow key presses to be processed anywhere
543   g_signal_connect_swapped (G_OBJECT (vw), "key_press_event", G_CALLBACK (key_press_event), vw);
544
545   gtk_window_set_default_size ( GTK_WINDOW(vw), VIKING_WINDOW_WIDTH, VIKING_WINDOW_HEIGHT);
546
547   hpaned = gtk_hpaned_new ();
548   gtk_paned_pack1 ( GTK_PANED(hpaned), GTK_WIDGET (vw->viking_vlp), FALSE, FALSE );
549   gtk_paned_pack2 ( GTK_PANED(hpaned), GTK_WIDGET (vw->viking_vvp), TRUE, TRUE );
550
551   /* This packs the button into the window (a gtk container). */
552   gtk_box_pack_start (GTK_BOX(main_vbox), hpaned, TRUE, TRUE, 0);
553
554   gtk_box_pack_end (GTK_BOX(main_vbox), GTK_WIDGET(vw->viking_vs), FALSE, TRUE, 0);
555
556   a_background_add_window ( vw );
557
558   window_list = g_slist_prepend ( window_list, vw);
559
560   vw->open_dia = NULL;
561   vw->save_dia = NULL;
562   vw->save_img_dia = NULL;
563   vw->save_img_dir_dia = NULL;
564
565   // Store the thread value so comparisons can be made to determine the gdk update method
566   // Hopefully we are storing the main thread value here :)
567   //  [ATM any window initialization is always be performed by the main thread]
568   vw->thread = g_thread_self();
569 }
570
571 static VikWindow *window_new ()
572 {
573   return VIK_WINDOW ( g_object_new ( VIK_WINDOW_TYPE, NULL ) );
574 }
575
576 /**
577  * Update the displayed map
578  *  Only update the top most visible map layer
579  *  ATM this assumes (as per defaults) the top most map has full alpha setting
580  *   such that other other maps even though they may be active will not be seen
581  *  It's more complicated to work out which maps are actually visible due to alpha settings
582  *   and overkill for this simple refresh method.
583  */
584 static void simple_map_update ( VikWindow *vw, gboolean only_new )
585 {
586   // Find the most relevent single map layer to operate on
587   VikLayer *vl = vik_aggregate_layer_get_top_visible_layer_of_type (vik_layers_panel_get_top_layer(vw->viking_vlp), VIK_LAYER_MAPS);
588   if ( vl )
589         vik_maps_layer_download ( VIK_MAPS_LAYER(vl), vw->viking_vvp, only_new );
590 }
591
592 /**
593  * This is the global key press handler
594  *  Global shortcuts are available at any time and hence are not restricted to when a certain tool is enabled
595  */
596 static gboolean key_press_event( VikWindow *vw, GdkEventKey *event, gpointer data )
597 {
598   // The keys handled here are not in the menuing system for a couple of reasons:
599   //  . Keeps the menu size compact (alebit at expense of discoverably)
600   //  . Allows differing key bindings to perform the same actions
601
602   // First decide if key events are related to the maps layer
603   gboolean map_download = FALSE;
604   gboolean map_download_only_new = TRUE; // Only new or reload
605
606   GdkModifierType modifiers = gtk_accelerator_get_default_mod_mask();
607
608   // Standard 'Refresh' keys: F5 or Ctrl+r
609   // Note 'F5' is actually handled via draw_refresh_cb() later on
610   //  (not 'R' it's 'r' notice the case difference!!)
611   if ( event->keyval == GDK_r && (event->state & modifiers) == GDK_CONTROL_MASK ) {
612         map_download = TRUE;
613         map_download_only_new = TRUE;
614   }
615   // Full cache reload with Ctrl+F5 or Ctrl+Shift+r [This is not in the menu system]
616   // Note the use of uppercase R here since shift key has been pressed
617   else if ( (event->keyval == GDK_F5 && (event->state & modifiers) == GDK_CONTROL_MASK ) ||
618            ( event->keyval == GDK_R && (event->state & modifiers) == (GDK_CONTROL_MASK + GDK_SHIFT_MASK) ) ) {
619         map_download = TRUE;
620         map_download_only_new = FALSE;
621   }
622
623   if ( map_download ) {
624     simple_map_update ( vw, map_download_only_new );
625   }
626
627   VikLayer *vl = vik_layers_panel_get_selected ( vw->viking_vlp );
628   if (vl && vw->vt->active_tool != -1 && vw->vt->tools[vw->vt->active_tool].ti.key_press ) {
629     gint ltype = vw->vt->tools[vw->vt->active_tool].layer_type;
630     if ( vl && ltype == vl->type )
631       return vw->vt->tools[vw->vt->active_tool].ti.key_press(vl, event, vw->vt->tools[vw->vt->active_tool].state);
632   }
633
634   // Ensure called only on window tools (i.e. not on any of the Layer tools since the layer is NULL)
635   if ( vw->current_tool < TOOL_LAYER ) {
636     // No layer - but enable window tool keypress processing - these should be able to handle a NULL layer
637     if ( vw->vt->tools[vw->vt->active_tool].ti.key_press ) {
638       return vw->vt->tools[vw->vt->active_tool].ti.key_press ( vl, event, vw->vt->tools[vw->vt->active_tool].state );
639     }
640   }
641
642   /* Restore Main Menu via Escape key if the user has hidden it */
643   /* This key is more likely to be used as they may not remember the function key */
644   if ( event->keyval == GDK_Escape ) {
645     GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ViewMainMenu" );
646     if ( check_box ) {
647       gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box) );
648       if ( !state ) {
649         gtk_widget_show ( gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu" ) );
650         gtk_check_menu_item_set_active ( GTK_CHECK_MENU_ITEM(check_box), TRUE );
651         return TRUE; /* handled keypress */
652       }
653     }
654   }
655
656   return FALSE; /* don't handle the keypress */
657 }
658
659 static gboolean delete_event( VikWindow *vw )
660 {
661 #ifdef VIKING_PROMPT_IF_MODIFIED
662   if ( vw->modified )
663 #else
664   if (0)
665 #endif
666   {
667     GtkDialog *dia;
668     dia = GTK_DIALOG ( gtk_message_dialog_new ( GTK_WINDOW(vw), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
669       _("Do you want to save the changes you made to the document \"%s\"?\n"
670         "\n"
671         "Your changes will be lost if you don't save them."),
672       window_get_filename ( vw ) ) );
673     gtk_dialog_add_buttons ( dia, _("Don't Save"), GTK_RESPONSE_NO, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_YES, NULL );
674     switch ( gtk_dialog_run ( dia ) )
675     {
676       case GTK_RESPONSE_NO: gtk_widget_destroy ( GTK_WIDGET(dia) ); return FALSE;
677       case GTK_RESPONSE_CANCEL: gtk_widget_destroy ( GTK_WIDGET(dia) ); return TRUE;
678       default: gtk_widget_destroy ( GTK_WIDGET(dia) ); return ! save_file(NULL, vw);
679     }
680   }
681   return FALSE;
682 }
683
684 /* Drawing stuff */
685 static void newwindow_cb ( GtkAction *a, VikWindow *vw )
686 {
687   g_signal_emit ( G_OBJECT(vw), window_signals[VW_NEWWINDOW_SIGNAL], 0 );
688 }
689
690 static void draw_update ( VikWindow *vw )
691 {
692   draw_redraw (vw);
693   draw_sync (vw);
694 }
695
696 static void draw_sync ( VikWindow *vw )
697 {
698   vik_viewport_sync(vw->viking_vvp);
699   draw_status ( vw );
700 }
701
702 /*
703  * Split the status update, as sometimes only need to update the tool part
704  *  also on initialization the zoom related stuff is not ready to be used
705  */
706 static void draw_status_tool ( VikWindow *vw )
707 {
708   if ( vw->current_tool == TOOL_LAYER )
709     // Use tooltip rather than the internal name as the tooltip is i8n
710     vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_TOOL, vik_layer_get_interface(vw->tool_layer_id)->tools[vw->tool_tool_id].radioActionEntry.tooltip );
711   else
712     vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_TOOL, _(tool_names[vw->current_tool]) );
713 }
714
715 static void draw_status ( VikWindow *vw )
716 {
717   static gchar zoom_level[22];
718   gdouble xmpp = vik_viewport_get_xmpp (vw->viking_vvp);
719   gdouble ympp = vik_viewport_get_ympp(vw->viking_vvp);
720   gchar *unit = vik_viewport_get_coord_mode(vw->viking_vvp) == VIK_COORD_UTM ? _("mpp") : _("pixelfact");
721   if (xmpp != ympp)
722     g_snprintf ( zoom_level, 22, "%.3f/%.3f %s", xmpp, ympp, unit );
723   else
724     if ( (int)xmpp - xmpp < 0.0 )
725       g_snprintf ( zoom_level, 22, "%.3f %s", xmpp, unit );
726     else
727       /* xmpp should be a whole number so don't show useless .000 bit */
728       g_snprintf ( zoom_level, 22, "%d %s", (int)xmpp, unit );
729
730   vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_ZOOM, zoom_level );
731
732   draw_status_tool ( vw );  
733 }
734
735 void vik_window_set_redraw_trigger(VikLayer *vl)
736 {
737   VikWindow *vw = VIK_WINDOW(VIK_GTK_WINDOW_FROM_LAYER(vl));
738   if (NULL != vw)
739     vw->trigger = vl;
740 }
741
742 static void window_configure_event ( VikWindow *vw )
743 {
744   static int first = 1;
745   draw_redraw ( vw );
746   if (first) {
747     // This is a hack to set the cursor corresponding to the first tool
748     // FIXME find the correct way to initialize both tool and its cursor
749     first = 0;
750     vw->viewport_cursor = (GdkCursor *)toolbox_get_cursor(vw->vt, "Pan");
751     /* We set cursor, even if it is NULL: it resets to default */
752     gdk_window_set_cursor ( gtk_widget_get_window(GTK_WIDGET(vw->viking_vvp)), vw->viewport_cursor );
753   }
754 }
755
756 static void draw_redraw ( VikWindow *vw )
757 {
758   VikCoord old_center = vw->trigger_center;
759   vw->trigger_center = *(vik_viewport_get_center(vw->viking_vvp));
760   VikLayer *new_trigger = vw->trigger;
761   vw->trigger = NULL;
762   VikLayer *old_trigger = VIK_LAYER(vik_viewport_get_trigger(vw->viking_vvp));
763
764   if ( ! new_trigger )
765     ; /* do nothing -- have to redraw everything. */
766   else if ( (old_trigger != new_trigger) || !vik_coord_equals(&old_center, &vw->trigger_center) || (new_trigger->type == VIK_LAYER_AGGREGATE) )
767     vik_viewport_set_trigger ( vw->viking_vvp, new_trigger ); /* todo: set to half_drawn mode if new trigger is above old */
768   else
769     vik_viewport_set_half_drawn ( vw->viking_vvp, TRUE );
770
771   /* actually draw */
772   vik_viewport_clear ( vw->viking_vvp);
773   vik_layers_panel_draw_all ( vw->viking_vlp );
774   vik_viewport_draw_scale ( vw->viking_vvp );
775   vik_viewport_draw_copyright ( vw->viking_vvp );
776   vik_viewport_draw_centermark ( vw->viking_vvp );
777   vik_viewport_draw_logo ( vw->viking_vvp );
778
779   vik_viewport_set_half_drawn ( vw->viking_vvp, FALSE ); /* just in case. */
780 }
781
782 gboolean draw_buf_done = TRUE;
783
784 static gboolean draw_buf(gpointer data)
785 {
786   gpointer *pass_along = data;
787   gdk_threads_enter();
788   gdk_draw_drawable (pass_along[0], pass_along[1],
789                      pass_along[2], 0, 0, 0, 0, -1, -1);
790   draw_buf_done = TRUE;
791   gdk_threads_leave();
792   return FALSE;
793 }
794
795
796 /* Mouse event handlers ************************************************************************/
797
798 static void vik_window_pan_click (VikWindow *vw, GdkEventButton *event)
799 {
800   /* set panning origin */
801   vw->pan_move = FALSE;
802   vw->pan_x = (gint) event->x;
803   vw->pan_y = (gint) event->y;
804 }
805
806 static void draw_click (VikWindow *vw, GdkEventButton *event)
807 {
808   gtk_widget_grab_focus ( GTK_WIDGET(vw->viking_vvp) );
809
810   /* middle button pressed.  we reserve all middle button and scroll events
811    * for panning and zooming; tools only get left/right/movement 
812    */
813   if ( event->button == 2) {
814     if ( vw->vt->tools[vw->vt->active_tool].ti.pan_handler )
815       // Tool still may need to do something (such as disable something)
816       toolbox_click(vw->vt, event);
817     vik_window_pan_click ( vw, event );
818   } 
819   else {
820     toolbox_click(vw->vt, event);
821   }
822 }
823
824 static void vik_window_pan_move (VikWindow *vw, GdkEventMotion *event)
825 {
826   if ( vw->pan_x != -1 ) {
827     vik_viewport_set_center_screen ( vw->viking_vvp, vik_viewport_get_width(vw->viking_vvp)/2 - event->x + vw->pan_x,
828                                      vik_viewport_get_height(vw->viking_vvp)/2 - event->y + vw->pan_y );
829     vw->pan_move = TRUE;
830     vw->pan_x = event->x;
831     vw->pan_y = event->y;
832     draw_update ( vw );
833   }
834 }
835
836 static void draw_mouse_motion (VikWindow *vw, GdkEventMotion *event)
837 {
838   static VikCoord coord;
839   static struct UTM utm;
840   static struct LatLon ll;
841   #define BUFFER_SIZE 50
842   static char pointer_buf[BUFFER_SIZE];
843   gchar *lat = NULL, *lon = NULL;
844   gint16 alt;
845   gdouble zoom;
846   VikDemInterpol interpol_method;
847
848   /* This is a hack, but work far the best, at least for single pointer systems.
849    * See http://bugzilla.gnome.org/show_bug.cgi?id=587714 for more. */
850   gint x, y;
851   gdk_window_get_pointer (event->window, &x, &y, NULL);
852   event->x = x;
853   event->y = y;
854
855   toolbox_move(vw->vt, event);
856
857   vik_viewport_screen_to_coord ( vw->viking_vvp, event->x, event->y, &coord );
858   vik_coord_to_utm ( &coord, &utm );
859
860   if ( vik_viewport_get_drawmode ( vw->viking_vvp ) == VIK_VIEWPORT_DRAWMODE_UTM ) {
861     // Reuse lat for the first part (Zone + N or S, and lon for the second part (easting and northing) of a UTM format:
862     //  ZONE[N|S] EASTING NORTHING
863     lat = g_malloc(4*sizeof(gchar));
864     // NB zone is stored in a char but is an actual number
865     g_snprintf (lat, 4, "%d%c", utm.zone, utm.letter);
866     lon = g_malloc(16*sizeof(gchar));
867     g_snprintf (lon, 16, "%d %d", (gint)utm.easting, (gint)utm.northing);
868   }
869   else {
870     a_coords_utm_to_latlon ( &utm, &ll );
871     a_coords_latlon_to_string ( &ll, &lat, &lon );
872   }
873
874   /* Change interpolate method according to scale */
875   zoom = vik_viewport_get_zoom(vw->viking_vvp);
876   if (zoom > 2.0)
877     interpol_method = VIK_DEM_INTERPOL_NONE;
878   else if (zoom >= 1.0)
879     interpol_method = VIK_DEM_INTERPOL_SIMPLE;
880   else
881     interpol_method = VIK_DEM_INTERPOL_BEST;
882   if ((alt = a_dems_get_elev_by_coord(&coord, interpol_method)) != VIK_DEM_INVALID_ELEVATION) {
883     if ( a_vik_get_units_height () == VIK_UNITS_HEIGHT_METRES )
884       g_snprintf ( pointer_buf, BUFFER_SIZE, _("%s %s %dm"), lat, lon, alt );
885     else
886       g_snprintf ( pointer_buf, BUFFER_SIZE, _("%s %s %dft"), lat, lon, (int)VIK_METERS_TO_FEET(alt) );
887   }
888   else
889     g_snprintf ( pointer_buf, BUFFER_SIZE, _("%s %s"), lat, lon );
890   g_free (lat);
891   lat = NULL;
892   g_free (lon);
893   lon = NULL;
894   vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_POSITION, pointer_buf );
895
896   vik_window_pan_move ( vw, event );
897
898   /* This is recommended by the GTK+ documentation, but does not work properly.
899    * Use deprecated way until GTK+ gets a solution for correct motion hint handling:
900    * http://bugzilla.gnome.org/show_bug.cgi?id=587714
901   */
902   /* gdk_event_request_motions ( event ); */
903 }
904
905 static void vik_window_pan_release ( VikWindow *vw, GdkEventButton *event )
906 {
907   if ( vw->pan_move == FALSE )
908     vik_viewport_set_center_screen ( vw->viking_vvp, vw->pan_x, vw->pan_y );
909   else
910      vik_viewport_set_center_screen ( vw->viking_vvp, vik_viewport_get_width(vw->viking_vvp)/2 - event->x + vw->pan_x,
911                                       vik_viewport_get_height(vw->viking_vvp)/2 - event->y + vw->pan_y );
912   vw->pan_move = FALSE;
913   vw->pan_x = vw->pan_y = -1;
914   draw_update ( vw );
915 }
916
917 static void draw_release ( VikWindow *vw, GdkEventButton *event )
918 {
919   gtk_widget_grab_focus ( GTK_WIDGET(vw->viking_vvp) );
920
921   if ( event->button == 2 ) {  /* move / pan */
922     if ( vw->vt->tools[vw->vt->active_tool].ti.pan_handler )
923       // Tool still may need to do something (such as reenable something)
924       toolbox_release(vw->vt, event);
925     vik_window_pan_release ( vw, event );
926   }
927   else {
928     toolbox_release(vw->vt, event);
929   }
930 }
931
932 static void draw_scroll (VikWindow *vw, GdkEventScroll *event)
933 {
934   guint modifiers = event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK);
935   if ( modifiers == GDK_CONTROL_MASK ) {
936     /* control == pan up & down */
937     if ( event->direction == GDK_SCROLL_UP )
938       vik_viewport_set_center_screen ( vw->viking_vvp, vik_viewport_get_width(vw->viking_vvp)/2, vik_viewport_get_height(vw->viking_vvp)/3 );
939     else
940       vik_viewport_set_center_screen ( vw->viking_vvp, vik_viewport_get_width(vw->viking_vvp)/2, vik_viewport_get_height(vw->viking_vvp)*2/3 );
941   } else if ( modifiers == GDK_SHIFT_MASK ) {
942     /* shift == pan left & right */
943     if ( event->direction == GDK_SCROLL_UP )
944       vik_viewport_set_center_screen ( vw->viking_vvp, vik_viewport_get_width(vw->viking_vvp)/3, vik_viewport_get_height(vw->viking_vvp)/2 );
945     else
946       vik_viewport_set_center_screen ( vw->viking_vvp, vik_viewport_get_width(vw->viking_vvp)*2/3, vik_viewport_get_height(vw->viking_vvp)/2 );
947   } else if ( modifiers == (GDK_CONTROL_MASK | GDK_SHIFT_MASK) ) {
948     // This zoom is on the center position
949     if ( event->direction == GDK_SCROLL_UP )
950       vik_viewport_zoom_in (vw->viking_vvp);
951     else
952       vik_viewport_zoom_out (vw->viking_vvp);
953   } else {
954     /* make sure mouse is still over the same point on the map when we zoom */
955     VikCoord coord;
956     gint x, y;
957     gint center_x = vik_viewport_get_width ( vw->viking_vvp ) / 2;
958     gint center_y = vik_viewport_get_height ( vw->viking_vvp ) / 2;
959     vik_viewport_screen_to_coord ( vw->viking_vvp, event->x, event->y, &coord );
960     if ( event->direction == GDK_SCROLL_UP )
961       vik_viewport_zoom_in (vw->viking_vvp);
962     else
963       vik_viewport_zoom_out(vw->viking_vvp);
964     vik_viewport_coord_to_screen ( vw->viking_vvp, &coord, &x, &y );
965     vik_viewport_set_center_screen ( vw->viking_vvp, center_x + (x - event->x),
966                                      center_y + (y - event->y) );
967   }
968
969   draw_update(vw);
970 }
971
972
973
974 /********************************************************************************
975  ** Ruler tool code
976  ********************************************************************************/
977 static void draw_ruler(VikViewport *vvp, GdkDrawable *d, GdkGC *gc, gint x1, gint y1, gint x2, gint y2, gdouble distance)
978 {
979   PangoLayout *pl;
980   gchar str[128];
981   GdkGC *labgc = vik_viewport_new_gc ( vvp, "#cccccc", 1);
982   GdkGC *thickgc = gdk_gc_new(d);
983   
984   gdouble len = sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
985   gdouble dx = (x2-x1)/len*10; 
986   gdouble dy = (y2-y1)/len*10;
987   gdouble c = cos(DEG2RAD(15.0));
988   gdouble s = sin(DEG2RAD(15.0));
989   gdouble angle;
990   gdouble baseangle = 0;
991   gint i;
992
993   /* draw line with arrow ends */
994   {
995     gint tmp_x1=x1, tmp_y1=y1, tmp_x2=x2, tmp_y2=y2;
996     a_viewport_clip_line(&tmp_x1, &tmp_y1, &tmp_x2, &tmp_y2);
997     gdk_draw_line(d, gc, tmp_x1, tmp_y1, tmp_x2, tmp_y2);
998   }
999
1000   a_viewport_clip_line(&x1, &y1, &x2, &y2);
1001   gdk_draw_line(d, gc, x1, y1, x2, y2);
1002
1003   gdk_draw_line(d, gc, x1 - dy, y1 + dx, x1 + dy, y1 - dx);
1004   gdk_draw_line(d, gc, x2 - dy, y2 + dx, x2 + dy, y2 - dx);
1005   gdk_draw_line(d, gc, x2, y2, x2 - (dx * c + dy * s), y2 - (dy * c - dx * s));
1006   gdk_draw_line(d, gc, x2, y2, x2 - (dx * c - dy * s), y2 - (dy * c + dx * s));
1007   gdk_draw_line(d, gc, x1, y1, x1 + (dx * c + dy * s), y1 + (dy * c - dx * s));
1008   gdk_draw_line(d, gc, x1, y1, x1 + (dx * c - dy * s), y1 + (dy * c + dx * s));
1009
1010   /* draw compass */
1011 #define CR 80
1012 #define CW 4
1013
1014   vik_viewport_compute_bearing ( vvp, x1, y1, x2, y2, &angle, &baseangle );
1015
1016   {
1017     GdkColor color;
1018     gdk_gc_copy(thickgc, gc);
1019     gdk_gc_set_line_attributes(thickgc, CW, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER);
1020     gdk_color_parse("#2255cc", &color);
1021     gdk_gc_set_rgb_fg_color(thickgc, &color);
1022   }
1023   gdk_draw_arc (d, thickgc, FALSE, x1-CR+CW/2, y1-CR+CW/2, 2*CR-CW, 2*CR-CW, (90 - RAD2DEG(baseangle))*64, -RAD2DEG(angle)*64);
1024
1025
1026   gdk_gc_copy(thickgc, gc);
1027   gdk_gc_set_line_attributes(thickgc, 2, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER);
1028   for (i=0; i<180; i++) {
1029     c = cos(DEG2RAD(i)*2 + baseangle);
1030     s = sin(DEG2RAD(i)*2 + baseangle);
1031
1032     if (i%5) {
1033       gdk_draw_line (d, gc, x1 + CR*c, y1 + CR*s, x1 + (CR+CW)*c, y1 + (CR+CW)*s);
1034     } else {
1035       gdouble ticksize = 2*CW;
1036       gdk_draw_line (d, thickgc, x1 + (CR-CW)*c, y1 + (CR-CW)*s, x1 + (CR+ticksize)*c, y1 + (CR+ticksize)*s);
1037     }
1038   }
1039
1040   gdk_draw_arc (d, gc, FALSE, x1-CR, y1-CR, 2*CR, 2*CR, 0, 64*360);
1041   gdk_draw_arc (d, gc, FALSE, x1-CR-CW, y1-CR-CW, 2*(CR+CW), 2*(CR+CW), 0, 64*360);
1042   gdk_draw_arc (d, gc, FALSE, x1-CR+CW, y1-CR+CW, 2*(CR-CW), 2*(CR-CW), 0, 64*360);
1043   c = (CR+CW*2)*cos(baseangle);
1044   s = (CR+CW*2)*sin(baseangle);
1045   gdk_draw_line (d, gc, x1-c, y1-s, x1+c, y1+s);
1046   gdk_draw_line (d, gc, x1+s, y1-c, x1-s, y1+c);
1047
1048   /* draw labels */
1049 #define LABEL(x, y, w, h) { \
1050     gdk_draw_rectangle(d, labgc, TRUE, (x)-2, (y)-1, (w)+4, (h)+1); \
1051     gdk_draw_rectangle(d, gc, FALSE, (x)-2, (y)-1, (w)+4, (h)+1); \
1052     gdk_draw_layout(d, gc, (x), (y), pl); } 
1053   {
1054     gint wd, hd, xd, yd;
1055     gint wb, hb, xb, yb;
1056
1057     pl = gtk_widget_create_pango_layout (GTK_WIDGET(vvp), NULL);
1058     pango_layout_set_font_description (pl, gtk_widget_get_style(GTK_WIDGET(vvp))->font_desc);
1059     pango_layout_set_text(pl, "N", -1);
1060     gdk_draw_layout(d, gc, x1-5, y1-CR-3*CW-8, pl);
1061
1062     /* draw label with distance */
1063     vik_units_distance_t dist_units = a_vik_get_units_distance ();
1064     switch (dist_units) {
1065     case VIK_UNITS_DISTANCE_KILOMETRES:
1066       if (distance >= 1000 && distance < 100000) {
1067         g_sprintf(str, "%3.2f km", distance/1000.0);
1068       } else if (distance < 1000) {
1069         g_sprintf(str, "%d m", (int)distance);
1070       } else {
1071         g_sprintf(str, "%d km", (int)distance/1000);
1072       }
1073       break;
1074     case VIK_UNITS_DISTANCE_MILES:
1075       if (distance >= VIK_MILES_TO_METERS(1) && distance < VIK_MILES_TO_METERS(100)) {
1076         g_sprintf(str, "%3.2f miles", VIK_METERS_TO_MILES(distance));
1077       } else if (distance < VIK_MILES_TO_METERS(1)) {
1078         g_sprintf(str, "%d yards", (int)(distance*1.0936133));
1079       } else {
1080         g_sprintf(str, "%d miles", (int)VIK_METERS_TO_MILES(distance));
1081       }
1082       break;
1083     default:
1084       g_critical("Houston, we've had a problem. distance=%d", dist_units);
1085     }
1086
1087     pango_layout_set_text(pl, str, -1);
1088
1089     pango_layout_get_pixel_size ( pl, &wd, &hd );
1090     if (dy>0) {
1091       xd = (x1+x2)/2 + dy;
1092       yd = (y1+y2)/2 - hd/2 - dx;
1093     } else {
1094       xd = (x1+x2)/2 - dy;
1095       yd = (y1+y2)/2 - hd/2 + dx;
1096     }
1097
1098     if ( xd < -5 || yd < -5 || xd > vik_viewport_get_width(vvp)+5 || yd > vik_viewport_get_height(vvp)+5 ) {
1099       xd = x2 + 10;
1100       yd = y2 - 5;
1101     }
1102
1103     LABEL(xd, yd, wd, hd);
1104
1105     /* draw label with bearing */
1106     g_sprintf(str, "%3.1f°", RAD2DEG(angle));
1107     pango_layout_set_text(pl, str, -1);
1108     pango_layout_get_pixel_size ( pl, &wb, &hb );
1109     xb = x1 + CR*cos(angle-M_PI_2);
1110     yb = y1 + CR*sin(angle-M_PI_2);
1111
1112     if ( xb < -5 || yb < -5 || xb > vik_viewport_get_width(vvp)+5 || yb > vik_viewport_get_height(vvp)+5 ) {
1113       xb = x2 + 10;
1114       yb = y2 + 10;
1115     }
1116
1117     {
1118       GdkRectangle r1 = {xd-2, yd-1, wd+4, hd+1}, r2 = {xb-2, yb-1, wb+4, hb+1};
1119       if (gdk_rectangle_intersect(&r1, &r2, &r2)) {
1120         xb = xd + wd + 5;
1121       }
1122     }
1123     LABEL(xb, yb, wb, hb);
1124   }
1125 #undef LABEL
1126
1127   g_object_unref ( G_OBJECT ( pl ) );
1128   g_object_unref ( G_OBJECT ( labgc ) );
1129   g_object_unref ( G_OBJECT ( thickgc ) );
1130 }
1131
1132 typedef struct {
1133   VikWindow *vw;
1134   VikViewport *vvp;
1135   gboolean has_oldcoord;
1136   VikCoord oldcoord;
1137 } ruler_tool_state_t;
1138
1139 static gpointer ruler_create (VikWindow *vw, VikViewport *vvp) 
1140 {
1141   ruler_tool_state_t *s = g_new(ruler_tool_state_t, 1);
1142   s->vw = vw;
1143   s->vvp = vvp;
1144   s->has_oldcoord = FALSE;
1145   return s;
1146 }
1147
1148 static void ruler_destroy (ruler_tool_state_t *s)
1149 {
1150   g_free(s);
1151 }
1152
1153 static VikLayerToolFuncStatus ruler_click (VikLayer *vl, GdkEventButton *event, ruler_tool_state_t *s)
1154 {
1155   struct LatLon ll;
1156   VikCoord coord;
1157   gchar *temp;
1158   if ( event->button == 1 ) {
1159     gchar *lat=NULL, *lon=NULL;
1160     vik_viewport_screen_to_coord ( s->vvp, (gint) event->x, (gint) event->y, &coord );
1161     vik_coord_to_latlon ( &coord, &ll );
1162     a_coords_latlon_to_string ( &ll, &lat, &lon );
1163     if ( s->has_oldcoord ) {
1164       vik_units_distance_t dist_units = a_vik_get_units_distance ();
1165       switch (dist_units) {
1166       case VIK_UNITS_DISTANCE_KILOMETRES:
1167         temp = g_strdup_printf ( "%s %s DIFF %f meters", lat, lon, vik_coord_diff( &coord, &(s->oldcoord) ) );
1168         break;
1169       case VIK_UNITS_DISTANCE_MILES:
1170         temp = g_strdup_printf ( "%s %s DIFF %f miles", lat, lon, VIK_METERS_TO_MILES(vik_coord_diff( &coord, &(s->oldcoord) )) );
1171         break;
1172       default:
1173         temp = g_strdup_printf ("Just to keep the compiler happy");
1174         g_critical("Houston, we've had a problem. distance=%d", dist_units);
1175       }
1176
1177       s->has_oldcoord = FALSE;
1178     }
1179     else {
1180       temp = g_strdup_printf ( "%s %s", lat, lon );
1181       s->has_oldcoord = TRUE;
1182     }
1183
1184     vik_statusbar_set_message ( s->vw->viking_vs, VIK_STATUSBAR_INFO, temp );
1185     g_free ( temp );
1186
1187     s->oldcoord = coord;
1188   }
1189   else {
1190     vik_viewport_set_center_screen ( s->vvp, (gint) event->x, (gint) event->y );
1191     draw_update ( s->vw );
1192   }
1193   return VIK_LAYER_TOOL_ACK;
1194 }
1195
1196 static VikLayerToolFuncStatus ruler_move (VikLayer *vl, GdkEventMotion *event, ruler_tool_state_t *s)
1197 {
1198   VikViewport *vvp = s->vvp;
1199   VikWindow *vw = s->vw;
1200
1201   struct LatLon ll;
1202   VikCoord coord;
1203   gchar *temp;
1204
1205   if ( s->has_oldcoord ) {
1206     int oldx, oldy, w1, h1, w2, h2;
1207     static GdkPixmap *buf = NULL;
1208     gchar *lat=NULL, *lon=NULL;
1209     w1 = vik_viewport_get_width(vvp); 
1210     h1 = vik_viewport_get_height(vvp);
1211     if (!buf) {
1212       buf = gdk_pixmap_new ( gtk_widget_get_window(GTK_WIDGET(vvp)), w1, h1, -1 );
1213     }
1214     gdk_drawable_get_size(buf, &w2, &h2);
1215     if (w1 != w2 || h1 != h2) {
1216       g_object_unref ( G_OBJECT ( buf ) );
1217       buf = gdk_pixmap_new ( gtk_widget_get_window(GTK_WIDGET(vvp)), w1, h1, -1 );
1218     }
1219
1220     vik_viewport_screen_to_coord ( vvp, (gint) event->x, (gint) event->y, &coord );
1221     vik_coord_to_latlon ( &coord, &ll );
1222     vik_viewport_coord_to_screen ( vvp, &s->oldcoord, &oldx, &oldy );
1223
1224     gdk_draw_drawable (buf, gtk_widget_get_style(GTK_WIDGET(vvp))->black_gc,
1225                        vik_viewport_get_pixmap(vvp), 0, 0, 0, 0, -1, -1);
1226     draw_ruler(vvp, buf, gtk_widget_get_style(GTK_WIDGET(vvp))->black_gc, oldx, oldy, event->x, event->y, vik_coord_diff( &coord, &(s->oldcoord)) );
1227     if (draw_buf_done) {
1228       static gpointer pass_along[3];
1229       pass_along[0] = gtk_widget_get_window(GTK_WIDGET(vvp));
1230       pass_along[1] = gtk_widget_get_style(GTK_WIDGET(vvp))->black_gc;
1231       pass_along[2] = buf;
1232       g_idle_add_full (G_PRIORITY_HIGH_IDLE + 10, draw_buf, pass_along, NULL);
1233       draw_buf_done = FALSE;
1234     }
1235     a_coords_latlon_to_string(&ll, &lat, &lon);
1236     vik_units_distance_t dist_units = a_vik_get_units_distance ();
1237     switch (dist_units) {
1238     case VIK_UNITS_DISTANCE_KILOMETRES:
1239       temp = g_strdup_printf ( "%s %s DIFF %f meters", lat, lon, vik_coord_diff( &coord, &(s->oldcoord) ) );
1240       break;
1241     case VIK_UNITS_DISTANCE_MILES:
1242       temp = g_strdup_printf ( "%s %s DIFF %f miles", lat, lon, VIK_METERS_TO_MILES (vik_coord_diff( &coord, &(s->oldcoord) )) );
1243       break;
1244     default:
1245       temp = g_strdup_printf ("Just to keep the compiler happy");
1246       g_critical("Houston, we've had a problem. distance=%d", dist_units);
1247     }
1248     vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_INFO, temp );
1249     g_free ( temp );
1250   }
1251   return VIK_LAYER_TOOL_ACK;
1252 }
1253
1254 static VikLayerToolFuncStatus ruler_release (VikLayer *vl, GdkEventButton *event, ruler_tool_state_t *s)
1255 {
1256   return VIK_LAYER_TOOL_ACK;
1257 }
1258
1259 static void ruler_deactivate (VikLayer *vl, ruler_tool_state_t *s)
1260 {
1261   draw_update ( s->vw );
1262 }
1263
1264 static gboolean ruler_key_press (VikLayer *vl, GdkEventKey *event, ruler_tool_state_t *s)
1265 {
1266   if (event->keyval == GDK_Escape) {
1267     s->has_oldcoord = FALSE;
1268     ruler_deactivate ( vl, s );
1269     return TRUE;
1270   }
1271   // Regardless of whether we used it, return false so other GTK things may use it
1272   return FALSE;
1273 }
1274
1275 static VikToolInterface ruler_tool =
1276   // NB Ctrl+Shift+R is used for Refresh (deemed more important), so use 'U' instead
1277   { { "Ruler", "vik-icon-ruler", N_("_Ruler"), "<control><shift>U", N_("Ruler Tool"), 2 },
1278     (VikToolConstructorFunc) ruler_create,
1279     (VikToolDestructorFunc) ruler_destroy,
1280     (VikToolActivationFunc) NULL,
1281     (VikToolActivationFunc) ruler_deactivate, 
1282     (VikToolMouseFunc) ruler_click, 
1283     (VikToolMouseMoveFunc) ruler_move, 
1284     (VikToolMouseFunc) ruler_release,
1285     (VikToolKeyFunc) ruler_key_press,
1286     FALSE,
1287     GDK_CURSOR_IS_PIXMAP,
1288     &cursor_ruler_pixbuf };
1289 /*** end ruler code ********************************************************/
1290
1291
1292
1293 /********************************************************************************
1294  ** Zoom tool code
1295  ********************************************************************************/
1296
1297 typedef struct {
1298   VikWindow *vw;
1299   GdkPixmap *pixmap;
1300   // Track zoom bounds for zoom tool with shift modifier:
1301   gboolean bounds_active;
1302   gint start_x;
1303   gint start_y;
1304 } zoom_tool_state_t;
1305
1306 /*
1307  * In case the screen size has changed
1308  */
1309 static void zoomtool_resize_pixmap (zoom_tool_state_t *zts)
1310 {
1311     int w1, h1, w2, h2;
1312
1313     // Allocate a drawing area the size of the viewport
1314     w1 = vik_viewport_get_width ( zts->vw->viking_vvp );
1315     h1 = vik_viewport_get_height ( zts->vw->viking_vvp );
1316
1317     if ( !zts->pixmap ) {
1318       // Totally new
1319       zts->pixmap = gdk_pixmap_new ( gtk_widget_get_window(GTK_WIDGET(zts->vw->viking_vvp)), w1, h1, -1 );
1320     }
1321
1322     gdk_drawable_get_size ( zts->pixmap, &w2, &h2 );
1323
1324     if ( w1 != w2 || h1 != h2 ) {
1325       // Has changed - delete and recreate with new values
1326       g_object_unref ( G_OBJECT ( zts->pixmap ) );
1327       zts->pixmap = gdk_pixmap_new ( gtk_widget_get_window(GTK_WIDGET(zts->vw->viking_vvp)), w1, h1, -1 );
1328     }
1329 }
1330
1331 static gpointer zoomtool_create (VikWindow *vw, VikViewport *vvp)
1332 {
1333   zoom_tool_state_t *zts = g_new(zoom_tool_state_t, 1);
1334   zts->vw = vw;
1335   zts->pixmap = NULL;
1336   zts->start_x = 0;
1337   zts->start_y = 0;
1338   zts->bounds_active = FALSE;
1339   return zts;
1340 }
1341
1342 static void zoomtool_destroy ( zoom_tool_state_t *zts)
1343 {
1344   if ( zts->pixmap )
1345     g_object_unref ( G_OBJECT ( zts->pixmap ) );
1346   g_free(zts);
1347 }
1348
1349 static VikLayerToolFuncStatus zoomtool_click (VikLayer *vl, GdkEventButton *event, zoom_tool_state_t *zts)
1350 {
1351   zts->vw->modified = TRUE;
1352   guint modifiers = event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK);
1353
1354   VikCoord coord;
1355   gint x, y;
1356   gint center_x = vik_viewport_get_width ( zts->vw->viking_vvp ) / 2;
1357   gint center_y = vik_viewport_get_height ( zts->vw->viking_vvp ) / 2;
1358
1359   gboolean skip_update = FALSE;
1360
1361   zts->bounds_active = FALSE;
1362
1363   if ( modifiers == (GDK_CONTROL_MASK | GDK_SHIFT_MASK) ) {
1364     // This zoom is on the center position
1365     vik_viewport_set_center_screen ( zts->vw->viking_vvp, center_x, center_y );
1366     if ( event->button == 1 )
1367       vik_viewport_zoom_in (zts->vw->viking_vvp);
1368     else if ( event->button == 3 )
1369       vik_viewport_zoom_out (zts->vw->viking_vvp);
1370   }
1371   else if ( modifiers == GDK_CONTROL_MASK ) {
1372     // This zoom is to recenter on the mouse position
1373     vik_viewport_set_center_screen ( zts->vw->viking_vvp, (gint) event->x, (gint) event->y );
1374     if ( event->button == 1 )
1375       vik_viewport_zoom_in (zts->vw->viking_vvp);
1376     else if ( event->button == 3 )
1377       vik_viewport_zoom_out (zts->vw->viking_vvp);
1378   }
1379   else if ( modifiers == GDK_SHIFT_MASK ) {
1380     // Get start of new zoom bounds
1381     if ( event->button == 1 ) {
1382       zts->bounds_active = TRUE;
1383       zts->start_x = (gint) event->x;
1384       zts->start_y = (gint) event->y;
1385       skip_update = TRUE;
1386     }
1387   }
1388   else {
1389     /* make sure mouse is still over the same point on the map when we zoom */
1390     vik_viewport_screen_to_coord ( zts->vw->viking_vvp, event->x, event->y, &coord );
1391     if ( event->button == 1 )
1392       vik_viewport_zoom_in (zts->vw->viking_vvp);
1393     else if ( event->button == 3 )
1394       vik_viewport_zoom_out(zts->vw->viking_vvp);
1395     vik_viewport_coord_to_screen ( zts->vw->viking_vvp, &coord, &x, &y );
1396     vik_viewport_set_center_screen ( zts->vw->viking_vvp,
1397                                      center_x + (x - event->x),
1398                                      center_y + (y - event->y) );
1399   }
1400
1401   if ( !skip_update )
1402     draw_update ( zts->vw );
1403
1404   return VIK_LAYER_TOOL_ACK;
1405 }
1406
1407 static VikLayerToolFuncStatus zoomtool_move (VikLayer *vl, GdkEventMotion *event, zoom_tool_state_t *zts)
1408 {
1409   guint modifiers = event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK);
1410
1411   if ( zts->bounds_active && modifiers == GDK_SHIFT_MASK ) {
1412     zoomtool_resize_pixmap ( zts );
1413
1414     // Blank out currently drawn area
1415     gdk_draw_drawable ( zts->pixmap,
1416                         gtk_widget_get_style(GTK_WIDGET(zts->vw->viking_vvp))->black_gc,
1417                         vik_viewport_get_pixmap(zts->vw->viking_vvp),
1418                         0, 0, 0, 0, -1, -1);
1419
1420     // Calculate new box starting point & size in pixels
1421     int xx, yy, width, height;
1422     if ( event->y > zts->start_y ) {
1423       yy = zts->start_y;
1424       height = event->y-zts->start_y;
1425     }
1426     else {
1427       yy = event->y;
1428       height = zts->start_y-event->y;
1429     }
1430     if ( event->x > zts->start_x ) {
1431       xx = zts->start_x;
1432       width = event->x-zts->start_x;
1433     }
1434     else {
1435       xx = event->x;
1436       width = zts->start_x-event->x;
1437     }
1438
1439     // Draw the box
1440     gdk_draw_rectangle (zts->pixmap, gtk_widget_get_style(GTK_WIDGET(zts->vw->viking_vvp))->black_gc, FALSE, xx, yy, width, height);
1441
1442     // Only actually draw when there's time to do so
1443     if (draw_buf_done) {
1444       static gpointer pass_along[3];
1445       pass_along[0] = gtk_widget_get_window(GTK_WIDGET(zts->vw->viking_vvp));
1446       pass_along[1] = gtk_widget_get_style(GTK_WIDGET(zts->vw->viking_vvp))->black_gc;
1447       pass_along[2] = zts->pixmap;
1448       g_idle_add_full (G_PRIORITY_HIGH_IDLE + 10, draw_buf, pass_along, NULL);
1449       draw_buf_done = FALSE;
1450     }
1451   }
1452   return VIK_LAYER_TOOL_ACK;
1453 }
1454
1455 static VikLayerToolFuncStatus zoomtool_release (VikLayer *vl, GdkEventButton *event, zoom_tool_state_t *zts)
1456 {
1457   guint modifiers = event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK);
1458
1459   zts->bounds_active = FALSE;
1460
1461   // Ensure haven't just released on the exact same position
1462   //  i.e. probably haven't moved the mouse at all
1463   if ( modifiers == GDK_SHIFT_MASK && !( ( event->x == zts->start_x ) && ( event->y == zts->start_y )) ) {
1464
1465     VikCoord coord1, coord2;
1466     vik_viewport_screen_to_coord ( zts->vw->viking_vvp, zts->start_x, zts->start_y, &coord1);
1467     vik_viewport_screen_to_coord ( zts->vw->viking_vvp, event->x, event->y, &coord2);
1468
1469     // From the extend of the bounds pick the best zoom level
1470     // c.f. trw_layer_zoom_to_show_latlons()
1471     // Maybe refactor...
1472     struct LatLon ll1, ll2;
1473     vik_coord_to_latlon(&coord1, &ll1);
1474     vik_coord_to_latlon(&coord2, &ll2);
1475     struct LatLon average = { (ll1.lat+ll2.lat)/2,
1476                               (ll1.lon+ll2.lon)/2 };
1477
1478     VikCoord new_center;
1479     vik_coord_load_from_latlon ( &new_center, vik_viewport_get_coord_mode ( zts->vw->viking_vvp ), &average );
1480     vik_viewport_set_center_coord ( zts->vw->viking_vvp, &new_center );
1481
1482     /* Convert into definite 'smallest' and 'largest' positions */
1483     struct LatLon minmin;
1484     if ( ll1.lat < ll2.lat )
1485       minmin.lat = ll1.lat;
1486     else
1487       minmin.lat = ll2.lat;
1488
1489     struct LatLon maxmax;
1490     if ( ll1.lon > ll2.lon )
1491       maxmax.lon = ll1.lon;
1492     else
1493       maxmax.lon = ll2.lon;
1494
1495     /* Always recalculate the 'best' zoom level */
1496     gdouble zoom = VIK_VIEWPORT_MIN_ZOOM;
1497     vik_viewport_set_zoom ( zts->vw->viking_vvp, zoom );
1498
1499     gdouble min_lat, max_lat, min_lon, max_lon;
1500     /* Should only be a maximum of about 18 iterations from min to max zoom levels */
1501     while ( zoom <= VIK_VIEWPORT_MAX_ZOOM ) {
1502       vik_viewport_get_min_max_lat_lon ( zts->vw->viking_vvp, &min_lat, &max_lat, &min_lon, &max_lon );
1503       /* NB I think the logic used in this test to determine if the bounds is within view
1504          fails if track goes across 180 degrees longitude.
1505          Hopefully that situation is not too common...
1506          Mind you viking doesn't really do edge locations to well anyway */
1507       if ( min_lat < minmin.lat &&
1508            max_lat > minmin.lat &&
1509            min_lon < maxmax.lon &&
1510            max_lon > maxmax.lon )
1511         /* Found within zoom level */
1512         break;
1513
1514       /* Try next */
1515       zoom = zoom * 2;
1516       vik_viewport_set_zoom ( zts->vw->viking_vvp, zoom );
1517     }
1518
1519     draw_update ( zts->vw );
1520   }
1521   return VIK_LAYER_TOOL_ACK;
1522 }
1523
1524 static VikToolInterface zoom_tool = 
1525   { { "Zoom", "vik-icon-zoom", N_("_Zoom"), "<control><shift>Z", N_("Zoom Tool"), 1 },
1526     (VikToolConstructorFunc) zoomtool_create,
1527     (VikToolDestructorFunc) zoomtool_destroy,
1528     (VikToolActivationFunc) NULL,
1529     (VikToolActivationFunc) NULL,
1530     (VikToolMouseFunc) zoomtool_click, 
1531     (VikToolMouseMoveFunc) zoomtool_move,
1532     (VikToolMouseFunc) zoomtool_release,
1533     NULL,
1534     FALSE,
1535     GDK_CURSOR_IS_PIXMAP,
1536     &cursor_zoom_pixbuf };
1537 /*** end zoom code ********************************************************/
1538
1539 /********************************************************************************
1540  ** Pan tool code
1541  ********************************************************************************/
1542 static gpointer pantool_create (VikWindow *vw, VikViewport *vvp)
1543 {
1544   return vw;
1545 }
1546
1547 static VikLayerToolFuncStatus pantool_click (VikLayer *vl, GdkEventButton *event, VikWindow *vw)
1548 {
1549   vw->modified = TRUE;
1550   if ( event->button == 1 )
1551     vik_window_pan_click ( vw, event );
1552   draw_update ( vw );
1553   return VIK_LAYER_TOOL_ACK;
1554 }
1555
1556 static VikLayerToolFuncStatus pantool_move (VikLayer *vl, GdkEventMotion *event, VikWindow *vw)
1557 {
1558   vik_window_pan_move ( vw, event );
1559   return VIK_LAYER_TOOL_ACK;
1560 }
1561
1562 static VikLayerToolFuncStatus pantool_release (VikLayer *vl, GdkEventButton *event, VikWindow *vw)
1563 {
1564   if ( event->button == 1 )
1565     vik_window_pan_release ( vw, event );
1566   return VIK_LAYER_TOOL_ACK;
1567 }
1568
1569 static VikToolInterface pan_tool = 
1570   { { "Pan", "vik-icon-pan", N_("_Pan"), "<control><shift>P", N_("Pan Tool"), 0 },
1571     (VikToolConstructorFunc) pantool_create,
1572     (VikToolDestructorFunc) NULL,
1573     (VikToolActivationFunc) NULL,
1574     (VikToolActivationFunc) NULL,
1575     (VikToolMouseFunc) pantool_click, 
1576     (VikToolMouseMoveFunc) pantool_move,
1577     (VikToolMouseFunc) pantool_release,
1578     NULL,
1579     FALSE,
1580     GDK_FLEUR };
1581 /*** end pan code ********************************************************/
1582
1583 /********************************************************************************
1584  ** Select tool code
1585  ********************************************************************************/
1586 static gpointer selecttool_create (VikWindow *vw, VikViewport *vvp)
1587 {
1588   tool_ed_t *t = g_new(tool_ed_t, 1);
1589   t->vw = vw;
1590   t->vvp = vvp;
1591   t->vtl = NULL;
1592   t->is_waypoint = FALSE;
1593   return t;
1594 }
1595
1596 static void selecttool_destroy (tool_ed_t *t)
1597 {
1598   g_free(t);
1599 }
1600
1601 typedef struct {
1602   gboolean cont;
1603   VikViewport *vvp;
1604   GdkEventButton *event;
1605   tool_ed_t *tool_edit;
1606 } clicker;
1607
1608 static void click_layer_selected (VikLayer *vl, clicker *ck)
1609 {
1610   /* Do nothing when function call returns true; */
1611   /* i.e. stop on first found item */
1612   if ( ck->cont )
1613     if ( vl->visible )
1614       if ( vik_layer_get_interface(vl->type)->select_click )
1615         ck->cont = !vik_layer_get_interface(vl->type)->select_click ( vl, ck->event, ck->vvp, ck->tool_edit );
1616 }
1617
1618 static VikLayerToolFuncStatus selecttool_click (VikLayer *vl, GdkEventButton *event, tool_ed_t *t)
1619 {
1620   /* Only allow selection on primary button */
1621   if ( event->button == 1 ) {
1622     /* Enable click to apply callback to potentially all track/waypoint layers */
1623     /* Useful as we can find things that aren't necessarily in the currently selected layer */
1624     GList* gl = vik_layers_panel_get_all_layers_of_type ( t->vw->viking_vlp, VIK_LAYER_TRW, FALSE ); // Don't get invisible layers
1625     clicker ck;
1626     ck.cont = TRUE;
1627     ck.vvp = t->vw->viking_vvp;
1628     ck.event = event;
1629     ck.tool_edit = t;
1630     g_list_foreach ( gl, (GFunc) click_layer_selected, &ck );
1631     g_list_free ( gl );
1632
1633     // If nothing found then deselect & redraw screen if necessary to remove the highlight
1634     if ( ck.cont ) {
1635       GtkTreeIter iter;
1636       VikTreeview *vtv = vik_layers_panel_get_treeview ( t->vw->viking_vlp );
1637
1638       if ( vik_treeview_get_selected_iter ( vtv, &iter ) ) {
1639         // Only clear if selected thing is a TrackWaypoint layer or a sublayer
1640         gint type = vik_treeview_item_get_type ( vtv, &iter );
1641         if ( type == VIK_TREEVIEW_TYPE_SUBLAYER ||
1642              VIK_LAYER(vik_treeview_item_get_pointer ( vtv, &iter ))->type == VIK_LAYER_TRW ) {
1643    
1644           vik_treeview_item_unselect ( vtv, &iter );
1645           if ( vik_window_clear_highlight ( t->vw ) )
1646             draw_update ( t->vw );
1647         }
1648       }
1649     }
1650   }
1651   else if ( ( event->button == 3 ) && ( vl && ( vl->type == VIK_LAYER_TRW ) ) ) {
1652     if ( vl->visible )
1653       /* Act on currently selected item to show menu */
1654       if ( t->vw->selected_track || t->vw->selected_waypoint )
1655         if ( vik_layer_get_interface(vl->type)->show_viewport_menu )
1656           vik_layer_get_interface(vl->type)->show_viewport_menu ( vl, event, t->vw->viking_vvp );
1657   }
1658
1659   return VIK_LAYER_TOOL_ACK;
1660 }
1661
1662 static VikLayerToolFuncStatus selecttool_move (VikLayer *vl, GdkEventButton *event, tool_ed_t *t)
1663 {
1664   /* Only allow selection on primary button */
1665   if ( event->button == 1 ) {
1666     // Don't care about vl here
1667     if ( t->vtl )
1668       if ( vik_layer_get_interface(VIK_LAYER_TRW)->select_move )
1669         vik_layer_get_interface(VIK_LAYER_TRW)->select_move ( vl, event, t->vvp, t );
1670   }
1671   return VIK_LAYER_TOOL_ACK;
1672 }
1673
1674 static VikLayerToolFuncStatus selecttool_release (VikLayer *vl, GdkEventButton *event, tool_ed_t *t)
1675 {
1676   /* Only allow selection on primary button */
1677   if ( event->button == 1 ) {
1678     // Don't care about vl here
1679     if ( t->vtl )
1680       if ( vik_layer_get_interface(VIK_LAYER_TRW)->select_release )
1681         vik_layer_get_interface(VIK_LAYER_TRW)->select_release ( (VikLayer*)t->vtl, event, t->vvp, t );
1682   }
1683   return VIK_LAYER_TOOL_ACK;
1684 }
1685
1686 static VikToolInterface select_tool =
1687   { { "Select", "vik-icon-select", N_("_Select"), "<control><shift>S", N_("Select Tool"), 3 },
1688     (VikToolConstructorFunc) selecttool_create,
1689     (VikToolDestructorFunc) selecttool_destroy,
1690     (VikToolActivationFunc) NULL,
1691     (VikToolActivationFunc) NULL,
1692     (VikToolMouseFunc) selecttool_click,
1693     (VikToolMouseMoveFunc) selecttool_move,
1694     (VikToolMouseFunc) selecttool_release,
1695     (VikToolKeyFunc) NULL,
1696     FALSE,
1697     GDK_LEFT_PTR,
1698     NULL,
1699     NULL };
1700 /*** end select tool code ********************************************************/
1701
1702 static void draw_pan_cb ( GtkAction *a, VikWindow *vw )
1703 {
1704   // Since the treeview cell editting intercepts standard keyboard handlers, it means we can receive events here
1705   // Thus if currently editting, ensure we don't move the viewport when Ctrl+<arrow> is received
1706   VikLayer *sel = vik_layers_panel_get_selected ( vw->viking_vlp );
1707   if ( sel && vik_treeview_get_editing ( sel->vt ) )
1708     return;
1709
1710   if (!strcmp(gtk_action_get_name(a), "PanNorth")) {
1711     vik_viewport_set_center_screen ( vw->viking_vvp, vik_viewport_get_width(vw->viking_vvp)/2, 0 );
1712   } else if (!strcmp(gtk_action_get_name(a), "PanEast")) {
1713     vik_viewport_set_center_screen ( vw->viking_vvp, vik_viewport_get_width(vw->viking_vvp), vik_viewport_get_height(vw->viking_vvp)/2 );
1714   } else if (!strcmp(gtk_action_get_name(a), "PanSouth")) {
1715     vik_viewport_set_center_screen ( vw->viking_vvp, vik_viewport_get_width(vw->viking_vvp)/2, vik_viewport_get_height(vw->viking_vvp) );
1716   } else if (!strcmp(gtk_action_get_name(a), "PanWest")) {
1717     vik_viewport_set_center_screen ( vw->viking_vvp, 0, vik_viewport_get_height(vw->viking_vvp)/2 );
1718   }
1719   draw_update ( vw );
1720 }
1721
1722 static void full_screen_cb ( GtkAction *a, VikWindow *vw )
1723 {
1724   GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/FullScreen" );
1725   g_assert(check_box);
1726   gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box));
1727   if ( state )
1728     gtk_window_fullscreen ( GTK_WINDOW(vw) );
1729   else
1730     gtk_window_unfullscreen ( GTK_WINDOW(vw) );
1731 }
1732
1733 static void draw_zoom_cb ( GtkAction *a, VikWindow *vw )
1734 {
1735   guint what = 128;
1736
1737   if (!strcmp(gtk_action_get_name(a), "ZoomIn")) {
1738     what = -3;
1739   } 
1740   else if (!strcmp(gtk_action_get_name(a), "ZoomOut")) {
1741     what = -4;
1742   }
1743   else if (!strcmp(gtk_action_get_name(a), "Zoom0.25")) {
1744     what = -2;
1745   }
1746   else if (!strcmp(gtk_action_get_name(a), "Zoom0.5")) {
1747     what = -1;
1748   }
1749   else {
1750     gchar *s = (gchar *)gtk_action_get_name(a);
1751     what = atoi(s+4);
1752   }
1753
1754   switch (what)
1755   {
1756     case -3: vik_viewport_zoom_in ( vw->viking_vvp ); break;
1757     case -4: vik_viewport_zoom_out ( vw->viking_vvp ); break;
1758     case -1: vik_viewport_set_zoom ( vw->viking_vvp, 0.5 ); break;
1759     case -2: vik_viewport_set_zoom ( vw->viking_vvp, 0.25 ); break;
1760     default: vik_viewport_set_zoom ( vw->viking_vvp, what );
1761   }
1762   draw_update ( vw );
1763 }
1764
1765 static void draw_goto_cb ( GtkAction *a, VikWindow *vw )
1766 {
1767   VikCoord new_center;
1768
1769   if (!strcmp(gtk_action_get_name(a), "GotoLL")) {
1770     struct LatLon ll, llold;
1771     vik_coord_to_latlon ( vik_viewport_get_center ( vw->viking_vvp ), &llold );
1772     if ( a_dialog_goto_latlon ( GTK_WINDOW(vw), &ll, &llold ) )
1773       vik_coord_load_from_latlon ( &new_center, vik_viewport_get_coord_mode(vw->viking_vvp), &ll );
1774     else
1775       return;
1776   }
1777   else if (!strcmp(gtk_action_get_name(a), "GotoUTM")) {
1778     struct UTM utm, utmold;
1779     vik_coord_to_utm ( vik_viewport_get_center ( vw->viking_vvp ), &utmold );
1780     if ( a_dialog_goto_utm ( GTK_WINDOW(vw), &utm, &utmold ) )
1781       vik_coord_load_from_utm ( &new_center, vik_viewport_get_coord_mode(vw->viking_vvp), &utm );
1782     else
1783      return;
1784   }
1785   else {
1786     g_critical("Houston, we've had a problem.");
1787     return;
1788   }
1789
1790   vik_viewport_set_center_coord ( vw->viking_vvp, &new_center );
1791   draw_update ( vw );
1792 }
1793
1794 /**
1795  * Refresh maps displayed
1796  */
1797 static void draw_refresh_cb ( GtkAction *a, VikWindow *vw )
1798 {
1799   // Only get 'new' maps
1800   simple_map_update ( vw, TRUE );
1801 }
1802
1803 static void menu_addlayer_cb ( GtkAction *a, VikWindow *vw )
1804 {
1805  VikLayerTypeEnum type;
1806   for ( type = 0; type < VIK_LAYER_NUM_TYPES; type++ ) {
1807     if (!strcmp(vik_layer_get_interface(type)->name, gtk_action_get_name(a))) {
1808       if ( vik_layers_panel_new_layer ( vw->viking_vlp, type ) ) {
1809         draw_update ( vw );
1810         vw->modified = TRUE;
1811       }
1812     }
1813   }
1814 }
1815
1816 static void menu_copy_layer_cb ( GtkAction *a, VikWindow *vw )
1817 {
1818   a_clipboard_copy_selected ( vw->viking_vlp );
1819 }
1820
1821 static void menu_cut_layer_cb ( GtkAction *a, VikWindow *vw )
1822 {
1823   vik_layers_panel_cut_selected ( vw->viking_vlp );
1824   vw->modified = TRUE;
1825 }
1826
1827 static void menu_paste_layer_cb ( GtkAction *a, VikWindow *vw )
1828 {
1829   if ( vik_layers_panel_paste_selected ( vw->viking_vlp ) )
1830   {
1831     vw->modified = TRUE;
1832   }
1833 }
1834
1835 static void menu_properties_cb ( GtkAction *a, VikWindow *vw )
1836 {
1837   if ( ! vik_layers_panel_properties ( vw->viking_vlp ) )
1838     a_dialog_info_msg ( GTK_WINDOW(vw), _("You must select a layer to show its properties.") );
1839 }
1840
1841 static void help_help_cb ( GtkAction *a, VikWindow *vw )
1842 {
1843 #ifdef WINDOWS
1844   ShellExecute(NULL, "open", ""PACKAGE".pdf", NULL, NULL, SW_SHOWNORMAL);
1845 #else /* WINDOWS */
1846   gchar *uri;
1847   uri = g_strdup_printf("ghelp:%s", PACKAGE);
1848   GError *error = NULL;
1849   gboolean show = gtk_show_uri (NULL, uri, GDK_CURRENT_TIME, &error);
1850   if ( !show && !error )
1851     // No error to show, so unlikely this will get called
1852     a_dialog_error_msg ( GTK_WINDOW(vw), _("The help system is not available.") );
1853   else if ( error ) {
1854     // Main error path
1855     a_dialog_error_msg_extra ( GTK_WINDOW(vw), _("Help is not available because: %s.\nEnsure a Mime Type ghelp handler program is installed (e.g. yelp)."), error->message );
1856     g_error_free ( error );
1857   }
1858   g_free(uri);
1859 #endif /* WINDOWS */
1860 }
1861
1862 static void help_about_cb ( GtkAction *a, VikWindow *vw )
1863 {
1864   a_dialog_about(GTK_WINDOW(vw));
1865 }
1866
1867 static void menu_delete_layer_cb ( GtkAction *a, VikWindow *vw )
1868 {
1869   if ( vik_layers_panel_get_selected ( vw->viking_vlp ) )
1870   {
1871     vik_layers_panel_delete_selected ( vw->viking_vlp );
1872     vw->modified = TRUE;
1873   }
1874   else
1875     a_dialog_info_msg ( GTK_WINDOW(vw), _("You must select a layer to delete.") );
1876 }
1877
1878 static void view_side_panel_cb ( GtkAction *a, VikWindow *vw )
1879 {
1880   GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ViewSidePanel" );
1881   g_assert(check_box);
1882   gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box));
1883   if ( state )
1884     gtk_widget_show(GTK_WIDGET(vw->viking_vlp));
1885   else
1886     gtk_widget_hide(GTK_WIDGET(vw->viking_vlp));
1887 }
1888
1889 static void view_statusbar_cb ( GtkAction *a, VikWindow *vw )
1890 {
1891   GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ViewStatusBar" );
1892   if ( !check_box )
1893     return;
1894   gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box) );
1895   if ( state )
1896     gtk_widget_show ( GTK_WIDGET(vw->viking_vs) );
1897   else
1898     gtk_widget_hide ( GTK_WIDGET(vw->viking_vs) );
1899 }
1900
1901 static void view_toolbar_cb ( GtkAction *a, VikWindow *vw )
1902 {
1903   GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ViewToolbar" );
1904   if ( !check_box )
1905     return;
1906   gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box) );
1907   if ( state )
1908     gtk_widget_show ( GTK_WIDGET(vw->toolbar) );
1909   else
1910     gtk_widget_hide ( GTK_WIDGET(vw->toolbar) );
1911 }
1912
1913 static void view_main_menu_cb ( GtkAction *a, VikWindow *vw )
1914 {
1915   GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ViewMainMenu" );
1916   if ( !check_box )
1917     return;
1918   gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box) );
1919   if ( !state )
1920     gtk_widget_hide ( gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu" ) );
1921   else
1922     gtk_widget_show ( gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu" ) );
1923 }
1924
1925 /***************************************
1926  ** tool management routines
1927  **
1928  ***************************************/
1929
1930 static toolbox_tools_t* toolbox_create(VikWindow *vw)
1931 {
1932   toolbox_tools_t *vt = g_new(toolbox_tools_t, 1);
1933   vt->tools = NULL;
1934   vt->n_tools = 0;
1935   vt->active_tool = -1;
1936   vt->vw = vw;
1937   return vt;
1938 }
1939
1940 static void toolbox_add_tool(toolbox_tools_t *vt, VikToolInterface *vti, gint layer_type )
1941 {
1942   vt->tools = g_renew(toolbox_tool_t, vt->tools, vt->n_tools+1);
1943   vt->tools[vt->n_tools].ti = *vti;
1944   vt->tools[vt->n_tools].layer_type = layer_type;
1945   if (vti->create) {
1946     vt->tools[vt->n_tools].state = vti->create(vt->vw, vt->vw->viking_vvp);
1947   } 
1948   else {
1949     vt->tools[vt->n_tools].state = NULL;
1950   }
1951   vt->n_tools++;
1952 }
1953
1954 static int toolbox_get_tool(toolbox_tools_t *vt, const gchar *tool_name)
1955 {
1956   int i;
1957   for (i=0; i<vt->n_tools; i++) {
1958     if (!strcmp(tool_name, vt->tools[i].ti.radioActionEntry.name)) {
1959       break;
1960     }
1961   }
1962   return i;
1963 }
1964
1965 static void toolbox_activate(toolbox_tools_t *vt, const gchar *tool_name)
1966 {
1967   int tool = toolbox_get_tool(vt, tool_name);
1968   toolbox_tool_t *t = &vt->tools[tool];
1969   VikLayer *vl = vik_layers_panel_get_selected ( vt->vw->viking_vlp );
1970
1971   if (tool == vt->n_tools) {
1972     g_critical("trying to activate a non-existent tool...");
1973     return;
1974   }
1975   /* is the tool already active? */
1976   if (vt->active_tool == tool) {
1977     return;
1978   }
1979
1980   if (vt->active_tool != -1) {
1981     if (vt->tools[vt->active_tool].ti.deactivate) {
1982       vt->tools[vt->active_tool].ti.deactivate(NULL, vt->tools[vt->active_tool].state);
1983     }
1984   }
1985   if (t->ti.activate) {
1986     t->ti.activate(vl, t->state);
1987   }
1988   vt->active_tool = tool;
1989 }
1990
1991 static const GdkCursor *toolbox_get_cursor(toolbox_tools_t *vt, const gchar *tool_name)
1992 {
1993   int tool = toolbox_get_tool(vt, tool_name);
1994   toolbox_tool_t *t = &vt->tools[tool];
1995   if (t->ti.cursor == NULL) {
1996     if (t->ti.cursor_type == GDK_CURSOR_IS_PIXMAP && t->ti.cursor_data != NULL) {
1997       GError *cursor_load_err = NULL;
1998       GdkPixbuf *cursor_pixbuf = gdk_pixbuf_from_pixdata (t->ti.cursor_data, FALSE, &cursor_load_err);
1999       /* TODO: settable offeset */
2000       t->ti.cursor = gdk_cursor_new_from_pixbuf ( gdk_display_get_default(), cursor_pixbuf, 3, 3 );
2001       g_object_unref ( G_OBJECT(cursor_pixbuf) );
2002     } else {
2003       t->ti.cursor = gdk_cursor_new ( t->ti.cursor_type );
2004     }
2005   }
2006   return t->ti.cursor;
2007 }
2008
2009 static void toolbox_click (toolbox_tools_t *vt, GdkEventButton *event)
2010 {
2011   VikLayer *vl = vik_layers_panel_get_selected ( vt->vw->viking_vlp );
2012   if (vt->active_tool != -1 && vt->tools[vt->active_tool].ti.click) {
2013     gint ltype = vt->tools[vt->active_tool].layer_type;
2014     if ( ltype == TOOL_LAYER_TYPE_NONE || (vl && ltype == vl->type) )
2015       vt->tools[vt->active_tool].ti.click(vl, event, vt->tools[vt->active_tool].state);
2016   }
2017 }
2018
2019 static void toolbox_move (toolbox_tools_t *vt, GdkEventMotion *event)
2020 {
2021   VikLayer *vl = vik_layers_panel_get_selected ( vt->vw->viking_vlp );
2022   if (vt->active_tool != -1 && vt->tools[vt->active_tool].ti.move) {
2023     gint ltype = vt->tools[vt->active_tool].layer_type;
2024     if ( ltype == TOOL_LAYER_TYPE_NONE || (vl && ltype == vl->type) )
2025       if ( VIK_LAYER_TOOL_ACK_GRAB_FOCUS == vt->tools[vt->active_tool].ti.move(vl, event, vt->tools[vt->active_tool].state) )
2026         gtk_widget_grab_focus ( GTK_WIDGET(vt->vw->viking_vvp) );
2027   }
2028 }
2029
2030 static void toolbox_release (toolbox_tools_t *vt, GdkEventButton *event)
2031 {
2032   VikLayer *vl = vik_layers_panel_get_selected ( vt->vw->viking_vlp );
2033   if (vt->active_tool != -1 && vt->tools[vt->active_tool].ti.release ) {
2034     gint ltype = vt->tools[vt->active_tool].layer_type;
2035     if ( ltype == TOOL_LAYER_TYPE_NONE || (vl && ltype == vl->type) )
2036       vt->tools[vt->active_tool].ti.release(vl, event, vt->tools[vt->active_tool].state);
2037   }
2038 }
2039 /** End tool management ************************************/
2040
2041 void vik_window_enable_layer_tool ( VikWindow *vw, gint layer_id, gint tool_id )
2042 {
2043   gtk_action_activate ( gtk_action_group_get_action ( vw->action_group, vik_layer_get_interface(layer_id)->tools[tool_id].radioActionEntry.name ) );
2044 }
2045
2046 /* this function gets called whenever a toolbar tool is clicked */
2047 static void menu_tool_cb ( GtkAction *old, GtkAction *a, VikWindow *vw )
2048 {
2049   /* White Magic, my friends ... White Magic... */
2050   gint tool_id;
2051   toolbox_activate(vw->vt, gtk_action_get_name(a));
2052
2053   vw->viewport_cursor = (GdkCursor *)toolbox_get_cursor(vw->vt, gtk_action_get_name(a));
2054
2055   if ( gtk_widget_get_window(GTK_WIDGET(vw->viking_vvp)) )
2056     /* We set cursor, even if it is NULL: it resets to default */
2057     gdk_window_set_cursor ( gtk_widget_get_window(GTK_WIDGET(vw->viking_vvp)), vw->viewport_cursor );
2058
2059   if (!strcmp(gtk_action_get_name(a), "Pan")) {
2060     vw->current_tool = TOOL_PAN;
2061   } 
2062   else if (!strcmp(gtk_action_get_name(a), "Zoom")) {
2063     vw->current_tool = TOOL_ZOOM;
2064   } 
2065   else if (!strcmp(gtk_action_get_name(a), "Ruler")) {
2066     vw->current_tool = TOOL_RULER;
2067   }
2068   else if (!strcmp(gtk_action_get_name(a), "Select")) {
2069     vw->current_tool = TOOL_SELECT;
2070   }
2071   else {
2072     /* TODO: only enable tools from active layer */
2073     VikLayerTypeEnum layer_id;
2074     for (layer_id=0; layer_id<VIK_LAYER_NUM_TYPES; layer_id++) {
2075       for ( tool_id = 0; tool_id < vik_layer_get_interface(layer_id)->tools_count; tool_id++ ) {
2076         if (!strcmp(vik_layer_get_interface(layer_id)->tools[tool_id].radioActionEntry.name, gtk_action_get_name(a))) {
2077            vw->current_tool = TOOL_LAYER;
2078            vw->tool_layer_id = layer_id;
2079            vw->tool_tool_id = tool_id;
2080         }
2081       }
2082     }
2083   }
2084   draw_status_tool ( vw );
2085 }
2086
2087 static void window_set_filename ( VikWindow *vw, const gchar *filename )
2088 {
2089   gchar *title;
2090   const gchar *file;
2091   if ( vw->filename )
2092     g_free ( vw->filename );
2093   if ( filename == NULL )
2094   {
2095     vw->filename = NULL;
2096   }
2097   else
2098   {
2099     vw->filename = g_strdup(filename);
2100   }
2101
2102   /* Refresh window's title */
2103   file = window_get_filename ( vw );
2104   title = g_strdup_printf( "%s - Viking", file );
2105   gtk_window_set_title ( GTK_WINDOW(vw), title );
2106   g_free ( title );
2107 }
2108
2109 static const gchar *window_get_filename ( VikWindow *vw )
2110 {
2111   return vw->filename ? a_file_basename ( vw->filename ) : _("Untitled");
2112 }
2113
2114 GtkWidget *vik_window_get_drawmode_button ( VikWindow *vw, VikViewportDrawMode mode )
2115 {
2116   GtkWidget *mode_button;
2117   gchar *buttonname;
2118   switch ( mode ) {
2119 #ifdef VIK_CONFIG_EXPEDIA
2120     case VIK_VIEWPORT_DRAWMODE_EXPEDIA: buttonname = "/ui/MainMenu/View/ModeExpedia"; break;
2121 #endif
2122     case VIK_VIEWPORT_DRAWMODE_MERCATOR: buttonname = "/ui/MainMenu/View/ModeMercator"; break;
2123     case VIK_VIEWPORT_DRAWMODE_LATLON: buttonname = "/ui/MainMenu/View/ModeLatLon"; break;
2124     default: buttonname = "/ui/MainMenu/View/ModeUTM";
2125   }
2126   mode_button = gtk_ui_manager_get_widget ( vw->uim, buttonname );
2127   g_assert ( mode_button );
2128   return mode_button;
2129 }
2130
2131 /**
2132  * vik_window_get_pan_move:
2133  * @vw: some VikWindow
2134  *
2135  * Retrieves @vw's pan_move.
2136  *
2137  * Should be removed as soon as possible.
2138  *
2139  * Returns: @vw's pan_move
2140  *
2141  * Since: 0.9.96
2142  **/
2143 gboolean vik_window_get_pan_move ( VikWindow *vw )
2144 {
2145   return vw->pan_move;
2146 }
2147
2148 static void on_activate_recent_item (GtkRecentChooser *chooser,
2149                                      VikWindow *self)
2150 {
2151   gchar *filename;
2152
2153   filename = gtk_recent_chooser_get_current_uri (chooser);
2154   if (filename != NULL)
2155   {
2156     GFile *file = g_file_new_for_uri ( filename );
2157     gchar *path = g_file_get_path ( file );
2158     g_object_unref ( file );
2159     if ( self->filename )
2160     {
2161       GSList *filenames = NULL;
2162       filenames = g_slist_append ( filenames, path );
2163       g_signal_emit ( G_OBJECT(self), window_signals[VW_OPENWINDOW_SIGNAL], 0, filenames );
2164       // NB: GSList & contents are freed by main.open_window
2165     }
2166     else {
2167       vik_window_open_file ( self, path, TRUE );
2168       g_free ( path );
2169     }
2170   }
2171
2172   g_free (filename);
2173 }
2174
2175 static void setup_recent_files (VikWindow *self)
2176 {
2177   GtkRecentManager *manager;
2178   GtkRecentFilter *filter;
2179   GtkWidget *menu, *menu_item;
2180
2181   filter = gtk_recent_filter_new ();
2182   /* gtk_recent_filter_add_application (filter, g_get_application_name()); */
2183   gtk_recent_filter_add_group(filter, "viking");
2184
2185   manager = gtk_recent_manager_get_default ();
2186   menu = gtk_recent_chooser_menu_new_for_manager (manager);
2187   gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER (menu), GTK_RECENT_SORT_MRU);
2188   gtk_recent_chooser_add_filter (GTK_RECENT_CHOOSER (menu), filter);
2189
2190   menu_item = gtk_ui_manager_get_widget (self->uim, "/ui/MainMenu/File/OpenRecentFile");
2191   gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), menu);
2192
2193   g_signal_connect (G_OBJECT (menu), "item-activated",
2194                     G_CALLBACK (on_activate_recent_item), (gpointer) self);
2195 }
2196
2197 static void update_recently_used_document(const gchar *filename)
2198 {
2199   /* Update Recently Used Document framework */
2200   GtkRecentManager *manager = gtk_recent_manager_get_default();
2201   GtkRecentData *recent_data = g_slice_new (GtkRecentData);
2202   gchar *groups[] = {"viking", NULL};
2203   GFile *file = g_file_new_for_commandline_arg(filename);
2204   gchar *uri = g_file_get_uri(file);
2205   gchar *basename = g_path_get_basename(filename);
2206   g_object_unref(file);
2207   file = NULL;
2208
2209   recent_data->display_name   = basename;
2210   recent_data->description    = NULL;
2211   recent_data->mime_type      = "text/x-gps-data";
2212   recent_data->app_name       = (gchar *) g_get_application_name ();
2213   recent_data->app_exec       = g_strjoin (" ", g_get_prgname (), "%f", NULL);
2214   recent_data->groups         = groups;
2215   recent_data->is_private     = FALSE;
2216   if (!gtk_recent_manager_add_full (manager, uri, recent_data))
2217   {
2218     g_warning (_("Unable to add '%s' to the list of recently used documents"), uri);
2219   }
2220
2221   g_free (uri);
2222   g_free (basename);
2223   g_free (recent_data->app_exec);
2224   g_slice_free (GtkRecentData, recent_data);
2225 }
2226
2227 /**
2228  * Call this before doing things that may take a long time and otherwise not show any other feedback
2229  *  such as loading and saving files
2230  */
2231 void vik_window_set_busy_cursor ( VikWindow *vw )
2232 {
2233   gdk_window_set_cursor ( gtk_widget_get_window(GTK_WIDGET(vw)), vw->busy_cursor );
2234   // Viewport has a separate cursor
2235   gdk_window_set_cursor ( GTK_WIDGET(vw->viking_vvp)->window, vw->busy_cursor );
2236   // Ensure cursor updated before doing stuff
2237   while( gtk_events_pending() )
2238     gtk_main_iteration();
2239 }
2240
2241 void vik_window_clear_busy_cursor ( VikWindow *vw )
2242 {
2243   gdk_window_set_cursor ( gtk_widget_get_window(GTK_WIDGET(vw)), NULL );
2244   // Restore viewport cursor
2245   gdk_window_set_cursor ( GTK_WIDGET(vw->viking_vvp)->window, vw->viewport_cursor );
2246 }
2247
2248 void vik_window_open_file ( VikWindow *vw, const gchar *filename, gboolean change_filename )
2249 {
2250   vik_window_set_busy_cursor ( vw );
2251
2252   switch ( a_file_load ( vik_layers_panel_get_top_layer(vw->viking_vlp), vw->viking_vvp, filename ) )
2253   {
2254     case LOAD_TYPE_READ_FAILURE:
2255       a_dialog_error_msg ( GTK_WINDOW(vw), _("The file you requested could not be opened.") );
2256       break;
2257     case LOAD_TYPE_GPSBABEL_FAILURE:
2258       a_dialog_error_msg ( GTK_WINDOW(vw), _("GPSBabel is required to load files of this type or GPSBabel encountered problems.") );
2259       break;
2260     case LOAD_TYPE_GPX_FAILURE:
2261       a_dialog_error_msg_extra ( GTK_WINDOW(vw), _("Unable to load malformed GPX file %s"), filename );
2262       break;
2263     case LOAD_TYPE_UNSUPPORTED_FAILURE:
2264       a_dialog_error_msg_extra ( GTK_WINDOW(vw), _("Unsupported file type for %s"), filename );
2265       break;
2266     case LOAD_TYPE_VIK_FAILURE_NON_FATAL:
2267     {
2268       // Since we can process .vik files with issues just show a warning in the status bar
2269       // Not that a user can do much about it... or tells them what this issue is yet...
2270       gchar *msg = g_strdup_printf (_("WARNING: issues encountered loading %s"), a_file_basename (filename) );
2271       vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_INFO, msg );
2272       g_free ( msg );
2273     }
2274       // No break, carry on to show any data
2275     case LOAD_TYPE_VIK_SUCCESS:
2276     {
2277       GtkWidget *mode_button;
2278       /* Update UI */
2279       if ( change_filename )
2280         window_set_filename ( vw, filename );
2281       mode_button = vik_window_get_drawmode_button ( vw, vik_viewport_get_drawmode ( vw->viking_vvp ) );
2282       vw->only_updating_coord_mode_ui = TRUE; /* if we don't set this, it will change the coord to UTM if we click Lat/Lon. I don't know why. */
2283       gtk_check_menu_item_set_active ( GTK_CHECK_MENU_ITEM(mode_button), TRUE );
2284       vw->only_updating_coord_mode_ui = FALSE;
2285
2286       vik_layers_panel_change_coord_mode ( vw->viking_vlp, vik_viewport_get_coord_mode ( vw->viking_vvp ) );
2287       
2288       mode_button = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ShowScale" );
2289       g_assert ( mode_button );
2290       gtk_check_menu_item_set_active ( GTK_CHECK_MENU_ITEM(mode_button),vik_viewport_get_draw_scale(vw->viking_vvp) );
2291
2292       mode_button = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ShowCenterMark" );
2293       g_assert ( mode_button );
2294       gtk_check_menu_item_set_active ( GTK_CHECK_MENU_ITEM(mode_button),vik_viewport_get_draw_centermark(vw->viking_vvp) );
2295       
2296       mode_button = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ShowHighlight" );
2297       g_assert ( mode_button );
2298       gtk_check_menu_item_set_active ( GTK_CHECK_MENU_ITEM(mode_button),vik_viewport_get_draw_highlight (vw->viking_vvp) );
2299     }
2300     //case LOAD_TYPE_OTHER_SUCCESS:
2301     default:
2302       update_recently_used_document(filename);
2303       draw_update ( vw );
2304       break;
2305   }
2306
2307   vik_window_clear_busy_cursor ( vw );
2308 }
2309
2310 static void load_file ( GtkAction *a, VikWindow *vw )
2311 {
2312   GSList *files = NULL;
2313   GSList *cur_file = NULL;
2314   gboolean newwindow;
2315   if (!strcmp(gtk_action_get_name(a), "Open")) {
2316     newwindow = TRUE;
2317   } 
2318   else if (!strcmp(gtk_action_get_name(a), "Append")) {
2319     newwindow = FALSE;
2320   } 
2321   else {
2322     g_critical("Houston, we've had a problem.");
2323     return;
2324   }
2325     
2326   if ( ! vw->open_dia )
2327   {
2328     vw->open_dia = gtk_file_chooser_dialog_new (_("Please select a GPS data file to open. "),
2329                                                 GTK_WINDOW(vw),
2330                                                 GTK_FILE_CHOOSER_ACTION_OPEN,
2331                                                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2332                                                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2333                                                 NULL);
2334     gchar *cwd = g_get_current_dir();
2335     if ( cwd ) {
2336       gtk_file_chooser_set_current_folder ( GTK_FILE_CHOOSER(vw->open_dia), cwd );
2337       g_free ( cwd );
2338     }
2339
2340     GtkFileFilter *filter;
2341     // NB file filters are listed this way for alphabetical ordering
2342 #ifdef VIK_CONFIG_GEOCACHES
2343     filter = gtk_file_filter_new ();
2344     gtk_file_filter_set_name( filter, _("Geocaching") );
2345     gtk_file_filter_add_pattern ( filter, "*.loc" ); // No MIME type available
2346     gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(vw->open_dia), filter);
2347 #endif
2348
2349     filter = gtk_file_filter_new ();
2350     gtk_file_filter_set_name( filter, _("Google Earth") );
2351     gtk_file_filter_add_mime_type ( filter, "application/vnd.google-earth.kml+xml");
2352     gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(vw->open_dia), filter);
2353
2354     filter = gtk_file_filter_new ();
2355     gtk_file_filter_set_name( filter, _("GPX") );
2356     gtk_file_filter_add_pattern ( filter, "*.gpx" ); // No MIME type available
2357     gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(vw->open_dia), filter);
2358
2359     filter = gtk_file_filter_new ();
2360     gtk_file_filter_set_name( filter, _("Viking") );
2361     gtk_file_filter_add_pattern ( filter, "*.vik" );
2362     gtk_file_filter_add_pattern ( filter, "*.viking" );
2363     gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(vw->open_dia), filter);
2364
2365     // NB could have filters for gpspoint (*.gps,*.gpsoint?) + gpsmapper (*.gsm,*.gpsmapper?)
2366     // However assume this are barely used and thus not worthy of inclusion
2367     //   as they'll just make the options too many and have no clear file pattern
2368     //   one can always use the all option
2369     filter = gtk_file_filter_new ();
2370     gtk_file_filter_set_name( filter, _("All") );
2371     gtk_file_filter_add_pattern ( filter, "*" );
2372     gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(vw->open_dia), filter);
2373     // Default to any file - same as before open filters were added
2374     gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(vw->open_dia), filter);
2375
2376     gtk_file_chooser_set_select_multiple ( GTK_FILE_CHOOSER(vw->open_dia), TRUE );
2377     gtk_window_set_transient_for ( GTK_WINDOW(vw->open_dia), GTK_WINDOW(vw) );
2378     gtk_window_set_destroy_with_parent ( GTK_WINDOW(vw->open_dia), TRUE );
2379   }
2380   if ( gtk_dialog_run ( GTK_DIALOG(vw->open_dia) ) == GTK_RESPONSE_ACCEPT )
2381   {
2382     gtk_widget_hide ( vw->open_dia );
2383 #ifdef VIKING_PROMPT_IF_MODIFIED
2384     if ( (vw->modified || vw->filename) && newwindow )
2385 #else
2386     if ( vw->filename && newwindow )
2387 #endif
2388       g_signal_emit ( G_OBJECT(vw), window_signals[VW_OPENWINDOW_SIGNAL], 0, gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER(vw->open_dia) ) );
2389     else {
2390       files = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER(vw->open_dia) );
2391       gboolean change_fn = newwindow && (g_slist_length(files)==1); /* only change fn if one file */
2392       gboolean first_vik_file = TRUE;
2393       cur_file = files;
2394       while ( cur_file ) {
2395
2396         gchar *file_name = cur_file->data;
2397         if ( newwindow && check_file_magic_vik ( file_name ) ) {
2398           // Load first of many .vik files in current window
2399           if ( first_vik_file ) {
2400             vik_window_open_file ( vw, file_name, TRUE );
2401             first_vik_file = FALSE;
2402           }
2403           else {
2404             // Load each subsequent .vik file in a separate window
2405             VikWindow *newvw = vik_window_new_window ();
2406             if (newvw)
2407               vik_window_open_file ( newvw, file_name, TRUE );
2408           }
2409         }
2410         else
2411           // Other file types
2412           vik_window_open_file ( vw, file_name, change_fn );
2413
2414         g_free (file_name);
2415         cur_file = g_slist_next (cur_file);
2416       }
2417       g_slist_free (files);
2418     }
2419   }
2420   else
2421     gtk_widget_hide ( vw->open_dia );
2422 }
2423
2424 static gboolean save_file_as ( GtkAction *a, VikWindow *vw )
2425 {
2426   gboolean rv = FALSE;
2427   const gchar *fn;
2428   if ( ! vw->save_dia )
2429   {
2430     vw->save_dia = gtk_file_chooser_dialog_new (_("Save as Viking File."),
2431                                                 GTK_WINDOW(vw),
2432                                                 GTK_FILE_CHOOSER_ACTION_SAVE,
2433                                                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2434                                                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2435                                                 NULL);
2436     gchar *cwd = g_get_current_dir();
2437     if ( cwd ) {
2438       gtk_file_chooser_set_current_folder ( GTK_FILE_CHOOSER(vw->save_dia), cwd );
2439       g_free ( cwd );
2440     }
2441
2442     GtkFileFilter *filter;
2443     filter = gtk_file_filter_new ();
2444     gtk_file_filter_set_name( filter, _("All") );
2445     gtk_file_filter_add_pattern ( filter, "*" );
2446     gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(vw->save_dia), filter);
2447
2448     filter = gtk_file_filter_new ();
2449     gtk_file_filter_set_name( filter, _("Viking") );
2450     gtk_file_filter_add_pattern ( filter, "*.vik" );
2451     gtk_file_filter_add_pattern ( filter, "*.viking" );
2452     gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(vw->save_dia), filter);
2453     // Default to a Viking file
2454     gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(vw->save_dia), filter);
2455
2456     gtk_window_set_transient_for ( GTK_WINDOW(vw->save_dia), GTK_WINDOW(vw) );
2457     gtk_window_set_destroy_with_parent ( GTK_WINDOW(vw->save_dia), TRUE );
2458   }
2459   // Auto append / replace extension with '.vik' to the suggested file name as it's going to be a Viking File
2460   gchar* auto_save_name = g_strdup ( window_get_filename ( vw ) );
2461   if ( ! check_file_ext ( auto_save_name, ".vik" ) )
2462     auto_save_name = g_strconcat ( auto_save_name, ".vik", NULL );
2463
2464   gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER(vw->save_dia), auto_save_name);
2465
2466   while ( gtk_dialog_run ( GTK_DIALOG(vw->save_dia) ) == GTK_RESPONSE_ACCEPT )
2467   {
2468     fn = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(vw->save_dia) );
2469     if ( g_file_test ( fn, G_FILE_TEST_EXISTS ) == FALSE || a_dialog_yes_or_no ( GTK_WINDOW(vw->save_dia), _("The file \"%s\" exists, do you wish to overwrite it?"), a_file_basename ( fn ) ) )
2470     {
2471       window_set_filename ( vw, fn );
2472       rv = window_save ( vw );
2473       vw->modified = FALSE;
2474       break;
2475     }
2476   }
2477   g_free ( auto_save_name );
2478   gtk_widget_hide ( vw->save_dia );
2479   return rv;
2480 }
2481
2482 static gboolean window_save ( VikWindow *vw )
2483 {
2484   vik_window_set_busy_cursor ( vw );
2485   gboolean success = TRUE;
2486
2487   if ( a_file_save ( vik_layers_panel_get_top_layer ( vw->viking_vlp ), vw->viking_vvp, vw->filename ) )
2488   {
2489     update_recently_used_document ( vw->filename );
2490   }
2491   else
2492   {
2493     a_dialog_error_msg ( GTK_WINDOW(vw), _("The filename you requested could not be opened for writing.") );
2494     success = FALSE;
2495   }
2496   vik_window_clear_busy_cursor ( vw );
2497   return success;
2498 }
2499
2500 static gboolean save_file ( GtkAction *a, VikWindow *vw )
2501 {
2502   if ( ! vw->filename )
2503     return save_file_as ( NULL, vw );
2504   else
2505   {
2506     vw->modified = FALSE;
2507     return window_save ( vw );
2508   }
2509 }
2510
2511 /**
2512  * export_to:
2513  *
2514  * Export all TRW Layers in the list to individual files in the specified directory
2515  *
2516  * Returns: %TRUE on success
2517  */
2518 static gboolean export_to ( VikWindow *vw, GList *gl, VikFileType_t vft, const gchar *dir, const gchar *extension )
2519 {
2520   gboolean success = TRUE;
2521
2522   gint export_count = 0;
2523
2524   vik_window_set_busy_cursor ( vw );
2525
2526   while ( gl ) {
2527
2528     gchar *fn = g_strconcat ( dir, G_DIR_SEPARATOR_S, VIK_LAYER(gl->data)->name, extension, NULL );
2529
2530     // Some protection in attempting to write too many same named files
2531     // As this will get horribly slow...
2532     gboolean safe = FALSE;
2533     gint ii = 2;
2534     while ( ii < 5000 ) {
2535       if ( g_file_test ( fn, G_FILE_TEST_EXISTS ) ) {
2536         // Try rename
2537         g_free ( fn );
2538         fn = g_strdup_printf ( "%s%s%s#%03d%s", dir, G_DIR_SEPARATOR_S, VIK_LAYER(gl->data)->name, ii, extension );
2539           }
2540           else {
2541                   safe = TRUE;
2542                   break;
2543           }
2544           ii++;
2545     }
2546     if ( ii == 5000 )
2547       success = FALSE;
2548
2549     // NB: We allow exporting empty layers
2550     if ( safe ) {
2551       gboolean this_success = a_file_export ( VIK_TRW_LAYER(gl->data), fn, vft, NULL, TRUE );
2552
2553       // Show some progress
2554       if ( this_success ) {
2555         export_count++;
2556         gchar *message = g_strconcat ( _("Exporting to file: "), fn, NULL );
2557         vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_INFO, message );
2558         while ( gtk_events_pending() )
2559           gtk_main_iteration ();
2560         g_free ( message );
2561       }
2562       
2563       success = success && this_success;
2564     }
2565
2566     g_free ( fn );
2567     gl = g_list_next ( gl );
2568   }
2569
2570   vik_window_clear_busy_cursor ( vw );
2571
2572   // Confirm what happened.
2573   gchar *message = g_strdup_printf ( _("Exported files: %d"), export_count );
2574   vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_INFO, message );
2575   g_free ( message );
2576
2577   return success;
2578 }
2579
2580 static void export_to_common ( VikWindow *vw, VikFileType_t vft, const gchar *extension )
2581 {
2582   GList *gl = vik_layers_panel_get_all_layers_of_type ( vw->viking_vlp, VIK_LAYER_TRW, TRUE );
2583
2584   if ( !gl ) {
2585     a_dialog_info_msg ( GTK_WINDOW(vw), _("Nothing to Export!") );
2586     return;
2587   }
2588
2589   GtkWidget *dialog = gtk_dialog_new_with_buttons ( _("Export to directory"),
2590                                                     GTK_WINDOW(vw),
2591                                                     GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
2592                                                     GTK_STOCK_CANCEL,
2593                                                     GTK_RESPONSE_REJECT,
2594                                                     GTK_STOCK_OK,
2595                                                     GTK_RESPONSE_ACCEPT,
2596                                                     NULL );
2597
2598   GtkWidget *gw = gtk_file_chooser_widget_new ( GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER );
2599   gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(dialog)->vbox), gw, TRUE, TRUE, 0 );
2600
2601   // try to make it a nice size - otherwise seems to default to something impractically small
2602   gtk_window_set_default_size ( GTK_WINDOW(dialog), 600, 300 );
2603
2604   gtk_widget_show_all ( dialog );
2605
2606   if ( gtk_dialog_run ( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) {
2607     gchar *dir = gtk_file_chooser_get_filename ( GTK_FILE_CHOOSER(gw) );
2608     gtk_widget_destroy ( dialog );
2609     if ( dir ) {
2610       if ( !export_to ( vw, gl, vft, dir, extension ) )
2611         a_dialog_error_msg ( GTK_WINDOW(vw),_("Could not convert all files") );
2612       g_free ( dir );
2613     }
2614   }
2615   else
2616     gtk_widget_destroy ( dialog );
2617
2618   g_list_free ( gl );
2619 }
2620
2621 static void export_to_gpx ( GtkAction *a, VikWindow *vw )
2622 {
2623   export_to_common ( vw, FILE_TYPE_GPX, ".gpx" );
2624 }
2625
2626 static void export_to_kml ( GtkAction *a, VikWindow *vw )
2627 {
2628   export_to_common ( vw, FILE_TYPE_KML, ".kml" );
2629 }
2630
2631 static void acquire_from_gps ( GtkAction *a, VikWindow *vw )
2632 {
2633   // Via the file menu, acquiring from a GPS makes a new layer
2634   //  this has always been the way (not entirely sure if this was the real intention!)
2635   //  thus maintain the behaviour ATM.
2636   // Hence explicit setting here (as the value may be changed elsewhere)
2637   vik_datasource_gps_interface.mode = VIK_DATASOURCE_CREATENEWLAYER;
2638   a_acquire(vw, vw->viking_vlp, vw->viking_vvp, &vik_datasource_gps_interface, NULL, NULL );
2639 }
2640
2641 static void acquire_from_file ( GtkAction *a, VikWindow *vw )
2642 {
2643   a_acquire(vw, vw->viking_vlp, vw->viking_vvp, &vik_datasource_file_interface, NULL, NULL );
2644 }
2645
2646 #ifdef VIK_CONFIG_GOOGLE
2647 static void acquire_from_google ( GtkAction *a, VikWindow *vw )
2648 {
2649   a_acquire(vw, vw->viking_vlp, vw->viking_vvp, &vik_datasource_google_interface, NULL, NULL );
2650 }
2651 #endif
2652
2653 #ifdef VIK_CONFIG_OPENSTREETMAP
2654 static void acquire_from_osm ( GtkAction *a, VikWindow *vw )
2655 {
2656   a_acquire(vw, vw->viking_vlp, vw->viking_vvp, &vik_datasource_osm_interface, NULL, NULL );
2657 }
2658
2659 static void acquire_from_my_osm ( GtkAction *a, VikWindow *vw )
2660 {
2661   a_acquire(vw, vw->viking_vlp, vw->viking_vvp, &vik_datasource_osm_my_traces_interface, NULL, NULL );
2662 }
2663 #endif
2664
2665 #ifdef VIK_CONFIG_GEOCACHES
2666 static void acquire_from_gc ( GtkAction *a, VikWindow *vw )
2667 {
2668   a_acquire(vw, vw->viking_vlp, vw->viking_vvp, &vik_datasource_gc_interface, NULL, NULL );
2669 }
2670 #endif
2671
2672 #ifdef VIK_CONFIG_GEOTAG
2673 static void acquire_from_geotag ( GtkAction *a, VikWindow *vw )
2674 {
2675   vik_datasource_geotag_interface.mode = VIK_DATASOURCE_CREATENEWLAYER;
2676   a_acquire(vw, vw->viking_vlp, vw->viking_vvp, &vik_datasource_geotag_interface, NULL, NULL );
2677 }
2678 #endif
2679
2680 #ifdef VIK_CONFIG_GEONAMES
2681 static void acquire_from_wikipedia ( GtkAction *a, VikWindow *vw )
2682 {
2683   a_acquire(vw, vw->viking_vlp, vw->viking_vvp, &vik_datasource_wikipedia_interface, NULL, NULL );
2684 }
2685 #endif
2686
2687 static void goto_default_location( GtkAction *a, VikWindow *vw)
2688 {
2689   struct LatLon ll;
2690   ll.lat = a_vik_get_default_lat();
2691   ll.lon = a_vik_get_default_long();
2692   vik_viewport_set_center_latlon(vw->viking_vvp, &ll);
2693   vik_layers_panel_emit_update(vw->viking_vlp);
2694 }
2695
2696
2697 static void goto_address( GtkAction *a, VikWindow *vw)
2698 {
2699   a_vik_goto ( vw, vw->viking_vvp );
2700   vik_layers_panel_emit_update ( vw->viking_vlp );
2701 }
2702
2703 static void mapcache_flush_cb ( GtkAction *a, VikWindow *vw )
2704 {
2705   a_mapcache_flush();
2706 }
2707
2708 static void layer_defaults_cb ( GtkAction *a, VikWindow *vw )
2709 {
2710   gchar **texts = g_strsplit ( gtk_action_get_name(a), "Layer", 0 );
2711
2712   if ( !texts[1] )
2713     return; // Internally broken :(
2714
2715   if ( ! a_layer_defaults_show_window ( GTK_WINDOW(vw), texts[1] ) )
2716     a_dialog_info_msg ( GTK_WINDOW(vw), _("This layer has no configurable properties.") );
2717   // NB no update needed
2718
2719   g_strfreev ( texts );
2720 }
2721
2722 static void preferences_change_update ( VikWindow *vw, gpointer data )
2723 {
2724   // Want to update all TrackWaypoint layers
2725   GList *layers = vik_layers_panel_get_all_layers_of_type ( vw->viking_vlp, VIK_LAYER_TRW, TRUE );
2726
2727   GList *iter = g_list_first ( layers );
2728   while ( iter ) {
2729     // Reset the individual waypoints themselves due to the preferences change
2730     VikTrwLayer *vtl = VIK_TRW_LAYER(VIK_LAYER(layers->data));
2731     vik_trw_layer_reset_waypoints ( vtl );
2732         iter = g_list_next ( iter );
2733   }
2734
2735   g_list_free ( layers );
2736
2737   draw_update ( vw );
2738 }
2739
2740 static void preferences_cb ( GtkAction *a, VikWindow *vw )
2741 {
2742   gboolean wp_icon_size = a_vik_get_use_large_waypoint_icons();
2743
2744   a_preferences_show_window ( GTK_WINDOW(vw) );
2745
2746   // Has the waypoint size setting changed?
2747   if (wp_icon_size != a_vik_get_use_large_waypoint_icons()) {
2748     // Delete icon indexing 'cache' and so automatically regenerates with the new setting when changed
2749     clear_garmin_icon_syms ();
2750
2751     // Update all windows
2752     g_slist_foreach ( window_list, (GFunc) preferences_change_update, NULL );
2753   }
2754 }
2755
2756 static void default_location_cb ( GtkAction *a, VikWindow *vw )
2757 {
2758   /* Simplistic repeat of preference setting
2759      Only the name & type are important for setting the preference via this 'external' way */
2760   VikLayerParam pref_lat[] = {
2761     { VIK_LAYER_NUM_TYPES,
2762       VIKING_PREFERENCES_NAMESPACE "default_latitude",
2763       VIK_LAYER_PARAM_DOUBLE,
2764       VIK_LOCATION_LAT,
2765       NULL,
2766       VIK_LAYER_WIDGET_SPINBUTTON,
2767       NULL,
2768       NULL,
2769       NULL },
2770   };
2771   VikLayerParam pref_lon[] = {
2772     { VIK_LAYER_NUM_TYPES,
2773       VIKING_PREFERENCES_NAMESPACE "default_longitude",
2774       VIK_LAYER_PARAM_DOUBLE,
2775       VIK_LOCATION_LONG,
2776       NULL,
2777       VIK_LAYER_WIDGET_SPINBUTTON,
2778       NULL,
2779       NULL,
2780       NULL },
2781   };
2782
2783   /* Get current center */
2784   struct LatLon ll;
2785   vik_coord_to_latlon ( vik_viewport_get_center ( vw->viking_vvp ), &ll );
2786
2787   /* Apply to preferences */
2788   VikLayerParamData vlp_data;
2789   vlp_data.d = ll.lat;
2790   a_preferences_run_setparam (vlp_data, pref_lat);
2791   vlp_data.d = ll.lon;
2792   a_preferences_run_setparam (vlp_data, pref_lon);
2793   /* Remember to save */
2794   a_preferences_save_to_file();
2795 }
2796
2797 static void clear_cb ( GtkAction *a, VikWindow *vw )
2798 {
2799   vik_layers_panel_clear ( vw->viking_vlp );
2800   window_set_filename ( vw, NULL );
2801   draw_update ( vw );
2802 }
2803
2804 static void window_close ( GtkAction *a, VikWindow *vw )
2805 {
2806   if ( ! delete_event ( vw ) )
2807     gtk_widget_destroy ( GTK_WIDGET(vw) );
2808 }
2809
2810 static gboolean save_file_and_exit ( GtkAction *a, VikWindow *vw )
2811 {
2812   if (save_file( NULL, vw)) {
2813     window_close( NULL, vw);
2814     return(TRUE);
2815   }
2816   else
2817     return(FALSE);
2818 }
2819
2820 static void zoom_to_cb ( GtkAction *a, VikWindow *vw )
2821 {
2822   gdouble xmpp = vik_viewport_get_xmpp ( vw->viking_vvp ), ympp = vik_viewport_get_ympp ( vw->viking_vvp );
2823   if ( a_dialog_custom_zoom ( GTK_WINDOW(vw), &xmpp, &ympp ) )
2824   {
2825     vik_viewport_set_xmpp ( vw->viking_vvp, xmpp );
2826     vik_viewport_set_ympp ( vw->viking_vvp, ympp );
2827     draw_update ( vw );
2828   }
2829 }
2830
2831 static void save_image_file ( VikWindow *vw, const gchar *fn, guint w, guint h, gdouble zoom, gboolean save_as_png )
2832 {
2833   /* more efficient way: stuff draws directly to pixbuf (fork viewport) */
2834   GdkPixbuf *pixbuf_to_save;
2835   gdouble old_xmpp, old_ympp;
2836   GError *error = NULL;
2837
2838   GtkWidget *msgbox = gtk_message_dialog_new ( GTK_WINDOW(vw),
2839                                                GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
2840                                                GTK_MESSAGE_INFO,
2841                                                GTK_BUTTONS_NONE,
2842                                                _("Generating image file...") );
2843
2844   g_signal_connect_swapped (msgbox, "response", G_CALLBACK (gtk_widget_destroy), msgbox);
2845   // Ensure dialog shown
2846   gtk_widget_show_all ( msgbox );
2847   // Try harder...
2848   vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_INFO, _("Generating image file...") );
2849   while ( gtk_events_pending() )
2850     gtk_main_iteration ();
2851   // Despite many efforts & variations, GTK on my Linux system doesn't show the actual msgbox contents :(
2852   // At least the empty box can give a clue something's going on + the statusbar msg...
2853   // Windows version under Wine OK!
2854
2855   /* backup old zoom & set new */
2856   old_xmpp = vik_viewport_get_xmpp ( vw->viking_vvp );
2857   old_ympp = vik_viewport_get_ympp ( vw->viking_vvp );
2858   vik_viewport_set_zoom ( vw->viking_vvp, zoom );
2859
2860   /* reset width and height: */
2861   vik_viewport_configure_manually ( vw->viking_vvp, w, h );
2862
2863   /* draw all layers */
2864   draw_redraw ( vw );
2865
2866   /* save buffer as file. */
2867   pixbuf_to_save = gdk_pixbuf_get_from_drawable ( NULL, GDK_DRAWABLE(vik_viewport_get_pixmap ( vw->viking_vvp )), NULL, 0, 0, 0, 0, w, h);
2868   if ( !pixbuf_to_save ) {
2869     g_warning("Failed to generate internal pixmap size: %d x %d", w, h);
2870     gtk_message_dialog_set_markup ( GTK_MESSAGE_DIALOG(msgbox), _("Failed to generate internal image.\n\nTry creating a smaller image.") );
2871     goto cleanup;
2872   }
2873
2874   gdk_pixbuf_save ( pixbuf_to_save, fn, save_as_png ? "png" : "jpeg", &error, NULL );
2875   if (error)
2876   {
2877     g_warning("Unable to write to file %s: %s", fn, error->message );
2878     gtk_message_dialog_set_markup ( GTK_MESSAGE_DIALOG(msgbox), _("Failed to generate image file.") );
2879     g_error_free (error);
2880   }
2881   else {
2882     // Success
2883     gtk_message_dialog_set_markup ( GTK_MESSAGE_DIALOG(msgbox), _("Image file generated.") );
2884   }
2885   g_object_unref ( G_OBJECT(pixbuf_to_save) );
2886
2887  cleanup:
2888   vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_INFO, "" );
2889   gtk_dialog_add_button ( GTK_DIALOG(msgbox), GTK_STOCK_OK, GTK_RESPONSE_OK );
2890   gtk_dialog_run ( GTK_DIALOG(msgbox) ); // Don't care about the result
2891
2892   /* pretend like nothing happened ;) */
2893   vik_viewport_set_xmpp ( vw->viking_vvp, old_xmpp );
2894   vik_viewport_set_ympp ( vw->viking_vvp, old_ympp );
2895   vik_viewport_configure ( vw->viking_vvp );
2896   draw_update ( vw );
2897 }
2898
2899 static void save_image_dir ( VikWindow *vw, const gchar *fn, guint w, guint h, gdouble zoom, gboolean save_as_png, guint tiles_w, guint tiles_h )
2900 {
2901   gulong size = sizeof(gchar) * (strlen(fn) + 15);
2902   gchar *name_of_file = g_malloc ( size );
2903   guint x = 1, y = 1;
2904   struct UTM utm_orig, utm;
2905
2906   /* *** copied from above *** */
2907   GdkPixbuf *pixbuf_to_save;
2908   gdouble old_xmpp, old_ympp;
2909   GError *error = NULL;
2910
2911   /* backup old zoom & set new */
2912   old_xmpp = vik_viewport_get_xmpp ( vw->viking_vvp );
2913   old_ympp = vik_viewport_get_ympp ( vw->viking_vvp );
2914   vik_viewport_set_zoom ( vw->viking_vvp, zoom );
2915
2916   /* reset width and height: do this only once for all images (same size) */
2917   vik_viewport_configure_manually ( vw->viking_vvp, w, h );
2918   /* *** end copy from above *** */
2919
2920   g_assert ( vik_viewport_get_coord_mode ( vw->viking_vvp ) == VIK_COORD_UTM );
2921
2922   g_mkdir(fn,0777);
2923
2924   utm_orig = *((const struct UTM *)vik_viewport_get_center ( vw->viking_vvp ));
2925
2926   for ( y = 1; y <= tiles_h; y++ )
2927   {
2928     for ( x = 1; x <= tiles_w; x++ )
2929     {
2930       g_snprintf ( name_of_file, size, "%s%cy%d-x%d.%s", fn, G_DIR_SEPARATOR, y, x, save_as_png ? "png" : "jpg" );
2931       utm = utm_orig;
2932       if ( tiles_w & 0x1 )
2933         utm.easting += ((gdouble)x - ceil(((gdouble)tiles_w)/2)) * (w*zoom);
2934       else
2935         utm.easting += ((gdouble)x - (((gdouble)tiles_w)+1)/2) * (w*zoom);
2936       if ( tiles_h & 0x1 ) /* odd */
2937         utm.northing -= ((gdouble)y - ceil(((gdouble)tiles_h)/2)) * (h*zoom);
2938       else /* even */
2939         utm.northing -= ((gdouble)y - (((gdouble)tiles_h)+1)/2) * (h*zoom);
2940
2941       /* move to correct place. */
2942       vik_viewport_set_center_utm ( vw->viking_vvp, &utm );
2943
2944       draw_redraw ( vw );
2945
2946       /* save buffer as file. */
2947       pixbuf_to_save = gdk_pixbuf_get_from_drawable ( NULL, GDK_DRAWABLE(vik_viewport_get_pixmap ( vw->viking_vvp )), NULL, 0, 0, 0, 0, w, h);
2948       gdk_pixbuf_save ( pixbuf_to_save, name_of_file, save_as_png ? "png" : "jpeg", &error, NULL );
2949       if (error)
2950       {
2951         g_warning("Unable to write to file %s: %s", name_of_file, error->message );
2952         g_error_free (error);
2953       }
2954
2955       g_object_unref ( G_OBJECT(pixbuf_to_save) );
2956     }
2957   }
2958
2959   vik_viewport_set_center_utm ( vw->viking_vvp, &utm_orig );
2960   vik_viewport_set_xmpp ( vw->viking_vvp, old_xmpp );
2961   vik_viewport_set_ympp ( vw->viking_vvp, old_ympp );
2962   vik_viewport_configure ( vw->viking_vvp );
2963   draw_update ( vw );
2964
2965   g_free ( name_of_file );
2966 }
2967
2968 static void draw_to_image_file_current_window_cb(GtkWidget* widget,GdkEventButton *event,gpointer *pass_along)
2969 {
2970   VikWindow *vw = VIK_WINDOW(pass_along[0]);
2971   GtkSpinButton *width_spin = GTK_SPIN_BUTTON(pass_along[1]), *height_spin = GTK_SPIN_BUTTON(pass_along[2]);
2972
2973   gint active = gtk_combo_box_get_active ( GTK_COMBO_BOX(pass_along[3]) );
2974   gdouble zoom = pow (2, active-2 );
2975
2976   gdouble width_min, width_max, height_min, height_max;
2977   gint width, height;
2978
2979   gtk_spin_button_get_range ( width_spin, &width_min, &width_max );
2980   gtk_spin_button_get_range ( height_spin, &height_min, &height_max );
2981
2982   /* TODO: support for xzoom and yzoom values */
2983   width = vik_viewport_get_width ( vw->viking_vvp ) * vik_viewport_get_xmpp ( vw->viking_vvp ) / zoom;
2984   height = vik_viewport_get_height ( vw->viking_vvp ) * vik_viewport_get_xmpp ( vw->viking_vvp ) / zoom;
2985
2986   if ( width > width_max || width < width_min || height > height_max || height < height_min )
2987     a_dialog_info_msg ( GTK_WINDOW(vw), _("Viewable region outside allowable pixel size bounds for image. Clipping width/height values.") );
2988
2989   gtk_spin_button_set_value ( width_spin, width );
2990   gtk_spin_button_set_value ( height_spin, height );
2991 }
2992
2993 static void draw_to_image_file_total_area_cb (GtkSpinButton *spinbutton, gpointer *pass_along)
2994 {
2995   GtkSpinButton *width_spin = GTK_SPIN_BUTTON(pass_along[1]), *height_spin = GTK_SPIN_BUTTON(pass_along[2]);
2996
2997   gint active = gtk_combo_box_get_active ( GTK_COMBO_BOX(pass_along[3]) );
2998   gdouble zoom = pow (2, active-2 );
2999
3000   gchar *label_text;
3001   gdouble w, h;
3002   w = gtk_spin_button_get_value(width_spin) * zoom;
3003   h = gtk_spin_button_get_value(height_spin) * zoom;
3004   if (pass_along[4]) /* save many images; find TOTAL area covered */
3005   {
3006     w *= gtk_spin_button_get_value(GTK_SPIN_BUTTON(pass_along[4]));
3007     h *= gtk_spin_button_get_value(GTK_SPIN_BUTTON(pass_along[5]));
3008   }
3009   vik_units_distance_t dist_units = a_vik_get_units_distance ();
3010   switch (dist_units) {
3011   case VIK_UNITS_DISTANCE_KILOMETRES:
3012     label_text = g_strdup_printf ( _("Total area: %ldm x %ldm (%.3f sq. km)"), (glong)w, (glong)h, (w*h/1000000));
3013     break;
3014   case VIK_UNITS_DISTANCE_MILES:
3015     label_text = g_strdup_printf ( _("Total area: %ldm x %ldm (%.3f sq. miles)"), (glong)w, (glong)h, (w*h/2589988.11));
3016     break;
3017   default:
3018     label_text = g_strdup_printf ("Just to keep the compiler happy");
3019     g_critical("Houston, we've had a problem. distance=%d", dist_units);
3020   }
3021
3022   gtk_label_set_text(GTK_LABEL(pass_along[6]), label_text);
3023   g_free ( label_text );
3024 }
3025
3026 /*
3027  * Get an allocated filename (or directory as specified)
3028  */
3029 static gchar* draw_image_filename ( VikWindow *vw, gboolean one_image_only )
3030 {
3031   gchar *fn = NULL;
3032   if ( one_image_only )
3033   {
3034     // Single file
3035     if (!vw->save_img_dia) {
3036       vw->save_img_dia = gtk_file_chooser_dialog_new (_("Save Image"),
3037                                                       GTK_WINDOW(vw),
3038                                                       GTK_FILE_CHOOSER_ACTION_SAVE,
3039                                                       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
3040                                                       GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
3041                                                       NULL);
3042
3043       gchar *cwd = g_get_current_dir();
3044       if ( cwd ) {
3045         gtk_file_chooser_set_current_folder ( GTK_FILE_CHOOSER(vw->save_img_dia), cwd );
3046         g_free ( cwd );
3047       }
3048
3049       GtkFileChooser *chooser = GTK_FILE_CHOOSER ( vw->save_img_dia );
3050       /* Add filters */
3051       GtkFileFilter *filter;
3052       filter = gtk_file_filter_new ();
3053       gtk_file_filter_set_name ( filter, _("All") );
3054       gtk_file_filter_add_pattern ( filter, "*" );
3055       gtk_file_chooser_add_filter ( chooser, filter );
3056
3057       filter = gtk_file_filter_new ();
3058       gtk_file_filter_set_name ( filter, _("JPG") );
3059       gtk_file_filter_add_mime_type ( filter, "image/jpeg");
3060       gtk_file_chooser_add_filter ( chooser, filter );
3061
3062       if ( !vw->draw_image_save_as_png )
3063         gtk_file_chooser_set_filter ( chooser, filter );
3064
3065       filter = gtk_file_filter_new ();
3066       gtk_file_filter_set_name ( filter, _("PNG") );
3067       gtk_file_filter_add_mime_type ( filter, "image/png");
3068       gtk_file_chooser_add_filter ( chooser, filter );
3069
3070       if ( vw->draw_image_save_as_png )
3071         gtk_file_chooser_set_filter ( chooser, filter );
3072
3073       gtk_window_set_transient_for ( GTK_WINDOW(vw->save_img_dia), GTK_WINDOW(vw) );
3074       gtk_window_set_destroy_with_parent ( GTK_WINDOW(vw->save_img_dia), TRUE );
3075     }
3076
3077     if ( gtk_dialog_run ( GTK_DIALOG(vw->save_img_dia) ) == GTK_RESPONSE_ACCEPT ) {
3078       fn = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(vw->save_img_dia) );
3079       if ( g_file_test ( fn, G_FILE_TEST_EXISTS ) )
3080         if ( ! a_dialog_yes_or_no ( GTK_WINDOW(vw->save_img_dia), _("The file \"%s\" exists, do you wish to overwrite it?"), a_file_basename ( fn ) ) )
3081           fn = NULL;
3082     }
3083     gtk_widget_hide ( vw->save_img_dia );
3084   }
3085   else {
3086     // A directory
3087     // For some reason this method is only written to work in UTM...
3088     if ( vik_viewport_get_coord_mode(vw->viking_vvp) != VIK_COORD_UTM ) {
3089       a_dialog_error_msg ( GTK_WINDOW(vw), _("You must be in UTM mode to use this feature") );
3090       return fn;
3091     }
3092
3093     if (!vw->save_img_dir_dia) {
3094       vw->save_img_dir_dia = gtk_file_chooser_dialog_new (_("Choose a directory to hold images"),
3095                                                           GTK_WINDOW(vw),
3096                                                           GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
3097                                                           GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
3098                                                           GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
3099                                                           NULL);
3100       gtk_window_set_transient_for ( GTK_WINDOW(vw->save_img_dir_dia), GTK_WINDOW(vw) );
3101       gtk_window_set_destroy_with_parent ( GTK_WINDOW(vw->save_img_dir_dia), TRUE );
3102     }
3103
3104     if ( gtk_dialog_run ( GTK_DIALOG(vw->save_img_dir_dia) ) == GTK_RESPONSE_ACCEPT ) {
3105       fn = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(vw->save_img_dir_dia) );
3106     }
3107     gtk_widget_hide ( vw->save_img_dir_dia );
3108   }
3109   return fn;
3110 }
3111
3112 static void draw_to_image_file ( VikWindow *vw, gboolean one_image_only )
3113 {
3114   /* todo: default for answers inside VikWindow or static (thruout instance) */
3115   GtkWidget *dialog = gtk_dialog_new_with_buttons ( _("Save to Image File"), GTK_WINDOW(vw),
3116                                                   GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
3117                                                   GTK_STOCK_CANCEL,
3118                                                   GTK_RESPONSE_REJECT,
3119                                                   GTK_STOCK_OK,
3120                                                   GTK_RESPONSE_ACCEPT,
3121                                                   NULL );
3122   GtkWidget *width_label, *width_spin, *height_label, *height_spin;
3123   GtkWidget *png_radio, *jpeg_radio;
3124   GtkWidget *current_window_button;
3125   gpointer current_window_pass_along[7];
3126   GtkWidget *zoom_label, *zoom_combo;
3127   GtkWidget *total_size_label;
3128
3129   /* only used if (!one_image_only) */
3130   GtkWidget *tiles_width_spin = NULL, *tiles_height_spin = NULL;
3131
3132   width_label = gtk_label_new ( _("Width (pixels):") );
3133   width_spin = gtk_spin_button_new ( GTK_ADJUSTMENT(gtk_adjustment_new ( vw->draw_image_width, 10, 50000, 10, 100, 0 )), 10, 0 );
3134   height_label = gtk_label_new ( _("Height (pixels):") );
3135   height_spin = gtk_spin_button_new ( GTK_ADJUSTMENT(gtk_adjustment_new ( vw->draw_image_height, 10, 50000, 10, 100, 0 )), 10, 0 );
3136 #ifdef WINDOWS
3137   GtkWidget *win_warning_label = gtk_label_new ( _("WARNING: USING LARGE IMAGES OVER 10000x10000\nMAY CRASH THE PROGRAM!") );
3138 #endif
3139   zoom_label = gtk_label_new ( _("Zoom (meters per pixel):") );
3140   /* TODO: separate xzoom and yzoom factors */
3141   zoom_combo = create_zoom_combo_all_levels();
3142
3143   gdouble mpp = vik_viewport_get_xmpp(vw->viking_vvp);
3144   gint active = 2 + round ( log (mpp) / log (2) );
3145
3146   // Can we not hard code size here?
3147   if ( active > 17 )
3148     active = 17;
3149   if ( active < 0 )
3150     active = 0;
3151   gtk_combo_box_set_active ( GTK_COMBO_BOX(zoom_combo), active );
3152
3153   total_size_label = gtk_label_new ( NULL );
3154
3155   current_window_button = gtk_button_new_with_label ( _("Area in current viewable window") );
3156   current_window_pass_along [0] = vw;
3157   current_window_pass_along [1] = width_spin;
3158   current_window_pass_along [2] = height_spin;
3159   current_window_pass_along [3] = zoom_combo;
3160   current_window_pass_along [4] = NULL; /* used for one_image_only != 1 */
3161   current_window_pass_along [5] = NULL;
3162   current_window_pass_along [6] = total_size_label;
3163   g_signal_connect ( G_OBJECT(current_window_button), "button_press_event", G_CALLBACK(draw_to_image_file_current_window_cb), current_window_pass_along );
3164
3165   png_radio = gtk_radio_button_new_with_label ( NULL, _("Save as PNG") );
3166   jpeg_radio = gtk_radio_button_new_with_label_from_widget ( GTK_RADIO_BUTTON(png_radio), _("Save as JPEG") );
3167
3168   if ( ! vw->draw_image_save_as_png )
3169     gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(jpeg_radio), TRUE );
3170
3171   gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), width_label, FALSE, FALSE, 0);
3172   gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), width_spin, FALSE, FALSE, 0);
3173   gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), height_label, FALSE, FALSE, 0);
3174   gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), height_spin, FALSE, FALSE, 0);
3175 #ifdef WINDOWS
3176   gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), win_warning_label, FALSE, FALSE, 0);
3177 #endif
3178   gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), current_window_button, FALSE, FALSE, 0);
3179   gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), png_radio, FALSE, FALSE, 0);
3180   gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), jpeg_radio, FALSE, FALSE, 0);
3181   gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), zoom_label, FALSE, FALSE, 0);
3182   gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), zoom_combo, FALSE, FALSE, 0);
3183
3184   if ( ! one_image_only )
3185   {
3186     GtkWidget *tiles_width_label, *tiles_height_label;
3187
3188     tiles_width_label = gtk_label_new ( _("East-west image tiles:") );
3189     tiles_width_spin = gtk_spin_button_new ( GTK_ADJUSTMENT(gtk_adjustment_new ( 5, 1, 10, 1, 100, 0 )), 1, 0 );
3190     tiles_height_label = gtk_label_new ( _("North-south image tiles:") );
3191     tiles_height_spin = gtk_spin_button_new ( GTK_ADJUSTMENT(gtk_adjustment_new ( 5, 1, 10, 1, 100, 0 )), 1, 0 );
3192     gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), tiles_width_label, FALSE, FALSE, 0);
3193     gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), tiles_width_spin, FALSE, FALSE, 0);
3194     gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), tiles_height_label, FALSE, FALSE, 0);
3195     gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), tiles_height_spin, FALSE, FALSE, 0);
3196
3197     current_window_pass_along [4] = tiles_width_spin;
3198     current_window_pass_along [5] = tiles_height_spin;
3199     g_signal_connect ( G_OBJECT(tiles_width_spin), "value-changed", G_CALLBACK(draw_to_image_file_total_area_cb), current_window_pass_along );
3200     g_signal_connect ( G_OBJECT(tiles_height_spin), "value-changed", G_CALLBACK(draw_to_image_file_total_area_cb), current_window_pass_along );
3201   }
3202   gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), total_size_label, FALSE, FALSE, 0);
3203   g_signal_connect ( G_OBJECT(width_spin), "value-changed", G_CALLBACK(draw_to_image_file_total_area_cb), current_window_pass_along );
3204   g_signal_connect ( G_OBJECT(height_spin), "value-changed", G_CALLBACK(draw_to_image_file_total_area_cb), current_window_pass_along );
3205   g_signal_connect ( G_OBJECT(zoom_combo), "changed", G_CALLBACK(draw_to_image_file_total_area_cb), current_window_pass_along );
3206
3207   draw_to_image_file_total_area_cb ( NULL, current_window_pass_along ); /* set correct size info now */
3208
3209   gtk_dialog_set_default_response ( GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT );
3210
3211   gtk_widget_show_all ( gtk_dialog_get_content_area(GTK_DIALOG(dialog)) );
3212
3213   if ( gtk_dialog_run ( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT )
3214   {
3215     gtk_widget_hide ( GTK_WIDGET(dialog) );
3216
3217     gchar *fn = draw_image_filename ( vw, one_image_only );
3218     if ( !fn )
3219       return;
3220
3221     gint active = gtk_combo_box_get_active ( GTK_COMBO_BOX(zoom_combo) );
3222     gdouble zoom = pow (2, active-2 );
3223
3224     if ( one_image_only )
3225       save_image_file ( vw, fn, 
3226                       vw->draw_image_width = gtk_spin_button_get_value_as_int ( GTK_SPIN_BUTTON(width_spin) ),
3227                       vw->draw_image_height = gtk_spin_button_get_value_as_int ( GTK_SPIN_BUTTON(height_spin) ),
3228                       zoom,
3229                       vw->draw_image_save_as_png = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(png_radio) ) );
3230     else {
3231       // NB is in UTM mode ATM
3232       save_image_dir ( vw, fn,
3233                        vw->draw_image_width = gtk_spin_button_get_value_as_int ( GTK_SPIN_BUTTON(width_spin) ),
3234                        vw->draw_image_height = gtk_spin_button_get_value_as_int ( GTK_SPIN_BUTTON(height_spin) ),
3235                        zoom,
3236                        vw->draw_image_save_as_png = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(png_radio) ),
3237                        gtk_spin_button_get_value ( GTK_SPIN_BUTTON(tiles_width_spin) ),
3238                        gtk_spin_button_get_value ( GTK_SPIN_BUTTON(tiles_height_spin) ) );
3239     }
3240
3241     g_free ( fn );
3242   }
3243   gtk_widget_destroy ( GTK_WIDGET(dialog) );
3244 }
3245
3246
3247 static void draw_to_image_file_cb ( GtkAction *a, VikWindow *vw )
3248 {
3249   draw_to_image_file ( vw, TRUE );
3250 }
3251
3252 static void draw_to_image_dir_cb ( GtkAction *a, VikWindow *vw )
3253 {
3254   draw_to_image_file ( vw, FALSE );
3255 }
3256
3257 static void print_cb ( GtkAction *a, VikWindow *vw )
3258 {
3259   a_print(vw, vw->viking_vvp);
3260 }
3261
3262 /* really a misnomer: changes coord mode (actual coordinates) AND/OR draw mode (viewport only) */
3263 static void window_change_coord_mode_cb ( GtkAction *old_a, GtkAction *a, VikWindow *vw )
3264 {
3265   VikViewportDrawMode drawmode;
3266   if (!strcmp(gtk_action_get_name(a), "ModeUTM")) {
3267     drawmode = VIK_VIEWPORT_DRAWMODE_UTM;
3268   }
3269   else if (!strcmp(gtk_action_get_name(a), "ModeLatLon")) {
3270     drawmode = VIK_VIEWPORT_DRAWMODE_LATLON;
3271   }
3272   else if (!strcmp(gtk_action_get_name(a), "ModeExpedia")) {
3273     drawmode = VIK_VIEWPORT_DRAWMODE_EXPEDIA;
3274   }
3275   else if (!strcmp(gtk_action_get_name(a), "ModeMercator")) {
3276     drawmode = VIK_VIEWPORT_DRAWMODE_MERCATOR;
3277   }
3278   else {
3279     g_critical("Houston, we've had a problem.");
3280     return;
3281   }
3282
3283   if ( !vw->only_updating_coord_mode_ui )
3284   {
3285     VikViewportDrawMode olddrawmode = vik_viewport_get_drawmode ( vw->viking_vvp );
3286     if ( olddrawmode != drawmode )
3287     {
3288       /* this takes care of coord mode too */
3289       vik_viewport_set_drawmode ( vw->viking_vvp, drawmode );
3290       if ( drawmode == VIK_VIEWPORT_DRAWMODE_UTM ) {
3291         vik_layers_panel_change_coord_mode ( vw->viking_vlp, VIK_COORD_UTM );
3292       } else if ( olddrawmode == VIK_VIEWPORT_DRAWMODE_UTM ) {
3293         vik_layers_panel_change_coord_mode ( vw->viking_vlp, VIK_COORD_LATLON );
3294       }
3295       draw_update ( vw );
3296     }
3297   }
3298 }
3299
3300 static void set_draw_scale ( GtkAction *a, VikWindow *vw )
3301 {
3302   GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ShowScale" );
3303   g_assert(check_box);
3304   gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box));
3305   vik_viewport_set_draw_scale ( vw->viking_vvp, state );
3306   draw_update ( vw );
3307 }
3308
3309 static void set_draw_centermark ( GtkAction *a, VikWindow *vw )
3310 {
3311   GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ShowCenterMark" );
3312   g_assert(check_box);
3313   gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box));
3314   vik_viewport_set_draw_centermark ( vw->viking_vvp, state );
3315   draw_update ( vw );
3316 }
3317
3318 static void set_draw_highlight ( GtkAction *a, VikWindow *vw )
3319 {
3320   GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ShowHighlight" );
3321   g_assert(check_box);
3322   gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box));
3323   vik_viewport_set_draw_highlight (  vw->viking_vvp, state );
3324   draw_update ( vw );
3325 }
3326
3327 static void set_bg_color ( GtkAction *a, VikWindow *vw )
3328 {
3329   GtkWidget *colorsd = gtk_color_selection_dialog_new ( _("Choose a background color") );
3330   GdkColor *color = vik_viewport_get_background_gdkcolor ( vw->viking_vvp );
3331   gtk_color_selection_set_previous_color ( GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(colorsd))), color );
3332   gtk_color_selection_set_current_color ( GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(colorsd))), color );
3333   if ( gtk_dialog_run ( GTK_DIALOG(colorsd) ) == GTK_RESPONSE_OK )
3334   {
3335     gtk_color_selection_get_current_color ( GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(colorsd))), color );
3336     vik_viewport_set_background_gdkcolor ( vw->viking_vvp, color );
3337     draw_update ( vw );
3338   }
3339   g_free ( color );
3340   gtk_widget_destroy ( colorsd );
3341 }
3342
3343 static void set_highlight_color ( GtkAction *a, VikWindow *vw )
3344 {
3345   GtkWidget *colorsd = gtk_color_selection_dialog_new ( _("Choose a track highlight color") );
3346   GdkColor *color = vik_viewport_get_highlight_gdkcolor ( vw->viking_vvp );
3347   gtk_color_selection_set_previous_color ( GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(colorsd))), color );
3348   gtk_color_selection_set_current_color ( GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(colorsd))), color );
3349   if ( gtk_dialog_run ( GTK_DIALOG(colorsd) ) == GTK_RESPONSE_OK )
3350   {
3351     gtk_color_selection_get_current_color ( GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(colorsd))), color );
3352     vik_viewport_set_highlight_gdkcolor ( vw->viking_vvp, color );
3353     draw_update ( vw );
3354   }
3355   g_free ( color );
3356   gtk_widget_destroy ( colorsd );
3357 }
3358
3359
3360
3361 /***********************************************************************************************
3362  ** GUI Creation
3363  ***********************************************************************************************/
3364
3365 static GtkActionEntry entries[] = {
3366   { "File", NULL, N_("_File"), 0, 0, 0 },
3367   { "Edit", NULL, N_("_Edit"), 0, 0, 0 },
3368   { "View", NULL, N_("_View"), 0, 0, 0 },
3369   { "SetShow", NULL, N_("_Show"), 0, 0, 0 },
3370   { "SetZoom", NULL, N_("_Zoom"), 0, 0, 0 },
3371   { "SetPan", NULL, N_("_Pan"), 0, 0, 0 },
3372   { "Layers", NULL, N_("_Layers"), 0, 0, 0 },
3373   { "Tools", NULL, N_("_Tools"), 0, 0, 0 },
3374   { "Exttools", NULL, N_("_Webtools"), 0, 0, 0 },
3375   { "Help", NULL, N_("_Help"), 0, 0, 0 },
3376
3377   { "New",       GTK_STOCK_NEW,          N_("_New"),                          "<control>N", N_("New file"),                                     (GCallback)newwindow_cb          },
3378   { "Open",      GTK_STOCK_OPEN,         N_("_Open..."),                         "<control>O", N_("Open a file"),                                  (GCallback)load_file             },
3379   { "OpenRecentFile", NULL,              N_("Open _Recent File"),         NULL,         NULL,                                               (GCallback)NULL },
3380   { "Append",    GTK_STOCK_ADD,          N_("Append _File..."),           NULL,         N_("Append data from a different file"),            (GCallback)load_file             },
3381   { "Export",    GTK_STOCK_CONVERT,      N_("_Export All"),               NULL,         N_("Export All TrackWaypoint Layers"),              (GCallback)NULL                  },
3382   { "ExportGPX", NULL,                   N_("_GPX..."),                       NULL,         N_("Export as GPX"),                                (GCallback)export_to_gpx         },
3383   { "Acquire",   GTK_STOCK_GO_DOWN,      N_("A_cquire"),                  NULL,         NULL,                                               (GCallback)NULL },
3384   { "AcquireGPS",   NULL,                N_("From _GPS..."),              NULL,         N_("Transfer data from a GPS device"),              (GCallback)acquire_from_gps      },
3385   { "AcquireGPSBabel",   NULL,                N_("Import File With GPS_Babel..."),                NULL,         N_("Import file via GPSBabel converter"),              (GCallback)acquire_from_file      },
3386 #ifdef VIK_CONFIG_GOOGLE
3387   { "AcquireGoogle",   NULL,             N_("Google _Directions..."),     NULL,         N_("Get driving directions from Google"),           (GCallback)acquire_from_google   },
3388 #endif
3389 #ifdef VIK_CONFIG_OPENSTREETMAP
3390   { "AcquireOSM",   NULL,                 N_("_OSM Traces..."),           NULL,         N_("Get traces from OpenStreetMap"),            (GCallback)acquire_from_osm       },
3391   { "AcquireMyOSM", NULL,                 N_("_My OSM Traces..."),        NULL,         N_("Get Your Own Traces from OpenStreetMap"),   (GCallback)acquire_from_my_osm    },
3392 #endif
3393 #ifdef VIK_CONFIG_GEOCACHES
3394   { "AcquireGC",   NULL,                 N_("Geo_caches..."),             NULL,         N_("Get Geocaches from geocaching.com"),            (GCallback)acquire_from_gc       },
3395 #endif
3396 #ifdef VIK_CONFIG_GEOTAG
3397   { "AcquireGeotag", NULL,               N_("From Geotagged _Images..."), NULL,         N_("Create waypoints from geotagged images"),       (GCallback)acquire_from_geotag   },
3398 #endif
3399 #ifdef VIK_CONFIG_GEONAMES
3400   { "AcquireWikipedia", NULL,            N_("From _Wikipedia Waypoints"), NULL,         N_("Create waypoints from Wikipedia items in the current view"), (GCallback)acquire_from_wikipedia },
3401 #endif
3402   { "Save",      GTK_STOCK_SAVE,         N_("_Save"),                         "<control>S", N_("Save the file"),                                (GCallback)save_file             },
3403   { "SaveAs",    GTK_STOCK_SAVE_AS,      N_("Save _As..."),                      NULL,  N_("Save the file under different name"),           (GCallback)save_file_as          },
3404   { "GenImg",    GTK_STOCK_CLEAR,        N_("_Generate Image File..."),          NULL,  N_("Save a snapshot of the workspace into a file"), (GCallback)draw_to_image_file_cb },
3405   { "GenImgDir", GTK_STOCK_DND_MULTIPLE, N_("Generate _Directory of Images..."), NULL,  N_("FIXME:IMGDIR"),                                 (GCallback)draw_to_image_dir_cb  },
3406   { "Print",    GTK_STOCK_PRINT,        N_("_Print..."),          NULL,         N_("Print maps"), (GCallback)print_cb },
3407   { "Exit",      GTK_STOCK_QUIT,         N_("E_xit"),                         "<control>W", N_("Exit the program"),                             (GCallback)window_close          },
3408   { "SaveExit",  GTK_STOCK_QUIT,         N_("Save and Exit"),                 NULL, N_("Save and Exit the program"),                             (GCallback)save_file_and_exit          },
3409
3410   { "GotoDefaultLocation", GTK_STOCK_HOME, N_("Go to the _Default Location"),  NULL,         N_("Go to the default location"),                     (GCallback)goto_default_location },
3411   { "GotoSearch", GTK_STOCK_JUMP_TO,     N_("Go to _Location..."),            NULL,         N_("Go to address/place using text search"),        (GCallback)goto_address       },
3412   { "GotoLL",    GTK_STOCK_JUMP_TO,      N_("_Go to Lat/Lon..."),           NULL,         N_("Go to arbitrary lat/lon coordinate"),         (GCallback)draw_goto_cb          },
3413   { "GotoUTM",   GTK_STOCK_JUMP_TO,      N_("Go to UTM..."),                  NULL,         N_("Go to arbitrary UTM coordinate"),               (GCallback)draw_goto_cb          },
3414   { "Refresh",   GTK_STOCK_REFRESH,      N_("_Refresh"),                      "F5",         N_("Refresh any maps displayed"),               (GCallback)draw_refresh_cb       },
3415   { "SetHLColor",GTK_STOCK_SELECT_COLOR, N_("Set _Highlight Color..."),       NULL,         NULL,                                           (GCallback)set_highlight_color   },
3416   { "SetBGColor",GTK_STOCK_SELECT_COLOR, N_("Set Bac_kground Color..."),      NULL,         NULL,                                           (GCallback)set_bg_color          },
3417   { "ZoomIn",    GTK_STOCK_ZOOM_IN,      N_("Zoom _In"),                   "<control>plus", NULL,                                           (GCallback)draw_zoom_cb          },
3418   { "ZoomOut",   GTK_STOCK_ZOOM_OUT,     N_("Zoom _Out"),                 "<control>minus", NULL,                                           (GCallback)draw_zoom_cb          },
3419   { "ZoomTo",    GTK_STOCK_ZOOM_FIT,     N_("Zoom _To..."),               "<control>Z", NULL,                                           (GCallback)zoom_to_cb            },
3420   { "PanNorth",  NULL,                   N_("Pan _North"),                "<control>Up",    NULL,                                           (GCallback)draw_pan_cb },
3421   { "PanEast",   NULL,                   N_("Pan _East"),                 "<control>Right", NULL,                                           (GCallback)draw_pan_cb },
3422   { "PanSouth",  NULL,                   N_("Pan _South"),                "<control>Down",  NULL,                                           (GCallback)draw_pan_cb },
3423   { "PanWest",   NULL,                   N_("Pan _West"),                 "<control>Left",  NULL,                                           (GCallback)draw_pan_cb },
3424   { "BGJobs",    GTK_STOCK_EXECUTE,      N_("Background _Jobs"),              NULL,         NULL,                                           (GCallback)a_background_show_window },
3425
3426   { "Cut",       GTK_STOCK_CUT,          N_("Cu_t"),                          NULL,         NULL,                                           (GCallback)menu_cut_layer_cb     },
3427   { "Copy",      GTK_STOCK_COPY,         N_("_Copy"),                         NULL,         NULL,                                           (GCallback)menu_copy_layer_cb    },
3428   { "Paste",     GTK_STOCK_PASTE,        N_("_Paste"),                        NULL,         NULL,                                           (GCallback)menu_paste_layer_cb   },
3429   { "Delete",    GTK_STOCK_DELETE,       N_("_Delete"),                       NULL,         NULL,                                           (GCallback)menu_delete_layer_cb  },
3430   { "DeleteAll", NULL,                   N_("Delete All"),                    NULL,         NULL,                                           (GCallback)clear_cb              },
3431   { "MapCacheFlush",NULL,                N_("_Flush Map Cache"),              NULL,         NULL,                                           (GCallback)mapcache_flush_cb     },
3432   { "SetDefaultLocation", GTK_STOCK_GO_FORWARD, N_("_Set the Default Location"), NULL, N_("Set the Default Location to the current position"),(GCallback)default_location_cb },
3433   { "Preferences",GTK_STOCK_PREFERENCES, N_("_Preferences"),                  NULL,         NULL,                                           (GCallback)preferences_cb              },
3434   { "LayerDefaults",GTK_STOCK_PROPERTIES, N_("_Layer Defaults"),             NULL,         NULL,                                           NULL },
3435   { "Properties",GTK_STOCK_PROPERTIES,   N_("_Properties"),                   NULL,         NULL,                                           (GCallback)menu_properties_cb    },
3436
3437   { "HelpEntry", GTK_STOCK_HELP,         N_("_Help"),                         "F1",         NULL,                                           (GCallback)help_help_cb     },
3438   { "About",     GTK_STOCK_ABOUT,        N_("_About"),                        NULL,         NULL,                                           (GCallback)help_about_cb    },
3439 };
3440
3441 static GtkActionEntry entries_gpsbabel[] = {
3442   { "ExportKML", NULL,                   N_("_KML..."),                       NULL,         N_("Export as KML"),                                (GCallback)export_to_kml },
3443 };
3444
3445 /* Radio items */
3446 /* FIXME use VIEWPORT_DRAWMODE values */
3447 static GtkRadioActionEntry mode_entries[] = {
3448   { "ModeUTM",         NULL,         N_("_UTM Mode"),               "<control>u", NULL, 0 },
3449   { "ModeExpedia",     NULL,         N_("_Expedia Mode"),           "<control>e", NULL, 1 },
3450   { "ModeMercator",    NULL,         N_("_Mercator Mode"),            "<control>m", NULL, 4 },
3451   { "ModeLatLon",      NULL,         N_("Lat_/Lon Mode"),           "<control>l", NULL, 5 },
3452 };
3453
3454 static GtkToggleActionEntry toggle_entries[] = {
3455   { "ShowScale",      NULL,                 N_("Show _Scale"),               "<shift>F5",  N_("Show Scale"),                              (GCallback)set_draw_scale, TRUE },
3456   { "ShowCenterMark", NULL,                 N_("Show _Center Mark"),         "F6",         N_("Show Center Mark"),                        (GCallback)set_draw_centermark, TRUE },
3457   { "ShowHighlight",  GTK_STOCK_UNDERLINE,  N_("Show _Highlight"),           "F7",         N_("Show Highlight"),                          (GCallback)set_draw_highlight, TRUE },
3458   { "FullScreen",     GTK_STOCK_FULLSCREEN, N_("_Full Screen"),              "F11",        N_("Activate full screen mode"),               (GCallback)full_screen_cb, FALSE },
3459   { "ViewSidePanel",  GTK_STOCK_INDEX,      N_("Show Side _Panel"),          "F9",         N_("Show Side Panel"),                         (GCallback)view_side_panel_cb, TRUE },
3460   { "ViewStatusBar",  NULL,                 N_("Show Status_bar"),           "F12",        N_("Show Statusbar"),                          (GCallback)view_statusbar_cb, TRUE },
3461   { "ViewToolbar",    NULL,                 N_("Show _Toolbar"),             "F3",         N_("Show Toolbar"),                            (GCallback)view_toolbar_cb, TRUE },
3462   { "ViewMainMenu",   NULL,                 N_("Show _Menu"),                "F4",         N_("Show Menu"),                               (GCallback)view_main_menu_cb, TRUE },
3463 };
3464
3465 #include "menu.xml.h"
3466 static void window_create_ui( VikWindow *window )
3467 {
3468   GtkUIManager *uim;
3469   GtkActionGroup *action_group;
3470   GtkAccelGroup *accel_group;
3471   GError *error;
3472   guint i, j, mid;
3473   GtkIconFactory *icon_factory;
3474   GtkIconSet *icon_set; 
3475   GtkRadioActionEntry *tools = NULL, *radio;
3476   guint ntools;
3477   
3478   uim = gtk_ui_manager_new ();
3479   window->uim = uim;
3480
3481   toolbox_add_tool(window->vt, &ruler_tool, TOOL_LAYER_TYPE_NONE);
3482   toolbox_add_tool(window->vt, &zoom_tool, TOOL_LAYER_TYPE_NONE);
3483   toolbox_add_tool(window->vt, &pan_tool, TOOL_LAYER_TYPE_NONE);
3484   toolbox_add_tool(window->vt, &select_tool, TOOL_LAYER_TYPE_NONE);
3485
3486   error = NULL;
3487   if (!(mid = gtk_ui_manager_add_ui_from_string (uim, menu_xml, -1, &error))) {
3488     g_error_free (error);
3489     exit (1);
3490   }
3491
3492   action_group = gtk_action_group_new ("MenuActions");
3493   gtk_action_group_set_translation_domain(action_group, PACKAGE_NAME);
3494   gtk_action_group_add_actions (action_group, entries, G_N_ELEMENTS (entries), window);
3495   gtk_action_group_add_toggle_actions (action_group, toggle_entries, G_N_ELEMENTS (toggle_entries), window);
3496   gtk_action_group_add_radio_actions (action_group, mode_entries, G_N_ELEMENTS (mode_entries), 4, (GCallback)window_change_coord_mode_cb, window);
3497
3498   // Use this to see if GPSBabel is available:
3499   if ( a_babel_device_list ) {
3500         // If going to add more entries then might be worth creating a menu_gpsbabel.xml.h file
3501         if ( gtk_ui_manager_add_ui_from_string ( uim,
3502           "<ui><menubar name='MainMenu'><menu action='File'><menu action='Export'><menuitem action='ExportKML'/></menu></menu></menubar></ui>",
3503           -1, &error ) )
3504       gtk_action_group_add_actions ( action_group, entries_gpsbabel, G_N_ELEMENTS (entries_gpsbabel), window );
3505   }
3506
3507   icon_factory = gtk_icon_factory_new ();
3508   gtk_icon_factory_add_default (icon_factory); 
3509
3510   register_vik_icons(icon_factory);
3511
3512   // Copy the tool RadioActionEntries out of the main Window structure into an extending array 'tools'
3513   //  so that it can be applied to the UI in one action group add function call below
3514   ntools = 0;
3515   for (i=0; i<window->vt->n_tools; i++) {
3516       tools = g_renew(GtkRadioActionEntry, tools, ntools+1);
3517       radio = &tools[ntools];
3518       ntools++;
3519       *radio = window->vt->tools[i].ti.radioActionEntry;
3520       radio->value = ntools;
3521   }
3522
3523   for (i=0; i<VIK_LAYER_NUM_TYPES; i++) {
3524     GtkActionEntry action;
3525     gtk_ui_manager_add_ui(uim, mid,  "/ui/MainMenu/Layers/", 
3526                           vik_layer_get_interface(i)->name,
3527                           vik_layer_get_interface(i)->name,
3528                           GTK_UI_MANAGER_MENUITEM, FALSE);
3529
3530     icon_set = gtk_icon_set_new_from_pixbuf (gdk_pixbuf_from_pixdata (vik_layer_get_interface(i)->icon, FALSE, NULL ));
3531     gtk_icon_factory_add (icon_factory, vik_layer_get_interface(i)->name, icon_set);
3532     gtk_icon_set_unref (icon_set);
3533
3534     action.name = vik_layer_get_interface(i)->name;
3535     action.stock_id = vik_layer_get_interface(i)->name;
3536     action.label = g_strdup_printf( _("New _%s Layer"), vik_layer_get_interface(i)->name);
3537     action.accelerator = vik_layer_get_interface(i)->accelerator;
3538     action.tooltip = NULL;
3539     action.callback = (GCallback)menu_addlayer_cb;
3540     gtk_action_group_add_actions(action_group, &action, 1, window);
3541
3542     if ( vik_layer_get_interface(i)->tools_count ) {
3543       gtk_ui_manager_add_ui(uim, mid,  "/ui/MainMenu/Tools/", vik_layer_get_interface(i)->name, NULL, GTK_UI_MANAGER_SEPARATOR, FALSE);
3544       gtk_ui_manager_add_ui(uim, mid,  "/ui/MainToolbar/ToolItems/", vik_layer_get_interface(i)->name, NULL, GTK_UI_MANAGER_SEPARATOR, FALSE);
3545     }
3546
3547     // Further tool copying for to apply to the UI, also apply menu UI setup
3548     for ( j = 0; j < vik_layer_get_interface(i)->tools_count; j++ ) {
3549       tools = g_renew(GtkRadioActionEntry, tools, ntools+1);
3550       radio = &tools[ntools];
3551       ntools++;
3552       
3553       gtk_ui_manager_add_ui(uim, mid,  "/ui/MainMenu/Tools", 
3554                             vik_layer_get_interface(i)->tools[j].radioActionEntry.label,
3555                             vik_layer_get_interface(i)->tools[j].radioActionEntry.name,
3556                             GTK_UI_MANAGER_MENUITEM, FALSE);
3557       gtk_ui_manager_add_ui(uim, mid,  "/ui/MainToolbar/ToolItems", 
3558                             vik_layer_get_interface(i)->tools[j].radioActionEntry.label,
3559                             vik_layer_get_interface(i)->tools[j].radioActionEntry.name,
3560                             GTK_UI_MANAGER_TOOLITEM, FALSE);
3561
3562       toolbox_add_tool(window->vt, &(vik_layer_get_interface(i)->tools[j]), i);
3563
3564       *radio = vik_layer_get_interface(i)->tools[j].radioActionEntry;
3565       // Overwrite with actual number to use
3566       radio->value = ntools;
3567     }
3568
3569     GtkActionEntry action_dl;
3570     gtk_ui_manager_add_ui(uim, mid,  "/ui/MainMenu/Edit/LayerDefaults",
3571                           vik_layer_get_interface(i)->name,
3572                           g_strdup_printf("Layer%s", vik_layer_get_interface(i)->fixed_layer_name),
3573                           GTK_UI_MANAGER_MENUITEM, FALSE);
3574
3575     // For default layers use action names of the form 'Layer<LayerName>'
3576     // This is to avoid clashing with just the layer name used above for the tool actions
3577     action_dl.name = g_strconcat("Layer", vik_layer_get_interface(i)->fixed_layer_name, NULL);
3578     action_dl.stock_id = NULL;
3579     action_dl.label = g_strconcat("_", vik_layer_get_interface(i)->name, "...", NULL); // Prepend marker for keyboard accelerator
3580     action_dl.accelerator = NULL;
3581     action_dl.tooltip = NULL;
3582     action_dl.callback = (GCallback)layer_defaults_cb;
3583     gtk_action_group_add_actions(action_group, &action_dl, 1, window);
3584   }
3585   g_object_unref (icon_factory);
3586
3587   gtk_action_group_add_radio_actions(action_group, tools, ntools, 0, (GCallback)menu_tool_cb, window);
3588   g_free(tools);
3589
3590   gtk_ui_manager_insert_action_group (uim, action_group, 0);
3591
3592   for (i=0; i<VIK_LAYER_NUM_TYPES; i++) {
3593     for ( j = 0; j < vik_layer_get_interface(i)->tools_count; j++ ) {
3594       GtkAction *action = gtk_action_group_get_action(action_group,
3595                             vik_layer_get_interface(i)->tools[j].radioActionEntry.name);
3596       g_object_set(action, "sensitive", FALSE, NULL);
3597     }
3598   }
3599
3600   // This is done last so we don't need to track the value of mid anymore
3601   vik_ext_tools_add_action_items ( window, window->uim, action_group, mid );
3602
3603   window->action_group = action_group;
3604
3605   accel_group = gtk_ui_manager_get_accel_group (uim);
3606   gtk_window_add_accel_group (GTK_WINDOW (window), accel_group);
3607   gtk_ui_manager_ensure_update (uim);
3608   
3609   setup_recent_files(window);
3610 }
3611
3612
3613 // TODO - add method to add tool icons defined from outside this file
3614 //  and remove the reverse dependency on icon definition from this file
3615 static struct { 
3616   const GdkPixdata *data;
3617   gchar *stock_id;
3618 } stock_icons[] = {
3619   { &mover_22_pixbuf,           "vik-icon-pan"      },
3620   { &zoom_18_pixbuf,            "vik-icon-zoom"     },
3621   { &ruler_18_pixbuf,           "vik-icon-ruler"    },
3622   { &select_18_pixbuf,          "vik-icon-select"   },
3623   { &vik_new_route_18_pixbuf,   "vik-icon-Create Route"     },
3624   { &route_finder_18_pixbuf,    "vik-icon-Route Finder"     },
3625   { &demdl_18_pixbuf,           "vik-icon-DEM Download"     },
3626   { &showpic_18_pixbuf,         "vik-icon-Show Picture"     },
3627   { &addtr_18_pixbuf,           "vik-icon-Create Track"     },
3628   { &edtr_18_pixbuf,            "vik-icon-Edit Trackpoint"  },
3629   { &addwp_18_pixbuf,           "vik-icon-Create Waypoint"  },
3630   { &edwp_18_pixbuf,            "vik-icon-Edit Waypoint"    },
3631   { &geozoom_18_pixbuf,         "vik-icon-Georef Zoom Tool" },
3632   { &geomove_18_pixbuf,         "vik-icon-Georef Move Map"  },
3633   { &mapdl_18_pixbuf,           "vik-icon-Maps Download"    },
3634 };
3635  
3636 static gint n_stock_icons = G_N_ELEMENTS (stock_icons);
3637
3638 static void
3639 register_vik_icons (GtkIconFactory *icon_factory)
3640 {
3641   GtkIconSet *icon_set; 
3642   gint i;
3643
3644   for (i = 0; i < n_stock_icons; i++) {
3645     icon_set = gtk_icon_set_new_from_pixbuf (gdk_pixbuf_from_pixdata (
3646                    stock_icons[i].data, FALSE, NULL ));
3647     gtk_icon_factory_add (icon_factory, stock_icons[i].stock_id, icon_set);
3648     gtk_icon_set_unref (icon_set);
3649   }
3650 }
3651
3652 gpointer vik_window_get_selected_trw_layer ( VikWindow *vw )
3653 {
3654   return vw->selected_vtl;
3655 }
3656
3657 void vik_window_set_selected_trw_layer ( VikWindow *vw, gpointer vtl )
3658 {
3659   vw->selected_vtl   = vtl;
3660   vw->containing_vtl = vtl;
3661   /* Clear others */
3662   vw->selected_track     = NULL;
3663   vw->selected_tracks    = NULL;
3664   vw->selected_waypoint  = NULL;
3665   vw->selected_waypoints = NULL;
3666   // Set highlight thickness
3667   vik_viewport_set_highlight_thickness ( vw->viking_vvp, vik_trw_layer_get_property_tracks_line_thickness (vw->containing_vtl) );
3668 }
3669
3670 GHashTable *vik_window_get_selected_tracks ( VikWindow *vw )
3671 {
3672   return vw->selected_tracks;
3673 }
3674
3675 void vik_window_set_selected_tracks ( VikWindow *vw, GHashTable *ght, gpointer vtl )
3676 {
3677   vw->selected_tracks = ght;
3678   vw->containing_vtl  = vtl;
3679   /* Clear others */
3680   vw->selected_vtl       = NULL;
3681   vw->selected_track     = NULL;
3682   vw->selected_waypoint  = NULL;
3683   vw->selected_waypoints = NULL;
3684   // Set highlight thickness
3685   vik_viewport_set_highlight_thickness ( vw->viking_vvp, vik_trw_layer_get_property_tracks_line_thickness (vw->containing_vtl) );
3686 }
3687
3688 gpointer vik_window_get_selected_track ( VikWindow *vw )
3689 {
3690   return vw->selected_track;
3691 }
3692
3693 void vik_window_set_selected_track ( VikWindow *vw, gpointer *vt, gpointer vtl )
3694 {
3695   vw->selected_track = vt;
3696   vw->containing_vtl = vtl;
3697   /* Clear others */
3698   vw->selected_vtl       = NULL;
3699   vw->selected_tracks    = NULL;
3700   vw->selected_waypoint  = NULL;
3701   vw->selected_waypoints = NULL;
3702   // Set highlight thickness
3703   vik_viewport_set_highlight_thickness ( vw->viking_vvp, vik_trw_layer_get_property_tracks_line_thickness (vw->containing_vtl) );
3704 }
3705
3706 GHashTable *vik_window_get_selected_waypoints ( VikWindow *vw )
3707 {
3708   return vw->selected_waypoints;
3709 }
3710
3711 void vik_window_set_selected_waypoints ( VikWindow *vw, GHashTable *ght, gpointer vtl )
3712 {
3713   vw->selected_waypoints = ght;
3714   vw->containing_vtl     = vtl;
3715   /* Clear others */
3716   vw->selected_vtl       = NULL;
3717   vw->selected_track     = NULL;
3718   vw->selected_tracks    = NULL;
3719   vw->selected_waypoint  = NULL;
3720 }
3721
3722 gpointer vik_window_get_selected_waypoint ( VikWindow *vw )
3723 {
3724   return vw->selected_waypoint;
3725 }
3726
3727 void vik_window_set_selected_waypoint ( VikWindow *vw, gpointer *vwp, gpointer vtl )
3728 {
3729   vw->selected_waypoint = vwp;
3730   vw->containing_vtl    = vtl;
3731   /* Clear others */
3732   vw->selected_vtl       = NULL;
3733   vw->selected_track     = NULL;
3734   vw->selected_tracks    = NULL;
3735   vw->selected_waypoints = NULL;
3736 }
3737
3738 gboolean vik_window_clear_highlight ( VikWindow *vw )
3739 {
3740   gboolean need_redraw = FALSE;
3741   if ( vw->selected_vtl != NULL ) {
3742     vw->selected_vtl = NULL;
3743     need_redraw = TRUE;
3744   }
3745   if ( vw->selected_track != NULL ) {
3746     vw->selected_track = NULL;
3747     need_redraw = TRUE;
3748   }
3749   if ( vw->selected_tracks != NULL ) {
3750     vw->selected_tracks = NULL;
3751     need_redraw = TRUE;
3752   }
3753   if ( vw->selected_waypoint != NULL ) {
3754     vw->selected_waypoint = NULL;
3755     need_redraw = TRUE;
3756   }
3757   if ( vw->selected_waypoints != NULL ) {
3758     vw->selected_waypoints = NULL;
3759     need_redraw = TRUE;
3760   }
3761   return need_redraw;
3762 }
3763
3764 GThread *vik_window_get_thread ( VikWindow *vw )
3765 {
3766   return vw->thread;
3767 }