X-Git-Url: https://git.street.me.uk/andy/viking.git/blobdiff_plain/a459ee1050d526fe3072db2a5ab9211ff00074e3..22f536eab0ef962226db23c9f7ba51b6ae50977d:/src/vikwindow.c?ds=sidebyside diff --git a/src/vikwindow.c b/src/vikwindow.c index 8752f1d2..547df1af 100644 --- a/src/vikwindow.c +++ b/src/vikwindow.c @@ -3,6 +3,7 @@ * * Copyright (C) 2003-2005, Evan Battaglia * Copyright (C) 2005-2006, Alex Foobarian + * Copyright (C) 2012-2013, Rob Norris * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,14 +29,20 @@ #include "background.h" #include "acquire.h" #include "datasources.h" +#include "geojson.h" #include "vikgoto.h" #include "dems.h" #include "mapcache.h" #include "print.h" #include "preferences.h" +#include "viklayer_defaults.h" #include "icons/icons.h" #include "vikexttools.h" +#include "vikexttool_datasources.h" #include "garminsymbols.h" +#include "vikmapslayer.h" +#include "geonamessearch.h" +#include "vikutils.h" #ifdef HAVE_STDLIB_H #include @@ -52,6 +59,14 @@ #include #include #include +#include + +// This seems rather arbitary, quite large and pointless +// I mean, if you have a thousand windows open; +// why not be allowed to open a thousand more... +#define MAX_WINDOWS 1024 +static guint window_count = 0; +static GSList *window_list = NULL; #define VIKING_WINDOW_WIDTH 1000 #define VIKING_WINDOW_HEIGHT 800 @@ -62,20 +77,27 @@ static void window_finalize ( GObject *gob ); static GObjectClass *parent_class; -static void window_init ( VikWindow *vw ); -static void window_class_init ( VikWindowClass *klass ); static void window_set_filename ( VikWindow *vw, const gchar *filename ); +static const gchar *window_get_filename ( VikWindow *vw ); + +static VikWindow *window_new (); static void draw_update ( VikWindow *vw ); static void newwindow_cb ( GtkAction *a, VikWindow *vw ); +// Signals +static void open_window ( VikWindow *vw, GSList *files ); +static void destroy_window ( GtkWidget *widget, + gpointer data ); + /* Drawing & stuff */ static gboolean delete_event( VikWindow *vw ); static gboolean key_press_event( VikWindow *vw, GdkEventKey *event, gpointer data ); +static void center_changed_cb ( VikWindow *vw ); static void window_configure_event ( VikWindow *vw ); static void draw_sync ( VikWindow *vw ); static void draw_redraw ( VikWindow *vw ); @@ -85,8 +107,9 @@ static void draw_release ( VikWindow *vw, GdkEventButton *event ); static void draw_mouse_motion ( VikWindow *vw, GdkEventMotion *event ); static void draw_zoom_cb ( GtkAction *a, VikWindow *vw ); static void draw_goto_cb ( GtkAction *a, VikWindow *vw ); +static void draw_refresh_cb ( GtkAction *a, VikWindow *vw ); -static void draw_status (); +static void draw_status ( VikWindow *vw ); /* End Drawing Functions */ @@ -133,13 +156,15 @@ static gboolean window_save ( VikWindow *vw ); struct _VikWindow { GtkWindow gtkwindow; + GtkWidget *hpaned; VikViewport *viking_vvp; VikLayersPanel *viking_vlp; VikStatusbar *viking_vs; GtkToolbar *toolbar; - GtkItemFactory *item_factory; + GdkCursor *busy_cursor; + GdkCursor *viewport_cursor; // only a reference /* tool management state */ guint current_tool; @@ -151,12 +176,15 @@ struct _VikWindow { gboolean pan_move; gint pan_x, pan_y; + gint delayed_pan_x, delayed_pan_y; // Temporary storage + gboolean single_click_pending; guint draw_image_width, draw_image_height; gboolean draw_image_save_as_png; gchar *filename; gboolean modified; + VikLoadType_t loaded_type; GtkWidget *open_dia, *save_dia; GtkWidget *save_img_dia, *save_img_dir_dia; @@ -164,15 +192,28 @@ struct _VikWindow { gboolean only_updating_coord_mode_ui; /* hack for a bug in GTK */ GtkUIManager *uim; + GThread *thread; /* half-drawn update */ VikLayer *trigger; VikCoord trigger_center; + + /* Store at this level for highlighted selection drawing since it applies to the viewport and the layers panel */ + /* Only one of these items can be selected at the same time */ + gpointer selected_vtl; /* notionally VikTrwLayer */ + GHashTable *selected_tracks; + gpointer selected_track; /* notionally VikTrack */ + GHashTable *selected_waypoints; + gpointer selected_waypoint; /* notionally VikWaypoint */ + /* only use for individual track or waypoint */ + /* For track(s) & waypoint(s) it is the layer they are in - this helps refering to the individual item easier */ + gpointer containing_vtl; /* notionally VikTrwLayer */ }; enum { TOOL_PAN = 0, TOOL_ZOOM, TOOL_RULER, + TOOL_SELECT, TOOL_LAYER, NUMBER_OF_TOOLS }; @@ -185,36 +226,268 @@ enum { static guint window_signals[VW_LAST_SIGNAL] = { 0 }; -static gchar *tool_names[NUMBER_OF_TOOLS] = { N_("Pan"), N_("Zoom"), N_("Ruler") }; +// TODO get rid of this as this is unnecessary duplication... +static gchar *tool_names[NUMBER_OF_TOOLS] = { N_("Pan"), N_("Zoom"), N_("Ruler"), N_("Select") }; + +G_DEFINE_TYPE (VikWindow, vik_window, GTK_TYPE_WINDOW) + +VikViewport * vik_window_viewport(VikWindow *vw) +{ + return(vw->viking_vvp); +} + +VikLayersPanel * vik_window_layers_panel(VikWindow *vw) +{ + return(vw->viking_vlp); +} + +/** + * Returns the statusbar for the window + */ +VikStatusbar * vik_window_get_statusbar ( VikWindow *vw ) +{ + return vw->viking_vs; +} + +/** + * Returns the 'project' filename + */ +const gchar *vik_window_get_filename (VikWindow *vw) +{ + return vw->filename; +} + +typedef struct { + VikStatusbar *vs; + vik_statusbar_type_t vs_type; + gchar* message; // Always make a copy of this data +} statusbar_idle_data; + +/** + * For the actual statusbar update! + */ +static gboolean statusbar_idle_update ( statusbar_idle_data *sid ) +{ + vik_statusbar_set_message ( sid->vs, sid->vs_type, sid->message ); + g_free ( sid->message ); + g_free ( sid ); + return FALSE; +} + +/** + * vik_window_statusbar_update: + * @vw: The main window in which the statusbar will be updated. + * @message: The string to be displayed. This is copied. + * @vs_type: The part of the statusbar to be updated. + * + * This updates any part of the statusbar with the new string. + * It handles calling from the main thread or any background thread + * ATM this mostly used from background threads - as from the main thread + * one may use the vik_statusbar_set_message() directly. + */ +void vik_window_statusbar_update ( VikWindow *vw, const gchar* message, vik_statusbar_type_t vs_type ) +{ + statusbar_idle_data *sid = g_malloc ( sizeof (statusbar_idle_data) ); + sid->vs = vw->viking_vs; + sid->vs_type = vs_type; + sid->message = g_strdup ( message ); -GType vik_window_get_type (void) + if ( g_thread_self() == vik_window_get_thread ( vw ) ) { + g_idle_add ( (GSourceFunc) statusbar_idle_update, sid ); + } + else { + // From a background thread + gdk_threads_add_idle ( (GSourceFunc) statusbar_idle_update, sid ); + } +} + +// Actual signal handlers +static void destroy_window ( GtkWidget *widget, + gpointer data ) { - static GType vw_type = 0; + if ( ! --window_count ) + gtk_main_quit (); +} + +#define VIK_SETTINGS_WIN_SIDEPANEL "window_sidepanel" +#define VIK_SETTINGS_WIN_STATUSBAR "window_statusbar" +#define VIK_SETTINGS_WIN_TOOLBAR "window_toolbar" +// Menubar setting to off is never auto saved in case it's accidentally turned off +// It's not so obvious so to recover the menu visibility. +// Thus this value is for setting manually via editting the settings file directly +#define VIK_SETTINGS_WIN_MENUBAR "window_menubar" - if (!vw_type) +VikWindow *vik_window_new_window () +{ + if ( window_count < MAX_WINDOWS ) { - static const GTypeInfo vw_info = - { - sizeof (VikWindowClass), - NULL, /* base_init */ - NULL, /* base_finalize */ - (GClassInitFunc) window_class_init, /* class_init */ - NULL, /* class_finalize */ - NULL, /* class_data */ - sizeof (VikWindow), - 0, - (GInstanceInitFunc) window_init, - }; - vw_type = g_type_register_static ( GTK_TYPE_WINDOW, "VikWindow", &vw_info, 0 ); + VikWindow *vw = window_new (); + + g_signal_connect (G_OBJECT (vw), "destroy", + G_CALLBACK (destroy_window), NULL); + g_signal_connect (G_OBJECT (vw), "newwindow", + G_CALLBACK (vik_window_new_window), NULL); + g_signal_connect (G_OBJECT (vw), "openwindow", + G_CALLBACK (open_window), NULL); + + gtk_widget_show_all ( GTK_WIDGET(vw) ); + + if ( a_vik_get_restore_window_state() ) { + // These settings are applied after the show all as these options hide widgets + gboolean sidepanel; + if ( a_settings_get_boolean ( VIK_SETTINGS_WIN_SIDEPANEL, &sidepanel ) ) + if ( ! sidepanel ) { + gtk_widget_hide ( GTK_WIDGET(vw->viking_vlp) ); + GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ViewSidePanel" ); + gtk_check_menu_item_set_active ( GTK_CHECK_MENU_ITEM(check_box), FALSE ); + } + + gboolean statusbar; + if ( a_settings_get_boolean ( VIK_SETTINGS_WIN_STATUSBAR, &statusbar ) ) + if ( ! statusbar ) { + gtk_widget_hide ( GTK_WIDGET(vw->viking_vs) ); + GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ViewStatusBar" ); + gtk_check_menu_item_set_active ( GTK_CHECK_MENU_ITEM(check_box), FALSE ); + } + + gboolean toolbar; + if ( a_settings_get_boolean ( VIK_SETTINGS_WIN_TOOLBAR, &toolbar ) ) + if ( ! toolbar ) { + gtk_widget_hide ( GTK_WIDGET(vw->toolbar) ); + GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ViewToolBar" ); + gtk_check_menu_item_set_active ( GTK_CHECK_MENU_ITEM(check_box), FALSE ); + } + + gboolean menubar; + if ( a_settings_get_boolean ( VIK_SETTINGS_WIN_MENUBAR, &menubar ) ) + if ( ! menubar ) { + gtk_widget_hide ( gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu" ) ); + GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ViewMainMenu" ); + gtk_check_menu_item_set_active ( GTK_CHECK_MENU_ITEM(check_box), FALSE ); + } + } + window_count++; + + return vw; + } + return NULL; +} + +/** + * determine_location_thread: + * @vw: The window that will get updated + * @threaddata: Data used by our background thread mechanism + * + * Use the features in vikgoto to determine where we are + * Then set up the viewport: + * 1. To goto the location + * 2. Set an appropriate level zoom for the location type + * 3. Some statusbar message feedback + */ +static int determine_location_thread ( VikWindow *vw, gpointer threaddata ) +{ + struct LatLon ll; + gchar *name = NULL; + gint ans = a_vik_goto_where_am_i ( vw->viking_vvp, &ll, &name ); + + int result = a_background_thread_progress ( threaddata, 1.0 ); + if ( result != 0 ) { + vik_window_statusbar_update ( vw, _("Location lookup aborted"), VIK_STATUSBAR_INFO ); + return -1; /* Abort thread */ + } + + if ( ans ) { + // Zoom out a little + gdouble zoom = 16.0; + + if ( ans == 2 ) { + // Position found with city precision - so zoom out more + zoom = 128.0; + } + else if ( ans == 3 ) { + // Position found via country name search - so zoom wayyyy out + zoom = 2048.0; + } + + vik_viewport_set_zoom ( vw->viking_vvp, zoom ); + vik_viewport_set_center_latlon ( vw->viking_vvp, &ll, FALSE ); + + gchar *message = g_strdup_printf ( _("Location found: %s"), name ); + vik_window_statusbar_update ( vw, message, VIK_STATUSBAR_INFO ); + g_free ( name ); + g_free ( message ); + + // Signal to redraw from the background + vik_layers_panel_emit_update ( vw->viking_vlp ); + } + else + vik_window_statusbar_update ( vw, _("Unable to determine location"), VIK_STATUSBAR_INFO ); + + return 0; +} + +/** + * Steps to be taken once initial loading has completed + */ +void vik_window_new_window_finish ( VikWindow *vw ) +{ + // Don't add a map if we've loaded a Viking file already + if ( vw->filename ) + return; + + if ( a_vik_get_startup_method ( ) == VIK_STARTUP_METHOD_SPECIFIED_FILE ) { + vik_window_open_file ( vw, a_vik_get_startup_file(), TRUE ); + if ( vw->filename ) + return; } - return vw_type; + // Maybe add a default map layer + if ( a_vik_get_add_default_map_layer () ) { + VikMapsLayer *vml = VIK_MAPS_LAYER ( vik_layer_create(VIK_LAYER_MAPS, vw->viking_vvp, FALSE) ); + vik_layer_rename ( VIK_LAYER(vml), _("Default Map") ); + vik_aggregate_layer_add_layer ( vik_layers_panel_get_top_layer(vw->viking_vlp), VIK_LAYER(vml), TRUE ); + + draw_update ( vw ); + } + + // If not loaded any file, maybe try the location lookup + if ( vw->loaded_type == LOAD_TYPE_READ_FAILURE ) { + if ( a_vik_get_startup_method ( ) == VIK_STARTUP_METHOD_AUTO_LOCATION ) { + + vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_INFO, _("Trying to determine location...") ); + + a_background_thread ( GTK_WINDOW(vw), + _("Determining location"), + (vik_thr_func) determine_location_thread, + vw, + NULL, + NULL, + 1 ); + } + } } -VikViewport * vik_window_viewport(VikWindow *vw) +static void open_window ( VikWindow *vw, GSList *files ) { - return(vw->viking_vvp); + gboolean change_fn = (g_slist_length(files) == 1); /* only change fn if one file */ + GSList *cur_file = files; + while ( cur_file ) { + // Only open a new window if a viking file + gchar *file_name = cur_file->data; + if (vw != NULL && vw->filename && check_file_magic_vik ( file_name ) ) { + VikWindow *newvw = vik_window_new_window (); + if (newvw) + vik_window_open_file ( newvw, file_name, TRUE ); + } + else { + vik_window_open_file ( vw, file_name, change_fn ); + } + g_free (file_name); + cur_file = g_slist_next (cur_file); + } + g_slist_free (files); } +// End signals void vik_window_selected_layer(VikWindow *vw, VikLayer *vl) { @@ -230,7 +503,7 @@ void vik_window_selected_layer(VikWindow *vw, VikLayer *vl) for (j = 0; j < tool_count; j++) { action = gtk_action_group_get_action(vw->action_group, - layer_interface->tools[j].name); + layer_interface->tools[j].radioActionEntry.name); g_object_set(action, "sensitive", i == vl->type, NULL); } } @@ -241,13 +514,23 @@ static void window_finalize ( GObject *gob ) VikWindow *vw = VIK_WINDOW(gob); g_return_if_fail ( vw != NULL ); - a_background_remove_status ( vw->viking_vs ); + a_background_remove_window ( vw ); + + window_list = g_slist_remove ( window_list, vw ); + + gdk_cursor_unref ( vw->busy_cursor ); + int tt; + for (tt = 0; tt < vw->vt->n_tools; tt++ ) + if ( vw->vt->tools[tt].ti.destroy ) + vw->vt->tools[tt].ti.destroy ( vw->vt->tools[tt].state ); + g_free ( vw->vt->tools ); + g_free ( vw->vt ); G_OBJECT_CLASS(parent_class)->finalize(gob); } -static void window_class_init ( VikWindowClass *klass ) +static void vik_window_class_init ( VikWindowClass *klass ) { /* destructor */ GObjectClass *object_class; @@ -263,10 +546,161 @@ static void window_class_init ( VikWindowClass *klass ) } -static void window_init ( VikWindow *vw ) +static void zoom_changed (GtkMenuShell *menushell, + gpointer user_data) +{ + VikWindow *vw = VIK_WINDOW (user_data); + + GtkWidget *aw = gtk_menu_get_active ( GTK_MENU (menushell) ); + gint active = GPOINTER_TO_INT(g_object_get_data ( G_OBJECT (aw), "position" )); + + gdouble zoom_request = pow (2, active-2 ); + + // But has it really changed? + gdouble current_zoom = vik_viewport_get_zoom ( vw->viking_vvp ); + if ( current_zoom != 0.0 && zoom_request != current_zoom ) { + vik_viewport_set_zoom ( vw->viking_vvp, zoom_request ); + // Force drawing update + draw_update ( vw ); + } +} + +/** + * @mpp: The initial zoom level + */ +static GtkWidget *create_zoom_menu_all_levels ( gdouble mpp ) +{ + GtkWidget *menu = gtk_menu_new (); + char *itemLabels[] = { "0.25", "0.5", "1", "2", "4", "8", "16", "32", "64", "128", "256", "512", "1024", "2048", "4096", "8192", "16384", "32768" }; + + int i; + for (i = 0 ; i < G_N_ELEMENTS(itemLabels) ; i++) + { + GtkWidget *item = gtk_menu_item_new_with_label (itemLabels[i]); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + g_object_set_data (G_OBJECT (item), "position", GINT_TO_POINTER(i)); + } + + gint active = 2 + round ( log (mpp) / log (2) ); + // Ensure value derived from mpp is in bounds of the menu + if ( active >= G_N_ELEMENTS(itemLabels) ) + active = G_N_ELEMENTS(itemLabels) - 1; + if ( active < 0 ) + active = 0; + gtk_menu_set_active ( GTK_MENU(menu), active ); + + return menu; +} + +static GtkWidget *create_zoom_combo_all_levels () +{ + GtkWidget *combo = vik_combo_box_text_new(); + vik_combo_box_text_append ( combo, "0.25"); + vik_combo_box_text_append ( combo, "0.5"); + vik_combo_box_text_append ( combo, "1"); + vik_combo_box_text_append ( combo, "2"); + vik_combo_box_text_append ( combo, "4"); + vik_combo_box_text_append ( combo, "8"); + vik_combo_box_text_append ( combo, "16"); + vik_combo_box_text_append ( combo, "32"); + vik_combo_box_text_append ( combo, "64"); + vik_combo_box_text_append ( combo, "128"); + vik_combo_box_text_append ( combo, "256"); + vik_combo_box_text_append ( combo, "512"); + vik_combo_box_text_append ( combo, "1024"); + vik_combo_box_text_append ( combo, "2048"); + vik_combo_box_text_append ( combo, "4096"); + vik_combo_box_text_append ( combo, "8192"); + vik_combo_box_text_append ( combo, "16384"); + vik_combo_box_text_append ( combo, "32768"); + /* Create tooltip */ + gtk_widget_set_tooltip_text (combo, _("Select zoom level")); + return combo; +} + +static gint zoom_popup_handler (GtkWidget *widget) +{ + GtkMenu *menu; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (GTK_IS_MENU (widget), FALSE); + + /* The "widget" is the menu that was supplied when + * g_signal_connect_swapped() was called. + */ + menu = GTK_MENU (widget); + + gtk_menu_popup (menu, NULL, NULL, NULL, NULL, + 1, gtk_get_current_event_time()); + return TRUE; +} + +enum { + TARGET_URIS, +}; + +static void drag_data_received_cb ( GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint target_type, + guint time, + gpointer data ) +{ + gboolean success = FALSE; + + if ( (selection_data != NULL) && (gtk_selection_data_get_length(selection_data) > 0) ) { + switch (target_type) { + case TARGET_URIS: { + gchar *str = (gchar*)gtk_selection_data_get_data(selection_data); + g_debug ("drag received string:%s \n", str); + + // Convert string into GSList of individual entries for use with our open signal + gchar **entries = g_strsplit(str, "\r\n", 0); + GSList *filenames = NULL; + gint entry_runner = 0; + gchar *entry = entries[entry_runner]; + while (entry) { + if ( g_strcmp0 ( entry, "" ) ) { + // Drag+Drop gives URIs. And so in particular, %20 in place of spaces in filenames + // thus need to convert the text into a plain string + gchar *filename = g_filename_from_uri ( entry, NULL, NULL ); + if ( filename ) + filenames = g_slist_append ( filenames, filename ); + } + entry_runner++; + entry = entries[entry_runner]; + } + + if ( filenames ) + g_signal_emit ( G_OBJECT(VIK_WINDOW_FROM_WIDGET(widget)), window_signals[VW_OPENWINDOW_SIGNAL], 0, filenames ); + // NB: GSList & contents are freed by main.open_window + + success = TRUE; + break; + } + default: break; + } + } + + gtk_drag_finish ( context, success, FALSE, time ); +} + +#define VIK_SETTINGS_WIN_MAX "window_maximized" +#define VIK_SETTINGS_WIN_FULLSCREEN "window_fullscreen" +#define VIK_SETTINGS_WIN_WIDTH "window_width" +#define VIK_SETTINGS_WIN_HEIGHT "window_height" +#define VIK_SETTINGS_WIN_PANE_POSITION "window_horizontal_pane_position" +#define VIK_SETTINGS_WIN_SAVE_IMAGE_WIDTH "window_save_image_width" +#define VIK_SETTINGS_WIN_SAVE_IMAGE_HEIGHT "window_save_image_height" +#define VIK_SETTINGS_WIN_SAVE_IMAGE_PNG "window_save_image_as_png" +#define VIK_SETTINGS_WIN_COPY_CENTRE_FULL_FORMAT "window_copy_centre_full_format" + +static void vik_window_init ( VikWindow *vw ) { GtkWidget *main_vbox; - GtkWidget *hpaned; vw->action_group = NULL; @@ -278,33 +712,59 @@ static void window_init ( VikWindow *vw ) vw->vt = toolbox_create(vw); window_create_ui(vw); window_set_filename (vw, NULL); - - toolbox_activate(vw->vt, "Pan"); + vw->toolbar = GTK_TOOLBAR(gtk_ui_manager_get_widget (vw->uim, "/MainToolbar")); - vw->filename = NULL; - vw->item_factory = NULL; + vw->busy_cursor = gdk_cursor_new ( GDK_WATCH ); + + // Set the default tool + gtk_action_activate ( gtk_action_group_get_action ( vw->action_group, "Pan" ) ); + vw->filename = NULL; + vw->loaded_type = LOAD_TYPE_READ_FAILURE; //AKA none vw->modified = FALSE; vw->only_updating_coord_mode_ui = FALSE; vw->pan_move = FALSE; vw->pan_x = vw->pan_y = -1; - vw->draw_image_width = DRAW_IMAGE_DEFAULT_WIDTH; - vw->draw_image_height = DRAW_IMAGE_DEFAULT_HEIGHT; - vw->draw_image_save_as_png = DRAW_IMAGE_DEFAULT_SAVE_AS_PNG; + vw->single_click_pending = FALSE; + + gint draw_image_width; + if ( a_settings_get_integer ( VIK_SETTINGS_WIN_SAVE_IMAGE_WIDTH, &draw_image_width ) ) + vw->draw_image_width = draw_image_width; + else + vw->draw_image_width = DRAW_IMAGE_DEFAULT_WIDTH; + gint draw_image_height; + if ( a_settings_get_integer ( VIK_SETTINGS_WIN_SAVE_IMAGE_HEIGHT, &draw_image_height ) ) + vw->draw_image_height = draw_image_height; + else + vw->draw_image_height = DRAW_IMAGE_DEFAULT_HEIGHT; + gboolean draw_image_save_as_png; + if ( a_settings_get_boolean ( VIK_SETTINGS_WIN_SAVE_IMAGE_PNG, &draw_image_save_as_png ) ) + vw->draw_image_save_as_png = draw_image_save_as_png; + else + vw->draw_image_save_as_png = DRAW_IMAGE_DEFAULT_SAVE_AS_PNG; main_vbox = gtk_vbox_new(FALSE, 1); gtk_container_add (GTK_CONTAINER (vw), main_vbox); gtk_box_pack_start (GTK_BOX(main_vbox), gtk_ui_manager_get_widget (vw->uim, "/MainMenu"), FALSE, TRUE, 0); - gtk_box_pack_start (GTK_BOX(main_vbox), gtk_ui_manager_get_widget (vw->uim, "/MainToolbar"), FALSE, TRUE, 0); - gtk_toolbar_set_icon_size(GTK_TOOLBAR(gtk_ui_manager_get_widget (vw->uim, "/MainToolbar")), GTK_ICON_SIZE_SMALL_TOOLBAR); - gtk_toolbar_set_style (GTK_TOOLBAR(gtk_ui_manager_get_widget (vw->uim, "/MainToolbar")), GTK_TOOLBAR_ICONS); + gtk_box_pack_start (GTK_BOX(main_vbox), GTK_WIDGET(vw->toolbar), FALSE, TRUE, 0); + gtk_toolbar_set_icon_size (vw->toolbar, GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_toolbar_set_style (vw->toolbar, GTK_TOOLBAR_ICONS); - vik_ext_tools_add_menu_items ( vw, vw->uim ); + vik_ext_tool_datasources_add_menu_items ( vw, vw->uim ); + + GtkWidget * zoom_levels = gtk_ui_manager_get_widget (vw->uim, "/MainMenu/View/SetZoom"); + GtkWidget * zoom_levels_menu = create_zoom_menu_all_levels ( vik_viewport_get_zoom(vw->viking_vvp) ); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (zoom_levels), zoom_levels_menu); + g_signal_connect ( G_OBJECT(zoom_levels_menu), "selection-done", G_CALLBACK(zoom_changed), vw); + g_signal_connect_swapped ( G_OBJECT(vw->viking_vs), "clicked", G_CALLBACK(zoom_popup_handler), zoom_levels_menu ); g_signal_connect (G_OBJECT (vw), "delete_event", G_CALLBACK (delete_event), NULL); + // Own signals + g_signal_connect_swapped (G_OBJECT(vw->viking_vvp), "updated_center", G_CALLBACK(center_changed_cb), vw); + // Signals from GTK g_signal_connect_swapped (G_OBJECT(vw->viking_vvp), "expose_event", G_CALLBACK(draw_sync), vw); g_signal_connect_swapped (G_OBJECT(vw->viking_vvp), "configure_event", G_CALLBACK(window_configure_event), vw); 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 ); @@ -312,42 +772,187 @@ static void window_init ( VikWindow *vw ) g_signal_connect_swapped (G_OBJECT(vw->viking_vvp), "button_press_event", G_CALLBACK(draw_click), vw); g_signal_connect_swapped (G_OBJECT(vw->viking_vvp), "button_release_event", G_CALLBACK(draw_release), vw); g_signal_connect_swapped (G_OBJECT(vw->viking_vvp), "motion_notify_event", G_CALLBACK(draw_mouse_motion), vw); + g_signal_connect_swapped (G_OBJECT(vw->viking_vlp), "update", G_CALLBACK(draw_update), vw); + g_signal_connect_swapped (G_OBJECT(vw->viking_vlp), "delete_layer", G_CALLBACK(vik_window_clear_highlight), vw); - g_signal_connect_swapped (G_OBJECT (vw->viking_vvp), "key_press_event", G_CALLBACK (key_press_event), vw); + // Allow key presses to be processed anywhere + g_signal_connect_swapped (G_OBJECT (vw), "key_press_event", G_CALLBACK (key_press_event), vw); - gtk_window_set_default_size ( GTK_WINDOW(vw), VIKING_WINDOW_WIDTH, VIKING_WINDOW_HEIGHT); + // Set initial button sensitivity + center_changed_cb ( vw ); - hpaned = gtk_hpaned_new (); - gtk_paned_pack1 ( GTK_PANED(hpaned), GTK_WIDGET (vw->viking_vlp), FALSE, FALSE ); - gtk_paned_pack2 ( GTK_PANED(hpaned), GTK_WIDGET (vw->viking_vvp), TRUE, TRUE ); + vw->hpaned = gtk_hpaned_new (); + gtk_paned_pack1 ( GTK_PANED(vw->hpaned), GTK_WIDGET (vw->viking_vlp), FALSE, FALSE ); + gtk_paned_pack2 ( GTK_PANED(vw->hpaned), GTK_WIDGET (vw->viking_vvp), TRUE, TRUE ); /* This packs the button into the window (a gtk container). */ - gtk_box_pack_start (GTK_BOX(main_vbox), hpaned, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX(main_vbox), vw->hpaned, TRUE, TRUE, 0); gtk_box_pack_end (GTK_BOX(main_vbox), GTK_WIDGET(vw->viking_vs), FALSE, TRUE, 0); - a_background_add_status(vw->viking_vs); + a_background_add_window ( vw ); + + window_list = g_slist_prepend ( window_list, vw); + + gint height = VIKING_WINDOW_HEIGHT; + gint width = VIKING_WINDOW_WIDTH; + + if ( a_vik_get_restore_window_state() ) { + if ( a_settings_get_integer ( VIK_SETTINGS_WIN_HEIGHT, &height ) ) { + // Enforce a basic minimum size + if ( height < 160 ) + height = 160; + } + else + // No setting - so use default + height = VIKING_WINDOW_HEIGHT; + + if ( a_settings_get_integer ( VIK_SETTINGS_WIN_WIDTH, &width ) ) { + // Enforce a basic minimum size + if ( width < 320 ) + width = 320; + } + else + // No setting - so use default + width = VIKING_WINDOW_WIDTH; + + gboolean maxed; + if ( a_settings_get_boolean ( VIK_SETTINGS_WIN_MAX, &maxed ) ) + if ( maxed ) + gtk_window_maximize ( GTK_WINDOW(vw) ); + + gboolean full; + if ( a_settings_get_boolean ( VIK_SETTINGS_WIN_FULLSCREEN, &full ) ) { + if ( full ) { + gtk_window_fullscreen ( GTK_WINDOW(vw) ); + GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/FullScreen" ); + gtk_check_menu_item_set_active ( GTK_CHECK_MENU_ITEM(check_box), TRUE ); + } + } + + gint position = -1; // Let GTK determine default positioning + if ( !a_settings_get_integer ( VIK_SETTINGS_WIN_PANE_POSITION, &position ) ) { + position = -1; + } + gtk_paned_set_position ( GTK_PANED(vw->hpaned), position ); + } + + gtk_window_set_default_size ( GTK_WINDOW(vw), width, height ); vw->open_dia = NULL; vw->save_dia = NULL; vw->save_img_dia = NULL; vw->save_img_dir_dia = NULL; + + // Only accept Drag and Drop of files onto the viewport + gtk_drag_dest_set ( GTK_WIDGET(vw->viking_vvp), GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY ); + gtk_drag_dest_add_uri_targets ( GTK_WIDGET(vw->viking_vvp) ); + g_signal_connect ( GTK_WIDGET(vw->viking_vvp), "drag-data-received", G_CALLBACK(drag_data_received_cb), NULL ); + + // Store the thread value so comparisons can be made to determine the gdk update method + // Hopefully we are storing the main thread value here :) + // [ATM any window initialization is always be performed by the main thread] + vw->thread = g_thread_self(); } -VikWindow *vik_window_new () +static VikWindow *window_new () { return VIK_WINDOW ( g_object_new ( VIK_WINDOW_TYPE, NULL ) ); } +/** + * Update the displayed map + * Only update the top most visible map layer + * ATM this assumes (as per defaults) the top most map has full alpha setting + * such that other other maps even though they may be active will not be seen + * It's more complicated to work out which maps are actually visible due to alpha settings + * and overkill for this simple refresh method. + */ +static void simple_map_update ( VikWindow *vw, gboolean only_new ) +{ + // Find the most relevent single map layer to operate on + VikLayer *vl = vik_aggregate_layer_get_top_visible_layer_of_type (vik_layers_panel_get_top_layer(vw->viking_vlp), VIK_LAYER_MAPS); + if ( vl ) + vik_maps_layer_download ( VIK_MAPS_LAYER(vl), vw->viking_vvp, only_new ); +} + +/** + * This is the global key press handler + * Global shortcuts are available at any time and hence are not restricted to when a certain tool is enabled + */ static gboolean key_press_event( VikWindow *vw, GdkEventKey *event, gpointer data ) { + // The keys handled here are not in the menuing system for a couple of reasons: + // . Keeps the menu size compact (alebit at expense of discoverably) + // . Allows differing key bindings to perform the same actions + + // First decide if key events are related to the maps layer + gboolean map_download = FALSE; + gboolean map_download_only_new = TRUE; // Only new or reload + + GdkModifierType modifiers = gtk_accelerator_get_default_mod_mask(); + + // Standard 'Refresh' keys: F5 or Ctrl+r + // Note 'F5' is actually handled via draw_refresh_cb() later on + // (not 'R' it's 'r' notice the case difference!!) + if ( event->keyval == GDK_r && (event->state & modifiers) == GDK_CONTROL_MASK ) { + map_download = TRUE; + map_download_only_new = TRUE; + } + // Full cache reload with Ctrl+F5 or Ctrl+Shift+r [This is not in the menu system] + // Note the use of uppercase R here since shift key has been pressed + else if ( (event->keyval == GDK_F5 && (event->state & modifiers) == GDK_CONTROL_MASK ) || + ( event->keyval == GDK_R && (event->state & modifiers) == (GDK_CONTROL_MASK + GDK_SHIFT_MASK) ) ) { + map_download = TRUE; + map_download_only_new = FALSE; + } + // Standard Ctrl+KP+ / Ctrl+KP- to zoom in/out respectively + else if ( event->keyval == GDK_KEY_KP_Add && (event->state & modifiers) == GDK_CONTROL_MASK ) { + vik_viewport_zoom_in ( vw->viking_vvp ); + draw_update(vw); + return TRUE; // handled keypress + } + else if ( event->keyval == GDK_KEY_KP_Subtract && (event->state & modifiers) == GDK_CONTROL_MASK ) { + vik_viewport_zoom_out ( vw->viking_vvp ); + draw_update(vw); + return TRUE; // handled keypress + } + + if ( map_download ) { + simple_map_update ( vw, map_download_only_new ); + return TRUE; // handled keypress + } + VikLayer *vl = vik_layers_panel_get_selected ( vw->viking_vlp ); if (vl && vw->vt->active_tool != -1 && vw->vt->tools[vw->vt->active_tool].ti.key_press ) { gint ltype = vw->vt->tools[vw->vt->active_tool].layer_type; if ( vl && ltype == vl->type ) return vw->vt->tools[vw->vt->active_tool].ti.key_press(vl, event, vw->vt->tools[vw->vt->active_tool].state); } + + // Ensure called only on window tools (i.e. not on any of the Layer tools since the layer is NULL) + if ( vw->current_tool < TOOL_LAYER ) { + // No layer - but enable window tool keypress processing - these should be able to handle a NULL layer + if ( vw->vt->tools[vw->vt->active_tool].ti.key_press ) { + return vw->vt->tools[vw->vt->active_tool].ti.key_press ( vl, event, vw->vt->tools[vw->vt->active_tool].state ); + } + } + + /* Restore Main Menu via Escape key if the user has hidden it */ + /* This key is more likely to be used as they may not remember the function key */ + if ( event->keyval == GDK_Escape ) { + GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ViewMainMenu" ); + if ( check_box ) { + gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box) ); + if ( !state ) { + gtk_widget_show ( gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu" ) ); + gtk_check_menu_item_set_active ( GTK_CHECK_MENU_ITEM(check_box), TRUE ); + return TRUE; /* handled keypress */ + } + } + } + return FALSE; /* don't handle the keypress */ } @@ -364,7 +969,7 @@ static gboolean delete_event( VikWindow *vw ) _("Do you want to save the changes you made to the document \"%s\"?\n" "\n" "Your changes will be lost if you don't save them."), - vw->filename ? a_file_basename ( vw->filename ) : _("Untitled") ) ); + window_get_filename ( vw ) ) ); gtk_dialog_add_buttons ( dia, _("Don't Save"), GTK_RESPONSE_NO, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_YES, NULL ); switch ( gtk_dialog_run ( dia ) ) { @@ -373,6 +978,39 @@ static gboolean delete_event( VikWindow *vw ) default: gtk_widget_destroy ( GTK_WIDGET(dia) ); return ! save_file(NULL, vw); } } + + if ( window_count == 1 ) { + // On the final window close - save latest state - if it's wanted... + if ( a_vik_get_restore_window_state() ) { + gint state = gdk_window_get_state ( GTK_WIDGET(vw)->window ); + gboolean state_max = state & GDK_WINDOW_STATE_MAXIMIZED; + a_settings_set_boolean ( VIK_SETTINGS_WIN_MAX, state_max ); + + gboolean state_fullscreen = state & GDK_WINDOW_STATE_FULLSCREEN; + a_settings_set_boolean ( VIK_SETTINGS_WIN_FULLSCREEN, state_fullscreen ); + + a_settings_set_boolean ( VIK_SETTINGS_WIN_SIDEPANEL, GTK_WIDGET_VISIBLE (GTK_WIDGET(vw->viking_vlp)) ); + + a_settings_set_boolean ( VIK_SETTINGS_WIN_STATUSBAR, GTK_WIDGET_VISIBLE (GTK_WIDGET(vw->viking_vs)) ); + + a_settings_set_boolean ( VIK_SETTINGS_WIN_TOOLBAR, GTK_WIDGET_VISIBLE (GTK_WIDGET(vw->toolbar)) ); + + // If supersized - no need to save the enlarged width+height values + if ( ! (state_fullscreen || state_max) ) { + gint width, height; + gtk_window_get_size ( GTK_WINDOW (vw), &width, &height ); + a_settings_set_integer ( VIK_SETTINGS_WIN_WIDTH, width ); + a_settings_set_integer ( VIK_SETTINGS_WIN_HEIGHT, height ); + } + + a_settings_set_integer ( VIK_SETTINGS_WIN_PANE_POSITION, gtk_paned_get_position (GTK_PANED(vw->hpaned)) ); + } + + a_settings_set_integer ( VIK_SETTINGS_WIN_SAVE_IMAGE_WIDTH, vw->draw_image_width ); + a_settings_set_integer ( VIK_SETTINGS_WIN_SAVE_IMAGE_HEIGHT, vw->draw_image_height ); + a_settings_set_boolean ( VIK_SETTINGS_WIN_SAVE_IMAGE_PNG, vw->draw_image_save_as_png ); + } + return FALSE; } @@ -392,7 +1030,19 @@ static void draw_sync ( VikWindow *vw ) { vik_viewport_sync(vw->viking_vvp); draw_status ( vw ); - /* other things may be necc here later. */ +} + +/* + * Split the status update, as sometimes only need to update the tool part + * also on initialization the zoom related stuff is not ready to be used + */ +static void draw_status_tool ( VikWindow *vw ) +{ + if ( vw->current_tool == TOOL_LAYER ) + // Use tooltip rather than the internal name as the tooltip is i8n + 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 ); + else + vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_TOOL, _(tool_names[vw->current_tool]) ); } static void draw_status ( VikWindow *vw ) @@ -409,12 +1059,10 @@ static void draw_status ( VikWindow *vw ) else /* xmpp should be a whole number so don't show useless .000 bit */ g_snprintf ( zoom_level, 22, "%d %s", (int)xmpp, unit ); - if ( vw->current_tool == TOOL_LAYER ) - vik_statusbar_set_message ( vw->viking_vs, 0, vik_layer_get_interface(vw->tool_layer_id)->tools[vw->tool_tool_id].name ); - else - vik_statusbar_set_message ( vw->viking_vs, 0, _(tool_names[vw->current_tool]) ); - vik_statusbar_set_message ( vw->viking_vs, 2, zoom_level ); + vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_ZOOM, zoom_level ); + + draw_status_tool ( vw ); } void vik_window_set_redraw_trigger(VikLayer *vl) @@ -431,11 +1079,10 @@ static void window_configure_event ( VikWindow *vw ) if (first) { // This is a hack to set the cursor corresponding to the first tool // FIXME find the correct way to initialize both tool and its cursor - const GdkCursor *cursor = NULL; first = 0; - cursor = toolbox_get_cursor(vw->vt, "Pan"); + vw->viewport_cursor = (GdkCursor *)toolbox_get_cursor(vw->vt, "Pan"); /* We set cursor, even if it is NULL: it resets to default */ - gdk_window_set_cursor ( GTK_WIDGET(vw->viking_vvp)->window, (GdkCursor *)cursor ); + gdk_window_set_cursor ( gtk_widget_get_window(GTK_WIDGET(vw->viking_vvp)), vw->viewport_cursor ); } } @@ -456,9 +1103,25 @@ static void draw_redraw ( VikWindow *vw ) /* actually draw */ vik_viewport_clear ( vw->viking_vvp); + // Main layer drawing vik_layers_panel_draw_all ( vw->viking_vlp ); + // Draw highlight (possibly again but ensures it is on top - especially for when tracks overlap) + if ( vik_viewport_get_draw_highlight (vw->viking_vvp) ) { + if ( vw->containing_vtl && (vw->selected_tracks || vw->selected_waypoints ) ) { + vik_trw_layer_draw_highlight_items ( vw->containing_vtl, vw->selected_tracks, vw->selected_waypoints, vw->viking_vvp ); + } + else if ( vw->containing_vtl && (vw->selected_track || vw->selected_waypoint) ) { + vik_trw_layer_draw_highlight_item ( vw->containing_vtl, vw->selected_track, vw->selected_waypoint, vw->viking_vvp ); + } + else if ( vw->selected_vtl ) { + vik_trw_layer_draw_highlight ( vw->selected_vtl, vw->viking_vvp ); + } + } + // Other viewport decoration items on top if they are enabled/in use vik_viewport_draw_scale ( vw->viking_vvp ); + vik_viewport_draw_copyright ( vw->viking_vvp ); vik_viewport_draw_centermark ( vw->viking_vvp ); + vik_viewport_draw_logo ( vw->viking_vvp ); vik_viewport_set_half_drawn ( vw->viking_vvp, FALSE ); /* just in case. */ } @@ -495,6 +1158,9 @@ static void draw_click (VikWindow *vw, GdkEventButton *event) * for panning and zooming; tools only get left/right/movement */ if ( event->button == 2) { + if ( vw->vt->tools[vw->vt->active_tool].ti.pan_handler ) + // Tool still may need to do something (such as disable something) + toolbox_click(vw->vt, event); vik_window_pan_click ( vw, event ); } else { @@ -514,11 +1180,34 @@ static void vik_window_pan_move (VikWindow *vw, GdkEventMotion *event) } } +/** + * get_location_strings: + * + * Utility function to get positional strings for the given location + * lat and lon strings will get allocated and so need to be freed after use + */ +static void get_location_strings ( VikWindow *vw, struct UTM utm, gchar **lat, gchar **lon ) +{ + if ( vik_viewport_get_drawmode ( vw->viking_vvp ) == VIK_VIEWPORT_DRAWMODE_UTM ) { + // Reuse lat for the first part (Zone + N or S, and lon for the second part (easting and northing) of a UTM format: + // ZONE[N|S] EASTING NORTHING + *lat = g_malloc(4*sizeof(gchar)); + // NB zone is stored in a char but is an actual number + g_snprintf (*lat, 4, "%d%c", utm.zone, utm.letter); + *lon = g_malloc(16*sizeof(gchar)); + g_snprintf (*lon, 16, "%d %d", (gint)utm.easting, (gint)utm.northing); + } + else { + struct LatLon ll; + a_coords_utm_to_latlon ( &utm, &ll ); + a_coords_latlon_to_string ( &ll, lat, lon ); + } +} + static void draw_mouse_motion (VikWindow *vw, GdkEventMotion *event) { static VikCoord coord; static struct UTM utm; - static struct LatLon ll; #define BUFFER_SIZE 50 static char pointer_buf[BUFFER_SIZE]; gchar *lat = NULL, *lon = NULL; @@ -537,8 +1226,9 @@ static void draw_mouse_motion (VikWindow *vw, GdkEventMotion *event) vik_viewport_screen_to_coord ( vw->viking_vvp, event->x, event->y, &coord ); vik_coord_to_utm ( &coord, &utm ); - a_coords_utm_to_latlon ( &utm, &ll ); - a_coords_latlon_to_string ( &ll, &lat, &lon ); + + get_location_strings ( vw, utm, &lat, &lon ); + /* Change interpolate method according to scale */ zoom = vik_viewport_get_zoom(vw->viking_vvp); if (zoom > 2.0) @@ -559,7 +1249,7 @@ static void draw_mouse_motion (VikWindow *vw, GdkEventMotion *event) lat = NULL; g_free (lon); lon = NULL; - vik_statusbar_set_message ( vw->viking_vs, 4, pointer_buf ); + vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_POSITION, pointer_buf ); vik_window_pan_move ( vw, event ); @@ -570,16 +1260,62 @@ static void draw_mouse_motion (VikWindow *vw, GdkEventMotion *event) /* gdk_event_request_motions ( event ); */ } +/** + * Action the single click after a small timeout + * If a double click has occurred then this will do nothing + */ +static gboolean vik_window_pan_timeout (VikWindow *vw) +{ + if ( ! vw->single_click_pending ) { + // Double click happened, so don't do anything + return FALSE; + } + + /* set panning origin */ + vw->pan_move = FALSE; + vw->single_click_pending = FALSE; + vik_viewport_set_center_screen ( vw->viking_vvp, vw->delayed_pan_x, vw->delayed_pan_y ); + draw_update ( vw ); + + // Really turn off the pan moving!! + vw->pan_x = vw->pan_y = -1; + return FALSE; +} + static void vik_window_pan_release ( VikWindow *vw, GdkEventButton *event ) { - if ( vw->pan_move == FALSE ) - vik_viewport_set_center_screen ( vw->viking_vvp, vw->pan_x, vw->pan_y ); - else + gboolean do_draw = TRUE; + + if ( vw->pan_move == FALSE ) { + vw->single_click_pending = !vw->single_click_pending; + + if ( vw->single_click_pending ) { + // Store offset to use + vw->delayed_pan_x = vw->pan_x; + vw->delayed_pan_y = vw->pan_y; + // Get double click time + GtkSettings *gs = gtk_widget_get_settings ( GTK_WIDGET(vw) ); + GValue dct = { 0 }; // = G_VALUE_INIT; // GLIB 2.30+ only + g_value_init ( &dct, G_TYPE_INT ); + g_object_get_property ( G_OBJECT(gs), "gtk-double-click-time", &dct ); + // Give chance for a double click to occur + gint timer = g_value_get_int ( &dct ) + 50; + g_timeout_add ( timer, (GSourceFunc)vik_window_pan_timeout, vw ); + do_draw = FALSE; + } + else { + vik_viewport_set_center_screen ( vw->viking_vvp, vw->pan_x, vw->pan_y ); + } + } + else { vik_viewport_set_center_screen ( vw->viking_vvp, vik_viewport_get_width(vw->viking_vvp)/2 - event->x + vw->pan_x, vik_viewport_get_height(vw->viking_vvp)/2 - event->y + vw->pan_y ); + } + vw->pan_move = FALSE; vw->pan_x = vw->pan_y = -1; - draw_update ( vw ); + if ( do_draw ) + draw_update ( vw ); } static void draw_release ( VikWindow *vw, GdkEventButton *event ) @@ -587,7 +1323,10 @@ static void draw_release ( VikWindow *vw, GdkEventButton *event ) gtk_widget_grab_focus ( GTK_WIDGET(vw->viking_vvp) ); if ( event->button == 2 ) { /* move / pan */ - vik_window_pan_release(vw, event); + if ( vw->vt->tools[vw->vt->active_tool].ti.pan_handler ) + // Tool still may need to do something (such as reenable something) + toolbox_release(vw->vt, event); + vik_window_pan_release ( vw, event ); } else { toolbox_release(vw->vt, event); @@ -610,7 +1349,13 @@ static void draw_scroll (VikWindow *vw, GdkEventScroll *event) else 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 ); } else if ( modifiers == (GDK_CONTROL_MASK | GDK_SHIFT_MASK) ) { - /* control+shift == make sure mouse is still over the same point on the map when we zoom */ + // This zoom is on the center position + if ( event->direction == GDK_SCROLL_UP ) + vik_viewport_zoom_in (vw->viking_vvp); + else + vik_viewport_zoom_out (vw->viking_vvp); + } else { + /* make sure mouse is still over the same point on the map when we zoom */ VikCoord coord; gint x, y; gint center_x = vik_viewport_get_width ( vw->viking_vvp ) / 2; @@ -623,11 +1368,6 @@ static void draw_scroll (VikWindow *vw, GdkEventScroll *event) vik_viewport_coord_to_screen ( vw->viking_vvp, &coord, &x, &y ); vik_viewport_set_center_screen ( vw->viking_vvp, center_x + (x - event->x), center_y + (y - event->y) ); - } else { - if ( event->direction == GDK_SCROLL_UP ) - vik_viewport_zoom_in (vw->viking_vvp); - else - vik_viewport_zoom_out (vw->viking_vvp); } draw_update(vw); @@ -640,7 +1380,6 @@ static void draw_scroll (VikWindow *vw, GdkEventScroll *event) ********************************************************************************/ static void draw_ruler(VikViewport *vvp, GdkDrawable *d, GdkGC *gc, gint x1, gint y1, gint x2, gint y2, gdouble distance) { - PangoFontDescription *pfd; PangoLayout *pl; gchar str[128]; GdkGC *labgc = vik_viewport_new_gc ( vvp, "#cccccc", 1); @@ -649,8 +1388,8 @@ static void draw_ruler(VikViewport *vvp, GdkDrawable *d, GdkGC *gc, gint x1, gin gdouble len = sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)); gdouble dx = (x2-x1)/len*10; gdouble dy = (y2-y1)/len*10; - gdouble c = cos(15.0 * M_PI/180.0); - gdouble s = sin(15.0 * M_PI/180.0); + gdouble c = cos(DEG2RAD(15.0)); + gdouble s = sin(DEG2RAD(15.0)); gdouble angle; gdouble baseangle = 0; gint i; @@ -675,29 +1414,8 @@ static void draw_ruler(VikViewport *vvp, GdkDrawable *d, GdkGC *gc, gint x1, gin /* draw compass */ #define CR 80 #define CW 4 - angle = atan2(dy, dx) + M_PI_2; - - if ( vik_viewport_get_drawmode ( vvp ) == VIK_VIEWPORT_DRAWMODE_UTM) { - VikCoord test; - struct LatLon ll; - struct UTM u; - gint tx, ty; - - vik_viewport_screen_to_coord ( vvp, x1, y1, &test ); - vik_coord_to_latlon ( &test, &ll ); - ll.lat += vik_viewport_get_ympp ( vvp ) * vik_viewport_get_height ( vvp ) / 11000.0; // about 11km per degree latitude - a_coords_latlon_to_utm ( &ll, &u ); - vik_coord_load_from_utm ( &test, VIK_VIEWPORT_DRAWMODE_UTM, &u ); - vik_viewport_coord_to_screen ( vvp, &test, &tx, &ty ); - - baseangle = M_PI - atan2(tx-x1, ty-y1); - angle -= baseangle; - } - if (angle<0) - angle+=2*M_PI; - if (angle>2*M_PI) - angle-=2*M_PI; + vik_viewport_compute_bearing ( vvp, x1, y1, x2, y2, &angle, &baseangle ); { GdkColor color; @@ -706,14 +1424,14 @@ static void draw_ruler(VikViewport *vvp, GdkDrawable *d, GdkGC *gc, gint x1, gin gdk_color_parse("#2255cc", &color); gdk_gc_set_rgb_fg_color(thickgc, &color); } - gdk_draw_arc (d, thickgc, FALSE, x1-CR+CW/2, y1-CR+CW/2, 2*CR-CW, 2*CR-CW, (90 - baseangle*180/M_PI)*64, -angle*180/M_PI*64); + 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); gdk_gc_copy(thickgc, gc); gdk_gc_set_line_attributes(thickgc, 2, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER); for (i=0; i<180; i++) { - c = cos(i*M_PI/90.0 + baseangle); - s = sin(i*M_PI/90.0 + baseangle); + c = cos(DEG2RAD(i)*2 + baseangle); + s = sin(DEG2RAD(i)*2 + baseangle); if (i%5) { gdk_draw_line (d, gc, x1 + CR*c, y1 + CR*s, x1 + (CR+CW)*c, y1 + (CR+CW)*s); @@ -741,11 +1459,7 @@ static void draw_ruler(VikViewport *vvp, GdkDrawable *d, GdkGC *gc, gint x1, gin gint wb, hb, xb, yb; pl = gtk_widget_create_pango_layout (GTK_WIDGET(vvp), NULL); - - pfd = pango_font_description_from_string ("Sans 8"); // FIXME: settable option? global variable? - pango_layout_set_font_description (pl, pfd); - pango_font_description_free (pfd); - + pango_layout_set_font_description (pl, gtk_widget_get_style(GTK_WIDGET(vvp))->font_desc); pango_layout_set_text(pl, "N", -1); gdk_draw_layout(d, gc, x1-5, y1-CR-3*CW-8, pl); @@ -754,20 +1468,29 @@ static void draw_ruler(VikViewport *vvp, GdkDrawable *d, GdkGC *gc, gint x1, gin switch (dist_units) { case VIK_UNITS_DISTANCE_KILOMETRES: if (distance >= 1000 && distance < 100000) { - g_sprintf(str, "%3.2f km", distance/1000.0); + g_sprintf(str, "%3.2f km", distance/1000.0); } else if (distance < 1000) { - g_sprintf(str, "%d m", (int)distance); + g_sprintf(str, "%d m", (int)distance); } else { - g_sprintf(str, "%d km", (int)distance/1000); + g_sprintf(str, "%d km", (int)distance/1000); } break; case VIK_UNITS_DISTANCE_MILES: - if (distance >= 1600 && distance < 160000) { - g_sprintf(str, "%3.2f miles", distance/1600.0); - } else if (distance < 1600) { - g_sprintf(str, "%d yards", (int)(distance*1.0936133)); + if (distance >= VIK_MILES_TO_METERS(1) && distance < VIK_MILES_TO_METERS(100)) { + g_sprintf(str, "%3.2f miles", VIK_METERS_TO_MILES(distance)); + } else if (distance < VIK_MILES_TO_METERS(1)) { + g_sprintf(str, "%d yards", (int)(distance*1.0936133)); + } else { + g_sprintf(str, "%d miles", (int)VIK_METERS_TO_MILES(distance)); + } + break; + case VIK_UNITS_DISTANCE_NAUTICAL_MILES: + if (distance >= VIK_NAUTICAL_MILES_TO_METERS(1) && distance < VIK_NAUTICAL_MILES_TO_METERS(100)) { + g_sprintf(str, "%3.2f NM", VIK_METERS_TO_NAUTICAL_MILES(distance)); + } else if (distance < VIK_NAUTICAL_MILES_TO_METERS(1)) { + g_sprintf(str, "%d yards", (int)(distance*1.0936133)); } else { - g_sprintf(str, "%d miles", (int)distance/1600); + g_sprintf(str, "%d NM", (int)VIK_METERS_TO_NAUTICAL_MILES(distance)); } break; default: @@ -793,7 +1516,7 @@ static void draw_ruler(VikViewport *vvp, GdkDrawable *d, GdkGC *gc, gint x1, gin LABEL(xd, yd, wd, hd); /* draw label with bearing */ - g_sprintf(str, "%3.1f°", angle*180.0/M_PI); + g_sprintf(str, "%3.1f°", RAD2DEG(angle)); pango_layout_set_text(pl, str, -1); pango_layout_get_pixel_size ( pl, &wb, &hb ); xb = x1 + CR*cos(angle-M_PI_2); @@ -854,14 +1577,17 @@ static VikLayerToolFuncStatus ruler_click (VikLayer *vl, GdkEventButton *event, vik_units_distance_t dist_units = a_vik_get_units_distance (); switch (dist_units) { case VIK_UNITS_DISTANCE_KILOMETRES: - temp = g_strdup_printf ( "%s %s DIFF %f meters", lat, lon, vik_coord_diff( &coord, &(s->oldcoord) ) ); - break; + temp = g_strdup_printf ( "%s %s DIFF %f meters", lat, lon, vik_coord_diff( &coord, &(s->oldcoord) ) ); + break; case VIK_UNITS_DISTANCE_MILES: - temp = g_strdup_printf ( "%s %s DIFF %f miles", lat, lon, vik_coord_diff( &coord, &(s->oldcoord) )* 0.000621371192); - break; + temp = g_strdup_printf ( "%s %s DIFF %f miles", lat, lon, VIK_METERS_TO_MILES(vik_coord_diff( &coord, &(s->oldcoord) )) ); + break; + case VIK_UNITS_DISTANCE_NAUTICAL_MILES: + temp = g_strdup_printf ( "%s %s DIFF %f NM", lat, lon, VIK_METERS_TO_NAUTICAL_MILES(vik_coord_diff( &coord, &(s->oldcoord) )) ); + break; default: - temp = g_strdup_printf ("Just to keep the compiler happy"); - g_critical("Houston, we've had a problem. distance=%d", dist_units); + temp = g_strdup_printf ("Just to keep the compiler happy"); + g_critical("Houston, we've had a problem. distance=%d", dist_units); } s->has_oldcoord = FALSE; @@ -871,7 +1597,7 @@ static VikLayerToolFuncStatus ruler_click (VikLayer *vl, GdkEventButton *event, s->has_oldcoord = TRUE; } - vik_statusbar_set_message ( s->vw->viking_vs, 3, temp ); + vik_statusbar_set_message ( s->vw->viking_vs, VIK_STATUSBAR_INFO, temp ); g_free ( temp ); s->oldcoord = coord; @@ -899,25 +1625,25 @@ static VikLayerToolFuncStatus ruler_move (VikLayer *vl, GdkEventMotion *event, r w1 = vik_viewport_get_width(vvp); h1 = vik_viewport_get_height(vvp); if (!buf) { - buf = gdk_pixmap_new ( GTK_WIDGET(vvp)->window, w1, h1, -1 ); + buf = gdk_pixmap_new ( gtk_widget_get_window(GTK_WIDGET(vvp)), w1, h1, -1 ); } gdk_drawable_get_size(buf, &w2, &h2); if (w1 != w2 || h1 != h2) { g_object_unref ( G_OBJECT ( buf ) ); - buf = gdk_pixmap_new ( GTK_WIDGET(vvp)->window, w1, h1, -1 ); + buf = gdk_pixmap_new ( gtk_widget_get_window(GTK_WIDGET(vvp)), w1, h1, -1 ); } vik_viewport_screen_to_coord ( vvp, (gint) event->x, (gint) event->y, &coord ); vik_coord_to_latlon ( &coord, &ll ); vik_viewport_coord_to_screen ( vvp, &s->oldcoord, &oldx, &oldy ); - gdk_draw_drawable (buf, GTK_WIDGET(vvp)->style->black_gc, + gdk_draw_drawable (buf, gtk_widget_get_style(GTK_WIDGET(vvp))->black_gc, vik_viewport_get_pixmap(vvp), 0, 0, 0, 0, -1, -1); - draw_ruler(vvp, buf, GTK_WIDGET(vvp)->style->black_gc, oldx, oldy, event->x, event->y, vik_coord_diff( &coord, &(s->oldcoord)) ); + 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)) ); if (draw_buf_done) { static gpointer pass_along[3]; - pass_along[0] = GTK_WIDGET(vvp)->window; - pass_along[1] = GTK_WIDGET(vvp)->style->black_gc; + pass_along[0] = gtk_widget_get_window(GTK_WIDGET(vvp)); + pass_along[1] = gtk_widget_get_style(GTK_WIDGET(vvp))->black_gc; pass_along[2] = buf; g_idle_add_full (G_PRIORITY_HIGH_IDLE + 10, draw_buf, pass_along, NULL); draw_buf_done = FALSE; @@ -929,13 +1655,16 @@ static VikLayerToolFuncStatus ruler_move (VikLayer *vl, GdkEventMotion *event, r temp = g_strdup_printf ( "%s %s DIFF %f meters", lat, lon, vik_coord_diff( &coord, &(s->oldcoord) ) ); break; case VIK_UNITS_DISTANCE_MILES: - temp = g_strdup_printf ( "%s %s DIFF %f miles", lat, lon, vik_coord_diff( &coord, &(s->oldcoord) )* 0.000621371192); + temp = g_strdup_printf ( "%s %s DIFF %f miles", lat, lon, VIK_METERS_TO_MILES (vik_coord_diff( &coord, &(s->oldcoord) )) ); + break; + case VIK_UNITS_DISTANCE_NAUTICAL_MILES: + temp = g_strdup_printf ( "%s %s DIFF %f NM", lat, lon, VIK_METERS_TO_NAUTICAL_MILES (vik_coord_diff( &coord, &(s->oldcoord) )) ); break; default: temp = g_strdup_printf ("Just to keep the compiler happy"); g_critical("Houston, we've had a problem. distance=%d", dist_units); } - vik_statusbar_set_message ( vw->viking_vs, 3, temp ); + vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_INFO, temp ); g_free ( temp ); } return VIK_LAYER_TOOL_ACK; @@ -951,8 +1680,20 @@ static void ruler_deactivate (VikLayer *vl, ruler_tool_state_t *s) draw_update ( s->vw ); } -static VikToolInterface ruler_tool = - { "Ruler", +static gboolean ruler_key_press (VikLayer *vl, GdkEventKey *event, ruler_tool_state_t *s) +{ + if (event->keyval == GDK_Escape) { + s->has_oldcoord = FALSE; + ruler_deactivate ( vl, s ); + return TRUE; + } + // Regardless of whether we used it, return false so other GTK things may use it + return FALSE; +} + +static VikToolInterface ruler_tool = + // NB Ctrl+Shift+R is used for Refresh (deemed more important), so use 'U' instead + { { "Ruler", "vik-icon-ruler", N_("_Ruler"), "U", N_("Ruler Tool"), 2 }, (VikToolConstructorFunc) ruler_create, (VikToolDestructorFunc) ruler_destroy, (VikToolActivationFunc) NULL, @@ -960,9 +1701,11 @@ static VikToolInterface ruler_tool = (VikToolMouseFunc) ruler_click, (VikToolMouseMoveFunc) ruler_move, (VikToolMouseFunc) ruler_release, - NULL, + (VikToolKeyFunc) ruler_key_press, + FALSE, GDK_CURSOR_IS_PIXMAP, - &cursor_ruler_pixbuf }; + &cursor_ruler_pixbuf, + NULL }; /*** end ruler code ********************************************************/ @@ -970,45 +1713,272 @@ static VikToolInterface ruler_tool = /******************************************************************************** ** Zoom tool code ********************************************************************************/ + +typedef struct { + VikWindow *vw; + GdkPixmap *pixmap; + // Track zoom bounds for zoom tool with shift modifier: + gboolean bounds_active; + gint start_x; + gint start_y; +} zoom_tool_state_t; + +/* + * In case the screen size has changed + */ +static void zoomtool_resize_pixmap (zoom_tool_state_t *zts) +{ + int w1, h1, w2, h2; + + // Allocate a drawing area the size of the viewport + w1 = vik_viewport_get_width ( zts->vw->viking_vvp ); + h1 = vik_viewport_get_height ( zts->vw->viking_vvp ); + + if ( !zts->pixmap ) { + // Totally new + zts->pixmap = gdk_pixmap_new ( gtk_widget_get_window(GTK_WIDGET(zts->vw->viking_vvp)), w1, h1, -1 ); + } + + gdk_drawable_get_size ( zts->pixmap, &w2, &h2 ); + + if ( w1 != w2 || h1 != h2 ) { + // Has changed - delete and recreate with new values + g_object_unref ( G_OBJECT ( zts->pixmap ) ); + zts->pixmap = gdk_pixmap_new ( gtk_widget_get_window(GTK_WIDGET(zts->vw->viking_vvp)), w1, h1, -1 ); + } +} + static gpointer zoomtool_create (VikWindow *vw, VikViewport *vvp) { - return vw; + zoom_tool_state_t *zts = g_new(zoom_tool_state_t, 1); + zts->vw = vw; + zts->pixmap = NULL; + zts->start_x = 0; + zts->start_y = 0; + zts->bounds_active = FALSE; + return zts; } -static VikLayerToolFuncStatus zoomtool_click (VikLayer *vl, GdkEventButton *event, VikWindow *vw) +static void zoomtool_destroy ( zoom_tool_state_t *zts) { - vw->modified = TRUE; - vik_viewport_set_center_screen ( vw->viking_vvp, (gint) event->x, (gint) event->y ); - if ( event->button == 1 ) - vik_viewport_zoom_in (vw->viking_vvp); - else if ( event->button == 3 ) - vik_viewport_zoom_out (vw->viking_vvp); - draw_update ( vw ); + if ( zts->pixmap ) + g_object_unref ( G_OBJECT ( zts->pixmap ) ); + g_free(zts); +} + +static VikLayerToolFuncStatus zoomtool_click (VikLayer *vl, GdkEventButton *event, zoom_tool_state_t *zts) +{ + zts->vw->modified = TRUE; + guint modifiers = event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK); + + VikCoord coord; + gint x, y; + gint center_x = vik_viewport_get_width ( zts->vw->viking_vvp ) / 2; + gint center_y = vik_viewport_get_height ( zts->vw->viking_vvp ) / 2; + + gboolean skip_update = FALSE; + + zts->bounds_active = FALSE; + + if ( modifiers == (GDK_CONTROL_MASK | GDK_SHIFT_MASK) ) { + // This zoom is on the center position + vik_viewport_set_center_screen ( zts->vw->viking_vvp, center_x, center_y ); + if ( event->button == 1 ) + vik_viewport_zoom_in (zts->vw->viking_vvp); + else if ( event->button == 3 ) + vik_viewport_zoom_out (zts->vw->viking_vvp); + } + else if ( modifiers == GDK_CONTROL_MASK ) { + // This zoom is to recenter on the mouse position + vik_viewport_set_center_screen ( zts->vw->viking_vvp, (gint) event->x, (gint) event->y ); + if ( event->button == 1 ) + vik_viewport_zoom_in (zts->vw->viking_vvp); + else if ( event->button == 3 ) + vik_viewport_zoom_out (zts->vw->viking_vvp); + } + else if ( modifiers == GDK_SHIFT_MASK ) { + // Get start of new zoom bounds + if ( event->button == 1 ) { + zts->bounds_active = TRUE; + zts->start_x = (gint) event->x; + zts->start_y = (gint) event->y; + skip_update = TRUE; + } + } + else { + /* make sure mouse is still over the same point on the map when we zoom */ + vik_viewport_screen_to_coord ( zts->vw->viking_vvp, event->x, event->y, &coord ); + if ( event->button == 1 ) + vik_viewport_zoom_in (zts->vw->viking_vvp); + else if ( event->button == 3 ) + vik_viewport_zoom_out(zts->vw->viking_vvp); + vik_viewport_coord_to_screen ( zts->vw->viking_vvp, &coord, &x, &y ); + vik_viewport_set_center_screen ( zts->vw->viking_vvp, + center_x + (x - event->x), + center_y + (y - event->y) ); + } + + if ( !skip_update ) + draw_update ( zts->vw ); + return VIK_LAYER_TOOL_ACK; } -static VikLayerToolFuncStatus zoomtool_move (VikLayer *vl, GdkEventMotion *event, VikViewport *vvp) +static VikLayerToolFuncStatus zoomtool_move (VikLayer *vl, GdkEventMotion *event, zoom_tool_state_t *zts) { + guint modifiers = event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK); + + if ( zts->bounds_active && modifiers == GDK_SHIFT_MASK ) { + zoomtool_resize_pixmap ( zts ); + + // Blank out currently drawn area + gdk_draw_drawable ( zts->pixmap, + gtk_widget_get_style(GTK_WIDGET(zts->vw->viking_vvp))->black_gc, + vik_viewport_get_pixmap(zts->vw->viking_vvp), + 0, 0, 0, 0, -1, -1); + + // Calculate new box starting point & size in pixels + int xx, yy, width, height; + if ( event->y > zts->start_y ) { + yy = zts->start_y; + height = event->y-zts->start_y; + } + else { + yy = event->y; + height = zts->start_y-event->y; + } + if ( event->x > zts->start_x ) { + xx = zts->start_x; + width = event->x-zts->start_x; + } + else { + xx = event->x; + width = zts->start_x-event->x; + } + + // Draw the box + gdk_draw_rectangle (zts->pixmap, gtk_widget_get_style(GTK_WIDGET(zts->vw->viking_vvp))->black_gc, FALSE, xx, yy, width, height); + + // Only actually draw when there's time to do so + if (draw_buf_done) { + static gpointer pass_along[3]; + pass_along[0] = gtk_widget_get_window(GTK_WIDGET(zts->vw->viking_vvp)); + pass_along[1] = gtk_widget_get_style(GTK_WIDGET(zts->vw->viking_vvp))->black_gc; + pass_along[2] = zts->pixmap; + g_idle_add_full (G_PRIORITY_HIGH_IDLE + 10, draw_buf, pass_along, NULL); + draw_buf_done = FALSE; + } + } + else + zts->bounds_active = FALSE; + return VIK_LAYER_TOOL_ACK; } -static VikLayerToolFuncStatus zoomtool_release (VikLayer *vl, GdkEventButton *event, VikViewport *vvp) +static VikLayerToolFuncStatus zoomtool_release (VikLayer *vl, GdkEventButton *event, zoom_tool_state_t *zts) { + guint modifiers = event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK); + + // Ensure haven't just released on the exact same position + // i.e. probably haven't moved the mouse at all + if ( zts->bounds_active && modifiers == GDK_SHIFT_MASK && + ( event->x < zts->start_x-5 || event->x > zts->start_x+5 ) && + ( event->y < zts->start_y-5 || event->y > zts->start_y+5 ) ) { + + VikCoord coord1, coord2; + vik_viewport_screen_to_coord ( zts->vw->viking_vvp, zts->start_x, zts->start_y, &coord1); + vik_viewport_screen_to_coord ( zts->vw->viking_vvp, event->x, event->y, &coord2); + + // From the extend of the bounds pick the best zoom level + // c.f. trw_layer_zoom_to_show_latlons() + // Maybe refactor... + struct LatLon ll1, ll2; + vik_coord_to_latlon(&coord1, &ll1); + vik_coord_to_latlon(&coord2, &ll2); + struct LatLon average = { (ll1.lat+ll2.lat)/2, + (ll1.lon+ll2.lon)/2 }; + + VikCoord new_center; + vik_coord_load_from_latlon ( &new_center, vik_viewport_get_coord_mode ( zts->vw->viking_vvp ), &average ); + vik_viewport_set_center_coord ( zts->vw->viking_vvp, &new_center, FALSE ); + + /* Convert into definite 'smallest' and 'largest' positions */ + struct LatLon minmin; + if ( ll1.lat < ll2.lat ) + minmin.lat = ll1.lat; + else + minmin.lat = ll2.lat; + + struct LatLon maxmax; + if ( ll1.lon > ll2.lon ) + maxmax.lon = ll1.lon; + else + maxmax.lon = ll2.lon; + + /* Always recalculate the 'best' zoom level */ + gdouble zoom = VIK_VIEWPORT_MIN_ZOOM; + vik_viewport_set_zoom ( zts->vw->viking_vvp, zoom ); + + gdouble min_lat, max_lat, min_lon, max_lon; + /* Should only be a maximum of about 18 iterations from min to max zoom levels */ + while ( zoom <= VIK_VIEWPORT_MAX_ZOOM ) { + vik_viewport_get_min_max_lat_lon ( zts->vw->viking_vvp, &min_lat, &max_lat, &min_lon, &max_lon ); + /* NB I think the logic used in this test to determine if the bounds is within view + fails if track goes across 180 degrees longitude. + Hopefully that situation is not too common... + Mind you viking doesn't really do edge locations to well anyway */ + if ( min_lat < minmin.lat && + max_lat > minmin.lat && + min_lon < maxmax.lon && + max_lon > maxmax.lon ) + /* Found within zoom level */ + break; + + /* Try next */ + zoom = zoom * 2; + vik_viewport_set_zoom ( zts->vw->viking_vvp, zoom ); + } + } + else { + // When pressing shift and clicking for zoom, then jump three levels + if ( modifiers == GDK_SHIFT_MASK ) { + // Zoom in/out by three if possible + vik_viewport_set_center_screen ( zts->vw->viking_vvp, event->x, event->y ); + if ( event->button == 1 ) { + vik_viewport_zoom_in ( zts->vw->viking_vvp ); + vik_viewport_zoom_in ( zts->vw->viking_vvp ); + vik_viewport_zoom_in ( zts->vw->viking_vvp ); + } + else if ( event->button == 3 ) { + vik_viewport_zoom_out ( zts->vw->viking_vvp ); + vik_viewport_zoom_out ( zts->vw->viking_vvp ); + vik_viewport_zoom_out ( zts->vw->viking_vvp ); + } + } + } + + draw_update ( zts->vw ); + + // Reset + zts->bounds_active = FALSE; + return VIK_LAYER_TOOL_ACK; } static VikToolInterface zoom_tool = - { "Zoom", + { { "Zoom", "vik-icon-zoom", N_("_Zoom"), "Z", N_("Zoom Tool"), 1 }, (VikToolConstructorFunc) zoomtool_create, - (VikToolDestructorFunc) NULL, + (VikToolDestructorFunc) zoomtool_destroy, (VikToolActivationFunc) NULL, (VikToolActivationFunc) NULL, (VikToolMouseFunc) zoomtool_click, (VikToolMouseMoveFunc) zoomtool_move, (VikToolMouseFunc) zoomtool_release, NULL, + FALSE, GDK_CURSOR_IS_PIXMAP, - &cursor_zoom_pixbuf }; + &cursor_zoom_pixbuf, + NULL }; /*** end zoom code ********************************************************/ /******************************************************************************** @@ -1019,12 +1989,31 @@ static gpointer pantool_create (VikWindow *vw, VikViewport *vvp) return vw; } +// NB Double clicking means this gets called THREE times!!! static VikLayerToolFuncStatus pantool_click (VikLayer *vl, GdkEventButton *event, VikWindow *vw) { vw->modified = TRUE; - if ( event->button == 1 ) - vik_window_pan_click ( vw, event ); - draw_update ( vw ); + + if ( event->type == GDK_2BUTTON_PRESS ) { + // Zoom in / out on double click + // No need to change the center as that has already occurred in the first click of a double click occurrence + if ( event->button == 1 ) { + guint modifier = event->state & GDK_SHIFT_MASK; + if ( modifier ) + vik_viewport_zoom_out ( vw->viking_vvp ); + else + vik_viewport_zoom_in ( vw->viking_vvp ); + } + else if ( event->button == 3 ) + vik_viewport_zoom_out ( vw->viking_vvp ); + + draw_update ( vw ); + } + else + // Standard pan click + if ( event->button == 1 ) + vik_window_pan_click ( vw, event ); + return VIK_LAYER_TOOL_ACK; } @@ -1042,7 +2031,7 @@ static VikLayerToolFuncStatus pantool_release (VikLayer *vl, GdkEventButton *eve } static VikToolInterface pan_tool = - { "Pan", + { { "Pan", "vik-icon-pan", N_("_Pan"), "P", N_("Pan Tool"), 0 }, (VikToolConstructorFunc) pantool_create, (VikToolDestructorFunc) NULL, (VikToolActivationFunc) NULL, @@ -1051,11 +2040,139 @@ static VikToolInterface pan_tool = (VikToolMouseMoveFunc) pantool_move, (VikToolMouseFunc) pantool_release, NULL, - GDK_FLEUR }; + FALSE, + GDK_FLEUR, + NULL, + NULL }; /*** end pan code ********************************************************/ +/******************************************************************************** + ** Select tool code + ********************************************************************************/ +static gpointer selecttool_create (VikWindow *vw, VikViewport *vvp) +{ + tool_ed_t *t = g_new(tool_ed_t, 1); + t->vw = vw; + t->vvp = vvp; + t->vtl = NULL; + t->is_waypoint = FALSE; + return t; +} + +static void selecttool_destroy (tool_ed_t *t) +{ + g_free(t); +} + +typedef struct { + gboolean cont; + VikViewport *vvp; + GdkEventButton *event; + tool_ed_t *tool_edit; +} clicker; + +static void click_layer_selected (VikLayer *vl, clicker *ck) +{ + /* Do nothing when function call returns true; */ + /* i.e. stop on first found item */ + if ( ck->cont ) + if ( vl->visible ) + if ( vik_layer_get_interface(vl->type)->select_click ) + ck->cont = !vik_layer_get_interface(vl->type)->select_click ( vl, ck->event, ck->vvp, ck->tool_edit ); +} + +static VikLayerToolFuncStatus selecttool_click (VikLayer *vl, GdkEventButton *event, tool_ed_t *t) +{ + /* Only allow selection on primary button */ + if ( event->button == 1 ) { + /* Enable click to apply callback to potentially all track/waypoint layers */ + /* Useful as we can find things that aren't necessarily in the currently selected layer */ + GList* gl = vik_layers_panel_get_all_layers_of_type ( t->vw->viking_vlp, VIK_LAYER_TRW, FALSE ); // Don't get invisible layers + clicker ck; + ck.cont = TRUE; + ck.vvp = t->vw->viking_vvp; + ck.event = event; + ck.tool_edit = t; + g_list_foreach ( gl, (GFunc) click_layer_selected, &ck ); + g_list_free ( gl ); + + // If nothing found then deselect & redraw screen if necessary to remove the highlight + if ( ck.cont ) { + GtkTreeIter iter; + VikTreeview *vtv = vik_layers_panel_get_treeview ( t->vw->viking_vlp ); + + if ( vik_treeview_get_selected_iter ( vtv, &iter ) ) { + // Only clear if selected thing is a TrackWaypoint layer or a sublayer + gint type = vik_treeview_item_get_type ( vtv, &iter ); + if ( type == VIK_TREEVIEW_TYPE_SUBLAYER || + VIK_LAYER(vik_treeview_item_get_pointer ( vtv, &iter ))->type == VIK_LAYER_TRW ) { + + vik_treeview_item_unselect ( vtv, &iter ); + if ( vik_window_clear_highlight ( t->vw ) ) + draw_update ( t->vw ); + } + } + } + } + else if ( ( event->button == 3 ) && ( vl && ( vl->type == VIK_LAYER_TRW ) ) ) { + if ( vl->visible ) + /* Act on currently selected item to show menu */ + if ( t->vw->selected_track || t->vw->selected_waypoint ) + if ( vik_layer_get_interface(vl->type)->show_viewport_menu ) + vik_layer_get_interface(vl->type)->show_viewport_menu ( vl, event, t->vw->viking_vvp ); + } + + return VIK_LAYER_TOOL_ACK; +} + +static VikLayerToolFuncStatus selecttool_move (VikLayer *vl, GdkEventButton *event, tool_ed_t *t) +{ + /* Only allow selection on primary button */ + if ( event->button == 1 ) { + // Don't care about vl here + if ( t->vtl ) + if ( vik_layer_get_interface(VIK_LAYER_TRW)->select_move ) + vik_layer_get_interface(VIK_LAYER_TRW)->select_move ( vl, event, t->vvp, t ); + } + return VIK_LAYER_TOOL_ACK; +} + +static VikLayerToolFuncStatus selecttool_release (VikLayer *vl, GdkEventButton *event, tool_ed_t *t) +{ + /* Only allow selection on primary button */ + if ( event->button == 1 ) { + // Don't care about vl here + if ( t->vtl ) + if ( vik_layer_get_interface(VIK_LAYER_TRW)->select_release ) + vik_layer_get_interface(VIK_LAYER_TRW)->select_release ( (VikLayer*)t->vtl, event, t->vvp, t ); + } + return VIK_LAYER_TOOL_ACK; +} + +static VikToolInterface select_tool = + { { "Select", "vik-icon-select", N_("_Select"), "S", N_("Select Tool"), 3 }, + (VikToolConstructorFunc) selecttool_create, + (VikToolDestructorFunc) selecttool_destroy, + (VikToolActivationFunc) NULL, + (VikToolActivationFunc) NULL, + (VikToolMouseFunc) selecttool_click, + (VikToolMouseMoveFunc) selecttool_move, + (VikToolMouseFunc) selecttool_release, + (VikToolKeyFunc) NULL, + FALSE, + GDK_LEFT_PTR, + NULL, + NULL }; +/*** end select tool code ********************************************************/ + static void draw_pan_cb ( GtkAction *a, VikWindow *vw ) { + // Since the treeview cell editting intercepts standard keyboard handlers, it means we can receive events here + // Thus if currently editting, ensure we don't move the viewport when Ctrl+ is received + VikLayer *sel = vik_layers_panel_get_selected ( vw->viking_vlp ); + if ( sel && vik_treeview_get_editing ( sel->vt ) ) + return; + if (!strcmp(gtk_action_get_name(a), "PanNorth")) { vik_viewport_set_center_screen ( vw->viking_vvp, vik_viewport_get_width(vw->viking_vvp)/2, 0 ); } else if (!strcmp(gtk_action_get_name(a), "PanEast")) { @@ -1111,7 +2228,7 @@ static void draw_zoom_cb ( GtkAction *a, VikWindow *vw ) draw_update ( vw ); } -void draw_goto_cb ( GtkAction *a, VikWindow *vw ) +static void draw_goto_cb ( GtkAction *a, VikWindow *vw ) { VikCoord new_center; @@ -1136,18 +2253,69 @@ void draw_goto_cb ( GtkAction *a, VikWindow *vw ) return; } - vik_viewport_set_center_coord ( vw->viking_vvp, &new_center ); + vik_viewport_set_center_coord ( vw->viking_vvp, &new_center, TRUE ); draw_update ( vw ); } +/** + * center_changed_cb: + */ +static void center_changed_cb ( VikWindow *vw ) +{ +// ATM Keep back always available, so when we pan - we can jump to the last requested position +/* + GtkAction* action_back = gtk_action_group_get_action ( vw->action_group, "GoBack" ); + if ( action_back ) { + gtk_action_set_sensitive ( action_back, vik_viewport_back_available(vw->viking_vvp) ); + } +*/ + GtkAction* action_forward = gtk_action_group_get_action ( vw->action_group, "GoForward" ); + if ( action_forward ) { + gtk_action_set_sensitive ( action_forward, vik_viewport_forward_available(vw->viking_vvp) ); + } +} + +/** + * draw_goto_back_and_forth: + */ +static void draw_goto_back_and_forth ( GtkAction *a, VikWindow *vw ) +{ + gboolean changed = FALSE; + if (!strcmp(gtk_action_get_name(a), "GoBack")) { + changed = vik_viewport_go_back ( vw->viking_vvp ); + } + else if (!strcmp(gtk_action_get_name(a), "GoForward")) { + changed = vik_viewport_go_forward ( vw->viking_vvp ); + } + else { + return; + } + + // Recheck buttons sensitivities, as the center changed signal is not sent on back/forward changes + // (otherwise we would get stuck in an infinite loop!) + center_changed_cb ( vw ); + + if ( changed ) + draw_update ( vw ); +} + +/** + * Refresh maps displayed + */ +static void draw_refresh_cb ( GtkAction *a, VikWindow *vw ) +{ + // Only get 'new' maps + simple_map_update ( vw, TRUE ); +} + static void menu_addlayer_cb ( GtkAction *a, VikWindow *vw ) { - gint type; + VikLayerTypeEnum type; for ( type = 0; type < VIK_LAYER_NUM_TYPES; type++ ) { if (!strcmp(vik_layer_get_interface(type)->name, gtk_action_get_name(a))) { if ( vik_layers_panel_new_layer ( vw->viking_vlp, type ) ) { - draw_update ( vw ); - vw->modified = TRUE; + draw_update ( vw ); + vw->modified = TRUE; } } } @@ -1160,15 +2328,14 @@ static void menu_copy_layer_cb ( GtkAction *a, VikWindow *vw ) static void menu_cut_layer_cb ( GtkAction *a, VikWindow *vw ) { - a_clipboard_copy_selected ( vw->viking_vlp ); - menu_delete_layer_cb ( a, vw ); + vik_layers_panel_cut_selected ( vw->viking_vlp ); + vw->modified = TRUE; } static void menu_paste_layer_cb ( GtkAction *a, VikWindow *vw ) { - if ( a_clipboard_paste ( vw->viking_vlp ) ) + if ( vik_layers_panel_paste_selected ( vw->viking_vlp ) ) { - draw_update ( vw ); vw->modified = TRUE; } } @@ -1181,12 +2348,23 @@ static void menu_properties_cb ( GtkAction *a, VikWindow *vw ) static void help_help_cb ( GtkAction *a, VikWindow *vw ) { -#if GTK_CHECK_VERSION (2, 14, 0) +#ifdef WINDOWS + ShellExecute(NULL, "open", ""PACKAGE".pdf", NULL, NULL, SW_SHOWNORMAL); +#else /* WINDOWS */ gchar *uri; uri = g_strdup_printf("ghelp:%s", PACKAGE); - gtk_show_uri(NULL, uri, GDK_CURRENT_TIME, NULL); + GError *error = NULL; + gboolean show = gtk_show_uri (NULL, uri, GDK_CURRENT_TIME, &error); + if ( !show && !error ) + // No error to show, so unlikely this will get called + a_dialog_error_msg ( GTK_WINDOW(vw), _("The help system is not available.") ); + else if ( error ) { + // Main error path + 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 ); + g_error_free ( error ); + } g_free(uri); -#endif +#endif /* WINDOWS */ } static void help_about_cb ( GtkAction *a, VikWindow *vw ) @@ -1194,6 +2372,23 @@ static void help_about_cb ( GtkAction *a, VikWindow *vw ) a_dialog_about(GTK_WINDOW(vw)); } +static void help_cache_info_cb ( GtkAction *a, VikWindow *vw ) +{ + // NB: No i18n as this is just for debug + gint byte_size = a_mapcache_get_size(); + gchar *msg_sz = NULL; + gchar *msg = NULL; +#if GLIB_CHECK_VERSION(2,30,0) + msg_sz = g_format_size_full ( byte_size, G_FORMAT_SIZE_LONG_FORMAT ); +#else + msg_sz = g_format_size_for_display ( byte_size ); +#endif + msg = g_strdup_printf ( "Map Cache size is %s with %d items", msg_sz, a_mapcache_get_count()); + a_dialog_info_msg_extra ( GTK_WINDOW(vw), "%s", msg ); + g_free ( msg_sz ); + g_free ( msg ); +} + static void menu_delete_layer_cb ( GtkAction *a, VikWindow *vw ) { if ( vik_layers_panel_get_selected ( vw->viking_vlp ) ) @@ -1207,7 +2402,7 @@ static void menu_delete_layer_cb ( GtkAction *a, VikWindow *vw ) static void view_side_panel_cb ( GtkAction *a, VikWindow *vw ) { - GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/ViewSidePanel" ); + GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ViewSidePanel" ); g_assert(check_box); gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box)); if ( state ) @@ -1218,7 +2413,7 @@ static void view_side_panel_cb ( GtkAction *a, VikWindow *vw ) static void view_statusbar_cb ( GtkAction *a, VikWindow *vw ) { - GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/ViewStatusBar" ); + GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ViewStatusBar" ); if ( !check_box ) return; gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box) ); @@ -1228,6 +2423,30 @@ static void view_statusbar_cb ( GtkAction *a, VikWindow *vw ) gtk_widget_hide ( GTK_WIDGET(vw->viking_vs) ); } +static void view_toolbar_cb ( GtkAction *a, VikWindow *vw ) +{ + GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ViewToolbar" ); + if ( !check_box ) + return; + gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box) ); + if ( state ) + gtk_widget_show ( GTK_WIDGET(vw->toolbar) ); + else + gtk_widget_hide ( GTK_WIDGET(vw->toolbar) ); +} + +static void view_main_menu_cb ( GtkAction *a, VikWindow *vw ) +{ + GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ViewMainMenu" ); + if ( !check_box ) + return; + gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box) ); + if ( !state ) + gtk_widget_hide ( gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu" ) ); + else + gtk_widget_show ( gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu" ) ); +} + /*************************************** ** tool management routines ** @@ -1240,10 +2459,6 @@ static toolbox_tools_t* toolbox_create(VikWindow *vw) vt->n_tools = 0; vt->active_tool = -1; vt->vw = vw; - if (!vw->viking_vvp) { - g_critical("no viewport found."); - exit(1); - } return vt; } @@ -1265,7 +2480,7 @@ static int toolbox_get_tool(toolbox_tools_t *vt, const gchar *tool_name) { int i; for (i=0; in_tools; i++) { - if (!strcmp(tool_name, vt->tools[i].ti.name)) { + if (!strcmp(tool_name, vt->tools[i].ti.radioActionEntry.name)) { break; } } @@ -1280,7 +2495,7 @@ static void toolbox_activate(toolbox_tools_t *vt, const gchar *tool_name) if (tool == vt->n_tools) { g_critical("trying to activate a non-existent tool..."); - exit(1); + return; } /* is the tool already active? */ if (vt->active_tool == tool) { @@ -1350,21 +2565,21 @@ static void toolbox_release (toolbox_tools_t *vt, GdkEventButton *event) void vik_window_enable_layer_tool ( VikWindow *vw, gint layer_id, gint tool_id ) { - gtk_action_activate ( gtk_action_group_get_action ( vw->action_group, vik_layer_get_interface(layer_id)->tools[tool_id].name ) ); + gtk_action_activate ( gtk_action_group_get_action ( vw->action_group, vik_layer_get_interface(layer_id)->tools[tool_id].radioActionEntry.name ) ); } /* this function gets called whenever a toolbar tool is clicked */ static void menu_tool_cb ( GtkAction *old, GtkAction *a, VikWindow *vw ) { /* White Magic, my friends ... White Magic... */ - int layer_id, tool_id; - const GdkCursor *cursor = NULL; - + gint tool_id; toolbox_activate(vw->vt, gtk_action_get_name(a)); - cursor = toolbox_get_cursor(vw->vt, gtk_action_get_name(a)); - /* We set cursor, even if it is NULL: it resets to default */ - gdk_window_set_cursor ( GTK_WIDGET(vw->viking_vvp)->window, (GdkCursor *)cursor ); + vw->viewport_cursor = (GdkCursor *)toolbox_get_cursor(vw->vt, gtk_action_get_name(a)); + + if ( gtk_widget_get_window(GTK_WIDGET(vw->viking_vvp)) ) + /* We set cursor, even if it is NULL: it resets to default */ + gdk_window_set_cursor ( gtk_widget_get_window(GTK_WIDGET(vw->viking_vvp)), vw->viewport_cursor ); if (!strcmp(gtk_action_get_name(a), "Pan")) { vw->current_tool = TOOL_PAN; @@ -1375,19 +2590,22 @@ static void menu_tool_cb ( GtkAction *old, GtkAction *a, VikWindow *vw ) else if (!strcmp(gtk_action_get_name(a), "Ruler")) { vw->current_tool = TOOL_RULER; } + else if (!strcmp(gtk_action_get_name(a), "Select")) { + vw->current_tool = TOOL_SELECT; + } else { - /* TODO: only enable tools from active layer */ + VikLayerTypeEnum layer_id; for (layer_id=0; layer_idtools_count; tool_id++ ) { - if (!strcmp(vik_layer_get_interface(layer_id)->tools[tool_id].name, gtk_action_get_name(a))) { + if (!strcmp(vik_layer_get_interface(layer_id)->tools[tool_id].radioActionEntry.name, gtk_action_get_name(a))) { vw->current_tool = TOOL_LAYER; vw->tool_layer_id = layer_id; vw->tool_tool_id = tool_id; - } + } } } } - draw_status ( vw ); + draw_status_tool ( vw ); } static void window_set_filename ( VikWindow *vw, const gchar *filename ) @@ -1399,18 +2617,24 @@ static void window_set_filename ( VikWindow *vw, const gchar *filename ) if ( filename == NULL ) { vw->filename = NULL; - file = _("Untitled"); } else { vw->filename = g_strdup(filename); - file = a_file_basename ( filename ); } + + /* Refresh window's title */ + file = window_get_filename ( vw ); title = g_strdup_printf( "%s - Viking", file ); gtk_window_set_title ( GTK_WINDOW(vw), title ); g_free ( title ); } +static const gchar *window_get_filename ( VikWindow *vw ) +{ + return vw->filename ? a_file_basename ( vw->filename ) : _("Untitled"); +} + GtkWidget *vik_window_get_drawmode_button ( VikWindow *vw, VikViewportDrawMode mode ) { GtkWidget *mode_button; @@ -1420,6 +2644,7 @@ GtkWidget *vik_window_get_drawmode_button ( VikWindow *vw, VikViewportDrawMode m case VIK_VIEWPORT_DRAWMODE_EXPEDIA: buttonname = "/ui/MainMenu/View/ModeExpedia"; break; #endif case VIK_VIEWPORT_DRAWMODE_MERCATOR: buttonname = "/ui/MainMenu/View/ModeMercator"; break; + case VIK_VIEWPORT_DRAWMODE_LATLON: buttonname = "/ui/MainMenu/View/ModeLatLon"; break; default: buttonname = "/ui/MainMenu/View/ModeUTM"; } mode_button = gtk_ui_manager_get_widget ( vw->uim, buttonname ); @@ -1457,12 +2682,15 @@ static void on_activate_recent_item (GtkRecentChooser *chooser, g_object_unref ( file ); if ( self->filename ) { - gchar *filenames[] = { path, NULL }; + GSList *filenames = NULL; + filenames = g_slist_append ( filenames, path ); g_signal_emit ( G_OBJECT(self), window_signals[VW_OPENWINDOW_SIGNAL], 0, filenames ); + // NB: GSList & contents are freed by main.open_window } - else + else { vik_window_open_file ( self, path, TRUE ); - g_free ( path ); + g_free ( path ); + } } g_free (filename); @@ -1482,6 +2710,7 @@ static void setup_recent_files (VikWindow *self) menu = gtk_recent_chooser_menu_new_for_manager (manager); gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER (menu), GTK_RECENT_SORT_MRU); gtk_recent_chooser_add_filter (GTK_RECENT_CHOOSER (menu), filter); + gtk_recent_chooser_set_limit (GTK_RECENT_CHOOSER (menu), a_vik_get_recent_number_files() ); menu_item = gtk_ui_manager_get_widget (self->uim, "/ui/MainMenu/File/OpenRecentFile"); gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), menu); @@ -1490,7 +2719,10 @@ static void setup_recent_files (VikWindow *self) G_CALLBACK (on_activate_recent_item), (gpointer) self); } -static void update_recently_used_document(const gchar *filename) +/* + * + */ +static void update_recently_used_document (VikWindow *vw, const gchar *filename) { /* Update Recently Used Document framework */ GtkRecentManager *manager = gtk_recent_manager_get_default(); @@ -1511,7 +2743,9 @@ static void update_recently_used_document(const gchar *filename) recent_data->is_private = FALSE; if (!gtk_recent_manager_add_full (manager, uri, recent_data)) { - g_warning (_("Unable to add '%s' to the list of recently used documents"), uri); + gchar *msg = g_strdup_printf (_("Unable to add '%s' to the list of recently used documents"), uri); + vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_INFO, msg ); + g_free ( msg ); } g_free (uri); @@ -1520,15 +2754,65 @@ static void update_recently_used_document(const gchar *filename) g_slice_free (GtkRecentData, recent_data); } +/** + * Call this before doing things that may take a long time and otherwise not show any other feedback + * such as loading and saving files + */ +void vik_window_set_busy_cursor ( VikWindow *vw ) +{ + gdk_window_set_cursor ( gtk_widget_get_window(GTK_WIDGET(vw)), vw->busy_cursor ); + // Viewport has a separate cursor + gdk_window_set_cursor ( gtk_widget_get_window(GTK_WIDGET(vw->viking_vvp)), vw->busy_cursor ); + // Ensure cursor updated before doing stuff + while( gtk_events_pending() ) + gtk_main_iteration(); +} + +void vik_window_clear_busy_cursor ( VikWindow *vw ) +{ + gdk_window_set_cursor ( gtk_widget_get_window(GTK_WIDGET(vw)), NULL ); + // Restore viewport cursor + gdk_window_set_cursor ( gtk_widget_get_window(GTK_WIDGET(vw->viking_vvp)), vw->viewport_cursor ); +} + void vik_window_open_file ( VikWindow *vw, const gchar *filename, gboolean change_filename ) { - switch ( a_file_load ( vik_layers_panel_get_top_layer(vw->viking_vlp), vw->viking_vvp, filename ) ) + vik_window_set_busy_cursor ( vw ); + + // Enable the *new* filename to be accessible by the Layers codez + gchar *original_filename = g_strdup ( vw->filename ); + g_free ( vw->filename ); + vw->filename = g_strdup ( filename ); + gboolean success = FALSE; + gboolean restore_original_filename = FALSE; + + vw->loaded_type = a_file_load ( vik_layers_panel_get_top_layer(vw->viking_vlp), vw->viking_vvp, filename ); + switch ( vw->loaded_type ) { - case 0: + case LOAD_TYPE_READ_FAILURE: a_dialog_error_msg ( GTK_WINDOW(vw), _("The file you requested could not be opened.") ); break; - case 1: + case LOAD_TYPE_GPSBABEL_FAILURE: + a_dialog_error_msg ( GTK_WINDOW(vw), _("GPSBabel is required to load files of this type or GPSBabel encountered problems.") ); + break; + case LOAD_TYPE_GPX_FAILURE: + a_dialog_error_msg_extra ( GTK_WINDOW(vw), _("Unable to load malformed GPX file %s"), filename ); + break; + case LOAD_TYPE_UNSUPPORTED_FAILURE: + a_dialog_error_msg_extra ( GTK_WINDOW(vw), _("Unsupported file type for %s"), filename ); + break; + case LOAD_TYPE_VIK_FAILURE_NON_FATAL: + { + // Since we can process .vik files with issues just show a warning in the status bar + // Not that a user can do much about it... or tells them what this issue is yet... + gchar *msg = g_strdup_printf (_("WARNING: issues encountered loading %s"), a_file_basename (filename) ); + vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_INFO, msg ); + g_free ( msg ); + } + // No break, carry on to show any data + case LOAD_TYPE_VIK_SUCCESS: { + restore_original_filename = TRUE; // NB Will actually get inverted by the 'success' component below GtkWidget *mode_button; /* Update UI */ if ( change_filename ) @@ -1539,20 +2823,38 @@ void vik_window_open_file ( VikWindow *vw, const gchar *filename, gboolean chang vw->only_updating_coord_mode_ui = FALSE; vik_layers_panel_change_coord_mode ( vw->viking_vlp, vik_viewport_get_coord_mode ( vw->viking_vvp ) ); - - mode_button = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/ShowScale" ); + + mode_button = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ShowScale" ); g_assert ( mode_button ); gtk_check_menu_item_set_active ( GTK_CHECK_MENU_ITEM(mode_button),vik_viewport_get_draw_scale(vw->viking_vvp) ); - mode_button = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/ShowCenterMark" ); + mode_button = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ShowCenterMark" ); g_assert ( mode_button ); gtk_check_menu_item_set_active ( GTK_CHECK_MENU_ITEM(mode_button),vik_viewport_get_draw_centermark(vw->viking_vvp) ); + + mode_button = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ShowHighlight" ); + g_assert ( mode_button ); + gtk_check_menu_item_set_active ( GTK_CHECK_MENU_ITEM(mode_button),vik_viewport_get_draw_highlight (vw->viking_vvp) ); } + // NB No break, carry on to redraw + //case LOAD_TYPE_OTHER_SUCCESS: default: - update_recently_used_document(filename); + success = TRUE; + // When LOAD_TYPE_OTHER_SUCCESS *only*, this will maintain the existing Viking project + restore_original_filename = ! restore_original_filename; + update_recently_used_document (vw, filename); draw_update ( vw ); + break; } + + if ( ! success || restore_original_filename ) + // Load didn't work or want to keep as the existing Viking project, keep using the original name + window_set_filename ( vw, original_filename ); + g_free ( original_filename ); + + vik_window_clear_busy_cursor ( vw ); } + static void load_file ( GtkAction *a, VikWindow *vw ) { GSList *files = NULL; @@ -1572,11 +2874,58 @@ static void load_file ( GtkAction *a, VikWindow *vw ) if ( ! vw->open_dia ) { vw->open_dia = gtk_file_chooser_dialog_new (_("Please select a GPS data file to open. "), - GTK_WINDOW(vw), - GTK_FILE_CHOOSER_ACTION_OPEN, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, - NULL); + GTK_WINDOW(vw), + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + gchar *cwd = g_get_current_dir(); + if ( cwd ) { + gtk_file_chooser_set_current_folder ( GTK_FILE_CHOOSER(vw->open_dia), cwd ); + g_free ( cwd ); + } + + GtkFileFilter *filter; + // NB file filters are listed this way for alphabetical ordering +#ifdef VIK_CONFIG_GEOCACHES + filter = gtk_file_filter_new (); + gtk_file_filter_set_name( filter, _("Geocaching") ); + gtk_file_filter_add_pattern ( filter, "*.loc" ); // No MIME type available + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(vw->open_dia), filter); +#endif + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name( filter, _("Google Earth") ); + gtk_file_filter_add_mime_type ( filter, "application/vnd.google-earth.kml+xml"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(vw->open_dia), filter); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name( filter, _("GPX") ); + gtk_file_filter_add_pattern ( filter, "*.gpx" ); // No MIME type available + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(vw->open_dia), filter); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name ( filter, _("JPG") ); + gtk_file_filter_add_mime_type ( filter, "image/jpeg"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(vw->open_dia), filter); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name( filter, _("Viking") ); + gtk_file_filter_add_pattern ( filter, "*.vik" ); + gtk_file_filter_add_pattern ( filter, "*.viking" ); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(vw->open_dia), filter); + + // NB could have filters for gpspoint (*.gps,*.gpsoint?) + gpsmapper (*.gsm,*.gpsmapper?) + // However assume this are barely used and thus not worthy of inclusion + // as they'll just make the options too many and have no clear file pattern + // one can always use the all option + filter = gtk_file_filter_new (); + gtk_file_filter_set_name( filter, _("All") ); + gtk_file_filter_add_pattern ( filter, "*" ); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(vw->open_dia), filter); + // Default to any file - same as before open filters were added + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(vw->open_dia), filter); + gtk_file_chooser_set_select_multiple ( GTK_FILE_CHOOSER(vw->open_dia), TRUE ); gtk_window_set_transient_for ( GTK_WINDOW(vw->open_dia), GTK_WINDOW(vw) ); gtk_window_set_destroy_with_parent ( GTK_WINDOW(vw->open_dia), TRUE ); @@ -1593,11 +2942,28 @@ static void load_file ( GtkAction *a, VikWindow *vw ) else { files = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER(vw->open_dia) ); gboolean change_fn = newwindow && (g_slist_length(files)==1); /* only change fn if one file */ - + gboolean first_vik_file = TRUE; cur_file = files; while ( cur_file ) { + gchar *file_name = cur_file->data; - vik_window_open_file ( vw, file_name, change_fn ); + if ( newwindow && check_file_magic_vik ( file_name ) ) { + // Load first of many .vik files in current window + if ( first_vik_file ) { + vik_window_open_file ( vw, file_name, TRUE ); + first_vik_file = FALSE; + } + else { + // Load each subsequent .vik file in a separate window + VikWindow *newvw = vik_window_new_window (); + if (newvw) + vik_window_open_file ( newvw, file_name, TRUE ); + } + } + else + // Other file types + vik_window_open_file ( vw, file_name, change_fn ); + g_free (file_name); cur_file = g_slist_next (cur_file); } @@ -1605,95 +2971,329 @@ static void load_file ( GtkAction *a, VikWindow *vw ) } } else - gtk_widget_hide ( vw->open_dia ); + gtk_widget_hide ( vw->open_dia ); +} + +static gboolean save_file_as ( GtkAction *a, VikWindow *vw ) +{ + gboolean rv = FALSE; + const gchar *fn; + if ( ! vw->save_dia ) + { + vw->save_dia = gtk_file_chooser_dialog_new (_("Save as Viking File."), + GTK_WINDOW(vw), + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gchar *cwd = g_get_current_dir(); + if ( cwd ) { + gtk_file_chooser_set_current_folder ( GTK_FILE_CHOOSER(vw->save_dia), cwd ); + g_free ( cwd ); + } + + GtkFileFilter *filter; + filter = gtk_file_filter_new (); + gtk_file_filter_set_name( filter, _("All") ); + gtk_file_filter_add_pattern ( filter, "*" ); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(vw->save_dia), filter); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name( filter, _("Viking") ); + gtk_file_filter_add_pattern ( filter, "*.vik" ); + gtk_file_filter_add_pattern ( filter, "*.viking" ); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(vw->save_dia), filter); + // Default to a Viking file + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(vw->save_dia), filter); + + gtk_window_set_transient_for ( GTK_WINDOW(vw->save_dia), GTK_WINDOW(vw) ); + gtk_window_set_destroy_with_parent ( GTK_WINDOW(vw->save_dia), TRUE ); + } + // Auto append / replace extension with '.vik' to the suggested file name as it's going to be a Viking File + gchar* auto_save_name = g_strdup ( window_get_filename ( vw ) ); + if ( ! a_file_check_ext ( auto_save_name, ".vik" ) ) + auto_save_name = g_strconcat ( auto_save_name, ".vik", NULL ); + + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER(vw->save_dia), auto_save_name); + + while ( gtk_dialog_run ( GTK_DIALOG(vw->save_dia) ) == GTK_RESPONSE_ACCEPT ) + { + fn = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(vw->save_dia) ); + 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 ) ) ) + { + window_set_filename ( vw, fn ); + rv = window_save ( vw ); + vw->modified = FALSE; + break; + } + } + g_free ( auto_save_name ); + gtk_widget_hide ( vw->save_dia ); + return rv; +} + +static gboolean window_save ( VikWindow *vw ) +{ + vik_window_set_busy_cursor ( vw ); + gboolean success = TRUE; + + if ( a_file_save ( vik_layers_panel_get_top_layer ( vw->viking_vlp ), vw->viking_vvp, vw->filename ) ) + { + update_recently_used_document ( vw, vw->filename ); + } + else + { + a_dialog_error_msg ( GTK_WINDOW(vw), _("The filename you requested could not be opened for writing.") ); + success = FALSE; + } + vik_window_clear_busy_cursor ( vw ); + return success; +} + +static gboolean save_file ( GtkAction *a, VikWindow *vw ) +{ + if ( ! vw->filename ) + return save_file_as ( NULL, vw ); + else + { + vw->modified = FALSE; + return window_save ( vw ); + } +} + +/** + * export_to: + * + * Export all TRW Layers in the list to individual files in the specified directory + * + * Returns: %TRUE on success + */ +static gboolean export_to ( VikWindow *vw, GList *gl, VikFileType_t vft, const gchar *dir, const gchar *extension ) +{ + gboolean success = TRUE; + + gint export_count = 0; + + vik_window_set_busy_cursor ( vw ); + + while ( gl ) { + + gchar *fn = g_strconcat ( dir, G_DIR_SEPARATOR_S, VIK_LAYER(gl->data)->name, extension, NULL ); + + // Some protection in attempting to write too many same named files + // As this will get horribly slow... + gboolean safe = FALSE; + gint ii = 2; + while ( ii < 5000 ) { + if ( g_file_test ( fn, G_FILE_TEST_EXISTS ) ) { + // Try rename + g_free ( fn ); + fn = g_strdup_printf ( "%s%s%s#%03d%s", dir, G_DIR_SEPARATOR_S, VIK_LAYER(gl->data)->name, ii, extension ); + } + else { + safe = TRUE; + break; + } + ii++; + } + if ( ii == 5000 ) + success = FALSE; + + // NB: We allow exporting empty layers + if ( safe ) { + gboolean this_success = a_file_export ( VIK_TRW_LAYER(gl->data), fn, vft, NULL, TRUE ); + + // Show some progress + if ( this_success ) { + export_count++; + gchar *message = g_strdup_printf ( _("Exporting to file: %s"), fn ); + vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_INFO, message ); + while ( gtk_events_pending() ) + gtk_main_iteration (); + g_free ( message ); + } + + success = success && this_success; + } + + g_free ( fn ); + gl = g_list_next ( gl ); + } + + vik_window_clear_busy_cursor ( vw ); + + // Confirm what happened. + gchar *message = g_strdup_printf ( _("Exported files: %d"), export_count ); + vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_INFO, message ); + g_free ( message ); + + return success; +} + +static void export_to_common ( VikWindow *vw, VikFileType_t vft, const gchar *extension ) +{ + GList *gl = vik_layers_panel_get_all_layers_of_type ( vw->viking_vlp, VIK_LAYER_TRW, TRUE ); + + if ( !gl ) { + a_dialog_info_msg ( GTK_WINDOW(vw), _("Nothing to Export!") ); + return; + } + + GtkWidget *dialog = gtk_file_chooser_dialog_new ( _("Export to directory"), + GTK_WINDOW(vw), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + GTK_STOCK_CANCEL, + GTK_RESPONSE_REJECT, + GTK_STOCK_OK, + GTK_RESPONSE_ACCEPT, + NULL ); + gtk_window_set_transient_for ( GTK_WINDOW(dialog), GTK_WINDOW(vw) ); + gtk_window_set_destroy_with_parent ( GTK_WINDOW(dialog), TRUE ); + gtk_window_set_modal ( GTK_WINDOW(dialog), TRUE ); + + gtk_widget_show_all ( dialog ); + + if ( gtk_dialog_run ( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) { + gchar *dir = gtk_file_chooser_get_filename ( GTK_FILE_CHOOSER(dialog) ); + gtk_widget_destroy ( dialog ); + if ( dir ) { + if ( !export_to ( vw, gl, vft, dir, extension ) ) + a_dialog_error_msg ( GTK_WINDOW(vw),_("Could not convert all files") ); + g_free ( dir ); + } + } + else + gtk_widget_destroy ( dialog ); + + g_list_free ( gl ); +} + +static void export_to_gpx ( GtkAction *a, VikWindow *vw ) +{ + export_to_common ( vw, FILE_TYPE_GPX, ".gpx" ); +} + +static void export_to_kml ( GtkAction *a, VikWindow *vw ) +{ + export_to_common ( vw, FILE_TYPE_KML, ".kml" ); +} + +#if !GLIB_CHECK_VERSION(2,26,0) +typedef struct stat GStatBuf; +#endif + +static void file_properties_cb ( GtkAction *a, VikWindow *vw ) +{ + gchar *message = NULL; + if ( vw->filename ) { + if ( g_file_test ( vw->filename, G_FILE_TEST_EXISTS ) ) { + // Get some timestamp information of the file + GStatBuf stat_buf; + if ( g_stat ( vw->filename, &stat_buf ) == 0 ) { + gchar time_buf[64]; + strftime ( time_buf, sizeof(time_buf), "%c", gmtime((const time_t *)&stat_buf.st_mtime) ); + gchar *size = NULL; + gint byte_size = stat_buf.st_size; +#if GLIB_CHECK_VERSION(2,30,0) + size = g_format_size_full ( byte_size, G_FORMAT_SIZE_DEFAULT ); +#else + size = g_format_size_for_display ( byte_size ); +#endif + message = g_strdup_printf ( "%s\n\n%s\n\n%s", vw->filename, time_buf, size ); + g_free (size); + } + } + else + message = g_strdup ( _("File not accessible") ); + } + else + message = g_strdup ( _("No Viking File") ); + + // Show the info + a_dialog_info_msg ( GTK_WINDOW(vw), message ); + g_free ( message ); } -static gboolean save_file_as ( GtkAction *a, VikWindow *vw ) +static void my_acquire ( VikWindow *vw, VikDataSourceInterface *datasource ) { - gboolean rv = FALSE; - const gchar *fn; - if ( ! vw->save_dia ) - { - vw->save_dia = gtk_file_chooser_dialog_new (_("Save as Viking File."), - GTK_WINDOW(vw), - GTK_FILE_CHOOSER_ACTION_SAVE, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, - NULL); - gtk_window_set_transient_for ( GTK_WINDOW(vw->save_dia), GTK_WINDOW(vw) ); - gtk_window_set_destroy_with_parent ( GTK_WINDOW(vw->save_dia), TRUE ); - } + vik_datasource_mode_t mode = datasource->mode; + if ( mode == VIK_DATASOURCE_AUTO_LAYER_MANAGEMENT ) + mode = VIK_DATASOURCE_CREATENEWLAYER; + a_acquire ( vw, vw->viking_vlp, vw->viking_vvp, mode, datasource, NULL, NULL ); +} - while ( gtk_dialog_run ( GTK_DIALOG(vw->save_dia) ) == GTK_RESPONSE_ACCEPT ) - { - fn = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(vw->save_dia) ); - if ( g_file_test ( fn, G_FILE_TEST_EXISTS ) == FALSE || a_dialog_overwrite ( GTK_WINDOW(vw->save_dia), _("The file \"%s\" exists, do you wish to overwrite it?"), a_file_basename ( fn ) ) ) - { - window_set_filename ( vw, fn ); - rv = window_save ( vw ); - vw->modified = FALSE; - break; - } - } - gtk_widget_hide ( vw->save_dia ); - return rv; +static void acquire_from_gps ( GtkAction *a, VikWindow *vw ) +{ + my_acquire ( vw, &vik_datasource_gps_interface ); } -static gboolean window_save ( VikWindow *vw ) +static void acquire_from_file ( GtkAction *a, VikWindow *vw ) { - if ( a_file_save ( vik_layers_panel_get_top_layer ( vw->viking_vlp ), vw->viking_vvp, vw->filename ) ) - { - update_recently_used_document ( vw->filename ); - return TRUE; - } - else - { - a_dialog_error_msg ( GTK_WINDOW(vw), _("The filename you requested could not be opened for writing.") ); - return FALSE; - } + my_acquire ( vw, &vik_datasource_file_interface ); } -static gboolean save_file ( GtkAction *a, VikWindow *vw ) +static void acquire_from_geojson ( GtkAction *a, VikWindow *vw ) { - if ( ! vw->filename ) - return save_file_as ( NULL, vw ); - else - { - vw->modified = FALSE; - return window_save ( vw ); - } + my_acquire ( vw, &vik_datasource_geojson_interface ); } -static void acquire_from_gps ( GtkAction *a, VikWindow *vw ) +static void acquire_from_routing ( GtkAction *a, VikWindow *vw ) +{ + my_acquire ( vw, &vik_datasource_routing_interface ); +} + +#ifdef VIK_CONFIG_OPENSTREETMAP +static void acquire_from_osm ( GtkAction *a, VikWindow *vw ) { - a_acquire(vw, vw->viking_vlp, vw->viking_vvp, &vik_datasource_gps_interface ); + my_acquire ( vw, &vik_datasource_osm_interface ); } -static void acquire_from_google ( GtkAction *a, VikWindow *vw ) +static void acquire_from_my_osm ( GtkAction *a, VikWindow *vw ) { - a_acquire(vw, vw->viking_vlp, vw->viking_vvp, &vik_datasource_google_interface ); + my_acquire ( vw, &vik_datasource_osm_my_traces_interface ); } +#endif #ifdef VIK_CONFIG_GEOCACHES static void acquire_from_gc ( GtkAction *a, VikWindow *vw ) { - a_acquire(vw, vw->viking_vlp, vw->viking_vvp, &vik_datasource_gc_interface ); + my_acquire ( vw, &vik_datasource_gc_interface ); +} +#endif + +#ifdef VIK_CONFIG_GEOTAG +static void acquire_from_geotag ( GtkAction *a, VikWindow *vw ) +{ + my_acquire ( vw, &vik_datasource_geotag_interface ); +} +#endif + +#ifdef VIK_CONFIG_GEONAMES +static void acquire_from_wikipedia ( GtkAction *a, VikWindow *vw ) +{ + my_acquire ( vw, &vik_datasource_wikipedia_interface ); } #endif +static void acquire_from_url ( GtkAction *a, VikWindow *vw ) +{ + my_acquire ( vw, &vik_datasource_url_interface ); +} + static void goto_default_location( GtkAction *a, VikWindow *vw) { struct LatLon ll; ll.lat = a_vik_get_default_lat(); ll.lon = a_vik_get_default_long(); - vik_viewport_set_center_latlon(vw->viking_vvp, &ll); + vik_viewport_set_center_latlon(vw->viking_vvp, &ll, TRUE); vik_layers_panel_emit_update(vw->viking_vlp); } static void goto_address( GtkAction *a, VikWindow *vw) { - a_vik_goto(vw, vw->viking_vlp, vw->viking_vvp); + a_vik_goto ( vw, vw->viking_vvp ); + vik_layers_panel_emit_update ( vw->viking_vlp ); } static void mapcache_flush_cb ( GtkAction *a, VikWindow *vw ) @@ -1701,17 +3301,90 @@ static void mapcache_flush_cb ( GtkAction *a, VikWindow *vw ) a_mapcache_flush(); } +static void menu_copy_centre_cb ( GtkAction *a, VikWindow *vw ) +{ + const VikCoord* coord; + struct UTM utm; + gchar *lat = NULL, *lon = NULL; + + coord = vik_viewport_get_center ( vw->viking_vvp ); + vik_coord_to_utm ( coord, &utm ); + + gboolean full_format = FALSE; + a_settings_get_boolean ( VIK_SETTINGS_WIN_COPY_CENTRE_FULL_FORMAT, &full_format ); + + if ( full_format ) + // Bells & Whistles - may include degrees, minutes and second symbols + get_location_strings ( vw, utm, &lat, &lon ); + else { + // Simple x.xx y.yy format + struct LatLon ll; + a_coords_utm_to_latlon ( &utm, &ll ); + lat = g_strdup_printf ( "%.6f", ll.lat ); + lon = g_strdup_printf ( "%.6f", ll.lon ); + } + + gchar *msg = g_strdup_printf ( "%s %s", lat, lon ); + g_free (lat); + g_free (lon); + + a_clipboard_copy ( VIK_CLIPBOARD_DATA_TEXT, 0, 0, 0, msg, NULL ); + + g_free ( msg ); +} + +static void layer_defaults_cb ( GtkAction *a, VikWindow *vw ) +{ + gchar **texts = g_strsplit ( gtk_action_get_name(a), "Layer", 0 ); + + if ( !texts[1] ) + return; // Internally broken :( + + if ( ! a_layer_defaults_show_window ( GTK_WINDOW(vw), texts[1] ) ) + a_dialog_info_msg ( GTK_WINDOW(vw), _("This layer has no configurable properties.") ); + // NB no update needed + + g_strfreev ( texts ); +} + +static void preferences_change_update ( VikWindow *vw, gpointer data ) +{ + // Want to update all TrackWaypoint layers + GList *layers = vik_layers_panel_get_all_layers_of_type ( vw->viking_vlp, VIK_LAYER_TRW, TRUE ); + + if ( !layers ) + return; + + while ( layers ) { + // Reset the individual waypoints themselves due to the preferences change + VikTrwLayer *vtl = VIK_TRW_LAYER(layers->data); + vik_trw_layer_reset_waypoints ( vtl ); + layers = g_list_next ( layers ); + } + + g_list_free ( layers ); + + draw_update ( vw ); +} + static void preferences_cb ( GtkAction *a, VikWindow *vw ) { gboolean wp_icon_size = a_vik_get_use_large_waypoint_icons(); a_preferences_show_window ( GTK_WINDOW(vw) ); - // Delete icon indexing 'cache' and so automatically regenerates with the new setting when changed - if (wp_icon_size != a_vik_get_use_large_waypoint_icons()) + // Has the waypoint size setting changed? + if (wp_icon_size != a_vik_get_use_large_waypoint_icons()) { + // Delete icon indexing 'cache' and so automatically regenerates with the new setting when changed clear_garmin_icon_syms (); - draw_update ( vw ); + // Update all windows + g_slist_foreach ( window_list, (GFunc) preferences_change_update, NULL ); + } + + // Ensure TZ Lookup initialized + if ( a_vik_get_time_ref_frame() == VIK_TIME_REF_WORLD ) + vu_setup_lat_lon_tz_lookup(); } static void default_location_cb ( GtkAction *a, VikWindow *vw ) @@ -1719,22 +3392,34 @@ static void default_location_cb ( GtkAction *a, VikWindow *vw ) /* Simplistic repeat of preference setting Only the name & type are important for setting the preference via this 'external' way */ VikLayerParam pref_lat[] = { - { VIKING_PREFERENCES_NAMESPACE "default_latitude", + { VIK_LAYER_NUM_TYPES, + VIKING_PREFERENCES_NAMESPACE "default_latitude", VIK_LAYER_PARAM_DOUBLE, VIK_LOCATION_LAT, NULL, VIK_LAYER_WIDGET_SPINBUTTON, NULL, - NULL }, + NULL, + NULL, + NULL, + NULL, + NULL, + }, }; VikLayerParam pref_lon[] = { - { VIKING_PREFERENCES_NAMESPACE "default_longitude", + { VIK_LAYER_NUM_TYPES, + VIKING_PREFERENCES_NAMESPACE "default_longitude", VIK_LAYER_PARAM_DOUBLE, VIK_LOCATION_LONG, NULL, VIK_LAYER_WIDGET_SPINBUTTON, NULL, - NULL }, + NULL, + NULL, + NULL, + NULL, + NULL, + }, }; /* Get current center */ @@ -1792,6 +3477,23 @@ static void save_image_file ( VikWindow *vw, const gchar *fn, guint w, guint h, gdouble old_xmpp, old_ympp; GError *error = NULL; + GtkWidget *msgbox = gtk_message_dialog_new ( GTK_WINDOW(vw), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_NONE, + _("Generating image file...") ); + + g_signal_connect_swapped (msgbox, "response", G_CALLBACK (gtk_widget_destroy), msgbox); + // Ensure dialog shown + gtk_widget_show_all ( msgbox ); + // Try harder... + vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_INFO, _("Generating image file...") ); + while ( gtk_events_pending() ) + gtk_main_iteration (); + // Despite many efforts & variations, GTK on my Linux system doesn't show the actual msgbox contents :( + // At least the empty box can give a clue something's going on + the statusbar msg... + // Windows version under Wine OK! + /* backup old zoom & set new */ old_xmpp = vik_viewport_get_xmpp ( vw->viking_vvp ); old_ympp = vik_viewport_get_ympp ( vw->viking_vvp ); @@ -1805,14 +3507,30 @@ static void save_image_file ( VikWindow *vw, const gchar *fn, guint w, guint h, /* save buffer as file. */ 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); + if ( !pixbuf_to_save ) { + g_warning("Failed to generate internal pixmap size: %d x %d", w, h); + gtk_message_dialog_set_markup ( GTK_MESSAGE_DIALOG(msgbox), _("Failed to generate internal image.\n\nTry creating a smaller image.") ); + goto cleanup; + } + gdk_pixbuf_save ( pixbuf_to_save, fn, save_as_png ? "png" : "jpeg", &error, NULL ); if (error) { g_warning("Unable to write to file %s: %s", fn, error->message ); + gtk_message_dialog_set_markup ( GTK_MESSAGE_DIALOG(msgbox), _("Failed to generate image file.") ); g_error_free (error); } + else { + // Success + gtk_message_dialog_set_markup ( GTK_MESSAGE_DIALOG(msgbox), _("Image file generated.") ); + } g_object_unref ( G_OBJECT(pixbuf_to_save) ); + cleanup: + vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_INFO, "" ); + gtk_dialog_add_button ( GTK_DIALOG(msgbox), GTK_STOCK_OK, GTK_RESPONSE_OK ); + gtk_dialog_run ( GTK_DIALOG(msgbox) ); // Don't care about the result + /* pretend like nothing happened ;) */ vik_viewport_set_xmpp ( vw->viking_vvp, old_xmpp ); vik_viewport_set_ympp ( vw->viking_vvp, old_ympp ); @@ -1863,7 +3581,7 @@ static void save_image_dir ( VikWindow *vw, const gchar *fn, guint w, guint h, g utm.northing -= ((gdouble)y - (((gdouble)tiles_h)+1)/2) * (h*zoom); /* move to correct place. */ - vik_viewport_set_center_utm ( vw->viking_vvp, &utm ); + vik_viewport_set_center_utm ( vw->viking_vvp, &utm, FALSE ); draw_redraw ( vw ); @@ -1872,7 +3590,9 @@ static void save_image_dir ( VikWindow *vw, const gchar *fn, guint w, guint h, g gdk_pixbuf_save ( pixbuf_to_save, name_of_file, save_as_png ? "png" : "jpeg", &error, NULL ); if (error) { - g_warning("Unable to write to file %s: %s", name_of_file, error->message ); + gchar *msg = g_strdup_printf (_("Unable to write to file %s: %s"), name_of_file, error->message ); + vik_statusbar_set_message ( vw->viking_vs, VIK_STATUSBAR_INFO, msg ); + g_free ( msg ); g_error_free (error); } @@ -1880,7 +3600,7 @@ static void save_image_dir ( VikWindow *vw, const gchar *fn, guint w, guint h, g } } - vik_viewport_set_center_utm ( vw->viking_vvp, &utm_orig ); + vik_viewport_set_center_utm ( vw->viking_vvp, &utm_orig, FALSE ); vik_viewport_set_xmpp ( vw->viking_vvp, old_xmpp ); vik_viewport_set_ympp ( vw->viking_vvp, old_ympp ); vik_viewport_configure ( vw->viking_vvp ); @@ -1893,7 +3613,10 @@ static void draw_to_image_file_current_window_cb(GtkWidget* widget,GdkEventButto { VikWindow *vw = VIK_WINDOW(pass_along[0]); GtkSpinButton *width_spin = GTK_SPIN_BUTTON(pass_along[1]), *height_spin = GTK_SPIN_BUTTON(pass_along[2]); - GtkSpinButton *zoom_spin = GTK_SPIN_BUTTON(pass_along[3]); + + gint active = gtk_combo_box_get_active ( GTK_COMBO_BOX(pass_along[3]) ); + gdouble zoom = pow (2, active-2 ); + gdouble width_min, width_max, height_min, height_max; gint width, height; @@ -1901,8 +3624,8 @@ static void draw_to_image_file_current_window_cb(GtkWidget* widget,GdkEventButto gtk_spin_button_get_range ( height_spin, &height_min, &height_max ); /* TODO: support for xzoom and yzoom values */ - width = vik_viewport_get_width ( vw->viking_vvp ) * vik_viewport_get_xmpp ( vw->viking_vvp ) / gtk_spin_button_get_value ( zoom_spin ); - height = vik_viewport_get_height ( vw->viking_vvp ) * vik_viewport_get_xmpp ( vw->viking_vvp ) / gtk_spin_button_get_value ( zoom_spin ); + width = vik_viewport_get_width ( vw->viking_vvp ) * vik_viewport_get_xmpp ( vw->viking_vvp ) / zoom; + height = vik_viewport_get_height ( vw->viking_vvp ) * vik_viewport_get_xmpp ( vw->viking_vvp ) / zoom; if ( width > width_max || width < width_min || height > height_max || height < height_min ) a_dialog_info_msg ( GTK_WINDOW(vw), _("Viewable region outside allowable pixel size bounds for image. Clipping width/height values.") ); @@ -1914,11 +3637,14 @@ static void draw_to_image_file_current_window_cb(GtkWidget* widget,GdkEventButto static void draw_to_image_file_total_area_cb (GtkSpinButton *spinbutton, gpointer *pass_along) { GtkSpinButton *width_spin = GTK_SPIN_BUTTON(pass_along[1]), *height_spin = GTK_SPIN_BUTTON(pass_along[2]); - GtkSpinButton *zoom_spin = GTK_SPIN_BUTTON(pass_along[3]); + + gint active = gtk_combo_box_get_active ( GTK_COMBO_BOX(pass_along[3]) ); + gdouble zoom = pow (2, active-2 ); + gchar *label_text; gdouble w, h; - w = gtk_spin_button_get_value(width_spin) * gtk_spin_button_get_value(zoom_spin); - h = gtk_spin_button_get_value(height_spin) * gtk_spin_button_get_value(zoom_spin); + w = gtk_spin_button_get_value(width_spin) * zoom; + h = gtk_spin_button_get_value(height_spin) * zoom; if (pass_along[4]) /* save many images; find TOTAL area covered */ { w *= gtk_spin_button_get_value(GTK_SPIN_BUTTON(pass_along[4])); @@ -1932,6 +3658,9 @@ static void draw_to_image_file_total_area_cb (GtkSpinButton *spinbutton, gpointe case VIK_UNITS_DISTANCE_MILES: label_text = g_strdup_printf ( _("Total area: %ldm x %ldm (%.3f sq. miles)"), (glong)w, (glong)h, (w*h/2589988.11)); break; + case VIK_UNITS_DISTANCE_NAUTICAL_MILES: + label_text = g_strdup_printf ( _("Total area: %ldm x %ldm (%.3f sq. NM)"), (glong)w, (glong)h, (w*h/(1852.0*1852.0))); + break; default: label_text = g_strdup_printf ("Just to keep the compiler happy"); g_critical("Houston, we've had a problem. distance=%d", dist_units); @@ -1941,7 +3670,93 @@ static void draw_to_image_file_total_area_cb (GtkSpinButton *spinbutton, gpointe g_free ( label_text ); } -static void draw_to_image_file ( VikWindow *vw, const gchar *fn, gboolean one_image_only ) +/* + * Get an allocated filename (or directory as specified) + */ +static gchar* draw_image_filename ( VikWindow *vw, gboolean one_image_only ) +{ + gchar *fn = NULL; + if ( one_image_only ) + { + // Single file + if (!vw->save_img_dia) { + vw->save_img_dia = gtk_file_chooser_dialog_new (_("Save Image"), + GTK_WINDOW(vw), + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + + gchar *cwd = g_get_current_dir(); + if ( cwd ) { + gtk_file_chooser_set_current_folder ( GTK_FILE_CHOOSER(vw->save_img_dia), cwd ); + g_free ( cwd ); + } + + GtkFileChooser *chooser = GTK_FILE_CHOOSER ( vw->save_img_dia ); + /* Add filters */ + GtkFileFilter *filter; + filter = gtk_file_filter_new (); + gtk_file_filter_set_name ( filter, _("All") ); + gtk_file_filter_add_pattern ( filter, "*" ); + gtk_file_chooser_add_filter ( chooser, filter ); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name ( filter, _("JPG") ); + gtk_file_filter_add_mime_type ( filter, "image/jpeg"); + gtk_file_chooser_add_filter ( chooser, filter ); + + if ( !vw->draw_image_save_as_png ) + gtk_file_chooser_set_filter ( chooser, filter ); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name ( filter, _("PNG") ); + gtk_file_filter_add_mime_type ( filter, "image/png"); + gtk_file_chooser_add_filter ( chooser, filter ); + + if ( vw->draw_image_save_as_png ) + gtk_file_chooser_set_filter ( chooser, filter ); + + gtk_window_set_transient_for ( GTK_WINDOW(vw->save_img_dia), GTK_WINDOW(vw) ); + gtk_window_set_destroy_with_parent ( GTK_WINDOW(vw->save_img_dia), TRUE ); + } + + if ( gtk_dialog_run ( GTK_DIALOG(vw->save_img_dia) ) == GTK_RESPONSE_ACCEPT ) { + fn = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(vw->save_img_dia) ); + if ( g_file_test ( fn, G_FILE_TEST_EXISTS ) ) + 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 ) ) ) + fn = NULL; + } + gtk_widget_hide ( vw->save_img_dia ); + } + else { + // A directory + // For some reason this method is only written to work in UTM... + if ( vik_viewport_get_coord_mode(vw->viking_vvp) != VIK_COORD_UTM ) { + a_dialog_error_msg ( GTK_WINDOW(vw), _("You must be in UTM mode to use this feature") ); + return fn; + } + + if (!vw->save_img_dir_dia) { + vw->save_img_dir_dia = gtk_file_chooser_dialog_new (_("Choose a directory to hold images"), + GTK_WINDOW(vw), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + gtk_window_set_transient_for ( GTK_WINDOW(vw->save_img_dir_dia), GTK_WINDOW(vw) ); + gtk_window_set_destroy_with_parent ( GTK_WINDOW(vw->save_img_dir_dia), TRUE ); + } + + if ( gtk_dialog_run ( GTK_DIALOG(vw->save_img_dir_dia) ) == GTK_RESPONSE_ACCEPT ) { + fn = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(vw->save_img_dir_dia) ); + } + gtk_widget_hide ( vw->save_img_dir_dia ); + } + return fn; +} + +static void draw_to_image_file ( VikWindow *vw, gboolean one_image_only ) { /* todo: default for answers inside VikWindow or static (thruout instance) */ GtkWidget *dialog = gtk_dialog_new_with_buttons ( _("Save to Image File"), GTK_WINDOW(vw), @@ -1955,21 +3770,32 @@ static void draw_to_image_file ( VikWindow *vw, const gchar *fn, gboolean one_im GtkWidget *png_radio, *jpeg_radio; GtkWidget *current_window_button; gpointer current_window_pass_along[7]; - GtkWidget *zoom_label, *zoom_spin; + GtkWidget *zoom_label, *zoom_combo; GtkWidget *total_size_label; /* only used if (!one_image_only) */ GtkWidget *tiles_width_spin = NULL, *tiles_height_spin = NULL; - width_label = gtk_label_new ( _("Width (pixels):") ); - width_spin = gtk_spin_button_new ( GTK_ADJUSTMENT(gtk_adjustment_new ( vw->draw_image_width, 10, 5000, 10, 100, 0 )), 10, 0 ); + width_spin = gtk_spin_button_new ( GTK_ADJUSTMENT(gtk_adjustment_new ( vw->draw_image_width, 10, 50000, 10, 100, 0 )), 10, 0 ); height_label = gtk_label_new ( _("Height (pixels):") ); - height_spin = gtk_spin_button_new ( GTK_ADJUSTMENT(gtk_adjustment_new ( vw->draw_image_height, 10, 5000, 10, 100, 0 )), 10, 0 ); - + height_spin = gtk_spin_button_new ( GTK_ADJUSTMENT(gtk_adjustment_new ( vw->draw_image_height, 10, 50000, 10, 100, 0 )), 10, 0 ); +#ifdef WINDOWS + GtkWidget *win_warning_label = gtk_label_new ( _("WARNING: USING LARGE IMAGES OVER 10000x10000\nMAY CRASH THE PROGRAM!") ); +#endif zoom_label = gtk_label_new ( _("Zoom (meters per pixel):") ); /* TODO: separate xzoom and yzoom factors */ - zoom_spin = gtk_spin_button_new ( GTK_ADJUSTMENT(gtk_adjustment_new ( vik_viewport_get_xmpp(vw->viking_vvp), VIK_VIEWPORT_MIN_ZOOM, VIK_VIEWPORT_MAX_ZOOM/2.0, 1, 100, 0 )), 16, 0); + zoom_combo = create_zoom_combo_all_levels(); + + gdouble mpp = vik_viewport_get_xmpp(vw->viking_vvp); + gint active = 2 + round ( log (mpp) / log (2) ); + + // Can we not hard code size here? + if ( active > 17 ) + active = 17; + if ( active < 0 ) + active = 0; + gtk_combo_box_set_active ( GTK_COMBO_BOX(zoom_combo), active ); total_size_label = gtk_label_new ( NULL ); @@ -1977,7 +3803,7 @@ static void draw_to_image_file ( VikWindow *vw, const gchar *fn, gboolean one_im current_window_pass_along [0] = vw; current_window_pass_along [1] = width_spin; current_window_pass_along [2] = height_spin; - current_window_pass_along [3] = zoom_spin; + current_window_pass_along [3] = zoom_combo; current_window_pass_along [4] = NULL; /* used for one_image_only != 1 */ current_window_pass_along [5] = NULL; current_window_pass_along [6] = total_size_label; @@ -1986,68 +3812,81 @@ static void draw_to_image_file ( VikWindow *vw, const gchar *fn, gboolean one_im png_radio = gtk_radio_button_new_with_label ( NULL, _("Save as PNG") ); jpeg_radio = gtk_radio_button_new_with_label_from_widget ( GTK_RADIO_BUTTON(png_radio), _("Save as JPEG") ); + gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), png_radio, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), jpeg_radio, FALSE, FALSE, 0); + if ( ! vw->draw_image_save_as_png ) gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(jpeg_radio), TRUE ); - gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), width_label, FALSE, FALSE, 0); - gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), width_spin, FALSE, FALSE, 0); - gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), height_label, FALSE, FALSE, 0); - gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), height_spin, FALSE, FALSE, 0); - gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), current_window_button, FALSE, FALSE, 0); - gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), png_radio, FALSE, FALSE, 0); - gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), jpeg_radio, FALSE, FALSE, 0); - gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), zoom_label, FALSE, FALSE, 0); - gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), zoom_spin, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), width_label, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), width_spin, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), height_label, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), height_spin, FALSE, FALSE, 0); +#ifdef WINDOWS + gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), win_warning_label, FALSE, FALSE, 0); +#endif + gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), current_window_button, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), zoom_label, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), zoom_combo, FALSE, FALSE, 0); if ( ! one_image_only ) { GtkWidget *tiles_width_label, *tiles_height_label; - tiles_width_label = gtk_label_new ( _("East-west image tiles:") ); tiles_width_spin = gtk_spin_button_new ( GTK_ADJUSTMENT(gtk_adjustment_new ( 5, 1, 10, 1, 100, 0 )), 1, 0 ); tiles_height_label = gtk_label_new ( _("North-south image tiles:") ); tiles_height_spin = gtk_spin_button_new ( GTK_ADJUSTMENT(gtk_adjustment_new ( 5, 1, 10, 1, 100, 0 )), 1, 0 ); - gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), tiles_width_label, FALSE, FALSE, 0); - gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), tiles_width_spin, FALSE, FALSE, 0); - gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), tiles_height_label, FALSE, FALSE, 0); - gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), tiles_height_spin, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), tiles_width_label, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), tiles_width_spin, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), tiles_height_label, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), tiles_height_spin, FALSE, FALSE, 0); current_window_pass_along [4] = tiles_width_spin; current_window_pass_along [5] = tiles_height_spin; g_signal_connect ( G_OBJECT(tiles_width_spin), "value-changed", G_CALLBACK(draw_to_image_file_total_area_cb), current_window_pass_along ); g_signal_connect ( G_OBJECT(tiles_height_spin), "value-changed", G_CALLBACK(draw_to_image_file_total_area_cb), current_window_pass_along ); } - gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), total_size_label, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), total_size_label, FALSE, FALSE, 0); g_signal_connect ( G_OBJECT(width_spin), "value-changed", G_CALLBACK(draw_to_image_file_total_area_cb), current_window_pass_along ); g_signal_connect ( G_OBJECT(height_spin), "value-changed", G_CALLBACK(draw_to_image_file_total_area_cb), current_window_pass_along ); - g_signal_connect ( G_OBJECT(zoom_spin), "value-changed", G_CALLBACK(draw_to_image_file_total_area_cb), current_window_pass_along ); + g_signal_connect ( G_OBJECT(zoom_combo), "changed", G_CALLBACK(draw_to_image_file_total_area_cb), current_window_pass_along ); draw_to_image_file_total_area_cb ( NULL, current_window_pass_along ); /* set correct size info now */ - gtk_widget_show_all ( GTK_DIALOG(dialog)->vbox ); + gtk_dialog_set_default_response ( GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT ); + + gtk_widget_show_all ( gtk_dialog_get_content_area(GTK_DIALOG(dialog)) ); if ( gtk_dialog_run ( GTK_DIALOG(dialog) ) == GTK_RESPONSE_ACCEPT ) { gtk_widget_hide ( GTK_WIDGET(dialog) ); + + gchar *fn = draw_image_filename ( vw, one_image_only ); + if ( !fn ) + return; + + gint active_z = gtk_combo_box_get_active ( GTK_COMBO_BOX(zoom_combo) ); + gdouble zoom = pow (2, active_z-2 ); + if ( one_image_only ) save_image_file ( vw, fn, vw->draw_image_width = gtk_spin_button_get_value_as_int ( GTK_SPIN_BUTTON(width_spin) ), vw->draw_image_height = gtk_spin_button_get_value_as_int ( GTK_SPIN_BUTTON(height_spin) ), - gtk_spin_button_get_value ( GTK_SPIN_BUTTON(zoom_spin) ), /* do not save this value, default is current zoom */ + zoom, vw->draw_image_save_as_png = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(png_radio) ) ); else { - if ( vik_viewport_get_coord_mode(vw->viking_vvp) == VIK_COORD_UTM ) - save_image_dir ( vw, fn, + // NB is in UTM mode ATM + save_image_dir ( vw, fn, vw->draw_image_width = gtk_spin_button_get_value_as_int ( GTK_SPIN_BUTTON(width_spin) ), vw->draw_image_height = gtk_spin_button_get_value_as_int ( GTK_SPIN_BUTTON(height_spin) ), - gtk_spin_button_get_value ( GTK_SPIN_BUTTON(zoom_spin) ), /* do not save this value, default is current zoom */ + zoom, vw->draw_image_save_as_png = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(png_radio) ), gtk_spin_button_get_value ( GTK_SPIN_BUTTON(tiles_width_spin) ), gtk_spin_button_get_value ( GTK_SPIN_BUTTON(tiles_height_spin) ) ); - else - a_dialog_error_msg ( GTK_WINDOW(vw), _("You must be in UTM mode to use this feature") ); } + + g_free ( fn ); } gtk_widget_destroy ( GTK_WIDGET(dialog) ); } @@ -2055,65 +3894,18 @@ static void draw_to_image_file ( VikWindow *vw, const gchar *fn, gboolean one_im static void draw_to_image_file_cb ( GtkAction *a, VikWindow *vw ) { - const gchar *fn; - if (!vw->save_img_dia) { - vw->save_img_dia = gtk_file_chooser_dialog_new (_("Save Image"), - GTK_WINDOW(vw), - GTK_FILE_CHOOSER_ACTION_SAVE, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, - NULL); - gtk_window_set_transient_for ( GTK_WINDOW(vw->save_img_dia), GTK_WINDOW(vw) ); - gtk_window_set_destroy_with_parent ( GTK_WINDOW(vw->save_img_dia), TRUE ); - } - - while ( gtk_dialog_run ( GTK_DIALOG(vw->save_img_dia) ) == GTK_RESPONSE_ACCEPT ) - { - fn = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(vw->save_img_dia) ); - if ( g_file_test ( fn, G_FILE_TEST_EXISTS ) == FALSE || a_dialog_overwrite ( GTK_WINDOW(vw->save_img_dia), _("The file \"%s\" exists, do you wish to overwrite it?"), a_file_basename ( fn ) ) ) - { - draw_to_image_file ( vw, fn, TRUE ); - break; - } - } - gtk_widget_hide ( vw->save_img_dia ); + draw_to_image_file ( vw, TRUE ); } static void draw_to_image_dir_cb ( GtkAction *a, VikWindow *vw ) { - gchar *fn = NULL; - - if (!vw->save_img_dir_dia) { - vw->save_img_dir_dia = gtk_file_chooser_dialog_new (_("Choose a directory to hold images"), - GTK_WINDOW(vw), - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, - NULL); - gtk_window_set_transient_for ( GTK_WINDOW(vw->save_img_dir_dia), GTK_WINDOW(vw) ); - gtk_window_set_destroy_with_parent ( GTK_WINDOW(vw->save_img_dir_dia), TRUE ); - } - - while ( gtk_dialog_run ( GTK_DIALOG(vw->save_img_dir_dia) ) == GTK_RESPONSE_ACCEPT ) - { - fn = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(vw->save_img_dir_dia) ); - if ( fn ) - { - draw_to_image_file ( vw, fn, FALSE ); - g_free(fn); - fn = NULL; - break; - } - } - gtk_widget_hide ( vw->save_img_dir_dia ); + draw_to_image_file ( vw, FALSE ); } -#if GTK_CHECK_VERSION(2,10,0) static void print_cb ( GtkAction *a, VikWindow *vw ) { a_print(vw, vw->viking_vvp); } -#endif /* really a misnomer: changes coord mode (actual coordinates) AND/OR draw mode (viewport only) */ static void window_change_coord_mode_cb ( GtkAction *old_a, GtkAction *a, VikWindow *vw ) @@ -2122,6 +3914,9 @@ static void window_change_coord_mode_cb ( GtkAction *old_a, GtkAction *a, VikWin if (!strcmp(gtk_action_get_name(a), "ModeUTM")) { drawmode = VIK_VIEWPORT_DRAWMODE_UTM; } + else if (!strcmp(gtk_action_get_name(a), "ModeLatLon")) { + drawmode = VIK_VIEWPORT_DRAWMODE_LATLON; + } else if (!strcmp(gtk_action_get_name(a), "ModeExpedia")) { drawmode = VIK_VIEWPORT_DRAWMODE_EXPEDIA; } @@ -2152,7 +3947,7 @@ static void window_change_coord_mode_cb ( GtkAction *old_a, GtkAction *a, VikWin static void set_draw_scale ( GtkAction *a, VikWindow *vw ) { - GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/ShowScale" ); + GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ShowScale" ); g_assert(check_box); gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box)); vik_viewport_set_draw_scale ( vw->viking_vvp, state ); @@ -2161,22 +3956,31 @@ static void set_draw_scale ( GtkAction *a, VikWindow *vw ) static void set_draw_centermark ( GtkAction *a, VikWindow *vw ) { - GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/ShowCenterMark" ); + GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ShowCenterMark" ); g_assert(check_box); gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box)); vik_viewport_set_draw_centermark ( vw->viking_vvp, state ); draw_update ( vw ); } +static void set_draw_highlight ( GtkAction *a, VikWindow *vw ) +{ + GtkWidget *check_box = gtk_ui_manager_get_widget ( vw->uim, "/ui/MainMenu/View/SetShow/ShowHighlight" ); + g_assert(check_box); + gboolean state = gtk_check_menu_item_get_active ( GTK_CHECK_MENU_ITEM(check_box)); + vik_viewport_set_draw_highlight ( vw->viking_vvp, state ); + draw_update ( vw ); +} + static void set_bg_color ( GtkAction *a, VikWindow *vw ) { GtkWidget *colorsd = gtk_color_selection_dialog_new ( _("Choose a background color") ); GdkColor *color = vik_viewport_get_background_gdkcolor ( vw->viking_vvp ); - gtk_color_selection_set_previous_color ( GTK_COLOR_SELECTION(GTK_COLOR_SELECTION_DIALOG(colorsd)->colorsel), color ); - gtk_color_selection_set_current_color ( GTK_COLOR_SELECTION(GTK_COLOR_SELECTION_DIALOG(colorsd)->colorsel), color ); + gtk_color_selection_set_previous_color ( GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(colorsd))), color ); + gtk_color_selection_set_current_color ( GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(colorsd))), color ); if ( gtk_dialog_run ( GTK_DIALOG(colorsd) ) == GTK_RESPONSE_OK ) { - gtk_color_selection_get_current_color ( GTK_COLOR_SELECTION(GTK_COLOR_SELECTION_DIALOG(colorsd)->colorsel), color ); + gtk_color_selection_get_current_color ( GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(colorsd))), color ); vik_viewport_set_background_gdkcolor ( vw->viking_vvp, color ); draw_update ( vw ); } @@ -2184,6 +3988,22 @@ static void set_bg_color ( GtkAction *a, VikWindow *vw ) gtk_widget_destroy ( colorsd ); } +static void set_highlight_color ( GtkAction *a, VikWindow *vw ) +{ + GtkWidget *colorsd = gtk_color_selection_dialog_new ( _("Choose a track highlight color") ); + GdkColor *color = vik_viewport_get_highlight_gdkcolor ( vw->viking_vvp ); + gtk_color_selection_set_previous_color ( GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(colorsd))), color ); + gtk_color_selection_set_current_color ( GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(colorsd))), color ); + if ( gtk_dialog_run ( GTK_DIALOG(colorsd) ) == GTK_RESPONSE_OK ) + { + gtk_color_selection_get_current_color ( GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(colorsd))), color ); + vik_viewport_set_highlight_gdkcolor ( vw->viking_vvp, color ); + draw_update ( vw ); + } + g_free ( color ); + gtk_widget_destroy ( colorsd ); +} + /*********************************************************************************************** @@ -2194,6 +4014,7 @@ static GtkActionEntry entries[] = { { "File", NULL, N_("_File"), 0, 0, 0 }, { "Edit", NULL, N_("_Edit"), 0, 0, 0 }, { "View", NULL, N_("_View"), 0, 0, 0 }, + { "SetShow", NULL, N_("_Show"), 0, 0, 0 }, { "SetZoom", NULL, N_("_Zoom"), 0, 0, 0 }, { "SetPan", NULL, N_("_Pan"), 0, 0, 0 }, { "Layers", NULL, N_("_Layers"), 0, 0, 0 }, @@ -2205,89 +4026,99 @@ static GtkActionEntry entries[] = { { "Open", GTK_STOCK_OPEN, N_("_Open..."), "O", N_("Open a file"), (GCallback)load_file }, { "OpenRecentFile", NULL, N_("Open _Recent File"), NULL, NULL, (GCallback)NULL }, { "Append", GTK_STOCK_ADD, N_("Append _File..."), NULL, N_("Append data from a different file"), (GCallback)load_file }, - { "Acquire", NULL, N_("A_cquire"), 0, 0, 0 }, + { "Export", GTK_STOCK_CONVERT, N_("_Export All"), NULL, N_("Export All TrackWaypoint Layers"), (GCallback)NULL }, + { "ExportGPX", NULL, N_("_GPX..."), NULL, N_("Export as GPX"), (GCallback)export_to_gpx }, + { "Acquire", GTK_STOCK_GO_DOWN, N_("A_cquire"), NULL, NULL, (GCallback)NULL }, { "AcquireGPS", NULL, N_("From _GPS..."), NULL, N_("Transfer data from a GPS device"), (GCallback)acquire_from_gps }, - { "AcquireGoogle", NULL, N_("Google _Directions..."), NULL, N_("Get driving directions from Google"), (GCallback)acquire_from_google }, + { "AcquireGPSBabel", NULL, N_("Import File With GPS_Babel..."), NULL, N_("Import file via GPSBabel converter"), (GCallback)acquire_from_file }, + { "AcquireRouting", NULL, N_("_Directions..."), NULL, N_("Get driving directions"), (GCallback)acquire_from_routing }, +#ifdef VIK_CONFIG_OPENSTREETMAP + { "AcquireOSM", NULL, N_("_OSM Traces..."), NULL, N_("Get traces from OpenStreetMap"), (GCallback)acquire_from_osm }, + { "AcquireMyOSM", NULL, N_("_My OSM Traces..."), NULL, N_("Get Your Own Traces from OpenStreetMap"), (GCallback)acquire_from_my_osm }, +#endif #ifdef VIK_CONFIG_GEOCACHES { "AcquireGC", NULL, N_("Geo_caches..."), NULL, N_("Get Geocaches from geocaching.com"), (GCallback)acquire_from_gc }, +#endif +#ifdef VIK_CONFIG_GEOTAG + { "AcquireGeotag", NULL, N_("From Geotagged _Images..."), NULL, N_("Create waypoints from geotagged images"), (GCallback)acquire_from_geotag }, +#endif + { "AcquireURL", NULL, N_("From _URL..."), NULL, N_("Get a file from a URL"), (GCallback)acquire_from_url }, +#ifdef VIK_CONFIG_GEONAMES + { "AcquireWikipedia", NULL, N_("From _Wikipedia Waypoints"), NULL, N_("Create waypoints from Wikipedia items in the current view"), (GCallback)acquire_from_wikipedia }, #endif { "Save", GTK_STOCK_SAVE, N_("_Save"), "S", N_("Save the file"), (GCallback)save_file }, { "SaveAs", GTK_STOCK_SAVE_AS, N_("Save _As..."), NULL, N_("Save the file under different name"), (GCallback)save_file_as }, + { "FileProperties", NULL, N_("Properties..."), NULL, N_("File Properties"), (GCallback)file_properties_cb }, { "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 }, - { "GenImgDir", GTK_STOCK_DND_MULTIPLE, N_("Generate _Directory of Images..."), NULL, N_("FIXME:IMGDIR"), (GCallback)draw_to_image_dir_cb }, - -#if GTK_CHECK_VERSION(2,10,0) + { "GenImgDir", GTK_STOCK_DND_MULTIPLE, N_("Generate _Directory of Images..."), NULL, N_("Generate _Directory of Images"), (GCallback)draw_to_image_dir_cb }, { "Print", GTK_STOCK_PRINT, N_("_Print..."), NULL, N_("Print maps"), (GCallback)print_cb }, -#endif - { "Exit", GTK_STOCK_QUIT, N_("E_xit"), "W", N_("Exit the program"), (GCallback)window_close }, { "SaveExit", GTK_STOCK_QUIT, N_("Save and Exit"), NULL, N_("Save and Exit the program"), (GCallback)save_file_and_exit }, + { "GoBack", GTK_STOCK_GO_BACK, N_("Go to the Pre_vious Location"), NULL, N_("Go to the previous location"), (GCallback)draw_goto_back_and_forth }, + { "GoForward", GTK_STOCK_GO_FORWARD, N_("Go to the _Next Location"), NULL, N_("Go to the next location"), (GCallback)draw_goto_back_and_forth }, { "GotoDefaultLocation", GTK_STOCK_HOME, N_("Go to the _Default Location"), NULL, N_("Go to the default location"), (GCallback)goto_default_location }, { "GotoSearch", GTK_STOCK_JUMP_TO, N_("Go to _Location..."), NULL, N_("Go to address/place using text search"), (GCallback)goto_address }, { "GotoLL", GTK_STOCK_JUMP_TO, N_("_Go to Lat/Lon..."), NULL, N_("Go to arbitrary lat/lon coordinate"), (GCallback)draw_goto_cb }, { "GotoUTM", GTK_STOCK_JUMP_TO, N_("Go to UTM..."), NULL, N_("Go to arbitrary UTM coordinate"), (GCallback)draw_goto_cb }, - { "SetBGColor",GTK_STOCK_SELECT_COLOR, N_("Set Bac_kground Color..."), NULL, NULL, (GCallback)set_bg_color }, - { "ZoomIn", GTK_STOCK_ZOOM_IN, N_("Zoom _In"), "plus", NULL, (GCallback)draw_zoom_cb }, - { "ZoomOut", GTK_STOCK_ZOOM_OUT, N_("Zoom _Out"), "minus", NULL, (GCallback)draw_zoom_cb }, - { "ZoomTo", GTK_STOCK_ZOOM_FIT, N_("Zoom _To..."), "Z", NULL, (GCallback)zoom_to_cb }, - { "Zoom0.25", NULL, N_("0.25"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom0.5", NULL, N_("0.5"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom1", NULL, N_("1"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom2", NULL, N_("2"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom4", NULL, N_("4"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom8", NULL, N_("8"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom16", NULL, N_("16"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom32", NULL, N_("32"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom64", NULL, N_("64"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom128", NULL, N_("128"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom256", NULL, N_("256"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom512", NULL, N_("512"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom1024", NULL, N_("1024"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom2048", NULL, N_("2048"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom4096", NULL, N_("4096"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom8192", NULL, N_("8192"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom16384", NULL, N_("16384"), NULL, NULL, (GCallback)draw_zoom_cb }, - { "Zoom32768", NULL, N_("32768"), NULL, NULL, (GCallback)draw_zoom_cb }, + { "Refresh", GTK_STOCK_REFRESH, N_("_Refresh"), "F5", N_("Refresh any maps displayed"), (GCallback)draw_refresh_cb }, + { "SetHLColor",GTK_STOCK_SELECT_COLOR, N_("Set _Highlight Color..."), NULL, N_("Set Highlight Color"), (GCallback)set_highlight_color }, + { "SetBGColor",GTK_STOCK_SELECT_COLOR, N_("Set Bac_kground Color..."), NULL, N_("Set Background Color"), (GCallback)set_bg_color }, + { "ZoomIn", GTK_STOCK_ZOOM_IN, N_("Zoom _In"), "plus", N_("Zoom In"), (GCallback)draw_zoom_cb }, + { "ZoomOut", GTK_STOCK_ZOOM_OUT, N_("Zoom _Out"), "minus", N_("Zoom Out"), (GCallback)draw_zoom_cb }, + { "ZoomTo", GTK_STOCK_ZOOM_FIT, N_("Zoom _To..."), "Z", N_("Zoom To"), (GCallback)zoom_to_cb }, { "PanNorth", NULL, N_("Pan _North"), "Up", NULL, (GCallback)draw_pan_cb }, { "PanEast", NULL, N_("Pan _East"), "Right", NULL, (GCallback)draw_pan_cb }, { "PanSouth", NULL, N_("Pan _South"), "Down", NULL, (GCallback)draw_pan_cb }, { "PanWest", NULL, N_("Pan _West"), "Left", NULL, (GCallback)draw_pan_cb }, - { "BGJobs", GTK_STOCK_EXECUTE, N_("Background _Jobs"), NULL, NULL, (GCallback)a_background_show_window }, + { "BGJobs", GTK_STOCK_EXECUTE, N_("Background _Jobs"), NULL, N_("Background Jobs"), (GCallback)a_background_show_window }, - { "Cut", GTK_STOCK_CUT, N_("Cu_t"), NULL, NULL, (GCallback)menu_cut_layer_cb }, - { "Copy", GTK_STOCK_COPY, N_("_Copy"), NULL, NULL, (GCallback)menu_copy_layer_cb }, - { "Paste", GTK_STOCK_PASTE, N_("_Paste"), NULL, NULL, (GCallback)menu_paste_layer_cb }, - { "Delete", GTK_STOCK_DELETE, N_("_Delete"), NULL, NULL, (GCallback)menu_delete_layer_cb }, + { "Cut", GTK_STOCK_CUT, N_("Cu_t"), NULL, N_("Cut selected layer"), (GCallback)menu_cut_layer_cb }, + { "Copy", GTK_STOCK_COPY, N_("_Copy"), NULL, N_("Copy selected layer"), (GCallback)menu_copy_layer_cb }, + { "Paste", GTK_STOCK_PASTE, N_("_Paste"), NULL, N_("Paste layer into selected container layer or otherwise above selected layer"), (GCallback)menu_paste_layer_cb }, + { "Delete", GTK_STOCK_DELETE, N_("_Delete"), NULL, N_("Remove selected layer"), (GCallback)menu_delete_layer_cb }, { "DeleteAll", NULL, N_("Delete All"), NULL, NULL, (GCallback)clear_cb }, + { "CopyCentre",NULL, N_("Copy Centre _Location"), "h", NULL, (GCallback)menu_copy_centre_cb }, { "MapCacheFlush",NULL, N_("_Flush Map Cache"), NULL, NULL, (GCallback)mapcache_flush_cb }, { "SetDefaultLocation", GTK_STOCK_GO_FORWARD, N_("_Set the Default Location"), NULL, N_("Set the Default Location to the current position"),(GCallback)default_location_cb }, - { "Preferences",GTK_STOCK_PREFERENCES, N_("_Preferences"), NULL, NULL, (GCallback)preferences_cb }, - { "Properties",GTK_STOCK_PROPERTIES, N_("_Properties"), NULL, NULL, (GCallback)menu_properties_cb }, + { "Preferences",GTK_STOCK_PREFERENCES, N_("_Preferences"), NULL, N_("Program Preferences"), (GCallback)preferences_cb }, + { "LayerDefaults",GTK_STOCK_PROPERTIES, N_("_Layer Defaults"), NULL, NULL, NULL }, + { "Properties",GTK_STOCK_PROPERTIES, N_("_Properties"), NULL, N_("Layer Properties"), (GCallback)menu_properties_cb }, + + { "HelpEntry", GTK_STOCK_HELP, N_("_Help"), "F1", N_("Help"), (GCallback)help_help_cb }, + { "About", GTK_STOCK_ABOUT, N_("_About"), NULL, N_("About"), (GCallback)help_about_cb }, +}; + +static GtkActionEntry debug_entries[] = { + { "MapCacheInfo", NULL, "_Map Cache Info", NULL, NULL, (GCallback)help_cache_info_cb }, +}; + +static GtkActionEntry entries_gpsbabel[] = { + { "ExportKML", NULL, N_("_KML..."), NULL, N_("Export as KML"), (GCallback)export_to_kml }, +}; - { "HelpEntry", GTK_STOCK_HELP, N_("_Help"), "F1", NULL, (GCallback)help_help_cb }, - { "About", GTK_STOCK_ABOUT, N_("_About"), NULL, NULL, (GCallback)help_about_cb }, +static GtkActionEntry entries_geojson[] = { + { "AcquireGeoJSON", NULL, N_("Import Geo_JSON File..."), NULL, N_("Import GeoJSON file"), (GCallback)acquire_from_geojson }, }; /* Radio items */ +/* FIXME use VIEWPORT_DRAWMODE values */ static GtkRadioActionEntry mode_entries[] = { { "ModeUTM", NULL, N_("_UTM Mode"), "u", NULL, 0 }, { "ModeExpedia", NULL, N_("_Expedia Mode"), "e", NULL, 1 }, - { "ModeMercator", NULL, N_("_Mercator Mode"), "g", NULL, 4 } -}; - -static GtkRadioActionEntry tool_entries[] = { - { "Pan", "vik-icon-pan", N_("_Pan"), "P", N_("Pan Tool"), 0 }, - { "Zoom", "vik-icon-zoom", N_("_Zoom"), "Z", N_("Zoom Tool"), 1 }, - { "Ruler", "vik-icon-ruler", N_("_Ruler"), "R", N_("Ruler Tool"), 2 } + { "ModeMercator", NULL, N_("_Mercator Mode"), "m", NULL, 4 }, + { "ModeLatLon", NULL, N_("Lat_/Lon Mode"), "l", NULL, 5 }, }; static GtkToggleActionEntry toggle_entries[] = { - { "ShowScale", NULL, N_("_Show Scale"), "F5", N_("Show Scale"), (GCallback)set_draw_scale, TRUE }, + { "ShowScale", NULL, N_("Show _Scale"), "F5", N_("Show Scale"), (GCallback)set_draw_scale, TRUE }, { "ShowCenterMark", NULL, N_("Show _Center Mark"), "F6", N_("Show Center Mark"), (GCallback)set_draw_centermark, TRUE }, + { "ShowHighlight", GTK_STOCK_UNDERLINE, N_("Show _Highlight"), "F7", N_("Show Highlight"), (GCallback)set_draw_highlight, TRUE }, { "FullScreen", GTK_STOCK_FULLSCREEN, N_("_Full Screen"), "F11", N_("Activate full screen mode"), (GCallback)full_screen_cb, FALSE }, - { "ViewSidePanel", GTK_STOCK_INDEX, N_("Show Side Pa_nel"), "F9", N_("Show Side Panel"), (GCallback)view_side_panel_cb, TRUE }, + { "ViewSidePanel", GTK_STOCK_INDEX, N_("Show Side _Panel"), "F9", N_("Show Side Panel"), (GCallback)view_side_panel_cb, TRUE }, { "ViewStatusBar", NULL, N_("Show Status_bar"), "F12", N_("Show Statusbar"), (GCallback)view_statusbar_cb, TRUE }, + { "ViewToolbar", NULL, N_("Show _Toolbar"), "F3", N_("Show Toolbar"), (GCallback)view_toolbar_cb, TRUE }, + { "ViewMainMenu", NULL, N_("Show _Menu"), "F4", N_("Show Menu"), (GCallback)view_main_menu_cb, TRUE }, }; #include "menu.xml.h" @@ -2309,6 +4140,7 @@ static void window_create_ui( VikWindow *window ) toolbox_add_tool(window->vt, &ruler_tool, TOOL_LAYER_TYPE_NONE); toolbox_add_tool(window->vt, &zoom_tool, TOOL_LAYER_TYPE_NONE); toolbox_add_tool(window->vt, &pan_tool, TOOL_LAYER_TYPE_NONE); + toolbox_add_tool(window->vt, &select_tool, TOOL_LAYER_TYPE_NONE); error = NULL; if (!(mid = gtk_ui_manager_add_ui_from_string (uim, menu_xml, -1, &error))) { @@ -2321,20 +4153,47 @@ static void window_create_ui( VikWindow *window ) gtk_action_group_add_actions (action_group, entries, G_N_ELEMENTS (entries), window); gtk_action_group_add_toggle_actions (action_group, toggle_entries, G_N_ELEMENTS (toggle_entries), window); gtk_action_group_add_radio_actions (action_group, mode_entries, G_N_ELEMENTS (mode_entries), 4, (GCallback)window_change_coord_mode_cb, window); + if ( vik_debug ) { + if ( gtk_ui_manager_add_ui_from_string ( uim, + "", + -1, NULL ) ) { + gtk_action_group_add_actions (action_group, debug_entries, G_N_ELEMENTS (debug_entries), window); + } + } + + + // Use this to see if GPSBabel is available: + if ( a_babel_available () ) { + // If going to add more entries then might be worth creating a menu_gpsbabel.xml.h file + if ( gtk_ui_manager_add_ui_from_string ( uim, + "", + -1, &error ) ) + gtk_action_group_add_actions ( action_group, entries_gpsbabel, G_N_ELEMENTS (entries_gpsbabel), window ); + } + + // GeoJSON import capability + if ( g_find_program_in_path ( a_geojson_program_import() ) ) { + if ( gtk_ui_manager_add_ui_from_string ( uim, + "", + -1, &error ) ) + gtk_action_group_add_actions ( action_group, entries_geojson, G_N_ELEMENTS (entries_geojson), window ); + } icon_factory = gtk_icon_factory_new (); gtk_icon_factory_add_default (icon_factory); register_vik_icons(icon_factory); + // Copy the tool RadioActionEntries out of the main Window structure into an extending array 'tools' + // so that it can be applied to the UI in one action group add function call below ntools = 0; - for (i=0; ivt->n_tools; i++) { tools = g_renew(GtkRadioActionEntry, tools, ntools+1); radio = &tools[ntools]; ntools++; - *radio = tool_entries[i]; + *radio = window->vt->tools[i].ti.radioActionEntry; radio->value = ntools; - } + } for (i=0; iname; action.stock_id = vik_layer_get_interface(i)->name; - action.label = g_strdup_printf( _("New %s Layer"), vik_layer_get_interface(i)->name); - action.accelerator = NULL; + action.label = g_strdup_printf( _("New _%s Layer"), vik_layer_get_interface(i)->name); + action.accelerator = vik_layer_get_interface(i)->accelerator; action.tooltip = NULL; action.callback = (GCallback)menu_addlayer_cb; gtk_action_group_add_actions(action_group, &action, 1, window); + g_free ( (gchar*)action.label ); + if ( vik_layer_get_interface(i)->tools_count ) { gtk_ui_manager_add_ui(uim, mid, "/ui/MainMenu/Tools/", vik_layer_get_interface(i)->name, NULL, GTK_UI_MANAGER_SEPARATOR, FALSE); gtk_ui_manager_add_ui(uim, mid, "/ui/MainToolbar/ToolItems/", vik_layer_get_interface(i)->name, NULL, GTK_UI_MANAGER_SEPARATOR, FALSE); } + // Further tool copying for to apply to the UI, also apply menu UI setup for ( j = 0; j < vik_layer_get_interface(i)->tools_count; j++ ) { tools = g_renew(GtkRadioActionEntry, tools, ntools+1); radio = &tools[ntools]; ntools++; gtk_ui_manager_add_ui(uim, mid, "/ui/MainMenu/Tools", - _(vik_layer_get_interface(i)->tools[j].name), - vik_layer_get_interface(i)->tools[j].name, + vik_layer_get_interface(i)->tools[j].radioActionEntry.label, + vik_layer_get_interface(i)->tools[j].radioActionEntry.name, GTK_UI_MANAGER_MENUITEM, FALSE); gtk_ui_manager_add_ui(uim, mid, "/ui/MainToolbar/ToolItems", - _(vik_layer_get_interface(i)->tools[j].name), - vik_layer_get_interface(i)->tools[j].name, + vik_layer_get_interface(i)->tools[j].radioActionEntry.label, + vik_layer_get_interface(i)->tools[j].radioActionEntry.name, GTK_UI_MANAGER_TOOLITEM, FALSE); toolbox_add_tool(window->vt, &(vik_layer_get_interface(i)->tools[j]), i); - radio->name = vik_layer_get_interface(i)->tools[j].name; - radio->stock_id = vik_layer_get_interface(i)->tools[j].name, - radio->label = _(vik_layer_get_interface(i)->tools[j].name); - radio->accelerator = NULL; - radio->tooltip = _(vik_layer_get_interface(i)->tools[j].name); + *radio = vik_layer_get_interface(i)->tools[j].radioActionEntry; + // Overwrite with actual number to use radio->value = ntools; } + + GtkActionEntry action_dl; + gchar *layername = g_strdup_printf ( "Layer%s", vik_layer_get_interface(i)->fixed_layer_name ); + gtk_ui_manager_add_ui(uim, mid, "/ui/MainMenu/Edit/LayerDefaults", + vik_layer_get_interface(i)->name, + layername, + GTK_UI_MANAGER_MENUITEM, FALSE); + g_free (layername); + + // For default layers use action names of the form 'Layer' + // This is to avoid clashing with just the layer name used above for the tool actions + action_dl.name = g_strconcat("Layer", vik_layer_get_interface(i)->fixed_layer_name, NULL); + action_dl.stock_id = NULL; + action_dl.label = g_strconcat("_", vik_layer_get_interface(i)->name, "...", NULL); // Prepend marker for keyboard accelerator + action_dl.accelerator = NULL; + action_dl.tooltip = NULL; + action_dl.callback = (GCallback)layer_defaults_cb; + gtk_action_group_add_actions(action_group, &action_dl, 1, window); + g_free ( (gchar*)action_dl.name ); + g_free ( (gchar*)action_dl.label ); } g_object_unref (icon_factory); @@ -2394,10 +4273,14 @@ static void window_create_ui( VikWindow *window ) for (i=0; itools_count; j++ ) { GtkAction *action = gtk_action_group_get_action(action_group, - vik_layer_get_interface(i)->tools[j].name); + vik_layer_get_interface(i)->tools[j].radioActionEntry.name); g_object_set(action, "sensitive", FALSE, NULL); } } + + // This is done last so we don't need to track the value of mid anymore + vik_ext_tools_add_action_items ( window, window->uim, action_group, mid ); + window->action_group = action_group; accel_group = gtk_ui_manager_get_accel_group (uim); @@ -2408,26 +4291,27 @@ static void window_create_ui( VikWindow *window ) } - +// TODO - add method to add tool icons defined from outside this file +// and remove the reverse dependency on icon definition from this file static struct { const GdkPixdata *data; gchar *stock_id; } stock_icons[] = { - { &begintr_18_pixbuf, "Begin Track" }, - { &iscissors_18_pixbuf, "Magic Scissors" }, - { &mover_22_pixbuf, "vik-icon-pan" }, - { &demdl_18_pixbuf, "DEM Download/Import" }, - { &showpic_18_pixbuf, "Show Picture" }, - { &addtr_18_pixbuf, "Create Track" }, - { &edtr_18_pixbuf, "Edit Trackpoint" }, - { &addwp_18_pixbuf, "Create Waypoint" }, - { &edwp_18_pixbuf, "Edit Waypoint" }, + { &mover_22_pixbuf, "vik-icon-pan" }, { &zoom_18_pixbuf, "vik-icon-zoom" }, { &ruler_18_pixbuf, "vik-icon-ruler" }, - { &geozoom_18_pixbuf, "Georef Zoom Tool" }, - { &geomove_18_pixbuf, "Georef Move Map" }, - { &mapdl_18_pixbuf, "Maps Download" }, - { &showpic_18_pixbuf, "Show Picture" }, + { &select_18_pixbuf, "vik-icon-select" }, + { &vik_new_route_18_pixbuf, "vik-icon-Create Route" }, + { &route_finder_18_pixbuf, "vik-icon-Route Finder" }, + { &demdl_18_pixbuf, "vik-icon-DEM Download" }, + { &showpic_18_pixbuf, "vik-icon-Show Picture" }, + { &addtr_18_pixbuf, "vik-icon-Create Track" }, + { &edtr_18_pixbuf, "vik-icon-Edit Trackpoint" }, + { &addwp_18_pixbuf, "vik-icon-Create Waypoint" }, + { &edwp_18_pixbuf, "vik-icon-Edit Waypoint" }, + { &geozoom_18_pixbuf, "vik-icon-Georef Zoom Tool" }, + { &geomove_18_pixbuf, "vik-icon-Georef Move Map" }, + { &mapdl_18_pixbuf, "vik-icon-Maps Download" }, }; static gint n_stock_icons = G_N_ELEMENTS (stock_icons); @@ -2446,3 +4330,119 @@ register_vik_icons (GtkIconFactory *icon_factory) } } +gpointer vik_window_get_selected_trw_layer ( VikWindow *vw ) +{ + return vw->selected_vtl; +} + +void vik_window_set_selected_trw_layer ( VikWindow *vw, gpointer vtl ) +{ + vw->selected_vtl = vtl; + vw->containing_vtl = vtl; + /* Clear others */ + vw->selected_track = NULL; + vw->selected_tracks = NULL; + vw->selected_waypoint = NULL; + vw->selected_waypoints = NULL; + // Set highlight thickness + vik_viewport_set_highlight_thickness ( vw->viking_vvp, vik_trw_layer_get_property_tracks_line_thickness (vw->containing_vtl) ); +} + +GHashTable *vik_window_get_selected_tracks ( VikWindow *vw ) +{ + return vw->selected_tracks; +} + +void vik_window_set_selected_tracks ( VikWindow *vw, GHashTable *ght, gpointer vtl ) +{ + vw->selected_tracks = ght; + vw->containing_vtl = vtl; + /* Clear others */ + vw->selected_vtl = NULL; + vw->selected_track = NULL; + vw->selected_waypoint = NULL; + vw->selected_waypoints = NULL; + // Set highlight thickness + vik_viewport_set_highlight_thickness ( vw->viking_vvp, vik_trw_layer_get_property_tracks_line_thickness (vw->containing_vtl) ); +} + +gpointer vik_window_get_selected_track ( VikWindow *vw ) +{ + return vw->selected_track; +} + +void vik_window_set_selected_track ( VikWindow *vw, gpointer *vt, gpointer vtl ) +{ + vw->selected_track = vt; + vw->containing_vtl = vtl; + /* Clear others */ + vw->selected_vtl = NULL; + vw->selected_tracks = NULL; + vw->selected_waypoint = NULL; + vw->selected_waypoints = NULL; + // Set highlight thickness + vik_viewport_set_highlight_thickness ( vw->viking_vvp, vik_trw_layer_get_property_tracks_line_thickness (vw->containing_vtl) ); +} + +GHashTable *vik_window_get_selected_waypoints ( VikWindow *vw ) +{ + return vw->selected_waypoints; +} + +void vik_window_set_selected_waypoints ( VikWindow *vw, GHashTable *ght, gpointer vtl ) +{ + vw->selected_waypoints = ght; + vw->containing_vtl = vtl; + /* Clear others */ + vw->selected_vtl = NULL; + vw->selected_track = NULL; + vw->selected_tracks = NULL; + vw->selected_waypoint = NULL; +} + +gpointer vik_window_get_selected_waypoint ( VikWindow *vw ) +{ + return vw->selected_waypoint; +} + +void vik_window_set_selected_waypoint ( VikWindow *vw, gpointer *vwp, gpointer vtl ) +{ + vw->selected_waypoint = vwp; + vw->containing_vtl = vtl; + /* Clear others */ + vw->selected_vtl = NULL; + vw->selected_track = NULL; + vw->selected_tracks = NULL; + vw->selected_waypoints = NULL; +} + +gboolean vik_window_clear_highlight ( VikWindow *vw ) +{ + gboolean need_redraw = FALSE; + if ( vw->selected_vtl != NULL ) { + vw->selected_vtl = NULL; + need_redraw = TRUE; + } + if ( vw->selected_track != NULL ) { + vw->selected_track = NULL; + need_redraw = TRUE; + } + if ( vw->selected_tracks != NULL ) { + vw->selected_tracks = NULL; + need_redraw = TRUE; + } + if ( vw->selected_waypoint != NULL ) { + vw->selected_waypoint = NULL; + need_redraw = TRUE; + } + if ( vw->selected_waypoints != NULL ) { + vw->selected_waypoints = NULL; + need_redraw = TRUE; + } + return need_redraw; +} + +GThread *vik_window_get_thread ( VikWindow *vw ) +{ + return vw->thread; +}