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