]> git.street.me.uk Git - andy/viking.git/blob - src/viktrwlayer_propwin.c
be96f509a6786bd39a042e92ab5ca7d3dcf0d318
[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       elev -= alt_offset;
1307       if ( elev != VIK_DEM_INVALID_ELEVATION ) {
1308         // Convert into height units
1309         if (a_vik_get_units_height () == VIK_UNITS_HEIGHT_FEET)
1310           elev =  VIK_METERS_TO_FEET(elev);
1311         // No conversion needed if already in metres
1312
1313         // consider chunk size
1314         int y_alt = h2 - ((height * elev)/achunk );
1315         gdk_draw_rectangle(GDK_DRAWABLE(pix), alt_gc, TRUE, x-2, y_alt-2, 4, 4);
1316       }
1317     }
1318     if (do_speed) {
1319       // This is just a speed indicator - no actual values can be inferred by user
1320       if (!isnan(VIK_TRACKPOINT(iter->data)->speed)) {
1321         int y_speed = h2 - (height * VIK_TRACKPOINT(iter->data)->speed)/max_speed;
1322         gdk_draw_rectangle(GDK_DRAWABLE(pix), speed_gc, TRUE, x-2, y_speed-2, 4, 4);
1323       }
1324     }
1325   }
1326 }
1327
1328 /**
1329  * draw_grid_y:
1330  *
1331  * A common way to draw the grid with y axis labels
1332  *
1333  */
1334 static void draw_grid_y ( GtkWidget *window, GtkWidget *image, PropWidgets *widgets, GdkPixmap *pix, gchar *ss, gint i )
1335 {
1336   PangoLayout *pl = gtk_widget_create_pango_layout (GTK_WIDGET(image), NULL);
1337
1338   pango_layout_set_alignment (pl, PANGO_ALIGN_RIGHT);
1339   pango_layout_set_font_description (pl, gtk_widget_get_style(window)->font_desc);
1340
1341   gchar *label_markup = g_strdup_printf ( "<span size=\"small\">%s</span>", ss );
1342   pango_layout_set_markup ( pl, label_markup, -1 );
1343   g_free ( label_markup );
1344
1345   int w, h;
1346   pango_layout_get_pixel_size ( pl, &w, &h );
1347
1348   gdk_draw_layout ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->fg_gc[0],
1349                     MARGIN_X-w-3,
1350                     CLAMP((int)i*widgets->profile_height/LINES - h/2 + MARGIN_Y, 0, widgets->profile_height-h+MARGIN_Y),
1351                     pl );
1352   g_object_unref ( G_OBJECT ( pl ) );
1353
1354   gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[0],
1355                   MARGIN_X, MARGIN_Y + widgets->profile_height/LINES * i,
1356                   MARGIN_X + widgets->profile_width, MARGIN_Y + widgets->profile_height/LINES * i );
1357 }
1358
1359 /**
1360  * draw_grid_x_time:
1361  *
1362  * A common way to draw the grid with x axis labels for time graphs
1363  *
1364  */
1365 static void draw_grid_x_time ( GtkWidget *window, GtkWidget *image, PropWidgets *widgets, GdkPixmap *pix, guint ii, guint tt, guint xx )
1366 {
1367   gchar *label_markup = NULL;
1368   switch (ii) {
1369     case 0:
1370     case 1:
1371     case 2:
1372     case 3:
1373       // Minutes
1374       label_markup = g_strdup_printf ( "<span size=\"small\">%d %s</span>", tt/60, _("mins") );
1375       break;
1376     case 4:
1377     case 5:
1378     case 6:
1379     case 7:
1380       // Hours
1381       label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", (gdouble)tt/(60*60), _("h") );
1382       break;
1383     case 8:
1384     case 9:
1385     case 10:
1386       // Days
1387       label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", (gdouble)tt/(60*60*24), _("d") );
1388       break;
1389     case 11:
1390     case 12:
1391       // Weeks
1392       label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", (gdouble)tt/(60*60*24*7), _("w") );
1393       break;
1394     case 13:
1395       // 'Months'
1396       label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", (gdouble)tt/(60*60*24*28), _("M") );
1397       break;
1398     default:
1399       break;
1400   }
1401   if ( label_markup ) {
1402
1403     PangoLayout *pl = gtk_widget_create_pango_layout (GTK_WIDGET(image), NULL);
1404     pango_layout_set_font_description (pl, gtk_widget_get_style(window)->font_desc);
1405
1406     pango_layout_set_markup ( pl, label_markup, -1 );
1407     g_free ( label_markup );
1408     int ww, hh;
1409     pango_layout_get_pixel_size ( pl, &ww, &hh );
1410
1411     gdk_draw_layout ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->fg_gc[0],
1412                       MARGIN_X+xx-ww/2, MARGIN_Y/2-hh/2, pl );
1413     g_object_unref ( G_OBJECT ( pl ) );
1414   }
1415
1416   gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[0],
1417                   MARGIN_X+xx, MARGIN_Y, MARGIN_X+xx, MARGIN_Y+widgets->profile_height );
1418 }
1419
1420 /**
1421  * draw_grid_x_distance:
1422  *
1423  * A common way to draw the grid with x axis labels for distance graphs
1424  *
1425  */
1426 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 )
1427 {
1428   gchar *label_markup = NULL;
1429   switch ( dist_units ) {
1430   case VIK_UNITS_DISTANCE_MILES:
1431     if ( ii > 4 )
1432       label_markup = g_strdup_printf ( "<span size=\"small\">%d %s</span>", (guint)dd, _("miles") );
1433     else
1434       label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", dd, _("miles") );
1435     break;
1436   case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1437     if ( ii > 4 )
1438       label_markup = g_strdup_printf ( "<span size=\"small\">%d %s</span>", (guint)dd, _("NM") );
1439     else
1440       label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", dd, _("NM") );
1441     break;
1442   default:
1443     // VIK_UNITS_DISTANCE_KILOMETRES:
1444     if ( ii > 4 )
1445       label_markup = g_strdup_printf ( "<span size=\"small\">%d %s</span>", (guint)dd, _("km") );
1446     else
1447       label_markup = g_strdup_printf ( "<span size=\"small\">%.1f %s</span>", dd, _("km") );
1448     break;
1449   }
1450
1451   if ( label_markup ) {
1452     PangoLayout *pl = gtk_widget_create_pango_layout (GTK_WIDGET(image), NULL);
1453     pango_layout_set_font_description (pl, gtk_widget_get_style(window)->font_desc);
1454
1455     pango_layout_set_markup ( pl, label_markup, -1 );
1456     g_free ( label_markup );
1457     int ww, hh;
1458     pango_layout_get_pixel_size ( pl, &ww, &hh );
1459
1460     gdk_draw_layout ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->fg_gc[0],
1461                       MARGIN_X+xx-ww/2, MARGIN_Y/2-hh/2, pl );
1462     g_object_unref ( G_OBJECT ( pl ) );
1463   }
1464
1465   gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[0],
1466                   MARGIN_X+xx, MARGIN_Y, MARGIN_X+xx, MARGIN_Y+widgets->profile_height );
1467 }
1468
1469 /**
1470  * clear the images (scale texts & actual graph)
1471  */
1472 static void clear_images (GdkPixmap *pix, GtkWidget *window, PropWidgets *widgets)
1473 {
1474   gdk_draw_rectangle(GDK_DRAWABLE(pix), gtk_widget_get_style(window)->bg_gc[0],
1475                      TRUE, 0, 0, widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y);
1476   gdk_draw_rectangle(GDK_DRAWABLE(pix), gtk_widget_get_style(window)->mid_gc[0],
1477                      TRUE, 0, 0, widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y);
1478 }
1479
1480 /**
1481  *
1482  */
1483 static void draw_distance_divisions ( GtkWidget *window, GtkWidget *image, GdkPixmap *pix, PropWidgets *widgets, vik_units_distance_t dist_units )
1484 {
1485   // Set to display units from length in metres.
1486   gdouble length = widgets->track_length_inc_gaps;
1487   switch (dist_units) {
1488     case VIK_UNITS_DISTANCE_MILES:
1489       length = VIK_METERS_TO_MILES(length);
1490       break;
1491     case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1492       length = VIK_METERS_TO_NAUTICAL_MILES(length);
1493       break;
1494     default:
1495       // KM
1496       length = length/1000.0;
1497       break;
1498   }
1499   guint index = get_distance_chunk_index ( length );
1500   gdouble dist_per_pixel = length/widgets->profile_width;
1501
1502   for (guint i=1; chunksd[index]*i <= length; i++) {
1503     draw_grid_x_distance ( window, image, widgets, pix, index, chunksd[index]*i, (guint)(chunksd[index]*i/dist_per_pixel), dist_units );
1504   }
1505 }
1506
1507 /**
1508  * Draw just the height profile image
1509  */
1510 static void draw_elevations (GtkWidget *image, VikTrack *tr, PropWidgets *widgets )
1511 {
1512   guint i;
1513
1514   GdkGC *no_alt_info;
1515   GdkColor color;
1516
1517   // Free previous allocation
1518   if ( widgets->altitudes )
1519     g_free ( widgets->altitudes );
1520
1521   widgets->altitudes = vik_track_make_elevation_map ( tr, widgets->profile_width );
1522
1523   if ( widgets->altitudes == NULL )
1524     return;
1525
1526   // Convert into appropriate units
1527   vik_units_height_t height_units = a_vik_get_units_height ();
1528   if ( height_units == VIK_UNITS_HEIGHT_FEET ) {
1529     // Convert altitudes into feet units
1530     for ( i = 0; i < widgets->profile_width; i++ ) {
1531       widgets->altitudes[i] = VIK_METERS_TO_FEET(widgets->altitudes[i]);
1532     }
1533   }
1534   // Otherwise leave in metres
1535
1536   minmax_array(widgets->altitudes, &widgets->min_altitude, &widgets->max_altitude, TRUE, widgets->profile_width);
1537
1538   get_new_min_and_chunk_index (widgets->min_altitude, widgets->max_altitude, chunksa, G_N_ELEMENTS(chunksa), &widgets->draw_min_altitude, &widgets->cia);
1539
1540   // Assign locally
1541   gdouble mina = widgets->draw_min_altitude;
1542
1543   GtkWidget *window = gtk_widget_get_toplevel (widgets->elev_box);
1544   GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
1545
1546   gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
1547
1548   no_alt_info = gdk_gc_new ( gtk_widget_get_window(window) );
1549   gdk_color_parse ( "yellow", &color );
1550   gdk_gc_set_rgb_fg_color ( no_alt_info, &color);
1551
1552   // Reset before redrawing
1553   clear_images (pix, window, widgets);
1554
1555   /* draw grid */
1556   for (i=0; i<=LINES; i++) {
1557     gchar s[32];
1558
1559     switch (height_units) {
1560     case VIK_UNITS_HEIGHT_METRES:
1561       sprintf(s, "%8dm", (int)(mina + (LINES-i)*chunksa[widgets->cia]));
1562       break;
1563     case VIK_UNITS_HEIGHT_FEET:
1564       // NB values already converted into feet
1565       sprintf(s, "%8dft", (int)(mina + (LINES-i)*chunksa[widgets->cia]));
1566       break;
1567     default:
1568       sprintf(s, "--");
1569       g_critical("Houston, we've had a problem. height=%d", height_units);
1570     }
1571
1572     draw_grid_y ( window, image, widgets, pix, s, i );
1573   }
1574
1575   draw_distance_divisions ( window, image, pix, widgets, a_vik_get_units_distance() );
1576
1577   /* draw elevations */
1578   guint height = MARGIN_Y+widgets->profile_height;
1579   for ( i = 0; i < widgets->profile_width; i++ )
1580     if ( widgets->altitudes[i] == VIK_DEFAULT_ALTITUDE )
1581       gdk_draw_line ( GDK_DRAWABLE(pix), no_alt_info, 
1582                       i + MARGIN_X, MARGIN_Y, i + MARGIN_X, height );
1583     else 
1584       gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
1585                       i + MARGIN_X, height, i + MARGIN_X, height-widgets->profile_height*(widgets->altitudes[i]-mina)/(chunksa[widgets->cia]*LINES) );
1586
1587   if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_dem)) ||
1588        gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_alt_gps_speed)) ) {
1589
1590     GdkGC *dem_alt_gc = gdk_gc_new ( gtk_widget_get_window(window) );
1591     GdkGC *gps_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
1592
1593     gdk_color_parse ( "green", &color );
1594     gdk_gc_set_rgb_fg_color ( dem_alt_gc, &color);
1595
1596     gdk_color_parse ( "red", &color );
1597     gdk_gc_set_rgb_fg_color ( gps_speed_gc, &color);
1598
1599     // Ensure somekind of max speed when not set
1600     if ( widgets->max_speed < 0.01 )
1601       widgets->max_speed = vik_track_get_max_speed(tr);
1602
1603     draw_dem_alt_speed_dist(tr,
1604                             GDK_DRAWABLE(pix),
1605                             dem_alt_gc,
1606                             gps_speed_gc,
1607                             mina,
1608                             widgets->max_altitude - mina,
1609                             widgets->max_speed,
1610                             widgets->cia,
1611                             widgets->profile_width,
1612                             widgets->profile_height,
1613                             MARGIN_X,
1614                             gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_dem)),
1615                             gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_alt_gps_speed)));
1616     
1617     g_object_unref ( G_OBJECT(dem_alt_gc) );
1618     g_object_unref ( G_OBJECT(gps_speed_gc) );
1619   }
1620
1621   /* draw border */
1622   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);
1623
1624   g_object_unref ( G_OBJECT(pix) );
1625   g_object_unref ( G_OBJECT(no_alt_info) );
1626 }
1627
1628 /**
1629  * Draws representative speed on the supplied pixmap
1630  *   (which is the gradients graph)
1631  */
1632 static void draw_speed_dist(VikTrack *tr,
1633                                     GdkDrawable *pix,
1634                                     GdkGC *speed_gc,
1635                                     gdouble max_speed_in,
1636                                     gint width,
1637                                     gint height,
1638                                     gint margin,
1639                                     gboolean do_speed)
1640 {
1641   GList *iter;
1642   gdouble max_speed = 0;
1643   gdouble total_length = vik_track_get_length_including_gaps(tr);
1644
1645   // Calculate the max speed factor
1646   if (do_speed)
1647     max_speed = max_speed_in * 110 / 100;
1648
1649   gdouble dist = 0;
1650   for (iter = tr->trackpoints->next; iter; iter = iter->next) {
1651     int x;
1652     dist += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord),
1653                              &(VIK_TRACKPOINT(iter->prev->data)->coord) );
1654     x = (width * dist)/total_length + MARGIN_X;
1655     if (do_speed) {
1656       // This is just a speed indicator - no actual values can be inferred by user
1657       if (!isnan(VIK_TRACKPOINT(iter->data)->speed)) {
1658         int y_speed = height - (height * VIK_TRACKPOINT(iter->data)->speed)/max_speed;
1659         gdk_draw_rectangle(GDK_DRAWABLE(pix), speed_gc, TRUE, x-2, y_speed-2, 4, 4);
1660       }
1661     }
1662   }
1663 }
1664
1665 /**
1666  * Draw just the gradient image
1667  */
1668 static void draw_gradients (GtkWidget *image, VikTrack *tr, PropWidgets *widgets )
1669 {
1670   guint i;
1671
1672   // Free previous allocation
1673   if ( widgets->gradients )
1674     g_free ( widgets->gradients );
1675
1676   widgets->gradients = vik_track_make_gradient_map ( tr, widgets->profile_width );
1677
1678   if ( widgets->gradients == NULL )
1679     return;
1680
1681   minmax_array(widgets->gradients, &widgets->min_gradient, &widgets->max_gradient, TRUE, widgets->profile_width);
1682
1683   get_new_min_and_chunk_index (widgets->min_gradient, widgets->max_gradient, chunksg, G_N_ELEMENTS(chunksg), &widgets->draw_min_gradient, &widgets->cig);
1684
1685   // Assign locally
1686   gdouble mina = widgets->draw_min_gradient;
1687
1688   GtkWidget *window = gtk_widget_get_toplevel (widgets->gradient_box);
1689   GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
1690
1691   gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
1692
1693   // Reset before redrawing
1694   clear_images (pix, window, widgets);
1695
1696   /* draw grid */
1697   for (i=0; i<=LINES; i++) {
1698     gchar s[32];
1699
1700     sprintf(s, "%8d%%", (int)(mina + (LINES-i)*chunksg[widgets->cig]));
1701
1702     draw_grid_y ( window, image, widgets, pix, s, i );
1703   }
1704
1705   draw_distance_divisions ( window, image, pix, widgets, a_vik_get_units_distance() );
1706
1707   /* draw gradients */
1708   guint height = widgets->profile_height + MARGIN_Y;
1709   for ( i = 0; i < widgets->profile_width; i++ )
1710     gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
1711                     i + MARGIN_X, height, i + MARGIN_X, height - widgets->profile_height*(widgets->gradients[i]-mina)/(chunksg[widgets->cig]*LINES) );
1712
1713   if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_gradient_gps_speed)) ) {
1714     GdkGC *gps_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
1715
1716     GdkColor color;
1717     gdk_color_parse ( "red", &color );
1718     gdk_gc_set_rgb_fg_color ( gps_speed_gc, &color);
1719
1720     // Ensure somekind of max speed when not set
1721     if ( widgets->max_speed < 0.01 )
1722       widgets->max_speed = vik_track_get_max_speed(tr);
1723
1724     draw_speed_dist(tr,
1725                             GDK_DRAWABLE(pix),
1726                             gps_speed_gc,
1727                             widgets->max_speed,
1728                             widgets->profile_width,
1729                             widgets->profile_height,
1730                             MARGIN_X,
1731                             gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widgets->w_show_alt_gps_speed)));
1732     
1733     g_object_unref ( G_OBJECT(gps_speed_gc) );
1734   }
1735
1736   /* draw border */
1737   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);
1738
1739   g_object_unref ( G_OBJECT(pix) );
1740 }
1741
1742 static void draw_time_lines ( GtkWidget *window, GtkWidget *image, GdkPixmap *pix, PropWidgets *widgets )
1743 {
1744   guint index = get_time_chunk_index ( widgets->duration );
1745   gdouble time_per_pixel = (gdouble)(widgets->duration)/widgets->profile_width;
1746
1747   // If stupidly long track in time - don't bother trying to draw grid lines
1748   if ( widgets->duration > chunkst[G_N_ELEMENTS(chunkst)-1]*LINES*LINES )
1749     return;
1750
1751   for (guint i=1; chunkst[index]*i <= widgets->duration; i++) {
1752     draw_grid_x_time ( window, image, widgets, pix, index, chunkst[index]*i, (guint)(chunkst[index]*i/time_per_pixel) );
1753   }
1754 }
1755
1756 /**
1757  * Draw just the speed (velocity)/time image
1758  */
1759 static void draw_vt ( GtkWidget *image, VikTrack *tr, PropWidgets *widgets)
1760 {
1761   guint i;
1762
1763   // Free previous allocation
1764   if ( widgets->speeds )
1765     g_free ( widgets->speeds );
1766
1767   widgets->speeds = vik_track_make_speed_map ( tr, widgets->profile_width );
1768   if ( widgets->speeds == NULL )
1769     return;
1770
1771   widgets->duration = vik_track_get_duration ( tr );
1772   // Negative time or other problem
1773   if ( widgets->duration <= 0 )
1774     return;
1775
1776   // Convert into appropriate units
1777   vik_units_speed_t speed_units = a_vik_get_units_speed ();
1778   switch (speed_units) {
1779   case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
1780     for ( i = 0; i < widgets->profile_width; i++ ) {
1781       widgets->speeds[i] = VIK_MPS_TO_KPH(widgets->speeds[i]);
1782     }
1783     break;
1784   case VIK_UNITS_SPEED_MILES_PER_HOUR:
1785     for ( i = 0; i < widgets->profile_width; i++ ) {
1786       widgets->speeds[i] = VIK_MPS_TO_MPH(widgets->speeds[i]);
1787     }
1788     break;
1789   case VIK_UNITS_SPEED_KNOTS:
1790     for ( i = 0; i < widgets->profile_width; i++ ) {
1791       widgets->speeds[i] = VIK_MPS_TO_KNOTS(widgets->speeds[i]);
1792     }
1793     break;
1794   default:
1795     // VIK_UNITS_SPEED_METRES_PER_SECOND:
1796     // No need to convert as already in m/s
1797     break;
1798   }
1799
1800   GtkWidget *window = gtk_widget_get_toplevel (widgets->speed_box);
1801   GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
1802
1803   gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
1804
1805   minmax_array(widgets->speeds, &widgets->min_speed, &widgets->max_speed, FALSE, widgets->profile_width);
1806   if (widgets->min_speed < 0.0)
1807     widgets->min_speed = 0; /* splines sometimes give negative speeds */
1808
1809   /* Find suitable chunk index */
1810   get_new_min_and_chunk_index (widgets->min_speed, widgets->max_speed, chunkss, G_N_ELEMENTS(chunkss), &widgets->draw_min_speed, &widgets->cis);
1811
1812   // Assign locally
1813   gdouble mins = widgets->draw_min_speed;
1814
1815   // Reset before redrawing
1816   clear_images (pix, window, widgets);
1817
1818   /* draw grid */
1819   for (i=0; i<=LINES; i++) {
1820     gchar s[32];
1821
1822     // NB: No need to convert here anymore as numbers are in the appropriate units
1823     switch (speed_units) {
1824     case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
1825       sprintf(s, "%8dkm/h", (int)(mins + (LINES-i)*chunkss[widgets->cis]));
1826       break;
1827     case VIK_UNITS_SPEED_MILES_PER_HOUR:
1828       sprintf(s, "%8dmph", (int)(mins + (LINES-i)*chunkss[widgets->cis]));
1829       break;
1830     case VIK_UNITS_SPEED_METRES_PER_SECOND:
1831       sprintf(s, "%8dm/s", (int)(mins + (LINES-i)*chunkss[widgets->cis]));
1832       break;
1833     case VIK_UNITS_SPEED_KNOTS:
1834       sprintf(s, "%8dknots", (int)(mins + (LINES-i)*chunkss[widgets->cis]));
1835       break;
1836     default:
1837       sprintf(s, "--");
1838       g_critical("Houston, we've had a problem. speed=%d", speed_units);
1839     }
1840
1841     draw_grid_y ( window, image, widgets, pix, s, i );
1842   }
1843
1844   draw_time_lines ( window, image, pix, widgets );
1845
1846   /* draw speeds */
1847   guint height = widgets->profile_height + MARGIN_Y;
1848   for ( i = 0; i < widgets->profile_width; i++ )
1849     gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
1850                     i + MARGIN_X, height, i + MARGIN_X, height - widgets->profile_height*(widgets->speeds[i]-mins)/(chunkss[widgets->cis]*LINES) );
1851
1852   if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->w_show_gps_speed)) ) {
1853
1854     GdkGC *gps_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
1855     GdkColor color;
1856     gdk_color_parse ( "red", &color );
1857     gdk_gc_set_rgb_fg_color ( gps_speed_gc, &color);
1858
1859     time_t beg_time = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
1860     time_t dur =  VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp - beg_time;
1861
1862     GList *iter;
1863     for (iter = tr->trackpoints; iter; iter = iter->next) {
1864       gdouble gps_speed = VIK_TRACKPOINT(iter->data)->speed;
1865       if (isnan(gps_speed))
1866         continue;
1867       switch (speed_units) {
1868       case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
1869         gps_speed = VIK_MPS_TO_KPH(gps_speed);
1870         break;
1871       case VIK_UNITS_SPEED_MILES_PER_HOUR:
1872         gps_speed = VIK_MPS_TO_MPH(gps_speed);
1873         break;
1874       case VIK_UNITS_SPEED_KNOTS:
1875         gps_speed = VIK_MPS_TO_KNOTS(gps_speed);
1876         break;
1877       default:
1878         // VIK_UNITS_SPEED_METRES_PER_SECOND:
1879         // No need to convert as already in m/s
1880         break;
1881       }
1882       int x = MARGIN_X + widgets->profile_width * (VIK_TRACKPOINT(iter->data)->timestamp - beg_time) / dur;
1883       int y = height - widgets->profile_height*(gps_speed - mins)/(chunkss[widgets->cis]*LINES);
1884       gdk_draw_rectangle(GDK_DRAWABLE(pix), gps_speed_gc, TRUE, x-2, y-2, 4, 4);
1885     }
1886     g_object_unref ( G_OBJECT(gps_speed_gc) );
1887   }
1888
1889   /* draw border */
1890   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);
1891
1892   g_object_unref ( G_OBJECT(pix) );
1893 }
1894
1895 /**
1896  * Draw just the distance/time image
1897  */
1898 static void draw_dt ( GtkWidget *image, VikTrack *tr, PropWidgets *widgets )
1899 {
1900   guint i;
1901
1902   // Free previous allocation
1903   if ( widgets->distances )
1904     g_free ( widgets->distances );
1905
1906   widgets->distances = vik_track_make_distance_map ( tr, widgets->profile_width );
1907   if ( widgets->distances == NULL )
1908     return;
1909
1910   // Convert into appropriate units
1911   vik_units_distance_t dist_units = a_vik_get_units_distance ();
1912   switch ( dist_units ) {
1913     case VIK_UNITS_DISTANCE_MILES:
1914       for ( i = 0; i < widgets->profile_width; i++ ) {
1915         widgets->distances[i] = VIK_METERS_TO_MILES(widgets->distances[i]);
1916       }
1917       break;
1918     case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1919       for ( i = 0; i < widgets->profile_width; i++ ) {
1920         widgets->distances[i] = VIK_METERS_TO_NAUTICAL_MILES(widgets->distances[i]);
1921       }
1922       break;
1923     default:
1924       // Metres - but want in kms
1925       for ( i = 0; i < widgets->profile_width; i++ ) {
1926         widgets->distances[i] = widgets->distances[i]/1000.0;
1927       }
1928       break;
1929   }
1930
1931   widgets->duration = vik_track_get_duration ( widgets->tr );
1932   // Negative time or other problem
1933   if ( widgets->duration <= 0 )
1934     return;
1935
1936   GtkWidget *window = gtk_widget_get_toplevel (widgets->dist_box);
1937   GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
1938
1939   gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
1940
1941   // easy to work out min / max of distance!
1942   // Assign locally
1943   // mind = 0.0; - Thus not used
1944   gdouble maxd;
1945   switch ( dist_units ) {
1946   case VIK_UNITS_DISTANCE_MILES:
1947     maxd = VIK_METERS_TO_MILES(vik_track_get_length_including_gaps (tr));
1948     break;
1949   case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1950     maxd = VIK_METERS_TO_NAUTICAL_MILES(vik_track_get_length_including_gaps (tr));
1951     break;
1952   default:
1953     maxd = vik_track_get_length_including_gaps (tr) / 1000.0;
1954     break;
1955   }
1956
1957   /* Find suitable chunk index */
1958   gdouble dummy = 0.0; // expect this to remain the same! (not that it's used)
1959   get_new_min_and_chunk_index (0, maxd, chunksd, G_N_ELEMENTS(chunksd), &dummy, &widgets->cid);
1960
1961   // Reset before redrawing
1962   clear_images (pix, window, widgets);
1963
1964   /* draw grid */
1965   for (i=0; i<=LINES; i++) {
1966     gchar s[32];
1967
1968     switch ( dist_units ) {
1969     case VIK_UNITS_DISTANCE_MILES:
1970       sprintf(s, _("%.1f miles"), ((LINES-i)*chunksd[widgets->cid]));
1971       break;
1972     case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
1973       sprintf(s, _("%.1f NM"), ((LINES-i)*chunksd[widgets->cid]));
1974       break;
1975     default:
1976       sprintf(s, _("%.1f km"), ((LINES-i)*chunksd[widgets->cid]));
1977       break;
1978     }
1979
1980     draw_grid_y ( window, image, widgets, pix, s, i );
1981   }
1982   
1983   draw_time_lines ( window, image, pix, widgets );
1984
1985   /* draw distance */
1986   guint height = widgets->profile_height + MARGIN_Y;
1987   for ( i = 0; i < widgets->profile_width; i++ )
1988     gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
1989                     i + MARGIN_X, height, i + MARGIN_X, height - widgets->profile_height*(widgets->distances[i])/(chunksd[widgets->cid]*LINES) );
1990
1991   // Show speed indicator
1992   if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->w_show_dist_speed)) ) {
1993     GdkGC *dist_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
1994     GdkColor color;
1995     gdk_color_parse ( "red", &color );
1996     gdk_gc_set_rgb_fg_color ( dist_speed_gc, &color);
1997
1998     gdouble max_speed = 0;
1999     max_speed = widgets->max_speed * 110 / 100;
2000
2001     // This is just an indicator - no actual values can be inferred by user
2002     for ( i = 0; i < widgets->profile_width; i++ ) {
2003       int y_speed = widgets->profile_height - (widgets->profile_height * widgets->speeds[i])/max_speed;
2004       gdk_draw_rectangle(GDK_DRAWABLE(pix), dist_speed_gc, TRUE, i+MARGIN_X-2, y_speed-2, 4, 4);
2005     }
2006     g_object_unref ( G_OBJECT(dist_speed_gc) );
2007   }
2008
2009   /* draw border */
2010   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);
2011
2012   g_object_unref ( G_OBJECT(pix) );
2013
2014 }
2015
2016 /**
2017  * Draw just the elevation/time image
2018  */
2019 static void draw_et ( GtkWidget *image, VikTrack *tr, PropWidgets *widgets )
2020 {
2021   guint i;
2022
2023   // Free previous allocation
2024   if ( widgets->ats )
2025     g_free ( widgets->ats );
2026
2027   widgets->ats = vik_track_make_elevation_time_map ( tr, widgets->profile_width );
2028
2029   if ( widgets->ats == NULL )
2030     return;
2031
2032   // Convert into appropriate units
2033   vik_units_height_t height_units = a_vik_get_units_height ();
2034   if ( height_units == VIK_UNITS_HEIGHT_FEET ) {
2035     // Convert altitudes into feet units
2036     for ( i = 0; i < widgets->profile_width; i++ ) {
2037       widgets->ats[i] = VIK_METERS_TO_FEET(widgets->ats[i]);
2038     }
2039   }
2040   // Otherwise leave in metres
2041
2042   minmax_array(widgets->ats, &widgets->min_altitude, &widgets->max_altitude, TRUE, widgets->profile_width);
2043
2044   get_new_min_and_chunk_index (widgets->min_altitude, widgets->max_altitude, chunksa, G_N_ELEMENTS(chunksa), &widgets->draw_min_altitude_time, &widgets->ciat);
2045
2046   // Assign locally
2047   gdouble mina = widgets->draw_min_altitude_time;
2048
2049   widgets->duration = vik_track_get_duration ( widgets->tr );
2050   // Negative time or other problem
2051   if ( widgets->duration <= 0 )
2052     return;
2053
2054   GtkWidget *window = gtk_widget_get_toplevel (widgets->elev_time_box);
2055   GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2056
2057   gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
2058
2059   // Reset before redrawing
2060   clear_images (pix, window, widgets);
2061
2062   /* draw grid */
2063   for (i=0; i<=LINES; i++) {
2064     gchar s[32];
2065
2066     switch (height_units) {
2067     case VIK_UNITS_HEIGHT_METRES:
2068       sprintf(s, "%8dm", (int)(mina + (LINES-i)*chunksa[widgets->ciat]));
2069       break;
2070     case VIK_UNITS_HEIGHT_FEET:
2071       // NB values already converted into feet
2072       sprintf(s, "%8dft", (int)(mina + (LINES-i)*chunksa[widgets->ciat]));
2073       break;
2074     default:
2075       sprintf(s, "--");
2076       g_critical("Houston, we've had a problem. height=%d", height_units);
2077     }
2078
2079     draw_grid_y ( window, image, widgets, pix, s, i );
2080   }
2081
2082   draw_time_lines ( window, image, pix, widgets );
2083
2084   /* draw elevations */
2085   guint height = widgets->profile_height + MARGIN_Y;
2086   for ( i = 0; i < widgets->profile_width; i++ )
2087     gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
2088                     i + MARGIN_X, height, i + MARGIN_X, height-widgets->profile_height*(widgets->ats[i]-mina)/(chunksa[widgets->ciat]*LINES) );
2089
2090   // Show DEMS
2091   if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->w_show_elev_dem)) )  {
2092     GdkColor color;
2093     GdkGC *dem_alt_gc = gdk_gc_new ( gtk_widget_get_window(window) );
2094     gdk_color_parse ( "green", &color );
2095     gdk_gc_set_rgb_fg_color ( dem_alt_gc, &color);
2096
2097     gint h2 = widgets->profile_height + MARGIN_Y; // Adjust height for x axis labelling offset
2098     gint achunk = chunksa[widgets->ciat]*LINES;
2099
2100     for ( i = 0; i < widgets->profile_width; i++ ) {
2101       // This could be slow doing this each time...
2102       VikTrackpoint *tp = vik_track_get_closest_tp_by_percentage_time ( widgets->tr, ((gdouble)i/(gdouble)widgets->profile_width), NULL );
2103       if ( tp ) {
2104         gint16 elev = a_dems_get_elev_by_coord(&(tp->coord), VIK_DEM_INTERPOL_SIMPLE);
2105         elev -= mina; //?
2106         if ( elev != VIK_DEM_INVALID_ELEVATION ) {
2107           // Convert into height units
2108           if ( a_vik_get_units_height () == VIK_UNITS_HEIGHT_FEET )
2109             elev = VIK_METERS_TO_FEET(elev);
2110           // No conversion needed if already in metres
2111
2112           // consider chunk size
2113           int y_alt = h2 - ((widgets->profile_height * elev)/achunk );
2114           gdk_draw_rectangle(GDK_DRAWABLE(pix), dem_alt_gc, TRUE, i+MARGIN_X-2, y_alt-2, 4, 4);
2115         }
2116       }
2117     }
2118     g_object_unref ( G_OBJECT(dem_alt_gc) );
2119   }
2120
2121   // Show speeds
2122   if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->w_show_elev_speed)) ) {
2123     GdkColor color;
2124     // This is just an indicator - no actual values can be inferred by user
2125     GdkGC *elev_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
2126     gdk_color_parse ( "red", &color );
2127     gdk_gc_set_rgb_fg_color ( elev_speed_gc, &color);
2128
2129     gdouble max_speed = widgets->max_speed * 110 / 100;
2130
2131     for ( i = 0; i < widgets->profile_width; i++ ) {
2132       int y_speed = widgets->profile_height - (widgets->profile_height * widgets->speeds[i])/max_speed;
2133       gdk_draw_rectangle(GDK_DRAWABLE(pix), elev_speed_gc, TRUE, i+MARGIN_X-2, y_speed-2, 4, 4);
2134     }
2135
2136     g_object_unref ( G_OBJECT(elev_speed_gc) );
2137   }
2138
2139   /* draw border */
2140   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);
2141
2142   g_object_unref ( G_OBJECT(pix) );
2143 }
2144
2145 /**
2146  * Draw just the speed/distance image
2147  */
2148 static void draw_sd ( GtkWidget *image, VikTrack *tr, PropWidgets *widgets)
2149 {
2150   gdouble mins;
2151   guint i;
2152
2153   // Free previous allocation
2154   if ( widgets->speeds_dist )
2155     g_free ( widgets->speeds_dist );
2156
2157   widgets->speeds_dist = vik_track_make_speed_dist_map ( tr, widgets->profile_width );
2158   if ( widgets->speeds_dist == NULL )
2159     return;
2160
2161   // Convert into appropriate units
2162   vik_units_speed_t speed_units = a_vik_get_units_speed ();
2163   switch (speed_units) {
2164   case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
2165     for ( i = 0; i < widgets->profile_width; i++ ) {
2166       widgets->speeds_dist[i] = VIK_MPS_TO_KPH(widgets->speeds_dist[i]);
2167     }
2168     break;
2169   case VIK_UNITS_SPEED_MILES_PER_HOUR:
2170     for ( i = 0; i < widgets->profile_width; i++ ) {
2171       widgets->speeds_dist[i] = VIK_MPS_TO_MPH(widgets->speeds_dist[i]);
2172     }
2173     break;
2174   case VIK_UNITS_SPEED_KNOTS:
2175     for ( i = 0; i < widgets->profile_width; i++ ) {
2176       widgets->speeds_dist[i] = VIK_MPS_TO_KNOTS(widgets->speeds_dist[i]);
2177     }
2178     break;
2179   default:
2180     // VIK_UNITS_SPEED_METRES_PER_SECOND:
2181     // No need to convert as already in m/s
2182     break;
2183   }
2184
2185   GtkWidget *window = gtk_widget_get_toplevel (widgets->speed_dist_box);
2186   GdkPixmap *pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2187
2188   gtk_image_set_from_pixmap ( GTK_IMAGE(image), pix, NULL );
2189
2190   // OK to resuse min_speed here
2191   minmax_array(widgets->speeds_dist, &widgets->min_speed, &widgets->max_speed_dist, FALSE, widgets->profile_width);
2192   if (widgets->min_speed < 0.0)
2193     widgets->min_speed = 0; /* splines sometimes give negative speeds */
2194
2195   /* Find suitable chunk index */
2196   get_new_min_and_chunk_index (widgets->min_speed, widgets->max_speed_dist, chunkss, G_N_ELEMENTS(chunkss), &widgets->draw_min_speed, &widgets->cisd);
2197
2198   // Assign locally
2199   mins = widgets->draw_min_speed;
2200   
2201   // Reset before redrawing
2202   clear_images (pix, window, widgets);
2203
2204   /* draw grid */
2205   for (i=0; i<=LINES; i++) {
2206     gchar s[32];
2207
2208     // NB: No need to convert here anymore as numbers are in the appropriate units
2209     switch (speed_units) {
2210     case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
2211       sprintf(s, "%8dkm/h", (int)(mins + (LINES-i)*chunkss[widgets->cisd]));
2212       break;
2213     case VIK_UNITS_SPEED_MILES_PER_HOUR:
2214       sprintf(s, "%8dmph", (int)(mins + (LINES-i)*chunkss[widgets->cisd]));
2215       break;
2216     case VIK_UNITS_SPEED_METRES_PER_SECOND:
2217       sprintf(s, "%8dm/s", (int)(mins + (LINES-i)*chunkss[widgets->cisd]));
2218       break;
2219     case VIK_UNITS_SPEED_KNOTS:
2220       sprintf(s, "%8dknots", (int)(mins + (LINES-i)*chunkss[widgets->cisd]));
2221       break;
2222     default:
2223       sprintf(s, "--");
2224       g_critical("Houston, we've had a problem. speed=%d", speed_units);
2225     }
2226
2227     draw_grid_y ( window, image, widgets, pix, s, i );
2228   }
2229
2230   draw_distance_divisions ( window, image, pix, widgets, a_vik_get_units_distance() );
2231
2232   /* draw speeds */
2233   guint height = widgets->profile_height + MARGIN_Y;
2234   for ( i = 0; i < widgets->profile_width; i++ )
2235     gdk_draw_line ( GDK_DRAWABLE(pix), gtk_widget_get_style(window)->dark_gc[3],
2236                     i + MARGIN_X, height, i + MARGIN_X, height - widgets->profile_height*(widgets->speeds_dist[i]-mins)/(chunkss[widgets->cisd]*LINES) );
2237
2238
2239   if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widgets->w_show_sd_gps_speed)) ) {
2240
2241     GdkGC *gps_speed_gc = gdk_gc_new ( gtk_widget_get_window(window) );
2242     GdkColor color;
2243     gdk_color_parse ( "red", &color );
2244     gdk_gc_set_rgb_fg_color ( gps_speed_gc, &color);
2245
2246     gdouble dist = vik_track_get_length_including_gaps(tr);
2247     gdouble dist_tp = 0.0;
2248
2249     GList *iter = tr->trackpoints;
2250     for (iter = iter->next; iter; iter = iter->next) {
2251       gdouble gps_speed = VIK_TRACKPOINT(iter->data)->speed;
2252       if (isnan(gps_speed))
2253         continue;
2254       switch (speed_units) {
2255       case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
2256         gps_speed = VIK_MPS_TO_KPH(gps_speed);
2257         break;
2258       case VIK_UNITS_SPEED_MILES_PER_HOUR:
2259         gps_speed = VIK_MPS_TO_MPH(gps_speed);
2260         break;
2261       case VIK_UNITS_SPEED_KNOTS:
2262         gps_speed = VIK_MPS_TO_KNOTS(gps_speed);
2263         break;
2264       default:
2265         // VIK_UNITS_SPEED_METRES_PER_SECOND:
2266         // No need to convert as already in m/s
2267         break;
2268       }
2269       dist_tp += vik_coord_diff ( &(VIK_TRACKPOINT(iter->data)->coord), &(VIK_TRACKPOINT(iter->prev->data)->coord) );
2270       int x = MARGIN_X + (widgets->profile_width * dist_tp / dist);
2271       int y = height - widgets->profile_height*(gps_speed - mins)/(chunkss[widgets->cisd]*LINES);
2272       gdk_draw_rectangle(GDK_DRAWABLE(pix), gps_speed_gc, TRUE, x-2, y-2, 4, 4);
2273     }
2274     g_object_unref ( G_OBJECT(gps_speed_gc) );
2275   }
2276
2277   /* draw border */
2278   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);
2279
2280   g_object_unref ( G_OBJECT(pix) );
2281 }
2282 #undef LINES
2283
2284 /**
2285  * Draw all graphs
2286  */
2287 static void draw_all_graphs ( GtkWidget *widget, PropWidgets *widgets, gboolean resized )
2288 {
2289   // Draw graphs even if they are not visible
2290
2291   GList *child = NULL;
2292   GtkWidget *image = NULL;
2293   GtkWidget *window = gtk_widget_get_toplevel(widget);
2294   gdouble pc = NAN;
2295   gdouble pc_blob = NAN;
2296
2297   // Draw elevations
2298   if (widgets->elev_box != NULL) {
2299
2300     // Saved image no longer any good as we've resized, so we remove it here
2301     if (resized && widgets->elev_graph_saved_img.img) {
2302       g_object_unref(widgets->elev_graph_saved_img.img);
2303       widgets->elev_graph_saved_img.img = NULL;
2304       widgets->elev_graph_saved_img.saved = FALSE;
2305     }
2306
2307     child = gtk_container_get_children(GTK_CONTAINER(widgets->elev_box));
2308     draw_elevations (GTK_WIDGET(child->data), widgets->tr, widgets );
2309
2310     image = GTK_WIDGET(child->data);
2311     g_list_free(child);
2312
2313     // Ensure marker or blob are redrawn if necessary
2314     if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2315
2316       pc = tp_percentage_by_distance ( widgets->tr, widgets->marker_tp, widgets->track_length_inc_gaps );
2317       gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2318       gint y_blob = 0;
2319       if (widgets->is_blob_drawn) {
2320         pc_blob = tp_percentage_by_distance ( widgets->tr, widgets->blob_tp, widgets->track_length_inc_gaps );
2321         if (!isnan(pc_blob)) {
2322           x_blob = (pc_blob * widgets->profile_width);
2323         }
2324         y_blob = blobby_altitude (x_blob, widgets);
2325       }
2326
2327       gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2328       if (!isnan(pc)) {
2329         marker_x = (pc * widgets->profile_width) + MARGIN_X;
2330       }
2331
2332       save_image_and_draw_graph_marks (image,
2333                                        marker_x,
2334                                        gtk_widget_get_style(window)->black_gc,
2335                                        x_blob+MARGIN_X,
2336                                        y_blob+MARGIN_Y,
2337                                        &widgets->elev_graph_saved_img,
2338                                        widgets->profile_width,
2339                                        widgets->profile_height,
2340                                        &widgets->is_marker_drawn,
2341                                        &widgets->is_blob_drawn);
2342     }
2343   }
2344
2345   // Draw gradients
2346   if (widgets->gradient_box != NULL) {
2347
2348     // Saved image no longer any good as we've resized, so we remove it here
2349     if (resized && widgets->gradient_graph_saved_img.img) {
2350       g_object_unref(widgets->gradient_graph_saved_img.img);
2351       widgets->gradient_graph_saved_img.img = NULL;
2352       widgets->gradient_graph_saved_img.saved = FALSE;
2353     }
2354
2355     child = gtk_container_get_children(GTK_CONTAINER(widgets->gradient_box));
2356     draw_gradients (GTK_WIDGET(child->data), widgets->tr, widgets );
2357
2358     image = GTK_WIDGET(child->data);
2359     g_list_free(child);
2360
2361     // Ensure marker or blob are redrawn if necessary
2362     if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2363
2364       pc = tp_percentage_by_distance ( widgets->tr, widgets->marker_tp, widgets->track_length_inc_gaps );
2365       gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2366       gint y_blob = 0;
2367       if (widgets->is_blob_drawn) {
2368         pc_blob = tp_percentage_by_distance ( widgets->tr, widgets->blob_tp, widgets->track_length_inc_gaps );
2369         if (!isnan(pc_blob)) {
2370           x_blob = (pc_blob * widgets->profile_width);
2371         }
2372         y_blob = blobby_gradient (x_blob, widgets);
2373       }
2374
2375       gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2376       if (!isnan(pc)) {
2377         marker_x = (pc * widgets->profile_width) + MARGIN_X;
2378       }
2379
2380       save_image_and_draw_graph_marks (image,
2381                                        marker_x,
2382                                        gtk_widget_get_style(window)->black_gc,
2383                                        x_blob+MARGIN_X,
2384                                        y_blob+MARGIN_Y,
2385                                        &widgets->gradient_graph_saved_img,
2386                                        widgets->profile_width,
2387                                        widgets->profile_height,
2388                                        &widgets->is_marker_drawn,
2389                                        &widgets->is_blob_drawn);
2390     }
2391   }
2392
2393   // Draw speeds
2394   if (widgets->speed_box != NULL) {
2395
2396     // Saved image no longer any good as we've resized
2397     if (resized && widgets->speed_graph_saved_img.img) {
2398       g_object_unref(widgets->speed_graph_saved_img.img);
2399       widgets->speed_graph_saved_img.img = NULL;
2400       widgets->speed_graph_saved_img.saved = FALSE;
2401     }
2402
2403     child = gtk_container_get_children(GTK_CONTAINER(widgets->speed_box));
2404     draw_vt (GTK_WIDGET(child->data), widgets->tr, widgets );
2405
2406     image = GTK_WIDGET(child->data);
2407     g_list_free(child);
2408
2409     // Ensure marker or blob are redrawn if necessary
2410     if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2411
2412       pc = tp_percentage_by_time ( widgets->tr, widgets->marker_tp );
2413
2414       gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2415       gint    y_blob = 0;
2416       if (widgets->is_blob_drawn) {
2417         pc_blob = tp_percentage_by_time ( widgets->tr, widgets->blob_tp );
2418         if (!isnan(pc_blob)) {
2419           x_blob = (pc_blob * widgets->profile_width);
2420         }
2421          
2422         y_blob = blobby_speed (x_blob, widgets);
2423       }
2424
2425       gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2426       if (!isnan(pc)) {
2427         marker_x = (pc * widgets->profile_width) + MARGIN_X;
2428       }
2429
2430       save_image_and_draw_graph_marks (image,
2431                                        marker_x,
2432                                        gtk_widget_get_style(window)->black_gc,
2433                                        x_blob+MARGIN_X,
2434                                        y_blob+MARGIN_Y,
2435                                        &widgets->speed_graph_saved_img,
2436                                        widgets->profile_width,
2437                                        widgets->profile_height,
2438                                        &widgets->is_marker_drawn,
2439                                        &widgets->is_blob_drawn);
2440     }
2441   }
2442
2443   // Draw Distances
2444   if (widgets->dist_box != NULL) {
2445
2446     // Saved image no longer any good as we've resized
2447     if (resized && widgets->dist_graph_saved_img.img) {
2448       g_object_unref(widgets->dist_graph_saved_img.img);
2449       widgets->dist_graph_saved_img.img = NULL;
2450       widgets->dist_graph_saved_img.saved = FALSE;
2451     }
2452
2453     child = gtk_container_get_children(GTK_CONTAINER(widgets->dist_box));
2454     draw_dt (GTK_WIDGET(child->data), widgets->tr, widgets );
2455
2456     image = GTK_WIDGET(child->data);
2457     g_list_free(child);
2458
2459     // Ensure marker or blob are redrawn if necessary
2460     if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2461
2462       pc = tp_percentage_by_time ( widgets->tr, widgets->marker_tp );
2463
2464       gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2465       gint    y_blob = 0;
2466       if (widgets->is_blob_drawn) {
2467         pc_blob = tp_percentage_by_time ( widgets->tr, widgets->blob_tp );
2468         if (!isnan(pc_blob)) {
2469           x_blob = (pc_blob * widgets->profile_width);
2470         }
2471          
2472         y_blob = blobby_distance (x_blob, widgets);
2473       }
2474
2475       gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2476       if (!isnan(pc)) {
2477         marker_x = (pc * widgets->profile_width) + MARGIN_X;
2478       }
2479
2480       save_image_and_draw_graph_marks (image,
2481                                        marker_x,
2482                                        gtk_widget_get_style(window)->black_gc,
2483                                        x_blob+MARGIN_X,
2484                                        y_blob+MARGIN_Y,
2485                                        &widgets->dist_graph_saved_img,
2486                                        widgets->profile_width,
2487                                        widgets->profile_height,
2488                                        &widgets->is_marker_drawn,
2489                                        &widgets->is_blob_drawn);
2490     }
2491   }
2492
2493   // Draw Elevations in timely manner
2494   if (widgets->elev_time_box != NULL) {
2495
2496     // Saved image no longer any good as we've resized
2497     if (resized && widgets->elev_time_graph_saved_img.img) {
2498       g_object_unref(widgets->elev_time_graph_saved_img.img);
2499       widgets->elev_time_graph_saved_img.img = NULL;
2500       widgets->elev_time_graph_saved_img.saved = FALSE;
2501     }
2502
2503     child = gtk_container_get_children(GTK_CONTAINER(widgets->elev_time_box));
2504     draw_et (GTK_WIDGET(child->data), widgets->tr, widgets );
2505
2506     image = GTK_WIDGET(child->data);
2507     g_list_free(child);
2508
2509     // Ensure marker or blob are redrawn if necessary
2510     if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2511
2512       pc = tp_percentage_by_time ( widgets->tr, widgets->marker_tp );
2513
2514       gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2515       gint    y_blob = 0;
2516       if (widgets->is_blob_drawn) {
2517         pc_blob = tp_percentage_by_time ( widgets->tr, widgets->blob_tp );
2518         if (!isnan(pc_blob)) {
2519           x_blob = (pc_blob * widgets->profile_width);
2520         }
2521         y_blob = blobby_altitude_time (x_blob, widgets);
2522       }
2523
2524       gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2525       if (!isnan(pc)) {
2526         marker_x = (pc * widgets->profile_width) + MARGIN_X;
2527       }
2528
2529       save_image_and_draw_graph_marks (image,
2530                                        marker_x,
2531                                        gtk_widget_get_style(window)->black_gc,
2532                                        x_blob+MARGIN_X,
2533                                        y_blob+MARGIN_Y,
2534                                        &widgets->elev_time_graph_saved_img,
2535                                        widgets->profile_width,
2536                                        widgets->profile_height,
2537                                        &widgets->is_marker_drawn,
2538                                        &widgets->is_blob_drawn);
2539     }
2540   }
2541
2542   // Draw speed distances
2543   if (widgets->speed_dist_box != NULL) {
2544
2545     // Saved image no longer any good as we've resized, so we remove it here
2546     if (resized && widgets->speed_dist_graph_saved_img.img) {
2547       g_object_unref(widgets->speed_dist_graph_saved_img.img);
2548       widgets->speed_dist_graph_saved_img.img = NULL;
2549       widgets->speed_dist_graph_saved_img.saved = FALSE;
2550     }
2551
2552     child = gtk_container_get_children(GTK_CONTAINER(widgets->speed_dist_box));
2553     draw_sd (GTK_WIDGET(child->data), widgets->tr, widgets );
2554
2555     image = GTK_WIDGET(child->data);
2556     g_list_free(child);
2557
2558     // Ensure marker or blob are redrawn if necessary
2559     if (widgets->is_marker_drawn || widgets->is_blob_drawn) {
2560
2561       pc = tp_percentage_by_distance ( widgets->tr, widgets->marker_tp, widgets->track_length_inc_gaps );
2562       gdouble x_blob = -MARGIN_X - 1.0; // i.e. Don't draw unless we get a valid value
2563       gint y_blob = 0;
2564       if (widgets->is_blob_drawn) {
2565         pc_blob = tp_percentage_by_distance ( widgets->tr, widgets->blob_tp, widgets->track_length_inc_gaps );
2566         if (!isnan(pc_blob)) {
2567           x_blob = (pc_blob * widgets->profile_width);
2568         }
2569         y_blob = blobby_speed_dist (x_blob, widgets);
2570       }
2571
2572       gdouble marker_x = -1.0; // i.e. Don't draw unless we get a valid value
2573       if (!isnan(pc)) {
2574         marker_x = (pc * widgets->profile_width) + MARGIN_X;
2575       }
2576
2577       save_image_and_draw_graph_marks (image,
2578                                        marker_x,
2579                                        gtk_widget_get_style(window)->black_gc,
2580                                        x_blob+MARGIN_X,
2581                                        y_blob+MARGIN_Y,
2582                                        &widgets->speed_dist_graph_saved_img,
2583                                        widgets->profile_width,
2584                                        widgets->profile_height,
2585                                        &widgets->is_marker_drawn,
2586                                        &widgets->is_blob_drawn);
2587     }
2588   }
2589
2590 }
2591
2592 /**
2593  * Configure/Resize the profile & speed/time images
2594  */
2595 static gboolean configure_event ( GtkWidget *widget, GdkEventConfigure *event, PropWidgets *widgets )
2596 {
2597   if (widgets->configure_dialog) {
2598     // Determine size offsets between dialog size and size for images
2599     // Only on the initialisation of the dialog
2600     widgets->profile_width_offset = event->width - widgets->profile_width;
2601     widgets->profile_height_offset = event->height - widgets->profile_height;
2602     widgets->configure_dialog = FALSE;
2603
2604     // Without this the settting, the dialog will only grow in vertical size - one can not then make it smaller!
2605     gtk_widget_set_size_request ( widget, widgets->profile_width+widgets->profile_width_offset, widgets->profile_height+widgets->profile_height_offset );
2606
2607     // Allow resizing back down to a minimal size (especially useful if the initial size has been made bigger after restoring from the saved settings)
2608     GdkGeometry geom = { 600+widgets->profile_width_offset, 300+widgets->profile_height_offset, 0, 0, 0, 0, 0, 0, 0, 0, GDK_GRAVITY_STATIC };
2609     gdk_window_set_geometry_hints ( gtk_widget_get_window(widget), &geom, GDK_HINT_MIN_SIZE );
2610   }
2611   else {
2612     widgets->profile_width_old = widgets->profile_width;
2613     widgets->profile_height_old = widgets->profile_height;
2614   }
2615
2616   // Now adjust From Dialog size to get image size
2617   widgets->profile_width = event->width - widgets->profile_width_offset;
2618   widgets->profile_height = event->height - widgets->profile_height_offset;
2619
2620   // ATM we receive configure_events when the dialog is moved and so no further action is necessary
2621   if ( !widgets->configure_dialog &&
2622        (widgets->profile_width_old == widgets->profile_width) && (widgets->profile_height_old == widgets->profile_height) )
2623     return FALSE;
2624
2625   // Draw stuff
2626   draw_all_graphs ( widget, widgets, TRUE );
2627
2628   return FALSE;
2629 }
2630
2631 /**
2632  * Create height profile widgets including the image and callbacks
2633  */
2634 GtkWidget *vik_trw_layer_create_profile ( GtkWidget *window, PropWidgets *widgets, gdouble *min_alt, gdouble *max_alt)
2635 {
2636   GdkPixmap *pix;
2637   GtkWidget *image;
2638   GtkWidget *eventbox;
2639
2640   // First allocation
2641   widgets->altitudes = vik_track_make_elevation_map ( widgets->tr, widgets->profile_width );
2642
2643   if ( widgets->altitudes == NULL ) {
2644     *min_alt = *max_alt = VIK_DEFAULT_ALTITUDE;
2645     return NULL;
2646   }
2647
2648   minmax_array(widgets->altitudes, min_alt, max_alt, TRUE, widgets->profile_width);
2649   
2650   pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2651   image = gtk_image_new_from_pixmap ( pix, NULL );
2652
2653   g_object_unref ( G_OBJECT(pix) );
2654
2655   eventbox = gtk_event_box_new ();
2656   g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_profile_click), widgets );
2657   g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_profile_move), widgets );
2658   gtk_container_add ( GTK_CONTAINER(eventbox), image );
2659   gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_STRUCTURE_MASK);
2660
2661   return eventbox;
2662 }
2663
2664 /**
2665  * Create height profile widgets including the image and callbacks
2666  */
2667 GtkWidget *vik_trw_layer_create_gradient ( GtkWidget *window, PropWidgets *widgets)
2668 {
2669   GdkPixmap *pix;
2670   GtkWidget *image;
2671   GtkWidget *eventbox;
2672
2673   // First allocation
2674   widgets->gradients = vik_track_make_gradient_map ( widgets->tr, widgets->profile_width );
2675
2676   if ( widgets->gradients == NULL ) {
2677     return NULL;
2678   }
2679
2680   pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2681   image = gtk_image_new_from_pixmap ( pix, NULL );
2682
2683   g_object_unref ( G_OBJECT(pix) );
2684
2685   eventbox = gtk_event_box_new ();
2686   g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_gradient_click), widgets );
2687   g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_gradient_move), widgets );
2688   gtk_container_add ( GTK_CONTAINER(eventbox), image );
2689   gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_STRUCTURE_MASK);
2690
2691   return eventbox;
2692 }
2693
2694 /**
2695  * Create speed/time widgets including the image and callbacks
2696  */
2697 GtkWidget *vik_trw_layer_create_vtdiag ( GtkWidget *window, PropWidgets *widgets)
2698 {
2699   GdkPixmap *pix;
2700   GtkWidget *image;
2701   GtkWidget *eventbox;
2702
2703   // First allocation
2704   widgets->speeds = vik_track_make_speed_map ( widgets->tr, widgets->profile_width );
2705   if ( widgets->speeds == NULL )
2706     return NULL;
2707
2708   pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2709   image = gtk_image_new_from_pixmap ( pix, NULL );
2710
2711 #if 0
2712   /* XXX this can go out, it's just a helpful dev tool */
2713   {
2714     int j;
2715     GdkGC **colors[8] = { gtk_widget_get_style(window)->bg_gc,
2716                           gtk_widget_get_style(window)->fg_gc,
2717                           gtk_widget_get_style(window)->light_gc,
2718                           gtk_widget_get_style(window)->dark_gc,
2719                           gtk_widget_get_style(window)->mid_gc,
2720                           gtk_widget_get_style(window)->text_gc,
2721                           gtk_widget_get_style(window)->base_gc,
2722                           gtk_widget_get_style(window)->text_aa_gc };
2723     for (i=0; i<5; i++) {
2724       for (j=0; j<8; j++) {
2725         gdk_draw_rectangle(GDK_DRAWABLE(pix), colors[j][i],
2726                            TRUE, i*20, j*20, 20, 20);
2727         gdk_draw_rectangle(GDK_DRAWABLE(pix), gtk_widget_get_style(window)->black_gc,
2728                            FALSE, i*20, j*20, 20, 20);
2729       }
2730     }
2731   }
2732 #endif
2733
2734   g_object_unref ( G_OBJECT(pix) );
2735
2736   eventbox = gtk_event_box_new ();
2737   g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_vt_click), widgets );
2738   g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_vt_move), widgets );
2739   gtk_container_add ( GTK_CONTAINER(eventbox), image );
2740   gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
2741
2742   return eventbox;
2743 }
2744
2745 /**
2746  * Create distance / time widgets including the image and callbacks
2747  */
2748 GtkWidget *vik_trw_layer_create_dtdiag ( GtkWidget *window, PropWidgets *widgets)
2749 {
2750   GdkPixmap *pix;
2751   GtkWidget *image;
2752   GtkWidget *eventbox;
2753
2754   // First allocation
2755   widgets->distances = vik_track_make_distance_map ( widgets->tr, widgets->profile_width );
2756   if ( widgets->distances == NULL )
2757     return NULL;
2758
2759   pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2760   image = gtk_image_new_from_pixmap ( pix, NULL );
2761
2762   g_object_unref ( G_OBJECT(pix) );
2763
2764   eventbox = gtk_event_box_new ();
2765   g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_dt_click), widgets );
2766   g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_dt_move), widgets );
2767   //g_signal_connect_swapped ( G_OBJECT(eventbox), "destroy", G_CALLBACK(g_free), widgets );
2768   gtk_container_add ( GTK_CONTAINER(eventbox), image );
2769   gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
2770
2771   return eventbox;
2772 }
2773
2774 /**
2775  * Create elevation / time widgets including the image and callbacks
2776  */
2777 GtkWidget *vik_trw_layer_create_etdiag ( GtkWidget *window, PropWidgets *widgets)
2778 {
2779   GdkPixmap *pix;
2780   GtkWidget *image;
2781   GtkWidget *eventbox;
2782
2783   // First allocation
2784   widgets->ats = vik_track_make_elevation_time_map ( widgets->tr, widgets->profile_width );
2785   if ( widgets->ats == NULL )
2786     return NULL;
2787
2788   pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2789   image = gtk_image_new_from_pixmap ( pix, NULL );
2790
2791   g_object_unref ( G_OBJECT(pix) );
2792
2793   eventbox = gtk_event_box_new ();
2794   g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_et_click), widgets );
2795   g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_et_move), widgets );
2796   gtk_container_add ( GTK_CONTAINER(eventbox), image );
2797   gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
2798
2799   return eventbox;
2800 }
2801
2802 /**
2803  * Create speed/distance widgets including the image and callbacks
2804  */
2805 GtkWidget *vik_trw_layer_create_sddiag ( GtkWidget *window, PropWidgets *widgets)
2806 {
2807   GdkPixmap *pix;
2808   GtkWidget *image;
2809   GtkWidget *eventbox;
2810
2811   // First allocation
2812   widgets->speeds_dist = vik_track_make_speed_dist_map ( widgets->tr, widgets->profile_width );
2813   if ( widgets->speeds_dist == NULL )
2814     return NULL;
2815
2816   pix = gdk_pixmap_new( gtk_widget_get_window(window), widgets->profile_width+MARGIN_X, widgets->profile_height+MARGIN_Y, -1 );
2817   image = gtk_image_new_from_pixmap ( pix, NULL );
2818
2819   g_object_unref ( G_OBJECT(pix) );
2820
2821   eventbox = gtk_event_box_new ();
2822   g_signal_connect ( G_OBJECT(eventbox), "button_press_event", G_CALLBACK(track_sd_click), widgets );
2823   g_signal_connect ( G_OBJECT(eventbox), "motion_notify_event", G_CALLBACK(track_sd_move), widgets );
2824   gtk_container_add ( GTK_CONTAINER(eventbox), image );
2825   gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
2826
2827   return eventbox;
2828 }
2829 #undef MARGIN_X
2830
2831 #define VIK_SETTINGS_TRACK_PROFILE_WIDTH "track_profile_display_width"
2832 #define VIK_SETTINGS_TRACK_PROFILE_HEIGHT "track_profile_display_height"
2833
2834 static void save_values ( PropWidgets *widgets )
2835 {
2836   // Session settings
2837   a_settings_set_integer ( VIK_SETTINGS_TRACK_PROFILE_WIDTH, widgets->profile_width );
2838   a_settings_set_integer ( VIK_SETTINGS_TRACK_PROFILE_HEIGHT, widgets->profile_height );
2839
2840   // Just for this session ATM
2841   show_dem                = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_dem) );
2842   show_alt_gps_speed      = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_alt_gps_speed) );
2843   show_gps_speed          = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_gps_speed) );
2844   show_gradient_gps_speed = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_gradient_gps_speed) );
2845   show_dist_speed         = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_dist_speed) );
2846   show_elev_dem           = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_elev_dem) );
2847   show_elev_speed         = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_elev_speed) );
2848   show_sd_gps_speed       = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->w_show_sd_gps_speed) );
2849 }
2850
2851 static void destroy_cb ( GtkDialog *dialog, PropWidgets *widgets )
2852 {
2853   save_values(widgets);
2854   prop_widgets_free(widgets);
2855 }
2856
2857 static void propwin_response_cb( GtkDialog *dialog, gint resp, PropWidgets *widgets )
2858 {
2859   VikTrack *tr = widgets->tr;
2860   VikTrwLayer *vtl = widgets->vtl;
2861   gboolean keep_dialog = FALSE;
2862
2863   /* FIXME: check and make sure the track still exists before doing anything to it */
2864   /* Note: destroying diaglog (eg, parent window exit) won't give "response" */
2865   switch (resp) {
2866     case GTK_RESPONSE_DELETE_EVENT: /* received delete event (not from buttons) */
2867     case GTK_RESPONSE_REJECT:
2868       break;
2869     case GTK_RESPONSE_ACCEPT:
2870       vik_track_set_comment(tr, gtk_entry_get_text(GTK_ENTRY(widgets->w_comment)));
2871       vik_track_set_description(tr, gtk_entry_get_text(GTK_ENTRY(widgets->w_description)));
2872       gtk_color_button_get_color ( GTK_COLOR_BUTTON(widgets->w_color), &(tr->color) );
2873       tr->draw_name_mode = gtk_combo_box_get_active ( GTK_COMBO_BOX(widgets->w_namelabel) );
2874       tr->max_number_dist_labels = gtk_spin_button_get_value_as_int ( GTK_SPIN_BUTTON(widgets->w_number_distlabels) );
2875       trw_layer_update_treeview ( widgets->vtl, widgets->tr );
2876       vik_layer_emit_update ( VIK_LAYER(vtl) );
2877       break;
2878     case VIK_TRW_LAYER_PROPWIN_REVERSE:
2879       vik_track_reverse(tr);
2880       vik_layer_emit_update ( VIK_LAYER(vtl) );
2881       break;
2882     case VIK_TRW_LAYER_PROPWIN_DEL_DUP:
2883       vik_track_remove_dup_points(tr); // NB ignore the returned answer
2884       // As we could have seen the nuber of dulplicates that would be deleted in the properties statistics tab,
2885       //   choose not to inform the user unnecessarily
2886
2887       /* above operation could have deleted current_tp or last_tp */
2888       trw_layer_cancel_tps_of_track ( vtl, tr );
2889       vik_layer_emit_update ( VIK_LAYER(vtl) );
2890       break;
2891     case VIK_TRW_LAYER_PROPWIN_SPLIT:
2892       {
2893         /* get new tracks, add them and then the delete old one. old can still exist on clipboard. */
2894         guint ntracks;
2895         
2896         VikTrack **tracks = vik_track_split_into_segments(tr, &ntracks);
2897         gchar *new_tr_name;
2898         guint i;
2899         for ( i = 0; i < ntracks; i++ )
2900         {
2901           if ( tracks[i] ) {
2902             new_tr_name = trw_layer_new_unique_sublayer_name ( vtl,
2903                                                                widgets->tr->is_route ? VIK_TRW_LAYER_SUBLAYER_ROUTE : VIK_TRW_LAYER_SUBLAYER_TRACK,
2904                                                                widgets->tr->name);
2905             if ( widgets->tr->is_route )
2906               vik_trw_layer_add_route ( vtl, new_tr_name, tracks[i] );
2907             else
2908               vik_trw_layer_add_track ( vtl, new_tr_name, tracks[i] );
2909             vik_track_calculate_bounds ( tracks[i] );
2910
2911             g_free ( new_tr_name );
2912           }
2913         }
2914         if ( tracks )
2915         {
2916           g_free ( tracks );
2917           /* Don't let track destroy this dialog */
2918           vik_track_clear_property_dialog(tr);
2919           if ( widgets->tr->is_route )
2920             vik_trw_layer_delete_route ( vtl, tr );
2921           else
2922             vik_trw_layer_delete_track ( vtl, tr );
2923           vik_layer_emit_update ( VIK_LAYER(vtl) ); /* chase thru the hoops */
2924         }
2925       }
2926       break;
2927     case VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER:
2928       {
2929         GList *iter = tr->trackpoints;
2930         while ((iter = iter->next)) {
2931           if (widgets->marker_tp == VIK_TRACKPOINT(iter->data))
2932             break;
2933         }
2934         if (iter == NULL) {
2935           a_dialog_msg(VIK_GTK_WINDOW_FROM_LAYER(vtl), GTK_MESSAGE_ERROR,
2936                   _("Failed spliting track. Track unchanged"), NULL);
2937           keep_dialog = TRUE;
2938           break;
2939         }
2940
2941         gchar *r_name = trw_layer_new_unique_sublayer_name(vtl,
2942                                                            widgets->tr->is_route ? VIK_TRW_LAYER_SUBLAYER_ROUTE : VIK_TRW_LAYER_SUBLAYER_TRACK,
2943                                                            widgets->tr->name);
2944         iter->prev->next = NULL;
2945         iter->prev = NULL;
2946         VikTrack *tr_right = vik_track_new();
2947         if ( tr->comment )
2948           vik_track_set_comment ( tr_right, tr->comment );
2949         tr_right->visible = tr->visible;
2950         tr_right->is_route = tr->is_route;
2951         tr_right->trackpoints = iter;
2952
2953         if ( widgets->tr->is_route )
2954           vik_trw_layer_add_route(vtl, r_name, tr_right);
2955         else
2956           vik_trw_layer_add_track(vtl, r_name, tr_right);
2957         vik_track_calculate_bounds ( tr );
2958         vik_track_calculate_bounds ( tr_right );
2959
2960         g_free ( r_name );
2961
2962         vik_layer_emit_update ( VIK_LAYER(vtl) );
2963       }
2964       break;
2965     default:
2966       fprintf(stderr, "DEBUG: unknown response\n");
2967       return;
2968   }
2969
2970   /* Keep same behaviour for now: destroy dialog if click on any button */
2971   if (!keep_dialog) {
2972     vik_track_clear_property_dialog(tr);
2973     gtk_widget_destroy ( GTK_WIDGET(dialog) );
2974   }
2975 }
2976
2977 /**
2978  * Force a redraw when checkbutton has been toggled to show/hide that information
2979  */
2980 static void checkbutton_toggle_cb ( GtkToggleButton *togglebutton, PropWidgets *widgets, gpointer dummy )
2981 {
2982   // Even though not resized, we'll pretend it is -
2983   //  as this invalidates the saved images (since the image may have changed)
2984   draw_all_graphs ( widgets->dialog, widgets, TRUE );
2985 }
2986
2987 /**
2988  *  Create the widgets for the given graph tab
2989  */
2990 static GtkWidget *create_graph_page ( GtkWidget *graph,
2991                                       const gchar *markup,
2992                                       GtkWidget *value,
2993                                       const gchar *markup2,
2994                                       GtkWidget *value2,
2995                                       const gchar *markup3,
2996                                       GtkWidget *value3,
2997                                       GtkWidget *checkbutton1,
2998                                       gboolean checkbutton1_default,
2999                                       GtkWidget *checkbutton2,
3000                                       gboolean checkbutton2_default )
3001 {
3002   GtkWidget *hbox = gtk_hbox_new ( FALSE, 10 );
3003   GtkWidget *vbox = gtk_vbox_new ( FALSE, 10 );
3004   GtkWidget *label = gtk_label_new (NULL);
3005   GtkWidget *label2 = gtk_label_new (NULL);
3006   GtkWidget *label3 = gtk_label_new (NULL);
3007   gtk_box_pack_start (GTK_BOX(vbox), graph, FALSE, FALSE, 0);
3008   gtk_label_set_markup ( GTK_LABEL(label), markup );
3009   gtk_label_set_markup ( GTK_LABEL(label2), markup2 );
3010   gtk_label_set_markup ( GTK_LABEL(label3), markup3 );
3011   gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, FALSE, 0);
3012   gtk_box_pack_start (GTK_BOX(hbox), value, FALSE, FALSE, 0);
3013   gtk_box_pack_start (GTK_BOX(hbox), label2, FALSE, FALSE, 0);
3014   gtk_box_pack_start (GTK_BOX(hbox), value2, FALSE, FALSE, 0);
3015   if ( value3 ) {
3016     gtk_box_pack_start (GTK_BOX(hbox), label3, FALSE, FALSE, 0);
3017     gtk_box_pack_start (GTK_BOX(hbox), value3, FALSE, FALSE, 0);
3018   }
3019   if (checkbutton2) {
3020     gtk_box_pack_end (GTK_BOX(hbox), checkbutton2, FALSE, FALSE, 0);
3021     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(checkbutton2), checkbutton2_default);
3022   }
3023   if (checkbutton1) {
3024     gtk_box_pack_end (GTK_BOX(hbox), checkbutton1, FALSE, FALSE, 0);
3025     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(checkbutton1), checkbutton1_default);
3026   }
3027   gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
3028
3029   return vbox;
3030 }
3031
3032 static GtkWidget *create_table (int cnt, char *labels[], GtkWidget *contents[])
3033 {
3034   GtkTable *table;
3035   int i;
3036
3037   table = GTK_TABLE(gtk_table_new (cnt, 2, FALSE));
3038   gtk_table_set_col_spacing (table, 0, 10);
3039   for (i=0; i<cnt; i++) {
3040     GtkWidget *label;
3041
3042     // Settings so the text positioning only moves around vertically when the dialog is resized
3043     // This also gives more room to see the track comment
3044     label = gtk_label_new(NULL);
3045     gtk_misc_set_alignment ( GTK_MISC(label), 1, 0.5 ); // Position text centrally in vertical plane
3046     gtk_label_set_markup ( GTK_LABEL(label), _(labels[i]) );
3047     gtk_table_attach ( table, label, 0, 1, i, i+1, GTK_FILL, GTK_SHRINK, 0, 0 );
3048     if (GTK_IS_MISC(contents[i])) {
3049       gtk_misc_set_alignment ( GTK_MISC(contents[i]), 0, 0.5 );
3050     }
3051     if ( GTK_IS_COLOR_BUTTON(contents[i]) || GTK_IS_COMBO_BOX(contents[i]) )
3052       // Buttons compressed - otherwise look weird (to me) if vertically massive
3053       gtk_table_attach ( table, contents[i], 1, 2, i, i+1, GTK_FILL, GTK_SHRINK, 0, 5 );
3054     else
3055       // Expand for comments + descriptions / labels
3056       gtk_table_attach_defaults ( table, contents[i], 1, 2, i, i+1 );
3057   }
3058
3059   return GTK_WIDGET (table);
3060 }
3061
3062 void vik_trw_layer_propwin_run ( GtkWindow *parent,
3063                                  VikTrwLayer *vtl,
3064                                  VikTrack *tr,
3065                                  gpointer vlp,
3066                                  VikViewport *vvp,
3067                                  gboolean start_on_stats )
3068 {
3069   PropWidgets *widgets = prop_widgets_new();
3070   widgets->vtl = vtl;
3071   widgets->vvp = vvp;
3072   widgets->vlp = vlp;
3073   widgets->tr = tr;
3074
3075   gint profile_size_value;
3076   // Ensure minimum values
3077   widgets->profile_width = 600;
3078   if ( a_settings_get_integer ( VIK_SETTINGS_TRACK_PROFILE_WIDTH, &profile_size_value ) )
3079     if ( profile_size_value > widgets->profile_width )
3080       widgets->profile_width = profile_size_value;
3081
3082   widgets->profile_height = 300;
3083   if ( a_settings_get_integer ( VIK_SETTINGS_TRACK_PROFILE_HEIGHT, &profile_size_value ) )
3084     if ( profile_size_value > widgets->profile_height )
3085       widgets->profile_height = profile_size_value;
3086
3087   gchar *title = g_strdup_printf(_("%s - Track Properties"), tr->name);
3088   GtkWidget *dialog = gtk_dialog_new_with_buttons (title,
3089                          parent,
3090                          GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
3091                          GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
3092                          _("Split at _Marker"), VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER,
3093                          _("Split _Segments"), VIK_TRW_LAYER_PROPWIN_SPLIT,
3094                          _("_Reverse"),        VIK_TRW_LAYER_PROPWIN_REVERSE,
3095                          _("_Delete Dupl."),   VIK_TRW_LAYER_PROPWIN_DEL_DUP,
3096                          GTK_STOCK_OK,     GTK_RESPONSE_ACCEPT,
3097                          NULL);
3098   widgets->dialog = dialog;
3099   g_signal_connect( G_OBJECT(dialog), "response", G_CALLBACK(propwin_response_cb), widgets);
3100
3101   g_free(title);
3102   GtkWidget *table;
3103   gdouble tr_len;
3104   gulong tp_count;
3105   guint seg_count;
3106
3107   gdouble min_alt, max_alt;
3108   widgets->elev_box = vik_trw_layer_create_profile(GTK_WIDGET(parent), widgets, &min_alt, &max_alt);
3109   widgets->gradient_box = vik_trw_layer_create_gradient(GTK_WIDGET(parent), widgets);
3110   widgets->speed_box = vik_trw_layer_create_vtdiag(GTK_WIDGET(parent), widgets);
3111   widgets->dist_box = vik_trw_layer_create_dtdiag(GTK_WIDGET(parent), widgets);
3112   widgets->elev_time_box = vik_trw_layer_create_etdiag(GTK_WIDGET(parent), widgets);
3113   widgets->speed_dist_box = vik_trw_layer_create_sddiag(GTK_WIDGET(parent), widgets);
3114   GtkWidget *graphs = gtk_notebook_new();
3115
3116   GtkWidget *content_prop[20];
3117   int cnt_prop = 0;
3118
3119   static gchar *label_texts[] = {
3120     N_("<b>Comment:</b>"),
3121     N_("<b>Description:</b>"),
3122     N_("<b>Color:</b>"),
3123     N_("<b>Draw Name:</b>"),
3124     N_("<b>Distance Labels:</b>"),
3125   };
3126   static gchar *stats_texts[] = {
3127     N_("<b>Track Length:</b>"),
3128     N_("<b>Trackpoints:</b>"),
3129     N_("<b>Segments:</b>"),
3130     N_("<b>Duplicate Points:</b>"),
3131     N_("<b>Max Speed:</b>"),
3132     N_("<b>Avg. Speed:</b>"),
3133     N_("<b>Moving Avg. Speed:</b>"),
3134     N_("<b>Avg. Dist. Between TPs:</b>"),
3135     N_("<b>Elevation Range:</b>"),
3136     N_("<b>Total Elevation Gain/Loss:</b>"),
3137     N_("<b>Start:</b>"),
3138     N_("<b>End:</b>"),
3139     N_("<b>Duration:</b>"),
3140   };
3141   static gchar tmp_buf[50];
3142   gdouble tmp_speed;
3143
3144   // Properties
3145   widgets->w_comment = gtk_entry_new ();
3146   if ( tr->comment )
3147     gtk_entry_set_text ( GTK_ENTRY(widgets->w_comment), tr->comment );
3148   g_signal_connect_swapped ( widgets->w_comment, "activate", G_CALLBACK(a_dialog_response_accept), GTK_DIALOG(dialog) );
3149   content_prop[cnt_prop++] = widgets->w_comment;
3150
3151   widgets->w_description = gtk_entry_new ();
3152   if ( tr->description )
3153     gtk_entry_set_text ( GTK_ENTRY(widgets->w_description), tr->description );
3154   g_signal_connect_swapped ( widgets->w_description, "activate", G_CALLBACK(a_dialog_response_accept), GTK_DIALOG(dialog) );
3155   content_prop[cnt_prop++] = widgets->w_description;
3156
3157   widgets->w_color = content_prop[cnt_prop++] = gtk_color_button_new_with_color ( &(tr->color) );
3158
3159   static gchar *draw_name_labels[] = {
3160     N_("No"),
3161     N_("Centre"),
3162     N_("Start only"),
3163     N_("End only"),
3164     N_("Start and End"),
3165     N_("Centre, Start and End"),
3166     NULL
3167   };
3168
3169   widgets->w_namelabel = content_prop[cnt_prop++] = vik_combo_box_text_new ();
3170   gchar **pstr = draw_name_labels;
3171   while ( *pstr )
3172     vik_combo_box_text_append ( widgets->w_namelabel, *(pstr++) );
3173   gtk_combo_box_set_active ( GTK_COMBO_BOX(widgets->w_namelabel), tr->draw_name_mode );
3174
3175   widgets->w_number_distlabels = content_prop[cnt_prop++] =
3176    gtk_spin_button_new ( GTK_ADJUSTMENT(gtk_adjustment_new(tr->max_number_dist_labels, 0, 100, 1, 1, 0)), 1, 0 );
3177   gtk_widget_set_tooltip_text ( GTK_WIDGET(widgets->w_number_distlabels), _("Maximum number of distance labels to be shown") );
3178
3179   table = create_table (cnt_prop, label_texts, content_prop);
3180
3181   gtk_notebook_append_page(GTK_NOTEBOOK(graphs), GTK_WIDGET(table), gtk_label_new(_("Properties")));
3182
3183   // Statistics
3184   GtkWidget *content[20];
3185   int cnt = 0;
3186
3187   vik_units_distance_t dist_units = a_vik_get_units_distance ();
3188
3189   // NB This value not shown yet - but is used by internal calculations
3190   widgets->track_length_inc_gaps = vik_track_get_length_including_gaps(tr);
3191
3192   tr_len = widgets->track_length = vik_track_get_length(tr);
3193   switch (dist_units) {
3194   case VIK_UNITS_DISTANCE_KILOMETRES:
3195     g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km", tr_len/1000.0 );
3196     break;
3197   case VIK_UNITS_DISTANCE_MILES:
3198     g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f miles", VIK_METERS_TO_MILES(tr_len) );
3199     break;
3200   case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
3201     g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f NM", VIK_METERS_TO_NAUTICAL_MILES(tr_len) );
3202     break;
3203   default:
3204     g_critical("Houston, we've had a problem. distance=%d", dist_units);
3205   }
3206   widgets->w_track_length = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3207
3208   tp_count = vik_track_get_tp_count(tr);
3209   g_snprintf(tmp_buf, sizeof(tmp_buf), "%lu", tp_count );
3210   widgets->w_tp_count = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3211
3212   seg_count = vik_track_get_segment_count(tr) ;
3213   g_snprintf(tmp_buf, sizeof(tmp_buf), "%u", seg_count );
3214   widgets->w_segment_count = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3215
3216   g_snprintf(tmp_buf, sizeof(tmp_buf), "%lu", vik_track_get_dup_point_count(tr) );
3217   widgets->w_duptp_count = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3218
3219   vik_units_speed_t speed_units = a_vik_get_units_speed ();
3220   tmp_speed = vik_track_get_max_speed(tr);
3221   if ( tmp_speed == 0 )
3222     g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
3223   else {
3224     switch (speed_units) {
3225     case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
3226       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km/h", VIK_MPS_TO_KPH(tmp_speed));
3227       break;
3228     case VIK_UNITS_SPEED_MILES_PER_HOUR:
3229       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f mph", VIK_MPS_TO_MPH(tmp_speed));
3230       break;
3231     case VIK_UNITS_SPEED_METRES_PER_SECOND:
3232       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f m/s", tmp_speed );
3233       break;
3234     case VIK_UNITS_SPEED_KNOTS:
3235       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f knots", VIK_MPS_TO_KNOTS(tmp_speed));
3236       break;
3237     default:
3238       g_snprintf (tmp_buf, sizeof(tmp_buf), "--" );
3239       g_critical("Houston, we've had a problem. speed=%d", speed_units);
3240     }
3241   }
3242   widgets->w_max_speed = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3243
3244   tmp_speed = vik_track_get_average_speed(tr);
3245   if ( tmp_speed == 0 )
3246     g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
3247   else {
3248     switch (speed_units) {
3249     case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
3250       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km/h", VIK_MPS_TO_KPH(tmp_speed));
3251       break;
3252     case VIK_UNITS_SPEED_MILES_PER_HOUR:
3253       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f mph", VIK_MPS_TO_MPH(tmp_speed));
3254       break;
3255     case VIK_UNITS_SPEED_METRES_PER_SECOND:
3256       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f m/s", tmp_speed );
3257       break;
3258     case VIK_UNITS_SPEED_KNOTS:
3259       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f knots", VIK_MPS_TO_KNOTS(tmp_speed));
3260       break;
3261     default:
3262       g_snprintf (tmp_buf, sizeof(tmp_buf), "--" );
3263       g_critical("Houston, we've had a problem. speed=%d", speed_units);
3264     }
3265   }
3266   widgets->w_avg_speed = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3267
3268   // Use 60sec as the default period to be considered stopped
3269   //  this is the TrackWaypoint draw stops default value 'vtl->stop_length'
3270   //  however this variable is not directly accessible - and I don't expect it's often changed from the default
3271   //  so ATM just put in the number
3272   tmp_speed = vik_track_get_average_speed_moving(tr, 60);
3273   if ( tmp_speed == 0 )
3274     g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
3275   else {
3276     switch (speed_units) {
3277     case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
3278       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f km/h", VIK_MPS_TO_KPH(tmp_speed));
3279       break;
3280     case VIK_UNITS_SPEED_MILES_PER_HOUR:
3281       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f mph", VIK_MPS_TO_MPH(tmp_speed));
3282       break;
3283     case VIK_UNITS_SPEED_METRES_PER_SECOND:
3284       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f m/s", tmp_speed );
3285       break;
3286     case VIK_UNITS_SPEED_KNOTS:
3287       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f knots", VIK_MPS_TO_KNOTS(tmp_speed));
3288       break;
3289     default:
3290       g_snprintf (tmp_buf, sizeof(tmp_buf), "--" );
3291       g_critical("Houston, we've had a problem. speed=%d", speed_units);
3292     }
3293   }
3294   widgets->w_mvg_speed = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3295
3296   switch (dist_units) {
3297   case VIK_UNITS_DISTANCE_KILOMETRES:
3298     // Even though kilometres, the average distance between points is going to be quite small so keep in metres
3299     g_snprintf(tmp_buf, sizeof(tmp_buf), "%.2f m", (tp_count - seg_count) == 0 ? 0 : tr_len / ( tp_count - seg_count ) );
3300     break;
3301   case VIK_UNITS_DISTANCE_MILES:
3302     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 )) );
3303     break;
3304   case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
3305     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 )) );
3306     break;
3307   default:
3308     g_critical("Houston, we've had a problem. distance=%d", dist_units);
3309   }
3310   widgets->w_avg_dist = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3311
3312   vik_units_height_t height_units = a_vik_get_units_height ();
3313   if ( min_alt == VIK_DEFAULT_ALTITUDE )
3314     g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
3315   else {
3316     switch (height_units) {
3317     case VIK_UNITS_HEIGHT_METRES:
3318       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.0f m - %.0f m", min_alt, max_alt );
3319       break;
3320     case VIK_UNITS_HEIGHT_FEET:
3321       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.0f feet - %.0f feet", VIK_METERS_TO_FEET(min_alt), VIK_METERS_TO_FEET(max_alt) );
3322       break;
3323     default:
3324       g_snprintf(tmp_buf, sizeof(tmp_buf), "--" );
3325       g_critical("Houston, we've had a problem. height=%d", height_units);
3326     }
3327   }
3328   widgets->w_elev_range = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3329
3330   vik_track_get_total_elevation_gain(tr, &max_alt, &min_alt );
3331   if ( min_alt == VIK_DEFAULT_ALTITUDE )
3332     g_snprintf(tmp_buf, sizeof(tmp_buf), _("No Data"));
3333   else {
3334     switch (height_units) {
3335     case VIK_UNITS_HEIGHT_METRES:
3336       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.0f m / %.0f m", max_alt, min_alt );
3337       break;
3338     case VIK_UNITS_HEIGHT_FEET:
3339       g_snprintf(tmp_buf, sizeof(tmp_buf), "%.0f feet / %.0f feet", VIK_METERS_TO_FEET(max_alt), VIK_METERS_TO_FEET(min_alt) );
3340       break;
3341     default:
3342       g_snprintf(tmp_buf, sizeof(tmp_buf), "--" );
3343       g_critical("Houston, we've had a problem. height=%d", height_units);
3344     }
3345   }
3346   widgets->w_elev_gain = content[cnt++] = ui_label_new_selectable ( tmp_buf );
3347
3348 #if 0
3349 #define PACK(w) gtk_box_pack_start (GTK_BOX(right_vbox), w, FALSE, FALSE, 0);
3350   gtk_box_pack_start (GTK_BOX(right_vbox), e_cmt, FALSE, FALSE, 0); 
3351   PACK(l_len);
3352   PACK(l_tps);
3353   PACK(l_segs);
3354   PACK(l_dups);
3355   PACK(l_maxs);
3356   PACK(l_avgs);
3357   PACK(l_avgd);
3358   PACK(l_elev);
3359   PACK(l_galo);
3360 #undef PACK;
3361 #endif
3362
3363   if ( tr->trackpoints && VIK_TRACKPOINT(tr->trackpoints->data)->timestamp )
3364   {
3365     time_t t1, t2;
3366     t1 = VIK_TRACKPOINT(tr->trackpoints->data)->timestamp;
3367     t2 = VIK_TRACKPOINT(g_list_last(tr->trackpoints)->data)->timestamp;
3368
3369     VikCoord vc;
3370     // Notional center of a track is simply an average of the bounding box extremities
3371     struct LatLon center = { (tr->bbox.north+tr->bbox.south)/2, (tr->bbox.east+tr->bbox.west)/2 };
3372     vik_coord_load_from_latlon ( &vc, vik_trw_layer_get_coord_mode(vtl), &center );
3373
3374     widgets->tz = vu_get_tz_at_location ( &vc );
3375
3376     gchar *msg;
3377     msg = vu_get_time_string ( &t1, "%c", &vc, widgets->tz );
3378     widgets->w_time_start = content[cnt++] = ui_label_new_selectable(msg);
3379     g_free ( msg );
3380
3381     msg = vu_get_time_string ( &t2, "%c", &vc, widgets->tz );
3382     widgets->w_time_end = content[cnt++] = ui_label_new_selectable(msg);
3383     g_free ( msg );
3384
3385     g_snprintf(tmp_buf, sizeof(tmp_buf), _("%d minutes"), (int)(t2-t1)/60);
3386     widgets->w_time_dur = content[cnt++] = ui_label_new_selectable(tmp_buf);
3387   } else {
3388     widgets->w_time_start = content[cnt++] = gtk_label_new(_("No Data"));
3389     widgets->w_time_end = content[cnt++] = gtk_label_new(_("No Data"));
3390     widgets->w_time_dur = content[cnt++] = gtk_label_new(_("No Data"));
3391   }
3392
3393   table = create_table (cnt, stats_texts, content);
3394
3395   gtk_notebook_append_page(GTK_NOTEBOOK(graphs), GTK_WIDGET(table), gtk_label_new(_("Statistics")));
3396
3397   if ( widgets->elev_box ) {
3398     GtkWidget *page = NULL;
3399     widgets->w_cur_dist = ui_label_new_selectable(_("No Data"));
3400     widgets->w_cur_elevation = ui_label_new_selectable(_("No Data"));
3401     widgets->w_show_dem = gtk_check_button_new_with_mnemonic(_("Show D_EM"));
3402     widgets->w_show_alt_gps_speed = gtk_check_button_new_with_mnemonic(_("Show _GPS Speed"));
3403     page = create_graph_page (widgets->elev_box,
3404                               _("<b>Track Distance:</b>"), widgets->w_cur_dist,
3405                               _("<b>Track Height:</b>"), widgets->w_cur_elevation,
3406                               NULL, NULL,
3407                               widgets->w_show_dem, show_dem,
3408                               widgets->w_show_alt_gps_speed, show_alt_gps_speed);
3409     g_signal_connect (widgets->w_show_dem, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3410     g_signal_connect (widgets->w_show_alt_gps_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3411     gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Elevation-distance")));
3412   }
3413
3414   if ( widgets->gradient_box ) {
3415     GtkWidget *page = NULL;
3416     widgets->w_cur_gradient_dist = ui_label_new_selectable(_("No Data"));
3417     widgets->w_cur_gradient_gradient = ui_label_new_selectable(_("No Data"));
3418     widgets->w_show_gradient_gps_speed = gtk_check_button_new_with_mnemonic(_("Show _GPS Speed"));
3419     page = create_graph_page (widgets->gradient_box,
3420                               _("<b>Track Distance:</b>"), widgets->w_cur_gradient_dist,
3421                               _("<b>Track Gradient:</b>"), widgets->w_cur_gradient_gradient,
3422                               NULL, NULL,
3423                               widgets->w_show_gradient_gps_speed, show_gradient_gps_speed,
3424                               NULL, FALSE);
3425     g_signal_connect (widgets->w_show_gradient_gps_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3426     gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Gradient-distance")));
3427   }
3428
3429   if ( widgets->speed_box ) {
3430     GtkWidget *page = NULL;
3431     widgets->w_cur_time = ui_label_new_selectable(_("No Data"));
3432     widgets->w_cur_speed = ui_label_new_selectable(_("No Data"));
3433     widgets->w_cur_time_real = ui_label_new_selectable(_("No Data"));
3434     widgets->w_show_gps_speed = gtk_check_button_new_with_mnemonic(_("Show _GPS Speed"));
3435     page = create_graph_page (widgets->speed_box,
3436                               _("<b>Track Time:</b>"), widgets->w_cur_time,
3437                               _("<b>Track Speed:</b>"), widgets->w_cur_speed,
3438                               _("<b>Time/Date:</b>"), widgets->w_cur_time_real,
3439                               widgets->w_show_gps_speed, show_gps_speed,
3440                               NULL, FALSE);
3441     g_signal_connect (widgets->w_show_gps_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3442     gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Speed-time")));
3443   }
3444
3445   if ( widgets->dist_box ) {
3446     GtkWidget *page = NULL;
3447     widgets->w_cur_dist_time = ui_label_new_selectable(_("No Data"));
3448     widgets->w_cur_dist_dist = ui_label_new_selectable(_("No Data"));
3449     widgets->w_cur_dist_time_real = ui_label_new_selectable(_("No Data"));
3450     widgets->w_show_dist_speed = gtk_check_button_new_with_mnemonic(_("Show S_peed"));
3451     page = create_graph_page (widgets->dist_box,
3452                               _("<b>Track Distance:</b>"), widgets->w_cur_dist_dist,
3453                               _("<b>Track Time:</b>"), widgets->w_cur_dist_time,
3454                               _("<b>Time/Date:</b>"), widgets->w_cur_dist_time_real,
3455                               widgets->w_show_dist_speed, show_dist_speed,
3456                               NULL, FALSE);
3457     g_signal_connect (widgets->w_show_dist_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3458     gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Distance-time")));
3459   }
3460
3461   if ( widgets->elev_time_box ) {
3462     GtkWidget *page = NULL;
3463     widgets->w_cur_elev_time = ui_label_new_selectable(_("No Data"));
3464     widgets->w_cur_elev_elev = ui_label_new_selectable(_("No Data"));
3465     widgets->w_cur_elev_time_real = ui_label_new_selectable(_("No Data"));
3466     widgets->w_show_elev_speed = gtk_check_button_new_with_mnemonic(_("Show S_peed"));
3467     widgets->w_show_elev_dem = gtk_check_button_new_with_mnemonic(_("Show D_EM"));
3468     page = create_graph_page (widgets->elev_time_box,
3469                               _("<b>Track Time:</b>"), widgets->w_cur_elev_time,
3470                               _("<b>Track Height:</b>"), widgets->w_cur_elev_elev,
3471                               _("<b>Time/Date:</b>"), widgets->w_cur_elev_time_real,
3472                               widgets->w_show_elev_dem, show_elev_dem,
3473                               widgets->w_show_elev_speed, show_elev_speed);
3474     g_signal_connect (widgets->w_show_elev_dem, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3475     g_signal_connect (widgets->w_show_elev_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3476     gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Elevation-time")));
3477   }
3478
3479   if ( widgets->speed_dist_box ) {
3480     GtkWidget *page = NULL;
3481     widgets->w_cur_speed_dist = ui_label_new_selectable(_("No Data"));
3482     widgets->w_cur_speed_speed = ui_label_new_selectable(_("No Data"));
3483     widgets->w_show_sd_gps_speed = gtk_check_button_new_with_mnemonic(_("Show _GPS Speed"));
3484     page = create_graph_page (widgets->speed_dist_box,
3485                               _("<b>Track Distance:</b>"), widgets->w_cur_speed_dist,
3486                               _("<b>Track Speed:</b>"), widgets->w_cur_speed_speed,
3487                               NULL, NULL,
3488                               widgets->w_show_sd_gps_speed, show_sd_gps_speed,
3489                               NULL, FALSE);
3490     g_signal_connect (widgets->w_show_sd_gps_speed, "toggled", G_CALLBACK (checkbutton_toggle_cb), widgets);
3491     gtk_notebook_append_page(GTK_NOTEBOOK(graphs), page, gtk_label_new(_("Speed-distance")));
3492   }
3493
3494   gtk_box_pack_start (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), graphs, FALSE, FALSE, 0);
3495
3496   gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), VIK_TRW_LAYER_PROPWIN_SPLIT_MARKER, FALSE);
3497   if (seg_count <= 1)
3498     gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), VIK_TRW_LAYER_PROPWIN_SPLIT, FALSE);
3499   if (vik_track_get_dup_point_count(tr) <= 0)
3500     gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), VIK_TRW_LAYER_PROPWIN_DEL_DUP, FALSE);
3501
3502   // On dialog realization configure_event causes the graphs to be initially drawn
3503   widgets->configure_dialog = TRUE;
3504   g_signal_connect ( G_OBJECT(dialog), "configure-event", G_CALLBACK (configure_event), widgets );
3505
3506   g_signal_connect ( G_OBJECT(dialog), "destroy", G_CALLBACK (destroy_cb), widgets );
3507
3508   vik_track_set_property_dialog(tr, dialog);
3509   gtk_dialog_set_default_response ( GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT );
3510   gtk_widget_show_all ( dialog );
3511
3512   // Gtk note: due to historical reasons, this must be done after widgets are shown
3513   if ( start_on_stats )
3514     gtk_notebook_set_current_page ( GTK_NOTEBOOK(graphs), 1 );
3515 }
3516
3517
3518 /**
3519  * Update this property dialog
3520  * e.g. if the track has been renamed
3521  */
3522 void vik_trw_layer_propwin_update ( VikTrack *trk )
3523 {
3524   // If not displayed do nothing
3525   if ( !trk->property_dialog )
3526     return;
3527
3528   // Update title with current name
3529   if ( trk->name ) {
3530     gchar *title = g_strdup_printf ( _("%s - Track Properties"), trk->name );
3531     gtk_window_set_title ( GTK_WINDOW(trk->property_dialog), title );
3532     g_free(title);
3533   }
3534
3535 }