]>
Commit | Line | Data |
---|---|---|
260d6f45 RN |
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 | ||
23a7ae3f RN |
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 | ||
260d6f45 RN |
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); | |
260d6f45 | 294 | gtk_widget_show ( item ); |
985bcc53 RN |
295 | |
296 | item = gtk_menu_item_new_with_mnemonic ( _("_Statistics") ); | |
260d6f45 RN |
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 | ||
23a7ae3f RN |
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 | ||
260d6f45 RN |
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 ) | |
23a7ae3f | 329 | return trw_layer_track_menu_popup_multi ( tree_view, event, data ); |
260d6f45 RN |
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 | ||
ab8c8dbf | 357 | gpointer trkf; |
260d6f45 RN |
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, | |
985bcc53 | 377 | tree_view, |
260d6f45 RN |
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, | |
ffdfa365 RN |
419 | vik_units_height_t height_units, |
420 | const gchar* date_format ) | |
260d6f45 RN |
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; | |
53d7cdd6 RN |
432 | case VIK_UNITS_DISTANCE_NAUTICAL_MILES: |
433 | trk_dist = VIK_METERS_TO_NAUTICAL_MILES(trk_dist); | |
434 | break; | |
260d6f45 RN |
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 ); | |
ffdfa365 | 447 | gchar *time = g_date_time_format ( gdt, date_format ); |
97dd3dee | 448 | g_strlcpy ( time_buf, time, sizeof(time_buf) ); |
260d6f45 RN |
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 ); | |
ffdfa365 | 454 | g_date_strftime ( time_buf, sizeof(time_buf), date_format, gdate_start ); |
260d6f45 RN |
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 | ||
39b57e75 | 463 | guint trk_len_time = 0; // In minutes |
260d6f45 RN |
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; | |
39b57e75 | 468 | trk_len_time = (int)round(labs(t2-t1)/60.0); |
260d6f45 RN |
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, | |
4a287810 | 525 | 8, (gint)round(max_alt), |
260d6f45 RN |
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 ); | |
e7b235ff RN |
536 | gtk_tree_view_column_set_reorderable ( column, TRUE ); |
537 | gtk_tree_view_column_set_resizable ( column, TRUE ); | |
260d6f45 RN |
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 | |
4a287810 | 568 | G_TYPE_INT, // 8: Max Height |
260d6f45 RN |
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 ); | |
ffdfa365 RN |
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 | ||
260d6f45 RN |
584 | GList *gl = tracks_and_layers; |
585 | while ( gl ) { | |
ffdfa365 | 586 | trw_layer_track_list_add ( (vik_trw_track_list_t*)gl->data, store, dist_units, speed_units, height_units, date_format ); |
260d6f45 RN |
587 | gl = g_list_next ( gl ); |
588 | } | |
ffdfa365 | 589 | g_free ( date_format ); |
260d6f45 RN |
590 | |
591 | GtkWidget *view = gtk_tree_view_new(); | |
592 | GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); | |
e7b235ff RN |
593 | g_object_set (G_OBJECT (renderer), |
594 | "xalign", 0.0, | |
595 | "ellipsize", PANGO_ELLIPSIZE_END, | |
596 | NULL); | |
597 | ||
260d6f45 RN |
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++ ); | |
e7b235ff | 605 | gtk_tree_view_column_set_expand ( column, TRUE ); |
260d6f45 RN |
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++ ); | |
e7b235ff | 613 | gtk_tree_view_column_set_expand ( column, TRUE ); |
260d6f45 RN |
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++ ); | |
e7b235ff | 619 | gtk_tree_view_column_set_expand ( column, TRUE ); |
260d6f45 RN |
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 ); | |
e7b235ff | 623 | gtk_tree_view_column_set_reorderable ( column, TRUE ); |
260d6f45 RN |
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; | |
53d7cdd6 RN |
632 | case VIK_UNITS_DISTANCE_NAUTICAL_MILES: |
633 | column = my_new_column_text ( _("Distance\n(NM)"), renderer, view, column_runner++ ); | |
634 | break; | |
260d6f45 RN |
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 | ||
5263679f | 642 | (void)my_new_column_text ( _("Length\n(minutes)"), renderer, view, column_runner++ ); |
260d6f45 RN |
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 ) | |
5263679f | 666 | (void)my_new_column_text ( _("Max Height\n(Feet)"), renderer, view, column_runner++ ); |
260d6f45 | 667 | else |
5263679f | 668 | (void)my_new_column_text ( _("Max Height\n(Metres)"), renderer, view, column_runner++ ); |
260d6f45 RN |
669 | |
670 | gtk_tree_view_set_model ( GTK_TREE_VIEW(view), GTK_TREE_MODEL(store) ); | |
23a7ae3f | 671 | gtk_tree_selection_set_mode ( gtk_tree_view_get_selection(GTK_TREE_VIEW(view)), GTK_SELECTION_MULTIPLE ); |
260d6f45 RN |
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 | |
e7b235ff RN |
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 ); | |
260d6f45 RN |
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 ); | |
e7b235ff RN |
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 ); | |
260d6f45 RN |
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 | } |