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