]> git.tdb.fi Git - ext/subsurface.git/blob - divelist.c
Fix crash when editing weight system info
[ext/subsurface.git] / divelist.c
1 /* divelist.c */
2 /* this creates the UI for the dive list -
3  * controlled through the following interfaces:
4  *
5  * void flush_divelist(struct dive *dive)
6  * GtkWidget dive_list_create(void)
7  * void dive_list_update_dives(void)
8  * void update_dive_list_units(void)
9  * void set_divelist_font(const char *font)
10  * void mark_divelist_changed(int changed)
11  * int unsaved_changes()
12  */
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <time.h>
17 #include <math.h>
18
19 #include "divelist.h"
20 #include "dive.h"
21 #include "display.h"
22 #include "display-gtk.h"
23
24 struct DiveList {
25         GtkWidget    *tree_view;
26         GtkWidget    *container_widget;
27         GtkTreeStore *model, *listmodel, *treemodel;
28         GtkTreeViewColumn *nr, *date, *stars, *depth, *duration, *location;
29         GtkTreeViewColumn *temperature, *cylinder, *totalweight, *suit, *nitrox, *sac, *otu;
30         int changed;
31 };
32
33 static struct DiveList dive_list;
34
35 /*
36  * The dive list has the dive data in both string format (for showing)
37  * and in "raw" format (for sorting purposes)
38  */
39 enum {
40         DIVE_INDEX = 0,
41         DIVE_NR,                /* int: dive->nr */
42         DIVE_DATE,              /* time_t: dive->when */
43         DIVE_RATING,            /* int: 0-5 stars */
44         DIVE_DEPTH,             /* int: dive->maxdepth in mm */
45         DIVE_DURATION,          /* int: in seconds */
46         DIVE_TEMPERATURE,       /* int: in mkelvin */
47         DIVE_TOTALWEIGHT,       /* int: in grams */
48         DIVE_SUIT,              /* "wet, 3mm" */
49         DIVE_CYLINDER,
50         DIVE_NITROX,            /* int: dummy */
51         DIVE_SAC,               /* int: in ml/min */
52         DIVE_OTU,               /* int: in OTUs */
53         DIVE_LOCATION,          /* "2nd Cathedral, Lanai" */
54         DIVELIST_COLUMNS
55 };
56
57 /* magic numbers that indicate (as negative values) model entries that
58  * are summary entries for a divetrip */
59 #define NEW_TRIP 1
60
61 #ifdef DEBUG_MODEL
62 static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path,
63                                 GtkTreeIter *iter, gpointer data)
64 {
65         char *location;
66         int idx, nr, rating, depth;
67
68         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, DIVE_RATING, &rating, DIVE_DEPTH, &depth, DIVE_LOCATION, &location, -1);
69         printf("entry #%d : nr %d rating %d depth %d location %s \n", idx, nr, rating, depth, location);
70         free(location);
71
72         return FALSE;
73 }
74
75 static void dump_model(GtkListStore *store)
76 {
77         gtk_tree_model_foreach(GTK_TREE_MODEL(store), dump_model_entry, NULL);
78 }
79 #endif
80
81 static GList *selected_dives;
82 static int st_size = 0;
83
84 gboolean is_in_st(int idx, int *atpos)
85 {
86         int i;
87
88         for (i = 0; i < amount_selected; i++)
89                 if (selectiontracker[i] == idx) {
90                         if (atpos)
91                                 *atpos = i;
92                         return TRUE;
93                 }
94         return FALSE;
95 }
96
97 #if DEBUG_SELECTION_TRACKING
98 void dump_selection(void)
99 {
100         int i;
101
102         printf("currently selected are ");
103         for (i = 0; i < amount_selected; i++)
104                 printf("%d ", selectiontracker[i]);
105         printf("\n");
106 }
107 #endif
108
109 void track_select(int idx)
110 {
111         if (idx < 0)
112                 return;
113
114 #if DEBUG_SELECTION_TRACKING
115         printf("add %d to selection of %d entries\n", idx, amount_selected);
116 #endif
117         if (is_in_st(idx, NULL))
118                 return;
119         if (amount_selected >= st_size) {
120                 selectiontracker = realloc(selectiontracker, dive_table.nr * sizeof(int));
121                 st_size = dive_table.nr;
122         }
123         selectiontracker[amount_selected] = idx;
124         amount_selected++;
125         if (amount_selected == 1)
126                 selected_dive = idx;
127 #if DEBUG_SELECTION_TRACKING
128         printf("increased amount_selected to %d\n", amount_selected);
129         dump_selection();
130 #endif
131 }
132
133 void track_unselect(int idx)
134 {
135         if (idx < 0)
136                 return;
137
138 #if DEBUG_SELECTION_TRACKING
139         printf("remove %d from selection of %d entries\n", idx, amount_selected);
140 #endif
141         int atpos;
142
143         if (! is_in_st(idx, &atpos))
144                 return;
145         memmove(selectiontracker + atpos,
146                 selectiontracker + atpos + 1,
147                 (amount_selected - atpos - 1) * sizeof(int));
148         amount_selected--;
149 #if DEBUG_SELECTION_TRACKING
150         printf("removed %d at pos %d and decreased amount_selected to %d\n", idx, atpos, amount_selected);
151         dump_selection();
152 #endif
153 }
154
155 void clear_tracker(void)
156 {
157         amount_selected = 0;
158 }
159
160 /* when subsurface starts we want to have the last dive selected. So we simply
161    walk to the first leaf (and skip the summary entries - which have negative
162    DIVE_INDEX) */
163 static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx)
164 {
165         GtkTreeIter parent;
166         GtkTreePath *tpath;
167
168         while (*diveidx < 0) {
169                 memcpy(&parent, iter, sizeof(parent));
170                 tpath = gtk_tree_model_get_path(model, &parent);
171                 if (!gtk_tree_model_iter_children(model, iter, &parent))
172                         /* we should never have a parent without child */
173                         return;
174                 if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath))
175                         gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
176                 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, DIVE_INDEX, diveidx, -1);
177         }
178 }
179
180 /* if we click on a summary dive, we actually want to select / unselect
181    all the dives "below" it */
182 static void select_children(GtkTreeModel *model, GtkTreeSelection * selection,
183                         GtkTreeIter *iter, gboolean was_selected)
184 {
185         int i, nr_children;
186         gboolean expanded = FALSE;
187         GtkTreeIter parent;
188         GtkTreePath *tpath;
189
190         memcpy(&parent, iter, sizeof(parent));
191
192         tpath = gtk_tree_model_get_path(model, &parent);
193         expanded = gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath);
194         nr_children = gtk_tree_model_iter_n_children(model, &parent);
195         for (i = 0; i < nr_children; i++) {
196                 gtk_tree_model_iter_nth_child(model, iter, &parent, i);
197
198                 /* if the parent is expanded, just (un)select the children and we'll
199                    track their selection status in the callback
200                    otherwise  just change the selection status directly without
201                    bothering gtk */
202                 if (expanded) {
203                         if (was_selected)
204                                 gtk_tree_selection_unselect_iter(selection, iter);
205                         else
206                                 gtk_tree_selection_select_iter(selection, iter);
207                 } else {
208                         int idx;
209                         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
210                         if (was_selected)
211                                 track_unselect(idx);
212                         else
213                                 track_select(idx);
214                 }
215         }
216 }
217
218 /* make sure that if we expand a summary row that is selected, the children show
219    up as selected, too */
220 void row_expanded_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data)
221 {
222         GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
223
224         if (gtk_tree_selection_path_is_selected(selection, path))
225                 select_children(GTK_TREE_MODEL(dive_list.model), selection, iter, FALSE);
226 }
227
228 /* this is called _before_ the selection is changed, for every single entry;
229  * we simply have it call down the tree to make sure that summary items toggle
230  * their children */
231 gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model,
232                         GtkTreePath *path, gboolean was_selected, gpointer userdata)
233 {
234         GtkTreeIter iter;
235         int dive_idx;
236
237         /* if gtk thinks nothing is selected we should clear out our
238            tracker as well - otherwise hidden selected rows can stay
239            "stuck". The down side is that we now have a different bug:
240            If you select a dive, collapse the dive trip and ctrl-click
241            another dive trip, the initial dive is no longer selected.
242            Just don't do that, ok? */
243         if (gtk_tree_selection_count_selected_rows(selection) == 0)
244                 clear_tracker();
245
246         if (gtk_tree_model_get_iter(model, &iter, path)) {
247                 gtk_tree_model_get(model, &iter, DIVE_INDEX, &dive_idx, -1);
248                 /* turns out we need to move the selectiontracker here */
249
250 #if DEBUG_SELECTION_TRACKING
251                 printf("modify_selection_cb with idx %d (according to gtk was %sselected)\n",
252                         dive_idx, was_selected ? "" : "un");
253 #endif
254                 if (dive_idx >= 0) {
255                         if (was_selected)
256                                 track_unselect(dive_idx);
257                         else
258                                 track_select(dive_idx);
259                 } else {
260                         select_children(model, selection, &iter, was_selected);
261                 }
262         }
263         /* allow this selection to proceed */
264         return TRUE;
265 }
266
267 /* this is called when gtk thinks that the selection has changed */
268 static void selection_cb(GtkTreeSelection *selection, gpointer userdata)
269 {
270         process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model));
271         repaint_dive();
272 }
273
274 const char *star_strings[] = {
275         ZERO_STARS,
276         ONE_STARS,
277         TWO_STARS,
278         THREE_STARS,
279         FOUR_STARS,
280         FIVE_STARS
281 };
282
283 static void star_data_func(GtkTreeViewColumn *col,
284                            GtkCellRenderer *renderer,
285                            GtkTreeModel *model,
286                            GtkTreeIter *iter,
287                            gpointer data)
288 {
289         int nr_stars, idx;
290         char buffer[40];
291
292         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_RATING, &nr_stars, -1);
293         if (idx < 0) {
294                 *buffer = '\0';
295         } else {
296                 if (nr_stars < 0 || nr_stars > 5)
297                         nr_stars = 0;
298                 snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]);
299         }
300         g_object_set(renderer, "text", buffer, NULL);
301 }
302
303 static void date_data_func(GtkTreeViewColumn *col,
304                            GtkCellRenderer *renderer,
305                            GtkTreeModel *model,
306                            GtkTreeIter *iter,
307                            gpointer data)
308 {
309         int val, idx;
310         struct tm *tm;
311         time_t when;
312         char buffer[40];
313
314         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &val, -1);
315
316         /* 2038 problem */
317         when = val;
318
319         tm = gmtime(&when);
320         switch(idx) {
321         case -NEW_TRIP:
322                 snprintf(buffer, sizeof(buffer),
323                         "Trip %s, %s %d, %d",
324                         weekday(tm->tm_wday),
325                         monthname(tm->tm_mon),
326                         tm->tm_mday, tm->tm_year + 1900);
327                 break;
328         default:
329                 snprintf(buffer, sizeof(buffer),
330                         "%s, %s %d, %d %02d:%02d",
331                         weekday(tm->tm_wday),
332                         monthname(tm->tm_mon),
333                         tm->tm_mday, tm->tm_year + 1900,
334                         tm->tm_hour, tm->tm_min);
335         }
336         g_object_set(renderer, "text", buffer, NULL);
337 }
338
339 static void depth_data_func(GtkTreeViewColumn *col,
340                             GtkCellRenderer *renderer,
341                             GtkTreeModel *model,
342                             GtkTreeIter *iter,
343                             gpointer data)
344 {
345         int depth, integer, frac, len, idx;
346         char buffer[40];
347
348         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1);
349
350         if (idx < 0) {
351                 *buffer = '\0';
352         } else {
353                 switch (output_units.length) {
354                 case METERS:
355                         /* To tenths of meters */
356                         depth = (depth + 49) / 100;
357                         integer = depth / 10;
358                         frac = depth % 10;
359                         if (integer < 20)
360                                 break;
361                         if (frac >= 5)
362                                 integer++;
363                         frac = -1;
364                         break;
365                 case FEET:
366                         integer = mm_to_feet(depth) + 0.5;
367                         frac = -1;
368                         break;
369                 default:
370                         return;
371                 }
372                 len = snprintf(buffer, sizeof(buffer), "%d", integer);
373                 if (frac >= 0)
374                         len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
375         }
376         g_object_set(renderer, "text", buffer, NULL);
377 }
378
379 static void duration_data_func(GtkTreeViewColumn *col,
380                                GtkCellRenderer *renderer,
381                                GtkTreeModel *model,
382                                GtkTreeIter *iter,
383                                gpointer data)
384 {
385         unsigned int sec;
386         int idx;
387         char buffer[16];
388
389         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1);
390         if (idx < 0)
391                 *buffer = '\0';
392         else
393                 snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
394
395         g_object_set(renderer, "text", buffer, NULL);
396 }
397
398 static void temperature_data_func(GtkTreeViewColumn *col,
399                                   GtkCellRenderer *renderer,
400                                   GtkTreeModel *model,
401                                   GtkTreeIter *iter,
402                                   gpointer data)
403 {
404         int value, idx;
405         char buffer[80];
406
407         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1);
408
409         *buffer = 0;
410         if (idx >= 0 && value) {
411                 double deg;
412                 switch (output_units.temperature) {
413                 case CELSIUS:
414                         deg = mkelvin_to_C(value);
415                         break;
416                 case FAHRENHEIT:
417                         deg = mkelvin_to_F(value);
418                         break;
419                 default:
420                         return;
421                 }
422                 snprintf(buffer, sizeof(buffer), "%.1f", deg);
423         }
424
425         g_object_set(renderer, "text", buffer, NULL);
426 }
427
428 static void nr_data_func(GtkTreeViewColumn *col,
429                            GtkCellRenderer *renderer,
430                            GtkTreeModel *model,
431                            GtkTreeIter *iter,
432                            gpointer data)
433 {
434         int idx, nr;
435         char buffer[40];
436
437         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1);
438         if (idx < 0)
439                 *buffer = '\0';
440         else
441                 snprintf(buffer, sizeof(buffer), "%d", nr);
442         g_object_set(renderer, "text", buffer, NULL);
443 }
444
445 /*
446  * Get "maximal" dive gas for a dive.
447  * Rules:
448  *  - Trimix trumps nitrox (highest He wins, O2 breaks ties)
449  *  - Nitrox trumps air (even if hypoxic)
450  * These are the same rules as the inter-dive sorting rules.
451  */
452 static void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
453 {
454         int i;
455         int maxo2 = -1, maxhe = -1, mino2 = 1000;
456
457         for (i = 0; i < MAX_CYLINDERS; i++) {
458                 cylinder_t *cyl = dive->cylinder + i;
459                 struct gasmix *mix = &cyl->gasmix;
460                 int o2 = mix->o2.permille;
461                 int he = mix->he.permille;
462
463                 if (cylinder_none(cyl))
464                         continue;
465                 if (!o2)
466                         o2 = AIR_PERMILLE;
467                 if (o2 < mino2)
468                         mino2 = o2;
469                 if (he > maxhe)
470                         goto newmax;
471                 if (he < maxhe)
472                         continue;
473                 if (o2 <= maxo2)
474                         continue;
475 newmax:
476                 maxhe = he;
477                 maxo2 = o2;
478         }
479         /* All air? Show/sort as "air"/zero */
480         if (!maxhe && maxo2 == AIR_PERMILLE && mino2 == maxo2)
481                 maxo2 = mino2 = 0;
482         *o2_p = maxo2;
483         *he_p = maxhe;
484         *o2low_p = mino2;
485 }
486
487 static int total_weight(struct dive *dive)
488 {
489         int i, total_grams = 0;
490
491         if (dive)
492                 for (i=0; i< MAX_WEIGHTSYSTEMS; i++)
493                         total_grams += dive->weightsystem[i].weight.grams;
494         return total_grams;
495 }
496
497 static void weight_data_func(GtkTreeViewColumn *col,
498                              GtkCellRenderer *renderer,
499                              GtkTreeModel *model,
500                              GtkTreeIter *iter,
501                              gpointer data)
502 {
503         int indx, decimals;
504         double value;
505         char buffer[80];
506         struct dive *dive;
507
508         gtk_tree_model_get(model, iter, DIVE_INDEX, &indx, -1);
509         dive = get_dive(indx);
510         value = get_weight_units(total_weight(dive), &decimals, NULL);
511         if (value == 0.0)
512                 *buffer = '\0';
513         else
514                 snprintf(buffer, sizeof(buffer), "%.*f", decimals, value);
515
516         g_object_set(renderer, "text", buffer, NULL);
517 }
518
519 static gint nitrox_sort_func(GtkTreeModel *model,
520         GtkTreeIter *iter_a,
521         GtkTreeIter *iter_b,
522         gpointer user_data)
523 {
524         int index_a, index_b;
525         struct dive *a, *b;
526         int a_o2, b_o2;
527         int a_he, b_he;
528         int a_o2low, b_o2low;
529
530         gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1);
531         gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1);
532         a = get_dive(index_a);
533         b = get_dive(index_b);
534         get_dive_gas(a, &a_o2, &a_he, &a_o2low);
535         get_dive_gas(b, &b_o2, &b_he, &b_o2low);
536
537         /* Sort by Helium first, O2 second */
538         if (a_he == b_he) {
539                 if (a_o2 == b_o2)
540                         return a_o2low - b_o2low;
541                 return a_o2 - b_o2;
542         }
543         return a_he - b_he;
544 }
545
546 #define UTF8_ELLIPSIS "\xE2\x80\xA6"
547
548 static void nitrox_data_func(GtkTreeViewColumn *col,
549                              GtkCellRenderer *renderer,
550                              GtkTreeModel *model,
551                              GtkTreeIter *iter,
552                              gpointer data)
553 {
554         int idx, o2, he, o2low;
555         char buffer[80];
556         struct dive *dive;
557
558         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
559         if (idx < 0) {
560                 *buffer = '\0';
561                 goto exit;
562         }
563         dive = get_dive(idx);
564         get_dive_gas(dive, &o2, &he, &o2low);
565         o2 = (o2 + 5) / 10;
566         he = (he + 5) / 10;
567         o2low = (o2low + 5) / 10;
568
569         if (he)
570                 snprintf(buffer, sizeof(buffer), "%d/%d", o2, he);
571         else if (o2)
572                 if (o2 == o2low)
573                         snprintf(buffer, sizeof(buffer), "%d", o2);
574                 else
575                         snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
576         else
577                 strcpy(buffer, "air");
578 exit:
579         g_object_set(renderer, "text", buffer, NULL);
580 }
581
582 /* Render the SAC data (integer value of "ml / min") */
583 static void sac_data_func(GtkTreeViewColumn *col,
584                           GtkCellRenderer *renderer,
585                           GtkTreeModel *model,
586                           GtkTreeIter *iter,
587                           gpointer data)
588 {
589         int value, idx;
590         const char *fmt;
591         char buffer[16];
592         double sac;
593
594         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1);
595
596         if (idx < 0 || !value) {
597                 *buffer = '\0';
598                 goto exit;
599         }
600
601         sac = value / 1000.0;
602         switch (output_units.volume) {
603         case LITER:
604                 fmt = "%4.1f";
605                 break;
606         case CUFT:
607                 fmt = "%4.2f";
608                 sac = ml_to_cuft(sac * 1000);
609                 break;
610         }
611         snprintf(buffer, sizeof(buffer), fmt, sac);
612 exit:
613         g_object_set(renderer, "text", buffer, NULL);
614 }
615
616 /* Render the OTU data (integer value of "OTU") */
617 static void otu_data_func(GtkTreeViewColumn *col,
618                           GtkCellRenderer *renderer,
619                           GtkTreeModel *model,
620                           GtkTreeIter *iter,
621                           gpointer data)
622 {
623         int value, idx;
624         char buffer[16];
625
626         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1);
627
628         if (idx < 0 || !value)
629                 *buffer = '\0';
630         else
631                 snprintf(buffer, sizeof(buffer), "%d", value);
632
633         g_object_set(renderer, "text", buffer, NULL);
634 }
635
636 /* calculate OTU for a dive */
637 static int calculate_otu(struct dive *dive)
638 {
639         int i;
640         double otu = 0.0;
641
642         for (i = 1; i < dive->samples; i++) {
643                 int t;
644                 double po2;
645                 struct sample *sample = dive->sample + i;
646                 struct sample *psample = sample - 1;
647                 t = sample->time.seconds - psample->time.seconds;
648                 int o2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille;
649                 if (!o2)
650                         o2 = AIR_PERMILLE;
651                 po2 = o2 / 1000.0 * (sample->depth.mm + 10000) / 10000.0;
652                 if (po2 >= 0.5)
653                         otu += pow(po2 - 0.5, 0.83) * t / 30.0;
654         }
655         return otu + 0.5;
656 }
657 /*
658  * Return air usage (in liters).
659  */
660 static double calculate_airuse(struct dive *dive)
661 {
662         double airuse = 0;
663         int i;
664
665         for (i = 0; i < MAX_CYLINDERS; i++) {
666                 pressure_t start, end;
667                 cylinder_t *cyl = dive->cylinder + i;
668                 int size = cyl->type.size.mliter;
669                 double kilo_atm;
670
671                 if (!size)
672                         continue;
673
674                 start = cyl->start.mbar ? cyl->start : cyl->sample_start;
675                 end = cyl->end.mbar ? cyl->end : cyl->sample_end;
676                 kilo_atm = (to_ATM(start) - to_ATM(end)) / 1000.0;
677
678                 /* Liters of air at 1 atm == milliliters at 1k atm*/
679                 airuse += kilo_atm * size;
680         }
681         return airuse;
682 }
683
684 static int calculate_sac(struct dive *dive)
685 {
686         double airuse, pressure, sac;
687         int duration, i;
688
689         airuse = calculate_airuse(dive);
690         if (!airuse)
691                 return 0;
692         if (!dive->duration.seconds)
693                 return 0;
694
695         /* find and eliminate long surface intervals */
696         duration = dive->duration.seconds;
697         for (i = 0; i < dive->samples; i++) {
698                 if (dive->sample[i].depth.mm < 100) { /* less than 10cm */
699                         int end = i + 1;
700                         while (end < dive->samples && dive->sample[end].depth.mm < 100)
701                                 end++;
702                         /* we only want the actual surface time during a dive */
703                         if (end < dive->samples) {
704                                 end--;
705                                 duration -= dive->sample[end].time.seconds -
706                                                 dive->sample[i].time.seconds;
707                                 i = end + 1;
708                         }
709                 }
710         }
711         /* Mean pressure in atm: 1 atm per 10m */
712         pressure = 1 + (dive->meandepth.mm / 10000.0);
713         sac = airuse / pressure * 60 / duration;
714
715         /* milliliters per minute.. */
716         return sac * 1000;
717 }
718
719 void update_cylinder_related_info(struct dive *dive)
720 {
721         if (dive != NULL) {
722                 dive->sac = calculate_sac(dive);
723                 dive->otu = calculate_otu(dive);
724         }
725 }
726
727 static void get_string(char **str, const char *s)
728 {
729         int len;
730         char *n;
731
732         if (!s)
733                 s = "";
734         len = strlen(s);
735         if (len > 60)
736                 len = 60;
737         n = malloc(len+1);
738         memcpy(n, s, len);
739         n[len] = 0;
740         *str = n;
741 }
742
743 static void get_location(struct dive *dive, char **str)
744 {
745         get_string(str, dive->location);
746 }
747
748 static void get_cylinder(struct dive *dive, char **str)
749 {
750         get_string(str, dive->cylinder[0].type.description);
751 }
752
753 static void get_suit(struct dive *dive, char **str)
754 {
755         get_string(str, dive->suit);
756 }
757
758 /*
759  * Set up anything that could have changed due to editing
760  * of dive information; we need to do this for both models,
761  * so we simply call set_one_dive again with the non-current model
762  */
763 /* forward declaration for recursion */
764 static gboolean set_one_dive(GtkTreeModel *model,
765                              GtkTreePath *path,
766                              GtkTreeIter *iter,
767                              gpointer data);
768
769 static void fill_one_dive(struct dive *dive,
770                           GtkTreeModel *model,
771                           GtkTreeIter *iter)
772 {
773         char *location, *cylinder, *suit;
774         GtkTreeStore *othermodel;
775
776         get_cylinder(dive, &cylinder);
777         get_location(dive, &location);
778         get_suit(dive, &suit);
779
780         gtk_tree_store_set(GTK_TREE_STORE(model), iter,
781                 DIVE_NR, dive->number,
782                 DIVE_LOCATION, location,
783                 DIVE_CYLINDER, cylinder,
784                 DIVE_RATING, dive->rating,
785                 DIVE_SAC, dive->sac,
786                 DIVE_OTU, dive->otu,
787                 DIVE_TOTALWEIGHT, total_weight(dive),
788                 DIVE_SUIT, suit,
789                 -1);
790
791         free(location);
792         free(cylinder);
793         free(suit);
794
795         if (model == GTK_TREE_MODEL(dive_list.treemodel))
796                 othermodel = dive_list.listmodel;
797         else
798                 othermodel = dive_list.treemodel;
799         if (othermodel != dive_list.model)
800                 /* recursive call */
801                 gtk_tree_model_foreach(GTK_TREE_MODEL(othermodel), set_one_dive, dive);
802 }
803
804 static gboolean set_one_dive(GtkTreeModel *model,
805                              GtkTreePath *path,
806                              GtkTreeIter *iter,
807                              gpointer data)
808 {
809         int idx;
810         struct dive *dive;
811
812         /* Get the dive number */
813         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
814         if (idx < 0)
815                 return FALSE;
816         dive = get_dive(idx);
817         if (!dive)
818                 return TRUE;
819         if (data && dive != data)
820                 return FALSE;
821
822         fill_one_dive(dive, model, iter);
823         return dive == data;
824 }
825
826 void flush_divelist(struct dive *dive)
827 {
828         GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
829
830         gtk_tree_model_foreach(model, set_one_dive, dive);
831 }
832
833 void set_divelist_font(const char *font)
834 {
835         PangoFontDescription *font_desc = pango_font_description_from_string(font);
836         gtk_widget_modify_font(dive_list.tree_view, font_desc);
837         pango_font_description_free(font_desc);
838 }
839
840 void update_dive_list_units(void)
841 {
842         const char *unit;
843         GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
844
845         (void) get_depth_units(0, NULL, &unit);
846         gtk_tree_view_column_set_title(dive_list.depth, unit);
847
848         (void) get_temp_units(0, &unit);
849         gtk_tree_view_column_set_title(dive_list.temperature, unit);
850
851         (void) get_weight_units(0, NULL, &unit);
852         gtk_tree_view_column_set_title(dive_list.totalweight, unit);
853
854         gtk_tree_model_foreach(model, set_one_dive, NULL);
855 }
856
857 void update_dive_list_col_visibility(void)
858 {
859         gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
860         gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
861         gtk_tree_view_column_set_visible(dive_list.totalweight, visible_cols.totalweight);
862         gtk_tree_view_column_set_visible(dive_list.suit, visible_cols.suit);
863         gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
864         gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
865         gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
866         return;
867 }
868
869 /* random heuristic - not diving in three days implies new dive trip */
870 #define TRIP_THRESHOLD 3600*24*3
871 static int new_group(struct dive *dive, struct dive **last_dive, time_t *tm_date)
872 {
873         if (!last_dive)
874                 return TRUE;
875         if (*last_dive) {
876                 struct dive *ldive = *last_dive;
877                 if (abs(dive->when - ldive->when) < TRIP_THRESHOLD) {
878                         *last_dive = dive;
879                         return FALSE;
880                 }
881         }
882         *last_dive = dive;
883         if (tm_date) {
884                 struct tm *tm1 = gmtime(&dive->when);
885                 tm1->tm_sec = 0;
886                 tm1->tm_min = 0;
887                 tm1->tm_hour = 0;
888                 *tm_date = mktime(tm1);
889         }
890         return TRUE;
891 }
892
893 static void fill_dive_list(void)
894 {
895         int i;
896         GtkTreeIter iter, parent_iter;
897         GtkTreeStore *liststore, *treestore;
898         struct dive *last_dive = NULL;
899         struct dive *first_trip_dive = NULL;
900         struct dive *last_trip_dive = NULL;
901         time_t dive_date;
902
903         treestore = GTK_TREE_STORE(dive_list.treemodel);
904         liststore = GTK_TREE_STORE(dive_list.listmodel);
905
906         i = dive_table.nr;
907         while (--i >= 0) {
908                 struct dive *dive = dive_table.dives[i];
909
910                 if (new_group(dive, &last_dive, &dive_date))
911                 {
912                         /* make sure we display the first date of the trip in previous summary */
913                         if (first_trip_dive && last_trip_dive && last_trip_dive->when < first_trip_dive->when)
914                                 gtk_tree_store_set(treestore, &parent_iter,
915                                                 DIVE_DATE, last_trip_dive->when,
916                                                 DIVE_LOCATION, last_trip_dive->location,
917                                                 -1);
918                         first_trip_dive = dive;
919
920                         gtk_tree_store_append(treestore, &parent_iter, NULL);
921                         gtk_tree_store_set(treestore, &parent_iter,
922                                         DIVE_INDEX, -NEW_TRIP,
923                                         DIVE_NR, -NEW_TRIP,
924                                         DIVE_DATE, dive_date,
925                                         DIVE_LOCATION, dive->location,
926                                         DIVE_TEMPERATURE, 0,
927                                         DIVE_SAC, 0,
928                                         -1);
929                 }
930                 last_trip_dive = dive;
931                 update_cylinder_related_info(dive);
932                 gtk_tree_store_append(treestore, &iter, &parent_iter);
933                 gtk_tree_store_set(treestore, &iter,
934                         DIVE_INDEX, i,
935                         DIVE_NR, dive->number,
936                         DIVE_DATE, dive->when,
937                         DIVE_DEPTH, dive->maxdepth,
938                         DIVE_DURATION, dive->duration.seconds,
939                         DIVE_LOCATION, dive->location,
940                         DIVE_RATING, dive->rating,
941                         DIVE_TEMPERATURE, dive->watertemp.mkelvin,
942                         DIVE_SAC, 0,
943                         -1);
944                 gtk_tree_store_append(liststore, &iter, NULL);
945                 gtk_tree_store_set(liststore, &iter,
946                         DIVE_INDEX, i,
947                         DIVE_NR, dive->number,
948                         DIVE_DATE, dive->when,
949                         DIVE_DEPTH, dive->maxdepth,
950                         DIVE_DURATION, dive->duration.seconds,
951                         DIVE_LOCATION, dive->location,
952                         DIVE_RATING, dive->rating,
953                         DIVE_TEMPERATURE, dive->watertemp.mkelvin,
954                         DIVE_TOTALWEIGHT, 0,
955                         DIVE_SUIT, dive->suit,
956                         DIVE_SAC, 0,
957                         -1);
958         }
959
960         update_dive_list_units();
961         if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
962                 GtkTreeSelection *selection;
963
964                 /* select the last dive (and make sure it's an actual dive that is selected) */
965                 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
966                 first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive);
967                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
968                 gtk_tree_selection_select_iter(selection, &iter);
969         }
970 }
971
972 void dive_list_update_dives(void)
973 {
974         gtk_tree_store_clear(GTK_TREE_STORE(dive_list.treemodel));
975         gtk_tree_store_clear(GTK_TREE_STORE(dive_list.listmodel));
976         fill_dive_list();
977         repaint_dive();
978 }
979
980 static struct divelist_column {
981         const char *header;
982         data_func_t data;
983         sort_func_t sort;
984         unsigned int flags;
985         int *visible;
986 } dl_column[] = {
987         [DIVE_NR] = { "#", nr_data_func, NULL, ALIGN_RIGHT | UNSORTABLE },
988         [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
989         [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
990         [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
991         [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
992         [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
993         [DIVE_TOTALWEIGHT] = { "lbs", weight_data_func, NULL, ALIGN_RIGHT, &visible_cols.totalweight },
994         [DIVE_SUIT] = { "Suit", NULL, NULL, ALIGN_LEFT, &visible_cols.suit },
995         [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
996         [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
997         [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
998         [DIVE_OTU] = { "OTU", otu_data_func, NULL, 0, &visible_cols.otu },
999         [DIVE_LOCATION] = { "Location", NULL, NULL, ALIGN_LEFT },
1000 };
1001
1002
1003 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col)
1004 {
1005         int index = col - &dl_column[0];
1006         const char *title = col->header;
1007         data_func_t data_func = col->data;
1008         sort_func_t sort_func = col->sort;
1009         unsigned int flags = col->flags;
1010         int *visible = col->visible;
1011         GtkWidget *tree_view = dl->tree_view;
1012         GtkTreeStore *treemodel = dl->treemodel;
1013         GtkTreeStore *listmodel = dl->listmodel;
1014         GtkTreeViewColumn *ret;
1015
1016         if (visible && !*visible)
1017                 flags |= INVISIBLE;
1018         ret = tree_view_column(tree_view, index, title, data_func, flags);
1019         if (sort_func) {
1020                 /* the sort functions are needed in the corresponding models */
1021                 if (index == DIVE_DATE)
1022                         gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treemodel), index, sort_func, NULL, NULL);
1023                 else
1024                         gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(listmodel), index, sort_func, NULL, NULL);
1025         }
1026         return ret;
1027 }
1028
1029 /*
1030  * This is some crazy crap. The only way to get default focus seems
1031  * to be to grab focus as the widget is being shown the first time.
1032  */
1033 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
1034 {
1035         gtk_widget_grab_focus(tree_view);
1036 }
1037
1038 static void row_activated_cb(GtkTreeView *tree_view,
1039                         GtkTreePath *path,
1040                         GtkTreeViewColumn *column,
1041                         gpointer userdata)
1042 {
1043         int index;
1044         GtkTreeIter iter;
1045
1046         if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path))
1047                 return;
1048         gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &index, -1);
1049         /* a negative index is special for the "group by date" entries */
1050         if (index >= 0)
1051                 edit_dive_info(get_dive(index));
1052 }
1053
1054 void add_dive_cb(GtkWidget *menuitem, gpointer data)
1055 {
1056         struct dive *dive;
1057
1058         dive = alloc_dive();
1059         if (add_new_dive(dive)) {
1060                 record_dive(dive);
1061                 report_dives(TRUE);
1062                 return;
1063         }
1064         free(dive);
1065 }
1066
1067 void edit_dive_cb(GtkWidget *menuitem, gpointer data)
1068 {
1069         edit_multi_dive_info(amount_selected, selectiontracker);
1070 }
1071
1072 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
1073 {
1074         GtkWidget *menu, *menuitem, *image;
1075         char editlabel[] = "Edit dives";
1076
1077         menu = gtk_menu_new();
1078         menuitem = gtk_image_menu_item_new_with_label("Add dive");
1079         image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
1080         gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
1081         g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL);
1082         gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1083         if (amount_selected) {
1084                 if (amount_selected == 1)
1085                         editlabel[strlen(editlabel) - 1] = '\0';
1086                 menuitem = gtk_menu_item_new_with_label(editlabel);
1087                 g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_cb), model);
1088                 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1089         }
1090         gtk_widget_show_all(menu);
1091
1092         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
1093                 button, gtk_get_current_event_time());
1094 }
1095
1096 static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata)
1097 {
1098         popup_divelist_menu(tree_view, GTK_TREE_MODEL(dive_list.model), 0);
1099 }
1100
1101 static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
1102 {
1103         /* Right-click? Bring up the menu */
1104         if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3) {
1105                 popup_divelist_menu(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(dive_list.model), 3);
1106                 return TRUE;
1107         }
1108         return FALSE;
1109 }
1110
1111 /* we need to have a temporary copy of the selected dives while
1112    switching model as the selection_cb function keeps getting called
1113    when gtk_tree_selection_select_path is called.  We also need to
1114    keep copies of the sort order so we can restore that as well after
1115    switching models. */
1116 static int *oldselection;
1117 static int old_nr_selected;
1118 static gboolean second_call = FALSE;
1119 static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, };
1120 static int lastcol = DIVE_DATE;
1121
1122 /* Check if this dive was selected previously and select it again in the new model;
1123  * This is used after we switch models to maintain consistent selections.
1124  * We always return FALSE to iterate through all dives */
1125 static gboolean select_selected(GtkTreeModel *model, GtkTreePath *path,
1126                                 GtkTreeIter *iter, gpointer data)
1127 {
1128         int i, idx;
1129         GtkTreeSelection *selection = GTK_TREE_SELECTION(data);
1130
1131         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
1132         for (i = 0; i < old_nr_selected; i++)
1133                 if (oldselection[i] == idx) {
1134                         gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path);
1135                         gtk_tree_selection_select_path(selection, path);
1136
1137                         return FALSE;
1138                 }
1139         return FALSE;
1140
1141 }
1142
1143 static void update_column_and_order(int colid)
1144 {
1145         /* Careful: the index into treecolumns is off by one as we don't have a
1146            tree_view column for DIVE_INDEX */
1147         GtkTreeViewColumn **treecolumns = &dive_list.nr;
1148
1149         /* this will trigger a second call into sort_column_change_cb,
1150            so make sure we don't start an infinite recursion... */
1151         second_call = TRUE;
1152         gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, sortorder[colid]);
1153         gtk_tree_view_column_set_sort_order(treecolumns[colid - 1], sortorder[colid]);
1154         second_call = FALSE;
1155 }
1156
1157 /* If the sort column is date (default), show the tree model.
1158    For every other sort column only show the list model.
1159    If the model changed, inform the new model of the chosen sort column and make
1160    sure the same dives are still selected.
1161
1162    The challenge with this function is that once we change the model
1163    we also need to change the sort column again (as it was changed in
1164    the other model) and that causes this function to be called
1165    recursively - so we need to catch that.
1166 */
1167 static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data)
1168 {
1169         int colid;
1170         GtkSortType order;
1171         GtkTreeStore *currentmodel = dive_list.model;
1172
1173         if (second_call)
1174                 return;
1175
1176         gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order);
1177         if(colid == lastcol) {
1178                 /* we just changed sort order */
1179                 sortorder[colid] = order;
1180                 return;
1181         } else {
1182                 lastcol = colid;
1183         }
1184         if(colid == DIVE_DATE)
1185                 dive_list.model = dive_list.treemodel;
1186         else
1187                 dive_list.model = dive_list.listmodel;
1188         if (dive_list.model != currentmodel) {
1189                 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1190
1191                 /* remember what is currently selected, switch models and reselect the selected rows */
1192                 old_nr_selected = amount_selected;
1193                 oldselection = malloc(old_nr_selected * sizeof(int));
1194                 if (amount_selected)
1195                         memcpy(oldselection, selectiontracker, amount_selected * sizeof(int));
1196                 gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model));
1197
1198                 update_column_and_order(colid);
1199
1200                 if (old_nr_selected) {
1201                         /* we need to select all the dives that were selected */
1202                         /* this is fundamentally an n^2 algorithm as implemented - YUCK */
1203                         gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), select_selected, selection);
1204                 }
1205         } else {
1206                 if (order != sortorder[colid]) {
1207                         update_column_and_order(colid);
1208                 }
1209         }
1210 }
1211
1212 GtkWidget *dive_list_create(void)
1213 {
1214         GtkTreeSelection  *selection;
1215
1216         dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1217                                 G_TYPE_INT,                     /* index */
1218                                 G_TYPE_INT,                     /* nr */
1219                                 G_TYPE_INT,                     /* Date */
1220                                 G_TYPE_INT,                     /* Star rating */
1221                                 G_TYPE_INT,                     /* Depth */
1222                                 G_TYPE_INT,                     /* Duration */
1223                                 G_TYPE_INT,                     /* Temperature */
1224                                 G_TYPE_INT,                     /* Total weight */
1225                                 G_TYPE_STRING,                  /* Suit */
1226                                 G_TYPE_STRING,                  /* Cylinder */
1227                                 G_TYPE_INT,                     /* Nitrox */
1228                                 G_TYPE_INT,                     /* SAC */
1229                                 G_TYPE_INT,                     /* OTU */
1230                                 G_TYPE_STRING                   /* Location */
1231                                 );
1232         dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1233                                 G_TYPE_INT,                     /* index */
1234                                 G_TYPE_INT,                     /* nr */
1235                                 G_TYPE_INT,                     /* Date */
1236                                 G_TYPE_INT,                     /* Star rating */
1237                                 G_TYPE_INT,                     /* Depth */
1238                                 G_TYPE_INT,                     /* Duration */
1239                                 G_TYPE_INT,                     /* Temperature */
1240                                 G_TYPE_INT,                     /* Total weight */
1241                                 G_TYPE_STRING,                  /* Suit */
1242                                 G_TYPE_STRING,                  /* Cylinder */
1243                                 G_TYPE_INT,                     /* Nitrox */
1244                                 G_TYPE_INT,                     /* SAC */
1245                                 G_TYPE_INT,                     /* OTU */
1246                                 G_TYPE_STRING                   /* Location */
1247                                 );
1248         dive_list.model = dive_list.treemodel;
1249         dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
1250         set_divelist_font(divelist_font);
1251
1252         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1253
1254         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
1255         gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
1256
1257         dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR);
1258         dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE);
1259         dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING);
1260         dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH);
1261         dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION);
1262         dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE);
1263         dive_list.totalweight = divelist_column(&dive_list, dl_column + DIVE_TOTALWEIGHT);
1264         dive_list.suit = divelist_column(&dive_list, dl_column + DIVE_SUIT);
1265         dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER);
1266         dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX);
1267         dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC);
1268         dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU);
1269         dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION);
1270
1271         fill_dive_list();
1272
1273         g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
1274                                           "search-column", DIVE_LOCATION,
1275                                           "rules-hint", TRUE,
1276                                           NULL);
1277
1278         g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
1279         g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL);
1280         g_signal_connect(dive_list.tree_view, "row-expanded", G_CALLBACK(row_expanded_cb), NULL);
1281         g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL);
1282         g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL);
1283         g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), NULL);
1284         g_signal_connect(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1285         g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1286
1287         gtk_tree_selection_set_select_function(selection, modify_selection_cb, NULL, NULL);
1288
1289         dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
1290         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
1291                                GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1292         gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
1293
1294         dive_list.changed = 0;
1295
1296         return dive_list.container_widget;
1297 }
1298
1299 void mark_divelist_changed(int changed)
1300 {
1301         dive_list.changed = changed;
1302 }
1303
1304 int unsaved_changes()
1305 {
1306         return dive_list.changed;
1307 }