/* gps.c */ /* Creates the UI displaying the dives locations on a map. */ #include #include "osm-gps-map.h" #include "dive.h" #include "display.h" #include "display-gtk.h" #include "divelist.h" #include #include #include "flag.h" static GtkWidget *window = NULL; static OsmGpsMap *map = NULL; /* Several map providers are available, such as OSM_GPS_MAP_SOURCE_OPENSTREETMAP and OSM_GPS_MAP_SOURCE_GOOGLE_STREET. We should make more of them available from e.g. a pull-down menu */ static OsmGpsMapSource_t opt_map_provider = OSM_GPS_MAP_SOURCE_VIRTUAL_EARTH_HYBRID; static void on_close(GtkWidget *widget, gpointer user_data) { GtkWidget **window = user_data; gtk_grab_remove(widget); gtk_widget_destroy(widget); *window = NULL; } struct maplocation { OsmGpsMap *map; double x,y; void (* callback)(float, float); GtkWidget *mapwindow; }; static void add_location_cb(GtkWidget *widget, gpointer data) { struct maplocation *maplocation = data; OsmGpsMap *map = maplocation->map; OsmGpsMapPoint *pt = osm_gps_map_point_new_degrees(0.0, 0.0); float mark_lat, mark_lon; osm_gps_map_convert_screen_to_geographic(map, maplocation->x, maplocation->y, pt); osm_gps_map_point_get_degrees(pt, &mark_lat, &mark_lon); maplocation->callback(mark_lat, mark_lon); gtk_widget_destroy(gtk_widget_get_parent(maplocation->mapwindow)); } static void map_popup_menu_cb(GtkWidget *w, gpointer data) { GtkWidget *menu, *menuitem, *image; menu = gtk_menu_new(); menuitem = gtk_image_menu_item_new_with_label(_("Mark location here")); image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU); gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); g_signal_connect(menuitem, "activate", G_CALLBACK(add_location_cb), data); gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); gtk_widget_show_all(menu); gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, gtk_get_current_event_time()); } static gboolean button_cb(GtkWidget *widget, GdkEventButton *event, gpointer data) { static struct maplocation maplocation = {}; if (data && event->type == GDK_BUTTON_PRESS && event->button == 3) { maplocation.map = (OsmGpsMap *)widget; maplocation.x = event->x; maplocation.y = event->y; maplocation.callback = data; maplocation.mapwindow = widget; map_popup_menu_cb(widget, &maplocation); } return FALSE; } /* the osm gps map default is to scroll-and-recenter around the mouse position * that is BAT SHIT CRAZY. * So let's do our own scroll handling instead */ static gboolean scroll_cb(GtkWidget *widget, GdkEventScroll *event, gpointer data) { OsmGpsMap *map = (OsmGpsMap *)widget; OsmGpsMapPoint *pt, *lt, *rb, *target; float target_lat, target_lon; gint ltx, lty, rbx, rby, target_x, target_y; gint zoom, max_zoom, min_zoom; g_object_get(widget, "max-zoom", &max_zoom, "zoom", &zoom, "min-zoom", &min_zoom, NULL); pt = osm_gps_map_point_new_degrees(0.0, 0.0); lt = osm_gps_map_point_new_degrees(0.0, 0.0); rb = osm_gps_map_point_new_degrees(0.0, 0.0); target = osm_gps_map_point_new_degrees(0.0, 0.0); osm_gps_map_convert_screen_to_geographic(map, event->x, event->y, pt); osm_gps_map_get_bbox(map, lt, rb); osm_gps_map_convert_geographic_to_screen(map, lt, <x, <y); osm_gps_map_convert_geographic_to_screen(map, rb, &rbx, &rby); if (event->direction == GDK_SCROLL_UP) { if (zoom == max_zoom) return TRUE; target_x = event->x + ((ltx + rbx) / 2.0 - (gint)(event->x)) / 2; target_y = event->y + ((lty + rby) / 2.0 - (gint)(event->y)) / 2; osm_gps_map_convert_screen_to_geographic(map, target_x, target_y, target); osm_gps_map_point_get_degrees(target, &target_lat, &target_lon); osm_gps_map_zoom_in(map); osm_gps_map_set_center(map, target_lat, target_lon); } else if (event->direction == GDK_SCROLL_DOWN) { if (zoom == min_zoom) return TRUE; target_x = event->x + ((ltx + rbx) / 2.0 - (gint)(event->x)) * 2; target_y = event->y + ((lty + rby) / 2.0 - (gint)(event->y)) * 2; osm_gps_map_convert_screen_to_geographic(map, target_x, target_y, target); osm_gps_map_point_get_degrees(target, &target_lat, &target_lon); osm_gps_map_zoom_out(map); osm_gps_map_set_center(map, target_lat, target_lon); } /* don't allow the insane default handler to get its hands on this event */ return TRUE; } static void add_gps_point(OsmGpsMap *map, float latitude, float longitude) { OsmGpsMapTrack *track; GdkColor dotColor = { 0, 0xFFFF, 0xFFFF, 0x4444 }; track = g_object_new(OSM_TYPE_GPS_MAP_TRACK, "color", &dotColor, "line-width", (gfloat) 10, "alpha", (gfloat) 0.7, NULL); OsmGpsMapPoint *point = osm_gps_map_point_new_degrees(latitude, longitude); osm_gps_map_track_add_point(track, point); osm_gps_map_track_add(map, track); free((void *)point); } static void key_press_event(GtkWidget *window, GdkEventKey *event, gpointer data) { if ((event->string != NULL && event->keyval == GDK_Escape) || (event->string != NULL && event->keyval == GDK_w && event->state & GDK_CONTROL_MASK)) { gtk_widget_destroy(window); } } OsmGpsMap *init_map(void) { OsmGpsMap *map; OsmGpsMapLayer *osd; char *cachedir, *cachebasedir; cachebasedir = osm_gps_map_get_default_cache_directory(); cachedir = g_strdup(OSM_GPS_MAP_CACHE_AUTO); map = g_object_new(OSM_TYPE_GPS_MAP, "map-source", opt_map_provider, "tile-cache", cachedir, "tile-cache-base", cachebasedir, "proxy-uri", g_getenv("http_proxy"), NULL); osd = g_object_new(OSM_TYPE_GPS_MAP_OSD, "show-scale", TRUE, "show-coordinates", TRUE, "show-crosshair", FALSE, "show-dpad", TRUE, "show-zoom", TRUE, "show-gps-in-dpad", TRUE, "show-gps-in-zoom", FALSE, "dpad-radius", 30, NULL); osm_gps_map_layer_add(OSM_GPS_MAP(map), osd); g_object_unref(G_OBJECT(osd)); free((void*)cachebasedir); return map; } void show_map(OsmGpsMap *map, GtkWidget **window, struct dive *dive, void (*callback)(float, float)) { if (!*window) { /* Enable keyboard navigation */ osm_gps_map_set_keyboard_shortcut(map, OSM_GPS_MAP_KEY_FULLSCREEN, GDK_F11); osm_gps_map_set_keyboard_shortcut(map, OSM_GPS_MAP_KEY_UP, GDK_Up); osm_gps_map_set_keyboard_shortcut(map, OSM_GPS_MAP_KEY_DOWN, GDK_Down); osm_gps_map_set_keyboard_shortcut(map, OSM_GPS_MAP_KEY_LEFT, GDK_Left); osm_gps_map_set_keyboard_shortcut(map, OSM_GPS_MAP_KEY_RIGHT, GDK_Right); *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_position(GTK_WINDOW(*window), GTK_WIN_POS_MOUSE); gtk_window_set_default_size(GTK_WINDOW(*window), 560, 360); gtk_window_set_title(GTK_WINDOW(*window), _("Dive locations")); gtk_container_set_border_width(GTK_CONTAINER(*window), 5); gtk_window_set_resizable(GTK_WINDOW(*window), TRUE); gtk_container_add(GTK_CONTAINER(*window), GTK_WIDGET(map)); gtk_window_set_transient_for(GTK_WINDOW(*window), GTK_WINDOW(main_window)); gtk_window_set_modal(GTK_WINDOW(*window), TRUE); g_signal_connect(*window, "destroy", G_CALLBACK(on_close), (gpointer)window); g_signal_connect(G_OBJECT(map), "scroll-event", G_CALLBACK(scroll_cb), NULL); g_signal_connect(*window, "key_press_event", G_CALLBACK (key_press_event), NULL); } if (callback) { g_signal_connect(G_OBJECT(map), "button-press-event", G_CALLBACK(button_cb), callback); gtk_window_set_title(GTK_WINDOW(*window), _("Use right click to mark dive location at cursor")); } gtk_widget_show_all(*window); if (callback) gtk_grab_add(*window); } void show_gps_location(struct dive *dive, void (*callback)(float, float)) { GdkPixbuf *picture; GError *gerror = NULL; double lat = dive->latitude.udeg / 1000000.0; double lng = dive->longitude.udeg / 1000000.0; if (!map || !window) map = init_map(); if (lat != 0 || lng != 0) { add_gps_point(map, lat, lng); osm_gps_map_set_center_and_zoom(map, lat, lng, 9); picture = gdk_pixbuf_from_pixdata(&flag_pixbuf, TRUE, NULL); if (picture) { osm_gps_map_image_add_with_alignment(map, lat, lng, picture, 0, 1); g_object_unref(picture); } else { printf("error message: %s\n", gerror->message); } } else { osm_gps_map_set_center_and_zoom(map, 0, 0, 1); } show_map(map, &window, dive, callback); } void show_gps_locations() { struct dive *dive; int idx; if (!window || !map) map = init_map(); for_each_dive(idx, dive) { if (dive_has_gps_location(dive)) { add_gps_point(map, dive->latitude.udeg / 1000000.0, dive->longitude.udeg / 1000000.0); } } osm_gps_map_set_center_and_zoom(map, 0, 0, 1); show_map(map, &window, NULL, NULL); }