Highlight point on click
[gpx-viewer.git] / src / gpx-viewer.c
blob73b21cace088ac3f600fdda966a024e89739ec32
1 /* Gpx Viewer
2 * Copyright (C) 2009-2009 Qball Cow <qball@sarine.nl>
3 * Project homepage: http://blog.sarine.nl/
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include <gtk/gtk.h>
21 #include <stdlib.h>
22 #include <champlain/champlain.h>
23 #include <champlain-gtk/champlain-gtk.h>
24 #include <clutter-gtk/gtk-clutter-embed.h>
25 #include <config.h>
26 #include <glib/gi18n.h>
27 #include "speed-plot.h"
29 /* List of gpx files */
30 GList *files = NULL;
32 GtkWidget *champlain_view = NULL;
33 GtkBuilder *builder = NULL;
34 GpxGraph *gpx_graph =NULL;
35 ChamplainLayer *marker_layer = NULL;
36 /* Clutter collors */
37 ClutterColor waypoint = { 0xf3, 0x94, 0x07, 0xff };
38 ClutterColor highlight_track_color = { 0xf3, 0x94, 0x07, 0xff };
39 ClutterColor normal_track_color = { 0x00, 0x00, 0xff, 0x66 };
41 typedef struct Route {
42 GpxFile *file;
43 GpxTrack *track;
44 ChamplainPolygon *polygon;
45 gboolean visible;
46 } Route;
47 /* List of routes */
48 GList *routes = NULL;
49 /* The currently active route */
50 Route *active_route = NULL;
52 void on_destroy(void)
54 printf("Quit...\n");
55 gtk_main_quit();
57 gtk_widget_destroy(
58 GTK_WIDGET(gtk_builder_get_object(builder, "gpx_viewer_window")));
59 g_object_unref(builder);
61 /**
62 * Update on track changes
64 static void interface_update_heading(GtkBuilder *builder, GpxTrack *track)
66 time_t temp;
67 gdouble gtemp;
68 GtkWidget *label = NULL;
69 /* Duration */
70 label = (GtkWidget *)gtk_builder_get_object(builder, "duration_label");
71 temp = gpx_track_get_total_time(track);
72 if(temp > 0){
73 int hour = temp/3600;
74 int minutes = ((temp%3600)/60);
75 int seconds = (temp%60);
76 GString *string = g_string_new("");
77 if(hour > 0) {
78 g_string_append_printf(string, "%i %s", hour, (hour == 1)?"hour":"hours");
81 if(minutes > 0) {
82 if(hour > 0) g_string_append(string, ", ");
83 g_string_append_printf(string, "%i %s", minutes, (minutes == 1)?"minute":"minutes");
86 if(seconds > 0) {
87 if(minutes > 0) g_string_append(string, ", ");
88 g_string_append_printf(string, "%i %s", seconds, (seconds == 1)?"second":"seconds");
91 gtk_label_set_text(GTK_LABEL(label), string->str);
92 g_string_free(string, TRUE);
93 }else{
94 gtk_label_set_text(GTK_LABEL(label), "n/a");
96 /* Distance */
97 label = (GtkWidget *)gtk_builder_get_object(builder, "distance_label");
98 gtemp = track->total_distance;
99 if(gtemp > 0)
101 gchar *string = g_strdup_printf("%.2f km", gtemp);
102 gtk_label_set_text(GTK_LABEL(label), string);
103 g_free(string);
104 }else{
105 gtk_label_set_text(GTK_LABEL(label), "n/a");
108 /* Average */
109 label = (GtkWidget *)gtk_builder_get_object(builder, "average_label");
110 gtemp = gpx_track_get_track_average(track);
111 if(gtemp > 0)
113 gchar *string = g_strdup_printf("%.2f km/h", gtemp);
114 gtk_label_set_text(GTK_LABEL(label), string);
115 g_free(string);
116 }else{
117 gtk_label_set_text(GTK_LABEL(label), "n/a");
120 /* Moving Average */
121 label = (GtkWidget *)gtk_builder_get_object(builder, "moving_average_label");
122 gtemp = gpx_track_calculate_moving_average(track, &temp);
123 if(gtemp > 0)
125 gchar *string = g_strdup_printf("%.2f km/h", gtemp);
126 gtk_label_set_text(GTK_LABEL(label), string);
127 g_free(string);
128 }else{
129 gtk_label_set_text(GTK_LABEL(label), "n/a");
132 label = (GtkWidget *)gtk_builder_get_object(builder, "moving_average_time_label");
133 if(gtemp > 0)
135 int hour = temp/3600;
136 int minutes = ((temp%3600)/60);
137 int seconds = (temp%60);
138 GString *string = g_string_new("");
139 if(hour > 0) {
140 g_string_append_printf(string, "%i %s", hour, (hour == 1)?"hour":"hours");
143 if(minutes > 0) {
144 if(hour > 0) g_string_append(string, ", ");
145 g_string_append_printf(string, "%i %s", minutes, (minutes == 1)?"minute":"minutes");
148 if(seconds > 0) {
149 if(minutes > 0) g_string_append(string, ", ");
150 g_string_append_printf(string, "%i %s", seconds, (seconds == 1)?"second":"seconds");
153 gtk_label_set_text(GTK_LABEL(label), string->str);
154 g_string_free(string, TRUE);
155 }else{
156 gtk_label_set_text(GTK_LABEL(label), "n/a");
158 /* Max speed*/
159 label = (GtkWidget *)gtk_builder_get_object(builder, "max_speed_label");
160 gtemp = track->max_speed;
161 if(gtemp > 0)
163 gchar *string = g_strdup_printf("%.2f km/h", gtemp);
164 gtk_label_set_text(GTK_LABEL(label), string);
165 g_free(string);
166 }else{
167 gtk_label_set_text(GTK_LABEL(label), "n/a");
170 /* GPS Points */
171 label = (GtkWidget *)gtk_builder_get_object(builder, "num_points_label");
172 gtemp = (double)g_list_length(track->points);
173 if(gtemp > 0)
175 gchar *string = g_strdup_printf("%.0f points", gtemp);
176 gtk_label_set_text(GTK_LABEL(label), string);
177 g_free(string);
178 }else{
179 gtk_label_set_text(GTK_LABEL(label), "n/a");
183 static void interface_map_plot_route(ChamplainView *view,struct Route *route)
185 GpxPoint *p = NULL;
186 GList *iter;
187 route->polygon = champlain_polygon_new ();
188 for(iter = g_list_first(route->track->points); iter; iter = iter->next)
190 p = iter->data;
191 champlain_polygon_append_point(route->polygon, p->lat_dec, p->lon_dec);
193 champlain_polygon_set_stroke_width (route->polygon, 5.0);
194 champlain_polygon_set_stroke_color (route->polygon, &normal_track_color);
195 champlain_view_add_polygon (CHAMPLAIN_VIEW (view), route->polygon);
197 static void interface_map_make_waypoints(ChamplainView *view)
199 GList *iter;
200 if(marker_layer == NULL)
202 marker_layer = champlain_layer_new();
203 champlain_view_add_layer(view, marker_layer);
205 for(iter = g_list_first(files);iter != NULL; iter = g_list_next(iter))
207 GpxFile *file = iter->data;
208 GList *it = g_list_first(file->waypoints);
209 while(it)
211 GpxPoint *p = it->data;
212 ClutterActor *marker = champlain_marker_new_with_text(p->name, "Seric 12", NULL, NULL);
213 champlain_base_marker_set_position(CHAMPLAIN_BASE_MARKER(marker), p->lat_dec, p->lon_dec);
214 champlain_marker_set_color (CHAMPLAIN_MARKER (marker), &waypoint);
215 clutter_container_add(CLUTTER_CONTAINER(marker_layer), CLUTTER_ACTOR(marker),NULL);
216 clutter_actor_show(CLUTTER_ACTOR(marker_layer));
217 it = g_list_next(it);
221 /* UI functions */
222 void route_set_visible(GtkCheckButton *button, gpointer user_data)
224 gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
225 if(active_route)
227 if(active_route->visible != active)
229 active_route->visible = active;
230 if(active) {
231 champlain_polygon_show(active_route->polygon);
232 }else{
233 champlain_polygon_hide(active_route->polygon);
238 void routes_combo_changed_cb(GtkComboBox *box, gpointer user_data)
240 GtkTreeModel *model = gtk_combo_box_get_model(box);
241 GtkTreeIter iter;
242 if(gtk_combo_box_get_active_iter(box, &iter))
244 Route *route = NULL;
245 gtk_tree_model_get(model, &iter, 1, &route, -1);
246 if(active_route)
248 champlain_polygon_set_stroke_color (active_route->polygon, &normal_track_color);
249 if(active_route->visible)
250 champlain_polygon_show(active_route->polygon);
253 if(route)
255 ChamplainView *view = gtk_champlain_embed_get_view (GTK_CHAMPLAIN_EMBED (champlain_view));
256 champlain_polygon_set_stroke_color (route->polygon, &highlight_track_color);
258 if(route->visible)
259 champlain_polygon_show(route->polygon);
261 interface_update_heading(builder, route->track);
263 if(route->track->top && route->track->bottom)
265 champlain_view_ensure_visible(view,
266 route->track->top->lat_dec, route->track->top->lon_dec,
267 route->track->bottom->lat_dec,route->track->bottom->lon_dec,
268 FALSE);
270 gpx_graph_set_track(gpx_graph, route->track);
272 active_route = route;
274 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "route_visible_check_button")),
275 active_route->visible);
279 static void smooth_factor_changed (GpxGraph *graph,
280 GParamSpec *gobject,
281 GtkSpinButton *spinbutton)
283 gint zoom;
284 g_object_get(G_OBJECT(graph), "smooth-factor", &zoom, NULL);
285 gtk_spin_button_set_value(spinbutton, zoom);
286 printf("smooth changed: %i\n", zoom);
289 void smooth_factor_change_value_cb(GtkSpinButton *spin, gpointer user_data)
291 int current = gpx_graph_get_smooth_factor(gpx_graph);
292 int new = gtk_spin_button_get_value_as_int(spin);
293 printf("changed: %i %i\n", current, new);
294 if(current != new)
296 gpx_graph_set_smooth_factor(gpx_graph, new);
299 static void map_zoom_changed (ChamplainView *view,
300 GParamSpec *gobject,
301 GtkSpinButton *spinbutton)
303 gint zoom;
304 g_object_get(G_OBJECT(view), "zoom-level", &zoom, NULL);
305 gtk_spin_button_set_value(spinbutton, zoom);
306 printf("zoom changed: %i\n", zoom);
308 void map_zoom_level_change_value_cb(GtkSpinButton *spin, gpointer user_data)
310 ChamplainView *view = gtk_champlain_embed_get_view (GTK_CHAMPLAIN_EMBED (champlain_view));
311 int current = champlain_view_get_zoom_level(view);
312 int new = gtk_spin_button_get_value_as_int(spin);
313 printf("changed: %i %i\n", current, new);
314 if(current != new)
316 champlain_view_set_zoom_level(view, new);
320 #define MARKER_SIZE 10
321 static gboolean graph_point_remove(ClutterActor *marker)
323 clutter_actor_destroy(marker);
324 return FALSE;
326 static void graph_point_clicked(double lat_dec, double lon_dec)
328 ChamplainView *view = gtk_champlain_embed_get_view (GTK_CHAMPLAIN_EMBED (champlain_view));
329 if(marker_layer == NULL)
331 marker_layer = champlain_layer_new();
332 champlain_view_add_layer(view, marker_layer);
335 GtkIconInfo *ii = gtk_icon_theme_lookup_icon(gtk_icon_theme_get_default(),
336 "gtk-find",
337 24, 0);
339 ClutterActor *marker = NULL; if(ii) {
340 gchar *path2 = gtk_icon_info_get_filename(ii);
341 if(path2)
343 marker = champlain_marker_new_from_file(path2, NULL);
344 champlain_marker_set_draw_background (CHAMPLAIN_MARKER (marker), FALSE);
347 if(!marker)
349 marker = champlain_marker_new ();
351 /* Create the marker */
352 champlain_base_marker_set_position(CHAMPLAIN_BASE_MARKER(marker), lat_dec, lon_dec);
353 champlain_marker_set_color (CHAMPLAIN_MARKER (marker), &waypoint);
354 clutter_container_add(CLUTTER_CONTAINER(marker_layer), CLUTTER_ACTOR(marker),NULL);
355 clutter_actor_show(CLUTTER_ACTOR(marker_layer));
356 g_timeout_add_seconds(1, graph_point_remove, marker);
359 /* Create the interface */
360 static void create_interface(void)
362 time_t temp;
363 GError *error = NULL;
364 const gchar * const *dirs = g_get_system_data_dirs();
365 int i=0;
366 GList *fiter;
367 builder = gtk_builder_new();
368 gchar *path = g_build_filename(DATA_DIR, "gpx-viewer.ui", NULL);
369 if(!gtk_builder_add_from_file(builder,path, NULL)){
370 g_error("Failed to create ui: %s\n", error->message);
372 printf("path: %s\n", path);
373 g_free(path);
375 /* Create map view */
376 champlain_view = gtk_champlain_embed_new();
377 gtk_widget_set_size_request (champlain_view, 640, 280);
378 gtk_paned_pack1(GTK_PANED(gtk_builder_get_object(builder, "main_view_pane")), champlain_view, TRUE, TRUE);
379 /* graph */
380 gpx_graph = gpx_graph_new();
381 gtk_paned_pack2(GTK_PANED(gtk_builder_get_object(builder, "main_view_pane")), GTK_WIDGET(gpx_graph),FALSE, TRUE);
382 gtk_paned_set_position(GTK_PANED(gtk_builder_get_object(builder, "main_view_pane")),200);
384 /* show the interface */
385 gtk_widget_show_all(
386 GTK_WIDGET(gtk_builder_get_object(builder, "gpx_viewer_window")));
388 ChamplainView *view = gtk_champlain_embed_get_view (GTK_CHAMPLAIN_EMBED (champlain_view));
389 g_object_set (G_OBJECT (view), "scroll-mode", CHAMPLAIN_SCROLL_MODE_KINETIC,
390 "zoom-level", 5, NULL);
392 interface_map_make_waypoints(view);
394 double lon1= 1000, lon2=-1000,lat1=1000, lat2=-1000;
395 for(fiter = g_list_first(files); fiter; fiter = g_list_next(fiter))
397 GpxFile *file = fiter->data;
398 GpxTrack *track = file->tracks->data;
399 GList *iter = g_list_first(file->tracks);
400 /* Plot all tracks, and get total bounding box */
401 GtkTreeIter liter;
402 GtkTreeModel *model = (GtkTreeModel *)gtk_builder_get_object(builder, "routes_store");
403 while(iter)
405 struct Route *route= g_new0(Route, 1);
406 route->file = file;
407 route->track = iter->data;
408 route->visible = TRUE;
410 /* draw the track */
411 interface_map_plot_route(view, route);
412 if(track->top && track->top->lat_dec < lat1) lat1 = track->top->lat_dec;
413 if(track->top && track->top->lon_dec < lon1) lon1 = track->top->lon_dec;
415 if(track->bottom && track->bottom->lat_dec > lat2) lat2 = track->bottom->lat_dec;
416 if(track->bottom && track->bottom->lon_dec > lon2) lon2 = track->bottom->lon_dec;
418 gtk_list_store_append(GTK_LIST_STORE(model), &liter);
419 gtk_list_store_set(GTK_LIST_STORE(model), &liter, 0, (route->track->name)?route->track->name:"n/a", 1, route, -1);
421 routes = g_list_append(routes, route);
423 iter = g_list_next(iter);
426 /* Set up the zoom widget */
428 GtkWidget *sp = GTK_WIDGET(gtk_builder_get_object(builder, "map_zoom_level"));
429 int current;
430 champlain_view_set_min_zoom_level(view,1);
431 champlain_view_set_max_zoom_level(view,18);
432 current = champlain_view_get_zoom_level(view);
433 gtk_spin_button_set_value(GTK_SPIN_BUTTON(sp), (double)current);
435 g_signal_connect (view, "notify::zoom-level", G_CALLBACK (map_zoom_changed),
436 sp);
438 /* Set up the smooth widget */
440 GtkWidget *sp = GTK_WIDGET(gtk_builder_get_object(builder, "smooth_factor"));
441 int current;
442 current = gpx_graph_get_smooth_factor(gpx_graph);
443 gtk_spin_button_set_value(GTK_SPIN_BUTTON(sp), (double)current);
445 g_signal_connect (gpx_graph, "notify::smooth-factor", G_CALLBACK (smooth_factor_changed),
446 sp);
448 g_signal_connect (gpx_graph, "point-clicked", G_CALLBACK(graph_point_clicked), NULL);
449 gtk_builder_connect_signals(builder,NULL);
450 /* Try to center the track on map correctly */
451 if(lon1 < 1000.0 && lon2 < 1000.0)
453 champlain_view_set_zoom_level(view,8);
454 champlain_view_ensure_visible(view,
455 lat1, lon1,
456 lat2,lon2,
457 FALSE);
459 gtk_combo_box_set_active(GTK_COMBO_BOX(gtk_builder_get_object(builder, "routes_combo")), 0);
462 int main (int argc, char **argv)
465 int i =0;
466 GOptionContext *context = NULL;
467 GError *error = NULL;
468 context = g_option_context_new (_("GPX Viewer"));
471 g_option_context_add_group (context, gtk_get_option_group (TRUE));
472 g_option_context_parse (context, &argc, &argv, &error);
473 g_option_context_free(context);
475 if(error) {
476 g_log(NULL, G_LOG_LEVEL_ERROR, "Failed to parse commandline options: %s", error->message);
477 g_error_free(error);
481 g_thread_init (NULL);
482 gtk_clutter_init (&argc, &argv);
483 /* If no file(s) given, ask for it */
484 if(argc < 2)
486 GtkWidget *dialog;
487 GtkBuilder *fbuilder = gtk_builder_new();
488 /* Show dialog */
489 gchar *path = g_build_filename(DATA_DIR, "gpx-viewer-file-chooser.ui", NULL);
490 if(!gtk_builder_add_from_file(fbuilder,path, NULL)){
491 g_error("Failed to load gpx-viewer.ui");
493 g_free(path);
494 /* update filter */
496 GtkFileFilter *filter = (GtkFileFilter *)gtk_builder_get_object(fbuilder, "gpx_viewer_file_chooser_filter");
497 gtk_file_filter_add_pattern(filter, "*.gpx");
500 dialog = GTK_WIDGET(gtk_builder_get_object(fbuilder, "gpx_viewer_file_chooser"));
501 switch(gtk_dialog_run(GTK_DIALOG(dialog)))
503 case 1:
505 GSList *iter, *choosen_files = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
506 for(iter =choosen_files; iter; iter = g_slist_next(iter))
508 /* Try to open the gpx file */
509 GpxFile *file = gpx_file_new((gchar *)iter->data);
510 files = g_list_append(files, file);
512 g_slist_foreach(choosen_files, (GFunc)g_free, NULL);
513 g_slist_free(choosen_files);
515 break;
517 gtk_widget_destroy(dialog);
518 g_object_unref(fbuilder);
519 if(files == NULL) return EXIT_SUCCESS;
522 for(i =1; i < argc; i++)
524 /* Try to open the gpx file */
525 GpxFile *file = gpx_file_new(argv[i]);
526 files = g_list_append(files, file);
529 create_interface();
532 gtk_main();
533 /* Cleanup office */
534 /* Destroy the files*/
535 g_list_foreach(files, (GFunc)g_object_unref, NULL);
536 g_list_free(files);
538 return EXIT_SUCCESS;