2 * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
4 * Copyright (C) 2003-2005, Evan Battaglia <gtoevan@gmx.net>
5 * Copyright (C) 2005-2007, Alex Foobarian <foobarian@gmail.com>
6 * Copyright (C) 2007-2008, Quy Tonthat <qtonthat@gmail.com>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 #include <glib/gi18n.h>
41 #include "viktrwlayer.h"
42 #include "viktrwlayer_propwin.h"
43 #include "vikwaypoint.h"
48 #include "vikviewport.h" /* ugh */
49 #include "viktreeview.h" /* ugh */
50 #include <gdk-pixbuf/gdk-pixdata.h>
51 #include "viklayer.h" /* ugh */
52 #include "vikaggregatelayer.h"
53 #include "viklayerspanel.h" /* ugh */
55 #define PROPWIN_PROFILE_WIDTH 600
56 #define PROPWIN_PROFILE_HEIGHT 300
58 #define PROPWIN_LABEL_FONT "Sans 7"
60 #define MIN_ALT_DIFF 100.0
61 #define MIN_SPEED_DIFF 5.0
63 typedef struct _propsaved {
68 typedef struct _propwidgets {
69 gboolean configure_dialog;
76 gint profile_width_old;
77 gint profile_height_old;
78 gint profile_width_offset;
79 gint profile_height_offset;
82 GtkWidget *w_track_length;
83 GtkWidget *w_tp_count;
84 GtkWidget *w_segment_count;
85 GtkWidget *w_duptp_count;
86 GtkWidget *w_max_speed;
87 GtkWidget *w_avg_speed;
88 GtkWidget *w_avg_dist;
89 GtkWidget *w_elev_range;
90 GtkWidget *w_elev_gain;
91 GtkWidget *w_time_start;
92 GtkWidget *w_time_end;
93 GtkWidget *w_time_dur;
94 GtkWidget *w_cur_dist; /*< Current distance */
95 GtkWidget *w_cur_elevation;
96 GtkWidget *w_cur_time; /*< Current time */
97 GtkWidget *w_cur_speed;
98 GtkWidget *w_show_dem;
99 GtkWidget *w_show_alt_gps_speed;
100 GtkWidget *w_show_gps_speed;
101 gdouble track_length;
102 PropSaved elev_graph_saved_img;
103 PropSaved speed_graph_saved_img;
105 GtkWidget *speed_box;
107 gdouble min_altitude;
108 gdouble max_altitude;
112 VikTrackpoint *marker_tp;
113 gboolean is_marker_drawn;
114 VikTrackpoint *blob_tp;
115 gboolean is_blob_drawn;
118 static PropWidgets *prop_widgets_new()
120 PropWidgets *widgets = g_malloc0(sizeof(PropWidgets));
125 static void prop_widgets_free(PropWidgets *widgets)
127 if (widgets->elev_graph_saved_img.img)
128 g_object_unref(widgets->elev_graph_saved_img.img);
129 if (widgets->speed_graph_saved_img.img)
130 g_object_unref(widgets->speed_graph_saved_img.img);
131 if (widgets->altitudes)
132 g_free(widgets->altitudes);
134 g_free(widgets->speeds);
138 static void minmax_array(const gdouble *array, gdouble *min, gdouble *max, gboolean NO_ALT_TEST, gint PROFILE_WIDTH)
143 for ( i=0; i < PROFILE_WIDTH; i++ ) {
144 if ( NO_ALT_TEST || (array[i] != VIK_DEFAULT_ALTITUDE) ) {
145 if ( array[i] > *max )
147 if ( array[i] < *min )
155 static VikTrackpoint *set_center_at_graph_position(gdouble event_x,
164 VikTrackpoint *trackpoint;
165 gdouble x = event_x - img_width / 2 + PROFILE_WIDTH / 2 - MARGIN / 2;
168 if (x > PROFILE_WIDTH)
172 trackpoint = vik_track_get_closest_tp_by_percentage_time ( tr, (gdouble) x / PROFILE_WIDTH, NULL );
174 trackpoint = vik_track_get_closest_tp_by_percentage_dist ( tr, (gdouble) x / PROFILE_WIDTH, NULL );
177 VikCoord coord = trackpoint->coord;
179 vik_viewport_set_center_coord ( vik_layers_panel_get_viewport(vlp), &coord );
180 vik_layers_panel_emit_update ( vlp );
183 /* since vlp not set, vvp should be valid instead! */
185 vik_viewport_set_center_coord ( vvp, &coord );
186 vik_layer_emit_update ( VIK_LAYER(vtl) );
193 * Returns whether the marker was drawn or not and whether the blob was drawn or not
195 static void save_image_and_draw_graph_marks (GtkWidget *image,
200 PropSaved *saved_img,
203 gboolean *marker_drawn,
204 gboolean *blob_drawn)
206 GdkPixmap *pix = NULL;
207 /* the pixmap = margin + graph area */
208 gtk_image_get_pixmap(GTK_IMAGE(image), &pix, NULL);
210 /* Restore previously saved image */
211 if (saved_img->saved) {
212 gdk_draw_image(GDK_DRAWABLE(pix), gc, saved_img->img, 0, 0, 0, 0, MARGIN+PROFILE_WIDTH, PROFILE_HEIGHT);
213 saved_img->saved = FALSE;
216 // ATM always save whole image - as anywhere could have changed
218 gdk_drawable_copy_to_image(GDK_DRAWABLE(pix), saved_img->img, 0, 0, 0, 0, MARGIN+PROFILE_WIDTH, PROFILE_HEIGHT);
220 saved_img->img = gdk_drawable_copy_to_image(GDK_DRAWABLE(pix), saved_img->img, 0, 0, 0, 0, MARGIN+PROFILE_WIDTH, PROFILE_HEIGHT);
221 saved_img->saved = TRUE;
223 if ((marker_x >= MARGIN) && (marker_x < (PROFILE_WIDTH + MARGIN))) {
224 gdk_draw_line (GDK_DRAWABLE(pix), gc, marker_x, 0, marker_x, image->allocation.height);
225 *marker_drawn = TRUE;
228 *marker_drawn = FALSE;
230 // Draw a square blob to indicate where we are on track for this graph
231 if ( (blob_x >= MARGIN) && (blob_x < (PROFILE_WIDTH + MARGIN)) && (blob_y < PROFILE_HEIGHT) ) {
232 gdk_draw_rectangle (GDK_DRAWABLE(pix), gc, TRUE, blob_x-3, blob_y-3, 6, 6);
238 // Anywhere on image could have changed
239 if (*marker_drawn || *blob_drawn)
240 gtk_widget_queue_draw(image);
244 * Return the percentage of how far a trackpoint is a long a track via the time method
246 static gdouble tp_percentage_by_time ( VikTrack *tr, VikTrackpoint *trackpoint )
249 if (trackpoint == NULL)
251 time_t t_start, t_end, t_total;
252 t_start = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
253 t_end = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
254 t_total = t_end - t_start;
255 pc = (gdouble)(trackpoint->timestamp - t_start)/t_total;
260 * Return the percentage of how far a trackpoint is a long a track via the distance method
262 static gdouble tp_percentage_by_distance ( VikTrack *tr, VikTrackpoint *trackpoint, gdouble track_length )
265 if (trackpoint == NULL)
269 for (iter = tr->trackpoints->next; iter != NULL; iter = iter->next) {
270 dist += vik_coord_diff(&(VIK_TRACKPOINT(iter->data)->coord),
271 &(VIK_TRACKPOINT(iter->prev->data)->coord));
272 /* Assuming trackpoint is not a copy */
273 if (trackpoint == VIK_TRACKPOINT(iter->data))
277 pc = dist/track_length;
281 static void track_graph_click( GtkWidget *event_box, GdkEventButton *event, gpointer *pass_along, gboolean is_vt_graph )
283 VikTrack *tr = pass_along[0];
284 VikLayersPanel *vlp = pass_along[1];
285 VikViewport *vvp = pass_along[2];
286 PropWidgets *widgets = pass_along[3];
287 GList *child = gtk_container_get_children(GTK_CONTAINER(event_box));
288 GtkWidget *image = GTK_WIDGET(child->data);
289 GtkWidget *window = gtk_widget_get_toplevel(GTK_WIDGET(event_box));
291 VikTrackpoint *trackpoint = set_center_at_graph_position(event->x, event_box->allocation.width, widgets->vtl, vlp, vvp, tr, is_vt_graph, widgets->profile_width);
292 gdouble xm = event->x - event_box->allocation.width/2 + widgets->profile_width/2 + MARGIN/2;
293 save_image_and_draw_graph_marks(image,
295 window->style->black_gc,
296 -1, // Don't draw blob on clicks
298 is_vt_graph ? &widgets->speed_graph_saved_img : &widgets->elev_graph_saved_img,
299 widgets->profile_width,
300 widgets->profile_height,
301 &widgets->is_marker_drawn,
302 &widgets->is_blob_drawn);
304 widgets->marker_tp = trackpoint;
305 gtk_dialog_set_response_sensitive(GTK_DIALOG(widgets->dialog), VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER, widgets->is_marker_drawn);
307 /* draw on the other graph */
308 if (trackpoint == NULL || widgets->elev_box == NULL || widgets->speed_box == NULL)
309 /* This test assumes we have only 2 graphs */
313 GList *other_child = gtk_container_get_children(GTK_CONTAINER(
314 is_vt_graph ? widgets->elev_box : widgets->speed_box));
315 GtkWidget *other_image = GTK_WIDGET(other_child->data);
317 pc = tp_percentage_by_distance ( tr, trackpoint, widgets->track_length );
319 pc = tp_percentage_by_time ( tr, trackpoint );
322 gdouble x2 = pc * widgets->profile_width + MARGIN;
323 save_image_and_draw_graph_marks(other_image,
325 window->style->black_gc,
326 -1, // Don't draw blob on clicks
328 is_vt_graph ? &widgets->elev_graph_saved_img : &widgets->speed_graph_saved_img,
329 widgets->profile_width,
330 widgets->profile_height,
331 &widgets->is_marker_drawn,
332 &widgets->is_blob_drawn);
335 g_list_free(other_child);
339 static gboolean track_profile_click( GtkWidget *event_box, GdkEventButton *event, gpointer *pass_along )
341 track_graph_click(event_box, event, pass_along, FALSE);
342 return TRUE; /* don't call other (further) callbacks */
345 static gboolean track_vt_click( GtkWidget *event_box, GdkEventButton *event, gpointer *pass_along )
347 track_graph_click(event_box, event, pass_along, TRUE);
348 return TRUE; /* don't call other (further) callbacks */
352 * Calculate y position for blob on elevation graph
354 static gint blobby_altitude ( gdouble x_blob, PropWidgets *widgets )
356 gint ix = (gint)x_blob;
357 // Ensure ix is inbounds
358 if (ix == widgets->profile_width)
361 gdouble diff = widgets->max_altitude - widgets->min_altitude;
363 // Ensure no divide by zero
365 y_blob = widgets->profile_height-widgets->profile_height*(widgets->altitudes[ix]-widgets->min_altitude)/(widgets->max_altitude-widgets->min_altitude);
371 * Calculate y position for blob on speed graph
373 static gint blobby_speed ( gdouble x_blob, PropWidgets *widgets )
375 gint ix = (gint)x_blob;
376 // Ensure ix is inbounds
377 if (ix == widgets->profile_width)
380 gdouble diff = widgets->max_speed - widgets->min_speed;
382 // Ensure no divide by zero
384 y_blob = widgets->profile_height-widgets->profile_height*(widgets->speeds[ix]-widgets->min_speed)/(widgets->max_speed-widgets->min_speed);
389 void track_profile_move( GtkWidget *event_box, GdkEventMotion *event, gpointer *pass_along )
391 VikTrack *tr = pass_along[0];
392 PropWidgets *widgets = pass_along[3];
393 int mouse_x, mouse_y;
394 GdkModifierType state;
397 gdk_window_get_pointer (event->window, &mouse_x, &mouse_y, &state);
401 gdouble x = mouse_x - event_box->allocation.width / 2 + widgets->profile_width / 2 - MARGIN / 2;
404 if (x > widgets->profile_width)
405 x = widgets->profile_width;
407 gdouble meters_from_start;
408 VikTrackpoint *trackpoint = vik_track_get_closest_tp_by_percentage_dist ( tr, (gdouble) x / widgets->profile_width, &meters_from_start );
409 if (trackpoint && widgets->w_cur_dist) {
410 static gchar tmp_buf[20];
411 vik_units_distance_t dist_units = a_vik_get_units_distance ();
412 switch (dist_units) {
413 case VIK_UNITS_DISTANCE_KILOMETRES:
414 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km", meters_from_start/1000.0);
416 case VIK_UNITS_DISTANCE_MILES:
417 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f miles", VIK_METERS_TO_MILES(meters_from_start) );
420 g_critical("Houston, we've had a problem. distance=%d", dist_units);
422 gtk_label_set_text(GTK_LABEL(widgets->w_cur_dist), tmp_buf);
425 // Show track elevation for this position - to the nearest whole number
426 if (trackpoint && widgets->w_cur_elevation) {
427 static gchar tmp_buf[20];
428 if (a_vik_get_units_height () == VIK_UNITS_HEIGHT_FEET)
429 g_snprintf(tmp_buf, sizeof(tmp_buf), "%d ft", (int)VIK_METERS_TO_FEET(trackpoint->altitude));
431 g_snprintf(tmp_buf, sizeof(tmp_buf), "%d m", (int)trackpoint->altitude);
432 gtk_label_set_text(GTK_LABEL(widgets->w_cur_elevation), tmp_buf);
435 widgets->blob_tp = trackpoint;
437 if ( widgets->altitudes == NULL )
440 GtkWidget *window = gtk_widget_get_toplevel (event_box);
441 GList *child = gtk_container_get_children(GTK_CONTAINER(event_box));
442 GtkWidget *image = GTK_WIDGET(child->data);
444 gint y_blob = blobby_altitude (x, widgets);
446 gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
447 if (widgets->is_marker_drawn) {
448 gdouble pc = tp_percentage_by_distance ( tr, widgets->marker_tp, widgets->track_length );
450 marker_x = (pc * widgets->profile_width) + MARGIN;
454 save_image_and_draw_graph_marks (image,
456 window->style->black_gc,
459 &widgets->elev_graph_saved_img,
460 widgets->profile_width,
461 widgets->profile_height,
462 &widgets->is_marker_drawn,
463 &widgets->is_blob_drawn);
468 void track_vt_move( GtkWidget *event_box, GdkEventMotion *event, gpointer *pass_along )
470 VikTrack *tr = pass_along[0];
471 PropWidgets *widgets = pass_along[3];
472 int mouse_x, mouse_y;
473 GdkModifierType state;
476 gdk_window_get_pointer (event->window, &mouse_x, &mouse_y, &state);
480 gdouble x = mouse_x - event_box->allocation.width / 2 + widgets->profile_width / 2 - MARGIN / 2;
483 if (x > widgets->profile_width)
484 x = widgets->profile_width;
486 time_t seconds_from_start;
487 VikTrackpoint *trackpoint = vik_track_get_closest_tp_by_percentage_time ( tr, (gdouble) x / widgets->profile_width, &seconds_from_start );
488 if (trackpoint && widgets->w_cur_time) {
489 static gchar tmp_buf[20];
491 h = seconds_from_start/3600;
492 m = (seconds_from_start - h*3600)/60;
493 s = seconds_from_start - (3600*h) - (60*m);
494 g_snprintf(tmp_buf, sizeof(tmp_buf), "%02d:%02d:%02d", h, m, s);
496 gtk_label_set_text(GTK_LABEL(widgets->w_cur_time), tmp_buf);
500 // Ensure ix is inbounds
501 if (ix == widgets->profile_width)
504 // Show track speed for this position
505 if (trackpoint && widgets->w_cur_speed) {
506 static gchar tmp_buf[20];
507 // Even if GPS speed available (trackpoint->speed), the text will correspond to the speed map shown
508 vik_units_speed_t speed_units = a_vik_get_units_speed ();
509 switch (speed_units) {
510 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
511 g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f kph"), VIK_MPS_TO_KPH(widgets->speeds[ix]));
513 case VIK_UNITS_SPEED_MILES_PER_HOUR:
514 g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f mph"), VIK_MPS_TO_MPH(widgets->speeds[ix]));
516 case VIK_UNITS_SPEED_KNOTS:
517 g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f knots"), VIK_MPS_TO_KNOTS(widgets->speeds[ix]));
520 // VIK_UNITS_SPEED_METRES_PER_SECOND:
521 // No need to convert as already in m/s
522 g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f m/s"), widgets->speeds[ix]);
525 gtk_label_set_text(GTK_LABEL(widgets->w_cur_speed), tmp_buf);
528 widgets->blob_tp = trackpoint;
530 if ( widgets->speeds == NULL )
533 GtkWidget *window = gtk_widget_get_toplevel (event_box);
534 GList *child = gtk_container_get_children(GTK_CONTAINER(event_box));
535 GtkWidget *image = GTK_WIDGET(child->data);
537 gint y_blob = blobby_speed (x, widgets);
539 gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
540 if (widgets->is_marker_drawn) {
541 gdouble pc = tp_percentage_by_time ( tr, widgets->marker_tp );
543 marker_x = (pc * widgets->profile_width) + MARGIN;
547 save_image_and_draw_graph_marks (image,
549 window->style->black_gc,
552 &widgets->speed_graph_saved_img,
553 widgets->profile_width,
554 widgets->profile_height,
555 &widgets->is_marker_drawn,
556 &widgets->is_blob_drawn);
562 * Draws DEM points and a respresentative speed on the supplied pixmap
563 * (which is the elevations graph)
565 static void draw_dem_alt_speed_dist(VikTrack *tr,
571 gdouble max_speed_in,
579 gdouble max_speed = 0;
580 gdouble total_length = vik_track_get_length_including_gaps(tr);
582 // Calculate the max speed factor
584 max_speed = max_speed_in * 110 / 100;
587 for (iter = tr->trackpoints->next; iter; iter = iter->next) {
589 dist += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
590 &(VIK_TRACKPOINT(iter->prev->data)->coord) );
591 x = (width * dist)/total_length + margin;
593 gint16 elev = a_dems_get_elev_by_coord(&(VIK_TRACKPOINT(iter->data)->coord), VIK_DEM_INTERPOL_BEST);
595 if ( elev != VIK_DEM_INVALID_ELEVATION ) {
596 int y_alt = height - ((height * elev)/alt_diff);
597 gdk_draw_rectangle(GDK_DRAWABLE(pix), alt_gc, TRUE, x-2, y_alt-2, 4, 4);
601 // This is just a speed indicator - no actual values can be inferred by user
602 if (!isnan(VIK_TRACKPOINT(iter->data)->speed)) {
603 int y_speed = height - (height * VIK_TRACKPOINT(iter->data)->speed)/max_speed;
604 gdk_draw_rectangle(GDK_DRAWABLE(pix), speed_gc, TRUE, x-2, y_speed-2, 4, 4);
611 * Draw just the height profile image
613 static void draw_elevations (GtkWidget *image, VikTrack *tr, PropWidgets *widgets )
626 // Free previous allocation
627 if ( widgets->altitudes )
628 g_free ( widgets->altitudes );
630 widgets->altitudes = vik_track_make_elevation_map ( tr, widgets->profile_width );
632 if ( widgets->altitudes == NULL )
635 minmax_array(widgets->altitudes, &widgets->min_altitude, &widgets->max_altitude, TRUE, widgets->profile_width);
636 widgets->max_altitude = widgets->max_altitude + ((widgets->max_altitude - widgets->min_altitude) * 0.25); // Make visible window a bit bigger than highest point
638 mina = widgets->min_altitude;
639 maxa = widgets->max_altitude;
641 window = gtk_widget_get_toplevel (widgets->elev_box);
643 pix = gdk_pixmap_new( window->window, widgets->profile_width + MARGIN, widgets->profile_height, -1 );
645 gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
647 no_alt_info = gdk_gc_new ( window->window );
648 gdk_color_parse ( "yellow", &color );
649 gdk_gc_set_rgb_fg_color ( no_alt_info, &color);
651 dem_alt_gc = gdk_gc_new ( window->window );
652 gdk_color_parse ( "green", &color );
653 gdk_gc_set_rgb_fg_color ( dem_alt_gc, &color);
655 gps_speed_gc = gdk_gc_new ( window->window );
656 gdk_color_parse ( "red", &color );
657 gdk_gc_set_rgb_fg_color ( gps_speed_gc, &color);
659 /* clear the image */
660 gdk_draw_rectangle(GDK_DRAWABLE(pix), window->style->bg_gc[0],
661 TRUE, 0, 0, MARGIN, widgets->profile_height);
662 gdk_draw_rectangle(GDK_DRAWABLE(pix), window->style->mid_gc[0],
663 TRUE, MARGIN, 0, widgets->profile_width, widgets->profile_height);
666 vik_units_height_t height_units = a_vik_get_units_height ();
667 for (i=0; i<=LINES; i++) {
668 PangoFontDescription *pfd;
669 PangoLayout *pl = gtk_widget_create_pango_layout (GTK_WIDGET(image), NULL);
673 pango_layout_set_alignment (pl, PANGO_ALIGN_RIGHT);
674 pfd = pango_font_description_from_string (PROPWIN_LABEL_FONT);
675 pango_layout_set_font_description (pl, pfd);
676 pango_font_description_free (pfd);
677 switch (height_units) {
678 case VIK_UNITS_HEIGHT_METRES:
679 sprintf(s, "%8dm", (int)(mina + (LINES-i)*(maxa-mina)/LINES));
681 case VIK_UNITS_HEIGHT_FEET:
682 sprintf(s, "%8dft", (int)VIK_METERS_TO_FEET(mina + (LINES-i)*(maxa-mina)/LINES));
686 g_critical("Houston, we've had a problem. height=%d", height_units);
688 pango_layout_set_text(pl, s, -1);
689 pango_layout_get_pixel_size (pl, &w, &h);
690 gdk_draw_layout(GDK_DRAWABLE(pix), window->style->fg_gc[0], MARGIN-w-3,
691 CLAMP((int)i*widgets->profile_height/LINES - h/2, 0, widgets->profile_height-h), pl);
693 gdk_draw_line (GDK_DRAWABLE(pix), window->style->dark_gc[0],
694 MARGIN, widgets->profile_height/LINES * i, MARGIN + widgets->profile_width, widgets->profile_height/LINES * i);
695 g_object_unref ( G_OBJECT ( pl ) );
699 /* draw elevations */
700 for ( i = 0; i < widgets->profile_width; i++ )
701 if ( widgets->altitudes[i] == VIK_DEFAULT_ALTITUDE )
702 gdk_draw_line ( GDK_DRAWABLE(pix), no_alt_info,
703 i + MARGIN, 0, i + MARGIN, widgets->profile_height );
705 gdk_draw_line ( GDK_DRAWABLE(pix), window->style->dark_gc[3],
706 i + MARGIN, widgets->profile_height, i + MARGIN, widgets->profile_height-widgets->profile_height*(widgets->altitudes[i]-mina)/(maxa-mina) );
708 // Ensure somekind of max speed when not set
709 if ( widgets->max_speed < 0.01 )
710 widgets->max_speed = vik_track_get_max_speed(tr);
712 draw_dem_alt_speed_dist(tr,
719 widgets->profile_width,
720 widgets->profile_height,
722 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_dem)),
723 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_alt_gps_speed)));
726 gdk_draw_rectangle(GDK_DRAWABLE(pix), window->style->black_gc, FALSE, MARGIN, 0, widgets->profile_width-1, widgets->profile_height-1);
728 g_object_unref ( G_OBJECT(pix) );
729 g_object_unref ( G_OBJECT(no_alt_info) );
730 g_object_unref ( G_OBJECT(dem_alt_gc) );
731 g_object_unref ( G_OBJECT(gps_speed_gc) );
736 * Draw just the speed (velocity)/time image
738 static void draw_vt ( GtkWidget *image, VikTrack *tr, PropWidgets *widgets)
745 // Free previous allocation
746 if ( widgets->speeds )
747 g_free ( widgets->speeds );
749 widgets->speeds = vik_track_make_speed_map ( tr, widgets->profile_width );
750 if ( widgets->speeds == NULL )
756 window = gtk_widget_get_toplevel (widgets->speed_box);
758 pix = gdk_pixmap_new( window->window, widgets->profile_width + MARGIN, widgets->profile_height, -1 );
760 gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
762 gps_speed_gc = gdk_gc_new ( window->window );
763 gdk_color_parse ( "red", &color );
764 gdk_gc_set_rgb_fg_color ( gps_speed_gc, &color);
766 minmax_array(widgets->speeds, &widgets->min_speed, &widgets->max_speed, FALSE, widgets->profile_width);
767 if (widgets->min_speed < 0.0)
768 widgets->min_speed = 0; /* splines sometimes give negative speeds */
769 widgets->max_speed = widgets->max_speed + ((widgets->max_speed - widgets->min_speed) * 0.1);
770 if (widgets->max_speed-widgets->min_speed < MIN_SPEED_DIFF) {
771 widgets->max_speed = widgets->min_speed + MIN_SPEED_DIFF;
774 mins = widgets->min_speed;
775 maxs = widgets->max_speed;
777 /* clear the image */
778 gdk_draw_rectangle(GDK_DRAWABLE(pix), window->style->bg_gc[0],
779 TRUE, 0, 0, MARGIN, widgets->profile_height);
780 gdk_draw_rectangle(GDK_DRAWABLE(pix), window->style->mid_gc[0],
781 TRUE, MARGIN, 0, widgets->profile_width, widgets->profile_height);
784 for (i=0; i<=LINES; i++) {
785 PangoFontDescription *pfd;
786 PangoLayout *pl = gtk_widget_create_pango_layout (GTK_WIDGET(image), NULL);
790 pango_layout_set_alignment (pl, PANGO_ALIGN_RIGHT);
791 pfd = pango_font_description_from_string (PROPWIN_LABEL_FONT);
792 pango_layout_set_font_description (pl, pfd);
793 pango_font_description_free (pfd);
794 vik_units_speed_t speed_units = a_vik_get_units_speed ();
795 switch (speed_units) {
796 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
797 sprintf(s, "%6.1fkm/h", VIK_MPS_TO_KPH(mins + (LINES-i)*(maxs-mins)/LINES));
799 case VIK_UNITS_SPEED_MILES_PER_HOUR:
800 sprintf(s, "%6.1fmph", VIK_MPS_TO_MPH(mins + (LINES-i)*(maxs-mins)/LINES));
802 case VIK_UNITS_SPEED_METRES_PER_SECOND:
803 sprintf(s, "%8dm/s", (int)(mins + (LINES-i)*(maxs-mins)/LINES));
805 case VIK_UNITS_SPEED_KNOTS:
806 sprintf(s, "%6.1fknots", VIK_MPS_TO_KNOTS(mins + (LINES-i)*(maxs-mins)/LINES));
810 g_critical("Houston, we've had a problem. speed=%d", speed_units);
813 pango_layout_set_text(pl, s, -1);
814 pango_layout_get_pixel_size (pl, &w, &h);
815 gdk_draw_layout(GDK_DRAWABLE(pix), window->style->fg_gc[0], MARGIN-w-3,
816 CLAMP((int)i*widgets->profile_height/LINES - h/2, 0, widgets->profile_height-h), pl);
818 gdk_draw_line (GDK_DRAWABLE(pix), window->style->dark_gc[0],
819 MARGIN, widgets->profile_height/LINES * i, MARGIN + widgets->profile_width, widgets->profile_height/LINES * i);
820 g_object_unref ( G_OBJECT ( pl ) );
826 for ( i = 0; i < widgets->profile_width; i++ )
827 gdk_draw_line ( GDK_DRAWABLE(pix), window->style->dark_gc[3],
828 i + MARGIN, widgets->profile_height, i + MARGIN, widgets->profile_height-widgets->profile_height*(widgets->speeds[i]-mins)/(maxs-mins) );
831 time_t beg_time = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
832 time_t dur = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp - beg_time;
834 if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->w_show_gps_speed)) ) {
836 for (iter = tr->trackpoints; iter; iter = iter->next) {
837 gdouble gps_speed = VIK_TRACKPOINT(iter->data)->speed;
838 if (isnan(gps_speed))
840 int x = MARGIN + widgets->profile_width * (VIK_TRACKPOINT(iter->data)->timestamp - beg_time) / dur;
841 int y = widgets->profile_height - widgets->profile_height*(gps_speed - mins)/(maxs - mins);
842 gdk_draw_rectangle(GDK_DRAWABLE(pix), gps_speed_gc, TRUE, x-2, y-2, 4, 4);
847 gdk_draw_rectangle(GDK_DRAWABLE(pix), window->style->black_gc, FALSE, MARGIN, 0, widgets->profile_width-1, widgets->profile_height-1);
849 g_object_unref ( G_OBJECT(pix) );
850 g_object_unref ( G_OBJECT(gps_speed_gc) );
858 static void draw_all_graphs ( GtkWidget *widget, gpointer *pass_along, gboolean resized )
860 VikTrack *tr = pass_along[0];
861 PropWidgets *widgets = pass_along[3];
863 // Draw graphs even if they are not visible
866 GtkWidget *image = NULL;
867 GtkWidget *window = gtk_widget_get_toplevel(widget);
869 gdouble pc_blob = NAN;
872 if (widgets->elev_box != NULL) {
874 // Saved image no longer any good as we've resized, so we remove it here
875 if (resized && widgets->elev_graph_saved_img.img) {
876 g_object_unref(widgets->elev_graph_saved_img.img);
877 widgets->elev_graph_saved_img.img = NULL;
878 widgets->elev_graph_saved_img.saved = FALSE;
881 child = gtk_container_get_children(GTK_CONTAINER(widgets->elev_box));
882 draw_elevations (GTK_WIDGET(child->data), tr, widgets );
884 image = GTK_WIDGET(child->data);
887 // Ensure marker or blob are redrawn if necessary
888 if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
890 pc = tp_percentage_by_distance ( tr, widgets->marker_tp, widgets->track_length );
891 gdouble x_blob = -MARGIN - 1.0; // i.e. Don't draw unless we get a valid value
893 if (widgets->is_blob_drawn) {
894 pc_blob = tp_percentage_by_distance ( tr, widgets->blob_tp, widgets->track_length );
895 if (!isnan(pc_blob)) {
896 x_blob = (pc_blob * widgets->profile_width);
898 y_blob = blobby_altitude (x_blob, widgets);
901 gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
903 marker_x = (pc * widgets->profile_width) + MARGIN;
906 save_image_and_draw_graph_marks (image,
908 window->style->black_gc,
911 &widgets->elev_graph_saved_img,
912 widgets->profile_width,
913 widgets->profile_height,
914 &widgets->is_marker_drawn,
915 &widgets->is_blob_drawn);
920 if (widgets->speed_box != NULL) {
922 // Saved image no longer any good as we've resized
923 if (resized && widgets->speed_graph_saved_img.img) {
924 g_object_unref(widgets->speed_graph_saved_img.img);
925 widgets->speed_graph_saved_img.img = NULL;
926 widgets->speed_graph_saved_img.saved = FALSE;
929 child = gtk_container_get_children(GTK_CONTAINER(widgets->speed_box));
930 draw_vt (GTK_WIDGET(child->data), tr, widgets );
932 image = GTK_WIDGET(child->data);
935 // Ensure marker or blob are redrawn if necessary
936 if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
938 pc = tp_percentage_by_time ( tr, widgets->marker_tp );
940 gdouble x_blob = -MARGIN - 1.0; // i.e. Don't draw unless we get a valid value
942 if (widgets->is_blob_drawn) {
943 pc_blob = tp_percentage_by_time ( tr, widgets->blob_tp );
944 if (!isnan(pc_blob)) {
945 x_blob = (pc_blob * widgets->profile_width);
948 y_blob = blobby_speed (x_blob, widgets);
951 gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
953 marker_x = (pc * widgets->profile_width) + MARGIN;
956 save_image_and_draw_graph_marks (image,
958 window->style->black_gc,
961 &widgets->speed_graph_saved_img,
962 widgets->profile_width,
963 widgets->profile_height,
964 &widgets->is_marker_drawn,
965 &widgets->is_blob_drawn);
971 * Configure/Resize the profile & speed/time images
973 static gboolean configure_event ( GtkWidget *widget, GdkEventConfigure *event, gpointer *pass_along )
975 PropWidgets *widgets = pass_along[3];
977 if (widgets->configure_dialog) {
978 // Determine size offsets between dialog size and size for images
979 // Only on the initialisation of the dialog
980 widgets->profile_width_offset = event->width - widgets->profile_width;
981 widgets->profile_height_offset = event->height - widgets->profile_height;
982 widgets->configure_dialog = FALSE;
984 // Without this the settting, the dialog will only grow in vertical size - one can not then make it smaller!
985 gtk_widget_set_size_request ( widget, widgets->profile_width+widgets->profile_width_offset, widgets->profile_height );
986 // In fact this allows one to compress it a bit more vertically as I don't add on the height offset
989 widgets->profile_width_old = widgets->profile_width;
990 widgets->profile_height_old = widgets->profile_height;
993 // Now adjust From Dialog size to get image size
994 widgets->profile_width = event->width - widgets->profile_width_offset;
995 widgets->profile_height = event->height - widgets->profile_height_offset;
997 // ATM we receive configure_events when the dialog is moved and so no further action is necessary
998 if ( !widgets->configure_dialog &&
999 (widgets->profile_width_old == widgets->profile_width) && (widgets->profile_height_old == widgets->profile_height) )
1003 draw_all_graphs ( widget, pass_along, TRUE );
1009 * Create height profile widgets including the image and callbacks
1011 GtkWidget *vik_trw_layer_create_profile ( GtkWidget *window, VikTrack *tr, gpointer vlp, VikViewport *vvp, PropWidgets *widgets, gdouble *min_alt, gdouble *max_alt)
1015 GtkWidget *eventbox;
1016 gpointer *pass_along;
1019 widgets->altitudes = vik_track_make_elevation_map ( tr, widgets->profile_width );
1021 if ( widgets->altitudes == NULL ) {
1022 *min_alt = *max_alt = VIK_DEFAULT_ALTITUDE;
1026 minmax_array(widgets->altitudes, min_alt, max_alt, TRUE, widgets->profile_width);
1028 pix = gdk_pixmap_new( window->window, widgets->profile_width + MARGIN, widgets->profile_height, -1 );
1029 image = gtk_image_new_from_pixmap ( pix, NULL );
1031 g_object_unref ( G_OBJECT(pix) );
1033 pass_along = g_malloc ( sizeof(gpointer) * 4 ); /* FIXME: mem leak -- never be freed */
1035 pass_along[1] = vlp;
1036 pass_along[2] = vvp;
1037 pass_along[3] = widgets;
1039 eventbox = gtk_event_box_new ();
1040 g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_profile_click), pass_along );
1041 g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_profile_move), pass_along );
1042 g_signal_connect_swapped ( G_OBJECT(eventbox), "destroy", G_CALLBACK(g_free), pass_along );
1043 gtk_container_add ( GTK_CONTAINER(eventbox), image );
1044 gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_STRUCTURE_MASK);
1050 * Create speed/time widgets including the image and callbacks
1052 GtkWidget *vik_trw_layer_create_vtdiag ( GtkWidget *window, VikTrack *tr, gpointer vlp, VikViewport *vvp, PropWidgets *widgets)
1056 GtkWidget *eventbox;
1057 gpointer *pass_along;
1060 widgets->speeds = vik_track_make_speed_map ( tr, widgets->profile_width );
1061 if ( widgets->speeds == NULL )
1064 pass_along = g_malloc ( sizeof(gpointer) * 4 ); /* FIXME: mem leak -- never be freed */
1066 pass_along[1] = vlp;
1067 pass_along[2] = vvp;
1068 pass_along[3] = widgets;
1070 pix = gdk_pixmap_new( window->window, widgets->profile_width + MARGIN, widgets->profile_height, -1 );
1071 image = gtk_image_new_from_pixmap ( pix, NULL );
1074 /* XXX this can go out, it's just a helpful dev tool */
1077 GdkGC **colors[8] = { window->style->bg_gc,
1078 window->style->fg_gc,
1079 window->style->light_gc,
1080 window->style->dark_gc,
1081 window->style->mid_gc,
1082 window->style->text_gc,
1083 window->style->base_gc,
1084 window->style->text_aa_gc };
1085 for (i=0; i<5; i++) {
1086 for (j=0; j<8; j++) {
1087 gdk_draw_rectangle(GDK_DRAWABLE(pix), colors[j][i],
1088 TRUE, i*20, j*20, 20, 20);
1089 gdk_draw_rectangle(GDK_DRAWABLE(pix), window->style->black_gc,
1090 FALSE, i*20, j*20, 20, 20);
1096 g_object_unref ( G_OBJECT(pix) );
1098 eventbox = gtk_event_box_new ();
1099 g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_vt_click), pass_along );
1100 g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_vt_move), pass_along );
1101 g_signal_connect_swapped ( G_OBJECT(eventbox), "destroy", G_CALLBACK(g_free), pass_along );
1102 gtk_container_add ( GTK_CONTAINER(eventbox), image );
1103 gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
1109 static void propwin_response_cb( GtkDialog *dialog, gint resp, PropWidgets *widgets)
1111 VikTrack *tr = widgets->tr;
1112 VikTrwLayer *vtl = widgets->vtl;
1113 gboolean keep_dialog = FALSE;
1115 /* FIXME: check and make sure the track still exists before doing anything to it */
1116 /* Note: destroying diaglog (eg, parent window exit) won't give "response" */
1118 case GTK_RESPONSE_DELETE_EVENT: /* received delete event (not from buttons) */
1119 case GTK_RESPONSE_REJECT:
1121 case GTK_RESPONSE_ACCEPT:
1122 vik_track_set_comment(tr, gtk_entry_get_text(GTK_ENTRY(widgets->w_comment)));
1124 case VIK_TRW_LAYER_PROPWIN_REVERSE:
1125 vik_track_reverse(tr);
1126 vik_layer_emit_update ( VIK_LAYER(vtl) );
1128 case VIK_TRW_LAYER_PROPWIN_DEL_DUP:
1129 vik_track_remove_dup_points(tr);
1130 /* above operation could have deleted current_tp or last_tp */
1131 trw_layer_cancel_tps_of_track ( vtl, widgets->track_name );
1132 vik_layer_emit_update ( VIK_LAYER(vtl) );
1134 case VIK_TRW_LAYER_PROPWIN_SPLIT:
1136 /* get new tracks, add them, resolve naming conflicts (free if cancel), and delete old. old can still exist on clipboard. */
1138 VikTrack **tracks = vik_track_split_into_segments(tr, &ntracks);
1141 for ( i = 0; i < ntracks; i++ )
1143 g_assert ( tracks[i] );
1144 new_tr_name = g_strdup_printf("%s #%d", widgets->track_name, i+1);
1145 /* if ( (wp_exists) && (! overwrite) ) */
1146 /* don't need to upper case new_tr_name because old tr name was uppercase */
1147 if ( vik_trw_layer_get_track(vtl, new_tr_name ) &&
1148 ( ! a_dialog_yes_or_no ( VIK_GTK_WINDOW_FROM_LAYER(vtl), "The track \"%s\" exists, do you wish to overwrite it?", new_tr_name ) ) )
1150 gchar *new_new_tr_name = a_dialog_new_track ( VIK_GTK_WINDOW_FROM_LAYER(vtl), vik_trw_layer_get_tracks(vtl), NULL );
1151 g_free ( new_tr_name );
1152 if (new_new_tr_name)
1153 new_tr_name = new_new_tr_name;
1157 vik_track_free ( tracks[i] );
1161 vik_trw_layer_add_track ( vtl, new_tr_name, tracks[i] );
1166 /* Don't let track destroy this dialog */
1167 vik_track_clear_property_dialog(tr);
1168 vik_trw_layer_delete_track ( vtl, widgets->track_name );
1169 vik_layer_emit_update ( VIK_LAYER(vtl) ); /* chase thru the hoops */
1173 case VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER:
1175 GList *iter = tr->trackpoints;
1176 while ((iter = iter->next)) {
1177 if (widgets->marker_tp == VIK_TRACKPOINT(iter->data))
1181 a_dialog_msg(VIK_GTK_WINDOW_FROM_LAYER(vtl), GTK_MESSAGE_ERROR,
1182 _("Failed spliting track. Track unchanged"), NULL);
1187 gchar *r_name = g_strdup_printf("%s #R", widgets->track_name);
1188 if (vik_trw_layer_get_track(vtl, r_name ) &&
1189 ( ! a_dialog_yes_or_no( VIK_GTK_WINDOW_FROM_LAYER(vtl),
1190 "The track \"%s\" exists, do you wish to overwrite it?", r_name)))
1192 gchar *new_r_name = a_dialog_new_track( VIK_GTK_WINDOW_FROM_LAYER(vtl), vik_trw_layer_get_tracks(vtl), NULL );
1195 r_name = new_r_name;
1198 a_dialog_msg(VIK_GTK_WINDOW_FROM_LAYER(vtl), GTK_MESSAGE_WARNING,
1199 _("Operation Aborted. Track unchanged"), NULL);
1204 iter->prev->next = NULL;
1206 VikTrack *tr_right = vik_track_new();
1208 vik_track_set_comment ( tr_right, tr->comment );
1209 tr_right->visible = tr->visible;
1210 tr_right->trackpoints = iter;
1212 vik_trw_layer_add_track(vtl, r_name, tr_right);
1213 vik_layer_emit_update ( VIK_LAYER(vtl) );
1217 fprintf(stderr, "DEBUG: unknown response\n");
1221 /* Keep same behaviour for now: destroy dialog if click on any button */
1223 prop_widgets_free(widgets);
1224 vik_track_clear_property_dialog(tr);
1225 gtk_widget_destroy ( GTK_WIDGET(dialog) );
1230 * Force a redraw when checkbutton has been toggled to show/hide that information
1232 static void checkbutton_toggle_cb ( GtkToggleButton *togglebutton, gpointer *pass_along, gpointer dummy)
1234 PropWidgets *widgets = pass_along[3];
1235 // Even though not resized, we'll pretend it is -
1236 // as this invalidates the saved images (since the image may have changed)
1237 draw_all_graphs ( widgets->dialog, pass_along, TRUE);
1241 * Create the widgets for the given graph tab
1243 static GtkWidget *create_graph_page ( GtkWidget *graph,
1244 const gchar *markup,
1246 const gchar *markup2,
1248 GtkWidget *checkbutton1,
1249 gboolean checkbutton1_default,
1250 GtkWidget *checkbutton2,
1251 gboolean checkbutton2_default )
1253 GtkWidget *hbox = gtk_hbox_new ( FALSE, 10 );
1254 GtkWidget *vbox = gtk_vbox_new ( FALSE, 10 );
1255 GtkWidget *label = gtk_label_new (NULL);
1256 GtkWidget *label2 = gtk_label_new (NULL);
1257 gtk_box_pack_start (GTK_BOX(vbox), graph, FALSE, FALSE, 0);
1258 gtk_label_set_markup ( GTK_LABEL(label), markup );
1259 gtk_label_set_markup ( GTK_LABEL(label2), markup2 );
1260 gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, FALSE, 0);
1261 gtk_box_pack_start (GTK_BOX(hbox), value, FALSE, FALSE, 0);
1262 gtk_box_pack_start (GTK_BOX(hbox), label2, FALSE, FALSE, 0);
1263 gtk_box_pack_start (GTK_BOX(hbox), value2, FALSE, FALSE, 0);
1265 gtk_box_pack_end (GTK_BOX(hbox), checkbutton2, FALSE, FALSE, 0);
1266 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(checkbutton2), checkbutton2_default);
1269 gtk_box_pack_end (GTK_BOX(hbox), checkbutton1, FALSE, FALSE, 0);
1270 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(checkbutton1), checkbutton1_default);
1272 gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1277 void vik_trw_layer_propwin_run ( GtkWindow *parent, VikTrwLayer *vtl, VikTrack *tr, gpointer vlp, gchar *track_name, VikViewport *vvp )
1279 /* FIXME: free widgets when destroy signal received */
1280 PropWidgets *widgets = prop_widgets_new();
1284 widgets->profile_width = PROPWIN_PROFILE_WIDTH;
1285 widgets->profile_height = PROPWIN_PROFILE_HEIGHT;
1286 widgets->track_name = track_name;
1287 gchar *title = g_strdup_printf(_("%s - Track Properties"), track_name);
1288 GtkWidget *dialog = gtk_dialog_new_with_buttons (title,
1290 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
1291 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1292 _("Split at _Marker"), VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER,
1293 _("Split _Segments"), VIK_TRW_LAYER_PROPWIN_SPLIT,
1294 _("_Reverse"), VIK_TRW_LAYER_PROPWIN_REVERSE,
1295 _("_Delete Dupl."), VIK_TRW_LAYER_PROPWIN_DEL_DUP,
1296 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1298 widgets->dialog = dialog;
1300 g_signal_connect(dialog, "response", G_CALLBACK(propwin_response_cb), widgets);
1303 guint32 tp_count, seg_count;
1305 gdouble min_alt, max_alt;
1306 widgets->elev_box = vik_trw_layer_create_profile(GTK_WIDGET(parent), tr, vlp, vvp, widgets, &min_alt, &max_alt);
1307 widgets->speed_box = vik_trw_layer_create_vtdiag(GTK_WIDGET(parent), tr, vlp, vvp, widgets);
1308 GtkWidget *graphs = gtk_notebook_new();
1310 GtkWidget *content[20];
1314 static gchar *label_texts[] = { N_("<b>Comment:</b>"), N_("<b>Track Length:</b>"), N_("<b>Trackpoints:</b>"), N_("<b>Segments:</b>"), N_("<b>Duplicate Points:</b>"), N_("<b>Max Speed:</b>"), N_("<b>Avg. Speed:</b>"), N_("<b>Avg. Dist. Between TPs:</b>"), N_("<b>Elevation Range:</b>"), N_("<b>Total Elevation Gain/Loss:</b>"), N_("<b>Start:</b>"), N_("<b>End:</b>"), N_("<b>Duration:</b>") };
1315 static gchar tmp_buf[50];
1319 widgets->w_comment = gtk_entry_new ();
1321 gtk_entry_set_text ( GTK_ENTRY(widgets->w_comment), tr->comment );
1322 g_signal_connect_swapped ( widgets->w_comment, "activate", G_CALLBACK(a_dialog_response_accept), GTK_DIALOG(dialog) );
1323 content[cnt++] = widgets->w_comment;
1325 vik_units_distance_t dist_units = a_vik_get_units_distance ();
1327 tr_len = widgets->track_length = vik_track_get_length(tr);
1328 switch (dist_units) {
1329 case VIK_UNITS_DISTANCE_KILOMETRES:
1330 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km", tr_len/1000.0 );
1332 case VIK_UNITS_DISTANCE_MILES:
1333 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f miles", VIK_METERS_TO_MILES(tr_len) );
1336 g_critical("Houston, we've had a problem. distance=%d", dist_units);
1338 widgets->w_track_length = content[cnt++] = gtk_label_new ( tmp_buf );
1340 tp_count = vik_track_get_tp_count(tr);
1341 g_snprintf(tmp_buf, sizeof(tmp_buf), "%u", tp_count );
1342 widgets->w_tp_count = content[cnt++] = gtk_label_new ( tmp_buf );
1344 seg_count = vik_track_get_segment_count(tr) ;
1345 g_snprintf(tmp_buf, sizeof(tmp_buf), "%u", seg_count );
1346 widgets->w_segment_count = content[cnt++] = gtk_label_new ( tmp_buf );
1348 g_snprintf(tmp_buf, sizeof(tmp_buf), "%lu", vik_track_get_dup_point_count(tr) );
1349 widgets->w_duptp_count = content[cnt++] = gtk_label_new ( tmp_buf );
1351 vik_units_speed_t speed_units = a_vik_get_units_speed ();
1352 tmp_speed = vik_track_get_max_speed(tr);
1353 if ( tmp_speed == 0 )
1354 g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
1356 switch (speed_units) {
1357 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
1358 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km/h", VIK_MPS_TO_KPH(tmp_speed));
1360 case VIK_UNITS_SPEED_MILES_PER_HOUR:
1361 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f mph", VIK_MPS_TO_MPH(tmp_speed));
1363 case VIK_UNITS_SPEED_METRES_PER_SECOND:
1364 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f m/s", tmp_speed );
1366 case VIK_UNITS_SPEED_KNOTS:
1367 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f knots", VIK_MPS_TO_KNOTS(tmp_speed));
1370 g_snprintf (tmp_buf, sizeof(tmp_buf), "--" );
1371 g_critical("Houston, we've had a problem. speed=%d", speed_units);
1374 widgets->w_max_speed = content[cnt++] = gtk_label_new ( tmp_buf );
1376 tmp_speed = vik_track_get_average_speed(tr);
1377 if ( tmp_speed == 0 )
1378 g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
1380 switch (speed_units) {
1381 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
1382 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km/h", VIK_MPS_TO_KPH(tmp_speed));
1384 case VIK_UNITS_SPEED_MILES_PER_HOUR:
1385 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f mph", VIK_MPS_TO_MPH(tmp_speed));
1387 case VIK_UNITS_SPEED_METRES_PER_SECOND:
1388 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f m/s", tmp_speed );
1390 case VIK_UNITS_SPEED_KNOTS:
1391 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f knots", VIK_MPS_TO_KNOTS(tmp_speed));
1394 g_snprintf (tmp_buf, sizeof(tmp_buf), "--" );
1395 g_critical("Houston, we've had a problem. speed=%d", speed_units);
1398 widgets->w_avg_speed = content[cnt++] = gtk_label_new ( tmp_buf );
1400 switch (dist_units) {
1401 case VIK_UNITS_DISTANCE_KILOMETRES:
1402 // Even though kilometres, the average distance between points is going to be quite small so keep in metres
1403 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f m", (tp_count - seg_count) == 0 ? 0 : tr_len / ( tp_count - seg_count ) );
1405 case VIK_UNITS_DISTANCE_MILES:
1406 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.3f miles", (tp_count - seg_count) == 0 ? 0 : VIK_METERS_TO_MILES(tr_len / ( tp_count - seg_count )) );
1409 g_critical("Houston, we've had a problem. distance=%d", dist_units);
1411 widgets->w_avg_dist = content[cnt++] = gtk_label_new ( tmp_buf );
1413 vik_units_height_t height_units = a_vik_get_units_height ();
1414 if ( min_alt == VIK_DEFAULT_ALTITUDE )
1415 g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
1417 switch (height_units) {
1418 case VIK_UNITS_HEIGHT_METRES:
1419 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.0f m - %.0f m", min_alt, max_alt );
1421 case VIK_UNITS_HEIGHT_FEET:
1422 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.0f feet - %.0f feet", VIK_METERS_TO_FEET(min_alt), VIK_METERS_TO_FEET(max_alt) );
1425 g_snprintf(tmp_buf, sizeof(tmp_buf), "--" );
1426 g_critical("Houston, we've had a problem. height=%d", height_units);
1429 widgets->w_elev_range = content[cnt++] = gtk_label_new ( tmp_buf );
1431 vik_track_get_total_elevation_gain(tr, &max_alt, &min_alt );
1432 if ( min_alt == VIK_DEFAULT_ALTITUDE )
1433 g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
1435 switch (height_units) {
1436 case VIK_UNITS_HEIGHT_METRES:
1437 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.0f m / %.0f m", max_alt, min_alt );
1439 case VIK_UNITS_HEIGHT_FEET:
1440 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.0f feet / %.0f feet", VIK_METERS_TO_FEET(max_alt), VIK_METERS_TO_FEET(min_alt) );
1443 g_snprintf(tmp_buf, sizeof(tmp_buf), "--" );
1444 g_critical("Houston, we've had a problem. height=%d", height_units);
1447 widgets->w_elev_gain = content[cnt++] = gtk_label_new ( tmp_buf );
1450 #define PACK(w) gtk_box_pack_start (GTK_BOX(right_vbox), w, FALSE, FALSE, 0);
1451 gtk_box_pack_start (GTK_BOX(right_vbox), e_cmt, FALSE, FALSE, 0);
1464 if ( tr->trackpoints && VIK_TRACKPOINT(tr->trackpoints->data)->timestamp )
1467 t1 = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
1468 t2 = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
1470 strncpy(tmp_buf, ctime(&t1), sizeof(tmp_buf));
1471 tmp_buf[sizeof(tmp_buf)-1] = 0;
1472 g_strchomp(tmp_buf);
1473 widgets->w_time_start = content[cnt++] = gtk_label_new(tmp_buf);
1475 strncpy(tmp_buf, ctime(&t2), sizeof(tmp_buf));
1476 tmp_buf[sizeof(tmp_buf)-1] = 0;
1477 g_strchomp(tmp_buf);
1478 widgets->w_time_end = content[cnt++] = gtk_label_new(tmp_buf);
1480 g_snprintf(tmp_buf, sizeof(tmp_buf), _("%d minutes"), (int)(t2-t1)/60);
1481 widgets->w_time_dur = content[cnt++] = gtk_label_new(tmp_buf);
1483 widgets->w_time_start = content[cnt++] = gtk_label_new(_("No Data"));
1484 widgets->w_time_end = content[cnt++] = gtk_label_new(_("No Data"));
1485 widgets->w_time_dur = content[cnt++] = gtk_label_new(_("No Data"));
1488 table = GTK_TABLE(gtk_table_new (cnt, 2, FALSE));
1489 gtk_table_set_col_spacing (table, 0, 10);
1490 for (i=0; i<cnt; i++) {
1493 // Settings so the text positioning only moves around vertically when the dialog is resized
1494 // This also gives more room to see the track comment
1495 label = gtk_label_new(NULL);
1496 gtk_misc_set_alignment ( GTK_MISC(label), 1, 0.5 ); // Position text centrally in vertical plane
1497 gtk_label_set_markup ( GTK_LABEL(label), _(label_texts[i]) );
1498 gtk_table_attach ( table, label, 0, 1, i, i+1, GTK_FILL, GTK_SHRINK, 0, 0 );
1499 if (GTK_IS_MISC(content[i])) {
1500 gtk_misc_set_alignment ( GTK_MISC(content[i]), 0, 0.5 );
1502 gtk_table_attach_defaults ( table, content[i], 1, 2, i, i+1 );
1505 gtk_notebook_append_page(GTK_NOTEBOOK(graphs), GTK_WIDGET(table), gtk_label_new(_("Statistics")));
1507 gpointer *pass_along;
1508 pass_along = g_malloc ( sizeof(gpointer) * 4 ); /* FIXME: mem leak -- never be freed */
1510 pass_along[1] = vlp;
1511 pass_along[2] = vvp;
1512 pass_along[3] = widgets;
1514 if ( widgets->elev_box ) {
1515 GtkWidget *page = NULL;
1516 widgets->w_cur_dist = gtk_label_new(_("No Data"));
1517 widgets->w_cur_elevation = gtk_label_new(_("No Data"));
1518 widgets->w_show_dem = gtk_check_button_new_with_mnemonic(_("Show D_EM"));
1519 widgets->w_show_alt_gps_speed = gtk_check_button_new_with_mnemonic(_("Show _GPS Speed"));
1520 page = create_graph_page (widgets->elev_box,
1521 _("<b>Track Distance:</b>"), widgets->w_cur_dist,
1522 _("<b>Track Height:</b>"), widgets->w_cur_elevation,
1523 widgets->w_show_dem, TRUE,
1524 widgets->w_show_alt_gps_speed, TRUE);
1525 g_signal_connect (widgets->w_show_dem, "toggled", G_CALLBACK (checkbutton_toggle_cb), pass_along);
1526 g_signal_connect (widgets->w_show_alt_gps_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), pass_along);
1527 gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Elevation-distance")));
1530 if ( widgets->speed_box ) {
1531 GtkWidget *page = NULL;
1532 widgets->w_cur_time = gtk_label_new(_("No Data"));
1533 widgets->w_cur_speed = gtk_label_new(_("No Data"));
1534 widgets->w_show_gps_speed = gtk_check_button_new_with_mnemonic(_("Show _GPS Speed"));
1535 page = create_graph_page (widgets->speed_box,
1536 _("<b>Track Time:</b>"), widgets->w_cur_time,
1537 _("<b>Track Speed:</b>"), widgets->w_cur_speed,
1538 widgets->w_show_gps_speed, TRUE,
1540 g_signal_connect (widgets->w_show_gps_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), pass_along);
1541 gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Speed-time")));
1544 gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), graphs, FALSE, FALSE, 0);
1546 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER, FALSE);
1548 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), VIK_TRW_LAYER_PROPWIN_SPLIT, FALSE);
1549 if (vik_track_get_dup_point_count(tr) <= 0)
1550 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), VIK_TRW_LAYER_PROPWIN_DEL_DUP, FALSE);
1552 // On dialog realization configure_event casues the graphs to be initially drawn
1553 widgets->configure_dialog = TRUE;
1554 g_signal_connect ( G_OBJECT(dialog), "configure-event", G_CALLBACK (configure_event), pass_along);
1556 vik_track_set_property_dialog(tr, dialog);
1557 gtk_dialog_set_default_response ( GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT );
1558 gtk_widget_show_all ( dialog );