]> git.tdb.fi Git - ext/subsurface.git/blob - divelist.c
Improve tree model implementation
[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;
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, GtkTreeModel *model)
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(model, &iter, path)) {
104                         gtk_tree_model_get(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(model, &parent);
112                                 if (!gtk_tree_model_iter_children(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(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, 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
597  */
598 static void fill_one_dive(struct dive *dive,
599                           GtkTreeModel *model,
600                           GtkTreeIter *iter)
601 {
602         char *location, *cylinder;
603
604         get_cylinder(dive, &cylinder);
605         get_location(dive, &location);
606
607         gtk_tree_store_set(GTK_TREE_STORE(model), iter,
608                 DIVE_NR, dive->number,
609                 DIVE_LOCATION, location,
610                 DIVE_CYLINDER, cylinder,
611                 DIVE_RATING, dive->rating,
612                 DIVE_SAC, dive->sac,
613                 DIVE_OTU, dive->otu,
614                 -1);
615 }
616
617 static gboolean set_one_dive(GtkTreeModel *model,
618                              GtkTreePath *path,
619                              GtkTreeIter *iter,
620                              gpointer data)
621 {
622         int idx;
623         struct dive *dive;
624
625         /* Get the dive number */
626         gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
627         if (idx < 0)
628                 return FALSE;
629         dive = get_dive(idx);
630         if (!dive)
631                 return TRUE;
632         if (data && dive != data)
633                 return FALSE;
634
635         fill_one_dive(dive, model, iter);
636         return dive == data;
637 }
638
639 void flush_divelist(struct dive *dive)
640 {
641         GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
642
643         gtk_tree_model_foreach(model, set_one_dive, dive);
644 }
645
646 void set_divelist_font(const char *font)
647 {
648         PangoFontDescription *font_desc = pango_font_description_from_string(font);
649         gtk_widget_modify_font(dive_list.tree_view, font_desc);
650         pango_font_description_free(font_desc);
651 }
652
653 void update_dive_list_units(void)
654 {
655         const char *unit;
656         GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
657
658         (void) get_depth_units(0, NULL, &unit);
659         gtk_tree_view_column_set_title(dive_list.depth, unit);
660
661         (void) get_temp_units(0, &unit);
662         gtk_tree_view_column_set_title(dive_list.temperature, unit);
663
664         gtk_tree_model_foreach(model, set_one_dive, NULL);
665 }
666
667 void update_dive_list_col_visibility(void)
668 {
669         gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
670         gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
671         gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
672         gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
673         gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
674         return;
675 }
676
677 static int new_date(struct dive *dive, struct dive **last_dive, const int flag, time_t *tm_date)
678 {
679         if (!last_dive)
680                 return TRUE;
681         if (*last_dive) {
682                 struct dive *ldive = *last_dive;
683                 struct tm tm1, tm2;
684                 (void) gmtime_r(&dive->when, &tm1);
685                 (void) gmtime_r(&ldive->when, &tm2);
686                 if (tm1.tm_year == tm2.tm_year &&
687                         (tm1.tm_mon == tm2.tm_mon || flag > NEW_MON) &&
688                         (tm1.tm_mday == tm2.tm_mday || flag > NEW_DAY))
689                         return FALSE;
690         }
691         if (flag == NEW_DAY)
692                 *last_dive = dive;
693         if (tm_date) {
694                 struct tm *tm1 = gmtime(&dive->when);
695                 tm1->tm_sec = 0;
696                 tm1->tm_min = 0;
697                 tm1->tm_hour = 0;
698                 *tm_date = mktime(tm1);
699         }
700         return TRUE;
701 }
702
703 static void fill_dive_list(void)
704 {
705         int i, j;
706         GtkTreeIter iter, parent_iter[NEW_YR + 2], *parents[NEW_YR + 2] = {NULL, };
707         GtkTreeStore *store;
708         struct dive *last_dive = NULL;
709         time_t dive_date;
710
711         store = GTK_TREE_STORE(dive_list.model);
712
713         i = dive_table.nr;
714         while (--i >= 0) {
715                 struct dive *dive = dive_table.dives[i];
716
717                 for (j = NEW_YR; j >= NEW_DAY; j--) {
718                         if (new_date(dive, &last_dive, j, &dive_date))
719                         {
720                                 gtk_tree_store_append(store, &parent_iter[j], parents[j+1]);
721                                 parents[j] = &parent_iter[j];
722                                 gtk_tree_store_set(store, parents[j],
723                                                 DIVE_INDEX, -j,
724                                                 DIVE_NR, -j,
725                                                 DIVE_DATE, dive_date,
726                                                 DIVE_LOCATION, "",
727                                                 DIVE_TEMPERATURE, 0,
728                                                 DIVE_SAC, 0,
729                                                 -1);
730                         }
731                 }
732                 update_cylinder_related_info(dive);
733                 gtk_tree_store_append(store, &iter, parents[NEW_DAY]);
734                 gtk_tree_store_set(store, &iter,
735                         DIVE_INDEX, i,
736                         DIVE_NR, dive->number,
737                         DIVE_DATE, dive->when,
738                         DIVE_DEPTH, dive->maxdepth,
739                         DIVE_DURATION, dive->duration.seconds,
740                         DIVE_LOCATION, dive->location,
741                         DIVE_RATING, dive->rating,
742                         DIVE_TEMPERATURE, dive->watertemp.mkelvin,
743                         DIVE_SAC, 0,
744                         -1);
745         }
746
747         update_dive_list_units();
748         if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
749                 GtkTreeSelection *selection;
750                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
751                 gtk_tree_selection_select_iter(selection, &iter);
752         }
753 }
754
755 void dive_list_update_dives(void)
756 {
757         gtk_tree_store_clear(GTK_TREE_STORE(dive_list.model));
758         fill_dive_list();
759         repaint_dive();
760 }
761
762 static struct divelist_column {
763         const char *header;
764         data_func_t data;
765         sort_func_t sort;
766         unsigned int flags;
767         int *visible;
768 } dl_column[] = {
769         [DIVE_NR] = { "#", nr_data_func, NULL, ALIGN_RIGHT | UNSORTABLE },
770         [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
771         [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
772         [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
773         [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
774         [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
775         [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
776         [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
777         [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
778         [DIVE_OTU] = { "OTU", otu_data_func, NULL, 0, &visible_cols.otu },
779         [DIVE_LOCATION] = { "Location", NULL, NULL, ALIGN_LEFT },
780 };
781
782
783 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col)
784 {
785         int index = col - &dl_column[0];
786         const char *title = col->header;
787         data_func_t data_func = col->data;
788         sort_func_t sort_func = col->sort;
789         unsigned int flags = col->flags;
790         int *visible = col->visible;
791         GtkWidget *tree_view = dl->tree_view;
792         GtkTreeStore *model = dl->model;
793         GtkTreeViewColumn *ret;
794
795         if (visible && !*visible)
796                 flags |= INVISIBLE;
797         ret = tree_view_column(tree_view, index, title, data_func, flags);
798         if (sort_func)
799                 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), index, sort_func, NULL, NULL);
800         return ret;
801 }
802
803 /*
804  * This is some crazy crap. The only way to get default focus seems
805  * to be to grab focus as the widget is being shown the first time.
806  */
807 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
808 {
809         gtk_widget_grab_focus(tree_view);
810 }
811
812 static void row_activated_cb(GtkTreeView *tree_view,
813                         GtkTreePath *path,
814                         GtkTreeViewColumn *column,
815                         GtkTreeModel *model)
816 {
817         int index;
818         GtkTreeIter iter;
819
820         if (!gtk_tree_model_get_iter(model, &iter, path))
821                 return;
822         gtk_tree_model_get(model, &iter, DIVE_INDEX, &index, -1);
823         /* a negative index is special for the "group by date" entries */
824         if (index >= 0)
825                 edit_dive_info(get_dive(index));
826 }
827
828 void add_dive_cb(GtkWidget *menuitem, gpointer data)
829 {
830         struct dive *dive;
831
832         dive = alloc_dive();
833         if (add_new_dive(dive)) {
834                 record_dive(dive);
835                 report_dives(TRUE);
836                 return;
837         }
838         free(dive);
839 }
840
841 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
842 {
843         GtkWidget *menu, *menuitem;
844
845         menu = gtk_menu_new();
846         menuitem = gtk_menu_item_new_with_label("Add dive");
847         g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), model);
848         gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
849         gtk_widget_show_all(menu);
850
851         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
852                 button, gtk_get_current_event_time());
853 }
854
855 static void popup_menu_cb(GtkTreeView *tree_view,
856                         GtkTreeModel *model)
857 {
858         popup_divelist_menu(tree_view, model, 0);
859 }
860
861 static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, GtkTreeModel *model)
862 {
863         /* Right-click? Bring up the menu */
864         if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3) {
865                 popup_divelist_menu(GTK_TREE_VIEW(treeview), model, 3);
866                 return TRUE;
867         }
868         return FALSE;
869 }
870
871 GtkWidget *dive_list_create(void)
872 {
873         GtkTreeSelection  *selection;
874
875         dive_list.model = gtk_tree_store_new(DIVELIST_COLUMNS,
876                                 G_TYPE_INT,                     /* index */
877                                 G_TYPE_INT,                     /* nr */
878                                 G_TYPE_INT,                     /* Date */
879                                 G_TYPE_INT,                     /* Star rating */
880                                 G_TYPE_INT,                     /* Depth */
881                                 G_TYPE_INT,                     /* Duration */
882                                 G_TYPE_INT,                     /* Temperature */
883                                 G_TYPE_STRING,                  /* Cylinder */
884                                 G_TYPE_INT,                     /* Nitrox */
885                                 G_TYPE_INT,                     /* SAC */
886                                 G_TYPE_INT,                     /* OTU */
887                                 G_TYPE_STRING                   /* Location */
888                                 );
889         dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
890         set_divelist_font(divelist_font);
891
892         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
893
894         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
895         gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
896
897         dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR);
898         dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE);
899         dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING);
900         dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH);
901         dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION);
902         dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE);
903         dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER);
904         dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX);
905         dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC);
906         dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU);
907         dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION);
908
909         fill_dive_list();
910
911         g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
912                                           "search-column", DIVE_LOCATION,
913                                           "rules-hint", TRUE,
914                                           NULL);
915
916         g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
917         g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), dive_list.model);
918         g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), dive_list.model);
919         g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), dive_list.model);
920         g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), dive_list.model);
921
922         dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
923         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
924                                GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
925         gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
926
927         dive_list.changed = 0;
928
929         return dive_list.container_widget;
930 }
931
932 void mark_divelist_changed(int changed)
933 {
934         dive_list.changed = changed;
935 }
936
937 int unsaved_changes()
938 {
939         return dive_list.changed;
940 }