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