]> git.street.me.uk Git - andy/viking.git/blob - src/viktrwlayer_analysis.c
b0fea7e90858227a9bc206c73e71d91de455936b
[andy/viking.git] / src / viktrwlayer_analysis.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
4  *
5  * Copyright (C) 2013 Rob Norris <rw_norris@hotmail.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  ***********************************************************
22  *
23  */
24
25 #include <math.h>
26 #include <time.h>
27 #include <string.h>
28 #include <glib/gprintf.h>
29 #include <glib/gi18n.h>
30
31 #include "viking.h"
32 #include "viktrwlayer_analysis.h"
33 #include "ui_util.h"
34
35 // Units of each item are in SI Units
36 // (as returned by the appropriate internal viking track functions)
37 typedef struct {
38         gdouble  min_alt;
39         gdouble  max_alt;
40         gdouble  elev_gain;
41         gdouble  elev_loss;
42         gdouble  length;
43         gdouble  length_gaps;
44         gdouble  max_speed;
45         gulong   trackpoints;
46         guint    segments;
47         gint     duration;
48         time_t   start_time;
49         time_t   end_time;
50         gint     count;
51 } track_stats;
52
53 // Early incarnations of the code had facilities to print output for multiple files
54 //  but has been rescoped to work on a single list of tracks for the GUI
55 typedef enum {
56         //TS_TRACK,
57         TS_TRACKS,
58         //TS_FILES,
59 } track_stat_block;
60 static track_stats tracks_stats[1];
61
62 // cf with vik_track_get_minmax_alt internals
63 #define VIK_VAL_MIN_ALT 25000.0
64 #define VIK_VAL_MAX_ALT -5000.0
65
66 /**
67  * Reset the specified block
68  * Call this when starting to processing multiple items
69  */
70 static void val_reset ( track_stat_block block )
71 {
72         tracks_stats[block].min_alt     = VIK_VAL_MIN_ALT;
73         tracks_stats[block].max_alt     = VIK_VAL_MAX_ALT;
74         tracks_stats[block].elev_gain   = 0.0;
75         tracks_stats[block].elev_loss   = 0.0;
76         tracks_stats[block].length      = 0.0;
77         tracks_stats[block].length_gaps = 0.0;
78         tracks_stats[block].max_speed   = 0.0;
79         tracks_stats[block].trackpoints = 0;
80         tracks_stats[block].segments    = 0;
81         tracks_stats[block].duration    = 0;
82         tracks_stats[block].start_time  = 0;
83         tracks_stats[block].end_time    = 0;
84         tracks_stats[block].count       = 0;
85 }
86
87 /**
88  * @val_analyse_track:
89  * @trk: The track to be analyse
90  *
91  * Function to collect statistics, using the internal track functions
92  */
93 static void val_analyse_track ( VikTrack *trk )
94 {
95         //val_reset ( TS_TRACK );
96         gdouble min_alt;
97         gdouble max_alt;
98         gdouble up;
99         gdouble down;
100
101         gdouble  length      = 0.0;
102         gdouble  length_gaps = 0.0;
103         gdouble  max_speed   = 0.0;
104         gulong   trackpoints = 0;
105         guint    segments    = 0;
106
107         tracks_stats[TS_TRACKS].count++;
108
109         trackpoints = vik_track_get_tp_count (trk);
110         segments    = vik_track_get_segment_count (trk);
111         length      = vik_track_get_length (trk);
112         length_gaps = vik_track_get_length_including_gaps (trk);
113         max_speed   = vik_track_get_max_speed (trk);
114
115         int ii;
116         for (ii = 0; ii < G_N_ELEMENTS(tracks_stats); ii++) {
117                 tracks_stats[ii].trackpoints += trackpoints;
118                 tracks_stats[ii].segments    += segments;
119                 tracks_stats[ii].length      += length;
120                 tracks_stats[ii].length_gaps += length_gaps;
121                 if ( max_speed > tracks_stats[ii].max_speed )
122                         tracks_stats[ii].max_speed = max_speed;
123         }
124
125         if ( vik_track_get_minmax_alt (trk, &min_alt, &max_alt) ) {
126                 for (ii = 0; ii < G_N_ELEMENTS(tracks_stats); ii++) {
127                         if ( min_alt < tracks_stats[ii].min_alt )
128                                 tracks_stats[ii].min_alt = min_alt;
129                         if ( max_alt > tracks_stats[ii].max_alt )
130                                 tracks_stats[ii].max_alt = max_alt;
131                 }
132         }
133
134         vik_track_get_total_elevation_gain (trk, &up, &down );
135
136         for (ii = 0; ii < G_N_ELEMENTS(tracks_stats); ii++) {
137                 tracks_stats[ii].elev_gain += up;
138                 tracks_stats[ii].elev_loss += down;
139         }
140
141         if ( trk->trackpoints && VIK_TRACKPOINT(trk->trackpoints->data)->timestamp ) {
142                 time_t t1, t2;
143                 t1 = VIK_TRACKPOINT(g_list_first(trk->trackpoints)->data)->timestamp;
144                 t2 = VIK_TRACKPOINT(g_list_last(trk->trackpoints)->data)->timestamp;
145
146                 // Assume never actually have a track with a time of 0 (1st Jan 1970)
147                 for (ii = 0; ii < G_N_ELEMENTS(tracks_stats); ii++) {
148                         if ( tracks_stats[ii].start_time == 0)
149                                 tracks_stats[ii].start_time = t1;
150                         if ( tracks_stats[ii].end_time == 0)
151                                 tracks_stats[ii].end_time = t2;
152                 }
153
154                 // Initialize to the first value
155                 for (ii = 0; ii < G_N_ELEMENTS(tracks_stats); ii++) {
156                         if (t1 < tracks_stats[ii].start_time)
157                                 tracks_stats[ii].start_time = t1;
158                         if (t2 > tracks_stats[ii].end_time)
159                                 tracks_stats[ii].end_time = t2;
160                 }
161
162                 for (ii = 0; ii < G_N_ELEMENTS(tracks_stats); ii++) {
163                         tracks_stats[ii].duration = tracks_stats[ii].duration + (int)(t2-t1);
164                 }
165         }
166 }
167
168 // Could use GtkGrids but that is Gtk3+
169 static GtkWidget *create_table (int cnt, char *labels[], GtkWidget *contents[])
170 {
171         GtkTable *table;
172         int i;
173
174         table = GTK_TABLE(gtk_table_new (cnt, 2, FALSE));
175         gtk_table_set_col_spacing (table, 0, 10);
176         for (i=0; i<cnt; i++) {
177                 GtkWidget *label;
178                 label = gtk_label_new(NULL);
179                 gtk_misc_set_alignment ( GTK_MISC(label), 1, 0.5 ); // Position text centrally in vertical plane
180                 // All text labels are set to be in bold
181                 char *markup = g_markup_printf_escaped ("<b>%s:</b>", _(labels[i]) );
182                 gtk_label_set_markup ( GTK_LABEL(label), markup );
183                 g_free ( markup );
184                 gtk_table_attach ( table, label, 0, 1, i, i+1, GTK_FILL, GTK_EXPAND, 4, 2 );
185                 if (GTK_IS_MISC(contents[i])) {
186                         gtk_misc_set_alignment ( GTK_MISC(contents[i]), 0, 0.5 );
187                 }
188                 gtk_table_attach_defaults ( table, contents[i], 1, 2, i, i+1 );
189         }
190         return GTK_WIDGET (table);
191 }
192
193 static gchar *label_texts[] = {
194         N_("Number of Tracks"),
195         N_("Date Range"),
196         N_("Total Length"),
197         N_("Average Length"),
198         N_("Max Speed"),
199         N_("Avg. Speed"),
200         N_("Minimum Altitude"),
201         N_("Maximum Altitude"),
202         N_("Total Elevation Gain/Loss"),
203         N_("Avg. Elevation Gain/Loss"),
204         N_("Total Duration"),
205         N_("Avg. Duration"),
206 };
207
208 /**
209  * create_layout:
210  *
211  * Returns a widget to hold the stats information in a table grid layout
212  */
213 static GtkWidget *create_layout ( GtkWidget *content[] )
214 {
215         int cnt = 0;
216         for ( cnt = 0; cnt < G_N_ELEMENTS(label_texts); cnt++ )
217                 content[cnt] = ui_label_new_selectable ( NULL );
218
219         return create_table (cnt, label_texts, content);
220 }
221
222 /**
223  * table_output:
224  *
225  * Update the given widgets table with the values from the track stats
226  */
227 static void table_output ( track_stats ts, GtkWidget *content[] )
228 {
229         int cnt = 0;
230
231         gchar tmp_buf[64];
232         g_snprintf ( tmp_buf, sizeof(tmp_buf), "%d", ts.count );
233         gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
234
235         if ( ts.count == 0 ) {
236                 // Blank all other fields
237                 g_snprintf ( tmp_buf, sizeof(tmp_buf), "--" );
238                 for ( cnt = 1; cnt < G_N_ELEMENTS(label_texts); cnt++ )
239                         gtk_label_set_text ( GTK_LABEL(content[cnt]), tmp_buf );
240                 return;
241         }
242
243         // Check for potential date range
244         // Test if the same day by comparing the date string of the timestamp
245         GDate* gdate_start = g_date_new ();
246         g_date_set_time_t ( gdate_start, ts.start_time );
247         gchar time_start[32];
248         g_date_strftime ( time_start, sizeof(time_start), "%x", gdate_start );
249         g_date_free ( gdate_start );
250
251         GDate* gdate_end = g_date_new ();
252         g_date_set_time_t ( gdate_end, ts.end_time );
253         gchar time_end[32];
254         g_date_strftime ( time_end, sizeof(time_end), "%x", gdate_end );
255         g_date_free ( gdate_end );
256
257         if ( ts.start_time == ts.end_time )
258                 g_snprintf ( tmp_buf, sizeof(tmp_buf), _("No Data") );
259         else if ( strncmp(time_start, time_end, 32) )
260                 g_snprintf ( tmp_buf, sizeof(tmp_buf), "%s --> %s", time_start, time_end );
261         else
262                 g_snprintf ( tmp_buf, sizeof(tmp_buf), "%s", time_start );
263
264         gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
265
266         switch (a_vik_get_units_distance ()) {
267         case VIK_UNITS_DISTANCE_MILES:
268                 g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%.1f miles"), VIK_METERS_TO_MILES(ts.length) );
269                 break;
270         case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
271                 g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%.1f NM"), VIK_METERS_TO_NAUTICAL_MILES(ts.length) );
272                 break;
273         default:
274                 //VIK_UNITS_DISTANCE_KILOMETRES
275                 g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%.1f km"), ts.length/1000.0 );
276                 break;
277         }
278         gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
279
280         switch (a_vik_get_units_distance ()) {
281         case VIK_UNITS_DISTANCE_MILES:
282                 g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%.2f miles"), (VIK_METERS_TO_MILES(ts.length)/ts.count) );
283                 break;
284         case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
285                 g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%.2f NM"), (VIK_METERS_TO_NAUTICAL_MILES(ts.length)/ts.count) );
286                 break;
287         default:
288                 //VIK_UNITS_DISTANCE_KILOMETRES
289                 g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%.2f km"), ts.length/(1000.0*ts.count) );
290                 break;
291         }
292         gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
293
294         // I'm sure this could be cleaner...
295         g_snprintf ( tmp_buf, sizeof(tmp_buf), "--" );
296         switch (a_vik_get_units_speed()) {
297         case VIK_UNITS_SPEED_MILES_PER_HOUR:
298                 if ( ts.max_speed > 0 )
299                         g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%.1f mph"), (double)VIK_MPS_TO_MPH(ts.max_speed) );
300                 gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
301                 if ( ts.duration > 0 )
302                         g_snprintf ( tmp_buf, sizeof(tmp_buf), ("%.1f mph"), (double)VIK_MPS_TO_MPH(ts.length/ts.duration) );
303                 else
304                         g_snprintf ( tmp_buf, sizeof(tmp_buf), "--" );
305                 break;
306         case VIK_UNITS_SPEED_METRES_PER_SECOND:
307                 if ( ts.max_speed > 0 )
308                         g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%.2f m/s"), (double)ts.max_speed );
309                 gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
310                 if ( ts.duration > 0 )
311                         g_snprintf ( tmp_buf, sizeof(tmp_buf), ("%.2f m/s"), (double)(ts.length/ts.duration) );
312                 else
313                         g_snprintf ( tmp_buf, sizeof(tmp_buf), "--" );
314                 break;
315         case VIK_UNITS_SPEED_KNOTS:
316                 if ( ts.max_speed > 0 )
317                         g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%.2f knots\n"), (double)VIK_MPS_TO_KNOTS(ts.max_speed) );
318                 gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
319                 if ( ts.duration > 0 )
320                         g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%.2f knots"), (double)VIK_MPS_TO_KNOTS(ts.length/ts.duration) );
321                 else
322                         g_snprintf ( tmp_buf, sizeof(tmp_buf), "--" );
323                 break;
324         default:
325                 //VIK_UNITS_SPEED_KILOMETRES_PER_HOUR:
326                 if ( ts.max_speed > 0 )
327                         g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%.2f km/h"), (double)VIK_MPS_TO_KPH(ts.max_speed) );
328                 gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
329                 if ( ts.duration > 0 )
330                         g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%.2f km/h"), (double)VIK_MPS_TO_KPH(ts.length/ts.duration) );
331                 else
332                         g_snprintf ( tmp_buf, sizeof(tmp_buf), "--" );
333                 break;
334         }
335         gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
336
337         switch ( a_vik_get_units_height() ) {
338                 // Note always round off height value output since sub unit accuracy is overkill
339         case VIK_UNITS_HEIGHT_FEET:
340                 if ( ts.min_alt != VIK_VAL_MIN_ALT )
341                         g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%d feet"), (int)round(VIK_METERS_TO_FEET(ts.min_alt)) );
342                 else
343                         g_snprintf ( tmp_buf, sizeof(tmp_buf), "--" );
344                 gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
345
346                 if ( ts.max_alt != VIK_VAL_MAX_ALT )
347                         g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%d feet"), (int)round(VIK_METERS_TO_FEET(ts.max_alt)) );
348                 else
349                         g_snprintf ( tmp_buf, sizeof(tmp_buf), "--" );
350                 gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
351
352                 g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%d feet / %d feet"), (int)round(VIK_METERS_TO_FEET(ts.elev_gain)), (int)round(VIK_METERS_TO_FEET(ts.elev_loss)) );
353                 gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
354                 g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%d feet / %d feet"), (int)round(VIK_METERS_TO_FEET(ts.elev_gain/ts.count)), (int)round(VIK_METERS_TO_FEET(ts.elev_loss/ts.count)) );
355                 break;
356         default:
357                 //VIK_UNITS_HEIGHT_METRES
358                 if ( ts.min_alt != VIK_VAL_MIN_ALT )
359                         g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%d m"), (int)round(ts.min_alt) );
360                 else
361                         g_snprintf ( tmp_buf, sizeof(tmp_buf), "--" );
362                 gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
363
364                 if ( ts.max_alt != VIK_VAL_MAX_ALT )
365                         g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%d m"), (int)round(ts.max_alt) );
366                 else
367                         g_snprintf ( tmp_buf, sizeof(tmp_buf), "--" );
368                 gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
369
370                 g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%d m / %d m"), (int)round(ts.elev_gain), (int)round(ts.elev_loss) );
371                 gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
372                 g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%d m / %d m"), (int)round(ts.elev_gain/ts.count), (int)round(ts.elev_loss/ts.count) );
373                 break;
374         }
375         gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
376
377         gint hours;
378         gint minutes;
379         gint days;
380         // Total Duration
381         days    = (gint)(ts.duration / (60*60*24));
382         hours   = (gint)floor((ts.duration - (days*60*60*24)) / (60*60));
383         minutes = (gint)((ts.duration - (days*60*60*24) - (hours*60*60)) / 60);
384         g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%d:%02d:%02d days:hrs:mins"), days, hours, minutes );
385         gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
386
387         // Average Duration
388         gint avg_dur = ts.duration / ts.count;
389         hours   = (gint)floor(avg_dur / (60*60));
390         minutes = (gint)((avg_dur - (hours*60*60)) / 60);
391         g_snprintf ( tmp_buf, sizeof(tmp_buf), _("%d:%02d hrs:mins"), hours, minutes );
392         gtk_label_set_text ( GTK_LABEL(content[cnt++]), tmp_buf );
393 }
394
395 /**
396  * val_analyse_item_maybe:
397  * @vtlist: A track and the associated layer to consider for analysis
398  * @data:   Whether to include invisible items
399  *
400  * Analyse this particular track
401  *  considering whether it should be included depending on it's visibility
402  */
403 static void val_analyse_item_maybe ( vik_trw_track_list_t *vtlist, const gpointer data )
404 {
405         gboolean include_invisible = GPOINTER_TO_INT(data);
406         VikTrack *trk = vtlist->trk;
407         VikTrwLayer *vtl = vtlist->vtl;
408
409         // Safety first - items shouldn't be deleted...
410         if ( !IS_VIK_TRW_LAYER(vtl) ) return;
411         if ( !trk ) return;
412
413         if ( !include_invisible ) {
414                 // Skip invisible layers or sublayers
415                 if ( !VIK_LAYER(vtl)->visible ||
416                          (trk->is_route && !vik_trw_layer_get_routes_visibility(vtl)) ||
417                          (!trk->is_route && !vik_trw_layer_get_tracks_visibility(vtl)) )
418                         return;
419
420                 // Skip invisible tracks
421                 if ( !trk->visible )
422                         return;
423         }
424
425         val_analyse_track ( trk );
426 }
427
428 /**
429  * val_analyse:
430  * @widgets:           The widget layout
431  * @tracks_and_layers: A list of #vik_trw_track_list_t
432  * @include_invisible: Whether to include invisible layers and tracks
433  *
434  * Analyse each item in the @tracks_and_layers list
435  *
436  */
437 void val_analyse ( GtkWidget *widgets[], GList *tracks_and_layers, gboolean include_invisible )
438 {
439         val_reset ( TS_TRACKS );
440
441         GList *gl = g_list_first ( tracks_and_layers );
442         if ( gl ) {
443                 g_list_foreach ( gl, (GFunc) val_analyse_item_maybe, GINT_TO_POINTER(include_invisible) );
444         }
445
446         table_output ( tracks_stats[TS_TRACKS], widgets );
447 }
448
449 typedef struct {
450         GtkWidget **widgets;
451         GtkWidget *layout;
452         GtkWidget *check_button;
453         GList *tracks_and_layers;
454         VikLayer *vl;
455         gpointer user_data;
456         VikTrwlayerGetTracksAndLayersFunc get_tracks_and_layers_cb;
457         VikTrwlayerAnalyseCloseFunc on_close_cb;
458 } analyse_cb_t;
459
460 static void include_invisible_toggled_cb ( GtkToggleButton *togglebutton, analyse_cb_t *acb )
461 {
462         gboolean value = FALSE;
463         if ( gtk_toggle_button_get_active ( togglebutton ) )
464                 value = TRUE;
465
466         // Delete old list of items
467         if ( acb->tracks_and_layers ) {
468                 g_list_foreach ( acb->tracks_and_layers, (GFunc) g_free, NULL );
469                 g_list_free ( acb->tracks_and_layers );
470         }
471
472         // Get the latest list of items to analyse
473         acb->tracks_and_layers = acb->get_tracks_and_layers_cb ( acb->vl, acb->user_data );
474
475         val_analyse ( acb->widgets, acb->tracks_and_layers, value );
476         gtk_widget_show_all ( acb->layout );
477 }
478
479 #define VIK_SETTINGS_ANALYSIS_DO_INVISIBLE "track_analysis_do_invisible"
480
481 /**
482  * analyse_close:
483  *
484  * Multi stage closure - as we need to clear allocations made here
485  *  before passing on to the callee so they know then the dialog is closed too
486  */
487 static void analyse_close ( GtkWidget *dialog, gint resp, analyse_cb_t *data )
488 {
489         // Save current invisible value for next time
490         gboolean do_invisible = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(data->check_button) );
491         a_settings_set_boolean ( VIK_SETTINGS_ANALYSIS_DO_INVISIBLE, do_invisible );
492
493         //g_free ( data->layout );
494         g_free ( data->widgets );
495         g_list_foreach ( data->tracks_and_layers, (GFunc) g_free, NULL );
496         g_list_free ( data->tracks_and_layers );
497
498         if ( data->on_close_cb )
499                 data->on_close_cb ( dialog, resp, data->vl );
500
501         g_free ( data );
502 }
503
504 /**
505  * vik_trw_layer_analyse_this:
506  * @window:                   A window from which the dialog will be derived
507  * @name:                     The name to be shown
508  * @vl:                       The #VikLayer passed on into get_tracks_and_layers_cb()
509  * @user_data:                Data passed on into get_tracks_and_layers_cb()
510  * @get_tracks_and_layers_cb: The function to call to construct items to be analysed
511  *
512  * Display a dialog with stats across many tracks
513  *
514  * Returns: The dialog that is created to display the analyse information
515  */
516 GtkWidget* vik_trw_layer_analyse_this ( GtkWindow *window,
517                                         const gchar *name,
518                                         VikLayer *vl,
519                                         gpointer user_data,
520                                         VikTrwlayerGetTracksAndLayersFunc get_tracks_and_layers_cb,
521                                         VikTrwlayerAnalyseCloseFunc on_close_cb )
522 {
523         //VikWindow *vw = VIK_WINDOW(window);
524
525         GtkWidget *dialog;
526         dialog = gtk_dialog_new_with_buttons ( _("Statistics"),
527                                                window,
528                                                GTK_DIALOG_DESTROY_WITH_PARENT,
529                                                GTK_STOCK_CLOSE,     GTK_RESPONSE_CANCEL,
530                                                NULL );
531
532         GtkWidget *name_l = gtk_label_new ( NULL );
533         gchar *myname = g_markup_printf_escaped ( "<b>%s</b>", name );
534         gtk_label_set_markup ( GTK_LABEL(name_l), myname );
535         g_free ( myname );
536
537         GtkWidget *content = gtk_dialog_get_content_area ( GTK_DIALOG(dialog) );
538         gtk_box_pack_start ( GTK_BOX(content), name_l, FALSE, FALSE, 10);
539
540         // Get previous value (if any) from the settings
541         gboolean include_invisible;
542         if ( ! a_settings_get_boolean ( VIK_SETTINGS_ANALYSIS_DO_INVISIBLE, &include_invisible ) )
543                 include_invisible = TRUE;
544
545         analyse_cb_t *acb = g_malloc (sizeof(analyse_cb_t));
546         acb->vl = vl;
547         acb->user_data = user_data;
548         acb->get_tracks_and_layers_cb = get_tracks_and_layers_cb;
549         acb->on_close_cb = on_close_cb;
550         acb->tracks_and_layers = get_tracks_and_layers_cb ( vl, user_data );
551         acb->widgets = g_malloc ( sizeof(GtkWidget*) * G_N_ELEMENTS(label_texts) );
552         acb->layout = create_layout ( acb->widgets );
553
554         gtk_box_pack_start ( GTK_BOX(content), acb->layout, FALSE, FALSE, 0 );
555
556         // Analysis seems reasonably quick
557         //  unless you have really large numbers of tracks (i.e. many many thousands or a really slow computer)
558         // One day might store stats in the track itself....
559         val_analyse ( acb->widgets, acb->tracks_and_layers, include_invisible );
560         
561         GtkWidget *cb = gtk_check_button_new_with_label ( _("Include Invisible Items") );
562         gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(cb), include_invisible );
563         gtk_box_pack_start ( GTK_BOX(content), cb, FALSE, FALSE, 10);
564         acb->check_button = cb;
565         
566         gtk_widget_show_all ( dialog );
567
568         g_signal_connect ( G_OBJECT(cb), "toggled", G_CALLBACK(include_invisible_toggled_cb), acb );
569         g_signal_connect ( G_OBJECT(dialog), "response", G_CALLBACK(analyse_close), acb );
570
571         return dialog;
572 }