]> git.tdb.fi Git - ext/subsurface.git/blob - divelist.c
Fix default size for scrollable notebook
[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, nr;
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, DIVE_NR, &nr, -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 (%d dive%s)",
324                         weekday(tm->tm_wday),
325                         monthname(tm->tm_mon),
326                         tm->tm_mday, tm->tm_year + 1900,
327                         nr, nr > 1 ? "s" : "");
328                 break;
329         default:
330                 snprintf(buffer, sizeof(buffer),
331                         "%s, %s %d, %d %02d:%02d",
332                         weekday(tm->tm_wday),
333                         monthname(tm->tm_mon),
334                         tm->tm_mday, tm->tm_year + 1900,
335                         tm->tm_hour, tm->tm_min);
336         }
337         g_object_set(renderer, "text", buffer, NULL);
338 }
339
340 static void depth_data_func(GtkTreeViewColumn *col,
341                             GtkCellRenderer *renderer,
342                             GtkTreeModel *model,
343                             GtkTreeIter *iter,
344                             gpointer data)
345 {
346         int depth, integer, frac, len, idx;
347         char buffer[40];
348
349         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1);
350
351         if (idx < 0) {
352                 *buffer = '\0';
353         } else {
354                 switch (output_units.length) {
355                 case METERS:
356                         /* To tenths of meters */
357                         depth = (depth + 49) / 100;
358                         integer = depth / 10;
359                         frac = depth % 10;
360                         if (integer < 20)
361                                 break;
362                         if (frac >= 5)
363                                 integer++;
364                         frac = -1;
365                         break;
366                 case FEET:
367                         integer = mm_to_feet(depth) + 0.5;
368                         frac = -1;
369                         break;
370                 default:
371                         return;
372                 }
373                 len = snprintf(buffer, sizeof(buffer), "%d", integer);
374                 if (frac >= 0)
375                         len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
376         }
377         g_object_set(renderer, "text", buffer, NULL);
378 }
379
380 static void duration_data_func(GtkTreeViewColumn *col,
381                                GtkCellRenderer *renderer,
382                                GtkTreeModel *model,
383                                GtkTreeIter *iter,
384                                gpointer data)
385 {
386         unsigned int sec;
387         int idx;
388         char buffer[16];
389
390         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1);
391         if (idx < 0)
392                 *buffer = '\0';
393         else
394                 snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
395
396         g_object_set(renderer, "text", buffer, NULL);
397 }
398
399 static void temperature_data_func(GtkTreeViewColumn *col,
400                                   GtkCellRenderer *renderer,
401                                   GtkTreeModel *model,
402                                   GtkTreeIter *iter,
403                                   gpointer data)
404 {
405         int value, idx;
406         char buffer[80];
407
408         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1);
409
410         *buffer = 0;
411         if (idx >= 0 && value) {
412                 double deg;
413                 switch (output_units.temperature) {
414                 case CELSIUS:
415                         deg = mkelvin_to_C(value);
416                         break;
417                 case FAHRENHEIT:
418                         deg = mkelvin_to_F(value);
419                         break;
420                 default:
421                         return;
422                 }
423                 snprintf(buffer, sizeof(buffer), "%.1f", deg);
424         }
425
426         g_object_set(renderer, "text", buffer, NULL);
427 }
428
429 static void nr_data_func(GtkTreeViewColumn *col,
430                            GtkCellRenderer *renderer,
431                            GtkTreeModel *model,
432                            GtkTreeIter *iter,
433                            gpointer data)
434 {
435         int idx, nr;
436         char buffer[40];
437
438         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1);
439         if (idx < 0)
440                 *buffer = '\0';
441         else
442                 snprintf(buffer, sizeof(buffer), "%d", nr);
443         g_object_set(renderer, "text", buffer, NULL);
444 }
445
446 /*
447  * Get "maximal" dive gas for a dive.
448  * Rules:
449  *  - Trimix trumps nitrox (highest He wins, O2 breaks ties)
450  *  - Nitrox trumps air (even if hypoxic)
451  * These are the same rules as the inter-dive sorting rules.
452  */
453 static void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
454 {
455         int i;
456         int maxo2 = -1, maxhe = -1, mino2 = 1000;
457
458         for (i = 0; i < MAX_CYLINDERS; i++) {
459                 cylinder_t *cyl = dive->cylinder + i;
460                 struct gasmix *mix = &cyl->gasmix;
461                 int o2 = mix->o2.permille;
462                 int he = mix->he.permille;
463
464                 if (cylinder_none(cyl))
465                         continue;
466                 if (!o2)
467                         o2 = AIR_PERMILLE;
468                 if (o2 < mino2)
469                         mino2 = o2;
470                 if (he > maxhe)
471                         goto newmax;
472                 if (he < maxhe)
473                         continue;
474                 if (o2 <= maxo2)
475                         continue;
476 newmax:
477                 maxhe = he;
478                 maxo2 = o2;
479         }
480         /* All air? Show/sort as "air"/zero */
481         if (!maxhe && maxo2 == AIR_PERMILLE && mino2 == maxo2)
482                 maxo2 = mino2 = 0;
483         *o2_p = maxo2;
484         *he_p = maxhe;
485         *o2low_p = mino2;
486 }
487
488 static int total_weight(struct dive *dive)
489 {
490         int i, total_grams = 0;
491
492         if (dive)
493                 for (i=0; i< MAX_WEIGHTSYSTEMS; i++)
494                         total_grams += dive->weightsystem[i].weight.grams;
495         return total_grams;
496 }
497
498 static void weight_data_func(GtkTreeViewColumn *col,
499                              GtkCellRenderer *renderer,
500                              GtkTreeModel *model,
501                              GtkTreeIter *iter,
502                              gpointer data)
503 {
504         int indx, decimals;
505         double value;
506         char buffer[80];
507         struct dive *dive;
508
509         gtk_tree_model_get(model, iter, DIVE_INDEX, &indx, -1);
510         dive = get_dive(indx);
511         value = get_weight_units(total_weight(dive), &decimals, NULL);
512         if (value == 0.0)
513                 *buffer = '\0';
514         else
515                 snprintf(buffer, sizeof(buffer), "%.*f", decimals, value);
516
517         g_object_set(renderer, "text", buffer, NULL);
518 }
519
520 static gint nitrox_sort_func(GtkTreeModel *model,
521         GtkTreeIter *iter_a,
522         GtkTreeIter *iter_b,
523         gpointer user_data)
524 {
525         int index_a, index_b;
526         struct dive *a, *b;
527         int a_o2, b_o2;
528         int a_he, b_he;
529         int a_o2low, b_o2low;
530
531         gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1);
532         gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1);
533         a = get_dive(index_a);
534         b = get_dive(index_b);
535         get_dive_gas(a, &a_o2, &a_he, &a_o2low);
536         get_dive_gas(b, &b_o2, &b_he, &b_o2low);
537
538         /* Sort by Helium first, O2 second */
539         if (a_he == b_he) {
540                 if (a_o2 == b_o2)
541                         return a_o2low - b_o2low;
542                 return a_o2 - b_o2;
543         }
544         return a_he - b_he;
545 }
546
547 #define UTF8_ELLIPSIS "\xE2\x80\xA6"
548
549 static void nitrox_data_func(GtkTreeViewColumn *col,
550                              GtkCellRenderer *renderer,
551                              GtkTreeModel *model,
552                              GtkTreeIter *iter,
553                              gpointer data)
554 {
555         int idx, o2, he, o2low;
556         char buffer[80];
557         struct dive *dive;
558
559         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
560         if (idx < 0) {
561                 *buffer = '\0';
562                 goto exit;
563         }
564         dive = get_dive(idx);
565         get_dive_gas(dive, &o2, &he, &o2low);
566         o2 = (o2 + 5) / 10;
567         he = (he + 5) / 10;
568         o2low = (o2low + 5) / 10;
569
570         if (he)
571                 snprintf(buffer, sizeof(buffer), "%d/%d", o2, he);
572         else if (o2)
573                 if (o2 == o2low)
574                         snprintf(buffer, sizeof(buffer), "%d", o2);
575                 else
576                         snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
577         else
578                 strcpy(buffer, "air");
579 exit:
580         g_object_set(renderer, "text", buffer, NULL);
581 }
582
583 /* Render the SAC data (integer value of "ml / min") */
584 static void sac_data_func(GtkTreeViewColumn *col,
585                           GtkCellRenderer *renderer,
586                           GtkTreeModel *model,
587                           GtkTreeIter *iter,
588                           gpointer data)
589 {
590         int value, idx;
591         const char *fmt;
592         char buffer[16];
593         double sac;
594
595         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1);
596
597         if (idx < 0 || !value) {
598                 *buffer = '\0';
599                 goto exit;
600         }
601
602         sac = value / 1000.0;
603         switch (output_units.volume) {
604         case LITER:
605                 fmt = "%4.1f";
606                 break;
607         case CUFT:
608                 fmt = "%4.2f";
609                 sac = ml_to_cuft(sac * 1000);
610                 break;
611         }
612         snprintf(buffer, sizeof(buffer), fmt, sac);
613 exit:
614         g_object_set(renderer, "text", buffer, NULL);
615 }
616
617 /* Render the OTU data (integer value of "OTU") */
618 static void otu_data_func(GtkTreeViewColumn *col,
619                           GtkCellRenderer *renderer,
620                           GtkTreeModel *model,
621                           GtkTreeIter *iter,
622                           gpointer data)
623 {
624         int value, idx;
625         char buffer[16];
626
627         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1);
628
629         if (idx < 0 || !value)
630                 *buffer = '\0';
631         else
632                 snprintf(buffer, sizeof(buffer), "%d", value);
633
634         g_object_set(renderer, "text", buffer, NULL);
635 }
636
637 /* calculate OTU for a dive */
638 static int calculate_otu(struct dive *dive)
639 {
640         int i;
641         double otu = 0.0;
642
643         for (i = 1; i < dive->samples; i++) {
644                 int t;
645                 double po2;
646                 struct sample *sample = dive->sample + i;
647                 struct sample *psample = sample - 1;
648                 t = sample->time.seconds - psample->time.seconds;
649                 int o2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille;
650                 if (!o2)
651                         o2 = AIR_PERMILLE;
652                 po2 = o2 / 1000.0 * (sample->depth.mm + 10000) / 10000.0;
653                 if (po2 >= 0.5)
654                         otu += pow(po2 - 0.5, 0.83) * t / 30.0;
655         }
656         return otu + 0.5;
657 }
658 /*
659  * Return air usage (in liters).
660  */
661 static double calculate_airuse(struct dive *dive)
662 {
663         double airuse = 0;
664         int i;
665
666         for (i = 0; i < MAX_CYLINDERS; i++) {
667                 pressure_t start, end;
668                 cylinder_t *cyl = dive->cylinder + i;
669                 int size = cyl->type.size.mliter;
670                 double kilo_atm;
671
672                 if (!size)
673                         continue;
674
675                 start = cyl->start.mbar ? cyl->start : cyl->sample_start;
676                 end = cyl->end.mbar ? cyl->end : cyl->sample_end;
677                 kilo_atm = (to_ATM(start) - to_ATM(end)) / 1000.0;
678
679                 /* Liters of air at 1 atm == milliliters at 1k atm*/
680                 airuse += kilo_atm * size;
681         }
682         return airuse;
683 }
684
685 static int calculate_sac(struct dive *dive)
686 {
687         double airuse, pressure, sac;
688         int duration, i;
689
690         airuse = calculate_airuse(dive);
691         if (!airuse)
692                 return 0;
693         if (!dive->duration.seconds)
694                 return 0;
695
696         /* find and eliminate long surface intervals */
697         duration = dive->duration.seconds;
698         for (i = 0; i < dive->samples; i++) {
699                 if (dive->sample[i].depth.mm < 100) { /* less than 10cm */
700                         int end = i + 1;
701                         while (end < dive->samples && dive->sample[end].depth.mm < 100)
702                                 end++;
703                         /* we only want the actual surface time during a dive */
704                         if (end < dive->samples) {
705                                 end--;
706                                 duration -= dive->sample[end].time.seconds -
707                                                 dive->sample[i].time.seconds;
708                                 i = end + 1;
709                         }
710                 }
711         }
712         /* Mean pressure in atm: 1 atm per 10m */
713         pressure = 1 + (dive->meandepth.mm / 10000.0);
714         sac = airuse / pressure * 60 / duration;
715
716         /* milliliters per minute.. */
717         return sac * 1000;
718 }
719
720 void update_cylinder_related_info(struct dive *dive)
721 {
722         if (dive != NULL) {
723                 dive->sac = calculate_sac(dive);
724                 dive->otu = calculate_otu(dive);
725         }
726 }
727
728 static void get_string(char **str, const char *s)
729 {
730         int len;
731         char *n;
732
733         if (!s)
734                 s = "";
735         len = strlen(s);
736         if (len > 60)
737                 len = 60;
738         n = malloc(len+1);
739         memcpy(n, s, len);
740         n[len] = 0;
741         *str = n;
742 }
743
744 static void get_location(struct dive *dive, char **str)
745 {
746         get_string(str, dive->location);
747 }
748
749 static void get_cylinder(struct dive *dive, char **str)
750 {
751         get_string(str, dive->cylinder[0].type.description);
752 }
753
754 static void get_suit(struct dive *dive, char **str)
755 {
756         get_string(str, dive->suit);
757 }
758
759 /*
760  * Set up anything that could have changed due to editing
761  * of dive information; we need to do this for both models,
762  * so we simply call set_one_dive again with the non-current model
763  */
764 /* forward declaration for recursion */
765 static gboolean set_one_dive(GtkTreeModel *model,
766                              GtkTreePath *path,
767                              GtkTreeIter *iter,
768                              gpointer data);
769
770 static void fill_one_dive(struct dive *dive,
771                           GtkTreeModel *model,
772                           GtkTreeIter *iter)
773 {
774         char *location, *cylinder, *suit;
775         GtkTreeStore *othermodel;
776
777         get_cylinder(dive, &cylinder);
778         get_location(dive, &location);
779         get_suit(dive, &suit);
780
781         gtk_tree_store_set(GTK_TREE_STORE(model), iter,
782                 DIVE_NR, dive->number,
783                 DIVE_LOCATION, location,
784                 DIVE_CYLINDER, cylinder,
785                 DIVE_RATING, dive->rating,
786                 DIVE_SAC, dive->sac,
787                 DIVE_OTU, dive->otu,
788                 DIVE_TOTALWEIGHT, total_weight(dive),
789                 DIVE_SUIT, suit,
790                 -1);
791
792         free(location);
793         free(cylinder);
794         free(suit);
795
796         if (model == GTK_TREE_MODEL(dive_list.treemodel))
797                 othermodel = dive_list.listmodel;
798         else
799                 othermodel = dive_list.treemodel;
800         if (othermodel != dive_list.model)
801                 /* recursive call */
802                 gtk_tree_model_foreach(GTK_TREE_MODEL(othermodel), set_one_dive, dive);
803 }
804
805 static gboolean set_one_dive(GtkTreeModel *model,
806                              GtkTreePath *path,
807                              GtkTreeIter *iter,
808                              gpointer data)
809 {
810         int idx;
811         struct dive *dive;
812
813         /* Get the dive number */
814         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
815         if (idx < 0)
816                 return FALSE;
817         dive = get_dive(idx);
818         if (!dive)
819                 return TRUE;
820         if (data && dive != data)
821                 return FALSE;
822
823         fill_one_dive(dive, model, iter);
824         return dive == data;
825 }
826
827 void flush_divelist(struct dive *dive)
828 {
829         GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
830
831         gtk_tree_model_foreach(model, set_one_dive, dive);
832 }
833
834 void set_divelist_font(const char *font)
835 {
836         PangoFontDescription *font_desc = pango_font_description_from_string(font);
837         gtk_widget_modify_font(dive_list.tree_view, font_desc);
838         pango_font_description_free(font_desc);
839 }
840
841 void update_dive_list_units(void)
842 {
843         const char *unit;
844         GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
845
846         (void) get_depth_units(0, NULL, &unit);
847         gtk_tree_view_column_set_title(dive_list.depth, unit);
848
849         (void) get_temp_units(0, &unit);
850         gtk_tree_view_column_set_title(dive_list.temperature, unit);
851
852         (void) get_weight_units(0, NULL, &unit);
853         gtk_tree_view_column_set_title(dive_list.totalweight, unit);
854
855         gtk_tree_model_foreach(model, set_one_dive, NULL);
856 }
857
858 void update_dive_list_col_visibility(void)
859 {
860         gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
861         gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
862         gtk_tree_view_column_set_visible(dive_list.totalweight, visible_cols.totalweight);
863         gtk_tree_view_column_set_visible(dive_list.suit, visible_cols.suit);
864         gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
865         gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
866         gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
867         return;
868 }
869
870 /* random heuristic - not diving in three days implies new dive trip */
871 #define TRIP_THRESHOLD 3600*24*3
872 static int new_group(struct dive *dive, struct dive **last_dive, time_t *tm_date)
873 {
874         if (!last_dive)
875                 return TRUE;
876         if (*last_dive) {
877                 struct dive *ldive = *last_dive;
878                 if (abs(dive->when - ldive->when) < TRIP_THRESHOLD) {
879                         *last_dive = dive;
880                         return FALSE;
881                 }
882         }
883         *last_dive = dive;
884         if (tm_date) {
885                 struct tm *tm1 = gmtime(&dive->when);
886                 tm1->tm_sec = 0;
887                 tm1->tm_min = 0;
888                 tm1->tm_hour = 0;
889                 *tm_date = mktime(tm1);
890         }
891         return TRUE;
892 }
893
894 static void fill_dive_list(void)
895 {
896         int i, group_size;
897         GtkTreeIter iter, parent_iter;
898         GtkTreeStore *liststore, *treestore;
899         struct dive *last_dive = NULL;
900         struct dive *last_trip_dive = NULL;
901         const char *last_location = NULL;
902         time_t dive_date;
903
904         treestore = GTK_TREE_STORE(dive_list.treemodel);
905         liststore = GTK_TREE_STORE(dive_list.listmodel);
906
907         i = dive_table.nr;
908         while (--i >= 0) {
909                 struct dive *dive = dive_table.dives[i];
910
911                 if (new_group(dive, &last_dive, &dive_date))
912                 {
913                         /* make sure we display the first date of the trip in previous summary */
914                         if (last_trip_dive)
915                                 gtk_tree_store_set(treestore, &parent_iter,
916                                         DIVE_NR, group_size,
917                                         DIVE_DATE, last_trip_dive->when,
918                                         DIVE_LOCATION, last_location,
919                                         -1);
920
921                         gtk_tree_store_append(treestore, &parent_iter, NULL);
922                         gtk_tree_store_set(treestore, &parent_iter,
923                                         DIVE_INDEX, -NEW_TRIP,
924                                         DIVE_NR, 1,
925                                         DIVE_TEMPERATURE, 0,
926                                         DIVE_SAC, 0,
927                                         -1);
928
929                         group_size = 0;
930                         /* This might be NULL */
931                         last_location = dive->location;
932                 }
933                 group_size++;
934                 last_trip_dive = dive;
935                 if (dive->location)
936                         last_location = dive->location;
937                 update_cylinder_related_info(dive);
938                 gtk_tree_store_append(treestore, &iter, &parent_iter);
939                 gtk_tree_store_set(treestore, &iter,
940                         DIVE_INDEX, i,
941                         DIVE_NR, dive->number,
942                         DIVE_DATE, dive->when,
943                         DIVE_DEPTH, dive->maxdepth,
944                         DIVE_DURATION, dive->duration.seconds,
945                         DIVE_LOCATION, dive->location,
946                         DIVE_RATING, dive->rating,
947                         DIVE_TEMPERATURE, dive->watertemp.mkelvin,
948                         DIVE_SAC, 0,
949                         -1);
950                 gtk_tree_store_append(liststore, &iter, NULL);
951                 gtk_tree_store_set(liststore, &iter,
952                         DIVE_INDEX, i,
953                         DIVE_NR, dive->number,
954                         DIVE_DATE, dive->when,
955                         DIVE_DEPTH, dive->maxdepth,
956                         DIVE_DURATION, dive->duration.seconds,
957                         DIVE_LOCATION, dive->location,
958                         DIVE_RATING, dive->rating,
959                         DIVE_TEMPERATURE, dive->watertemp.mkelvin,
960                         DIVE_TOTALWEIGHT, 0,
961                         DIVE_SUIT, dive->suit,
962                         DIVE_SAC, 0,
963                         -1);
964         }
965
966         /* make sure we display the first date of the trip in previous summary */
967         if (last_trip_dive)
968                 gtk_tree_store_set(treestore, &parent_iter,
969                                 DIVE_NR, group_size,
970                                 DIVE_DATE, last_trip_dive->when,
971                                 DIVE_LOCATION, last_location,
972                                 -1);
973
974         update_dive_list_units();
975         if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
976                 GtkTreeSelection *selection;
977
978                 /* select the last dive (and make sure it's an actual dive that is selected) */
979                 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
980                 first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive);
981                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
982                 gtk_tree_selection_select_iter(selection, &iter);
983         }
984 }
985
986 void dive_list_update_dives(void)
987 {
988         gtk_tree_store_clear(GTK_TREE_STORE(dive_list.treemodel));
989         gtk_tree_store_clear(GTK_TREE_STORE(dive_list.listmodel));
990         fill_dive_list();
991         repaint_dive();
992 }
993
994 static struct divelist_column {
995         const char *header;
996         data_func_t data;
997         sort_func_t sort;
998         unsigned int flags;
999         int *visible;
1000 } dl_column[] = {
1001         [DIVE_NR] = { "#", nr_data_func, NULL, ALIGN_RIGHT | UNSORTABLE },
1002         [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
1003         [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
1004         [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
1005         [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
1006         [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
1007         [DIVE_TOTALWEIGHT] = { "lbs", weight_data_func, NULL, ALIGN_RIGHT, &visible_cols.totalweight },
1008         [DIVE_SUIT] = { "Suit", NULL, NULL, ALIGN_LEFT, &visible_cols.suit },
1009         [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
1010         [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
1011         [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
1012         [DIVE_OTU] = { "OTU", otu_data_func, NULL, 0, &visible_cols.otu },
1013         [DIVE_LOCATION] = { "Location", NULL, NULL, ALIGN_LEFT },
1014 };
1015
1016
1017 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col)
1018 {
1019         int index = col - &dl_column[0];
1020         const char *title = col->header;
1021         data_func_t data_func = col->data;
1022         sort_func_t sort_func = col->sort;
1023         unsigned int flags = col->flags;
1024         int *visible = col->visible;
1025         GtkWidget *tree_view = dl->tree_view;
1026         GtkTreeStore *treemodel = dl->treemodel;
1027         GtkTreeStore *listmodel = dl->listmodel;
1028         GtkTreeViewColumn *ret;
1029
1030         if (visible && !*visible)
1031                 flags |= INVISIBLE;
1032         ret = tree_view_column(tree_view, index, title, data_func, flags);
1033         if (sort_func) {
1034                 /* the sort functions are needed in the corresponding models */
1035                 if (index == DIVE_DATE)
1036                         gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treemodel), index, sort_func, NULL, NULL);
1037                 else
1038                         gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(listmodel), index, sort_func, NULL, NULL);
1039         }
1040         return ret;
1041 }
1042
1043 /*
1044  * This is some crazy crap. The only way to get default focus seems
1045  * to be to grab focus as the widget is being shown the first time.
1046  */
1047 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
1048 {
1049         gtk_widget_grab_focus(tree_view);
1050 }
1051
1052 /*
1053  * Double-clicking on a group entry will expand a collapsed group
1054  * and vice versa.
1055  */
1056 static void collapse_expand(GtkTreeView *tree_view, GtkTreePath *path)
1057 {
1058         if (!gtk_tree_view_row_expanded(tree_view, path))
1059                 gtk_tree_view_expand_row(tree_view, path, FALSE);
1060         else
1061                 gtk_tree_view_collapse_row(tree_view, path);
1062
1063 }
1064
1065 /* Double-click on a dive list */
1066 static void row_activated_cb(GtkTreeView *tree_view,
1067                         GtkTreePath *path,
1068                         GtkTreeViewColumn *column,
1069                         gpointer userdata)
1070 {
1071         int index;
1072         GtkTreeIter iter;
1073
1074         if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path))
1075                 return;
1076
1077         gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &index, -1);
1078         /* a negative index is special for the "group by date" entries */
1079         if (index < 0) {
1080                 collapse_expand(tree_view, path);
1081                 return;
1082         }
1083         edit_dive_info(get_dive(index));
1084 }
1085
1086 void add_dive_cb(GtkWidget *menuitem, gpointer data)
1087 {
1088         struct dive *dive;
1089
1090         dive = alloc_dive();
1091         if (add_new_dive(dive)) {
1092                 record_dive(dive);
1093                 report_dives(TRUE);
1094                 return;
1095         }
1096         free(dive);
1097 }
1098
1099 void edit_dive_cb(GtkWidget *menuitem, gpointer data)
1100 {
1101         edit_multi_dive_info(amount_selected, selectiontracker);
1102 }
1103
1104 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
1105 {
1106         GtkWidget *menu, *menuitem, *image;
1107         char editlabel[] = "Edit dives";
1108
1109         menu = gtk_menu_new();
1110         menuitem = gtk_image_menu_item_new_with_label("Add dive");
1111         image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
1112         gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
1113         g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL);
1114         gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1115         if (amount_selected) {
1116                 if (amount_selected == 1)
1117                         editlabel[strlen(editlabel) - 1] = '\0';
1118                 menuitem = gtk_menu_item_new_with_label(editlabel);
1119                 g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_cb), model);
1120                 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1121         }
1122         gtk_widget_show_all(menu);
1123
1124         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
1125                 button, gtk_get_current_event_time());
1126 }
1127
1128 static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata)
1129 {
1130         popup_divelist_menu(tree_view, GTK_TREE_MODEL(dive_list.model), 0);
1131 }
1132
1133 static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
1134 {
1135         /* Right-click? Bring up the menu */
1136         if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3) {
1137                 popup_divelist_menu(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(dive_list.model), 3);
1138                 return TRUE;
1139         }
1140         return FALSE;
1141 }
1142
1143 /* we need to have a temporary copy of the selected dives while
1144    switching model as the selection_cb function keeps getting called
1145    when gtk_tree_selection_select_path is called.  We also need to
1146    keep copies of the sort order so we can restore that as well after
1147    switching models. */
1148 static int *oldselection;
1149 static int old_nr_selected;
1150 static gboolean second_call = FALSE;
1151 static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, };
1152 static int lastcol = DIVE_DATE;
1153
1154 /* Check if this dive was selected previously and select it again in the new model;
1155  * This is used after we switch models to maintain consistent selections.
1156  * We always return FALSE to iterate through all dives */
1157 static gboolean select_selected(GtkTreeModel *model, GtkTreePath *path,
1158                                 GtkTreeIter *iter, gpointer data)
1159 {
1160         int i, idx;
1161         GtkTreeSelection *selection = GTK_TREE_SELECTION(data);
1162
1163         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
1164         for (i = 0; i < old_nr_selected; i++)
1165                 if (oldselection[i] == idx) {
1166                         gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path);
1167                         gtk_tree_selection_select_path(selection, path);
1168
1169                         return FALSE;
1170                 }
1171         return FALSE;
1172
1173 }
1174
1175 static void update_column_and_order(int colid)
1176 {
1177         /* Careful: the index into treecolumns is off by one as we don't have a
1178            tree_view column for DIVE_INDEX */
1179         GtkTreeViewColumn **treecolumns = &dive_list.nr;
1180
1181         /* this will trigger a second call into sort_column_change_cb,
1182            so make sure we don't start an infinite recursion... */
1183         second_call = TRUE;
1184         gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, sortorder[colid]);
1185         gtk_tree_view_column_set_sort_order(treecolumns[colid - 1], sortorder[colid]);
1186         second_call = FALSE;
1187 }
1188
1189 /* If the sort column is date (default), show the tree model.
1190    For every other sort column only show the list model.
1191    If the model changed, inform the new model of the chosen sort column and make
1192    sure the same dives are still selected.
1193
1194    The challenge with this function is that once we change the model
1195    we also need to change the sort column again (as it was changed in
1196    the other model) and that causes this function to be called
1197    recursively - so we need to catch that.
1198 */
1199 static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data)
1200 {
1201         int colid;
1202         GtkSortType order;
1203         GtkTreeStore *currentmodel = dive_list.model;
1204
1205         if (second_call)
1206                 return;
1207
1208         gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order);
1209         if(colid == lastcol) {
1210                 /* we just changed sort order */
1211                 sortorder[colid] = order;
1212                 return;
1213         } else {
1214                 lastcol = colid;
1215         }
1216         if(colid == DIVE_DATE)
1217                 dive_list.model = dive_list.treemodel;
1218         else
1219                 dive_list.model = dive_list.listmodel;
1220         if (dive_list.model != currentmodel) {
1221                 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1222
1223                 /* remember what is currently selected, switch models and reselect the selected rows */
1224                 old_nr_selected = amount_selected;
1225                 oldselection = malloc(old_nr_selected * sizeof(int));
1226                 if (amount_selected)
1227                         memcpy(oldselection, selectiontracker, amount_selected * sizeof(int));
1228                 gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model));
1229
1230                 update_column_and_order(colid);
1231
1232                 if (old_nr_selected) {
1233                         /* we need to select all the dives that were selected */
1234                         /* this is fundamentally an n^2 algorithm as implemented - YUCK */
1235                         gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), select_selected, selection);
1236                 }
1237         } else {
1238                 if (order != sortorder[colid]) {
1239                         update_column_and_order(colid);
1240                 }
1241         }
1242 }
1243
1244 GtkWidget *dive_list_create(void)
1245 {
1246         GtkTreeSelection  *selection;
1247
1248         dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1249                                 G_TYPE_INT,                     /* index */
1250                                 G_TYPE_INT,                     /* nr */
1251                                 G_TYPE_INT,                     /* Date */
1252                                 G_TYPE_INT,                     /* Star rating */
1253                                 G_TYPE_INT,                     /* Depth */
1254                                 G_TYPE_INT,                     /* Duration */
1255                                 G_TYPE_INT,                     /* Temperature */
1256                                 G_TYPE_INT,                     /* Total weight */
1257                                 G_TYPE_STRING,                  /* Suit */
1258                                 G_TYPE_STRING,                  /* Cylinder */
1259                                 G_TYPE_INT,                     /* Nitrox */
1260                                 G_TYPE_INT,                     /* SAC */
1261                                 G_TYPE_INT,                     /* OTU */
1262                                 G_TYPE_STRING                   /* Location */
1263                                 );
1264         dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1265                                 G_TYPE_INT,                     /* index */
1266                                 G_TYPE_INT,                     /* nr */
1267                                 G_TYPE_INT,                     /* Date */
1268                                 G_TYPE_INT,                     /* Star rating */
1269                                 G_TYPE_INT,                     /* Depth */
1270                                 G_TYPE_INT,                     /* Duration */
1271                                 G_TYPE_INT,                     /* Temperature */
1272                                 G_TYPE_INT,                     /* Total weight */
1273                                 G_TYPE_STRING,                  /* Suit */
1274                                 G_TYPE_STRING,                  /* Cylinder */
1275                                 G_TYPE_INT,                     /* Nitrox */
1276                                 G_TYPE_INT,                     /* SAC */
1277                                 G_TYPE_INT,                     /* OTU */
1278                                 G_TYPE_STRING                   /* Location */
1279                                 );
1280         dive_list.model = dive_list.treemodel;
1281         dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
1282         set_divelist_font(divelist_font);
1283
1284         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1285
1286         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
1287         gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
1288
1289         dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR);
1290         dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE);
1291         dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING);
1292         dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH);
1293         dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION);
1294         dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE);
1295         dive_list.totalweight = divelist_column(&dive_list, dl_column + DIVE_TOTALWEIGHT);
1296         dive_list.suit = divelist_column(&dive_list, dl_column + DIVE_SUIT);
1297         dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER);
1298         dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX);
1299         dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC);
1300         dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU);
1301         dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION);
1302
1303         fill_dive_list();
1304
1305         g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
1306                                           "search-column", DIVE_LOCATION,
1307                                           "rules-hint", TRUE,
1308                                           NULL);
1309
1310         g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
1311         g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL);
1312         g_signal_connect(dive_list.tree_view, "row-expanded", G_CALLBACK(row_expanded_cb), NULL);
1313         g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL);
1314         g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL);
1315         g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), NULL);
1316         g_signal_connect(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1317         g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1318
1319         gtk_tree_selection_set_select_function(selection, modify_selection_cb, NULL, NULL);
1320
1321         dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
1322         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
1323                                GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1324         gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
1325
1326         dive_list.changed = 0;
1327
1328         return dive_list.container_widget;
1329 }
1330
1331 void mark_divelist_changed(int changed)
1332 {
1333         dive_list.changed = changed;
1334 }
1335
1336 int unsaved_changes()
1337 {
1338         return dive_list.changed;
1339 }