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