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