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