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