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