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