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