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