]> git.street.me.uk Git - andy/viking.git/blob - src/viktrwlayer_tracklist.c
Fix small memory leak - free dirpath in all circumstances.
[andy/viking.git] / src / viktrwlayer_tracklist.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 #include <math.h>
23 #include <string.h>
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <glib.h>
27 #include <glib/gstdio.h>
28 #include <glib/gi18n.h>
29
30 #include "viking.h"
31 #include "viktrwlayer_tracklist.h"
32 #include "viktrwlayer_propwin.h"
33
34 // Long formatted date+basic time - listing this way ensures the string comparison sort works - so no local type format %x or %c here!
35 #define TRACK_LIST_DATE_FORMAT "%Y-%m-%d %H:%M"
36
37 /**
38  * track_close_cb:
39  *
40  */
41 static void track_close_cb ( GtkWidget *dialog, gint resp, GList *data )
42 {
43         g_list_foreach ( data, (GFunc) g_free, NULL );
44         g_list_free ( data );
45
46         gtk_widget_destroy (dialog);
47 }
48
49 /**
50  * format_1f_cell_data_func:
51  *
52  * General purpose column double formatting
53  *
54  */
55 static void format_1f_cell_data_func ( GtkTreeViewColumn *col,
56                                        GtkCellRenderer   *renderer,
57                                        GtkTreeModel      *model,
58                                        GtkTreeIter       *iter,
59                                        gpointer           user_data )
60 {
61         gdouble value;
62         gchar buf[20];
63         gint column = GPOINTER_TO_INT (user_data);
64         gtk_tree_model_get ( model, iter, column, &value, -1 );
65         g_snprintf ( buf, sizeof(buf), "%.1f", value );
66         g_object_set ( renderer, "text", buf, NULL );
67 }
68
69 #define TRK_LIST_COLS 11
70 #define TRK_COL_NUM TRK_LIST_COLS-1
71 #define TRW_COL_NUM TRK_COL_NUM-1
72
73 /*
74  * trw_layer_track_tooltip_cb:
75  *
76  * Show a tooltip when the mouse is over a track list entry.
77  * The tooltip contains the comment or description.
78  */
79 static gboolean trw_layer_track_tooltip_cb ( GtkWidget  *widget,
80                                              gint        x,
81                                              gint        y,
82                                              gboolean    keyboard_tip,
83                                              GtkTooltip *tooltip,
84                                              gpointer    data )
85 {
86         GtkTreeIter iter;
87         GtkTreePath *path = NULL;
88         GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
89         GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
90
91         if ( !gtk_tree_view_get_tooltip_context ( tree_view, &x, &y,
92                                                   keyboard_tip,
93                                                   &model, &path, &iter ) )
94                 return FALSE;
95
96         VikTrack *trk;
97         gtk_tree_model_get ( model, &iter, TRK_COL_NUM, &trk, -1 );
98         if ( !trk ) return FALSE;
99
100         gboolean tooltip_set = TRUE;
101         if ( trk->comment )
102                 gtk_tooltip_set_text ( tooltip, trk->comment );
103         else if ( trk->description )
104                 gtk_tooltip_set_text ( tooltip, trk->description );
105         else
106                 tooltip_set = FALSE;
107
108         if ( tooltip_set )
109                 gtk_tree_view_set_tooltip_row ( tree_view, tooltip, path );
110
111         gtk_tree_path_free ( path );
112
113         return tooltip_set;
114 }
115
116 /*
117 static void trw_layer_track_select_cb ( GtkTreeSelection *selection, gpointer data )
118 {
119         GtkTreeIter iter;
120         if ( !gtk_tree_selection_get_selected (selection, NULL, &iter) )
121                 return;
122
123         GtkTreeView *tree_view = GTK_TREE_VIEW ( data );
124         GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
125
126         VikTrack *trk;
127         gtk_tree_model_get ( model, &iter, TRK_COL_NUM, &trk, -1 );
128         if ( !trk ) return;
129
130         VikTrwLayer *vtl;
131         gtk_tree_model_get ( model, &iter, TRW_COL_NUM, &vtl, -1 );
132         if ( !IS_VIK_TRW_LAYER(vtl) ) return;
133
134         //vik_treeview_select_iter ( VIK_LAYER(vtl)->vt, g_hash_table_lookup ( vtl->track_iters, uuid ), TRUE );
135 }
136 */
137
138 // A slightly better way of defining the menu callback information
139 // This should be much easier to extend/rework compared to the current trw_layer menus
140 typedef enum {
141   MA_VTL = 0,
142   MA_TRK,
143   MA_TRK_UUID,
144   MA_VVP,
145   MA_TREEVIEW,
146   MA_TRKS_LIST,
147   MA_LAST
148 } menu_array_index;
149
150 typedef gpointer menu_array_values[MA_LAST];
151
152 // Instead of hooking automatically on treeview item selection
153 // This is performed on demand via the specific menu request
154 static void trw_layer_track_select ( menu_array_values values )
155 {
156         VikTrwLayer *vtl = VIK_TRW_LAYER(values[MA_VTL]);
157         VikTrack *trk = VIK_TRACK(values[MA_TRK]);
158
159         if ( values[MA_TRK_UUID] ) {
160                 GtkTreeIter *iter = NULL;
161                 if ( trk->is_route )
162                         iter = g_hash_table_lookup ( vik_trw_layer_get_routes_iters(vtl), values[MA_TRK_UUID] );
163                 else
164                         iter = g_hash_table_lookup ( vik_trw_layer_get_tracks_iters(vtl), values[MA_TRK_UUID] );
165
166                 if ( iter )
167                         vik_treeview_select_iter ( VIK_LAYER(vtl)->vt, iter, TRUE );
168         }
169 }
170
171 static void trw_layer_track_stats ( menu_array_values values )
172 {
173         VikTrwLayer *vtl = VIK_TRW_LAYER(values[MA_VTL]);
174         VikTrack *trk = VIK_TRACK(values[MA_TRK]);
175         VikViewport *vvp = VIK_VIEWPORT(values[MA_VVP]);
176
177         if ( trk && trk->name ) {
178                 // Kill off this dialog to allow interaction with properties window
179                 //  since the properties also allows track manipulations it won't cause conflicts here.
180                 GtkWidget *gw = gtk_widget_get_toplevel ( values[MA_TREEVIEW] );
181                 track_close_cb ( gw, 0, values[MA_TRKS_LIST] );
182
183                 vik_trw_layer_propwin_run ( VIK_GTK_WINDOW_FROM_LAYER(vtl),
184                                             vtl,
185                                             trk,
186                                             NULL, // vlp
187                                             vvp,
188                                             TRUE );
189     }
190 }
191
192 static void trw_layer_track_view ( menu_array_values values )
193 {
194         VikTrwLayer *vtl = VIK_TRW_LAYER(values[MA_VTL]);
195         VikTrack *trk = VIK_TRACK(values[MA_TRK]);
196         VikViewport *vvp = VIK_VIEWPORT(values[MA_VVP]);
197
198         // TODO create common function to convert between LatLon[2] and LatLonBBox or even change LatLonBBox to be 2 LatLons!
199         struct LatLon maxmin[2];
200         maxmin[0].lat = trk->bbox.north;
201         maxmin[1].lat = trk->bbox.south;
202         maxmin[0].lon = trk->bbox.east;
203         maxmin[1].lon = trk->bbox.west;
204
205     trw_layer_zoom_to_show_latlons ( vtl, vvp, maxmin );
206
207         trw_layer_track_select (values);
208 }
209
210 typedef struct {
211   gboolean has_layer_names;
212   GString *str;
213 } copy_data_t;
214
215 static void copy_selection (GtkTreeModel *model,
216                             GtkTreePath *path,
217                             GtkTreeIter *iter,
218                             gpointer data)
219 {
220         copy_data_t *cd = (copy_data_t*) data;
221
222         gchar* layername; gtk_tree_model_get ( model, iter, 0, &layername, -1 );
223         gchar* name; gtk_tree_model_get ( model, iter, 1, &name, -1 );
224         gchar* date; gtk_tree_model_get ( model, iter, 2, &date, -1 );
225         gdouble d1; gtk_tree_model_get ( model, iter, 4, &d1, -1 );
226         guint d2; gtk_tree_model_get ( model, iter, 5, &d2, -1 );
227         gdouble d3; gtk_tree_model_get ( model, iter, 6, &d3, -1 );
228         gdouble d4; gtk_tree_model_get ( model, iter, 7, &d4, -1 );
229         gint d5; gtk_tree_model_get ( model, iter, 8, &d5, -1 );
230         gchar sep = '\t'; // Could make this configurable - but simply always make it a tab character for now
231         // NB Even if the columns have been reordered - this copies it out only in the original default order
232         // if col 0 is displayed then also copy the layername
233         if ( cd->has_layer_names )
234                 g_string_append_printf ( cd->str, "%s%c%s%c%s%c%.1f%c%d%c%.1f%c%.1f%c%d\n", layername, sep, name, sep, date, sep, d1, sep, d2, sep, d3, sep, d4, sep, d5 );
235         else
236                 g_string_append_printf ( cd->str, "%s%c%s%c%.1f%c%d%c%.1f%c%.1f%c%d\n", name, sep, date, sep, d1, sep, d2, sep, d3, sep, d4, sep, d5 );
237         g_free ( layername );
238         g_free ( name );
239         g_free ( date );
240 }
241
242 static void trw_layer_copy_selected ( GtkWidget *tree_view )
243 {
244         GtkTreeSelection *selection = gtk_tree_view_get_selection ( GTK_TREE_VIEW(tree_view) );
245         // NB GTK3 has gtk_tree_view_get_n_columns() but we're GTK2 ATM
246         GList *gl = gtk_tree_view_get_columns ( GTK_TREE_VIEW(tree_view) );
247         guint count = g_list_length ( gl );
248         g_list_free ( gl );
249         copy_data_t cd;
250         cd.has_layer_names = (count > TRK_LIST_COLS-3);
251         // Or use gtk_tree_view_column_get_visible()?
252         cd.str = g_string_new ( NULL );
253         gtk_tree_selection_selected_foreach ( selection, copy_selection, &cd );
254
255         a_clipboard_copy ( VIK_CLIPBOARD_DATA_TEXT, 0, 0, 0, cd.str->str, NULL );
256
257         g_string_free ( cd.str, TRUE );
258 }
259
260 static void add_copy_menu_item ( GtkMenu *menu, GtkWidget *tree_view )
261 {
262         GtkWidget *item = gtk_image_menu_item_new_with_mnemonic ( _("_Copy Data") );
263         gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_COPY, GTK_ICON_SIZE_MENU) );
264         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(trw_layer_copy_selected), tree_view );
265         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
266         gtk_widget_show ( item );
267 }
268
269 static gboolean add_menu_items ( GtkMenu *menu, VikTrwLayer *vtl, VikTrack *trk, gpointer trk_uuid, VikViewport *vvp, GtkWidget *tree_view, gpointer data )
270 {
271         static menu_array_values values;
272         GtkWidget *item;
273
274         values[MA_VTL]       = vtl;
275         values[MA_TRK]       = trk;
276         values[MA_TRK_UUID]  = trk_uuid;
277         values[MA_VVP]       = vvp;
278         values[MA_TREEVIEW]  = tree_view;
279         values[MA_TRKS_LIST] = data;
280
281         /*
282         item = gtk_image_menu_item_new_with_mnemonic ( _("_Select") );
283         gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_FIND, GTK_ICON_SIZE_MENU) );
284         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(trw_layer_track_select), values );
285         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
286         gtk_widget_show ( item );
287         */
288
289         // ATM view auto selects, so don't bother with separate select menu entry
290         item = gtk_image_menu_item_new_with_mnemonic ( _("_View") );
291         gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_ZOOM_FIT, GTK_ICON_SIZE_MENU) );
292         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(trw_layer_track_view), values );
293         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
294         gtk_widget_show ( item );
295
296         item = gtk_menu_item_new_with_mnemonic ( _("_Statistics") );
297         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(trw_layer_track_stats), values );
298         gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item );
299         gtk_widget_show ( item );
300
301         add_copy_menu_item ( menu, tree_view );
302
303         return TRUE;
304 }
305
306 static gboolean trw_layer_track_menu_popup_multi  ( GtkWidget *tree_view,
307                                                     GdkEventButton *event,
308                                                     gpointer data )
309 {
310         GtkWidget *menu = gtk_menu_new();
311
312         add_copy_menu_item ( GTK_MENU(menu), tree_view );
313
314         gtk_menu_popup ( GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, gtk_get_current_event_time() );
315
316         return TRUE;
317 }
318
319 static gboolean trw_layer_track_menu_popup ( GtkWidget *tree_view,
320                                              GdkEventButton *event,
321                                              gpointer data )
322 {
323         static GtkTreeIter iter;
324
325         // Use selected item to get a single iterator ref
326         // This relies on an row being selected as part of the right click
327         GtkTreeSelection *selection = gtk_tree_view_get_selection ( GTK_TREE_VIEW(tree_view) );
328         if ( gtk_tree_selection_count_selected_rows (selection) != 1 )
329                 return trw_layer_track_menu_popup_multi ( tree_view, event, data );
330
331         GtkTreePath *path;
332         GtkTreeModel *model = gtk_tree_view_get_model ( GTK_TREE_VIEW(tree_view) );
333
334         // All this just to get the iter
335         if ( gtk_tree_view_get_path_at_pos ( GTK_TREE_VIEW(tree_view),
336                                              (gint) event->x,
337                                              (gint) event->y,
338                                              &path, NULL, NULL, NULL)) {
339                 gtk_tree_model_get_iter_from_string ( model, &iter, gtk_tree_path_to_string (path) );
340                 gtk_tree_path_free ( path );
341         }
342         else
343                 return FALSE;
344
345         VikTrack *trk;
346         gtk_tree_model_get ( model, &iter, TRK_COL_NUM, &trk, -1 );
347         if ( !trk ) return FALSE;
348
349         VikTrwLayer *vtl;
350         gtk_tree_model_get ( model, &iter, TRW_COL_NUM, &vtl, -1 );
351         if ( !IS_VIK_TRW_LAYER(vtl) ) return FALSE;
352
353         trku_udata udataU;
354         udataU.trk  = trk;
355         udataU.uuid = NULL;
356
357         gpointer trkf;
358         if ( trk->is_route )
359                 trkf = g_hash_table_find ( vik_trw_layer_get_routes(vtl), (GHRFunc) trw_layer_track_find_uuid, &udataU );
360         else
361                 trkf = g_hash_table_find ( vik_trw_layer_get_tracks(vtl), (GHRFunc) trw_layer_track_find_uuid, &udataU );
362
363         if ( trkf && udataU.uuid ) {
364                 VikViewport *vvp = vik_window_viewport((VikWindow *)(VIK_GTK_WINDOW_FROM_LAYER(vtl)));
365
366                 GtkWidget *menu = gtk_menu_new();
367
368                 // Originally started to reuse the trw_layer menu items
369                 //  however these offer too many ways to edit the track data
370                 //  so without an easy way to distinguish read only operations,
371                 //  create a very minimal new set of operations
372                 add_menu_items ( GTK_MENU(menu),
373                                  vtl,
374                                  trk,
375                                  udataU.uuid,
376                                  vvp,
377                                  tree_view,
378                                  data );
379
380                 gtk_menu_popup ( GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, gtk_get_current_event_time() );
381                 return TRUE;
382         }
383         return FALSE;
384 }
385
386 static gboolean trw_layer_track_button_pressed ( GtkWidget *tree_view,
387                                                  GdkEventButton *event,
388                                                  gpointer data )
389 {
390         // Only on right clicks...
391         if ( ! (event->type == GDK_BUTTON_PRESS && event->button == 3) )
392                 return FALSE;
393
394         // ATM Force a selection...
395         GtkTreeSelection *selection = gtk_tree_view_get_selection ( GTK_TREE_VIEW(tree_view) );
396         if ( gtk_tree_selection_count_selected_rows (selection) <= 1 ) {
397                 GtkTreePath *path;
398                 /* Get tree path for row that was clicked */
399                 if ( gtk_tree_view_get_path_at_pos ( GTK_TREE_VIEW(tree_view),
400                                                      (gint) event->x,
401                                                  (gint) event->y,
402                                                      &path, NULL, NULL, NULL)) {
403                         gtk_tree_selection_unselect_all ( selection );
404                         gtk_tree_selection_select_path ( selection, path );
405                         gtk_tree_path_free ( path );
406                 }
407         }
408         return trw_layer_track_menu_popup ( tree_view, event, data );
409 }
410
411 /*
412  * Foreach entry we copy the various individual track properties into the tree store
413  *  formatting & converting the internal values into something for display
414  */
415 static void trw_layer_track_list_add ( vik_trw_track_list_t *vtdl,
416                                        GtkTreeStore *store,
417                                        vik_units_distance_t dist_units,
418                                        vik_units_speed_t speed_units,
419                                        vik_units_height_t height_units,
420                                        const gchar* date_format )
421 {
422         GtkTreeIter t_iter;
423         VikTrack *trk = vtdl->trk;
424         VikTrwLayer *vtl = vtdl->vtl;
425
426         gdouble trk_dist = vik_track_get_length ( trk );
427         // Store unit converted value
428         switch ( dist_units ) {
429         case VIK_UNITS_DISTANCE_MILES:
430                 trk_dist = VIK_METERS_TO_MILES(trk_dist);
431                 break;
432         case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
433                 trk_dist = VIK_METERS_TO_NAUTICAL_MILES(trk_dist);
434                 break;
435         default:
436                 trk_dist = trk_dist/1000.0;
437                 break;
438         }
439
440         // Get start date
441         gchar time_buf[32];
442         time_buf[0] = '\0';
443         if ( trk->trackpoints && VIK_TRACKPOINT(trk->trackpoints->data)->has_timestamp ) {
444
445 #if GLIB_CHECK_VERSION(2,26,0)
446                 GDateTime* gdt = g_date_time_new_from_unix_utc ( VIK_TRACKPOINT(trk->trackpoints->data)->timestamp );
447                 gchar *time = g_date_time_format ( gdt, date_format );
448                 g_strlcpy ( time_buf, time, sizeof(time_buf) );
449                 g_free ( time );
450                 g_date_time_unref ( gdt);
451 #else
452                 GDate* gdate_start = g_date_new ();
453                 g_date_set_time_t ( gdate_start, VIK_TRACKPOINT(trk->trackpoints->data)->timestamp );
454                 g_date_strftime ( time_buf, sizeof(time_buf), date_format, gdate_start );
455                 g_date_free ( gdate_start );
456 #endif
457         }
458
459         // NB: doesn't include aggegrate visibility
460         gboolean visible = VIK_LAYER(vtl)->visible && trk->visible;
461         visible = visible && (trk->is_route ? vik_trw_layer_get_routes_visibility(vtl) : vik_trw_layer_get_tracks_visibility(vtl));
462
463         guint trk_len_time = 0; // In minutes
464         if ( trk->trackpoints ) {
465                 time_t t1, t2;
466                 t1 = VIK_TRACKPOINT(g_list_first(trk->trackpoints)->data)->timestamp;
467                 t2 = VIK_TRACKPOINT(g_list_last(trk->trackpoints)->data)->timestamp;
468                 trk_len_time = (int)round(labs(t2-t1)/60.0);
469         }
470
471         gdouble av_speed = 0.0;
472         gdouble max_speed = 0.0;
473         gdouble max_alt = 0.0;
474
475         av_speed = vik_track_get_average_speed ( trk );
476         switch (speed_units) {
477         case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR: av_speed = VIK_MPS_TO_KPH(av_speed); break;
478         case VIK_UNITS_SPEED_MILES_PER_HOUR:      av_speed = VIK_MPS_TO_MPH(av_speed); break;
479         case VIK_UNITS_SPEED_KNOTS:               av_speed = VIK_MPS_TO_KNOTS(av_speed); break;
480         default: // VIK_UNITS_SPEED_METRES_PER_SECOND therefore no change
481                 break;
482         }
483
484         max_speed = vik_track_get_max_speed ( trk );
485         switch (speed_units) {
486         case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR: max_speed = VIK_MPS_TO_KPH(max_speed); break;
487         case VIK_UNITS_SPEED_MILES_PER_HOUR:      max_speed = VIK_MPS_TO_MPH(max_speed); break;
488         case VIK_UNITS_SPEED_KNOTS:               max_speed = VIK_MPS_TO_KNOTS(max_speed); break;
489         default: // VIK_UNITS_SPEED_METRES_PER_SECOND therefore no change
490                 break;
491         }
492
493         // TODO - make this a function to get min / max values?
494         gdouble *altitudes = NULL;
495         altitudes = vik_track_make_elevation_map ( trk, 500 );
496         if ( altitudes ) {
497                 max_alt = -1000;
498                 guint i;
499                 for ( i=0; i < 500; i++ ) {
500                         if ( altitudes[i] != VIK_DEFAULT_ALTITUDE ) {
501                                 if ( altitudes[i] > max_alt )
502                                         max_alt = altitudes[i];
503                         }
504                 }
505         }
506         g_free ( altitudes );
507
508         switch (height_units) {
509         case VIK_UNITS_HEIGHT_FEET: max_alt = VIK_METERS_TO_FEET(max_alt); break;
510         default:
511                 // VIK_UNITS_HEIGHT_METRES: no need to convert
512                 break;
513         }
514
515         gtk_tree_store_append ( store, &t_iter, NULL );
516         gtk_tree_store_set ( store, &t_iter,
517                              0, VIK_LAYER(vtl)->name,
518                              1, trk->name,
519                              2, time_buf,
520                              3, visible,
521                              4, trk_dist,
522                              5, trk_len_time,
523                              6, av_speed,
524                              7, max_speed,
525                              8, (gint)round(max_alt),
526                              TRW_COL_NUM, vtl,
527                              TRK_COL_NUM, trk,
528                              -1 );
529 }
530
531 static GtkTreeViewColumn *my_new_column_text ( const gchar *title, GtkCellRenderer *renderer, GtkWidget *view, gint column_runner )
532 {
533         GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes ( title, renderer, "text", column_runner, NULL );
534         gtk_tree_view_column_set_sort_column_id ( column, column_runner );
535         gtk_tree_view_append_column ( GTK_TREE_VIEW(view), column );
536         gtk_tree_view_column_set_reorderable ( column, TRUE );
537         gtk_tree_view_column_set_resizable ( column, TRUE );
538         return column;
539 }
540
541 /**
542  * vik_trw_layer_track_list_internal:
543  * @dialog:            The dialog to create the widgets in
544  * @tracks_and_layers: The list of tracks (and it's layer) to be shown
545  * @show_layer_names:  Show the layer names that each track belongs to
546  *
547  * Create a table of tracks with corresponding track information
548  * This table does not support being actively updated
549  */
550 static void vik_trw_layer_track_list_internal ( GtkWidget *dialog,
551                                                 GList *tracks_and_layers,
552                                                 gboolean show_layer_names )
553 {
554         if ( !tracks_and_layers )
555                 return;
556
557         // It's simple storing the gdouble values in the tree store as the sort works automatically
558         // Then apply specific cell data formatting (rather default double is to 6 decimal places!)
559         GtkTreeStore *store = gtk_tree_store_new ( TRK_LIST_COLS,
560                                                    G_TYPE_STRING,    // 0: Layer Name
561                                                    G_TYPE_STRING,    // 1: Track Name
562                                                    G_TYPE_STRING,    // 2: Date
563                                                    G_TYPE_BOOLEAN,   // 3: Visible
564                                                    G_TYPE_DOUBLE,    // 4: Distance
565                                                    G_TYPE_UINT,      // 5: Length in time
566                                                    G_TYPE_DOUBLE,    // 6: Av. Speed
567                                                    G_TYPE_DOUBLE,    // 7: Max Speed
568                                                    G_TYPE_INT,       // 8: Max Height
569                                                    G_TYPE_POINTER,   // 9: TrackWaypoint Layer pointer
570                                                    G_TYPE_POINTER ); // 10: Track pointer
571
572         //gtk_tree_selection_set_select_function ( gtk_tree_view_get_selection (GTK_TREE_VIEW(vt)), vik_treeview_selection_filter, vt, NULL );
573
574         vik_units_distance_t dist_units = a_vik_get_units_distance ();
575         vik_units_speed_t speed_units = a_vik_get_units_speed ();
576         vik_units_height_t height_units = a_vik_get_units_height ();
577
578         //GList *gl = get_tracks_and_layers_cb ( vl, user_data );
579         //g_list_foreach ( tracks_and_layers, (GFunc) trw_layer_track_list_add, store );
580         gchar *date_format = NULL;
581         if ( !a_settings_get_string ( VIK_SETTINGS_LIST_DATE_FORMAT, &date_format ) )
582                 date_format = g_strdup ( TRACK_LIST_DATE_FORMAT );
583
584         GList *gl = tracks_and_layers;
585         while ( gl ) {
586                 trw_layer_track_list_add ( (vik_trw_track_list_t*)gl->data, store, dist_units, speed_units, height_units, date_format );
587                 gl = g_list_next ( gl );
588         }
589         g_free ( date_format );
590
591         GtkWidget *view = gtk_tree_view_new();
592         GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
593         g_object_set (G_OBJECT (renderer),
594                       "xalign", 0.0,
595                       "ellipsize", PANGO_ELLIPSIZE_END,
596                       NULL);
597
598         GtkTreeViewColumn *column;
599         GtkTreeViewColumn *sort_by_column;
600
601         gint column_runner = 0;
602         if ( show_layer_names ) {
603                 // Insert column for the layer name when viewing multi layers
604                 column = my_new_column_text ( _("Layer"), renderer, view, column_runner++ );
605                 gtk_tree_view_column_set_expand ( column, TRUE );
606                 // remember the layer column so we can sort by it later
607                 sort_by_column = column;
608         }
609         else
610                 column_runner++;
611
612         column = my_new_column_text ( _("Name"), renderer, view, column_runner++ );
613         gtk_tree_view_column_set_expand ( column, TRUE );
614         if ( !show_layer_names )
615                 // remember the name column so we can sort by it later
616                 sort_by_column = column;
617
618         column = my_new_column_text ( _("Date"), renderer, view, column_runner++ );
619         gtk_tree_view_column_set_expand ( column, TRUE );
620
621         GtkCellRenderer *renderer_toggle = gtk_cell_renderer_toggle_new ();
622         column = gtk_tree_view_column_new_with_attributes ( _("Visible"), renderer_toggle, "active", column_runner, NULL );
623         gtk_tree_view_column_set_reorderable ( column, TRUE );
624         gtk_tree_view_column_set_sort_column_id ( column, column_runner );
625         gtk_tree_view_append_column ( GTK_TREE_VIEW(view), column );
626         column_runner++;
627
628         switch ( dist_units ) {
629         case VIK_UNITS_DISTANCE_MILES:
630                 column = my_new_column_text ( _("Distance\n(miles)"), renderer, view, column_runner++ );
631                 break;
632         case VIK_UNITS_DISTANCE_NAUTICAL_MILES:
633                 column = my_new_column_text ( _("Distance\n(NM)"), renderer, view, column_runner++ );
634                 break;
635         default:
636                 column = my_new_column_text ( _("Distance\n(km)"), renderer, view, column_runner++ );
637                 break;
638         }
639         // Apply own formatting of the data
640         gtk_tree_view_column_set_cell_data_func ( column, renderer, format_1f_cell_data_func, GINT_TO_POINTER(column_runner-1), NULL);
641
642         (void)my_new_column_text ( _("Length\n(minutes)"), renderer, view, column_runner++ );
643
644         gchar *spd_units = NULL;
645         switch (speed_units) {
646         case VIK_UNITS_SPEED_KILOMETRES_PER_HOUR: spd_units = g_strdup (_("km/h")); break;
647         case VIK_UNITS_SPEED_MILES_PER_HOUR:      spd_units = g_strdup (_("mph")); break;
648         case VIK_UNITS_SPEED_KNOTS:               spd_units = g_strdup (_("knots")); break;
649         // VIK_UNITS_SPEED_METRES_PER_SECOND:
650         default:                                  spd_units = g_strdup (_("m/s")); break;
651         }
652
653         gchar *title = g_strdup_printf ( _("Av. Speed\n(%s)"), spd_units );
654         column = my_new_column_text ( title, renderer, view, column_runner++ );
655         g_free ( title );
656         gtk_tree_view_column_set_cell_data_func ( column, renderer, format_1f_cell_data_func, GINT_TO_POINTER(column_runner-1), NULL); // Apply own formatting of the data
657
658         title = g_strdup_printf ( _("Max Speed\n(%s)"), spd_units );
659         column = my_new_column_text ( title, renderer, view, column_runner++ );
660         gtk_tree_view_column_set_cell_data_func ( column, renderer, format_1f_cell_data_func, GINT_TO_POINTER(column_runner-1), NULL); // Apply own formatting of the data
661
662         g_free ( title );
663         g_free ( spd_units );
664
665         if ( height_units == VIK_UNITS_HEIGHT_FEET )
666                 (void)my_new_column_text ( _("Max Height\n(Feet)"), renderer, view, column_runner++ );
667         else
668                 (void)my_new_column_text ( _("Max Height\n(Metres)"), renderer, view, column_runner++ );
669
670         gtk_tree_view_set_model ( GTK_TREE_VIEW(view), GTK_TREE_MODEL(store) );
671         gtk_tree_selection_set_mode ( gtk_tree_view_get_selection(GTK_TREE_VIEW(view)), GTK_SELECTION_MULTIPLE );
672         gtk_tree_view_set_rules_hint ( GTK_TREE_VIEW(view), TRUE );
673
674         g_object_unref(store);
675
676         GtkWidget *scrolledwindow = gtk_scrolled_window_new ( NULL, NULL );
677         gtk_scrolled_window_set_policy ( GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC );
678         gtk_container_add ( GTK_CONTAINER(scrolledwindow), view );
679
680         g_object_set ( view, "has-tooltip", TRUE, NULL);
681
682         g_signal_connect ( view, "query-tooltip", G_CALLBACK (trw_layer_track_tooltip_cb), NULL );
683         //g_signal_connect ( gtk_tree_view_get_selection (GTK_TREE_VIEW(view)), "changed", G_CALLBACK(trw_layer_track_select_cb), view );
684
685         g_signal_connect ( view, "popup-menu", G_CALLBACK(trw_layer_track_menu_popup), tracks_and_layers );
686         g_signal_connect ( view, "button-press-event", G_CALLBACK(trw_layer_track_button_pressed), tracks_and_layers );
687
688         gtk_box_pack_start (GTK_BOX(GTK_DIALOG(dialog)->vbox), scrolledwindow, TRUE, TRUE, 0);
689
690         // Set ordering of the initial view by one of the name columns
691         gtk_tree_view_column_clicked ( sort_by_column );
692
693         // Ensure a reasonable number of items are shown
694         //  TODO: may be save window size, column order, sorted by between invocations.
695         // Gtk too stupid to work out best size so need to tell it.
696         gtk_window_set_default_size ( GTK_WINDOW(dialog), show_layer_names ? 900 : 700, 400 );
697 }
698
699
700 /**
701  * vik_trw_layer_track_list_show_dialog:
702  * @title:                    The title for the dialog
703  * @vl:                       The #VikLayer passed on into get_tracks_and_layers_cb()
704  * @user_data:                Data passed on into get_tracks_and_layers_cb()
705  * @get_tracks_and_layers_cb: The function to call to construct items to be analysed
706  * @show_layer_names:         Normally only set when called from an aggregate level
707  *
708  * Common method for showing a list of tracks with extended information
709  *
710  */
711 void vik_trw_layer_track_list_show_dialog ( gchar *title,
712                                             VikLayer *vl,
713                                             gpointer user_data,
714                                             VikTrwlayerGetTracksAndLayersFunc get_tracks_and_layers_cb,
715                                             gboolean show_layer_names )
716 {
717         GtkWidget *dialog = gtk_dialog_new_with_buttons ( title,
718                                                           VIK_GTK_WINDOW_FROM_LAYER(vl),
719                                                           GTK_DIALOG_DESTROY_WITH_PARENT,
720                                                           GTK_STOCK_CLOSE,
721                                                           GTK_RESPONSE_CLOSE,
722                                                           NULL );
723
724         GList *gl = get_tracks_and_layers_cb ( vl, user_data );
725
726         vik_trw_layer_track_list_internal ( dialog, gl, show_layer_names );
727
728         // Use response to close the dialog with tidy up
729         g_signal_connect ( G_OBJECT(dialog), "response", G_CALLBACK(track_close_cb), gl );
730
731         gtk_widget_show_all ( dialog );
732         // Yes - set the size *AGAIN* - this time widgets are expanded nicely
733         gtk_window_resize ( GTK_WINDOW(dialog), show_layer_names ? 1000 : 800, 400 );
734
735         // ATM lock out on dialog run - to prevent list contents being manipulated in other parts of the GUI whilst shown here.
736         gtk_dialog_run (GTK_DIALOG (dialog));
737         // Unfortunately seems subsequently opening the Track Properties we can't interact with it until this dialog is closed
738         // Thus this dialog is then forcibly closed when opening the properties.
739
740         // Occassionally the 'View' doesn't update the viewport properly
741         //  viewport center + zoom is changed but the viewport isn't updated
742         // not sure why yet..
743 }