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