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>
7 * Copyright (C) 2012-2014, Rob Norris <rw_norris@hotmail.com>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
34 #include <glib/gi18n.h>
39 #include "viktrwlayer.h"
40 #include "viktrwlayer_propwin.h"
43 #include "vikviewport.h" /* ugh */
46 #include <gdk-pixbuf/gdk-pixdata.h>
49 PROPWIN_GRAPH_TYPE_ELEVATION_DISTANCE,
50 PROPWIN_GRAPH_TYPE_GRADIENT_DISTANCE,
51 PROPWIN_GRAPH_TYPE_SPEED_TIME,
52 PROPWIN_GRAPH_TYPE_DISTANCE_TIME,
53 PROPWIN_GRAPH_TYPE_ELEVATION_TIME,
54 PROPWIN_GRAPH_TYPE_SPEED_DISTANCE,
55 PROPWIN_GRAPH_TYPE_END,
56 } VikPropWinGraphType_t;
58 /* (Hopefully!) Human friendly altitude grid sizes - note no fixed 'ratio' just numbers that look nice...*/
59 static const gdouble chunksa[] = {2.0, 5.0, 10.0, 15.0, 20.0,
60 25.0, 40.0, 50.0, 75.0, 100.0,
61 150.0, 200.0, 250.0, 375.0, 500.0,
62 750.0, 1000.0, 2000.0, 5000.0, 10000.0, 100000.0};
64 /* (Hopefully!) Human friendly gradient grid sizes - note no fixed 'ratio' just numbers that look nice...*/
65 static const gdouble chunksg[] = {1.0, 2.0, 3.0, 4.0, 5.0, 8.0, 10.0,
66 12.0, 15.0, 20.0, 25.0, 30.0, 35.0, 40.0, 45.0, 50.0, 75.0,
67 100.0, 150.0, 200.0, 250.0, 375.0, 500.0,
68 750.0, 1000.0, 10000.0, 100000.0};
69 // Normally gradients should range up to couple hundred precent at most,
70 // however there are possibilities of having points with no altitude after a point with a big altitude
71 // (such as places with invalid DEM values in otherwise mountainous regions) - thus giving huge negative gradients.
73 /* (Hopefully!) Human friendly grid sizes - note no fixed 'ratio' just numbers that look nice...*/
74 /* As need to cover walking speeds - have many low numbers (but also may go up to airplane speeds!) */
75 static const gdouble chunkss[] = {1.0, 2.0, 3.0, 4.0, 5.0, 8.0, 10.0,
76 15.0, 20.0, 25.0, 40.0, 50.0, 75.0,
77 100.0, 150.0, 200.0, 250.0, 375.0, 500.0,
78 750.0, 1000.0, 10000.0};
80 /* (Hopefully!) Human friendly distance grid sizes - note no fixed 'ratio' just numbers that look nice...*/
81 static const gdouble chunksd[] = {0.1, 0.2, 0.5, 1.0, 2.0, 3.0, 4.0, 5.0, 8.0, 10.0,
82 15.0, 20.0, 25.0, 40.0, 50.0, 75.0,
83 100.0, 150.0, 200.0, 250.0, 375.0, 500.0,
84 750.0, 1000.0, 10000.0};
86 // Time chunks in seconds
87 static const time_t chunkst[] = {
104 // Local show settings to restore on dialog opening
105 static gboolean show_dem = TRUE;
106 static gboolean show_alt_gps_speed = TRUE;
107 static gboolean show_gps_speed = TRUE;
108 static gboolean show_gradient_gps_speed = TRUE;
109 static gboolean show_dist_speed = FALSE;
110 static gboolean show_elev_speed = FALSE;
111 static gboolean show_elev_dem = FALSE;
112 static gboolean show_sd_gps_speed = TRUE;
114 typedef struct _propsaved {
119 typedef struct _propwidgets {
120 gboolean configure_dialog;
127 gint profile_width_old;
128 gint profile_height_old;
129 gint profile_width_offset;
130 gint profile_height_offset;
132 GtkWidget *w_comment;
133 GtkWidget *w_description;
136 GtkWidget *w_track_length;
137 GtkWidget *w_tp_count;
138 GtkWidget *w_segment_count;
139 GtkWidget *w_duptp_count;
140 GtkWidget *w_max_speed;
141 GtkWidget *w_avg_speed;
142 GtkWidget *w_mvg_speed;
143 GtkWidget *w_avg_dist;
144 GtkWidget *w_elev_range;
145 GtkWidget *w_elev_gain;
146 GtkWidget *w_time_start;
147 GtkWidget *w_time_end;
148 GtkWidget *w_time_dur;
150 GtkWidget *w_namelabel;
151 GtkWidget *w_number_distlabels;
152 GtkWidget *w_cur_dist; /*< Current distance */
153 GtkWidget *w_cur_elevation;
154 GtkWidget *w_cur_gradient_dist; /*< Current distance on gradient graph */
155 GtkWidget *w_cur_gradient_gradient; /*< Current gradient on gradient graph */
156 GtkWidget *w_cur_time; /*< Current track time */
157 GtkWidget *w_cur_time_real; /*< Actual time as on a clock */
158 GtkWidget *w_cur_speed;
159 GtkWidget *w_cur_dist_dist; /*< Current distance on distance graph */
160 GtkWidget *w_cur_dist_time; /*< Current track time on distance graph */
161 GtkWidget *w_cur_dist_time_real; // Clock time
162 GtkWidget *w_cur_elev_elev;
163 GtkWidget *w_cur_elev_time; // Track time
164 GtkWidget *w_cur_elev_time_real; // Clock time
165 GtkWidget *w_cur_speed_dist;
166 GtkWidget *w_cur_speed_speed;
167 GtkWidget *w_show_dem;
168 GtkWidget *w_show_alt_gps_speed;
169 GtkWidget *w_show_gps_speed;
170 GtkWidget *w_show_gradient_gps_speed;
171 GtkWidget *w_show_dist_speed;
172 GtkWidget *w_show_elev_speed;
173 GtkWidget *w_show_elev_dem;
174 GtkWidget *w_show_sd_gps_speed;
175 gdouble track_length;
176 gdouble track_length_inc_gaps;
177 PropSaved elev_graph_saved_img;
178 PropSaved gradient_graph_saved_img;
179 PropSaved speed_graph_saved_img;
180 PropSaved dist_graph_saved_img;
181 PropSaved elev_time_graph_saved_img;
182 PropSaved speed_dist_graph_saved_img;
184 GtkWidget *gradient_box;
185 GtkWidget *speed_box;
187 GtkWidget *elev_time_box;
188 GtkWidget *speed_dist_box;
190 gdouble *ats; // altitudes in time
191 gdouble min_altitude;
192 gdouble max_altitude;
193 gdouble draw_min_altitude;
194 gdouble draw_min_altitude_time;
195 gint cia; // Chunk size Index into Altitudes
196 gint ciat; // Chunk size Index into Altitudes / Time
197 // NB cia & ciat are normally same value but sometimes not due to differing methods of altitude array creation
198 // thus also have draw_min_altitude for each altitude graph type
200 gdouble min_gradient;
201 gdouble max_gradient;
202 gdouble draw_min_gradient;
203 gint cig; // Chunk size Index into Gradients
205 gdouble *speeds_dist;
208 gdouble draw_min_speed;
209 gdouble max_speed_dist;
210 gint cis; // Chunk size Index into Speeds
211 gint cisd; // Chunk size Index into Speed/Distance
213 gint cid; // Chunk size Index into Distance
214 VikTrackpoint *marker_tp;
215 gboolean is_marker_drawn;
216 VikTrackpoint *blob_tp;
217 gboolean is_blob_drawn;
219 gchar *tz; // TimeZone at track's location
222 static PropWidgets *prop_widgets_new()
224 PropWidgets *widgets = g_malloc0(sizeof(PropWidgets));
229 static void prop_widgets_free(PropWidgets *widgets)
231 if (widgets->elev_graph_saved_img.img)
232 g_object_unref(widgets->elev_graph_saved_img.img);
233 if (widgets->gradient_graph_saved_img.img)
234 g_object_unref(widgets->gradient_graph_saved_img.img);
235 if (widgets->speed_graph_saved_img.img)
236 g_object_unref(widgets->speed_graph_saved_img.img);
237 if (widgets->dist_graph_saved_img.img)
238 g_object_unref(widgets->dist_graph_saved_img.img);
239 if (widgets->elev_time_graph_saved_img.img)
240 g_object_unref(widgets->elev_time_graph_saved_img.img);
241 if (widgets->speed_dist_graph_saved_img.img)
242 g_object_unref(widgets->speed_dist_graph_saved_img.img);
243 if (widgets->altitudes)
244 g_free(widgets->altitudes);
245 if (widgets->gradients)
246 g_free(widgets->gradients);
248 g_free(widgets->speeds);
249 if (widgets->distances)
250 g_free(widgets->distances);
252 g_free(widgets->ats);
253 if (widgets->speeds_dist)
254 g_free(widgets->speeds_dist);
258 static void minmax_array(const gdouble *array, gdouble *min, gdouble *max, gboolean NO_ALT_TEST, gint PROFILE_WIDTH)
263 for ( i=0; i < PROFILE_WIDTH; i++ ) {
264 if ( NO_ALT_TEST || (array[i] != VIK_DEFAULT_ALTITUDE) ) {
265 if ( array[i] > *max )
267 if ( array[i] < *min )
277 * get_new_min_and_chunk_index:
278 * Returns via pointers:
279 * the new minimum value to be used for the graph
280 * the index in to the chunk sizes array (ci = Chunk Index)
282 static void get_new_min_and_chunk_index (gdouble mina, gdouble maxa, const gdouble *chunks, size_t chunky, gdouble *new_min, gint *ci)
284 /* Get unitized chunk */
285 /* Find suitable chunk index */
287 gdouble diff_chunk = (maxa - mina)/LINES;
289 /* Loop through to find best match */
290 while (diff_chunk > chunks[*ci]) {
292 /* Last Resort Check */
293 if ( *ci == chunky ) {
294 // Use previous value and exit loop
300 /* Ensure adjusted minimum .. maximum covers mina->maxa */
302 // Now work out adjusted minimum point to the nearest lowest chunk divisor value
303 // When negative ensure logic uses lowest value
305 *new_min = (gdouble) ( (gint)((mina - chunks[*ci]) / chunks[*ci]) * chunks[*ci] );
307 *new_min = (gdouble) ( (gint)(mina / chunks[*ci]) * chunks[*ci] );
309 // Range not big enough - as new minimum has lowered
310 if ((*new_min + (chunks[*ci] * LINES) < maxa)) {
311 // Next chunk should cover it
312 if ( *ci < chunky-1 ) {
314 // Remember to adjust the minimum too...
316 *new_min = (gdouble) ( (gint)((mina - chunks[*ci]) / chunks[*ci]) * chunks[*ci] );
318 *new_min = (gdouble) ( (gint)(mina / chunks[*ci]) * chunks[*ci] );
323 static guint get_time_chunk_index (time_t duration)
326 time_t myduration = duration / LINES;
328 // Search nearest chunk index
330 guint last_chunk = G_N_ELEMENTS(chunkst);
332 // Loop through to find best match
333 while (myduration > chunkst[ci]) {
336 if ( ci == last_chunk )
339 // Use previous value
349 static guint get_distance_chunk_index (gdouble length)
352 gdouble mylength = length / LINES;
354 // Search nearest chunk index
356 guint last_chunk = G_N_ELEMENTS(chunksd);
358 // Loop through to find best match
359 while (mylength > chunksd[ci]) {
362 if ( ci == last_chunk )
365 // Use previous value
372 static VikTrackpoint *set_center_at_graph_position(gdouble event_x,
381 VikTrackpoint *trackpoint;
382 gdouble x = event_x - img_width / 2 + PROFILE_WIDTH / 2 - MARGIN_X / 2;
385 if (x > PROFILE_WIDTH)
389 trackpoint = vik_track_get_closest_tp_by_percentage_time ( tr, (gdouble) x / PROFILE_WIDTH, NULL );
391 trackpoint = vik_track_get_closest_tp_by_percentage_dist ( tr, (gdouble) x / PROFILE_WIDTH, NULL );
394 VikCoord coord = trackpoint->coord;
396 vik_viewport_set_center_coord ( vik_layers_panel_get_viewport(vlp), &coord, TRUE );
397 vik_layers_panel_emit_update ( vlp );
400 /* since vlp not set, vvp should be valid instead! */
402 vik_viewport_set_center_coord ( vvp, &coord, TRUE );
403 vik_layer_emit_update ( VIK_LAYER(vtl) );
410 * Returns whether the marker was drawn or not and whether the blob was drawn or not
412 static void save_image_and_draw_graph_marks (GtkWidget *image,
417 PropSaved *saved_img,
420 gboolean *marker_drawn,
421 gboolean *blob_drawn)
423 GdkPixmap *pix = NULL;
424 /* the pixmap = margin + graph area */
425 gtk_image_get_pixmap(GTK_IMAGE(image), &pix, NULL);
427 /* Restore previously saved image */
428 if (saved_img->saved) {
429 gdk_draw_image(GDK_DRAWABLE(pix), gc, saved_img->img, 0, 0, 0, 0, MARGIN_X+PROFILE_WIDTH, MARGIN_Y+PROFILE_HEIGHT);
430 saved_img->saved = FALSE;
433 // ATM always save whole image - as anywhere could have changed
435 gdk_drawable_copy_to_image(GDK_DRAWABLE(pix), saved_img->img, 0, 0, 0, 0, MARGIN_X+PROFILE_WIDTH, MARGIN_Y+PROFILE_HEIGHT);
437 saved_img->img = gdk_drawable_copy_to_image(GDK_DRAWABLE(pix), saved_img->img, 0, 0, 0, 0, MARGIN_X+PROFILE_WIDTH, MARGIN_Y+PROFILE_HEIGHT);
438 saved_img->saved = TRUE;
440 if ((marker_x >= MARGIN_X) && (marker_x < (PROFILE_WIDTH + MARGIN_X))) {
441 gdk_draw_line (GDK_DRAWABLE(pix), gc, marker_x, MARGIN_Y, marker_x, PROFILE_HEIGHT + MARGIN_Y);
442 *marker_drawn = TRUE;
445 *marker_drawn = FALSE;
447 // Draw a square blob to indicate where we are on track for this graph
448 if ( (blob_x >= MARGIN_X) && (blob_x < (PROFILE_WIDTH + MARGIN_X)) && (blob_y < PROFILE_HEIGHT+MARGIN_Y) ) {
449 gdk_draw_rectangle (GDK_DRAWABLE(pix), gc, TRUE, blob_x-3, blob_y-3, 6, 6);
455 // Anywhere on image could have changed
456 if (*marker_drawn || *blob_drawn)
457 gtk_widget_queue_draw(image);
461 * Return the percentage of how far a trackpoint is a long a track via the time method
463 static gdouble tp_percentage_by_time ( VikTrack *tr, VikTrackpoint *trackpoint )
466 if (trackpoint == NULL)
468 time_t t_start, t_end, t_total;
469 t_start = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
470 t_end = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
471 t_total = t_end - t_start;
472 pc = (gdouble)(trackpoint->timestamp - t_start)/t_total;
477 * Return the percentage of how far a trackpoint is a long a track via the distance method
479 static gdouble tp_percentage_by_distance ( VikTrack *tr, VikTrackpoint *trackpoint, gdouble track_length )
482 if (trackpoint == NULL)
486 for (iter = tr->trackpoints->next; iter != NULL; iter = iter->next) {
487 dist += vik_coord_diff(&(VIK_TRACKPOINT(iter->data)->coord),
488 &(VIK_TRACKPOINT(iter->prev->data)->coord));
489 /* Assuming trackpoint is not a copy */
490 if (trackpoint == VIK_TRACKPOINT(iter->data))
494 pc = dist/track_length;
498 static void track_graph_click( GtkWidget *event_box, GdkEventButton *event, PropWidgets *widgets, VikPropWinGraphType_t graph_type )
500 gboolean is_time_graph =
501 ( graph_type == PROPWIN_GRAPH_TYPE_SPEED_TIME ||
502 graph_type == PROPWIN_GRAPH_TYPE_DISTANCE_TIME ||
503 graph_type == PROPWIN_GRAPH_TYPE_ELEVATION_TIME );
505 GtkAllocation allocation;
506 gtk_widget_get_allocation ( event_box, &allocation );
508 VikTrackpoint *trackpoint = set_center_at_graph_position(event->x, allocation.width, widgets->vtl, widgets->vlp, widgets->vvp, widgets->tr, is_time_graph, widgets->profile_width);
509 // Unable to get the point so give up
510 if ( trackpoint == NULL ) {
511 gtk_dialog_set_response_sensitive(GTK_DIALOG(widgets->dialog), VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER, FALSE);
515 widgets->marker_tp = trackpoint;
519 GtkWidget *window = gtk_widget_get_toplevel(GTK_WIDGET(event_box));
520 GtkWidget *graph_box;
521 PropSaved *graph_saved_img;
524 // Attempt to redraw marker on all graph types
526 for ( graphite = PROPWIN_GRAPH_TYPE_ELEVATION_DISTANCE;
527 graphite < PROPWIN_GRAPH_TYPE_END;
530 // Switch commonal variables to particular graph type
533 case PROPWIN_GRAPH_TYPE_ELEVATION_DISTANCE:
534 graph_box = widgets->elev_box;
535 graph_saved_img = &widgets->elev_graph_saved_img;
536 is_time_graph = FALSE;
538 case PROPWIN_GRAPH_TYPE_GRADIENT_DISTANCE:
539 graph_box = widgets->gradient_box;
540 graph_saved_img = &widgets->gradient_graph_saved_img;
541 is_time_graph = FALSE;
543 case PROPWIN_GRAPH_TYPE_SPEED_TIME:
544 graph_box = widgets->speed_box;
545 graph_saved_img = &widgets->speed_graph_saved_img;
546 is_time_graph = TRUE;
548 case PROPWIN_GRAPH_TYPE_DISTANCE_TIME:
549 graph_box = widgets->dist_box;
550 graph_saved_img = &widgets->dist_graph_saved_img;
551 is_time_graph = TRUE;
553 case PROPWIN_GRAPH_TYPE_ELEVATION_TIME:
554 graph_box = widgets->elev_time_box;
555 graph_saved_img = &widgets->elev_time_graph_saved_img;
556 is_time_graph = TRUE;
558 case PROPWIN_GRAPH_TYPE_SPEED_DISTANCE:
559 graph_box = widgets->speed_dist_box;
560 graph_saved_img = &widgets->speed_dist_graph_saved_img;
561 is_time_graph = FALSE;
565 // Commonal method of redrawing marker
568 child = gtk_container_get_children(GTK_CONTAINER(graph_box));
569 image = GTK_WIDGET(child->data);
572 pc = tp_percentage_by_time ( widgets->tr, trackpoint );
574 pc = tp_percentage_by_distance ( widgets->tr, trackpoint, widgets->track_length_inc_gaps );
577 gdouble marker_x = (pc * widgets->profile_width) + MARGIN_X;
578 save_image_and_draw_graph_marks(image,
580 gtk_widget_get_style(window)->black_gc,
581 -1, // Don't draw blob on clicks
584 widgets->profile_width,
585 widgets->profile_height,
586 &widgets->is_marker_drawn,
587 &widgets->is_blob_drawn);
593 gtk_dialog_set_response_sensitive(GTK_DIALOG(widgets->dialog), VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER, widgets->is_marker_drawn);
596 static gboolean track_profile_click( GtkWidget *event_box, GdkEventButton *event, gpointer ptr )
598 track_graph_click(event_box, event, ptr, PROPWIN_GRAPH_TYPE_ELEVATION_DISTANCE);
599 return TRUE; /* don't call other (further) callbacks */
602 static gboolean track_gradient_click( GtkWidget *event_box, GdkEventButton *event, gpointer ptr )
604 track_graph_click(event_box, event, ptr, PROPWIN_GRAPH_TYPE_GRADIENT_DISTANCE);
605 return TRUE; /* don't call other (further) callbacks */
608 static gboolean track_vt_click( GtkWidget *event_box, GdkEventButton *event, gpointer ptr )
610 track_graph_click(event_box, event, ptr, PROPWIN_GRAPH_TYPE_SPEED_TIME);
611 return TRUE; /* don't call other (further) callbacks */
614 static gboolean track_dt_click( GtkWidget *event_box, GdkEventButton *event, gpointer ptr )
616 track_graph_click(event_box, event, ptr, PROPWIN_GRAPH_TYPE_DISTANCE_TIME);
617 return TRUE; /* don't call other (further) callbacks */
620 static gboolean track_et_click( GtkWidget *event_box, GdkEventButton *event, gpointer ptr )
622 track_graph_click(event_box, event, ptr, PROPWIN_GRAPH_TYPE_ELEVATION_TIME);
623 return TRUE; /* don't call other (further) callbacks */
626 static gboolean track_sd_click( GtkWidget *event_box, GdkEventButton *event, gpointer ptr )
628 track_graph_click(event_box, event, ptr, PROPWIN_GRAPH_TYPE_SPEED_DISTANCE);
629 return TRUE; /* don't call other (further) callbacks */
633 * Calculate y position for blob on elevation graph
635 static gint blobby_altitude ( gdouble x_blob, PropWidgets *widgets )
637 gint ix = (gint)x_blob;
638 // Ensure ix is inbounds
639 if (ix == widgets->profile_width)
642 gint y_blob = widgets->profile_height-widgets->profile_height*(widgets->altitudes[ix]-widgets->draw_min_altitude)/(chunksa[widgets->cia]*LINES);
648 * Calculate y position for blob on gradient graph
650 static gint blobby_gradient ( gdouble x_blob, PropWidgets *widgets )
652 gint ix = (gint)x_blob;
653 // Ensure ix is inbounds
654 if (ix == widgets->profile_width)
657 gint y_blob = widgets->profile_height-widgets->profile_height*(widgets->gradients[ix]-widgets->draw_min_gradient)/(chunksg[widgets->cig]*LINES);
663 * Calculate y position for blob on speed graph
665 static gint blobby_speed ( gdouble x_blob, PropWidgets *widgets )
667 gint ix = (gint)x_blob;
668 // Ensure ix is inbounds
669 if (ix == widgets->profile_width)
672 gint y_blob = widgets->profile_height-widgets->profile_height*(widgets->speeds[ix]-widgets->draw_min_speed)/(chunkss[widgets->cis]*LINES);
678 * Calculate y position for blob on distance graph
680 static gint blobby_distance ( gdouble x_blob, PropWidgets *widgets )
682 gint ix = (gint)x_blob;
683 // Ensure ix is inbounds
684 if (ix == widgets->profile_width)
687 gint y_blob = widgets->profile_height-widgets->profile_height*(widgets->distances[ix])/(chunksd[widgets->cid]*LINES);
688 //NB min distance is always 0, so no need to subtract that from this ______/
694 * Calculate y position for blob on elevation/time graph
696 static gint blobby_altitude_time ( gdouble x_blob, PropWidgets *widgets )
698 gint ix = (gint)x_blob;
699 // Ensure ix is inbounds
700 if (ix == widgets->profile_width)
703 gint y_blob = widgets->profile_height-widgets->profile_height*(widgets->ats[ix]-widgets->draw_min_altitude_time)/(chunksa[widgets->ciat]*LINES);
708 * Calculate y position for blob on speed/dist graph
710 static gint blobby_speed_dist ( gdouble x_blob, PropWidgets *widgets )
712 gint ix = (gint)x_blob;
713 // Ensure ix is inbounds
714 if (ix == widgets->profile_width)
717 gint y_blob = widgets->profile_height-widgets->profile_height*(widgets->speeds_dist[ix]-widgets->draw_min_speed)/(chunkss[widgets->cisd]*LINES);
723 void track_profile_move( GtkWidget *event_box, GdkEventMotion *event, PropWidgets *widgets )
725 int mouse_x, mouse_y;
726 GdkModifierType state;
729 gdk_window_get_pointer (event->window, &mouse_x, &mouse_y, &state);
733 GtkAllocation allocation;
734 gtk_widget_get_allocation ( event_box, &allocation );
736 gdouble x = mouse_x - allocation.width / 2 + widgets->profile_width / 2 - MARGIN_X / 2;
739 if (x > widgets->profile_width)
740 x = widgets->profile_width;
742 gdouble meters_from_start;
743 VikTrackpoint *trackpoint = vik_track_get_closest_tp_by_percentage_dist ( widgets->tr, (gdouble) x / widgets->profile_width, &meters_from_start );
744 if (trackpoint && widgets->w_cur_dist) {
745 static gchar tmp_buf[20];
746 vik_units_distance_t dist_units = a_vik_get_units_distance ();
747 switch (dist_units) {
748 case VIK_UNITS_DISTANCE_KILOMETRES:
749 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km", meters_from_start/1000.0);
751 case VIK_UNITS_DISTANCE_MILES:
752 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f miles", VIK_METERS_TO_MILES(meters_from_start) );
754 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
755 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f NM", VIK_METERS_TO_NAUTICAL_MILES(meters_from_start) );
758 g_critical("Houston, we've had a problem. distance=%d", dist_units);
760 gtk_label_set_text(GTK_LABEL(widgets->w_cur_dist), tmp_buf);
763 // Show track elevation for this position - to the nearest whole number
764 if (trackpoint && widgets->w_cur_elevation) {
765 static gchar tmp_buf[20];
766 if (a_vik_get_units_height () == VIK_UNITS_HEIGHT_FEET)
767 g_snprintf(tmp_buf, sizeof(tmp_buf), "%d ft", (int)VIK_METERS_TO_FEET(trackpoint->altitude));
769 g_snprintf(tmp_buf, sizeof(tmp_buf), "%d m", (int)trackpoint->altitude);
770 gtk_label_set_text(GTK_LABEL(widgets->w_cur_elevation), tmp_buf);
773 widgets->blob_tp = trackpoint;
775 if ( widgets->altitudes == NULL )
778 GtkWidget *window = gtk_widget_get_toplevel (event_box);
779 GList *child = gtk_container_get_children(GTK_CONTAINER(event_box));
780 GtkWidget *image = GTK_WIDGET(child->data);
782 gint y_blob = blobby_altitude (x, widgets);
784 gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
785 if (widgets->is_marker_drawn) {
786 gdouble pc = tp_percentage_by_distance ( widgets->tr, widgets->marker_tp, widgets->track_length_inc_gaps );
788 marker_x = (pc * widgets->profile_width) + MARGIN_X;
792 save_image_and_draw_graph_marks (image,
794 gtk_widget_get_style(window)->black_gc,
797 &widgets->elev_graph_saved_img,
798 widgets->profile_width,
799 widgets->profile_height,
800 &widgets->is_marker_drawn,
801 &widgets->is_blob_drawn);
806 void track_gradient_move( GtkWidget *event_box, GdkEventMotion *event, PropWidgets *widgets )
808 int mouse_x, mouse_y;
809 GdkModifierType state;
812 gdk_window_get_pointer (event->window, &mouse_x, &mouse_y, &state);
816 GtkAllocation allocation;
817 gtk_widget_get_allocation ( event_box, &allocation );
819 gdouble x = mouse_x - allocation.width / 2 + widgets->profile_width / 2 - MARGIN_X / 2;
822 if (x > widgets->profile_width)
823 x = widgets->profile_width;
825 gdouble meters_from_start;
826 VikTrackpoint *trackpoint = vik_track_get_closest_tp_by_percentage_dist ( widgets->tr, (gdouble) x / widgets->profile_width, &meters_from_start );
827 if (trackpoint && widgets->w_cur_gradient_dist) {
828 static gchar tmp_buf[20];
829 vik_units_distance_t dist_units = a_vik_get_units_distance ();
830 switch (dist_units) {
831 case VIK_UNITS_DISTANCE_KILOMETRES:
832 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km", meters_from_start/1000.0);
834 case VIK_UNITS_DISTANCE_MILES:
835 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f miles", VIK_METERS_TO_MILES(meters_from_start) );
837 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
838 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f NM", VIK_METERS_TO_NAUTICAL_MILES(meters_from_start) );
841 g_critical("Houston, we've had a problem. distance=%d", dist_units);
843 gtk_label_set_text(GTK_LABEL(widgets->w_cur_gradient_dist), tmp_buf);
846 // Show track gradient for this position - to the nearest whole number
847 if (trackpoint && widgets->w_cur_gradient_gradient) {
848 static gchar tmp_buf[20];
850 double gradient = widgets->gradients[(int) x];
852 g_snprintf(tmp_buf, sizeof(tmp_buf), "%d%%", (int)gradient);
853 gtk_label_set_text(GTK_LABEL(widgets->w_cur_gradient_gradient), tmp_buf);
856 widgets->blob_tp = trackpoint;
858 if ( widgets->gradients == NULL )
861 GtkWidget *window = gtk_widget_get_toplevel (event_box);
862 GList *child = gtk_container_get_children(GTK_CONTAINER(event_box));
863 GtkWidget *image = GTK_WIDGET(child->data);
865 gint y_blob = blobby_gradient (x, widgets);
867 gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
868 if (widgets->is_marker_drawn) {
869 gdouble pc = tp_percentage_by_distance ( widgets->tr, widgets->marker_tp, widgets->track_length_inc_gaps );
871 marker_x = (pc * widgets->profile_width) + MARGIN_X;
875 save_image_and_draw_graph_marks (image,
877 gtk_widget_get_style(window)->black_gc,
880 &widgets->gradient_graph_saved_img,
881 widgets->profile_width,
882 widgets->profile_height,
883 &widgets->is_marker_drawn,
884 &widgets->is_blob_drawn);
890 static void time_label_update (GtkWidget *widget, time_t seconds_from_start)
892 static gchar tmp_buf[20];
893 guint h = seconds_from_start/3600;
894 guint m = (seconds_from_start - h*3600)/60;
895 guint s = seconds_from_start - (3600*h) - (60*m);
896 g_snprintf(tmp_buf, sizeof(tmp_buf), "%02d:%02d:%02d", h, m, s);
897 gtk_label_set_text(GTK_LABEL(widget), tmp_buf);
901 static void real_time_label_update ( PropWidgets *widgets, GtkWidget *widget, VikTrackpoint *trackpoint)
903 static gchar tmp_buf[64];
904 if ( trackpoint->has_timestamp ) {
905 // Alternatively could use %c format but I prefer a slightly more compact form here
906 // The full date can of course be seen on the Statistics tab
907 strftime (tmp_buf, sizeof(tmp_buf), "%X %x %Z", localtime(&(trackpoint->timestamp)));
910 g_snprintf (tmp_buf, sizeof(tmp_buf), _("No Data"));
911 gtk_label_set_text(GTK_LABEL(widget), tmp_buf);
914 void track_vt_move( GtkWidget *event_box, GdkEventMotion *event, PropWidgets *widgets )
916 int mouse_x, mouse_y;
917 GdkModifierType state;
920 gdk_window_get_pointer (event->window, &mouse_x, &mouse_y, &state);
924 GtkAllocation allocation;
925 gtk_widget_get_allocation ( event_box, &allocation );
926 gdouble x = mouse_x - allocation.width / 2 + widgets->profile_width / 2 - MARGIN_X / 2;
929 if (x > widgets->profile_width)
930 x = widgets->profile_width;
932 time_t seconds_from_start;
933 VikTrackpoint *trackpoint = vik_track_get_closest_tp_by_percentage_time ( widgets->tr, (gdouble) x / widgets->profile_width, &seconds_from_start );
934 if (trackpoint && widgets->w_cur_time) {
935 time_label_update ( widgets->w_cur_time, seconds_from_start );
938 if (trackpoint && widgets->w_cur_time_real) {
939 real_time_label_update ( widgets, widgets->w_cur_time_real, trackpoint );
943 // Ensure ix is inbounds
944 if (ix == widgets->profile_width)
947 // Show track speed for this position
948 if (trackpoint && widgets->w_cur_speed) {
949 static gchar tmp_buf[20];
950 // Even if GPS speed available (trackpoint->speed), the text will correspond to the speed map shown
951 // No conversions needed as already in appropriate units
952 vik_units_speed_t speed_units = a_vik_get_units_speed ();
953 switch (speed_units) {
954 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
955 g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f kph"), widgets->speeds[ix]);
957 case VIK_UNITS_SPEED_MILES_PER_HOUR:
958 g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f mph"), widgets->speeds[ix]);
960 case VIK_UNITS_SPEED_KNOTS:
961 g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f knots"), widgets->speeds[ix]);
964 // VIK_UNITS_SPEED_METRES_PER_SECOND:
965 g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f m/s"), widgets->speeds[ix]);
968 gtk_label_set_text(GTK_LABEL(widgets->w_cur_speed), tmp_buf);
971 widgets->blob_tp = trackpoint;
973 if ( widgets->speeds == NULL )
976 GtkWidget *window = gtk_widget_get_toplevel (event_box);
977 GList *child = gtk_container_get_children(GTK_CONTAINER(event_box));
978 GtkWidget *image = GTK_WIDGET(child->data);
980 gint y_blob = blobby_speed (x, widgets);
982 gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
983 if (widgets->is_marker_drawn) {
984 gdouble pc = tp_percentage_by_time ( widgets->tr, widgets->marker_tp );
986 marker_x = (pc * widgets->profile_width) + MARGIN_X;
990 save_image_and_draw_graph_marks (image,
992 gtk_widget_get_style(window)->black_gc,
995 &widgets->speed_graph_saved_img,
996 widgets->profile_width,
997 widgets->profile_height,
998 &widgets->is_marker_drawn,
999 &widgets->is_blob_drawn);
1005 * Update labels and blob marker on mouse moves in the distance/time graph
1007 void track_dt_move( GtkWidget *event_box, GdkEventMotion *event, PropWidgets *widgets )
1009 int mouse_x, mouse_y;
1010 GdkModifierType state;
1013 gdk_window_get_pointer (event->window, &mouse_x, &mouse_y, &state);
1017 GtkAllocation allocation;
1018 gtk_widget_get_allocation ( event_box, &allocation );
1020 gdouble x = mouse_x - allocation.width / 2 + widgets->profile_width / 2 - MARGIN_X / 2;
1023 if (x > widgets->profile_width)
1024 x = widgets->profile_width;
1026 time_t seconds_from_start;
1027 VikTrackpoint *trackpoint = vik_track_get_closest_tp_by_percentage_time ( widgets->tr, (gdouble) x / widgets->profile_width, &seconds_from_start );
1028 if (trackpoint && widgets->w_cur_dist_time) {
1029 time_label_update ( widgets->w_cur_dist_time, seconds_from_start );
1032 if (trackpoint && widgets->w_cur_dist_time_real) {
1033 real_time_label_update ( widgets, widgets->w_cur_dist_time_real, trackpoint );
1037 // Ensure ix is inbounds
1038 if (ix == widgets->profile_width)
1041 if (trackpoint && widgets->w_cur_dist_dist) {
1042 static gchar tmp_buf[20];
1043 switch ( a_vik_get_units_distance () ) {
1044 case VIK_UNITS_DISTANCE_MILES:
1045 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f miles", widgets->distances[ix]);
1047 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1048 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f NM", widgets->distances[ix]);
1051 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km", widgets->distances[ix]);
1054 gtk_label_set_text(GTK_LABEL(widgets->w_cur_dist_dist), tmp_buf);
1057 widgets->blob_tp = trackpoint;
1059 if ( widgets->distances == NULL )
1062 GtkWidget *window = gtk_widget_get_toplevel (event_box);
1063 GList *child = gtk_container_get_children(GTK_CONTAINER(event_box));
1064 GtkWidget *image = GTK_WIDGET(child->data);
1066 gint y_blob = blobby_distance (x, widgets);
1068 gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
1069 if (widgets->is_marker_drawn) {
1070 gdouble pc = tp_percentage_by_time ( widgets->tr, widgets->marker_tp );
1072 marker_x = (pc * widgets->profile_width) + MARGIN_X;
1076 save_image_and_draw_graph_marks (image,
1078 gtk_widget_get_style(window)->black_gc,
1081 &widgets->dist_graph_saved_img,
1082 widgets->profile_width,
1083 widgets->profile_height,
1084 &widgets->is_marker_drawn,
1085 &widgets->is_blob_drawn);
1091 * Update labels and blob marker on mouse moves in the elevation/time graph
1093 void track_et_move( GtkWidget *event_box, GdkEventMotion *event, PropWidgets *widgets )
1095 int mouse_x, mouse_y;
1096 GdkModifierType state;
1099 gdk_window_get_pointer (event->window, &mouse_x, &mouse_y, &state);
1103 GtkAllocation allocation;
1104 gtk_widget_get_allocation ( event_box, &allocation );
1106 gdouble x = mouse_x - allocation.width / 2 + widgets->profile_width / 2 - MARGIN_X / 2;
1109 if (x > widgets->profile_width)
1110 x = widgets->profile_width;
1112 time_t seconds_from_start;
1113 VikTrackpoint *trackpoint = vik_track_get_closest_tp_by_percentage_time ( widgets->tr, (gdouble) x / widgets->profile_width, &seconds_from_start );
1114 if (trackpoint && widgets->w_cur_elev_time) {
1115 time_label_update ( widgets->w_cur_elev_time, seconds_from_start );
1118 if (trackpoint && widgets->w_cur_elev_time_real) {
1119 real_time_label_update ( widgets, widgets->w_cur_elev_time_real, trackpoint );
1123 // Ensure ix is inbounds
1124 if (ix == widgets->profile_width)
1127 if (trackpoint && widgets->w_cur_elev_elev) {
1128 static gchar tmp_buf[20];
1129 if (a_vik_get_units_height () == VIK_UNITS_HEIGHT_FEET)
1130 g_snprintf(tmp_buf, sizeof(tmp_buf), "%d ft", (int)VIK_METERS_TO_FEET(trackpoint->altitude));
1132 g_snprintf(tmp_buf, sizeof(tmp_buf), "%d m", (int)trackpoint->altitude);
1133 gtk_label_set_text(GTK_LABEL(widgets->w_cur_elev_elev), tmp_buf);
1136 widgets->blob_tp = trackpoint;
1138 if ( widgets->ats == NULL )
1141 GtkWidget *window = gtk_widget_get_toplevel (event_box);
1142 GList *child = gtk_container_get_children(GTK_CONTAINER(event_box));
1143 GtkWidget *image = GTK_WIDGET(child->data);
1145 gint y_blob = blobby_altitude_time (x, widgets);
1147 gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
1148 if (widgets->is_marker_drawn) {
1149 gdouble pc = tp_percentage_by_time ( widgets->tr, widgets->marker_tp );
1151 marker_x = (pc * widgets->profile_width) + MARGIN_X;
1155 save_image_and_draw_graph_marks (image,
1157 gtk_widget_get_style(window)->black_gc,
1160 &widgets->elev_time_graph_saved_img,
1161 widgets->profile_width,
1162 widgets->profile_height,
1163 &widgets->is_marker_drawn,
1164 &widgets->is_blob_drawn);
1169 void track_sd_move( GtkWidget *event_box, GdkEventMotion *event, PropWidgets *widgets )
1171 int mouse_x, mouse_y;
1172 GdkModifierType state;
1175 gdk_window_get_pointer (event->window, &mouse_x, &mouse_y, &state);
1179 GtkAllocation allocation;
1180 gtk_widget_get_allocation ( event_box, &allocation );
1182 gdouble x = mouse_x - allocation.width / 2 + widgets->profile_width / 2 - MARGIN_X / 2;
1185 if (x > widgets->profile_width)
1186 x = widgets->profile_width;
1188 gdouble meters_from_start;
1189 VikTrackpoint *trackpoint = vik_track_get_closest_tp_by_percentage_dist ( widgets->tr, (gdouble) x / widgets->profile_width, &meters_from_start );
1190 if (trackpoint && widgets->w_cur_speed_dist) {
1191 static gchar tmp_buf[20];
1192 vik_units_distance_t dist_units = a_vik_get_units_distance ();
1193 switch (dist_units) {
1194 case VIK_UNITS_DISTANCE_KILOMETRES:
1195 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km", meters_from_start/1000.0);
1197 case VIK_UNITS_DISTANCE_MILES:
1198 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f miles", VIK_METERS_TO_MILES(meters_from_start) );
1200 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1201 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f NM", VIK_METERS_TO_NAUTICAL_MILES(meters_from_start) );
1204 g_critical("Houston, we've had a problem. distance=%d", dist_units);
1206 gtk_label_set_text(GTK_LABEL(widgets->w_cur_speed_dist), tmp_buf);
1210 // Ensure ix is inbounds
1211 if (ix == widgets->profile_width)
1214 if ( widgets->speeds_dist == NULL )
1217 // Show track speed for this position
1218 if (widgets->w_cur_speed_speed) {
1219 static gchar tmp_buf[20];
1220 // Even if GPS speed available (trackpoint->speed), the text will correspond to the speed map shown
1221 // No conversions needed as already in appropriate units
1222 vik_units_speed_t speed_units = a_vik_get_units_speed ();
1223 switch (speed_units) {
1224 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
1225 g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f kph"), widgets->speeds_dist[ix]);
1227 case VIK_UNITS_SPEED_MILES_PER_HOUR:
1228 g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f mph"), widgets->speeds_dist[ix]);
1230 case VIK_UNITS_SPEED_KNOTS:
1231 g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f knots"), widgets->speeds_dist[ix]);
1234 // VIK_UNITS_SPEED_METRES_PER_SECOND:
1235 g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f m/s"), widgets->speeds_dist[ix]);
1238 gtk_label_set_text(GTK_LABEL(widgets->w_cur_speed_speed), tmp_buf);
1241 widgets->blob_tp = trackpoint;
1243 GtkWidget *window = gtk_widget_get_toplevel (event_box);
1244 GList *child = gtk_container_get_children(GTK_CONTAINER(event_box));
1245 GtkWidget *image = GTK_WIDGET(child->data);
1247 gint y_blob = blobby_speed_dist (x, widgets);
1249 gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
1250 if (widgets->is_marker_drawn) {
1251 gdouble pc = tp_percentage_by_distance ( widgets->tr, widgets->marker_tp, widgets->track_length_inc_gaps );
1253 marker_x = (pc * widgets->profile_width) + MARGIN_X;
1257 save_image_and_draw_graph_marks (image,
1259 gtk_widget_get_style(window)->black_gc,
1262 &widgets->speed_dist_graph_saved_img,
1263 widgets->profile_width,
1264 widgets->profile_height,
1265 &widgets->is_marker_drawn,
1266 &widgets->is_blob_drawn);
1272 * Draws DEM points and a respresentative speed on the supplied pixmap
1273 * (which is the elevations graph)
1275 static void draw_dem_alt_speed_dist(VikTrack *tr,
1281 gdouble max_speed_in,
1290 gdouble max_speed = 0;
1291 gdouble total_length = vik_track_get_length_including_gaps(tr);
1293 // Calculate the max speed factor
1295 max_speed = max_speed_in * 110 / 100;
1298 gint h2 = height + MARGIN_Y; // Adjust height for x axis labelling offset
1299 gint achunk = chunksa[cia]*LINES;
1301 for (iter = tr->trackpoints->next; iter; iter = iter->next) {
1303 dist += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
1304 &(VIK_TRACKPOINT(iter->prev->data)->coord) );
1305 x = (width * dist)/total_length + margin;
1307 gint16 elev = a_dems_get_elev_by_coord(&(VIK_TRACKPOINT(iter->data)->coord), VIK_DEM_INTERPOL_BEST);
1308 if ( elev != VIK_DEM_INVALID_ELEVATION ) {
1309 // Convert into height units
1310 if (a_vik_get_units_height () == VIK_UNITS_HEIGHT_FEET)
1311 elev = VIK_METERS_TO_FEET(elev);
1312 // No conversion needed if already in metres
1314 // offset is in current height units
1317 // consider chunk size
1318 int y_alt = h2 - ((height * elev)/achunk );
1319 gdk_draw_rectangle(GDK_DRAWABLE(pix), alt_gc, TRUE, x-2, y_alt-2, 4, 4);
1323 // This is just a speed indicator - no actual values can be inferred by user
1324 if (!isnan(VIK_TRACKPOINT(iter->data)->speed)) {
1325 int y_speed = h2 - (height * VIK_TRACKPOINT(iter->data)->speed)/max_speed;
1326 gdk_draw_rectangle(GDK_DRAWABLE(pix), speed_gc, TRUE, x-2, y_speed-2, 4, 4);
1335 * A common way to draw the grid with y axis labels
1338 static void draw_grid_y ( GtkWidget *window, GtkWidget *image, PropWidgets *widgets, GdkPixmap *pix, gchar *ss, gint i )
1340 PangoLayout *pl = gtk_widget_create_pango_layout (GTK_WIDGET(image), NULL);
1342 pango_layout_set_alignment (pl, PANGO_ALIGN_RIGHT);
1343 pango_layout_set_font_description (pl, gtk_widget_get_style(window)->font_desc);
1345 gchar *label_markup = g_strdup_printf ( "<span size=\"small\">%s</span>", ss );
1346 pango_layout_set_markup ( pl, label_markup, -1 );
1347 g_free ( label_markup );
1350 pango_layout_get_pixel_size ( pl, &w, &h );
1352 gdk_draw_layout ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->fg_gc[0],
1354 CLAMP((int)i*widgets->profile_height/LINES - h/2 + MARGIN_Y, 0, widgets->profile_height-h+MARGIN_Y),
1356 g_object_unref ( G_OBJECT ( pl ) );
1358 gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[0],
1359 MARGIN_X, MARGIN_Y + widgets->profile_height/LINES * i,
1360 MARGIN_X + widgets->profile_width, MARGIN_Y + widgets->profile_height/LINES * i );
1366 * A common way to draw the grid with x axis labels for time graphs
1369 static void draw_grid_x_time ( GtkWidget *window, GtkWidget *image, PropWidgets *widgets, GdkPixmap *pix, guint ii, guint tt, guint xx )
1371 gchar *label_markup = NULL;
1378 label_markup = g_strdup_printf ( "<span size=\"small\">%d %s</span>", tt/60, _("mins") );
1385 label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", (gdouble)tt/(60*60), _("h") );
1391 label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", (gdouble)tt/(60*60*24), _("d") );
1396 label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", (gdouble)tt/(60*60*24*7), _("w") );
1400 label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", (gdouble)tt/(60*60*24*28), _("M") );
1405 if ( label_markup ) {
1407 PangoLayout *pl = gtk_widget_create_pango_layout (GTK_WIDGET(image), NULL);
1408 pango_layout_set_font_description (pl, gtk_widget_get_style(window)->font_desc);
1410 pango_layout_set_markup ( pl, label_markup, -1 );
1411 g_free ( label_markup );
1413 pango_layout_get_pixel_size ( pl, &ww, &hh );
1415 gdk_draw_layout ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->fg_gc[0],
1416 MARGIN_X+xx-ww/2, MARGIN_Y/2-hh/2, pl );
1417 g_object_unref ( G_OBJECT ( pl ) );
1420 gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[0],
1421 MARGIN_X+xx, MARGIN_Y, MARGIN_X+xx, MARGIN_Y+widgets->profile_height );
1425 * draw_grid_x_distance:
1427 * A common way to draw the grid with x axis labels for distance graphs
1430 static void draw_grid_x_distance ( GtkWidget *window, GtkWidget *image, PropWidgets *widgets, GdkPixmap *pix, guint ii, gdouble dd, guint xx, vik_units_distance_t dist_units )
1432 gchar *label_markup = NULL;
1433 switch ( dist_units ) {
1434 case VIK_UNITS_DISTANCE_MILES:
1436 label_markup = g_strdup_printf ( "<span size=\"small\">%d %s</span>", (guint)dd, _("miles") );
1438 label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", dd, _("miles") );
1440 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1442 label_markup = g_strdup_printf ( "<span size=\"small\">%d %s</span>", (guint)dd, _("NM") );
1444 label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", dd, _("NM") );
1447 // VIK_UNITS_DISTANCE_KILOMETRES:
1449 label_markup = g_strdup_printf ( "<span size=\"small\">%d %s</span>", (guint)dd, _("km") );
1451 label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", dd, _("km") );
1455 if ( label_markup ) {
1456 PangoLayout *pl = gtk_widget_create_pango_layout (GTK_WIDGET(image), NULL);
1457 pango_layout_set_font_description (pl, gtk_widget_get_style(window)->font_desc);
1459 pango_layout_set_markup ( pl, label_markup, -1 );
1460 g_free ( label_markup );
1462 pango_layout_get_pixel_size ( pl, &ww, &hh );
1464 gdk_draw_layout ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->fg_gc[0],
1465 MARGIN_X+xx-ww/2, MARGIN_Y/2-hh/2, pl );
1466 g_object_unref ( G_OBJECT ( pl ) );
1469 gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[0],
1470 MARGIN_X+xx, MARGIN_Y, MARGIN_X+xx, MARGIN_Y+widgets->profile_height );
1474 * clear the images (scale texts & actual graph)
1476 static void clear_images (GdkPixmap *pix, GtkWidget *window, PropWidgets *widgets)
1478 gdk_draw_rectangle(GDK_DRAWABLE(pix), gtk_widget_get_style(window)->bg_gc[0],
1479 TRUE, 0, 0, widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y);
1480 gdk_draw_rectangle(GDK_DRAWABLE(pix), gtk_widget_get_style(window)->mid_gc[0],
1481 TRUE, 0, 0, widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y);
1487 static void draw_distance_divisions ( GtkWidget *window, GtkWidget *image, GdkPixmap *pix, PropWidgets *widgets, vik_units_distance_t dist_units )
1489 // Set to display units from length in metres.
1490 gdouble length = widgets->track_length_inc_gaps;
1491 switch (dist_units) {
1492 case VIK_UNITS_DISTANCE_MILES:
1493 length = VIK_METERS_TO_MILES(length);
1495 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1496 length = VIK_METERS_TO_NAUTICAL_MILES(length);
1500 length = length/1000.0;
1503 guint index = get_distance_chunk_index ( length );
1504 gdouble dist_per_pixel = length/widgets->profile_width;
1506 for (guint i=1; chunksd[index]*i <= length; i++) {
1507 draw_grid_x_distance ( window, image, widgets, pix, index, chunksd[index]*i, (guint)(chunksd[index]*i/dist_per_pixel), dist_units );
1512 * Draw just the height profile image
1514 static void draw_elevations (GtkWidget *image, VikTrack *tr, PropWidgets *widgets )
1521 // Free previous allocation
1522 if ( widgets->altitudes )
1523 g_free ( widgets->altitudes );
1525 widgets->altitudes = vik_track_make_elevation_map ( tr, widgets->profile_width );
1527 if ( widgets->altitudes == NULL )
1530 // Convert into appropriate units
1531 vik_units_height_t height_units = a_vik_get_units_height ();
1532 if ( height_units == VIK_UNITS_HEIGHT_FEET ) {
1533 // Convert altitudes into feet units
1534 for ( i = 0; i < widgets->profile_width; i++ ) {
1535 widgets->altitudes[i] = VIK_METERS_TO_FEET(widgets->altitudes[i]);
1538 // Otherwise leave in metres
1540 minmax_array(widgets->altitudes, &widgets->min_altitude, &widgets->max_altitude, TRUE, widgets->profile_width);
1542 get_new_min_and_chunk_index (widgets->min_altitude, widgets->max_altitude, chunksa, G_N_ELEMENTS(chunksa), &widgets->draw_min_altitude, &widgets->cia);
1545 gdouble mina = widgets->draw_min_altitude;
1547 GtkWidget *window = gtk_widget_get_toplevel (widgets->elev_box);
1548 GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
1550 gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
1552 no_alt_info = gdk_gc_new ( gtk_widget_get_window(window) );
1553 gdk_color_parse ( "yellow", &color );
1554 gdk_gc_set_rgb_fg_color ( no_alt_info, &color);
1556 // Reset before redrawing
1557 clear_images (pix, window, widgets);
1560 for (i=0; i<=LINES; i++) {
1563 switch (height_units) {
1564 case VIK_UNITS_HEIGHT_METRES:
1565 sprintf(s, "%8dm", (int)(mina + (LINES-i)*chunksa[widgets->cia]));
1567 case VIK_UNITS_HEIGHT_FEET:
1568 // NB values already converted into feet
1569 sprintf(s, "%8dft", (int)(mina + (LINES-i)*chunksa[widgets->cia]));
1573 g_critical("Houston, we've had a problem. height=%d", height_units);
1576 draw_grid_y ( window, image, widgets, pix, s, i );
1579 draw_distance_divisions ( window, image, pix, widgets, a_vik_get_units_distance() );
1581 /* draw elevations */
1582 guint height = MARGIN_Y+widgets->profile_height;
1583 for ( i = 0; i < widgets->profile_width; i++ )
1584 if ( widgets->altitudes[i] == VIK_DEFAULT_ALTITUDE )
1585 gdk_draw_line ( GDK_DRAWABLE(pix), no_alt_info,
1586 i + MARGIN_X, MARGIN_Y, i + MARGIN_X, height );
1588 gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
1589 i + MARGIN_X, height, i + MARGIN_X, height-widgets->profile_height*(widgets->altitudes[i]-mina)/(chunksa[widgets->cia]*LINES) );
1591 if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_dem)) ||
1592 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_alt_gps_speed)) ) {
1594 GdkGC *dem_alt_gc = gdk_gc_new ( gtk_widget_get_window(window) );
1595 GdkGC *gps_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
1597 gdk_color_parse ( "green", &color );
1598 gdk_gc_set_rgb_fg_color ( dem_alt_gc, &color);
1600 gdk_color_parse ( "red", &color );
1601 gdk_gc_set_rgb_fg_color ( gps_speed_gc, &color);
1603 // Ensure somekind of max speed when not set
1604 if ( widgets->max_speed < 0.01 )
1605 widgets->max_speed = vik_track_get_max_speed(tr);
1607 draw_dem_alt_speed_dist(tr,
1612 widgets->max_altitude - mina,
1615 widgets->profile_width,
1616 widgets->profile_height,
1618 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_dem)),
1619 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_alt_gps_speed)));
1621 g_object_unref ( G_OBJECT(dem_alt_gc) );
1622 g_object_unref ( G_OBJECT(gps_speed_gc) );
1626 gdk_draw_rectangle(GDK_DRAWABLE(pix), gtk_widget_get_style(window)->black_gc, FALSE, MARGIN_X, MARGIN_Y, widgets->profile_width-1, widgets->profile_height-1);
1628 g_object_unref ( G_OBJECT(pix) );
1629 g_object_unref ( G_OBJECT(no_alt_info) );
1633 * Draws representative speed on the supplied pixmap
1634 * (which is the gradients graph)
1636 static void draw_speed_dist(VikTrack *tr,
1639 gdouble max_speed_in,
1646 gdouble max_speed = 0;
1647 gdouble total_length = vik_track_get_length_including_gaps(tr);
1649 // Calculate the max speed factor
1651 max_speed = max_speed_in * 110 / 100;
1654 for (iter = tr->trackpoints->next; iter; iter = iter->next) {
1656 dist += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
1657 &(VIK_TRACKPOINT(iter->prev->data)->coord) );
1658 x = (width * dist)/total_length + MARGIN_X;
1660 // This is just a speed indicator - no actual values can be inferred by user
1661 if (!isnan(VIK_TRACKPOINT(iter->data)->speed)) {
1662 int y_speed = height - (height * VIK_TRACKPOINT(iter->data)->speed)/max_speed;
1663 gdk_draw_rectangle(GDK_DRAWABLE(pix), speed_gc, TRUE, x-2, y_speed-2, 4, 4);
1670 * Draw just the gradient image
1672 static void draw_gradients (GtkWidget *image, VikTrack *tr, PropWidgets *widgets )
1676 // Free previous allocation
1677 if ( widgets->gradients )
1678 g_free ( widgets->gradients );
1680 widgets->gradients = vik_track_make_gradient_map ( tr, widgets->profile_width );
1682 if ( widgets->gradients == NULL )
1685 minmax_array(widgets->gradients, &widgets->min_gradient, &widgets->max_gradient, TRUE, widgets->profile_width);
1687 get_new_min_and_chunk_index (widgets->min_gradient, widgets->max_gradient, chunksg, G_N_ELEMENTS(chunksg), &widgets->draw_min_gradient, &widgets->cig);
1690 gdouble mina = widgets->draw_min_gradient;
1692 GtkWidget *window = gtk_widget_get_toplevel (widgets->gradient_box);
1693 GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
1695 gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
1697 // Reset before redrawing
1698 clear_images (pix, window, widgets);
1701 for (i=0; i<=LINES; i++) {
1704 sprintf(s, "%8d%%", (int)(mina + (LINES-i)*chunksg[widgets->cig]));
1706 draw_grid_y ( window, image, widgets, pix, s, i );
1709 draw_distance_divisions ( window, image, pix, widgets, a_vik_get_units_distance() );
1711 /* draw gradients */
1712 guint height = widgets->profile_height + MARGIN_Y;
1713 for ( i = 0; i < widgets->profile_width; i++ )
1714 gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
1715 i + MARGIN_X, height, i + MARGIN_X, height - widgets->profile_height*(widgets->gradients[i]-mina)/(chunksg[widgets->cig]*LINES) );
1717 if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_gradient_gps_speed)) ) {
1718 GdkGC *gps_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
1721 gdk_color_parse ( "red", &color );
1722 gdk_gc_set_rgb_fg_color ( gps_speed_gc, &color);
1724 // Ensure somekind of max speed when not set
1725 if ( widgets->max_speed < 0.01 )
1726 widgets->max_speed = vik_track_get_max_speed(tr);
1732 widgets->profile_width,
1733 widgets->profile_height,
1735 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_alt_gps_speed)));
1737 g_object_unref ( G_OBJECT(gps_speed_gc) );
1741 gdk_draw_rectangle(GDK_DRAWABLE(pix), gtk_widget_get_style(window)->black_gc, FALSE, MARGIN_X, MARGIN_Y, widgets->profile_width-1, widgets->profile_height-1);
1743 g_object_unref ( G_OBJECT(pix) );
1746 static void draw_time_lines ( GtkWidget *window, GtkWidget *image, GdkPixmap *pix, PropWidgets *widgets )
1748 guint index = get_time_chunk_index ( widgets->duration );
1749 gdouble time_per_pixel = (gdouble)(widgets->duration)/widgets->profile_width;
1751 // If stupidly long track in time - don't bother trying to draw grid lines
1752 if ( widgets->duration > chunkst[G_N_ELEMENTS(chunkst)-1]*LINES*LINES )
1755 for (guint i=1; chunkst[index]*i <= widgets->duration; i++) {
1756 draw_grid_x_time ( window, image, widgets, pix, index, chunkst[index]*i, (guint)(chunkst[index]*i/time_per_pixel) );
1761 * Draw just the speed (velocity)/time image
1763 static void draw_vt ( GtkWidget *image, VikTrack *tr, PropWidgets *widgets)
1767 // Free previous allocation
1768 if ( widgets->speeds )
1769 g_free ( widgets->speeds );
1771 widgets->speeds = vik_track_make_speed_map ( tr, widgets->profile_width );
1772 if ( widgets->speeds == NULL )
1775 widgets->duration = vik_track_get_duration ( tr, TRUE );
1776 // Negative time or other problem
1777 if ( widgets->duration <= 0 )
1780 // Convert into appropriate units
1781 vik_units_speed_t speed_units = a_vik_get_units_speed ();
1782 switch (speed_units) {
1783 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
1784 for ( i = 0; i < widgets->profile_width; i++ ) {
1785 widgets->speeds[i] = VIK_MPS_TO_KPH(widgets->speeds[i]);
1788 case VIK_UNITS_SPEED_MILES_PER_HOUR:
1789 for ( i = 0; i < widgets->profile_width; i++ ) {
1790 widgets->speeds[i] = VIK_MPS_TO_MPH(widgets->speeds[i]);
1793 case VIK_UNITS_SPEED_KNOTS:
1794 for ( i = 0; i < widgets->profile_width; i++ ) {
1795 widgets->speeds[i] = VIK_MPS_TO_KNOTS(widgets->speeds[i]);
1799 // VIK_UNITS_SPEED_METRES_PER_SECOND:
1800 // No need to convert as already in m/s
1804 GtkWidget *window = gtk_widget_get_toplevel (widgets->speed_box);
1805 GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
1807 gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
1809 minmax_array(widgets->speeds, &widgets->min_speed, &widgets->max_speed, FALSE, widgets->profile_width);
1810 if (widgets->min_speed < 0.0)
1811 widgets->min_speed = 0; /* splines sometimes give negative speeds */
1813 /* Find suitable chunk index */
1814 get_new_min_and_chunk_index (widgets->min_speed, widgets->max_speed, chunkss, G_N_ELEMENTS(chunkss), &widgets->draw_min_speed, &widgets->cis);
1817 gdouble mins = widgets->draw_min_speed;
1819 // Reset before redrawing
1820 clear_images (pix, window, widgets);
1823 for (i=0; i<=LINES; i++) {
1826 // NB: No need to convert here anymore as numbers are in the appropriate units
1827 switch (speed_units) {
1828 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
1829 sprintf(s, "%8dkm/h", (int)(mins + (LINES-i)*chunkss[widgets->cis]));
1831 case VIK_UNITS_SPEED_MILES_PER_HOUR:
1832 sprintf(s, "%8dmph", (int)(mins + (LINES-i)*chunkss[widgets->cis]));
1834 case VIK_UNITS_SPEED_METRES_PER_SECOND:
1835 sprintf(s, "%8dm/s", (int)(mins + (LINES-i)*chunkss[widgets->cis]));
1837 case VIK_UNITS_SPEED_KNOTS:
1838 sprintf(s, "%8dknots", (int)(mins + (LINES-i)*chunkss[widgets->cis]));
1842 g_critical("Houston, we've had a problem. speed=%d", speed_units);
1845 draw_grid_y ( window, image, widgets, pix, s, i );
1848 draw_time_lines ( window, image, pix, widgets );
1851 guint height = widgets->profile_height + MARGIN_Y;
1852 for ( i = 0; i < widgets->profile_width; i++ )
1853 gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
1854 i + MARGIN_X, height, i + MARGIN_X, height - widgets->profile_height*(widgets->speeds[i]-mins)/(chunkss[widgets->cis]*LINES) );
1856 if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->w_show_gps_speed)) ) {
1858 GdkGC *gps_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
1860 gdk_color_parse ( "red", &color );
1861 gdk_gc_set_rgb_fg_color ( gps_speed_gc, &color);
1863 time_t beg_time = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
1864 time_t dur = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp - beg_time;
1867 for (iter = tr->trackpoints; iter; iter = iter->next) {
1868 gdouble gps_speed = VIK_TRACKPOINT(iter->data)->speed;
1869 if (isnan(gps_speed))
1871 switch (speed_units) {
1872 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
1873 gps_speed = VIK_MPS_TO_KPH(gps_speed);
1875 case VIK_UNITS_SPEED_MILES_PER_HOUR:
1876 gps_speed = VIK_MPS_TO_MPH(gps_speed);
1878 case VIK_UNITS_SPEED_KNOTS:
1879 gps_speed = VIK_MPS_TO_KNOTS(gps_speed);
1882 // VIK_UNITS_SPEED_METRES_PER_SECOND:
1883 // No need to convert as already in m/s
1886 int x = MARGIN_X + widgets->profile_width * (VIK_TRACKPOINT(iter->data)->timestamp - beg_time) / dur;
1887 int y = height - widgets->profile_height*(gps_speed - mins)/(chunkss[widgets->cis]*LINES);
1888 gdk_draw_rectangle(GDK_DRAWABLE(pix), gps_speed_gc, TRUE, x-2, y-2, 4, 4);
1890 g_object_unref ( G_OBJECT(gps_speed_gc) );
1894 gdk_draw_rectangle(GDK_DRAWABLE(pix), gtk_widget_get_style(window)->black_gc, FALSE, MARGIN_X, MARGIN_Y, widgets->profile_width-1, widgets->profile_height-1);
1896 g_object_unref ( G_OBJECT(pix) );
1900 * Draw just the distance/time image
1902 static void draw_dt ( GtkWidget *image, VikTrack *tr, PropWidgets *widgets )
1906 // Free previous allocation
1907 if ( widgets->distances )
1908 g_free ( widgets->distances );
1910 widgets->distances = vik_track_make_distance_map ( tr, widgets->profile_width );
1911 if ( widgets->distances == NULL )
1914 // Convert into appropriate units
1915 vik_units_distance_t dist_units = a_vik_get_units_distance ();
1916 switch ( dist_units ) {
1917 case VIK_UNITS_DISTANCE_MILES:
1918 for ( i = 0; i < widgets->profile_width; i++ ) {
1919 widgets->distances[i] = VIK_METERS_TO_MILES(widgets->distances[i]);
1922 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1923 for ( i = 0; i < widgets->profile_width; i++ ) {
1924 widgets->distances[i] = VIK_METERS_TO_NAUTICAL_MILES(widgets->distances[i]);
1928 // Metres - but want in kms
1929 for ( i = 0; i < widgets->profile_width; i++ ) {
1930 widgets->distances[i] = widgets->distances[i]/1000.0;
1935 widgets->duration = vik_track_get_duration ( widgets->tr, TRUE );
1936 // Negative time or other problem
1937 if ( widgets->duration <= 0 )
1940 GtkWidget *window = gtk_widget_get_toplevel (widgets->dist_box);
1941 GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
1943 gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
1945 // easy to work out min / max of distance!
1947 // mind = 0.0; - Thus not used
1949 switch ( dist_units ) {
1950 case VIK_UNITS_DISTANCE_MILES:
1951 maxd = VIK_METERS_TO_MILES(vik_track_get_length_including_gaps (tr));
1953 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1954 maxd = VIK_METERS_TO_NAUTICAL_MILES(vik_track_get_length_including_gaps (tr));
1957 maxd = vik_track_get_length_including_gaps (tr) / 1000.0;
1961 /* Find suitable chunk index */
1962 gdouble dummy = 0.0; // expect this to remain the same! (not that it's used)
1963 get_new_min_and_chunk_index (0, maxd, chunksd, G_N_ELEMENTS(chunksd), &dummy, &widgets->cid);
1965 // Reset before redrawing
1966 clear_images (pix, window, widgets);
1969 for (i=0; i<=LINES; i++) {
1972 switch ( dist_units ) {
1973 case VIK_UNITS_DISTANCE_MILES:
1974 sprintf(s, _("%.1f miles"), ((LINES-i)*chunksd[widgets->cid]));
1976 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1977 sprintf(s, _("%.1f NM"), ((LINES-i)*chunksd[widgets->cid]));
1980 sprintf(s, _("%.1f km"), ((LINES-i)*chunksd[widgets->cid]));
1984 draw_grid_y ( window, image, widgets, pix, s, i );
1987 draw_time_lines ( window, image, pix, widgets );
1990 guint height = widgets->profile_height + MARGIN_Y;
1991 for ( i = 0; i < widgets->profile_width; i++ )
1992 gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
1993 i + MARGIN_X, height, i + MARGIN_X, height - widgets->profile_height*(widgets->distances[i])/(chunksd[widgets->cid]*LINES) );
1995 // Show speed indicator
1996 if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->w_show_dist_speed)) ) {
1997 GdkGC *dist_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
1999 gdk_color_parse ( "red", &color );
2000 gdk_gc_set_rgb_fg_color ( dist_speed_gc, &color);
2002 gdouble max_speed = 0;
2003 max_speed = widgets->max_speed * 110 / 100;
2005 // This is just an indicator - no actual values can be inferred by user
2006 for ( i = 0; i < widgets->profile_width; i++ ) {
2007 int y_speed = widgets->profile_height - (widgets->profile_height * widgets->speeds[i])/max_speed;
2008 gdk_draw_rectangle(GDK_DRAWABLE(pix), dist_speed_gc, TRUE, i+MARGIN_X-2, y_speed-2, 4, 4);
2010 g_object_unref ( G_OBJECT(dist_speed_gc) );
2014 gdk_draw_rectangle(GDK_DRAWABLE(pix), gtk_widget_get_style(window)->black_gc, FALSE, MARGIN_X, MARGIN_Y, widgets->profile_width-1, widgets->profile_height-1);
2016 g_object_unref ( G_OBJECT(pix) );
2021 * Draw just the elevation/time image
2023 static void draw_et ( GtkWidget *image, VikTrack *tr, PropWidgets *widgets )
2027 // Free previous allocation
2029 g_free ( widgets->ats );
2031 widgets->ats = vik_track_make_elevation_time_map ( tr, widgets->profile_width );
2033 if ( widgets->ats == NULL )
2036 // Convert into appropriate units
2037 vik_units_height_t height_units = a_vik_get_units_height ();
2038 if ( height_units == VIK_UNITS_HEIGHT_FEET ) {
2039 // Convert altitudes into feet units
2040 for ( i = 0; i < widgets->profile_width; i++ ) {
2041 widgets->ats[i] = VIK_METERS_TO_FEET(widgets->ats[i]);
2044 // Otherwise leave in metres
2046 minmax_array(widgets->ats, &widgets->min_altitude, &widgets->max_altitude, TRUE, widgets->profile_width);
2048 get_new_min_and_chunk_index (widgets->min_altitude, widgets->max_altitude, chunksa, G_N_ELEMENTS(chunksa), &widgets->draw_min_altitude_time, &widgets->ciat);
2051 gdouble mina = widgets->draw_min_altitude_time;
2053 widgets->duration = vik_track_get_duration ( widgets->tr, TRUE );
2054 // Negative time or other problem
2055 if ( widgets->duration <= 0 )
2058 GtkWidget *window = gtk_widget_get_toplevel (widgets->elev_time_box);
2059 GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2061 gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
2063 // Reset before redrawing
2064 clear_images (pix, window, widgets);
2067 for (i=0; i<=LINES; i++) {
2070 switch (height_units) {
2071 case VIK_UNITS_HEIGHT_METRES:
2072 sprintf(s, "%8dm", (int)(mina + (LINES-i)*chunksa[widgets->ciat]));
2074 case VIK_UNITS_HEIGHT_FEET:
2075 // NB values already converted into feet
2076 sprintf(s, "%8dft", (int)(mina + (LINES-i)*chunksa[widgets->ciat]));
2080 g_critical("Houston, we've had a problem. height=%d", height_units);
2083 draw_grid_y ( window, image, widgets, pix, s, i );
2086 draw_time_lines ( window, image, pix, widgets );
2088 /* draw elevations */
2089 guint height = widgets->profile_height + MARGIN_Y;
2090 for ( i = 0; i < widgets->profile_width; i++ )
2091 gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
2092 i + MARGIN_X, height, i + MARGIN_X, height-widgets->profile_height*(widgets->ats[i]-mina)/(chunksa[widgets->ciat]*LINES) );
2095 if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->w_show_elev_dem)) ) {
2097 GdkGC *dem_alt_gc = gdk_gc_new ( gtk_widget_get_window(window) );
2098 gdk_color_parse ( "green", &color );
2099 gdk_gc_set_rgb_fg_color ( dem_alt_gc, &color);
2101 gint h2 = widgets->profile_height + MARGIN_Y; // Adjust height for x axis labelling offset
2102 gint achunk = chunksa[widgets->ciat]*LINES;
2104 for ( i = 0; i < widgets->profile_width; i++ ) {
2105 // This could be slow doing this each time...
2106 VikTrackpoint *tp = vik_track_get_closest_tp_by_percentage_time ( widgets->tr, ((gdouble)i/(gdouble)widgets->profile_width), NULL );
2108 gint16 elev = a_dems_get_elev_by_coord(&(tp->coord), VIK_DEM_INTERPOL_SIMPLE);
2109 if ( elev != VIK_DEM_INVALID_ELEVATION ) {
2110 // Convert into height units
2111 if ( a_vik_get_units_height () == VIK_UNITS_HEIGHT_FEET )
2112 elev = VIK_METERS_TO_FEET(elev);
2113 // No conversion needed if already in metres
2115 // offset is in current height units
2118 // consider chunk size
2119 int y_alt = h2 - ((widgets->profile_height * elev)/achunk );
2120 gdk_draw_rectangle(GDK_DRAWABLE(pix), dem_alt_gc, TRUE, i+MARGIN_X-2, y_alt-2, 4, 4);
2124 g_object_unref ( G_OBJECT(dem_alt_gc) );
2128 if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->w_show_elev_speed)) ) {
2130 // This is just an indicator - no actual values can be inferred by user
2131 GdkGC *elev_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
2132 gdk_color_parse ( "red", &color );
2133 gdk_gc_set_rgb_fg_color ( elev_speed_gc, &color);
2135 gdouble max_speed = widgets->max_speed * 110 / 100;
2137 for ( i = 0; i < widgets->profile_width; i++ ) {
2138 int y_speed = widgets->profile_height - (widgets->profile_height * widgets->speeds[i])/max_speed;
2139 gdk_draw_rectangle(GDK_DRAWABLE(pix), elev_speed_gc, TRUE, i+MARGIN_X-2, y_speed-2, 4, 4);
2142 g_object_unref ( G_OBJECT(elev_speed_gc) );
2146 gdk_draw_rectangle(GDK_DRAWABLE(pix), gtk_widget_get_style(window)->black_gc, FALSE, MARGIN_X, MARGIN_Y, widgets->profile_width-1, widgets->profile_height-1);
2148 g_object_unref ( G_OBJECT(pix) );
2152 * Draw just the speed/distance image
2154 static void draw_sd ( GtkWidget *image, VikTrack *tr, PropWidgets *widgets)
2159 // Free previous allocation
2160 if ( widgets->speeds_dist )
2161 g_free ( widgets->speeds_dist );
2163 widgets->speeds_dist = vik_track_make_speed_dist_map ( tr, widgets->profile_width );
2164 if ( widgets->speeds_dist == NULL )
2167 // Convert into appropriate units
2168 vik_units_speed_t speed_units = a_vik_get_units_speed ();
2169 switch (speed_units) {
2170 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
2171 for ( i = 0; i < widgets->profile_width; i++ ) {
2172 widgets->speeds_dist[i] = VIK_MPS_TO_KPH(widgets->speeds_dist[i]);
2175 case VIK_UNITS_SPEED_MILES_PER_HOUR:
2176 for ( i = 0; i < widgets->profile_width; i++ ) {
2177 widgets->speeds_dist[i] = VIK_MPS_TO_MPH(widgets->speeds_dist[i]);
2180 case VIK_UNITS_SPEED_KNOTS:
2181 for ( i = 0; i < widgets->profile_width; i++ ) {
2182 widgets->speeds_dist[i] = VIK_MPS_TO_KNOTS(widgets->speeds_dist[i]);
2186 // VIK_UNITS_SPEED_METRES_PER_SECOND:
2187 // No need to convert as already in m/s
2191 GtkWidget *window = gtk_widget_get_toplevel (widgets->speed_dist_box);
2192 GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2194 gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
2196 // OK to resuse min_speed here
2197 minmax_array(widgets->speeds_dist, &widgets->min_speed, &widgets->max_speed_dist, FALSE, widgets->profile_width);
2198 if (widgets->min_speed < 0.0)
2199 widgets->min_speed = 0; /* splines sometimes give negative speeds */
2201 /* Find suitable chunk index */
2202 get_new_min_and_chunk_index (widgets->min_speed, widgets->max_speed_dist, chunkss, G_N_ELEMENTS(chunkss), &widgets->draw_min_speed, &widgets->cisd);
2205 mins = widgets->draw_min_speed;
2207 // Reset before redrawing
2208 clear_images (pix, window, widgets);
2211 for (i=0; i<=LINES; i++) {
2214 // NB: No need to convert here anymore as numbers are in the appropriate units
2215 switch (speed_units) {
2216 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
2217 sprintf(s, "%8dkm/h", (int)(mins + (LINES-i)*chunkss[widgets->cisd]));
2219 case VIK_UNITS_SPEED_MILES_PER_HOUR:
2220 sprintf(s, "%8dmph", (int)(mins + (LINES-i)*chunkss[widgets->cisd]));
2222 case VIK_UNITS_SPEED_METRES_PER_SECOND:
2223 sprintf(s, "%8dm/s", (int)(mins + (LINES-i)*chunkss[widgets->cisd]));
2225 case VIK_UNITS_SPEED_KNOTS:
2226 sprintf(s, "%8dknots", (int)(mins + (LINES-i)*chunkss[widgets->cisd]));
2230 g_critical("Houston, we've had a problem. speed=%d", speed_units);
2233 draw_grid_y ( window, image, widgets, pix, s, i );
2236 draw_distance_divisions ( window, image, pix, widgets, a_vik_get_units_distance() );
2239 guint height = widgets->profile_height + MARGIN_Y;
2240 for ( i = 0; i < widgets->profile_width; i++ )
2241 gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
2242 i + MARGIN_X, height, i + MARGIN_X, height - widgets->profile_height*(widgets->speeds_dist[i]-mins)/(chunkss[widgets->cisd]*LINES) );
2245 if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->w_show_sd_gps_speed)) ) {
2247 GdkGC *gps_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
2249 gdk_color_parse ( "red", &color );
2250 gdk_gc_set_rgb_fg_color ( gps_speed_gc, &color);
2252 gdouble dist = vik_track_get_length_including_gaps(tr);
2253 gdouble dist_tp = 0.0;
2255 GList *iter = tr->trackpoints;
2256 for (iter = iter->next; iter; iter = iter->next) {
2257 gdouble gps_speed = VIK_TRACKPOINT(iter->data)->speed;
2258 if (isnan(gps_speed))
2260 switch (speed_units) {
2261 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
2262 gps_speed = VIK_MPS_TO_KPH(gps_speed);
2264 case VIK_UNITS_SPEED_MILES_PER_HOUR:
2265 gps_speed = VIK_MPS_TO_MPH(gps_speed);
2267 case VIK_UNITS_SPEED_KNOTS:
2268 gps_speed = VIK_MPS_TO_KNOTS(gps_speed);
2271 // VIK_UNITS_SPEED_METRES_PER_SECOND:
2272 // No need to convert as already in m/s
2275 dist_tp += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord), &(VIK_TRACKPOINT(iter->prev->data)->coord) );
2276 int x = MARGIN_X + (widgets->profile_width * dist_tp / dist);
2277 int y = height - widgets->profile_height*(gps_speed - mins)/(chunkss[widgets->cisd]*LINES);
2278 gdk_draw_rectangle(GDK_DRAWABLE(pix), gps_speed_gc, TRUE, x-2, y-2, 4, 4);
2280 g_object_unref ( G_OBJECT(gps_speed_gc) );
2284 gdk_draw_rectangle(GDK_DRAWABLE(pix), gtk_widget_get_style(window)->black_gc, FALSE, MARGIN_X, MARGIN_Y, widgets->profile_width-1, widgets->profile_height-1);
2286 g_object_unref ( G_OBJECT(pix) );
2293 static void draw_all_graphs ( GtkWidget *widget, PropWidgets *widgets, gboolean resized )
2295 // Draw graphs even if they are not visible
2297 GList *child = NULL;
2298 GtkWidget *image = NULL;
2299 GtkWidget *window = gtk_widget_get_toplevel(widget);
2301 gdouble pc_blob = NAN;
2304 if (widgets->elev_box != NULL) {
2306 // Saved image no longer any good as we've resized, so we remove it here
2307 if (resized && widgets->elev_graph_saved_img.img) {
2308 g_object_unref(widgets->elev_graph_saved_img.img);
2309 widgets->elev_graph_saved_img.img = NULL;
2310 widgets->elev_graph_saved_img.saved = FALSE;
2313 child = gtk_container_get_children(GTK_CONTAINER(widgets->elev_box));
2314 draw_elevations (GTK_WIDGET(child->data), widgets->tr, widgets );
2316 image = GTK_WIDGET(child->data);
2319 // Ensure marker or blob are redrawn if necessary
2320 if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2322 pc = tp_percentage_by_distance ( widgets->tr, widgets->marker_tp, widgets->track_length_inc_gaps );
2323 gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2325 if (widgets->is_blob_drawn) {
2326 pc_blob = tp_percentage_by_distance ( widgets->tr, widgets->blob_tp, widgets->track_length_inc_gaps );
2327 if (!isnan(pc_blob)) {
2328 x_blob = (pc_blob * widgets->profile_width);
2330 y_blob = blobby_altitude (x_blob, widgets);
2333 gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2335 marker_x = (pc * widgets->profile_width) + MARGIN_X;
2338 save_image_and_draw_graph_marks (image,
2340 gtk_widget_get_style(window)->black_gc,
2343 &widgets->elev_graph_saved_img,
2344 widgets->profile_width,
2345 widgets->profile_height,
2346 &widgets->is_marker_drawn,
2347 &widgets->is_blob_drawn);
2352 if (widgets->gradient_box != NULL) {
2354 // Saved image no longer any good as we've resized, so we remove it here
2355 if (resized && widgets->gradient_graph_saved_img.img) {
2356 g_object_unref(widgets->gradient_graph_saved_img.img);
2357 widgets->gradient_graph_saved_img.img = NULL;
2358 widgets->gradient_graph_saved_img.saved = FALSE;
2361 child = gtk_container_get_children(GTK_CONTAINER(widgets->gradient_box));
2362 draw_gradients (GTK_WIDGET(child->data), widgets->tr, widgets );
2364 image = GTK_WIDGET(child->data);
2367 // Ensure marker or blob are redrawn if necessary
2368 if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2370 pc = tp_percentage_by_distance ( widgets->tr, widgets->marker_tp, widgets->track_length_inc_gaps );
2371 gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2373 if (widgets->is_blob_drawn) {
2374 pc_blob = tp_percentage_by_distance ( widgets->tr, widgets->blob_tp, widgets->track_length_inc_gaps );
2375 if (!isnan(pc_blob)) {
2376 x_blob = (pc_blob * widgets->profile_width);
2378 y_blob = blobby_gradient (x_blob, widgets);
2381 gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2383 marker_x = (pc * widgets->profile_width) + MARGIN_X;
2386 save_image_and_draw_graph_marks (image,
2388 gtk_widget_get_style(window)->black_gc,
2391 &widgets->gradient_graph_saved_img,
2392 widgets->profile_width,
2393 widgets->profile_height,
2394 &widgets->is_marker_drawn,
2395 &widgets->is_blob_drawn);
2400 if (widgets->speed_box != NULL) {
2402 // Saved image no longer any good as we've resized
2403 if (resized && widgets->speed_graph_saved_img.img) {
2404 g_object_unref(widgets->speed_graph_saved_img.img);
2405 widgets->speed_graph_saved_img.img = NULL;
2406 widgets->speed_graph_saved_img.saved = FALSE;
2409 child = gtk_container_get_children(GTK_CONTAINER(widgets->speed_box));
2410 draw_vt (GTK_WIDGET(child->data), widgets->tr, widgets );
2412 image = GTK_WIDGET(child->data);
2415 // Ensure marker or blob are redrawn if necessary
2416 if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2418 pc = tp_percentage_by_time ( widgets->tr, widgets->marker_tp );
2420 gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2422 if (widgets->is_blob_drawn) {
2423 pc_blob = tp_percentage_by_time ( widgets->tr, widgets->blob_tp );
2424 if (!isnan(pc_blob)) {
2425 x_blob = (pc_blob * widgets->profile_width);
2428 y_blob = blobby_speed (x_blob, widgets);
2431 gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2433 marker_x = (pc * widgets->profile_width) + MARGIN_X;
2436 save_image_and_draw_graph_marks (image,
2438 gtk_widget_get_style(window)->black_gc,
2441 &widgets->speed_graph_saved_img,
2442 widgets->profile_width,
2443 widgets->profile_height,
2444 &widgets->is_marker_drawn,
2445 &widgets->is_blob_drawn);
2450 if (widgets->dist_box != NULL) {
2452 // Saved image no longer any good as we've resized
2453 if (resized && widgets->dist_graph_saved_img.img) {
2454 g_object_unref(widgets->dist_graph_saved_img.img);
2455 widgets->dist_graph_saved_img.img = NULL;
2456 widgets->dist_graph_saved_img.saved = FALSE;
2459 child = gtk_container_get_children(GTK_CONTAINER(widgets->dist_box));
2460 draw_dt (GTK_WIDGET(child->data), widgets->tr, widgets );
2462 image = GTK_WIDGET(child->data);
2465 // Ensure marker or blob are redrawn if necessary
2466 if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2468 pc = tp_percentage_by_time ( widgets->tr, widgets->marker_tp );
2470 gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2472 if (widgets->is_blob_drawn) {
2473 pc_blob = tp_percentage_by_time ( widgets->tr, widgets->blob_tp );
2474 if (!isnan(pc_blob)) {
2475 x_blob = (pc_blob * widgets->profile_width);
2478 y_blob = blobby_distance (x_blob, widgets);
2481 gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2483 marker_x = (pc * widgets->profile_width) + MARGIN_X;
2486 save_image_and_draw_graph_marks (image,
2488 gtk_widget_get_style(window)->black_gc,
2491 &widgets->dist_graph_saved_img,
2492 widgets->profile_width,
2493 widgets->profile_height,
2494 &widgets->is_marker_drawn,
2495 &widgets->is_blob_drawn);
2499 // Draw Elevations in timely manner
2500 if (widgets->elev_time_box != NULL) {
2502 // Saved image no longer any good as we've resized
2503 if (resized && widgets->elev_time_graph_saved_img.img) {
2504 g_object_unref(widgets->elev_time_graph_saved_img.img);
2505 widgets->elev_time_graph_saved_img.img = NULL;
2506 widgets->elev_time_graph_saved_img.saved = FALSE;
2509 child = gtk_container_get_children(GTK_CONTAINER(widgets->elev_time_box));
2510 draw_et (GTK_WIDGET(child->data), widgets->tr, widgets );
2512 image = GTK_WIDGET(child->data);
2515 // Ensure marker or blob are redrawn if necessary
2516 if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2518 pc = tp_percentage_by_time ( widgets->tr, widgets->marker_tp );
2520 gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2522 if (widgets->is_blob_drawn) {
2523 pc_blob = tp_percentage_by_time ( widgets->tr, widgets->blob_tp );
2524 if (!isnan(pc_blob)) {
2525 x_blob = (pc_blob * widgets->profile_width);
2527 y_blob = blobby_altitude_time (x_blob, widgets);
2530 gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2532 marker_x = (pc * widgets->profile_width) + MARGIN_X;
2535 save_image_and_draw_graph_marks (image,
2537 gtk_widget_get_style(window)->black_gc,
2540 &widgets->elev_time_graph_saved_img,
2541 widgets->profile_width,
2542 widgets->profile_height,
2543 &widgets->is_marker_drawn,
2544 &widgets->is_blob_drawn);
2548 // Draw speed distances
2549 if (widgets->speed_dist_box != NULL) {
2551 // Saved image no longer any good as we've resized, so we remove it here
2552 if (resized && widgets->speed_dist_graph_saved_img.img) {
2553 g_object_unref(widgets->speed_dist_graph_saved_img.img);
2554 widgets->speed_dist_graph_saved_img.img = NULL;
2555 widgets->speed_dist_graph_saved_img.saved = FALSE;
2558 child = gtk_container_get_children(GTK_CONTAINER(widgets->speed_dist_box));
2559 draw_sd (GTK_WIDGET(child->data), widgets->tr, widgets );
2561 image = GTK_WIDGET(child->data);
2564 // Ensure marker or blob are redrawn if necessary
2565 if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2567 pc = tp_percentage_by_distance ( widgets->tr, widgets->marker_tp, widgets->track_length_inc_gaps );
2568 gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2570 if (widgets->is_blob_drawn) {
2571 pc_blob = tp_percentage_by_distance ( widgets->tr, widgets->blob_tp, widgets->track_length_inc_gaps );
2572 if (!isnan(pc_blob)) {
2573 x_blob = (pc_blob * widgets->profile_width);
2575 y_blob = blobby_speed_dist (x_blob, widgets);
2578 gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2580 marker_x = (pc * widgets->profile_width) + MARGIN_X;
2583 save_image_and_draw_graph_marks (image,
2585 gtk_widget_get_style(window)->black_gc,
2588 &widgets->speed_dist_graph_saved_img,
2589 widgets->profile_width,
2590 widgets->profile_height,
2591 &widgets->is_marker_drawn,
2592 &widgets->is_blob_drawn);
2599 * Configure/Resize the profile & speed/time images
2601 static gboolean configure_event ( GtkWidget *widget, GdkEventConfigure *event, PropWidgets *widgets )
2603 if (widgets->configure_dialog) {
2604 // Determine size offsets between dialog size and size for images
2605 // Only on the initialisation of the dialog
2606 widgets->profile_width_offset = event->width - widgets->profile_width;
2607 widgets->profile_height_offset = event->height - widgets->profile_height;
2608 widgets->configure_dialog = FALSE;
2610 // Without this the settting, the dialog will only grow in vertical size - one can not then make it smaller!
2611 gtk_widget_set_size_request ( widget, widgets->profile_width+widgets->profile_width_offset, widgets->profile_height+widgets->profile_height_offset );
2613 // Allow resizing back down to a minimal size (especially useful if the initial size has been made bigger after restoring from the saved settings)
2614 GdkGeometry geom = { 600+widgets->profile_width_offset, 300+widgets->profile_height_offset, 0, 0, 0, 0, 0, 0, 0, 0, GDK_GRAVITY_STATIC };
2615 gdk_window_set_geometry_hints ( gtk_widget_get_window(widget), &geom, GDK_HINT_MIN_SIZE );
2618 widgets->profile_width_old = widgets->profile_width;
2619 widgets->profile_height_old = widgets->profile_height;
2622 // Now adjust From Dialog size to get image size
2623 widgets->profile_width = event->width - widgets->profile_width_offset;
2624 widgets->profile_height = event->height - widgets->profile_height_offset;
2626 // ATM we receive configure_events when the dialog is moved and so no further action is necessary
2627 if ( !widgets->configure_dialog &&
2628 (widgets->profile_width_old == widgets->profile_width) && (widgets->profile_height_old == widgets->profile_height) )
2632 draw_all_graphs ( widget, widgets, TRUE );
2638 * Create height profile widgets including the image and callbacks
2640 GtkWidget *vik_trw_layer_create_profile ( GtkWidget *window, PropWidgets *widgets, gdouble *min_alt, gdouble *max_alt)
2644 GtkWidget *eventbox;
2647 widgets->altitudes = vik_track_make_elevation_map ( widgets->tr, widgets->profile_width );
2649 if ( widgets->altitudes == NULL ) {
2650 *min_alt = *max_alt = VIK_DEFAULT_ALTITUDE;
2654 minmax_array(widgets->altitudes, min_alt, max_alt, TRUE, widgets->profile_width);
2656 pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2657 image = gtk_image_new_from_pixmap ( pix, NULL );
2659 g_object_unref ( G_OBJECT(pix) );
2661 eventbox = gtk_event_box_new ();
2662 g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_profile_click), widgets );
2663 g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_profile_move), widgets );
2664 gtk_container_add ( GTK_CONTAINER(eventbox), image );
2665 gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_STRUCTURE_MASK);
2671 * Create height profile widgets including the image and callbacks
2673 GtkWidget *vik_trw_layer_create_gradient ( GtkWidget *window, PropWidgets *widgets)
2677 GtkWidget *eventbox;
2680 widgets->gradients = vik_track_make_gradient_map ( widgets->tr, widgets->profile_width );
2682 if ( widgets->gradients == NULL ) {
2686 pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2687 image = gtk_image_new_from_pixmap ( pix, NULL );
2689 g_object_unref ( G_OBJECT(pix) );
2691 eventbox = gtk_event_box_new ();
2692 g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_gradient_click), widgets );
2693 g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_gradient_move), widgets );
2694 gtk_container_add ( GTK_CONTAINER(eventbox), image );
2695 gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_STRUCTURE_MASK);
2701 * Create speed/time widgets including the image and callbacks
2703 GtkWidget *vik_trw_layer_create_vtdiag ( GtkWidget *window, PropWidgets *widgets)
2707 GtkWidget *eventbox;
2710 widgets->speeds = vik_track_make_speed_map ( widgets->tr, widgets->profile_width );
2711 if ( widgets->speeds == NULL )
2714 pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2715 image = gtk_image_new_from_pixmap ( pix, NULL );
2718 /* XXX this can go out, it's just a helpful dev tool */
2721 GdkGC **colors[8] = { gtk_widget_get_style(window)->bg_gc,
2722 gtk_widget_get_style(window)->fg_gc,
2723 gtk_widget_get_style(window)->light_gc,
2724 gtk_widget_get_style(window)->dark_gc,
2725 gtk_widget_get_style(window)->mid_gc,
2726 gtk_widget_get_style(window)->text_gc,
2727 gtk_widget_get_style(window)->base_gc,
2728 gtk_widget_get_style(window)->text_aa_gc };
2729 for (i=0; i<5; i++) {
2730 for (j=0; j<8; j++) {
2731 gdk_draw_rectangle(GDK_DRAWABLE(pix), colors[j][i],
2732 TRUE, i*20, j*20, 20, 20);
2733 gdk_draw_rectangle(GDK_DRAWABLE(pix), gtk_widget_get_style(window)->black_gc,
2734 FALSE, i*20, j*20, 20, 20);
2740 g_object_unref ( G_OBJECT(pix) );
2742 eventbox = gtk_event_box_new ();
2743 g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_vt_click), widgets );
2744 g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_vt_move), widgets );
2745 gtk_container_add ( GTK_CONTAINER(eventbox), image );
2746 gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
2752 * Create distance / time widgets including the image and callbacks
2754 GtkWidget *vik_trw_layer_create_dtdiag ( GtkWidget *window, PropWidgets *widgets)
2758 GtkWidget *eventbox;
2761 widgets->distances = vik_track_make_distance_map ( widgets->tr, widgets->profile_width );
2762 if ( widgets->distances == NULL )
2765 pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2766 image = gtk_image_new_from_pixmap ( pix, NULL );
2768 g_object_unref ( G_OBJECT(pix) );
2770 eventbox = gtk_event_box_new ();
2771 g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_dt_click), widgets );
2772 g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_dt_move), widgets );
2773 //g_signal_connect_swapped ( G_OBJECT(eventbox), "destroy", G_CALLBACK(g_free), widgets );
2774 gtk_container_add ( GTK_CONTAINER(eventbox), image );
2775 gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
2781 * Create elevation / time widgets including the image and callbacks
2783 GtkWidget *vik_trw_layer_create_etdiag ( GtkWidget *window, PropWidgets *widgets)
2787 GtkWidget *eventbox;
2790 widgets->ats = vik_track_make_elevation_time_map ( widgets->tr, widgets->profile_width );
2791 if ( widgets->ats == NULL )
2794 pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2795 image = gtk_image_new_from_pixmap ( pix, NULL );
2797 g_object_unref ( G_OBJECT(pix) );
2799 eventbox = gtk_event_box_new ();
2800 g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_et_click), widgets );
2801 g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_et_move), widgets );
2802 gtk_container_add ( GTK_CONTAINER(eventbox), image );
2803 gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
2809 * Create speed/distance widgets including the image and callbacks
2811 GtkWidget *vik_trw_layer_create_sddiag ( GtkWidget *window, PropWidgets *widgets)
2815 GtkWidget *eventbox;
2818 widgets->speeds_dist = vik_track_make_speed_dist_map ( widgets->tr, widgets->profile_width );
2819 if ( widgets->speeds_dist == NULL )
2822 pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2823 image = gtk_image_new_from_pixmap ( pix, NULL );
2825 g_object_unref ( G_OBJECT(pix) );
2827 eventbox = gtk_event_box_new ();
2828 g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_sd_click), widgets );
2829 g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_sd_move), widgets );
2830 gtk_container_add ( GTK_CONTAINER(eventbox), image );
2831 gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
2837 #define VIK_SETTINGS_TRACK_PROFILE_WIDTH "track_profile_display_width"
2838 #define VIK_SETTINGS_TRACK_PROFILE_HEIGHT "track_profile_display_height"
2840 static void save_values ( PropWidgets *widgets )
2843 a_settings_set_integer ( VIK_SETTINGS_TRACK_PROFILE_WIDTH, widgets->profile_width );
2844 a_settings_set_integer ( VIK_SETTINGS_TRACK_PROFILE_HEIGHT, widgets->profile_height );
2846 // Just for this session ATM
2847 if ( widgets->w_show_dem )
2848 show_dem = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_dem) );
2849 if ( widgets->w_show_alt_gps_speed )
2850 show_alt_gps_speed = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_alt_gps_speed) );
2851 if ( widgets->w_show_gps_speed )
2852 show_gps_speed = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_gps_speed) );
2853 if ( widgets->w_show_gradient_gps_speed )
2854 show_gradient_gps_speed = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_gradient_gps_speed) );
2855 if ( widgets->w_show_dist_speed )
2856 show_dist_speed = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_dist_speed) );
2857 if ( widgets->w_show_elev_dem )
2858 show_elev_dem = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_elev_dem) );
2859 if ( widgets->w_show_elev_speed )
2860 show_elev_speed = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_elev_speed) );
2861 if ( widgets->w_show_sd_gps_speed )
2862 show_sd_gps_speed = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_sd_gps_speed) );
2865 static void destroy_cb ( GtkDialog *dialog, PropWidgets *widgets )
2867 save_values(widgets);
2868 prop_widgets_free(widgets);
2871 static void propwin_response_cb( GtkDialog *dialog, gint resp, PropWidgets *widgets )
2873 VikTrack *tr = widgets->tr;
2874 VikTrwLayer *vtl = widgets->vtl;
2875 gboolean keep_dialog = FALSE;
2877 /* FIXME: check and make sure the track still exists before doing anything to it */
2878 /* Note: destroying diaglog (eg, parent window exit) won't give "response" */
2880 case GTK_RESPONSE_DELETE_EVENT: /* received delete event (not from buttons) */
2881 case GTK_RESPONSE_REJECT:
2883 case GTK_RESPONSE_ACCEPT:
2884 vik_track_set_comment(tr, gtk_entry_get_text(GTK_ENTRY(widgets->w_comment)));
2885 vik_track_set_description(tr, gtk_entry_get_text(GTK_ENTRY(widgets->w_description)));
2886 vik_track_set_source(tr, gtk_entry_get_text(GTK_ENTRY(widgets->w_source)));
2887 vik_track_set_type(tr, gtk_entry_get_text(GTK_ENTRY(widgets->w_type)));
2888 gtk_color_button_get_color ( GTK_COLOR_BUTTON(widgets->w_color), &(tr->color) );
2889 tr->draw_name_mode = gtk_combo_box_get_active ( GTK_COMBO_BOX(widgets->w_namelabel) );
2890 tr->max_number_dist_labels = gtk_spin_button_get_value_as_int ( GTK_SPIN_BUTTON(widgets->w_number_distlabels) );
2891 trw_layer_update_treeview ( widgets->vtl, widgets->tr );
2892 vik_layer_emit_update ( VIK_LAYER(vtl) );
2894 case VIK_TRW_LAYER_PROPWIN_REVERSE:
2895 vik_track_reverse(tr);
2896 vik_layer_emit_update ( VIK_LAYER(vtl) );
2898 case VIK_TRW_LAYER_PROPWIN_DEL_DUP:
2899 vik_track_remove_dup_points(tr); // NB ignore the returned answer
2900 // As we could have seen the nuber of dulplicates that would be deleted in the properties statistics tab,
2901 // choose not to inform the user unnecessarily
2903 /* above operation could have deleted current_tp or last_tp */
2904 trw_layer_cancel_tps_of_track ( vtl, tr );
2905 vik_layer_emit_update ( VIK_LAYER(vtl) );
2907 case VIK_TRW_LAYER_PROPWIN_SPLIT:
2909 /* get new tracks, add them and then the delete old one. old can still exist on clipboard. */
2912 VikTrack **tracks = vik_track_split_into_segments(tr, &ntracks);
2915 for ( i = 0; i < ntracks; i++ )
2918 new_tr_name = trw_layer_new_unique_sublayer_name ( vtl,
2919 widgets->tr->is_route ? VIK_TRW_LAYER_SUBLAYER_ROUTE : VIK_TRW_LAYER_SUBLAYER_TRACK,
2921 if ( widgets->tr->is_route )
2922 vik_trw_layer_add_route ( vtl, new_tr_name, tracks[i] );
2924 vik_trw_layer_add_track ( vtl, new_tr_name, tracks[i] );
2925 vik_track_calculate_bounds ( tracks[i] );
2927 g_free ( new_tr_name );
2933 /* Don't let track destroy this dialog */
2934 vik_track_clear_property_dialog(tr);
2935 if ( widgets->tr->is_route )
2936 vik_trw_layer_delete_route ( vtl, tr );
2938 vik_trw_layer_delete_track ( vtl, tr );
2939 vik_layer_emit_update ( VIK_LAYER(vtl) ); /* chase thru the hoops */
2943 case VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER:
2945 GList *iter = tr->trackpoints;
2946 while ((iter = iter->next)) {
2947 if (widgets->marker_tp == VIK_TRACKPOINT(iter->data))
2951 a_dialog_msg(VIK_GTK_WINDOW_FROM_LAYER(vtl), GTK_MESSAGE_ERROR,
2952 _("Failed spliting track. Track unchanged"), NULL);
2957 gchar *r_name = trw_layer_new_unique_sublayer_name(vtl,
2958 widgets->tr->is_route ? VIK_TRW_LAYER_SUBLAYER_ROUTE : VIK_TRW_LAYER_SUBLAYER_TRACK,
2960 iter->prev->next = NULL;
2962 VikTrack *tr_right = vik_track_new();
2964 vik_track_set_comment ( tr_right, tr->comment );
2965 tr_right->visible = tr->visible;
2966 tr_right->is_route = tr->is_route;
2967 tr_right->trackpoints = iter;
2969 if ( widgets->tr->is_route )
2970 vik_trw_layer_add_route(vtl, r_name, tr_right);
2972 vik_trw_layer_add_track(vtl, r_name, tr_right);
2973 vik_track_calculate_bounds ( tr );
2974 vik_track_calculate_bounds ( tr_right );
2978 vik_layer_emit_update ( VIK_LAYER(vtl) );
2982 fprintf(stderr, "DEBUG: unknown response\n");
2986 /* Keep same behaviour for now: destroy dialog if click on any button */
2988 vik_track_clear_property_dialog(tr);
2989 gtk_widget_destroy ( GTK_WIDGET(dialog) );
2994 * Force a redraw when checkbutton has been toggled to show/hide that information
2996 static void checkbutton_toggle_cb ( GtkToggleButton *togglebutton, PropWidgets *widgets, gpointer dummy )
2998 // Even though not resized, we'll pretend it is -
2999 // as this invalidates the saved images (since the image may have changed)
3000 draw_all_graphs ( widgets->dialog, widgets, TRUE );
3004 * Create the widgets for the given graph tab
3006 static GtkWidget *create_graph_page ( GtkWidget *graph,
3007 const gchar *markup,
3009 const gchar *markup2,
3011 const gchar *markup3,
3013 GtkWidget *checkbutton1,
3014 gboolean checkbutton1_default,
3015 GtkWidget *checkbutton2,
3016 gboolean checkbutton2_default )
3018 GtkWidget *hbox = gtk_hbox_new ( FALSE, 10 );
3019 GtkWidget *vbox = gtk_vbox_new ( FALSE, 10 );
3020 GtkWidget *label = gtk_label_new (NULL);
3021 GtkWidget *label2 = gtk_label_new (NULL);
3022 GtkWidget *label3 = gtk_label_new (NULL);
3023 gtk_box_pack_start (GTK_BOX(vbox), graph, FALSE, FALSE, 0);
3024 gtk_label_set_markup ( GTK_LABEL(label), markup );
3025 gtk_label_set_markup ( GTK_LABEL(label2), markup2 );
3026 gtk_label_set_markup ( GTK_LABEL(label3), markup3 );
3027 gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, FALSE, 0);
3028 gtk_box_pack_start (GTK_BOX(hbox), value, FALSE, FALSE, 0);
3029 gtk_box_pack_start (GTK_BOX(hbox), label2, FALSE, FALSE, 0);
3030 gtk_box_pack_start (GTK_BOX(hbox), value2, FALSE, FALSE, 0);
3032 gtk_box_pack_start (GTK_BOX(hbox), label3, FALSE, FALSE, 0);
3033 gtk_box_pack_start (GTK_BOX(hbox), value3, FALSE, FALSE, 0);
3036 gtk_box_pack_end (GTK_BOX(hbox), checkbutton2, FALSE, FALSE, 0);
3037 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(checkbutton2), checkbutton2_default);
3040 gtk_box_pack_end (GTK_BOX(hbox), checkbutton1, FALSE, FALSE, 0);
3041 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(checkbutton1), checkbutton1_default);
3043 gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
3048 static GtkWidget *create_table (int cnt, char *labels[], GtkWidget *contents[])
3053 table = GTK_TABLE(gtk_table_new (cnt, 2, FALSE));
3054 gtk_table_set_col_spacing (table, 0, 10);
3055 for (i=0; i<cnt; i++) {
3058 // Settings so the text positioning only moves around vertically when the dialog is resized
3059 // This also gives more room to see the track comment
3060 label = gtk_label_new(NULL);
3061 gtk_misc_set_alignment ( GTK_MISC(label), 1, 0.5 ); // Position text centrally in vertical plane
3062 gtk_label_set_markup ( GTK_LABEL(label), _(labels[i]) );
3063 gtk_table_attach ( table, label, 0, 1, i, i+1, GTK_FILL, GTK_SHRINK, 0, 0 );
3064 if (GTK_IS_MISC(contents[i])) {
3065 gtk_misc_set_alignment ( GTK_MISC(contents[i]), 0, 0.5 );
3067 if ( GTK_IS_COLOR_BUTTON(contents[i]) || GTK_IS_COMBO_BOX(contents[i]) )
3068 // Buttons compressed - otherwise look weird (to me) if vertically massive
3069 gtk_table_attach ( table, contents[i], 1, 2, i, i+1, GTK_FILL, GTK_SHRINK, 0, 5 );
3071 // Expand for comments + descriptions / labels
3072 gtk_table_attach_defaults ( table, contents[i], 1, 2, i, i+1 );
3075 return GTK_WIDGET (table);
3078 void vik_trw_layer_propwin_run ( GtkWindow *parent,
3083 gboolean start_on_stats )
3085 PropWidgets *widgets = prop_widgets_new();
3091 gint profile_size_value;
3092 // Ensure minimum values
3093 widgets->profile_width = 600;
3094 if ( a_settings_get_integer ( VIK_SETTINGS_TRACK_PROFILE_WIDTH, &profile_size_value ) )
3095 if ( profile_size_value > widgets->profile_width )
3096 widgets->profile_width = profile_size_value;
3098 widgets->profile_height = 300;
3099 if ( a_settings_get_integer ( VIK_SETTINGS_TRACK_PROFILE_HEIGHT, &profile_size_value ) )
3100 if ( profile_size_value > widgets->profile_height )
3101 widgets->profile_height = profile_size_value;
3103 gchar *title = g_strdup_printf(_("%s - Track Properties"), tr->name);
3104 GtkWidget *dialog = gtk_dialog_new_with_buttons (title,
3106 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
3107 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
3108 _("Split at _Marker"), VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER,
3109 _("Split _Segments"), VIK_TRW_LAYER_PROPWIN_SPLIT,
3110 _("_Reverse"), VIK_TRW_LAYER_PROPWIN_REVERSE,
3111 _("_Delete Dupl."), VIK_TRW_LAYER_PROPWIN_DEL_DUP,
3112 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
3114 widgets->dialog = dialog;
3115 g_signal_connect( G_OBJECT(dialog), "response", G_CALLBACK(propwin_response_cb), widgets);
3123 gdouble min_alt, max_alt;
3124 widgets->elev_box = vik_trw_layer_create_profile(GTK_WIDGET(parent), widgets, &min_alt, &max_alt);
3125 widgets->gradient_box = vik_trw_layer_create_gradient(GTK_WIDGET(parent), widgets);
3126 widgets->speed_box = vik_trw_layer_create_vtdiag(GTK_WIDGET(parent), widgets);
3127 widgets->dist_box = vik_trw_layer_create_dtdiag(GTK_WIDGET(parent), widgets);
3128 widgets->elev_time_box = vik_trw_layer_create_etdiag(GTK_WIDGET(parent), widgets);
3129 widgets->speed_dist_box = vik_trw_layer_create_sddiag(GTK_WIDGET(parent), widgets);
3130 GtkWidget *graphs = gtk_notebook_new();
3132 GtkWidget *content_prop[20];
3135 static gchar *label_texts[] = {
3136 N_("<b>Comment:</b>"),
3137 N_("<b>Description:</b>"),
3138 N_("<b>Source:</b>"),
3140 N_("<b>Color:</b>"),
3141 N_("<b>Draw Name:</b>"),
3142 N_("<b>Distance Labels:</b>"),
3144 static gchar *stats_texts[] = {
3145 N_("<b>Track Length:</b>"),
3146 N_("<b>Trackpoints:</b>"),
3147 N_("<b>Segments:</b>"),
3148 N_("<b>Duplicate Points:</b>"),
3149 N_("<b>Max Speed:</b>"),
3150 N_("<b>Avg. Speed:</b>"),
3151 N_("<b>Moving Avg. Speed:</b>"),
3152 N_("<b>Avg. Dist. Between TPs:</b>"),
3153 N_("<b>Elevation Range:</b>"),
3154 N_("<b>Total Elevation Gain/Loss:</b>"),
3155 N_("<b>Start:</b>"),
3157 N_("<b>Duration:</b>"),
3159 static gchar tmp_buf[50];
3163 widgets->w_comment = gtk_entry_new ();
3165 gtk_entry_set_text ( GTK_ENTRY(widgets->w_comment), tr->comment );
3166 content_prop[cnt_prop++] = widgets->w_comment;
3168 widgets->w_description = gtk_entry_new ();
3169 if ( tr->description )
3170 gtk_entry_set_text ( GTK_ENTRY(widgets->w_description), tr->description );
3171 content_prop[cnt_prop++] = widgets->w_description;
3173 widgets->w_source = gtk_entry_new ();
3175 gtk_entry_set_text ( GTK_ENTRY(widgets->w_source), tr->source );
3176 content_prop[cnt_prop++] = widgets->w_source;
3178 widgets->w_type = gtk_entry_new ();
3180 gtk_entry_set_text ( GTK_ENTRY(widgets->w_type), tr->type );
3181 content_prop[cnt_prop++] = widgets->w_type;
3183 widgets->w_color = content_prop[cnt_prop++] = gtk_color_button_new_with_color ( &(tr->color) );
3185 static gchar *draw_name_labels[] = {
3190 N_("Start and End"),
3191 N_("Centre, Start and End"),
3195 widgets->w_namelabel = content_prop[cnt_prop++] = vik_combo_box_text_new ();
3196 gchar **pstr = draw_name_labels;
3198 vik_combo_box_text_append ( widgets->w_namelabel, *(pstr++) );
3199 gtk_combo_box_set_active ( GTK_COMBO_BOX(widgets->w_namelabel), tr->draw_name_mode );
3201 widgets->w_number_distlabels = content_prop[cnt_prop++] =
3202 gtk_spin_button_new ( GTK_ADJUSTMENT(gtk_adjustment_new(tr->max_number_dist_labels, 0, 100, 1, 1, 0)), 1, 0 );
3203 gtk_widget_set_tooltip_text ( GTK_WIDGET(widgets->w_number_distlabels), _("Maximum number of distance labels to be shown") );
3205 table = create_table (cnt_prop, label_texts, content_prop);
3207 gtk_notebook_append_page(GTK_NOTEBOOK(graphs), GTK_WIDGET(table), gtk_label_new(_("Properties")));
3210 GtkWidget *content[20];
3213 vik_units_distance_t dist_units = a_vik_get_units_distance ();
3215 // NB This value not shown yet - but is used by internal calculations
3216 widgets->track_length_inc_gaps = vik_track_get_length_including_gaps(tr);
3218 tr_len = widgets->track_length = vik_track_get_length(tr);
3219 switch (dist_units) {
3220 case VIK_UNITS_DISTANCE_KILOMETRES:
3221 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km", tr_len/1000.0 );
3223 case VIK_UNITS_DISTANCE_MILES:
3224 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f miles", VIK_METERS_TO_MILES(tr_len) );
3226 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
3227 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f NM", VIK_METERS_TO_NAUTICAL_MILES(tr_len) );
3230 g_critical("Houston, we've had a problem. distance=%d", dist_units);
3232 widgets->w_track_length = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3234 tp_count = vik_track_get_tp_count(tr);
3235 g_snprintf(tmp_buf, sizeof(tmp_buf), "%lu", tp_count );
3236 widgets->w_tp_count = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3238 seg_count = vik_track_get_segment_count(tr) ;
3239 g_snprintf(tmp_buf, sizeof(tmp_buf), "%u", seg_count );
3240 widgets->w_segment_count = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3242 g_snprintf(tmp_buf, sizeof(tmp_buf), "%lu", vik_track_get_dup_point_count(tr) );
3243 widgets->w_duptp_count = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3245 vik_units_speed_t speed_units = a_vik_get_units_speed ();
3246 tmp_speed = vik_track_get_max_speed(tr);
3247 if ( tmp_speed == 0 )
3248 g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
3250 switch (speed_units) {
3251 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
3252 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km/h", VIK_MPS_TO_KPH(tmp_speed));
3254 case VIK_UNITS_SPEED_MILES_PER_HOUR:
3255 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f mph", VIK_MPS_TO_MPH(tmp_speed));
3257 case VIK_UNITS_SPEED_METRES_PER_SECOND:
3258 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f m/s", tmp_speed );
3260 case VIK_UNITS_SPEED_KNOTS:
3261 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f knots", VIK_MPS_TO_KNOTS(tmp_speed));
3264 g_snprintf (tmp_buf, sizeof(tmp_buf), "--" );
3265 g_critical("Houston, we've had a problem. speed=%d", speed_units);
3268 widgets->w_max_speed = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3270 tmp_speed = vik_track_get_average_speed(tr);
3271 if ( tmp_speed == 0 )
3272 g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
3274 switch (speed_units) {
3275 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
3276 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km/h", VIK_MPS_TO_KPH(tmp_speed));
3278 case VIK_UNITS_SPEED_MILES_PER_HOUR:
3279 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f mph", VIK_MPS_TO_MPH(tmp_speed));
3281 case VIK_UNITS_SPEED_METRES_PER_SECOND:
3282 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f m/s", tmp_speed );
3284 case VIK_UNITS_SPEED_KNOTS:
3285 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f knots", VIK_MPS_TO_KNOTS(tmp_speed));
3288 g_snprintf (tmp_buf, sizeof(tmp_buf), "--" );
3289 g_critical("Houston, we've had a problem. speed=%d", speed_units);
3292 widgets->w_avg_speed = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3294 // Use 60sec as the default period to be considered stopped
3295 // this is the TrackWaypoint draw stops default value 'vtl->stop_length'
3296 // however this variable is not directly accessible - and I don't expect it's often changed from the default
3297 // so ATM just put in the number
3298 tmp_speed = vik_track_get_average_speed_moving(tr, 60);
3299 if ( tmp_speed == 0 )
3300 g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
3302 switch (speed_units) {
3303 case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
3304 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km/h", VIK_MPS_TO_KPH(tmp_speed));
3306 case VIK_UNITS_SPEED_MILES_PER_HOUR:
3307 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f mph", VIK_MPS_TO_MPH(tmp_speed));
3309 case VIK_UNITS_SPEED_METRES_PER_SECOND:
3310 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f m/s", tmp_speed );
3312 case VIK_UNITS_SPEED_KNOTS:
3313 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f knots", VIK_MPS_TO_KNOTS(tmp_speed));
3316 g_snprintf (tmp_buf, sizeof(tmp_buf), "--" );
3317 g_critical("Houston, we've had a problem. speed=%d", speed_units);
3320 widgets->w_mvg_speed = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3322 switch (dist_units) {
3323 case VIK_UNITS_DISTANCE_KILOMETRES:
3324 // Even though kilometres, the average distance between points is going to be quite small so keep in metres
3325 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f m", (tp_count - seg_count) == 0 ? 0 : tr_len / ( tp_count - seg_count ) );
3327 case VIK_UNITS_DISTANCE_MILES:
3328 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 )) );
3330 case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
3331 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.3f NM", (tp_count - seg_count) == 0 ? 0 : VIK_METERS_TO_NAUTICAL_MILES(tr_len / ( tp_count - seg_count )) );
3334 g_critical("Houston, we've had a problem. distance=%d", dist_units);
3336 widgets->w_avg_dist = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3338 vik_units_height_t height_units = a_vik_get_units_height ();
3339 if ( min_alt == VIK_DEFAULT_ALTITUDE )
3340 g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
3342 switch (height_units) {
3343 case VIK_UNITS_HEIGHT_METRES:
3344 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.0f m - %.0f m", min_alt, max_alt );
3346 case VIK_UNITS_HEIGHT_FEET:
3347 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.0f feet - %.0f feet", VIK_METERS_TO_FEET(min_alt), VIK_METERS_TO_FEET(max_alt) );
3350 g_snprintf(tmp_buf, sizeof(tmp_buf), "--" );
3351 g_critical("Houston, we've had a problem. height=%d", height_units);
3354 widgets->w_elev_range = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3356 vik_track_get_total_elevation_gain(tr, &max_alt, &min_alt );
3357 if ( min_alt == VIK_DEFAULT_ALTITUDE )
3358 g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
3360 switch (height_units) {
3361 case VIK_UNITS_HEIGHT_METRES:
3362 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.0f m / %.0f m", max_alt, min_alt );
3364 case VIK_UNITS_HEIGHT_FEET:
3365 g_snprintf(tmp_buf, sizeof(tmp_buf), "%.0f feet / %.0f feet", VIK_METERS_TO_FEET(max_alt), VIK_METERS_TO_FEET(min_alt) );
3368 g_snprintf(tmp_buf, sizeof(tmp_buf), "--" );
3369 g_critical("Houston, we've had a problem. height=%d", height_units);
3372 widgets->w_elev_gain = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3375 #define PACK(w) gtk_box_pack_start (GTK_BOX(right_vbox), w, FALSE, FALSE, 0);
3376 gtk_box_pack_start (GTK_BOX(right_vbox), e_cmt, FALSE, FALSE, 0);
3389 if ( tr->trackpoints && VIK_TRACKPOINT(tr->trackpoints->data)->timestamp )
3392 t1 = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
3393 t2 = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
3396 // Notional center of a track is simply an average of the bounding box extremities
3397 struct LatLon center = { (tr->bbox.north+tr->bbox.south)/2, (tr->bbox.east+tr->bbox.west)/2 };
3398 vik_coord_load_from_latlon ( &vc, vik_trw_layer_get_coord_mode(vtl), ¢er );
3400 widgets->tz = vu_get_tz_at_location ( &vc );
3403 msg = vu_get_time_string ( &t1, "%c", &vc, widgets->tz );
3404 widgets->w_time_start = content[cnt++] = ui_label_new_selectable(msg);
3407 msg = vu_get_time_string ( &t2, "%c", &vc, widgets->tz );
3408 widgets->w_time_end = content[cnt++] = ui_label_new_selectable(msg);
3411 gint total_duration_s = (gint)(t2-t1);
3412 gint segments_duration_s = (gint)vik_track_get_duration(tr,FALSE);
3413 gint total_duration_m = total_duration_s/60;
3414 gint segments_duration_m = segments_duration_s/60;
3415 g_snprintf(tmp_buf, sizeof(tmp_buf), _("%d minutes - %d minutes moving"), total_duration_m, segments_duration_m);
3416 widgets->w_time_dur = content[cnt++] = ui_label_new_selectable(tmp_buf);
3418 // A tooltip to show in more readable hours:minutes
3419 gchar tip_buf_total[20];
3420 guint h_tot = total_duration_s/3600;
3421 guint m_tot = (total_duration_s - h_tot*3600)/60;
3422 g_snprintf(tip_buf_total, sizeof(tip_buf_total), "%d:%02d", h_tot, m_tot);
3423 gchar tip_buf_segments[20];
3424 guint h_seg = segments_duration_s/3600;
3425 guint m_seg = (segments_duration_s - h_seg*3600)/60;
3426 g_snprintf(tip_buf_segments, sizeof(tip_buf_segments), "%d:%02d", h_seg, m_seg);
3427 gchar *tip = g_strdup_printf (_("%s total - %s in segments"), tip_buf_total, tip_buf_segments);
3428 gtk_widget_set_tooltip_text ( GTK_WIDGET(widgets->w_time_dur), tip );
3431 widgets->w_time_start = content[cnt++] = gtk_label_new(_("No Data"));
3432 widgets->w_time_end = content[cnt++] = gtk_label_new(_("No Data"));
3433 widgets->w_time_dur = content[cnt++] = gtk_label_new(_("No Data"));
3436 table = create_table (cnt, stats_texts, content);
3438 gtk_notebook_append_page(GTK_NOTEBOOK(graphs), GTK_WIDGET(table), gtk_label_new(_("Statistics")));
3440 if ( widgets->elev_box ) {
3441 GtkWidget *page = NULL;
3442 widgets->w_cur_dist = ui_label_new_selectable(_("No Data"));
3443 widgets->w_cur_elevation = ui_label_new_selectable(_("No Data"));
3444 widgets->w_show_dem = gtk_check_button_new_with_mnemonic(_("Show D_EM"));
3445 widgets->w_show_alt_gps_speed = gtk_check_button_new_with_mnemonic(_("Show _GPS Speed"));
3446 page = create_graph_page (widgets->elev_box,
3447 _("<b>Track Distance:</b>"), widgets->w_cur_dist,
3448 _("<b>Track Height:</b>"), widgets->w_cur_elevation,
3450 widgets->w_show_dem, show_dem,
3451 widgets->w_show_alt_gps_speed, show_alt_gps_speed);
3452 g_signal_connect (widgets->w_show_dem, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3453 g_signal_connect (widgets->w_show_alt_gps_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3454 gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Elevation-distance")));
3457 if ( widgets->gradient_box ) {
3458 GtkWidget *page = NULL;
3459 widgets->w_cur_gradient_dist = ui_label_new_selectable(_("No Data"));
3460 widgets->w_cur_gradient_gradient = ui_label_new_selectable(_("No Data"));
3461 widgets->w_show_gradient_gps_speed = gtk_check_button_new_with_mnemonic(_("Show _GPS Speed"));
3462 page = create_graph_page (widgets->gradient_box,
3463 _("<b>Track Distance:</b>"), widgets->w_cur_gradient_dist,
3464 _("<b>Track Gradient:</b>"), widgets->w_cur_gradient_gradient,
3466 widgets->w_show_gradient_gps_speed, show_gradient_gps_speed,
3468 g_signal_connect (widgets->w_show_gradient_gps_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3469 gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Gradient-distance")));
3472 if ( widgets->speed_box ) {
3473 GtkWidget *page = NULL;
3474 widgets->w_cur_time = ui_label_new_selectable(_("No Data"));
3475 widgets->w_cur_speed = ui_label_new_selectable(_("No Data"));
3476 widgets->w_cur_time_real = ui_label_new_selectable(_("No Data"));
3477 widgets->w_show_gps_speed = gtk_check_button_new_with_mnemonic(_("Show _GPS Speed"));
3478 page = create_graph_page (widgets->speed_box,
3479 _("<b>Track Time:</b>"), widgets->w_cur_time,
3480 _("<b>Track Speed:</b>"), widgets->w_cur_speed,
3481 _("<b>Time/Date:</b>"), widgets->w_cur_time_real,
3482 widgets->w_show_gps_speed, show_gps_speed,
3484 g_signal_connect (widgets->w_show_gps_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3485 gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Speed-time")));
3488 if ( widgets->dist_box ) {
3489 GtkWidget *page = NULL;
3490 widgets->w_cur_dist_time = ui_label_new_selectable(_("No Data"));
3491 widgets->w_cur_dist_dist = ui_label_new_selectable(_("No Data"));
3492 widgets->w_cur_dist_time_real = ui_label_new_selectable(_("No Data"));
3493 widgets->w_show_dist_speed = gtk_check_button_new_with_mnemonic(_("Show S_peed"));
3494 page = create_graph_page (widgets->dist_box,
3495 _("<b>Track Distance:</b>"), widgets->w_cur_dist_dist,
3496 _("<b>Track Time:</b>"), widgets->w_cur_dist_time,
3497 _("<b>Time/Date:</b>"), widgets->w_cur_dist_time_real,
3498 widgets->w_show_dist_speed, show_dist_speed,
3500 g_signal_connect (widgets->w_show_dist_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3501 gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Distance-time")));
3504 if ( widgets->elev_time_box ) {
3505 GtkWidget *page = NULL;
3506 widgets->w_cur_elev_time = ui_label_new_selectable(_("No Data"));
3507 widgets->w_cur_elev_elev = ui_label_new_selectable(_("No Data"));
3508 widgets->w_cur_elev_time_real = ui_label_new_selectable(_("No Data"));
3509 widgets->w_show_elev_speed = gtk_check_button_new_with_mnemonic(_("Show S_peed"));
3510 widgets->w_show_elev_dem = gtk_check_button_new_with_mnemonic(_("Show D_EM"));
3511 page = create_graph_page (widgets->elev_time_box,
3512 _("<b>Track Time:</b>"), widgets->w_cur_elev_time,
3513 _("<b>Track Height:</b>"), widgets->w_cur_elev_elev,
3514 _("<b>Time/Date:</b>"), widgets->w_cur_elev_time_real,
3515 widgets->w_show_elev_dem, show_elev_dem,
3516 widgets->w_show_elev_speed, show_elev_speed);
3517 g_signal_connect (widgets->w_show_elev_dem, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3518 g_signal_connect (widgets->w_show_elev_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3519 gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Elevation-time")));
3522 if ( widgets->speed_dist_box ) {
3523 GtkWidget *page = NULL;
3524 widgets->w_cur_speed_dist = ui_label_new_selectable(_("No Data"));
3525 widgets->w_cur_speed_speed = ui_label_new_selectable(_("No Data"));
3526 widgets->w_show_sd_gps_speed = gtk_check_button_new_with_mnemonic(_("Show _GPS Speed"));
3527 page = create_graph_page (widgets->speed_dist_box,
3528 _("<b>Track Distance:</b>"), widgets->w_cur_speed_dist,
3529 _("<b>Track Speed:</b>"), widgets->w_cur_speed_speed,
3531 widgets->w_show_sd_gps_speed, show_sd_gps_speed,
3533 g_signal_connect (widgets->w_show_sd_gps_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3534 gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Speed-distance")));
3537 gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), graphs, FALSE, FALSE, 0);
3539 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER, FALSE);
3541 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), VIK_TRW_LAYER_PROPWIN_SPLIT, FALSE);
3542 if (vik_track_get_dup_point_count(tr) <= 0)
3543 gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), VIK_TRW_LAYER_PROPWIN_DEL_DUP, FALSE);
3545 // On dialog realization configure_event causes the graphs to be initially drawn
3546 widgets->configure_dialog = TRUE;
3547 g_signal_connect ( G_OBJECT(dialog), "configure-event", G_CALLBACK (configure_event), widgets );
3549 g_signal_connect ( G_OBJECT(dialog), "destroy", G_CALLBACK (destroy_cb), widgets );
3551 vik_track_set_property_dialog(tr, dialog);
3552 gtk_dialog_set_default_response ( GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT );
3553 gtk_widget_show_all ( dialog );
3555 // Gtk note: due to historical reasons, this must be done after widgets are shown
3556 if ( start_on_stats )
3557 gtk_notebook_set_current_page ( GTK_NOTEBOOK(graphs), 1 );
3562 * Update this property dialog
3563 * e.g. if the track has been renamed
3565 void vik_trw_layer_propwin_update ( VikTrack *trk )
3567 // If not displayed do nothing
3568 if ( !trk->property_dialog )
3571 // Update title with current name
3573 gchar *title = g_strdup_printf ( _("%s - Track Properties"), trk->name );
3574 gtk_window_set_title ( GTK_WINDOW(trk->property_dialog), title );