]> git.street.me.uk Git - andy/viking.git/blob - src/viktrwlayer_propwin.c
[QA] CID#101114+CID#101115: Result not floating point
[andy/viking.git] / src / viktrwlayer_propwin.c
1 /*
2  * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
3  *
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>
8  *
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.
13  *
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.
18  *
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
22  *
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28
29 #ifdef HAVE_MATH_H
30 #include <math.h>
31 #endif
32
33 #include <gtk/gtk.h>
34 #include <glib/gi18n.h>
35 #include <time.h>
36 #ifdef HAVE_STRING_H
37 #include <string.h>
38 #endif
39 #include "viktrwlayer.h"
40 #include "viktrwlayer_propwin.h"
41 #include "dems.h"
42 #include "viking.h"
43 #include "vikviewport.h" /* ugh */
44 #include "vikutils.h"
45 #include "ui_util.h"
46 #include <gdk-pixbuf/gdk-pixdata.h>
47
48 typedef enum {
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;
57
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};
63
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.
72
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};
79
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};
85
86 // Time chunks in seconds
87 static const time_t chunkst[] = {
88   60,     // 1 minute
89   120,    // 2 minutes
90   300,    // 5 minutes
91   900,    // 15 minutes
92   1800,   // half hour
93   3600,   // 1 hour
94   10800,  // 3 hours
95   21600,  // 6 hours
96   43200,  // 12 hours
97   86400,  // 1 day
98   172800, // 2 days
99   604800, // 1 week
100   1209600,// 2 weeks
101   2419200,// 4 weeks
102 };
103
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;
113
114 typedef struct _propsaved {
115   gboolean saved;
116   GdkImage *img;
117 } PropSaved;
118
119 typedef struct _propwidgets {
120   gboolean  configure_dialog;
121   VikTrwLayer *vtl;
122   VikTrack *tr;
123   VikViewport *vvp;
124   VikLayersPanel *vlp;
125   gint      profile_width;
126   gint      profile_height;
127   gint      profile_width_old;
128   gint      profile_height_old;
129   gint      profile_width_offset;
130   gint      profile_height_offset;
131   GtkWidget *dialog;
132   GtkWidget *w_comment;
133   GtkWidget *w_description;
134   GtkWidget *w_track_length;
135   GtkWidget *w_tp_count;
136   GtkWidget *w_segment_count;
137   GtkWidget *w_duptp_count;
138   GtkWidget *w_max_speed;
139   GtkWidget *w_avg_speed;
140   GtkWidget *w_mvg_speed;
141   GtkWidget *w_avg_dist;
142   GtkWidget *w_elev_range;
143   GtkWidget *w_elev_gain;
144   GtkWidget *w_time_start;
145   GtkWidget *w_time_end;
146   GtkWidget *w_time_dur;
147   GtkWidget *w_color;
148   GtkWidget *w_namelabel;
149   GtkWidget *w_number_distlabels;
150   GtkWidget *w_cur_dist; /*< Current distance */
151   GtkWidget *w_cur_elevation;
152   GtkWidget *w_cur_gradient_dist; /*< Current distance on gradient graph */
153   GtkWidget *w_cur_gradient_gradient; /*< Current gradient on gradient graph */
154   GtkWidget *w_cur_time; /*< Current track time */
155   GtkWidget *w_cur_time_real; /*< Actual time as on a clock */
156   GtkWidget *w_cur_speed;
157   GtkWidget *w_cur_dist_dist; /*< Current distance on distance graph */
158   GtkWidget *w_cur_dist_time; /*< Current track time on distance graph */
159   GtkWidget *w_cur_dist_time_real; // Clock time
160   GtkWidget *w_cur_elev_elev;
161   GtkWidget *w_cur_elev_time; // Track time
162   GtkWidget *w_cur_elev_time_real; // Clock time
163   GtkWidget *w_cur_speed_dist;
164   GtkWidget *w_cur_speed_speed;
165   GtkWidget *w_show_dem;
166   GtkWidget *w_show_alt_gps_speed;
167   GtkWidget *w_show_gps_speed;
168   GtkWidget *w_show_gradient_gps_speed;
169   GtkWidget *w_show_dist_speed;
170   GtkWidget *w_show_elev_speed;
171   GtkWidget *w_show_elev_dem;
172   GtkWidget *w_show_sd_gps_speed;
173   gdouble   track_length;
174   gdouble   track_length_inc_gaps;
175   PropSaved elev_graph_saved_img;
176   PropSaved gradient_graph_saved_img;
177   PropSaved speed_graph_saved_img;
178   PropSaved dist_graph_saved_img;
179   PropSaved elev_time_graph_saved_img;
180   PropSaved speed_dist_graph_saved_img;
181   GtkWidget *elev_box;
182   GtkWidget *gradient_box;
183   GtkWidget *speed_box;
184   GtkWidget *dist_box;
185   GtkWidget *elev_time_box;
186   GtkWidget *speed_dist_box;
187   gdouble   *altitudes;
188   gdouble   *ats; // altitudes in time
189   gdouble   min_altitude;
190   gdouble   max_altitude;
191   gdouble   draw_min_altitude;
192   gdouble   draw_min_altitude_time;
193   gint      cia; // Chunk size Index into Altitudes
194   gint      ciat; // Chunk size Index into Altitudes / Time
195   // NB cia & ciat are normally same value but sometimes not due to differing methods of altitude array creation
196   //    thus also have draw_min_altitude for each altitude graph type
197   gdouble   *gradients;
198   gdouble   min_gradient;
199   gdouble   max_gradient;
200   gdouble   draw_min_gradient;
201   gint      cig; // Chunk size Index into Gradients
202   gdouble   *speeds;
203   gdouble   *speeds_dist;
204   gdouble   min_speed;
205   gdouble   max_speed;
206   gdouble   draw_min_speed;
207   gdouble   max_speed_dist;
208   gint      cis; // Chunk size Index into Speeds
209   gint      cisd; // Chunk size Index into Speed/Distance
210   gdouble   *distances;
211   gint      cid; // Chunk size Index into Distance
212   VikTrackpoint *marker_tp;
213   gboolean  is_marker_drawn;
214   VikTrackpoint *blob_tp;
215   gboolean  is_blob_drawn;
216   time_t    duration;
217   gchar     *tz; // TimeZone at track's location
218 } PropWidgets;
219
220 static PropWidgets *prop_widgets_new()
221 {
222   PropWidgets *widgets = g_malloc0(sizeof(PropWidgets));
223
224   return widgets;
225 }
226
227 static void prop_widgets_free(PropWidgets *widgets)
228 {
229   if (widgets->elev_graph_saved_img.img)
230     g_object_unref(widgets->elev_graph_saved_img.img);
231   if (widgets->gradient_graph_saved_img.img)
232     g_object_unref(widgets->gradient_graph_saved_img.img);
233   if (widgets->speed_graph_saved_img.img)
234     g_object_unref(widgets->speed_graph_saved_img.img);
235   if (widgets->dist_graph_saved_img.img)
236     g_object_unref(widgets->dist_graph_saved_img.img);
237   if (widgets->elev_time_graph_saved_img.img)
238     g_object_unref(widgets->elev_time_graph_saved_img.img);
239   if (widgets->speed_dist_graph_saved_img.img)
240     g_object_unref(widgets->speed_dist_graph_saved_img.img);
241   if (widgets->altitudes)
242     g_free(widgets->altitudes);
243   if (widgets->gradients)
244     g_free(widgets->gradients);
245   if (widgets->speeds)
246     g_free(widgets->speeds);
247   if (widgets->distances)
248     g_free(widgets->distances);
249   if (widgets->ats)
250     g_free(widgets->ats);
251   if (widgets->speeds_dist)
252     g_free(widgets->speeds_dist);
253   g_free(widgets);
254 }
255
256 static void minmax_array(const gdouble *array, gdouble *min, gdouble *max, gboolean NO_ALT_TEST, gint PROFILE_WIDTH)
257 {
258   *max = -1000;
259   *min = 20000;
260   guint i;
261   for ( i=0; i < PROFILE_WIDTH; i++ ) {
262     if ( NO_ALT_TEST || (array[i] != VIK_DEFAULT_ALTITUDE) ) {
263       if ( array[i] > *max )
264         *max = array[i];
265       if ( array[i] < *min )
266         *min = array[i];
267     }
268   }
269 }
270
271 #define MARGIN_X 70
272 #define MARGIN_Y 20
273 #define LINES 5
274 /**
275  * get_new_min_and_chunk_index:
276  * Returns via pointers:
277  *   the new minimum value to be used for the graph
278  *   the index in to the chunk sizes array (ci = Chunk Index)
279  */
280 static void get_new_min_and_chunk_index (gdouble mina, gdouble maxa, const gdouble *chunks, size_t chunky, gdouble *new_min, gint *ci)
281 {
282   /* Get unitized chunk */
283   /* Find suitable chunk index */
284   *ci = 0;
285   gdouble diff_chunk = (maxa - mina)/LINES;
286
287   /* Loop through to find best match */
288   while (diff_chunk > chunks[*ci]) {
289     (*ci)++;
290     /* Last Resort Check */
291     if ( *ci == chunky ) {
292       // Use previous value and exit loop
293       (*ci)--;
294       break;
295     }
296   }
297
298   /* Ensure adjusted minimum .. maximum covers mina->maxa */
299
300   // Now work out adjusted minimum point to the nearest lowest chunk divisor value
301   // When negative ensure logic uses lowest value
302   if ( mina < 0 )
303     *new_min = (gdouble) ( (gint)((mina - chunks[*ci]) / chunks[*ci]) * chunks[*ci] );
304   else
305     *new_min = (gdouble) ( (gint)(mina / chunks[*ci]) * chunks[*ci] );
306
307   // Range not big enough - as new minimum has lowered
308   if ((*new_min + (chunks[*ci] * LINES) < maxa)) {
309     // Next chunk should cover it
310     if ( *ci < chunky-1 ) {
311       (*ci)++;
312       // Remember to adjust the minimum too...
313       if ( mina < 0 )
314         *new_min = (gdouble) ( (gint)((mina - chunks[*ci]) / chunks[*ci]) * chunks[*ci] );
315       else
316         *new_min = (gdouble) ( (gint)(mina / chunks[*ci]) * chunks[*ci] );
317     }
318   }
319 }
320
321 static guint get_time_chunk_index (time_t duration)
322 {
323   // Grid split
324   time_t myduration = duration / LINES;
325
326   // Search nearest chunk index
327   guint ci = 0;
328   guint last_chunk = G_N_ELEMENTS(chunkst);
329
330   // Loop through to find best match
331   while (myduration > chunkst[ci]) {
332     ci++;
333     // Last Resort Check
334     if ( ci == last_chunk )
335       break;
336   }
337   // Use previous value
338   if ( ci != 0 )
339    ci--;
340
341   return ci;
342 }
343
344 /**
345  *
346  */
347 static guint get_distance_chunk_index (gdouble length)
348 {
349   // Grid split
350   gdouble mylength = length / LINES;
351
352   // Search nearest chunk index
353   guint ci = 0;
354   guint last_chunk = G_N_ELEMENTS(chunksd);
355
356   // Loop through to find best match
357   while (mylength > chunksd[ci]) {
358     ci++;
359     // Last Resort Check
360     if ( ci == last_chunk )
361       break;
362   }
363   // Use previous value
364   if ( ci != 0 )
365    ci--;
366
367   return ci;
368 }
369
370 static VikTrackpoint *set_center_at_graph_position(gdouble event_x,
371                                                    gint img_width,
372                                                    VikTrwLayer *vtl,
373                                                    VikLayersPanel *vlp,
374                                                    VikViewport *vvp,
375                                                    VikTrack *tr,
376                                                    gboolean time_base,
377                                                    gint PROFILE_WIDTH)
378 {
379   VikTrackpoint *trackpoint;
380   gdouble x = event_x - img_width / 2 + PROFILE_WIDTH / 2 - MARGIN_X / 2;
381   if (x < 0)
382     x = 0;
383   if (x > PROFILE_WIDTH)
384     x = PROFILE_WIDTH;
385
386   if (time_base)
387     trackpoint = vik_track_get_closest_tp_by_percentage_time ( tr, (gdouble) x / PROFILE_WIDTH, NULL );
388   else
389     trackpoint = vik_track_get_closest_tp_by_percentage_dist ( tr, (gdouble) x / PROFILE_WIDTH, NULL );
390
391   if ( trackpoint ) {
392     VikCoord coord = trackpoint->coord;
393     if ( vlp ) {
394       vik_viewport_set_center_coord ( vik_layers_panel_get_viewport(vlp), &coord, TRUE );
395       vik_layers_panel_emit_update ( vlp );
396     }
397     else {
398       /* since vlp not set, vvp should be valid instead! */
399       if ( vvp )
400         vik_viewport_set_center_coord ( vvp, &coord, TRUE );
401       vik_layer_emit_update ( VIK_LAYER(vtl) );
402     }
403   }
404   return trackpoint;
405 }
406
407 /**
408  * Returns whether the marker was drawn or not and whether the blob was drawn or not
409  */
410 static void save_image_and_draw_graph_marks (GtkWidget *image,
411                                              gdouble marker_x,
412                                              GdkGC *gc,
413                                              gint blob_x,
414                                              gint blob_y,
415                                              PropSaved *saved_img,
416                                              gint PROFILE_WIDTH,
417                                              gint PROFILE_HEIGHT,
418                                              gboolean *marker_drawn,
419                                              gboolean *blob_drawn)
420 {
421   GdkPixmap *pix = NULL;
422   /* the pixmap = margin + graph area */
423   gtk_image_get_pixmap(GTK_IMAGE(image), &pix, NULL);
424
425   /* Restore previously saved image */
426   if (saved_img->saved) {
427     gdk_draw_image(GDK_DRAWABLE(pix), gc, saved_img->img, 0, 0, 0, 0, MARGIN_X+PROFILE_WIDTH, MARGIN_Y+PROFILE_HEIGHT);
428     saved_img->saved = FALSE;
429   }
430
431   // ATM always save whole image - as anywhere could have changed
432   if (saved_img->img)
433     gdk_drawable_copy_to_image(GDK_DRAWABLE(pix), saved_img->img, 0, 0, 0, 0, MARGIN_X+PROFILE_WIDTH, MARGIN_Y+PROFILE_HEIGHT);
434   else
435     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);
436   saved_img->saved = TRUE;
437
438   if ((marker_x >= MARGIN_X) && (marker_x < (PROFILE_WIDTH + MARGIN_X))) {
439     gdk_draw_line (GDK_DRAWABLE(pix), gc, marker_x, MARGIN_Y, marker_x, PROFILE_HEIGHT + MARGIN_Y);
440     *marker_drawn = TRUE;
441   }
442   else
443     *marker_drawn = FALSE;
444
445   // Draw a square blob to indicate where we are on track for this graph
446   if ( (blob_x >= MARGIN_X) && (blob_x < (PROFILE_WIDTH + MARGIN_X)) && (blob_y < PROFILE_HEIGHT+MARGIN_Y) ) {
447     gdk_draw_rectangle (GDK_DRAWABLE(pix), gc, TRUE, blob_x-3, blob_y-3, 6, 6);
448     *blob_drawn = TRUE;
449   }
450   else
451     *blob_drawn = FALSE;
452   
453   // Anywhere on image could have changed
454   if (*marker_drawn || *blob_drawn)
455     gtk_widget_queue_draw(image);
456 }
457
458 /**
459  * Return the percentage of how far a trackpoint is a long a track via the time method
460  */
461 static gdouble tp_percentage_by_time ( VikTrack *tr, VikTrackpoint *trackpoint )
462 {
463   gdouble pc = NAN;
464   if (trackpoint == NULL)
465     return pc;
466   time_t t_start, t_end, t_total;
467   t_start = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
468   t_end = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
469   t_total = t_end - t_start;
470   pc = (gdouble)(trackpoint->timestamp - t_start)/t_total;
471   return pc;
472 }
473
474 /**
475  * Return the percentage of how far a trackpoint is a long a track via the distance method
476  */
477 static gdouble tp_percentage_by_distance ( VikTrack *tr, VikTrackpoint *trackpoint, gdouble track_length )
478 {
479   gdouble pc = NAN;
480   if (trackpoint == NULL)
481     return pc;
482   gdouble dist = 0.0;
483   GList *iter;
484   for (iter = tr->trackpoints->next; iter != NULL; iter = iter->next) {
485     dist += vik_coord_diff(&(VIK_TRACKPOINT(iter->data)->coord),
486                            &(VIK_TRACKPOINT(iter->prev->data)->coord));
487     /* Assuming trackpoint is not a copy */
488     if (trackpoint == VIK_TRACKPOINT(iter->data))
489       break;
490   }
491   if (iter != NULL)
492     pc = dist/track_length;
493   return pc;
494 }
495
496 static void track_graph_click( GtkWidget *event_box, GdkEventButton *event, PropWidgets *widgets, VikPropWinGraphType_t graph_type )
497 {
498   gboolean is_time_graph =
499     ( graph_type == PROPWIN_GRAPH_TYPE_SPEED_TIME ||
500       graph_type == PROPWIN_GRAPH_TYPE_DISTANCE_TIME ||
501       graph_type == PROPWIN_GRAPH_TYPE_ELEVATION_TIME );
502
503   GtkAllocation allocation;
504   gtk_widget_get_allocation ( event_box, &allocation );
505
506   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);
507   // Unable to get the point so give up
508   if ( trackpoint == NULL ) {
509     gtk_dialog_set_response_sensitive(GTK_DIALOG(widgets->dialog), VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER, FALSE);
510     return;
511   }
512
513   widgets->marker_tp = trackpoint;
514
515   GList *child;
516   GtkWidget *image;
517   GtkWidget *window = gtk_widget_get_toplevel(GTK_WIDGET(event_box));
518   GtkWidget *graph_box;
519   PropSaved *graph_saved_img;
520   gdouble pc = NAN;
521
522   // Attempt to redraw marker on all graph types
523   gint graphite;
524   for ( graphite = PROPWIN_GRAPH_TYPE_ELEVATION_DISTANCE;
525         graphite < PROPWIN_GRAPH_TYPE_END;
526         graphite++ ) {
527
528     // Switch commonal variables to particular graph type
529     switch (graphite) {
530     default:
531     case PROPWIN_GRAPH_TYPE_ELEVATION_DISTANCE:
532       graph_box       = widgets->elev_box;
533       graph_saved_img = &widgets->elev_graph_saved_img;
534       is_time_graph   = FALSE;
535       break;
536     case PROPWIN_GRAPH_TYPE_GRADIENT_DISTANCE:
537       graph_box       = widgets->gradient_box;
538       graph_saved_img = &widgets->gradient_graph_saved_img;
539       is_time_graph   = FALSE;
540       break;
541     case PROPWIN_GRAPH_TYPE_SPEED_TIME:
542       graph_box       = widgets->speed_box;
543       graph_saved_img = &widgets->speed_graph_saved_img;
544       is_time_graph   = TRUE;
545       break;
546     case PROPWIN_GRAPH_TYPE_DISTANCE_TIME:
547       graph_box       = widgets->dist_box;
548       graph_saved_img = &widgets->dist_graph_saved_img;
549       is_time_graph   = TRUE;
550       break;
551     case PROPWIN_GRAPH_TYPE_ELEVATION_TIME:
552       graph_box       = widgets->elev_time_box;
553       graph_saved_img = &widgets->elev_time_graph_saved_img;
554       is_time_graph   = TRUE;
555       break;
556     case PROPWIN_GRAPH_TYPE_SPEED_DISTANCE:
557       graph_box       = widgets->speed_dist_box;
558       graph_saved_img = &widgets->speed_dist_graph_saved_img;
559       is_time_graph   = FALSE;
560       break;
561     }
562
563     // Commonal method of redrawing marker
564     if ( graph_box ) {
565
566       child = gtk_container_get_children(GTK_CONTAINER(graph_box));
567       image = GTK_WIDGET(child->data);
568
569       if (is_time_graph)
570         pc = tp_percentage_by_time ( widgets->tr, trackpoint );
571       else
572         pc = tp_percentage_by_distance ( widgets->tr, trackpoint, widgets->track_length_inc_gaps );
573
574       if (!isnan(pc)) {
575         gdouble marker_x = (pc * widgets->profile_width) + MARGIN_X;
576         save_image_and_draw_graph_marks(image,
577                                         marker_x,
578                                         gtk_widget_get_style(window)->black_gc,
579                                         -1, // Don't draw blob on clicks
580                                         0,
581                                         graph_saved_img,
582                                         widgets->profile_width,
583                                         widgets->profile_height,
584                                         &widgets->is_marker_drawn,
585                                         &widgets->is_blob_drawn);
586       }
587       g_list_free(child);
588     }
589   }
590
591   gtk_dialog_set_response_sensitive(GTK_DIALOG(widgets->dialog), VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER, widgets->is_marker_drawn);
592 }
593
594 static gboolean track_profile_click( GtkWidget *event_box, GdkEventButton *event, gpointer ptr )
595 {
596   track_graph_click(event_box, event, ptr, PROPWIN_GRAPH_TYPE_ELEVATION_DISTANCE);
597   return TRUE;  /* don't call other (further) callbacks */
598 }
599
600 static gboolean track_gradient_click( GtkWidget *event_box, GdkEventButton *event, gpointer ptr )
601 {
602   track_graph_click(event_box, event, ptr, PROPWIN_GRAPH_TYPE_GRADIENT_DISTANCE);
603   return TRUE;  /* don't call other (further) callbacks */
604 }
605
606 static gboolean track_vt_click( GtkWidget *event_box, GdkEventButton *event, gpointer ptr )
607 {
608   track_graph_click(event_box, event, ptr, PROPWIN_GRAPH_TYPE_SPEED_TIME);
609   return TRUE;  /* don't call other (further) callbacks */
610 }
611
612 static gboolean track_dt_click( GtkWidget *event_box, GdkEventButton *event, gpointer ptr )
613 {
614   track_graph_click(event_box, event, ptr, PROPWIN_GRAPH_TYPE_DISTANCE_TIME);
615   return TRUE;  /* don't call other (further) callbacks */
616 }
617
618 static gboolean track_et_click( GtkWidget *event_box, GdkEventButton *event, gpointer ptr )
619 {
620   track_graph_click(event_box, event, ptr, PROPWIN_GRAPH_TYPE_ELEVATION_TIME);
621   return TRUE;  /* don't call other (further) callbacks */
622 }
623
624 static gboolean track_sd_click( GtkWidget *event_box, GdkEventButton *event, gpointer ptr )
625 {
626   track_graph_click(event_box, event, ptr, PROPWIN_GRAPH_TYPE_SPEED_DISTANCE);
627   return TRUE;  /* don't call other (further) callbacks */
628 }
629
630 /**
631  * Calculate y position for blob on elevation graph
632  */
633 static gint blobby_altitude ( gdouble x_blob, PropWidgets *widgets )
634 {
635   gint ix = (gint)x_blob;
636   // Ensure ix is inbounds
637   if (ix == widgets->profile_width)
638     ix--;
639
640   gint y_blob = widgets->profile_height-widgets->profile_height*(widgets->altitudes[ix]-widgets->draw_min_altitude)/(chunksa[widgets->cia]*LINES);
641
642   return y_blob;
643 }
644
645 /**
646  * Calculate y position for blob on gradient graph
647  */
648 static gint blobby_gradient ( gdouble x_blob, PropWidgets *widgets )
649 {
650   gint ix = (gint)x_blob;
651   // Ensure ix is inbounds
652   if (ix == widgets->profile_width)
653     ix--;
654
655   gint y_blob = widgets->profile_height-widgets->profile_height*(widgets->gradients[ix]-widgets->draw_min_gradient)/(chunksg[widgets->cig]*LINES);
656
657   return y_blob;
658 }
659
660 /**
661  * Calculate y position for blob on speed graph
662  */
663 static gint blobby_speed ( gdouble x_blob, PropWidgets *widgets )
664 {
665   gint ix = (gint)x_blob;
666   // Ensure ix is inbounds
667   if (ix == widgets->profile_width)
668     ix--;
669
670   gint y_blob = widgets->profile_height-widgets->profile_height*(widgets->speeds[ix]-widgets->draw_min_speed)/(chunkss[widgets->cis]*LINES);
671
672   return y_blob;
673 }
674
675 /**
676  * Calculate y position for blob on distance graph
677  */
678 static gint blobby_distance ( gdouble x_blob, PropWidgets *widgets )
679 {
680   gint ix = (gint)x_blob;
681   // Ensure ix is inbounds
682   if (ix == widgets->profile_width)
683     ix--;
684
685   gint y_blob = widgets->profile_height-widgets->profile_height*(widgets->distances[ix])/(chunksd[widgets->cid]*LINES);
686   //NB min distance is always 0, so no need to subtract that from this  ______/
687
688   return y_blob;
689 }
690
691 /**
692  * Calculate y position for blob on elevation/time graph
693  */
694 static gint blobby_altitude_time ( gdouble x_blob, PropWidgets *widgets )
695 {
696   gint ix = (gint)x_blob;
697   // Ensure ix is inbounds
698   if (ix == widgets->profile_width)
699     ix--;
700
701   gint y_blob = widgets->profile_height-widgets->profile_height*(widgets->ats[ix]-widgets->draw_min_altitude_time)/(chunksa[widgets->ciat]*LINES);
702   return y_blob;
703 }
704
705 /**
706  * Calculate y position for blob on speed/dist graph
707  */
708 static gint blobby_speed_dist ( gdouble x_blob, PropWidgets *widgets )
709 {
710   gint ix = (gint)x_blob;
711   // Ensure ix is inbounds
712   if (ix == widgets->profile_width)
713     ix--;
714
715   gint y_blob = widgets->profile_height-widgets->profile_height*(widgets->speeds_dist[ix]-widgets->draw_min_speed)/(chunkss[widgets->cisd]*LINES);
716
717   return y_blob;
718 }
719
720
721 void track_profile_move( GtkWidget *event_box, GdkEventMotion *event, PropWidgets *widgets )
722 {
723   int mouse_x, mouse_y;
724   GdkModifierType state;
725
726   if (event->is_hint)
727     gdk_window_get_pointer (event->window, &mouse_x, &mouse_y, &state);
728   else
729     mouse_x = event->x;
730
731   GtkAllocation allocation;
732   gtk_widget_get_allocation ( event_box, &allocation );
733
734   gdouble x = mouse_x - allocation.width / 2 + widgets->profile_width / 2 - MARGIN_X / 2;
735   if (x < 0)
736     x = 0;
737   if (x > widgets->profile_width)
738     x = widgets->profile_width;
739
740   gdouble meters_from_start;
741   VikTrackpoint *trackpoint = vik_track_get_closest_tp_by_percentage_dist ( widgets->tr, (gdouble) x / widgets->profile_width, &meters_from_start );
742   if (trackpoint && widgets->w_cur_dist) {
743     static gchar tmp_buf[20];
744     vik_units_distance_t dist_units = a_vik_get_units_distance ();
745     switch (dist_units) {
746     case VIK_UNITS_DISTANCE_KILOMETRES:
747       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km", meters_from_start/1000.0);
748       break;
749     case VIK_UNITS_DISTANCE_MILES:
750       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f miles", VIK_METERS_TO_MILES(meters_from_start) );
751       break;
752     case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
753       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f NM", VIK_METERS_TO_NAUTICAL_MILES(meters_from_start) );
754       break;
755     default:
756       g_critical("Houston, we've had a problem. distance=%d", dist_units);
757     }
758     gtk_label_set_text(GTK_LABEL(widgets->w_cur_dist), tmp_buf);
759   }
760
761   // Show track elevation for this position - to the nearest whole number
762   if (trackpoint && widgets->w_cur_elevation) {
763     static gchar tmp_buf[20];
764     if (a_vik_get_units_height () == VIK_UNITS_HEIGHT_FEET)
765       g_snprintf(tmp_buf, sizeof(tmp_buf), "%d ft", (int)VIK_METERS_TO_FEET(trackpoint->altitude));
766     else
767       g_snprintf(tmp_buf, sizeof(tmp_buf), "%d m", (int)trackpoint->altitude);
768     gtk_label_set_text(GTK_LABEL(widgets->w_cur_elevation), tmp_buf);
769   }
770
771   widgets->blob_tp = trackpoint;
772
773   if ( widgets->altitudes == NULL )
774     return;
775
776   GtkWidget *window = gtk_widget_get_toplevel (event_box);
777   GList *child = gtk_container_get_children(GTK_CONTAINER(event_box));
778   GtkWidget *image = GTK_WIDGET(child->data);
779
780   gint y_blob = blobby_altitude (x, widgets);
781
782   gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
783   if (widgets->is_marker_drawn) {
784     gdouble pc = tp_percentage_by_distance ( widgets->tr, widgets->marker_tp, widgets->track_length_inc_gaps );
785     if (!isnan(pc)) {
786       marker_x = (pc * widgets->profile_width) + MARGIN_X;
787     }
788   }
789
790   save_image_and_draw_graph_marks (image,
791                                    marker_x,
792                                    gtk_widget_get_style(window)->black_gc,
793                                    MARGIN_X+x,
794                                    MARGIN_Y+y_blob,
795                                    &widgets->elev_graph_saved_img,
796                                    widgets->profile_width,
797                                    widgets->profile_height,
798                                    &widgets->is_marker_drawn,
799                                    &widgets->is_blob_drawn);
800
801   g_list_free(child);
802 }
803
804 void track_gradient_move( GtkWidget *event_box, GdkEventMotion *event, PropWidgets *widgets )
805 {
806   int mouse_x, mouse_y;
807   GdkModifierType state;
808
809   if (event->is_hint)
810     gdk_window_get_pointer (event->window, &mouse_x, &mouse_y, &state);
811   else
812     mouse_x = event->x;
813
814   GtkAllocation allocation;
815   gtk_widget_get_allocation ( event_box, &allocation );
816
817   gdouble x = mouse_x - allocation.width / 2 + widgets->profile_width / 2 - MARGIN_X / 2;
818   if (x < 0)
819     x = 0;
820   if (x > widgets->profile_width)
821     x = widgets->profile_width;
822
823   gdouble meters_from_start;
824   VikTrackpoint *trackpoint = vik_track_get_closest_tp_by_percentage_dist ( widgets->tr, (gdouble) x / widgets->profile_width, &meters_from_start );
825   if (trackpoint && widgets->w_cur_gradient_dist) {
826     static gchar tmp_buf[20];
827     vik_units_distance_t dist_units = a_vik_get_units_distance ();
828     switch (dist_units) {
829     case VIK_UNITS_DISTANCE_KILOMETRES:
830       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km", meters_from_start/1000.0);
831       break;
832     case VIK_UNITS_DISTANCE_MILES:
833       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f miles", VIK_METERS_TO_MILES(meters_from_start) );
834       break;
835     case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
836       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f NM", VIK_METERS_TO_NAUTICAL_MILES(meters_from_start) );
837       break;
838     default:
839       g_critical("Houston, we've had a problem. distance=%d", dist_units);
840     }
841     gtk_label_set_text(GTK_LABEL(widgets->w_cur_gradient_dist), tmp_buf);
842   }
843
844   // Show track gradient for this position - to the nearest whole number
845   if (trackpoint && widgets->w_cur_gradient_gradient) {
846     static gchar tmp_buf[20];
847     
848     double gradient = widgets->gradients[(int) x];
849
850     g_snprintf(tmp_buf, sizeof(tmp_buf), "%d%%", (int)gradient);
851     gtk_label_set_text(GTK_LABEL(widgets->w_cur_gradient_gradient), tmp_buf);
852   }
853
854   widgets->blob_tp = trackpoint;
855
856   if ( widgets->gradients == NULL )
857     return;
858
859   GtkWidget *window = gtk_widget_get_toplevel (event_box);
860   GList *child = gtk_container_get_children(GTK_CONTAINER(event_box));
861   GtkWidget *image = GTK_WIDGET(child->data);
862
863   gint y_blob = blobby_gradient (x, widgets);
864
865   gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
866   if (widgets->is_marker_drawn) {
867     gdouble pc = tp_percentage_by_distance ( widgets->tr, widgets->marker_tp, widgets->track_length_inc_gaps );
868     if (!isnan(pc)) {
869       marker_x = (pc * widgets->profile_width) + MARGIN_X;
870     }
871   }
872
873   save_image_and_draw_graph_marks (image,
874                                    marker_x,
875                                    gtk_widget_get_style(window)->black_gc,
876                                    MARGIN_X+x,
877                                    MARGIN_Y+y_blob,
878                                    &widgets->gradient_graph_saved_img,
879                                    widgets->profile_width,
880                                    widgets->profile_height,
881                                    &widgets->is_marker_drawn,
882                                    &widgets->is_blob_drawn);
883
884   g_list_free(child);
885 }
886
887 //
888 static void time_label_update (GtkWidget *widget, time_t seconds_from_start)
889 {
890   static gchar tmp_buf[20];
891   guint h = seconds_from_start/3600;
892   guint m = (seconds_from_start - h*3600)/60;
893   guint s = seconds_from_start - (3600*h) - (60*m);
894   g_snprintf(tmp_buf, sizeof(tmp_buf), "%02d:%02d:%02d", h, m, s);
895   gtk_label_set_text(GTK_LABEL(widget), tmp_buf);
896 }
897
898 //
899 static void real_time_label_update ( PropWidgets *widgets, GtkWidget *widget, VikTrackpoint *trackpoint)
900 {
901   static gchar tmp_buf[64];
902   if ( trackpoint->has_timestamp ) {
903     // Alternatively could use %c format but I prefer a slightly more compact form here
904     //  The full date can of course be seen on the Statistics tab
905     strftime (tmp_buf, sizeof(tmp_buf), "%X %x %Z", localtime(&(trackpoint->timestamp)));
906   }
907   else
908     g_snprintf (tmp_buf, sizeof(tmp_buf), _("No Data"));
909   gtk_label_set_text(GTK_LABEL(widget), tmp_buf);
910 }
911
912 void track_vt_move( GtkWidget *event_box, GdkEventMotion *event, PropWidgets *widgets )
913 {
914   int mouse_x, mouse_y;
915   GdkModifierType state;
916
917   if (event->is_hint)
918     gdk_window_get_pointer (event->window, &mouse_x, &mouse_y, &state);
919   else
920     mouse_x = event->x;
921
922   GtkAllocation allocation;
923   gtk_widget_get_allocation ( event_box, &allocation );
924   gdouble x = mouse_x - allocation.width / 2 + widgets->profile_width / 2 - MARGIN_X / 2;
925   if (x < 0)
926     x = 0;
927   if (x > widgets->profile_width)
928     x = widgets->profile_width;
929
930   time_t seconds_from_start;
931   VikTrackpoint *trackpoint = vik_track_get_closest_tp_by_percentage_time ( widgets->tr, (gdouble) x / widgets->profile_width, &seconds_from_start );
932   if (trackpoint && widgets->w_cur_time) {
933     time_label_update ( widgets->w_cur_time, seconds_from_start );
934   }
935
936   if (trackpoint && widgets->w_cur_time_real) {
937     real_time_label_update ( widgets, widgets->w_cur_time_real, trackpoint );
938   }
939
940   gint ix = (gint)x;
941   // Ensure ix is inbounds
942   if (ix == widgets->profile_width)
943     ix--;
944
945   // Show track speed for this position
946   if (trackpoint && widgets->w_cur_speed) {
947     static gchar tmp_buf[20];
948     // Even if GPS speed available (trackpoint->speed), the text will correspond to the speed map shown
949     // No conversions needed as already in appropriate units
950     vik_units_speed_t speed_units = a_vik_get_units_speed ();
951     switch (speed_units) {
952     case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
953       g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f kph"), widgets->speeds[ix]);
954       break;
955     case VIK_UNITS_SPEED_MILES_PER_HOUR:
956       g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f mph"), widgets->speeds[ix]);
957       break;
958     case VIK_UNITS_SPEED_KNOTS:
959       g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f knots"), widgets->speeds[ix]);
960       break;
961     default:
962       // VIK_UNITS_SPEED_METRES_PER_SECOND:
963       g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f m/s"), widgets->speeds[ix]);
964       break;
965     }
966     gtk_label_set_text(GTK_LABEL(widgets->w_cur_speed), tmp_buf);
967   }
968
969   widgets->blob_tp = trackpoint;
970
971   if ( widgets->speeds == NULL )
972     return;
973
974   GtkWidget *window = gtk_widget_get_toplevel (event_box);
975   GList *child = gtk_container_get_children(GTK_CONTAINER(event_box));
976   GtkWidget *image = GTK_WIDGET(child->data);
977
978   gint y_blob = blobby_speed (x, widgets);
979
980   gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
981   if (widgets->is_marker_drawn) {
982     gdouble pc = tp_percentage_by_time ( widgets->tr, widgets->marker_tp );
983     if (!isnan(pc)) {
984       marker_x = (pc * widgets->profile_width) + MARGIN_X;
985     }
986   }
987
988   save_image_and_draw_graph_marks (image,
989                                    marker_x,
990                                    gtk_widget_get_style(window)->black_gc,
991                                    MARGIN_X+x,
992                                    MARGIN_Y+y_blob,
993                                    &widgets->speed_graph_saved_img,
994                                    widgets->profile_width,
995                                    widgets->profile_height,
996                                    &widgets->is_marker_drawn,
997                                    &widgets->is_blob_drawn);
998
999   g_list_free(child);
1000 }
1001
1002 /**
1003  * Update labels and blob marker on mouse moves in the distance/time graph
1004  */
1005 void track_dt_move( GtkWidget *event_box, GdkEventMotion *event, PropWidgets *widgets )
1006 {
1007   int mouse_x, mouse_y;
1008   GdkModifierType state;
1009
1010   if (event->is_hint)
1011     gdk_window_get_pointer (event->window, &mouse_x, &mouse_y, &state);
1012   else
1013     mouse_x = event->x;
1014
1015   GtkAllocation allocation;
1016   gtk_widget_get_allocation ( event_box, &allocation );
1017
1018   gdouble x = mouse_x - allocation.width / 2 + widgets->profile_width / 2 - MARGIN_X / 2;
1019   if (x < 0)
1020     x = 0;
1021   if (x > widgets->profile_width)
1022     x = widgets->profile_width;
1023
1024   time_t seconds_from_start;
1025   VikTrackpoint *trackpoint = vik_track_get_closest_tp_by_percentage_time ( widgets->tr, (gdouble) x / widgets->profile_width, &seconds_from_start );
1026   if (trackpoint && widgets->w_cur_dist_time) {
1027     time_label_update ( widgets->w_cur_dist_time, seconds_from_start );
1028   }
1029
1030   if (trackpoint && widgets->w_cur_dist_time_real) {
1031     real_time_label_update ( widgets, widgets->w_cur_dist_time_real, trackpoint );
1032   }
1033
1034   gint ix = (gint)x;
1035   // Ensure ix is inbounds
1036   if (ix == widgets->profile_width)
1037     ix--;
1038
1039   if (trackpoint && widgets->w_cur_dist_dist) {
1040     static gchar tmp_buf[20];
1041     switch ( a_vik_get_units_distance () ) {
1042     case VIK_UNITS_DISTANCE_MILES:
1043       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f miles", widgets->distances[ix]);
1044       break;
1045     case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1046       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f NM", widgets->distances[ix]);
1047       break;
1048     default:
1049       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km", widgets->distances[ix]);
1050       break;
1051     }
1052     gtk_label_set_text(GTK_LABEL(widgets->w_cur_dist_dist), tmp_buf);
1053   }
1054
1055   widgets->blob_tp = trackpoint;
1056
1057   if ( widgets->distances == NULL )
1058     return;
1059
1060   GtkWidget *window = gtk_widget_get_toplevel (event_box);
1061   GList *child = gtk_container_get_children(GTK_CONTAINER(event_box));
1062   GtkWidget *image = GTK_WIDGET(child->data);
1063
1064   gint y_blob = blobby_distance (x, widgets);
1065
1066   gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
1067   if (widgets->is_marker_drawn) {
1068     gdouble pc = tp_percentage_by_time ( widgets->tr, widgets->marker_tp );
1069     if (!isnan(pc)) {
1070       marker_x = (pc * widgets->profile_width) + MARGIN_X;
1071     }
1072   }
1073
1074   save_image_and_draw_graph_marks (image,
1075                                    marker_x,
1076                                    gtk_widget_get_style(window)->black_gc,
1077                                    MARGIN_X+x,
1078                                    MARGIN_Y+y_blob,
1079                                    &widgets->dist_graph_saved_img,
1080                                    widgets->profile_width,
1081                                    widgets->profile_height,
1082                                    &widgets->is_marker_drawn,
1083                                    &widgets->is_blob_drawn);
1084
1085   g_list_free(child);
1086 }
1087
1088 /**
1089  * Update labels and blob marker on mouse moves in the elevation/time graph
1090  */
1091 void track_et_move( GtkWidget *event_box, GdkEventMotion *event, PropWidgets *widgets )
1092 {
1093   int mouse_x, mouse_y;
1094   GdkModifierType state;
1095
1096   if (event->is_hint)
1097     gdk_window_get_pointer (event->window, &mouse_x, &mouse_y, &state);
1098   else
1099     mouse_x = event->x;
1100
1101   GtkAllocation allocation;
1102   gtk_widget_get_allocation ( event_box, &allocation );
1103
1104   gdouble x = mouse_x - allocation.width / 2 + widgets->profile_width / 2 - MARGIN_X / 2;
1105   if (x < 0)
1106     x = 0;
1107   if (x > widgets->profile_width)
1108     x = widgets->profile_width;
1109
1110   time_t seconds_from_start;
1111   VikTrackpoint *trackpoint = vik_track_get_closest_tp_by_percentage_time ( widgets->tr, (gdouble) x / widgets->profile_width, &seconds_from_start );
1112   if (trackpoint && widgets->w_cur_elev_time) {
1113     time_label_update ( widgets->w_cur_elev_time, seconds_from_start );
1114   }
1115
1116   if (trackpoint && widgets->w_cur_elev_time_real) {
1117     real_time_label_update ( widgets, widgets->w_cur_elev_time_real, trackpoint );
1118   }
1119
1120   gint ix = (gint)x;
1121   // Ensure ix is inbounds
1122   if (ix == widgets->profile_width)
1123     ix--;
1124
1125   if (trackpoint && widgets->w_cur_elev_elev) {
1126     static gchar tmp_buf[20];
1127     if (a_vik_get_units_height () == VIK_UNITS_HEIGHT_FEET)
1128       g_snprintf(tmp_buf, sizeof(tmp_buf), "%d ft", (int)VIK_METERS_TO_FEET(trackpoint->altitude));
1129     else
1130       g_snprintf(tmp_buf, sizeof(tmp_buf), "%d m", (int)trackpoint->altitude);
1131     gtk_label_set_text(GTK_LABEL(widgets->w_cur_elev_elev), tmp_buf);
1132   }
1133
1134   widgets->blob_tp = trackpoint;
1135
1136   if ( widgets->ats == NULL )
1137     return;
1138
1139   GtkWidget *window = gtk_widget_get_toplevel (event_box);
1140   GList *child = gtk_container_get_children(GTK_CONTAINER(event_box));
1141   GtkWidget *image = GTK_WIDGET(child->data);
1142
1143   gint y_blob = blobby_altitude_time (x, widgets);
1144
1145   gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
1146   if (widgets->is_marker_drawn) {
1147     gdouble pc = tp_percentage_by_time ( widgets->tr, widgets->marker_tp );
1148     if (!isnan(pc)) {
1149       marker_x = (pc * widgets->profile_width) + MARGIN_X;
1150     }
1151   }
1152
1153   save_image_and_draw_graph_marks (image,
1154                                    marker_x,
1155                                    gtk_widget_get_style(window)->black_gc,
1156                                    MARGIN_X+x,
1157                                    MARGIN_Y+y_blob,
1158                                    &widgets->elev_time_graph_saved_img,
1159                                    widgets->profile_width,
1160                                    widgets->profile_height,
1161                                    &widgets->is_marker_drawn,
1162                                    &widgets->is_blob_drawn);
1163
1164   g_list_free(child);
1165 }
1166
1167 void track_sd_move( GtkWidget *event_box, GdkEventMotion *event, PropWidgets *widgets )
1168 {
1169   int mouse_x, mouse_y;
1170   GdkModifierType state;
1171
1172   if (event->is_hint)
1173     gdk_window_get_pointer (event->window, &mouse_x, &mouse_y, &state);
1174   else
1175     mouse_x = event->x;
1176
1177   GtkAllocation allocation;
1178   gtk_widget_get_allocation ( event_box, &allocation );
1179
1180   gdouble x = mouse_x - allocation.width / 2 + widgets->profile_width / 2 - MARGIN_X / 2;
1181   if (x < 0)
1182     x = 0;
1183   if (x > widgets->profile_width)
1184     x = widgets->profile_width;
1185
1186   gdouble meters_from_start;
1187   VikTrackpoint *trackpoint = vik_track_get_closest_tp_by_percentage_dist ( widgets->tr, (gdouble) x / widgets->profile_width, &meters_from_start );
1188   if (trackpoint && widgets->w_cur_speed_dist) {
1189     static gchar tmp_buf[20];
1190     vik_units_distance_t dist_units = a_vik_get_units_distance ();
1191     switch (dist_units) {
1192     case VIK_UNITS_DISTANCE_KILOMETRES:
1193       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km", meters_from_start/1000.0);
1194       break;
1195     case VIK_UNITS_DISTANCE_MILES:
1196       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f miles", VIK_METERS_TO_MILES(meters_from_start) );
1197       break;
1198     case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1199       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f NM", VIK_METERS_TO_NAUTICAL_MILES(meters_from_start) );
1200       break;
1201     default:
1202       g_critical("Houston, we've had a problem. distance=%d", dist_units);
1203     }
1204     gtk_label_set_text(GTK_LABEL(widgets->w_cur_speed_dist), tmp_buf);
1205   }
1206
1207   gint ix = (gint)x;
1208   // Ensure ix is inbounds
1209   if (ix == widgets->profile_width)
1210     ix--;
1211
1212   if ( widgets->speeds_dist == NULL )
1213     return;
1214
1215   // Show track speed for this position
1216   if (widgets->w_cur_speed_speed) {
1217     static gchar tmp_buf[20];
1218     // Even if GPS speed available (trackpoint->speed), the text will correspond to the speed map shown
1219     // No conversions needed as already in appropriate units
1220     vik_units_speed_t speed_units = a_vik_get_units_speed ();
1221     switch (speed_units) {
1222     case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
1223       g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f kph"), widgets->speeds_dist[ix]);
1224       break;
1225     case VIK_UNITS_SPEED_MILES_PER_HOUR:
1226       g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f mph"), widgets->speeds_dist[ix]);
1227       break;
1228     case VIK_UNITS_SPEED_KNOTS:
1229       g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f knots"), widgets->speeds_dist[ix]);
1230       break;
1231     default:
1232       // VIK_UNITS_SPEED_METRES_PER_SECOND:
1233       g_snprintf(tmp_buf, sizeof(tmp_buf), _("%.1f m/s"), widgets->speeds_dist[ix]);
1234       break;
1235     }
1236     gtk_label_set_text(GTK_LABEL(widgets->w_cur_speed_speed), tmp_buf);
1237   }
1238
1239   widgets->blob_tp = trackpoint;
1240
1241   GtkWidget *window = gtk_widget_get_toplevel (event_box);
1242   GList *child = gtk_container_get_children(GTK_CONTAINER(event_box));
1243   GtkWidget *image = GTK_WIDGET(child->data);
1244
1245   gint y_blob = blobby_speed_dist (x, widgets);
1246
1247   gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
1248   if (widgets->is_marker_drawn) {
1249     gdouble pc = tp_percentage_by_distance ( widgets->tr, widgets->marker_tp, widgets->track_length_inc_gaps );
1250     if (!isnan(pc)) {
1251       marker_x = (pc * widgets->profile_width) + MARGIN_X;
1252     }
1253   }
1254
1255   save_image_and_draw_graph_marks (image,
1256                                    marker_x,
1257                                    gtk_widget_get_style(window)->black_gc,
1258                                    MARGIN_X+x,
1259                                    MARGIN_Y+y_blob,
1260                                    &widgets->speed_dist_graph_saved_img,
1261                                    widgets->profile_width,
1262                                    widgets->profile_height,
1263                                    &widgets->is_marker_drawn,
1264                                    &widgets->is_blob_drawn);
1265
1266   g_list_free(child);
1267 }
1268
1269 /**
1270  * Draws DEM points and a respresentative speed on the supplied pixmap
1271  *   (which is the elevations graph)
1272  */
1273 static void draw_dem_alt_speed_dist(VikTrack *tr,
1274                                     GdkDrawable *pix,
1275                                     GdkGC *alt_gc,
1276                                     GdkGC *speed_gc,
1277                                     gdouble alt_offset,
1278                                     gdouble alt_diff,
1279                                     gdouble max_speed_in,
1280                                     gint cia,
1281                                     gint width,
1282                                     gint height,
1283                                     gint margin,
1284                                     gboolean do_dem,
1285                                     gboolean do_speed)
1286 {
1287   GList *iter;
1288   gdouble max_speed = 0;
1289   gdouble total_length = vik_track_get_length_including_gaps(tr);
1290
1291   // Calculate the max speed factor
1292   if (do_speed)
1293     max_speed = max_speed_in * 110 / 100;
1294
1295   gdouble dist = 0;
1296   gint h2 = height + MARGIN_Y; // Adjust height for x axis labelling offset
1297   gint achunk = chunksa[cia]*LINES;
1298
1299   for (iter = tr->trackpoints->next; iter; iter = iter->next) {
1300     int x;
1301     dist += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
1302                              &(VIK_TRACKPOINT(iter->prev->data)->coord) );
1303     x = (width * dist)/total_length + margin;
1304     if (do_dem) {
1305       gint16 elev = a_dems_get_elev_by_coord(&(VIK_TRACKPOINT(iter->data)->coord), VIK_DEM_INTERPOL_BEST);
1306       if ( elev != VIK_DEM_INVALID_ELEVATION ) {
1307         // Convert into height units
1308         if (a_vik_get_units_height () == VIK_UNITS_HEIGHT_FEET)
1309           elev =  VIK_METERS_TO_FEET(elev);
1310         // No conversion needed if already in metres
1311
1312         // offset is in current height units
1313         elev -= alt_offset;
1314
1315         // consider chunk size
1316         int y_alt = h2 - ((height * elev)/achunk );
1317         gdk_draw_rectangle(GDK_DRAWABLE(pix), alt_gc, TRUE, x-2, y_alt-2, 4, 4);
1318       }
1319     }
1320     if (do_speed) {
1321       // This is just a speed indicator - no actual values can be inferred by user
1322       if (!isnan(VIK_TRACKPOINT(iter->data)->speed)) {
1323         int y_speed = h2 - (height * VIK_TRACKPOINT(iter->data)->speed)/max_speed;
1324         gdk_draw_rectangle(GDK_DRAWABLE(pix), speed_gc, TRUE, x-2, y_speed-2, 4, 4);
1325       }
1326     }
1327   }
1328 }
1329
1330 /**
1331  * draw_grid_y:
1332  *
1333  * A common way to draw the grid with y axis labels
1334  *
1335  */
1336 static void draw_grid_y ( GtkWidget *window, GtkWidget *image, PropWidgets *widgets, GdkPixmap *pix, gchar *ss, gint i )
1337 {
1338   PangoLayout *pl = gtk_widget_create_pango_layout (GTK_WIDGET(image), NULL);
1339
1340   pango_layout_set_alignment (pl, PANGO_ALIGN_RIGHT);
1341   pango_layout_set_font_description (pl, gtk_widget_get_style(window)->font_desc);
1342
1343   gchar *label_markup = g_strdup_printf ( "<span size=\"small\">%s</span>", ss );
1344   pango_layout_set_markup ( pl, label_markup, -1 );
1345   g_free ( label_markup );
1346
1347   int w, h;
1348   pango_layout_get_pixel_size ( pl, &w, &h );
1349
1350   gdk_draw_layout ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->fg_gc[0],
1351                     MARGIN_X-w-3,
1352                     CLAMP((int)i*widgets->profile_height/LINES - h/2 + MARGIN_Y, 0, widgets->profile_height-h+MARGIN_Y),
1353                     pl );
1354   g_object_unref ( G_OBJECT ( pl ) );
1355
1356   gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[0],
1357                   MARGIN_X, MARGIN_Y + widgets->profile_height/LINES * i,
1358                   MARGIN_X + widgets->profile_width, MARGIN_Y + widgets->profile_height/LINES * i );
1359 }
1360
1361 /**
1362  * draw_grid_x_time:
1363  *
1364  * A common way to draw the grid with x axis labels for time graphs
1365  *
1366  */
1367 static void draw_grid_x_time ( GtkWidget *window, GtkWidget *image, PropWidgets *widgets, GdkPixmap *pix, guint ii, guint tt, guint xx )
1368 {
1369   gchar *label_markup = NULL;
1370   switch (ii) {
1371     case 0:
1372     case 1:
1373     case 2:
1374     case 3:
1375       // Minutes
1376       label_markup = g_strdup_printf ( "<span size=\"small\">%d %s</span>", tt/60, _("mins") );
1377       break;
1378     case 4:
1379     case 5:
1380     case 6:
1381     case 7:
1382       // Hours
1383       label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", (gdouble)tt/(60*60), _("h") );
1384       break;
1385     case 8:
1386     case 9:
1387     case 10:
1388       // Days
1389       label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", (gdouble)tt/(60*60*24), _("d") );
1390       break;
1391     case 11:
1392     case 12:
1393       // Weeks
1394       label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", (gdouble)tt/(60*60*24*7), _("w") );
1395       break;
1396     case 13:
1397       // 'Months'
1398       label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", (gdouble)tt/(60*60*24*28), _("M") );
1399       break;
1400     default:
1401       break;
1402   }
1403   if ( label_markup ) {
1404
1405     PangoLayout *pl = gtk_widget_create_pango_layout (GTK_WIDGET(image), NULL);
1406     pango_layout_set_font_description (pl, gtk_widget_get_style(window)->font_desc);
1407
1408     pango_layout_set_markup ( pl, label_markup, -1 );
1409     g_free ( label_markup );
1410     int ww, hh;
1411     pango_layout_get_pixel_size ( pl, &ww, &hh );
1412
1413     gdk_draw_layout ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->fg_gc[0],
1414                       MARGIN_X+xx-ww/2, MARGIN_Y/2-hh/2, pl );
1415     g_object_unref ( G_OBJECT ( pl ) );
1416   }
1417
1418   gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[0],
1419                   MARGIN_X+xx, MARGIN_Y, MARGIN_X+xx, MARGIN_Y+widgets->profile_height );
1420 }
1421
1422 /**
1423  * draw_grid_x_distance:
1424  *
1425  * A common way to draw the grid with x axis labels for distance graphs
1426  *
1427  */
1428 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 )
1429 {
1430   gchar *label_markup = NULL;
1431   switch ( dist_units ) {
1432   case VIK_UNITS_DISTANCE_MILES:
1433     if ( ii > 4 )
1434       label_markup = g_strdup_printf ( "<span size=\"small\">%d %s</span>", (guint)dd, _("miles") );
1435     else
1436       label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", dd, _("miles") );
1437     break;
1438   case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1439     if ( ii > 4 )
1440       label_markup = g_strdup_printf ( "<span size=\"small\">%d %s</span>", (guint)dd, _("NM") );
1441     else
1442       label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", dd, _("NM") );
1443     break;
1444   default:
1445     // VIK_UNITS_DISTANCE_KILOMETRES:
1446     if ( ii > 4 )
1447       label_markup = g_strdup_printf ( "<span size=\"small\">%d %s</span>", (guint)dd, _("km") );
1448     else
1449       label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", dd, _("km") );
1450     break;
1451   }
1452
1453   if ( label_markup ) {
1454     PangoLayout *pl = gtk_widget_create_pango_layout (GTK_WIDGET(image), NULL);
1455     pango_layout_set_font_description (pl, gtk_widget_get_style(window)->font_desc);
1456
1457     pango_layout_set_markup ( pl, label_markup, -1 );
1458     g_free ( label_markup );
1459     int ww, hh;
1460     pango_layout_get_pixel_size ( pl, &ww, &hh );
1461
1462     gdk_draw_layout ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->fg_gc[0],
1463                       MARGIN_X+xx-ww/2, MARGIN_Y/2-hh/2, pl );
1464     g_object_unref ( G_OBJECT ( pl ) );
1465   }
1466
1467   gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[0],
1468                   MARGIN_X+xx, MARGIN_Y, MARGIN_X+xx, MARGIN_Y+widgets->profile_height );
1469 }
1470
1471 /**
1472  * clear the images (scale texts & actual graph)
1473  */
1474 static void clear_images (GdkPixmap *pix, GtkWidget *window, PropWidgets *widgets)
1475 {
1476   gdk_draw_rectangle(GDK_DRAWABLE(pix), gtk_widget_get_style(window)->bg_gc[0],
1477                      TRUE, 0, 0, widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y);
1478   gdk_draw_rectangle(GDK_DRAWABLE(pix), gtk_widget_get_style(window)->mid_gc[0],
1479                      TRUE, 0, 0, widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y);
1480 }
1481
1482 /**
1483  *
1484  */
1485 static void draw_distance_divisions ( GtkWidget *window, GtkWidget *image, GdkPixmap *pix, PropWidgets *widgets, vik_units_distance_t dist_units )
1486 {
1487   // Set to display units from length in metres.
1488   gdouble length = widgets->track_length_inc_gaps;
1489   switch (dist_units) {
1490     case VIK_UNITS_DISTANCE_MILES:
1491       length = VIK_METERS_TO_MILES(length);
1492       break;
1493     case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1494       length = VIK_METERS_TO_NAUTICAL_MILES(length);
1495       break;
1496     default:
1497       // KM
1498       length = length/1000.0;
1499       break;
1500   }
1501   guint index = get_distance_chunk_index ( length );
1502   gdouble dist_per_pixel = length/widgets->profile_width;
1503
1504   for (guint i=1; chunksd[index]*i <= length; i++) {
1505     draw_grid_x_distance ( window, image, widgets, pix, index, chunksd[index]*i, (guint)(chunksd[index]*i/dist_per_pixel), dist_units );
1506   }
1507 }
1508
1509 /**
1510  * Draw just the height profile image
1511  */
1512 static void draw_elevations (GtkWidget *image, VikTrack *tr, PropWidgets *widgets )
1513 {
1514   guint i;
1515
1516   GdkGC *no_alt_info;
1517   GdkColor color;
1518
1519   // Free previous allocation
1520   if ( widgets->altitudes )
1521     g_free ( widgets->altitudes );
1522
1523   widgets->altitudes = vik_track_make_elevation_map ( tr, widgets->profile_width );
1524
1525   if ( widgets->altitudes == NULL )
1526     return;
1527
1528   // Convert into appropriate units
1529   vik_units_height_t height_units = a_vik_get_units_height ();
1530   if ( height_units == VIK_UNITS_HEIGHT_FEET ) {
1531     // Convert altitudes into feet units
1532     for ( i = 0; i < widgets->profile_width; i++ ) {
1533       widgets->altitudes[i] = VIK_METERS_TO_FEET(widgets->altitudes[i]);
1534     }
1535   }
1536   // Otherwise leave in metres
1537
1538   minmax_array(widgets->altitudes, &widgets->min_altitude, &widgets->max_altitude, TRUE, widgets->profile_width);
1539
1540   get_new_min_and_chunk_index (widgets->min_altitude, widgets->max_altitude, chunksa, G_N_ELEMENTS(chunksa), &widgets->draw_min_altitude, &widgets->cia);
1541
1542   // Assign locally
1543   gdouble mina = widgets->draw_min_altitude;
1544
1545   GtkWidget *window = gtk_widget_get_toplevel (widgets->elev_box);
1546   GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
1547
1548   gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
1549
1550   no_alt_info = gdk_gc_new ( gtk_widget_get_window(window) );
1551   gdk_color_parse ( "yellow", &color );
1552   gdk_gc_set_rgb_fg_color ( no_alt_info, &color);
1553
1554   // Reset before redrawing
1555   clear_images (pix, window, widgets);
1556
1557   /* draw grid */
1558   for (i=0; i<=LINES; i++) {
1559     gchar s[32];
1560
1561     switch (height_units) {
1562     case VIK_UNITS_HEIGHT_METRES:
1563       sprintf(s, "%8dm", (int)(mina + (LINES-i)*chunksa[widgets->cia]));
1564       break;
1565     case VIK_UNITS_HEIGHT_FEET:
1566       // NB values already converted into feet
1567       sprintf(s, "%8dft", (int)(mina + (LINES-i)*chunksa[widgets->cia]));
1568       break;
1569     default:
1570       sprintf(s, "--");
1571       g_critical("Houston, we've had a problem. height=%d", height_units);
1572     }
1573
1574     draw_grid_y ( window, image, widgets, pix, s, i );
1575   }
1576
1577   draw_distance_divisions ( window, image, pix, widgets, a_vik_get_units_distance() );
1578
1579   /* draw elevations */
1580   guint height = MARGIN_Y+widgets->profile_height;
1581   for ( i = 0; i < widgets->profile_width; i++ )
1582     if ( widgets->altitudes[i] == VIK_DEFAULT_ALTITUDE )
1583       gdk_draw_line ( GDK_DRAWABLE(pix), no_alt_info, 
1584                       i + MARGIN_X, MARGIN_Y, i + MARGIN_X, height );
1585     else 
1586       gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
1587                       i + MARGIN_X, height, i + MARGIN_X, height-widgets->profile_height*(widgets->altitudes[i]-mina)/(chunksa[widgets->cia]*LINES) );
1588
1589   if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_dem)) ||
1590        gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_alt_gps_speed)) ) {
1591
1592     GdkGC *dem_alt_gc = gdk_gc_new ( gtk_widget_get_window(window) );
1593     GdkGC *gps_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
1594
1595     gdk_color_parse ( "green", &color );
1596     gdk_gc_set_rgb_fg_color ( dem_alt_gc, &color);
1597
1598     gdk_color_parse ( "red", &color );
1599     gdk_gc_set_rgb_fg_color ( gps_speed_gc, &color);
1600
1601     // Ensure somekind of max speed when not set
1602     if ( widgets->max_speed < 0.01 )
1603       widgets->max_speed = vik_track_get_max_speed(tr);
1604
1605     draw_dem_alt_speed_dist(tr,
1606                             GDK_DRAWABLE(pix),
1607                             dem_alt_gc,
1608                             gps_speed_gc,
1609                             mina,
1610                             widgets->max_altitude - mina,
1611                             widgets->max_speed,
1612                             widgets->cia,
1613                             widgets->profile_width,
1614                             widgets->profile_height,
1615                             MARGIN_X,
1616                             gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_dem)),
1617                             gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_alt_gps_speed)));
1618     
1619     g_object_unref ( G_OBJECT(dem_alt_gc) );
1620     g_object_unref ( G_OBJECT(gps_speed_gc) );
1621   }
1622
1623   /* draw border */
1624   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);
1625
1626   g_object_unref ( G_OBJECT(pix) );
1627   g_object_unref ( G_OBJECT(no_alt_info) );
1628 }
1629
1630 /**
1631  * Draws representative speed on the supplied pixmap
1632  *   (which is the gradients graph)
1633  */
1634 static void draw_speed_dist(VikTrack *tr,
1635                                     GdkDrawable *pix,
1636                                     GdkGC *speed_gc,
1637                                     gdouble max_speed_in,
1638                                     gint width,
1639                                     gint height,
1640                                     gint margin,
1641                                     gboolean do_speed)
1642 {
1643   GList *iter;
1644   gdouble max_speed = 0;
1645   gdouble total_length = vik_track_get_length_including_gaps(tr);
1646
1647   // Calculate the max speed factor
1648   if (do_speed)
1649     max_speed = max_speed_in * 110 / 100;
1650
1651   gdouble dist = 0;
1652   for (iter = tr->trackpoints->next; iter; iter = iter->next) {
1653     int x;
1654     dist += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
1655                              &(VIK_TRACKPOINT(iter->prev->data)->coord) );
1656     x = (width * dist)/total_length + MARGIN_X;
1657     if (do_speed) {
1658       // This is just a speed indicator - no actual values can be inferred by user
1659       if (!isnan(VIK_TRACKPOINT(iter->data)->speed)) {
1660         int y_speed = height - (height * VIK_TRACKPOINT(iter->data)->speed)/max_speed;
1661         gdk_draw_rectangle(GDK_DRAWABLE(pix), speed_gc, TRUE, x-2, y_speed-2, 4, 4);
1662       }
1663     }
1664   }
1665 }
1666
1667 /**
1668  * Draw just the gradient image
1669  */
1670 static void draw_gradients (GtkWidget *image, VikTrack *tr, PropWidgets *widgets )
1671 {
1672   guint i;
1673
1674   // Free previous allocation
1675   if ( widgets->gradients )
1676     g_free ( widgets->gradients );
1677
1678   widgets->gradients = vik_track_make_gradient_map ( tr, widgets->profile_width );
1679
1680   if ( widgets->gradients == NULL )
1681     return;
1682
1683   minmax_array(widgets->gradients, &widgets->min_gradient, &widgets->max_gradient, TRUE, widgets->profile_width);
1684
1685   get_new_min_and_chunk_index (widgets->min_gradient, widgets->max_gradient, chunksg, G_N_ELEMENTS(chunksg), &widgets->draw_min_gradient, &widgets->cig);
1686
1687   // Assign locally
1688   gdouble mina = widgets->draw_min_gradient;
1689
1690   GtkWidget *window = gtk_widget_get_toplevel (widgets->gradient_box);
1691   GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
1692
1693   gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
1694
1695   // Reset before redrawing
1696   clear_images (pix, window, widgets);
1697
1698   /* draw grid */
1699   for (i=0; i<=LINES; i++) {
1700     gchar s[32];
1701
1702     sprintf(s, "%8d%%", (int)(mina + (LINES-i)*chunksg[widgets->cig]));
1703
1704     draw_grid_y ( window, image, widgets, pix, s, i );
1705   }
1706
1707   draw_distance_divisions ( window, image, pix, widgets, a_vik_get_units_distance() );
1708
1709   /* draw gradients */
1710   guint height = widgets->profile_height + MARGIN_Y;
1711   for ( i = 0; i < widgets->profile_width; i++ )
1712     gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
1713                     i + MARGIN_X, height, i + MARGIN_X, height - widgets->profile_height*(widgets->gradients[i]-mina)/(chunksg[widgets->cig]*LINES) );
1714
1715   if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_gradient_gps_speed)) ) {
1716     GdkGC *gps_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
1717
1718     GdkColor color;
1719     gdk_color_parse ( "red", &color );
1720     gdk_gc_set_rgb_fg_color ( gps_speed_gc, &color);
1721
1722     // Ensure somekind of max speed when not set
1723     if ( widgets->max_speed < 0.01 )
1724       widgets->max_speed = vik_track_get_max_speed(tr);
1725
1726     draw_speed_dist(tr,
1727                             GDK_DRAWABLE(pix),
1728                             gps_speed_gc,
1729                             widgets->max_speed,
1730                             widgets->profile_width,
1731                             widgets->profile_height,
1732                             MARGIN_X,
1733                             gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_alt_gps_speed)));
1734     
1735     g_object_unref ( G_OBJECT(gps_speed_gc) );
1736   }
1737
1738   /* draw border */
1739   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);
1740
1741   g_object_unref ( G_OBJECT(pix) );
1742 }
1743
1744 static void draw_time_lines ( GtkWidget *window, GtkWidget *image, GdkPixmap *pix, PropWidgets *widgets )
1745 {
1746   guint index = get_time_chunk_index ( widgets->duration );
1747   gdouble time_per_pixel = (gdouble)(widgets->duration)/widgets->profile_width;
1748
1749   // If stupidly long track in time - don't bother trying to draw grid lines
1750   if ( widgets->duration > chunkst[G_N_ELEMENTS(chunkst)-1]*LINES*LINES )
1751     return;
1752
1753   for (guint i=1; chunkst[index]*i <= widgets->duration; i++) {
1754     draw_grid_x_time ( window, image, widgets, pix, index, chunkst[index]*i, (guint)(chunkst[index]*i/time_per_pixel) );
1755   }
1756 }
1757
1758 /**
1759  * Draw just the speed (velocity)/time image
1760  */
1761 static void draw_vt ( GtkWidget *image, VikTrack *tr, PropWidgets *widgets)
1762 {
1763   guint i;
1764
1765   // Free previous allocation
1766   if ( widgets->speeds )
1767     g_free ( widgets->speeds );
1768
1769   widgets->speeds = vik_track_make_speed_map ( tr, widgets->profile_width );
1770   if ( widgets->speeds == NULL )
1771     return;
1772
1773   widgets->duration = vik_track_get_duration ( tr );
1774   // Negative time or other problem
1775   if ( widgets->duration <= 0 )
1776     return;
1777
1778   // Convert into appropriate units
1779   vik_units_speed_t speed_units = a_vik_get_units_speed ();
1780   switch (speed_units) {
1781   case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
1782     for ( i = 0; i < widgets->profile_width; i++ ) {
1783       widgets->speeds[i] = VIK_MPS_TO_KPH(widgets->speeds[i]);
1784     }
1785     break;
1786   case VIK_UNITS_SPEED_MILES_PER_HOUR:
1787     for ( i = 0; i < widgets->profile_width; i++ ) {
1788       widgets->speeds[i] = VIK_MPS_TO_MPH(widgets->speeds[i]);
1789     }
1790     break;
1791   case VIK_UNITS_SPEED_KNOTS:
1792     for ( i = 0; i < widgets->profile_width; i++ ) {
1793       widgets->speeds[i] = VIK_MPS_TO_KNOTS(widgets->speeds[i]);
1794     }
1795     break;
1796   default:
1797     // VIK_UNITS_SPEED_METRES_PER_SECOND:
1798     // No need to convert as already in m/s
1799     break;
1800   }
1801
1802   GtkWidget *window = gtk_widget_get_toplevel (widgets->speed_box);
1803   GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
1804
1805   gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
1806
1807   minmax_array(widgets->speeds, &widgets->min_speed, &widgets->max_speed, FALSE, widgets->profile_width);
1808   if (widgets->min_speed < 0.0)
1809     widgets->min_speed = 0; /* splines sometimes give negative speeds */
1810
1811   /* Find suitable chunk index */
1812   get_new_min_and_chunk_index (widgets->min_speed, widgets->max_speed, chunkss, G_N_ELEMENTS(chunkss), &widgets->draw_min_speed, &widgets->cis);
1813
1814   // Assign locally
1815   gdouble mins = widgets->draw_min_speed;
1816
1817   // Reset before redrawing
1818   clear_images (pix, window, widgets);
1819
1820   /* draw grid */
1821   for (i=0; i<=LINES; i++) {
1822     gchar s[32];
1823
1824     // NB: No need to convert here anymore as numbers are in the appropriate units
1825     switch (speed_units) {
1826     case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
1827       sprintf(s, "%8dkm/h", (int)(mins + (LINES-i)*chunkss[widgets->cis]));
1828       break;
1829     case VIK_UNITS_SPEED_MILES_PER_HOUR:
1830       sprintf(s, "%8dmph", (int)(mins + (LINES-i)*chunkss[widgets->cis]));
1831       break;
1832     case VIK_UNITS_SPEED_METRES_PER_SECOND:
1833       sprintf(s, "%8dm/s", (int)(mins + (LINES-i)*chunkss[widgets->cis]));
1834       break;
1835     case VIK_UNITS_SPEED_KNOTS:
1836       sprintf(s, "%8dknots", (int)(mins + (LINES-i)*chunkss[widgets->cis]));
1837       break;
1838     default:
1839       sprintf(s, "--");
1840       g_critical("Houston, we've had a problem. speed=%d", speed_units);
1841     }
1842
1843     draw_grid_y ( window, image, widgets, pix, s, i );
1844   }
1845
1846   draw_time_lines ( window, image, pix, widgets );
1847
1848   /* draw speeds */
1849   guint height = widgets->profile_height + MARGIN_Y;
1850   for ( i = 0; i < widgets->profile_width; i++ )
1851     gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
1852                     i + MARGIN_X, height, i + MARGIN_X, height - widgets->profile_height*(widgets->speeds[i]-mins)/(chunkss[widgets->cis]*LINES) );
1853
1854   if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->w_show_gps_speed)) ) {
1855
1856     GdkGC *gps_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
1857     GdkColor color;
1858     gdk_color_parse ( "red", &color );
1859     gdk_gc_set_rgb_fg_color ( gps_speed_gc, &color);
1860
1861     time_t beg_time = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
1862     time_t dur =  VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp - beg_time;
1863
1864     GList *iter;
1865     for (iter = tr->trackpoints; iter; iter = iter->next) {
1866       gdouble gps_speed = VIK_TRACKPOINT(iter->data)->speed;
1867       if (isnan(gps_speed))
1868         continue;
1869       switch (speed_units) {
1870       case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
1871         gps_speed = VIK_MPS_TO_KPH(gps_speed);
1872         break;
1873       case VIK_UNITS_SPEED_MILES_PER_HOUR:
1874         gps_speed = VIK_MPS_TO_MPH(gps_speed);
1875         break;
1876       case VIK_UNITS_SPEED_KNOTS:
1877         gps_speed = VIK_MPS_TO_KNOTS(gps_speed);
1878         break;
1879       default:
1880         // VIK_UNITS_SPEED_METRES_PER_SECOND:
1881         // No need to convert as already in m/s
1882         break;
1883       }
1884       int x = MARGIN_X + widgets->profile_width * (VIK_TRACKPOINT(iter->data)->timestamp - beg_time) / dur;
1885       int y = height - widgets->profile_height*(gps_speed - mins)/(chunkss[widgets->cis]*LINES);
1886       gdk_draw_rectangle(GDK_DRAWABLE(pix), gps_speed_gc, TRUE, x-2, y-2, 4, 4);
1887     }
1888     g_object_unref ( G_OBJECT(gps_speed_gc) );
1889   }
1890
1891   /* draw border */
1892   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);
1893
1894   g_object_unref ( G_OBJECT(pix) );
1895 }
1896
1897 /**
1898  * Draw just the distance/time image
1899  */
1900 static void draw_dt ( GtkWidget *image, VikTrack *tr, PropWidgets *widgets )
1901 {
1902   guint i;
1903
1904   // Free previous allocation
1905   if ( widgets->distances )
1906     g_free ( widgets->distances );
1907
1908   widgets->distances = vik_track_make_distance_map ( tr, widgets->profile_width );
1909   if ( widgets->distances == NULL )
1910     return;
1911
1912   // Convert into appropriate units
1913   vik_units_distance_t dist_units = a_vik_get_units_distance ();
1914   switch ( dist_units ) {
1915     case VIK_UNITS_DISTANCE_MILES:
1916       for ( i = 0; i < widgets->profile_width; i++ ) {
1917         widgets->distances[i] = VIK_METERS_TO_MILES(widgets->distances[i]);
1918       }
1919       break;
1920     case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1921       for ( i = 0; i < widgets->profile_width; i++ ) {
1922         widgets->distances[i] = VIK_METERS_TO_NAUTICAL_MILES(widgets->distances[i]);
1923       }
1924       break;
1925     default:
1926       // Metres - but want in kms
1927       for ( i = 0; i < widgets->profile_width; i++ ) {
1928         widgets->distances[i] = widgets->distances[i]/1000.0;
1929       }
1930       break;
1931   }
1932
1933   widgets->duration = vik_track_get_duration ( widgets->tr );
1934   // Negative time or other problem
1935   if ( widgets->duration <= 0 )
1936     return;
1937
1938   GtkWidget *window = gtk_widget_get_toplevel (widgets->dist_box);
1939   GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
1940
1941   gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
1942
1943   // easy to work out min / max of distance!
1944   // Assign locally
1945   // mind = 0.0; - Thus not used
1946   gdouble maxd;
1947   switch ( dist_units ) {
1948   case VIK_UNITS_DISTANCE_MILES:
1949     maxd = VIK_METERS_TO_MILES(vik_track_get_length_including_gaps (tr));
1950     break;
1951   case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1952     maxd = VIK_METERS_TO_NAUTICAL_MILES(vik_track_get_length_including_gaps (tr));
1953     break;
1954   default:
1955     maxd = vik_track_get_length_including_gaps (tr) / 1000.0;
1956     break;
1957   }
1958
1959   /* Find suitable chunk index */
1960   gdouble dummy = 0.0; // expect this to remain the same! (not that it's used)
1961   get_new_min_and_chunk_index (0, maxd, chunksd, G_N_ELEMENTS(chunksd), &dummy, &widgets->cid);
1962
1963   // Reset before redrawing
1964   clear_images (pix, window, widgets);
1965
1966   /* draw grid */
1967   for (i=0; i<=LINES; i++) {
1968     gchar s[32];
1969
1970     switch ( dist_units ) {
1971     case VIK_UNITS_DISTANCE_MILES:
1972       sprintf(s, _("%.1f miles"), ((LINES-i)*chunksd[widgets->cid]));
1973       break;
1974     case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1975       sprintf(s, _("%.1f NM"), ((LINES-i)*chunksd[widgets->cid]));
1976       break;
1977     default:
1978       sprintf(s, _("%.1f km"), ((LINES-i)*chunksd[widgets->cid]));
1979       break;
1980     }
1981
1982     draw_grid_y ( window, image, widgets, pix, s, i );
1983   }
1984   
1985   draw_time_lines ( window, image, pix, widgets );
1986
1987   /* draw distance */
1988   guint height = widgets->profile_height + MARGIN_Y;
1989   for ( i = 0; i < widgets->profile_width; i++ )
1990     gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
1991                     i + MARGIN_X, height, i + MARGIN_X, height - widgets->profile_height*(widgets->distances[i])/(chunksd[widgets->cid]*LINES) );
1992
1993   // Show speed indicator
1994   if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->w_show_dist_speed)) ) {
1995     GdkGC *dist_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
1996     GdkColor color;
1997     gdk_color_parse ( "red", &color );
1998     gdk_gc_set_rgb_fg_color ( dist_speed_gc, &color);
1999
2000     gdouble max_speed = 0;
2001     max_speed = widgets->max_speed * 110 / 100;
2002
2003     // This is just an indicator - no actual values can be inferred by user
2004     for ( i = 0; i < widgets->profile_width; i++ ) {
2005       int y_speed = widgets->profile_height - (widgets->profile_height * widgets->speeds[i])/max_speed;
2006       gdk_draw_rectangle(GDK_DRAWABLE(pix), dist_speed_gc, TRUE, i+MARGIN_X-2, y_speed-2, 4, 4);
2007     }
2008     g_object_unref ( G_OBJECT(dist_speed_gc) );
2009   }
2010
2011   /* draw border */
2012   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);
2013
2014   g_object_unref ( G_OBJECT(pix) );
2015
2016 }
2017
2018 /**
2019  * Draw just the elevation/time image
2020  */
2021 static void draw_et ( GtkWidget *image, VikTrack *tr, PropWidgets *widgets )
2022 {
2023   guint i;
2024
2025   // Free previous allocation
2026   if ( widgets->ats )
2027     g_free ( widgets->ats );
2028
2029   widgets->ats = vik_track_make_elevation_time_map ( tr, widgets->profile_width );
2030
2031   if ( widgets->ats == NULL )
2032     return;
2033
2034   // Convert into appropriate units
2035   vik_units_height_t height_units = a_vik_get_units_height ();
2036   if ( height_units == VIK_UNITS_HEIGHT_FEET ) {
2037     // Convert altitudes into feet units
2038     for ( i = 0; i < widgets->profile_width; i++ ) {
2039       widgets->ats[i] = VIK_METERS_TO_FEET(widgets->ats[i]);
2040     }
2041   }
2042   // Otherwise leave in metres
2043
2044   minmax_array(widgets->ats, &widgets->min_altitude, &widgets->max_altitude, TRUE, widgets->profile_width);
2045
2046   get_new_min_and_chunk_index (widgets->min_altitude, widgets->max_altitude, chunksa, G_N_ELEMENTS(chunksa), &widgets->draw_min_altitude_time, &widgets->ciat);
2047
2048   // Assign locally
2049   gdouble mina = widgets->draw_min_altitude_time;
2050
2051   widgets->duration = vik_track_get_duration ( widgets->tr );
2052   // Negative time or other problem
2053   if ( widgets->duration <= 0 )
2054     return;
2055
2056   GtkWidget *window = gtk_widget_get_toplevel (widgets->elev_time_box);
2057   GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2058
2059   gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
2060
2061   // Reset before redrawing
2062   clear_images (pix, window, widgets);
2063
2064   /* draw grid */
2065   for (i=0; i<=LINES; i++) {
2066     gchar s[32];
2067
2068     switch (height_units) {
2069     case VIK_UNITS_HEIGHT_METRES:
2070       sprintf(s, "%8dm", (int)(mina + (LINES-i)*chunksa[widgets->ciat]));
2071       break;
2072     case VIK_UNITS_HEIGHT_FEET:
2073       // NB values already converted into feet
2074       sprintf(s, "%8dft", (int)(mina + (LINES-i)*chunksa[widgets->ciat]));
2075       break;
2076     default:
2077       sprintf(s, "--");
2078       g_critical("Houston, we've had a problem. height=%d", height_units);
2079     }
2080
2081     draw_grid_y ( window, image, widgets, pix, s, i );
2082   }
2083
2084   draw_time_lines ( window, image, pix, widgets );
2085
2086   /* draw elevations */
2087   guint height = widgets->profile_height + MARGIN_Y;
2088   for ( i = 0; i < widgets->profile_width; i++ )
2089     gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
2090                     i + MARGIN_X, height, i + MARGIN_X, height-widgets->profile_height*(widgets->ats[i]-mina)/(chunksa[widgets->ciat]*LINES) );
2091
2092   // Show DEMS
2093   if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->w_show_elev_dem)) )  {
2094     GdkColor color;
2095     GdkGC *dem_alt_gc = gdk_gc_new ( gtk_widget_get_window(window) );
2096     gdk_color_parse ( "green", &color );
2097     gdk_gc_set_rgb_fg_color ( dem_alt_gc, &color);
2098
2099     gint h2 = widgets->profile_height + MARGIN_Y; // Adjust height for x axis labelling offset
2100     gint achunk = chunksa[widgets->ciat]*LINES;
2101
2102     for ( i = 0; i < widgets->profile_width; i++ ) {
2103       // This could be slow doing this each time...
2104       VikTrackpoint *tp = vik_track_get_closest_tp_by_percentage_time ( widgets->tr, ((gdouble)i/(gdouble)widgets->profile_width), NULL );
2105       if ( tp ) {
2106         gint16 elev = a_dems_get_elev_by_coord(&(tp->coord), VIK_DEM_INTERPOL_SIMPLE);
2107         if ( elev != VIK_DEM_INVALID_ELEVATION ) {
2108           // Convert into height units
2109           if ( a_vik_get_units_height () == VIK_UNITS_HEIGHT_FEET )
2110             elev = VIK_METERS_TO_FEET(elev);
2111           // No conversion needed if already in metres
2112
2113           // offset is in current height units
2114           elev -= mina;
2115
2116           // consider chunk size
2117           int y_alt = h2 - ((widgets->profile_height * elev)/achunk );
2118           gdk_draw_rectangle(GDK_DRAWABLE(pix), dem_alt_gc, TRUE, i+MARGIN_X-2, y_alt-2, 4, 4);
2119         }
2120       }
2121     }
2122     g_object_unref ( G_OBJECT(dem_alt_gc) );
2123   }
2124
2125   // Show speeds
2126   if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->w_show_elev_speed)) ) {
2127     GdkColor color;
2128     // This is just an indicator - no actual values can be inferred by user
2129     GdkGC *elev_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
2130     gdk_color_parse ( "red", &color );
2131     gdk_gc_set_rgb_fg_color ( elev_speed_gc, &color);
2132
2133     gdouble max_speed = widgets->max_speed * 110 / 100;
2134
2135     for ( i = 0; i < widgets->profile_width; i++ ) {
2136       int y_speed = widgets->profile_height - (widgets->profile_height * widgets->speeds[i])/max_speed;
2137       gdk_draw_rectangle(GDK_DRAWABLE(pix), elev_speed_gc, TRUE, i+MARGIN_X-2, y_speed-2, 4, 4);
2138     }
2139
2140     g_object_unref ( G_OBJECT(elev_speed_gc) );
2141   }
2142
2143   /* draw border */
2144   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);
2145
2146   g_object_unref ( G_OBJECT(pix) );
2147 }
2148
2149 /**
2150  * Draw just the speed/distance image
2151  */
2152 static void draw_sd ( GtkWidget *image, VikTrack *tr, PropWidgets *widgets)
2153 {
2154   gdouble mins;
2155   guint i;
2156
2157   // Free previous allocation
2158   if ( widgets->speeds_dist )
2159     g_free ( widgets->speeds_dist );
2160
2161   widgets->speeds_dist = vik_track_make_speed_dist_map ( tr, widgets->profile_width );
2162   if ( widgets->speeds_dist == NULL )
2163     return;
2164
2165   // Convert into appropriate units
2166   vik_units_speed_t speed_units = a_vik_get_units_speed ();
2167   switch (speed_units) {
2168   case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
2169     for ( i = 0; i < widgets->profile_width; i++ ) {
2170       widgets->speeds_dist[i] = VIK_MPS_TO_KPH(widgets->speeds_dist[i]);
2171     }
2172     break;
2173   case VIK_UNITS_SPEED_MILES_PER_HOUR:
2174     for ( i = 0; i < widgets->profile_width; i++ ) {
2175       widgets->speeds_dist[i] = VIK_MPS_TO_MPH(widgets->speeds_dist[i]);
2176     }
2177     break;
2178   case VIK_UNITS_SPEED_KNOTS:
2179     for ( i = 0; i < widgets->profile_width; i++ ) {
2180       widgets->speeds_dist[i] = VIK_MPS_TO_KNOTS(widgets->speeds_dist[i]);
2181     }
2182     break;
2183   default:
2184     // VIK_UNITS_SPEED_METRES_PER_SECOND:
2185     // No need to convert as already in m/s
2186     break;
2187   }
2188
2189   GtkWidget *window = gtk_widget_get_toplevel (widgets->speed_dist_box);
2190   GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2191
2192   gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
2193
2194   // OK to resuse min_speed here
2195   minmax_array(widgets->speeds_dist, &widgets->min_speed, &widgets->max_speed_dist, FALSE, widgets->profile_width);
2196   if (widgets->min_speed < 0.0)
2197     widgets->min_speed = 0; /* splines sometimes give negative speeds */
2198
2199   /* Find suitable chunk index */
2200   get_new_min_and_chunk_index (widgets->min_speed, widgets->max_speed_dist, chunkss, G_N_ELEMENTS(chunkss), &widgets->draw_min_speed, &widgets->cisd);
2201
2202   // Assign locally
2203   mins = widgets->draw_min_speed;
2204   
2205   // Reset before redrawing
2206   clear_images (pix, window, widgets);
2207
2208   /* draw grid */
2209   for (i=0; i<=LINES; i++) {
2210     gchar s[32];
2211
2212     // NB: No need to convert here anymore as numbers are in the appropriate units
2213     switch (speed_units) {
2214     case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
2215       sprintf(s, "%8dkm/h", (int)(mins + (LINES-i)*chunkss[widgets->cisd]));
2216       break;
2217     case VIK_UNITS_SPEED_MILES_PER_HOUR:
2218       sprintf(s, "%8dmph", (int)(mins + (LINES-i)*chunkss[widgets->cisd]));
2219       break;
2220     case VIK_UNITS_SPEED_METRES_PER_SECOND:
2221       sprintf(s, "%8dm/s", (int)(mins + (LINES-i)*chunkss[widgets->cisd]));
2222       break;
2223     case VIK_UNITS_SPEED_KNOTS:
2224       sprintf(s, "%8dknots", (int)(mins + (LINES-i)*chunkss[widgets->cisd]));
2225       break;
2226     default:
2227       sprintf(s, "--");
2228       g_critical("Houston, we've had a problem. speed=%d", speed_units);
2229     }
2230
2231     draw_grid_y ( window, image, widgets, pix, s, i );
2232   }
2233
2234   draw_distance_divisions ( window, image, pix, widgets, a_vik_get_units_distance() );
2235
2236   /* draw speeds */
2237   guint height = widgets->profile_height + MARGIN_Y;
2238   for ( i = 0; i < widgets->profile_width; i++ )
2239     gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
2240                     i + MARGIN_X, height, i + MARGIN_X, height - widgets->profile_height*(widgets->speeds_dist[i]-mins)/(chunkss[widgets->cisd]*LINES) );
2241
2242
2243   if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->w_show_sd_gps_speed)) ) {
2244
2245     GdkGC *gps_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
2246     GdkColor color;
2247     gdk_color_parse ( "red", &color );
2248     gdk_gc_set_rgb_fg_color ( gps_speed_gc, &color);
2249
2250     gdouble dist = vik_track_get_length_including_gaps(tr);
2251     gdouble dist_tp = 0.0;
2252
2253     GList *iter = tr->trackpoints;
2254     for (iter = iter->next; iter; iter = iter->next) {
2255       gdouble gps_speed = VIK_TRACKPOINT(iter->data)->speed;
2256       if (isnan(gps_speed))
2257         continue;
2258       switch (speed_units) {
2259       case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
2260         gps_speed = VIK_MPS_TO_KPH(gps_speed);
2261         break;
2262       case VIK_UNITS_SPEED_MILES_PER_HOUR:
2263         gps_speed = VIK_MPS_TO_MPH(gps_speed);
2264         break;
2265       case VIK_UNITS_SPEED_KNOTS:
2266         gps_speed = VIK_MPS_TO_KNOTS(gps_speed);
2267         break;
2268       default:
2269         // VIK_UNITS_SPEED_METRES_PER_SECOND:
2270         // No need to convert as already in m/s
2271         break;
2272       }
2273       dist_tp += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord), &(VIK_TRACKPOINT(iter->prev->data)->coord) );
2274       int x = MARGIN_X + (widgets->profile_width * dist_tp / dist);
2275       int y = height - widgets->profile_height*(gps_speed - mins)/(chunkss[widgets->cisd]*LINES);
2276       gdk_draw_rectangle(GDK_DRAWABLE(pix), gps_speed_gc, TRUE, x-2, y-2, 4, 4);
2277     }
2278     g_object_unref ( G_OBJECT(gps_speed_gc) );
2279   }
2280
2281   /* draw border */
2282   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);
2283
2284   g_object_unref ( G_OBJECT(pix) );
2285 }
2286 #undef LINES
2287
2288 /**
2289  * Draw all graphs
2290  */
2291 static void draw_all_graphs ( GtkWidget *widget, PropWidgets *widgets, gboolean resized )
2292 {
2293   // Draw graphs even if they are not visible
2294
2295   GList *child = NULL;
2296   GtkWidget *image = NULL;
2297   GtkWidget *window = gtk_widget_get_toplevel(widget);
2298   gdouble pc = NAN;
2299   gdouble pc_blob = NAN;
2300
2301   // Draw elevations
2302   if (widgets->elev_box != NULL) {
2303
2304     // Saved image no longer any good as we've resized, so we remove it here
2305     if (resized && widgets->elev_graph_saved_img.img) {
2306       g_object_unref(widgets->elev_graph_saved_img.img);
2307       widgets->elev_graph_saved_img.img = NULL;
2308       widgets->elev_graph_saved_img.saved = FALSE;
2309     }
2310
2311     child = gtk_container_get_children(GTK_CONTAINER(widgets->elev_box));
2312     draw_elevations (GTK_WIDGET(child->data), widgets->tr, widgets );
2313
2314     image = GTK_WIDGET(child->data);
2315     g_list_free(child);
2316
2317     // Ensure marker or blob are redrawn if necessary
2318     if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2319
2320       pc = tp_percentage_by_distance ( widgets->tr, widgets->marker_tp, widgets->track_length_inc_gaps );
2321       gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2322       gint y_blob = 0;
2323       if (widgets->is_blob_drawn) {
2324         pc_blob = tp_percentage_by_distance ( widgets->tr, widgets->blob_tp, widgets->track_length_inc_gaps );
2325         if (!isnan(pc_blob)) {
2326           x_blob = (pc_blob * widgets->profile_width);
2327         }
2328         y_blob = blobby_altitude (x_blob, widgets);
2329       }
2330
2331       gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2332       if (!isnan(pc)) {
2333         marker_x = (pc * widgets->profile_width) + MARGIN_X;
2334       }
2335
2336       save_image_and_draw_graph_marks (image,
2337                                        marker_x,
2338                                        gtk_widget_get_style(window)->black_gc,
2339                                        x_blob+MARGIN_X,
2340                                        y_blob+MARGIN_Y,
2341                                        &widgets->elev_graph_saved_img,
2342                                        widgets->profile_width,
2343                                        widgets->profile_height,
2344                                        &widgets->is_marker_drawn,
2345                                        &widgets->is_blob_drawn);
2346     }
2347   }
2348
2349   // Draw gradients
2350   if (widgets->gradient_box != NULL) {
2351
2352     // Saved image no longer any good as we've resized, so we remove it here
2353     if (resized && widgets->gradient_graph_saved_img.img) {
2354       g_object_unref(widgets->gradient_graph_saved_img.img);
2355       widgets->gradient_graph_saved_img.img = NULL;
2356       widgets->gradient_graph_saved_img.saved = FALSE;
2357     }
2358
2359     child = gtk_container_get_children(GTK_CONTAINER(widgets->gradient_box));
2360     draw_gradients (GTK_WIDGET(child->data), widgets->tr, widgets );
2361
2362     image = GTK_WIDGET(child->data);
2363     g_list_free(child);
2364
2365     // Ensure marker or blob are redrawn if necessary
2366     if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2367
2368       pc = tp_percentage_by_distance ( widgets->tr, widgets->marker_tp, widgets->track_length_inc_gaps );
2369       gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2370       gint y_blob = 0;
2371       if (widgets->is_blob_drawn) {
2372         pc_blob = tp_percentage_by_distance ( widgets->tr, widgets->blob_tp, widgets->track_length_inc_gaps );
2373         if (!isnan(pc_blob)) {
2374           x_blob = (pc_blob * widgets->profile_width);
2375         }
2376         y_blob = blobby_gradient (x_blob, widgets);
2377       }
2378
2379       gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2380       if (!isnan(pc)) {
2381         marker_x = (pc * widgets->profile_width) + MARGIN_X;
2382       }
2383
2384       save_image_and_draw_graph_marks (image,
2385                                        marker_x,
2386                                        gtk_widget_get_style(window)->black_gc,
2387                                        x_blob+MARGIN_X,
2388                                        y_blob+MARGIN_Y,
2389                                        &widgets->gradient_graph_saved_img,
2390                                        widgets->profile_width,
2391                                        widgets->profile_height,
2392                                        &widgets->is_marker_drawn,
2393                                        &widgets->is_blob_drawn);
2394     }
2395   }
2396
2397   // Draw speeds
2398   if (widgets->speed_box != NULL) {
2399
2400     // Saved image no longer any good as we've resized
2401     if (resized && widgets->speed_graph_saved_img.img) {
2402       g_object_unref(widgets->speed_graph_saved_img.img);
2403       widgets->speed_graph_saved_img.img = NULL;
2404       widgets->speed_graph_saved_img.saved = FALSE;
2405     }
2406
2407     child = gtk_container_get_children(GTK_CONTAINER(widgets->speed_box));
2408     draw_vt (GTK_WIDGET(child->data), widgets->tr, widgets );
2409
2410     image = GTK_WIDGET(child->data);
2411     g_list_free(child);
2412
2413     // Ensure marker or blob are redrawn if necessary
2414     if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2415
2416       pc = tp_percentage_by_time ( widgets->tr, widgets->marker_tp );
2417
2418       gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2419       gint    y_blob = 0;
2420       if (widgets->is_blob_drawn) {
2421         pc_blob = tp_percentage_by_time ( widgets->tr, widgets->blob_tp );
2422         if (!isnan(pc_blob)) {
2423           x_blob = (pc_blob * widgets->profile_width);
2424         }
2425          
2426         y_blob = blobby_speed (x_blob, widgets);
2427       }
2428
2429       gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2430       if (!isnan(pc)) {
2431         marker_x = (pc * widgets->profile_width) + MARGIN_X;
2432       }
2433
2434       save_image_and_draw_graph_marks (image,
2435                                        marker_x,
2436                                        gtk_widget_get_style(window)->black_gc,
2437                                        x_blob+MARGIN_X,
2438                                        y_blob+MARGIN_Y,
2439                                        &widgets->speed_graph_saved_img,
2440                                        widgets->profile_width,
2441                                        widgets->profile_height,
2442                                        &widgets->is_marker_drawn,
2443                                        &widgets->is_blob_drawn);
2444     }
2445   }
2446
2447   // Draw Distances
2448   if (widgets->dist_box != NULL) {
2449
2450     // Saved image no longer any good as we've resized
2451     if (resized && widgets->dist_graph_saved_img.img) {
2452       g_object_unref(widgets->dist_graph_saved_img.img);
2453       widgets->dist_graph_saved_img.img = NULL;
2454       widgets->dist_graph_saved_img.saved = FALSE;
2455     }
2456
2457     child = gtk_container_get_children(GTK_CONTAINER(widgets->dist_box));
2458     draw_dt (GTK_WIDGET(child->data), widgets->tr, widgets );
2459
2460     image = GTK_WIDGET(child->data);
2461     g_list_free(child);
2462
2463     // Ensure marker or blob are redrawn if necessary
2464     if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2465
2466       pc = tp_percentage_by_time ( widgets->tr, widgets->marker_tp );
2467
2468       gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2469       gint    y_blob = 0;
2470       if (widgets->is_blob_drawn) {
2471         pc_blob = tp_percentage_by_time ( widgets->tr, widgets->blob_tp );
2472         if (!isnan(pc_blob)) {
2473           x_blob = (pc_blob * widgets->profile_width);
2474         }
2475          
2476         y_blob = blobby_distance (x_blob, widgets);
2477       }
2478
2479       gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2480       if (!isnan(pc)) {
2481         marker_x = (pc * widgets->profile_width) + MARGIN_X;
2482       }
2483
2484       save_image_and_draw_graph_marks (image,
2485                                        marker_x,
2486                                        gtk_widget_get_style(window)->black_gc,
2487                                        x_blob+MARGIN_X,
2488                                        y_blob+MARGIN_Y,
2489                                        &widgets->dist_graph_saved_img,
2490                                        widgets->profile_width,
2491                                        widgets->profile_height,
2492                                        &widgets->is_marker_drawn,
2493                                        &widgets->is_blob_drawn);
2494     }
2495   }
2496
2497   // Draw Elevations in timely manner
2498   if (widgets->elev_time_box != NULL) {
2499
2500     // Saved image no longer any good as we've resized
2501     if (resized && widgets->elev_time_graph_saved_img.img) {
2502       g_object_unref(widgets->elev_time_graph_saved_img.img);
2503       widgets->elev_time_graph_saved_img.img = NULL;
2504       widgets->elev_time_graph_saved_img.saved = FALSE;
2505     }
2506
2507     child = gtk_container_get_children(GTK_CONTAINER(widgets->elev_time_box));
2508     draw_et (GTK_WIDGET(child->data), widgets->tr, widgets );
2509
2510     image = GTK_WIDGET(child->data);
2511     g_list_free(child);
2512
2513     // Ensure marker or blob are redrawn if necessary
2514     if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2515
2516       pc = tp_percentage_by_time ( widgets->tr, widgets->marker_tp );
2517
2518       gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2519       gint    y_blob = 0;
2520       if (widgets->is_blob_drawn) {
2521         pc_blob = tp_percentage_by_time ( widgets->tr, widgets->blob_tp );
2522         if (!isnan(pc_blob)) {
2523           x_blob = (pc_blob * widgets->profile_width);
2524         }
2525         y_blob = blobby_altitude_time (x_blob, widgets);
2526       }
2527
2528       gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2529       if (!isnan(pc)) {
2530         marker_x = (pc * widgets->profile_width) + MARGIN_X;
2531       }
2532
2533       save_image_and_draw_graph_marks (image,
2534                                        marker_x,
2535                                        gtk_widget_get_style(window)->black_gc,
2536                                        x_blob+MARGIN_X,
2537                                        y_blob+MARGIN_Y,
2538                                        &widgets->elev_time_graph_saved_img,
2539                                        widgets->profile_width,
2540                                        widgets->profile_height,
2541                                        &widgets->is_marker_drawn,
2542                                        &widgets->is_blob_drawn);
2543     }
2544   }
2545
2546   // Draw speed distances
2547   if (widgets->speed_dist_box != NULL) {
2548
2549     // Saved image no longer any good as we've resized, so we remove it here
2550     if (resized && widgets->speed_dist_graph_saved_img.img) {
2551       g_object_unref(widgets->speed_dist_graph_saved_img.img);
2552       widgets->speed_dist_graph_saved_img.img = NULL;
2553       widgets->speed_dist_graph_saved_img.saved = FALSE;
2554     }
2555
2556     child = gtk_container_get_children(GTK_CONTAINER(widgets->speed_dist_box));
2557     draw_sd (GTK_WIDGET(child->data), widgets->tr, widgets );
2558
2559     image = GTK_WIDGET(child->data);
2560     g_list_free(child);
2561
2562     // Ensure marker or blob are redrawn if necessary
2563     if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2564
2565       pc = tp_percentage_by_distance ( widgets->tr, widgets->marker_tp, widgets->track_length_inc_gaps );
2566       gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2567       gint y_blob = 0;
2568       if (widgets->is_blob_drawn) {
2569         pc_blob = tp_percentage_by_distance ( widgets->tr, widgets->blob_tp, widgets->track_length_inc_gaps );
2570         if (!isnan(pc_blob)) {
2571           x_blob = (pc_blob * widgets->profile_width);
2572         }
2573         y_blob = blobby_speed_dist (x_blob, widgets);
2574       }
2575
2576       gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2577       if (!isnan(pc)) {
2578         marker_x = (pc * widgets->profile_width) + MARGIN_X;
2579       }
2580
2581       save_image_and_draw_graph_marks (image,
2582                                        marker_x,
2583                                        gtk_widget_get_style(window)->black_gc,
2584                                        x_blob+MARGIN_X,
2585                                        y_blob+MARGIN_Y,
2586                                        &widgets->speed_dist_graph_saved_img,
2587                                        widgets->profile_width,
2588                                        widgets->profile_height,
2589                                        &widgets->is_marker_drawn,
2590                                        &widgets->is_blob_drawn);
2591     }
2592   }
2593
2594 }
2595
2596 /**
2597  * Configure/Resize the profile & speed/time images
2598  */
2599 static gboolean configure_event ( GtkWidget *widget, GdkEventConfigure *event, PropWidgets *widgets )
2600 {
2601   if (widgets->configure_dialog) {
2602     // Determine size offsets between dialog size and size for images
2603     // Only on the initialisation of the dialog
2604     widgets->profile_width_offset = event->width - widgets->profile_width;
2605     widgets->profile_height_offset = event->height - widgets->profile_height;
2606     widgets->configure_dialog = FALSE;
2607
2608     // Without this the settting, the dialog will only grow in vertical size - one can not then make it smaller!
2609     gtk_widget_set_size_request ( widget, widgets->profile_width+widgets->profile_width_offset, widgets->profile_height+widgets->profile_height_offset );
2610
2611     // Allow resizing back down to a minimal size (especially useful if the initial size has been made bigger after restoring from the saved settings)
2612     GdkGeometry geom = { 600+widgets->profile_width_offset, 300+widgets->profile_height_offset, 0, 0, 0, 0, 0, 0, 0, 0, GDK_GRAVITY_STATIC };
2613     gdk_window_set_geometry_hints ( gtk_widget_get_window(widget), &geom, GDK_HINT_MIN_SIZE );
2614   }
2615   else {
2616     widgets->profile_width_old = widgets->profile_width;
2617     widgets->profile_height_old = widgets->profile_height;
2618   }
2619
2620   // Now adjust From Dialog size to get image size
2621   widgets->profile_width = event->width - widgets->profile_width_offset;
2622   widgets->profile_height = event->height - widgets->profile_height_offset;
2623
2624   // ATM we receive configure_events when the dialog is moved and so no further action is necessary
2625   if ( !widgets->configure_dialog &&
2626        (widgets->profile_width_old == widgets->profile_width) && (widgets->profile_height_old == widgets->profile_height) )
2627     return FALSE;
2628
2629   // Draw stuff
2630   draw_all_graphs ( widget, widgets, TRUE );
2631
2632   return FALSE;
2633 }
2634
2635 /**
2636  * Create height profile widgets including the image and callbacks
2637  */
2638 GtkWidget *vik_trw_layer_create_profile ( GtkWidget *window, PropWidgets *widgets, gdouble *min_alt, gdouble *max_alt)
2639 {
2640   GdkPixmap *pix;
2641   GtkWidget *image;
2642   GtkWidget *eventbox;
2643
2644   // First allocation
2645   widgets->altitudes = vik_track_make_elevation_map ( widgets->tr, widgets->profile_width );
2646
2647   if ( widgets->altitudes == NULL ) {
2648     *min_alt = *max_alt = VIK_DEFAULT_ALTITUDE;
2649     return NULL;
2650   }
2651
2652   minmax_array(widgets->altitudes, min_alt, max_alt, TRUE, widgets->profile_width);
2653   
2654   pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2655   image = gtk_image_new_from_pixmap ( pix, NULL );
2656
2657   g_object_unref ( G_OBJECT(pix) );
2658
2659   eventbox = gtk_event_box_new ();
2660   g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_profile_click), widgets );
2661   g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_profile_move), widgets );
2662   gtk_container_add ( GTK_CONTAINER(eventbox), image );
2663   gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_STRUCTURE_MASK);
2664
2665   return eventbox;
2666 }
2667
2668 /**
2669  * Create height profile widgets including the image and callbacks
2670  */
2671 GtkWidget *vik_trw_layer_create_gradient ( GtkWidget *window, PropWidgets *widgets)
2672 {
2673   GdkPixmap *pix;
2674   GtkWidget *image;
2675   GtkWidget *eventbox;
2676
2677   // First allocation
2678   widgets->gradients = vik_track_make_gradient_map ( widgets->tr, widgets->profile_width );
2679
2680   if ( widgets->gradients == NULL ) {
2681     return NULL;
2682   }
2683
2684   pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2685   image = gtk_image_new_from_pixmap ( pix, NULL );
2686
2687   g_object_unref ( G_OBJECT(pix) );
2688
2689   eventbox = gtk_event_box_new ();
2690   g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_gradient_click), widgets );
2691   g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_gradient_move), widgets );
2692   gtk_container_add ( GTK_CONTAINER(eventbox), image );
2693   gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_STRUCTURE_MASK);
2694
2695   return eventbox;
2696 }
2697
2698 /**
2699  * Create speed/time widgets including the image and callbacks
2700  */
2701 GtkWidget *vik_trw_layer_create_vtdiag ( GtkWidget *window, PropWidgets *widgets)
2702 {
2703   GdkPixmap *pix;
2704   GtkWidget *image;
2705   GtkWidget *eventbox;
2706
2707   // First allocation
2708   widgets->speeds = vik_track_make_speed_map ( widgets->tr, widgets->profile_width );
2709   if ( widgets->speeds == NULL )
2710     return NULL;
2711
2712   pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2713   image = gtk_image_new_from_pixmap ( pix, NULL );
2714
2715 #if 0
2716   /* XXX this can go out, it's just a helpful dev tool */
2717   {
2718     int j;
2719     GdkGC **colors[8] = { gtk_widget_get_style(window)->bg_gc,
2720                           gtk_widget_get_style(window)->fg_gc,
2721                           gtk_widget_get_style(window)->light_gc,
2722                           gtk_widget_get_style(window)->dark_gc,
2723                           gtk_widget_get_style(window)->mid_gc,
2724                           gtk_widget_get_style(window)->text_gc,
2725                           gtk_widget_get_style(window)->base_gc,
2726                           gtk_widget_get_style(window)->text_aa_gc };
2727     for (i=0; i<5; i++) {
2728       for (j=0; j<8; j++) {
2729         gdk_draw_rectangle(GDK_DRAWABLE(pix), colors[j][i],
2730                            TRUE, i*20, j*20, 20, 20);
2731         gdk_draw_rectangle(GDK_DRAWABLE(pix), gtk_widget_get_style(window)->black_gc,
2732                            FALSE, i*20, j*20, 20, 20);
2733       }
2734     }
2735   }
2736 #endif
2737
2738   g_object_unref ( G_OBJECT(pix) );
2739
2740   eventbox = gtk_event_box_new ();
2741   g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_vt_click), widgets );
2742   g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_vt_move), widgets );
2743   gtk_container_add ( GTK_CONTAINER(eventbox), image );
2744   gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
2745
2746   return eventbox;
2747 }
2748
2749 /**
2750  * Create distance / time widgets including the image and callbacks
2751  */
2752 GtkWidget *vik_trw_layer_create_dtdiag ( GtkWidget *window, PropWidgets *widgets)
2753 {
2754   GdkPixmap *pix;
2755   GtkWidget *image;
2756   GtkWidget *eventbox;
2757
2758   // First allocation
2759   widgets->distances = vik_track_make_distance_map ( widgets->tr, widgets->profile_width );
2760   if ( widgets->distances == NULL )
2761     return NULL;
2762
2763   pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2764   image = gtk_image_new_from_pixmap ( pix, NULL );
2765
2766   g_object_unref ( G_OBJECT(pix) );
2767
2768   eventbox = gtk_event_box_new ();
2769   g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_dt_click), widgets );
2770   g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_dt_move), widgets );
2771   //g_signal_connect_swapped ( G_OBJECT(eventbox), "destroy", G_CALLBACK(g_free), widgets );
2772   gtk_container_add ( GTK_CONTAINER(eventbox), image );
2773   gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
2774
2775   return eventbox;
2776 }
2777
2778 /**
2779  * Create elevation / time widgets including the image and callbacks
2780  */
2781 GtkWidget *vik_trw_layer_create_etdiag ( GtkWidget *window, PropWidgets *widgets)
2782 {
2783   GdkPixmap *pix;
2784   GtkWidget *image;
2785   GtkWidget *eventbox;
2786
2787   // First allocation
2788   widgets->ats = vik_track_make_elevation_time_map ( widgets->tr, widgets->profile_width );
2789   if ( widgets->ats == NULL )
2790     return NULL;
2791
2792   pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2793   image = gtk_image_new_from_pixmap ( pix, NULL );
2794
2795   g_object_unref ( G_OBJECT(pix) );
2796
2797   eventbox = gtk_event_box_new ();
2798   g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_et_click), widgets );
2799   g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_et_move), widgets );
2800   gtk_container_add ( GTK_CONTAINER(eventbox), image );
2801   gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
2802
2803   return eventbox;
2804 }
2805
2806 /**
2807  * Create speed/distance widgets including the image and callbacks
2808  */
2809 GtkWidget *vik_trw_layer_create_sddiag ( GtkWidget *window, PropWidgets *widgets)
2810 {
2811   GdkPixmap *pix;
2812   GtkWidget *image;
2813   GtkWidget *eventbox;
2814
2815   // First allocation
2816   widgets->speeds_dist = vik_track_make_speed_dist_map ( widgets->tr, widgets->profile_width );
2817   if ( widgets->speeds_dist == NULL )
2818     return NULL;
2819
2820   pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2821   image = gtk_image_new_from_pixmap ( pix, NULL );
2822
2823   g_object_unref ( G_OBJECT(pix) );
2824
2825   eventbox = gtk_event_box_new ();
2826   g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_sd_click), widgets );
2827   g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_sd_move), widgets );
2828   gtk_container_add ( GTK_CONTAINER(eventbox), image );
2829   gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
2830
2831   return eventbox;
2832 }
2833 #undef MARGIN_X
2834
2835 #define VIK_SETTINGS_TRACK_PROFILE_WIDTH "track_profile_display_width"
2836 #define VIK_SETTINGS_TRACK_PROFILE_HEIGHT "track_profile_display_height"
2837
2838 static void save_values ( PropWidgets *widgets )
2839 {
2840   // Session settings
2841   a_settings_set_integer ( VIK_SETTINGS_TRACK_PROFILE_WIDTH, widgets->profile_width );
2842   a_settings_set_integer ( VIK_SETTINGS_TRACK_PROFILE_HEIGHT, widgets->profile_height );
2843
2844   // Just for this session ATM
2845   if ( widgets->w_show_dem )
2846     show_dem                = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_dem) );
2847   if ( widgets->w_show_alt_gps_speed )
2848     show_alt_gps_speed      = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_alt_gps_speed) );
2849   if ( widgets->w_show_gps_speed )
2850     show_gps_speed          = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_gps_speed) );
2851   if ( widgets->w_show_gradient_gps_speed )
2852     show_gradient_gps_speed = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_gradient_gps_speed) );
2853   if ( widgets->w_show_dist_speed )
2854     show_dist_speed         = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_dist_speed) );
2855   if ( widgets->w_show_elev_dem )
2856     show_elev_dem           = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_elev_dem) );
2857   if ( widgets->w_show_elev_speed )
2858     show_elev_speed         = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_elev_speed) );
2859   if ( widgets->w_show_sd_gps_speed )
2860     show_sd_gps_speed       = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_sd_gps_speed) );
2861 }
2862
2863 static void destroy_cb ( GtkDialog *dialog, PropWidgets *widgets )
2864 {
2865   save_values(widgets);
2866   prop_widgets_free(widgets);
2867 }
2868
2869 static void propwin_response_cb( GtkDialog *dialog, gint resp, PropWidgets *widgets )
2870 {
2871   VikTrack *tr = widgets->tr;
2872   VikTrwLayer *vtl = widgets->vtl;
2873   gboolean keep_dialog = FALSE;
2874
2875   /* FIXME: check and make sure the track still exists before doing anything to it */
2876   /* Note: destroying diaglog (eg, parent window exit) won't give "response" */
2877   switch (resp) {
2878     case GTK_RESPONSE_DELETE_EVENT: /* received delete event (not from buttons) */
2879     case GTK_RESPONSE_REJECT:
2880       break;
2881     case GTK_RESPONSE_ACCEPT:
2882       vik_track_set_comment(tr, gtk_entry_get_text(GTK_ENTRY(widgets->w_comment)));
2883       vik_track_set_description(tr, gtk_entry_get_text(GTK_ENTRY(widgets->w_description)));
2884       gtk_color_button_get_color ( GTK_COLOR_BUTTON(widgets->w_color), &(tr->color) );
2885       tr->draw_name_mode = gtk_combo_box_get_active ( GTK_COMBO_BOX(widgets->w_namelabel) );
2886       tr->max_number_dist_labels = gtk_spin_button_get_value_as_int ( GTK_SPIN_BUTTON(widgets->w_number_distlabels) );
2887       trw_layer_update_treeview ( widgets->vtl, widgets->tr );
2888       vik_layer_emit_update ( VIK_LAYER(vtl) );
2889       break;
2890     case VIK_TRW_LAYER_PROPWIN_REVERSE:
2891       vik_track_reverse(tr);
2892       vik_layer_emit_update ( VIK_LAYER(vtl) );
2893       break;
2894     case VIK_TRW_LAYER_PROPWIN_DEL_DUP:
2895       vik_track_remove_dup_points(tr); // NB ignore the returned answer
2896       // As we could have seen the nuber of dulplicates that would be deleted in the properties statistics tab,
2897       //   choose not to inform the user unnecessarily
2898
2899       /* above operation could have deleted current_tp or last_tp */
2900       trw_layer_cancel_tps_of_track ( vtl, tr );
2901       vik_layer_emit_update ( VIK_LAYER(vtl) );
2902       break;
2903     case VIK_TRW_LAYER_PROPWIN_SPLIT:
2904       {
2905         /* get new tracks, add them and then the delete old one. old can still exist on clipboard. */
2906         guint ntracks;
2907         
2908         VikTrack **tracks = vik_track_split_into_segments(tr, &ntracks);
2909         gchar *new_tr_name;
2910         guint i;
2911         for ( i = 0; i < ntracks; i++ )
2912         {
2913           if ( tracks[i] ) {
2914             new_tr_name = trw_layer_new_unique_sublayer_name ( vtl,
2915                                                                widgets->tr->is_route ? VIK_TRW_LAYER_SUBLAYER_ROUTE : VIK_TRW_LAYER_SUBLAYER_TRACK,
2916                                                                widgets->tr->name);
2917             if ( widgets->tr->is_route )
2918               vik_trw_layer_add_route ( vtl, new_tr_name, tracks[i] );
2919             else
2920               vik_trw_layer_add_track ( vtl, new_tr_name, tracks[i] );
2921             vik_track_calculate_bounds ( tracks[i] );
2922
2923             g_free ( new_tr_name );
2924           }
2925         }
2926         if ( tracks )
2927         {
2928           g_free ( tracks );
2929           /* Don't let track destroy this dialog */
2930           vik_track_clear_property_dialog(tr);
2931           if ( widgets->tr->is_route )
2932             vik_trw_layer_delete_route ( vtl, tr );
2933           else
2934             vik_trw_layer_delete_track ( vtl, tr );
2935           vik_layer_emit_update ( VIK_LAYER(vtl) ); /* chase thru the hoops */
2936         }
2937       }
2938       break;
2939     case VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER:
2940       {
2941         GList *iter = tr->trackpoints;
2942         while ((iter = iter->next)) {
2943           if (widgets->marker_tp == VIK_TRACKPOINT(iter->data))
2944             break;
2945         }
2946         if (iter == NULL) {
2947           a_dialog_msg(VIK_GTK_WINDOW_FROM_LAYER(vtl), GTK_MESSAGE_ERROR,
2948                   _("Failed spliting track. Track unchanged"), NULL);
2949           keep_dialog = TRUE;
2950           break;
2951         }
2952
2953         gchar *r_name = trw_layer_new_unique_sublayer_name(vtl,
2954                                                            widgets->tr->is_route ? VIK_TRW_LAYER_SUBLAYER_ROUTE : VIK_TRW_LAYER_SUBLAYER_TRACK,
2955                                                            widgets->tr->name);
2956         iter->prev->next = NULL;
2957         iter->prev = NULL;
2958         VikTrack *tr_right = vik_track_new();
2959         if ( tr->comment )
2960           vik_track_set_comment ( tr_right, tr->comment );
2961         tr_right->visible = tr->visible;
2962         tr_right->is_route = tr->is_route;
2963         tr_right->trackpoints = iter;
2964
2965         if ( widgets->tr->is_route )
2966           vik_trw_layer_add_route(vtl, r_name, tr_right);
2967         else
2968           vik_trw_layer_add_track(vtl, r_name, tr_right);
2969         vik_track_calculate_bounds ( tr );
2970         vik_track_calculate_bounds ( tr_right );
2971
2972         g_free ( r_name );
2973
2974         vik_layer_emit_update ( VIK_LAYER(vtl) );
2975       }
2976       break;
2977     default:
2978       fprintf(stderr, "DEBUG: unknown response\n");
2979       return;
2980   }
2981
2982   /* Keep same behaviour for now: destroy dialog if click on any button */
2983   if (!keep_dialog) {
2984     vik_track_clear_property_dialog(tr);
2985     gtk_widget_destroy ( GTK_WIDGET(dialog) );
2986   }
2987 }
2988
2989 /**
2990  * Force a redraw when checkbutton has been toggled to show/hide that information
2991  */
2992 static void checkbutton_toggle_cb ( GtkToggleButton *togglebutton, PropWidgets *widgets, gpointer dummy )
2993 {
2994   // Even though not resized, we'll pretend it is -
2995   //  as this invalidates the saved images (since the image may have changed)
2996   draw_all_graphs ( widgets->dialog, widgets, TRUE );
2997 }
2998
2999 /**
3000  *  Create the widgets for the given graph tab
3001  */
3002 static GtkWidget *create_graph_page ( GtkWidget *graph,
3003                                       const gchar *markup,
3004                                       GtkWidget *value,
3005                                       const gchar *markup2,
3006                                       GtkWidget *value2,
3007                                       const gchar *markup3,
3008                                       GtkWidget *value3,
3009                                       GtkWidget *checkbutton1,
3010                                       gboolean checkbutton1_default,
3011                                       GtkWidget *checkbutton2,
3012                                       gboolean checkbutton2_default )
3013 {
3014   GtkWidget *hbox = gtk_hbox_new ( FALSE, 10 );
3015   GtkWidget *vbox = gtk_vbox_new ( FALSE, 10 );
3016   GtkWidget *label = gtk_label_new (NULL);
3017   GtkWidget *label2 = gtk_label_new (NULL);
3018   GtkWidget *label3 = gtk_label_new (NULL);
3019   gtk_box_pack_start (GTK_BOX(vbox), graph, FALSE, FALSE, 0);
3020   gtk_label_set_markup ( GTK_LABEL(label), markup );
3021   gtk_label_set_markup ( GTK_LABEL(label2), markup2 );
3022   gtk_label_set_markup ( GTK_LABEL(label3), markup3 );
3023   gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, FALSE, 0);
3024   gtk_box_pack_start (GTK_BOX(hbox), value, FALSE, FALSE, 0);
3025   gtk_box_pack_start (GTK_BOX(hbox), label2, FALSE, FALSE, 0);
3026   gtk_box_pack_start (GTK_BOX(hbox), value2, FALSE, FALSE, 0);
3027   if ( value3 ) {
3028     gtk_box_pack_start (GTK_BOX(hbox), label3, FALSE, FALSE, 0);
3029     gtk_box_pack_start (GTK_BOX(hbox), value3, FALSE, FALSE, 0);
3030   }
3031   if (checkbutton2) {
3032     gtk_box_pack_end (GTK_BOX(hbox), checkbutton2, FALSE, FALSE, 0);
3033     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(checkbutton2), checkbutton2_default);
3034   }
3035   if (checkbutton1) {
3036     gtk_box_pack_end (GTK_BOX(hbox), checkbutton1, FALSE, FALSE, 0);
3037     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(checkbutton1), checkbutton1_default);
3038   }
3039   gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
3040
3041   return vbox;
3042 }
3043
3044 static GtkWidget *create_table (int cnt, char *labels[], GtkWidget *contents[])
3045 {
3046   GtkTable *table;
3047   int i;
3048
3049   table = GTK_TABLE(gtk_table_new (cnt, 2, FALSE));
3050   gtk_table_set_col_spacing (table, 0, 10);
3051   for (i=0; i<cnt; i++) {
3052     GtkWidget *label;
3053
3054     // Settings so the text positioning only moves around vertically when the dialog is resized
3055     // This also gives more room to see the track comment
3056     label = gtk_label_new(NULL);
3057     gtk_misc_set_alignment ( GTK_MISC(label), 1, 0.5 ); // Position text centrally in vertical plane
3058     gtk_label_set_markup ( GTK_LABEL(label), _(labels[i]) );
3059     gtk_table_attach ( table, label, 0, 1, i, i+1, GTK_FILL, GTK_SHRINK, 0, 0 );
3060     if (GTK_IS_MISC(contents[i])) {
3061       gtk_misc_set_alignment ( GTK_MISC(contents[i]), 0, 0.5 );
3062     }
3063     if ( GTK_IS_COLOR_BUTTON(contents[i]) || GTK_IS_COMBO_BOX(contents[i]) )
3064       // Buttons compressed - otherwise look weird (to me) if vertically massive
3065       gtk_table_attach ( table, contents[i], 1, 2, i, i+1, GTK_FILL, GTK_SHRINK, 0, 5 );
3066     else
3067       // Expand for comments + descriptions / labels
3068       gtk_table_attach_defaults ( table, contents[i], 1, 2, i, i+1 );
3069   }
3070
3071   return GTK_WIDGET (table);
3072 }
3073
3074 void vik_trw_layer_propwin_run ( GtkWindow *parent,
3075                                  VikTrwLayer *vtl,
3076                                  VikTrack *tr,
3077                                  gpointer vlp,
3078                                  VikViewport *vvp,
3079                                  gboolean start_on_stats )
3080 {
3081   PropWidgets *widgets = prop_widgets_new();
3082   widgets->vtl = vtl;
3083   widgets->vvp = vvp;
3084   widgets->vlp = vlp;
3085   widgets->tr = tr;
3086
3087   gint profile_size_value;
3088   // Ensure minimum values
3089   widgets->profile_width = 600;
3090   if ( a_settings_get_integer ( VIK_SETTINGS_TRACK_PROFILE_WIDTH, &profile_size_value ) )
3091     if ( profile_size_value > widgets->profile_width )
3092       widgets->profile_width = profile_size_value;
3093
3094   widgets->profile_height = 300;
3095   if ( a_settings_get_integer ( VIK_SETTINGS_TRACK_PROFILE_HEIGHT, &profile_size_value ) )
3096     if ( profile_size_value > widgets->profile_height )
3097       widgets->profile_height = profile_size_value;
3098
3099   gchar *title = g_strdup_printf(_("%s - Track Properties"), tr->name);
3100   GtkWidget *dialog = gtk_dialog_new_with_buttons (title,
3101                          parent,
3102                          GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
3103                          GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
3104                          _("Split at _Marker"), VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER,
3105                          _("Split _Segments"), VIK_TRW_LAYER_PROPWIN_SPLIT,
3106                          _("_Reverse"),        VIK_TRW_LAYER_PROPWIN_REVERSE,
3107                          _("_Delete Dupl."),   VIK_TRW_LAYER_PROPWIN_DEL_DUP,
3108                          GTK_STOCK_OK,     GTK_RESPONSE_ACCEPT,
3109                          NULL);
3110   widgets->dialog = dialog;
3111   g_signal_connect( G_OBJECT(dialog), "response", G_CALLBACK(propwin_response_cb), widgets);
3112
3113   g_free(title);
3114   GtkWidget *table;
3115   gdouble tr_len;
3116   gulong tp_count;
3117   guint seg_count;
3118
3119   gdouble min_alt, max_alt;
3120   widgets->elev_box = vik_trw_layer_create_profile(GTK_WIDGET(parent), widgets, &min_alt, &max_alt);
3121   widgets->gradient_box = vik_trw_layer_create_gradient(GTK_WIDGET(parent), widgets);
3122   widgets->speed_box = vik_trw_layer_create_vtdiag(GTK_WIDGET(parent), widgets);
3123   widgets->dist_box = vik_trw_layer_create_dtdiag(GTK_WIDGET(parent), widgets);
3124   widgets->elev_time_box = vik_trw_layer_create_etdiag(GTK_WIDGET(parent), widgets);
3125   widgets->speed_dist_box = vik_trw_layer_create_sddiag(GTK_WIDGET(parent), widgets);
3126   GtkWidget *graphs = gtk_notebook_new();
3127
3128   GtkWidget *content_prop[20];
3129   int cnt_prop = 0;
3130
3131   static gchar *label_texts[] = {
3132     N_("<b>Comment:</b>"),
3133     N_("<b>Description:</b>"),
3134     N_("<b>Color:</b>"),
3135     N_("<b>Draw Name:</b>"),
3136     N_("<b>Distance Labels:</b>"),
3137   };
3138   static gchar *stats_texts[] = {
3139     N_("<b>Track Length:</b>"),
3140     N_("<b>Trackpoints:</b>"),
3141     N_("<b>Segments:</b>"),
3142     N_("<b>Duplicate Points:</b>"),
3143     N_("<b>Max Speed:</b>"),
3144     N_("<b>Avg. Speed:</b>"),
3145     N_("<b>Moving Avg. Speed:</b>"),
3146     N_("<b>Avg. Dist. Between TPs:</b>"),
3147     N_("<b>Elevation Range:</b>"),
3148     N_("<b>Total Elevation Gain/Loss:</b>"),
3149     N_("<b>Start:</b>"),
3150     N_("<b>End:</b>"),
3151     N_("<b>Duration:</b>"),
3152   };
3153   static gchar tmp_buf[50];
3154   gdouble tmp_speed;
3155
3156   // Properties
3157   widgets->w_comment = gtk_entry_new ();
3158   if ( tr->comment )
3159     gtk_entry_set_text ( GTK_ENTRY(widgets->w_comment), tr->comment );
3160   g_signal_connect_swapped ( widgets->w_comment, "activate", G_CALLBACK(a_dialog_response_accept), GTK_DIALOG(dialog) );
3161   content_prop[cnt_prop++] = widgets->w_comment;
3162
3163   widgets->w_description = gtk_entry_new ();
3164   if ( tr->description )
3165     gtk_entry_set_text ( GTK_ENTRY(widgets->w_description), tr->description );
3166   g_signal_connect_swapped ( widgets->w_description, "activate", G_CALLBACK(a_dialog_response_accept), GTK_DIALOG(dialog) );
3167   content_prop[cnt_prop++] = widgets->w_description;
3168
3169   widgets->w_color = content_prop[cnt_prop++] = gtk_color_button_new_with_color ( &(tr->color) );
3170
3171   static gchar *draw_name_labels[] = {
3172     N_("No"),
3173     N_("Centre"),
3174     N_("Start only"),
3175     N_("End only"),
3176     N_("Start and End"),
3177     N_("Centre, Start and End"),
3178     NULL
3179   };
3180
3181   widgets->w_namelabel = content_prop[cnt_prop++] = vik_combo_box_text_new ();
3182   gchar **pstr = draw_name_labels;
3183   while ( *pstr )
3184     vik_combo_box_text_append ( widgets->w_namelabel, *(pstr++) );
3185   gtk_combo_box_set_active ( GTK_COMBO_BOX(widgets->w_namelabel), tr->draw_name_mode );
3186
3187   widgets->w_number_distlabels = content_prop[cnt_prop++] =
3188    gtk_spin_button_new ( GTK_ADJUSTMENT(gtk_adjustment_new(tr->max_number_dist_labels, 0, 100, 1, 1, 0)), 1, 0 );
3189   gtk_widget_set_tooltip_text ( GTK_WIDGET(widgets->w_number_distlabels), _("Maximum number of distance labels to be shown") );
3190
3191   table = create_table (cnt_prop, label_texts, content_prop);
3192
3193   gtk_notebook_append_page(GTK_NOTEBOOK(graphs), GTK_WIDGET(table), gtk_label_new(_("Properties")));
3194
3195   // Statistics
3196   GtkWidget *content[20];
3197   int cnt = 0;
3198
3199   vik_units_distance_t dist_units = a_vik_get_units_distance ();
3200
3201   // NB This value not shown yet - but is used by internal calculations
3202   widgets->track_length_inc_gaps = vik_track_get_length_including_gaps(tr);
3203
3204   tr_len = widgets->track_length = vik_track_get_length(tr);
3205   switch (dist_units) {
3206   case VIK_UNITS_DISTANCE_KILOMETRES:
3207     g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km", tr_len/1000.0 );
3208     break;
3209   case VIK_UNITS_DISTANCE_MILES:
3210     g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f miles", VIK_METERS_TO_MILES(tr_len) );
3211     break;
3212   case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
3213     g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f NM", VIK_METERS_TO_NAUTICAL_MILES(tr_len) );
3214     break;
3215   default:
3216     g_critical("Houston, we've had a problem. distance=%d", dist_units);
3217   }
3218   widgets->w_track_length = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3219
3220   tp_count = vik_track_get_tp_count(tr);
3221   g_snprintf(tmp_buf, sizeof(tmp_buf), "%lu", tp_count );
3222   widgets->w_tp_count = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3223
3224   seg_count = vik_track_get_segment_count(tr) ;
3225   g_snprintf(tmp_buf, sizeof(tmp_buf), "%u", seg_count );
3226   widgets->w_segment_count = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3227
3228   g_snprintf(tmp_buf, sizeof(tmp_buf), "%lu", vik_track_get_dup_point_count(tr) );
3229   widgets->w_duptp_count = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3230
3231   vik_units_speed_t speed_units = a_vik_get_units_speed ();
3232   tmp_speed = vik_track_get_max_speed(tr);
3233   if ( tmp_speed == 0 )
3234     g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
3235   else {
3236     switch (speed_units) {
3237     case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
3238       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km/h", VIK_MPS_TO_KPH(tmp_speed));
3239       break;
3240     case VIK_UNITS_SPEED_MILES_PER_HOUR:
3241       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f mph", VIK_MPS_TO_MPH(tmp_speed));
3242       break;
3243     case VIK_UNITS_SPEED_METRES_PER_SECOND:
3244       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f m/s", tmp_speed );
3245       break;
3246     case VIK_UNITS_SPEED_KNOTS:
3247       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f knots", VIK_MPS_TO_KNOTS(tmp_speed));
3248       break;
3249     default:
3250       g_snprintf (tmp_buf, sizeof(tmp_buf), "--" );
3251       g_critical("Houston, we've had a problem. speed=%d", speed_units);
3252     }
3253   }
3254   widgets->w_max_speed = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3255
3256   tmp_speed = vik_track_get_average_speed(tr);
3257   if ( tmp_speed == 0 )
3258     g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
3259   else {
3260     switch (speed_units) {
3261     case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
3262       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km/h", VIK_MPS_TO_KPH(tmp_speed));
3263       break;
3264     case VIK_UNITS_SPEED_MILES_PER_HOUR:
3265       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f mph", VIK_MPS_TO_MPH(tmp_speed));
3266       break;
3267     case VIK_UNITS_SPEED_METRES_PER_SECOND:
3268       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f m/s", tmp_speed );
3269       break;
3270     case VIK_UNITS_SPEED_KNOTS:
3271       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f knots", VIK_MPS_TO_KNOTS(tmp_speed));
3272       break;
3273     default:
3274       g_snprintf (tmp_buf, sizeof(tmp_buf), "--" );
3275       g_critical("Houston, we've had a problem. speed=%d", speed_units);
3276     }
3277   }
3278   widgets->w_avg_speed = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3279
3280   // Use 60sec as the default period to be considered stopped
3281   //  this is the TrackWaypoint draw stops default value 'vtl->stop_length'
3282   //  however this variable is not directly accessible - and I don't expect it's often changed from the default
3283   //  so ATM just put in the number
3284   tmp_speed = vik_track_get_average_speed_moving(tr, 60);
3285   if ( tmp_speed == 0 )
3286     g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
3287   else {
3288     switch (speed_units) {
3289     case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
3290       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km/h", VIK_MPS_TO_KPH(tmp_speed));
3291       break;
3292     case VIK_UNITS_SPEED_MILES_PER_HOUR:
3293       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f mph", VIK_MPS_TO_MPH(tmp_speed));
3294       break;
3295     case VIK_UNITS_SPEED_METRES_PER_SECOND:
3296       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f m/s", tmp_speed );
3297       break;
3298     case VIK_UNITS_SPEED_KNOTS:
3299       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f knots", VIK_MPS_TO_KNOTS(tmp_speed));
3300       break;
3301     default:
3302       g_snprintf (tmp_buf, sizeof(tmp_buf), "--" );
3303       g_critical("Houston, we've had a problem. speed=%d", speed_units);
3304     }
3305   }
3306   widgets->w_mvg_speed = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3307
3308   switch (dist_units) {
3309   case VIK_UNITS_DISTANCE_KILOMETRES:
3310     // Even though kilometres, the average distance between points is going to be quite small so keep in metres
3311     g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f m", (tp_count - seg_count) == 0 ? 0 : tr_len / ( tp_count - seg_count ) );
3312     break;
3313   case VIK_UNITS_DISTANCE_MILES:
3314     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 )) );
3315     break;
3316   case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
3317     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 )) );
3318     break;
3319   default:
3320     g_critical("Houston, we've had a problem. distance=%d", dist_units);
3321   }
3322   widgets->w_avg_dist = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3323
3324   vik_units_height_t height_units = a_vik_get_units_height ();
3325   if ( min_alt == VIK_DEFAULT_ALTITUDE )
3326     g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
3327   else {
3328     switch (height_units) {
3329     case VIK_UNITS_HEIGHT_METRES:
3330       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.0f m - %.0f m", min_alt, max_alt );
3331       break;
3332     case VIK_UNITS_HEIGHT_FEET:
3333       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.0f feet - %.0f feet", VIK_METERS_TO_FEET(min_alt), VIK_METERS_TO_FEET(max_alt) );
3334       break;
3335     default:
3336       g_snprintf(tmp_buf, sizeof(tmp_buf), "--" );
3337       g_critical("Houston, we've had a problem. height=%d", height_units);
3338     }
3339   }
3340   widgets->w_elev_range = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3341
3342   vik_track_get_total_elevation_gain(tr, &max_alt, &min_alt );
3343   if ( min_alt == VIK_DEFAULT_ALTITUDE )
3344     g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
3345   else {
3346     switch (height_units) {
3347     case VIK_UNITS_HEIGHT_METRES:
3348       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.0f m / %.0f m", max_alt, min_alt );
3349       break;
3350     case VIK_UNITS_HEIGHT_FEET:
3351       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.0f feet / %.0f feet", VIK_METERS_TO_FEET(max_alt), VIK_METERS_TO_FEET(min_alt) );
3352       break;
3353     default:
3354       g_snprintf(tmp_buf, sizeof(tmp_buf), "--" );
3355       g_critical("Houston, we've had a problem. height=%d", height_units);
3356     }
3357   }
3358   widgets->w_elev_gain = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3359
3360 #if 0
3361 #define PACK(w) gtk_box_pack_start (GTK_BOX(right_vbox), w, FALSE, FALSE, 0);
3362   gtk_box_pack_start (GTK_BOX(right_vbox), e_cmt, FALSE, FALSE, 0); 
3363   PACK(l_len);
3364   PACK(l_tps);
3365   PACK(l_segs);
3366   PACK(l_dups);
3367   PACK(l_maxs);
3368   PACK(l_avgs);
3369   PACK(l_avgd);
3370   PACK(l_elev);
3371   PACK(l_galo);
3372 #undef PACK;
3373 #endif
3374
3375   if ( tr->trackpoints && VIK_TRACKPOINT(tr->trackpoints->data)->timestamp )
3376   {
3377     time_t t1, t2;
3378     t1 = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
3379     t2 = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
3380
3381     VikCoord vc;
3382     // Notional center of a track is simply an average of the bounding box extremities
3383     struct LatLon center = { (tr->bbox.north+tr->bbox.south)/2, (tr->bbox.east+tr->bbox.west)/2 };
3384     vik_coord_load_from_latlon ( &vc, vik_trw_layer_get_coord_mode(vtl), &center );
3385
3386     widgets->tz = vu_get_tz_at_location ( &vc );
3387
3388     gchar *msg;
3389     msg = vu_get_time_string ( &t1, "%c", &vc, widgets->tz );
3390     widgets->w_time_start = content[cnt++] = ui_label_new_selectable(msg);
3391     g_free ( msg );
3392
3393     msg = vu_get_time_string ( &t2, "%c", &vc, widgets->tz );
3394     widgets->w_time_end = content[cnt++] = ui_label_new_selectable(msg);
3395     g_free ( msg );
3396
3397     g_snprintf(tmp_buf, sizeof(tmp_buf), _("%d minutes"), (int)(t2-t1)/60);
3398     widgets->w_time_dur = content[cnt++] = ui_label_new_selectable(tmp_buf);
3399   } else {
3400     widgets->w_time_start = content[cnt++] = gtk_label_new(_("No Data"));
3401     widgets->w_time_end = content[cnt++] = gtk_label_new(_("No Data"));
3402     widgets->w_time_dur = content[cnt++] = gtk_label_new(_("No Data"));
3403   }
3404
3405   table = create_table (cnt, stats_texts, content);
3406
3407   gtk_notebook_append_page(GTK_NOTEBOOK(graphs), GTK_WIDGET(table), gtk_label_new(_("Statistics")));
3408
3409   if ( widgets->elev_box ) {
3410     GtkWidget *page = NULL;
3411     widgets->w_cur_dist = ui_label_new_selectable(_("No Data"));
3412     widgets->w_cur_elevation = ui_label_new_selectable(_("No Data"));
3413     widgets->w_show_dem = gtk_check_button_new_with_mnemonic(_("Show D_EM"));
3414     widgets->w_show_alt_gps_speed = gtk_check_button_new_with_mnemonic(_("Show _GPS Speed"));
3415     page = create_graph_page (widgets->elev_box,
3416                               _("<b>Track Distance:</b>"), widgets->w_cur_dist,
3417                               _("<b>Track Height:</b>"), widgets->w_cur_elevation,
3418                               NULL, NULL,
3419                               widgets->w_show_dem, show_dem,
3420                               widgets->w_show_alt_gps_speed, show_alt_gps_speed);
3421     g_signal_connect (widgets->w_show_dem, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3422     g_signal_connect (widgets->w_show_alt_gps_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3423     gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Elevation-distance")));
3424   }
3425
3426   if ( widgets->gradient_box ) {
3427     GtkWidget *page = NULL;
3428     widgets->w_cur_gradient_dist = ui_label_new_selectable(_("No Data"));
3429     widgets->w_cur_gradient_gradient = ui_label_new_selectable(_("No Data"));
3430     widgets->w_show_gradient_gps_speed = gtk_check_button_new_with_mnemonic(_("Show _GPS Speed"));
3431     page = create_graph_page (widgets->gradient_box,
3432                               _("<b>Track Distance:</b>"), widgets->w_cur_gradient_dist,
3433                               _("<b>Track Gradient:</b>"), widgets->w_cur_gradient_gradient,
3434                               NULL, NULL,
3435                               widgets->w_show_gradient_gps_speed, show_gradient_gps_speed,
3436                               NULL, FALSE);
3437     g_signal_connect (widgets->w_show_gradient_gps_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3438     gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Gradient-distance")));
3439   }
3440
3441   if ( widgets->speed_box ) {
3442     GtkWidget *page = NULL;
3443     widgets->w_cur_time = ui_label_new_selectable(_("No Data"));
3444     widgets->w_cur_speed = ui_label_new_selectable(_("No Data"));
3445     widgets->w_cur_time_real = ui_label_new_selectable(_("No Data"));
3446     widgets->w_show_gps_speed = gtk_check_button_new_with_mnemonic(_("Show _GPS Speed"));
3447     page = create_graph_page (widgets->speed_box,
3448                               _("<b>Track Time:</b>"), widgets->w_cur_time,
3449                               _("<b>Track Speed:</b>"), widgets->w_cur_speed,
3450                               _("<b>Time/Date:</b>"), widgets->w_cur_time_real,
3451                               widgets->w_show_gps_speed, show_gps_speed,
3452                               NULL, FALSE);
3453     g_signal_connect (widgets->w_show_gps_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3454     gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Speed-time")));
3455   }
3456
3457   if ( widgets->dist_box ) {
3458     GtkWidget *page = NULL;
3459     widgets->w_cur_dist_time = ui_label_new_selectable(_("No Data"));
3460     widgets->w_cur_dist_dist = ui_label_new_selectable(_("No Data"));
3461     widgets->w_cur_dist_time_real = ui_label_new_selectable(_("No Data"));
3462     widgets->w_show_dist_speed = gtk_check_button_new_with_mnemonic(_("Show S_peed"));
3463     page = create_graph_page (widgets->dist_box,
3464                               _("<b>Track Distance:</b>"), widgets->w_cur_dist_dist,
3465                               _("<b>Track Time:</b>"), widgets->w_cur_dist_time,
3466                               _("<b>Time/Date:</b>"), widgets->w_cur_dist_time_real,
3467                               widgets->w_show_dist_speed, show_dist_speed,
3468                               NULL, FALSE);
3469     g_signal_connect (widgets->w_show_dist_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3470     gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Distance-time")));
3471   }
3472
3473   if ( widgets->elev_time_box ) {
3474     GtkWidget *page = NULL;
3475     widgets->w_cur_elev_time = ui_label_new_selectable(_("No Data"));
3476     widgets->w_cur_elev_elev = ui_label_new_selectable(_("No Data"));
3477     widgets->w_cur_elev_time_real = ui_label_new_selectable(_("No Data"));
3478     widgets->w_show_elev_speed = gtk_check_button_new_with_mnemonic(_("Show S_peed"));
3479     widgets->w_show_elev_dem = gtk_check_button_new_with_mnemonic(_("Show D_EM"));
3480     page = create_graph_page (widgets->elev_time_box,
3481                               _("<b>Track Time:</b>"), widgets->w_cur_elev_time,
3482                               _("<b>Track Height:</b>"), widgets->w_cur_elev_elev,
3483                               _("<b>Time/Date:</b>"), widgets->w_cur_elev_time_real,
3484                               widgets->w_show_elev_dem, show_elev_dem,
3485                               widgets->w_show_elev_speed, show_elev_speed);
3486     g_signal_connect (widgets->w_show_elev_dem, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3487     g_signal_connect (widgets->w_show_elev_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3488     gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Elevation-time")));
3489   }
3490
3491   if ( widgets->speed_dist_box ) {
3492     GtkWidget *page = NULL;
3493     widgets->w_cur_speed_dist = ui_label_new_selectable(_("No Data"));
3494     widgets->w_cur_speed_speed = ui_label_new_selectable(_("No Data"));
3495     widgets->w_show_sd_gps_speed = gtk_check_button_new_with_mnemonic(_("Show _GPS Speed"));
3496     page = create_graph_page (widgets->speed_dist_box,
3497                               _("<b>Track Distance:</b>"), widgets->w_cur_speed_dist,
3498                               _("<b>Track Speed:</b>"), widgets->w_cur_speed_speed,
3499                               NULL, NULL,
3500                               widgets->w_show_sd_gps_speed, show_sd_gps_speed,
3501                               NULL, FALSE);
3502     g_signal_connect (widgets->w_show_sd_gps_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3503     gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Speed-distance")));
3504   }
3505
3506   gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), graphs, FALSE, FALSE, 0);
3507
3508   gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER, FALSE);
3509   if (seg_count <= 1)
3510     gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), VIK_TRW_LAYER_PROPWIN_SPLIT, FALSE);
3511   if (vik_track_get_dup_point_count(tr) <= 0)
3512     gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), VIK_TRW_LAYER_PROPWIN_DEL_DUP, FALSE);
3513
3514   // On dialog realization configure_event causes the graphs to be initially drawn
3515   widgets->configure_dialog = TRUE;
3516   g_signal_connect ( G_OBJECT(dialog), "configure-event", G_CALLBACK (configure_event), widgets );
3517
3518   g_signal_connect ( G_OBJECT(dialog), "destroy", G_CALLBACK (destroy_cb), widgets );
3519
3520   vik_track_set_property_dialog(tr, dialog);
3521   gtk_dialog_set_default_response ( GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT );
3522   gtk_widget_show_all ( dialog );
3523
3524   // Gtk note: due to historical reasons, this must be done after widgets are shown
3525   if ( start_on_stats )
3526     gtk_notebook_set_current_page ( GTK_NOTEBOOK(graphs), 1 );
3527 }
3528
3529
3530 /**
3531  * Update this property dialog
3532  * e.g. if the track has been renamed
3533  */
3534 void vik_trw_layer_propwin_update ( VikTrack *trk )
3535 {
3536   // If not displayed do nothing
3537   if ( !trk->property_dialog )
3538     return;
3539
3540   // Update title with current name
3541   if ( trk->name ) {
3542     gchar *title = g_strdup_printf ( _("%s - Track Properties"), trk->name );
3543     gtk_window_set_title ( GTK_WINDOW(trk->property_dialog), title );
3544     g_free(title);
3545   }
3546
3547 }