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