]> git.tdb.fi Git - ext/subsurface.git/blob - divelist.c
ce61d20d16d75fd835a8f0777bd2685dac998763
[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         GtkListStore *model;
28         GtkTreeViewColumn *nr, *date, *stars, *depth, *duration, *location;
29         GtkTreeViewColumn *temperature, *cylinder, *totalweight, *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_CYLINDER,
49         DIVE_NITROX,            /* int: dummy */
50         DIVE_SAC,               /* int: in ml/min */
51         DIVE_OTU,               /* int: in OTUs */
52         DIVE_LOCATION,          /* "2nd Cathedral, Lanai" */
53         DIVELIST_COLUMNS
54 };
55
56 static GList *selected_dives;
57
58 static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model)
59 {
60         GtkTreeIter iter;
61         GValue value = {0, };
62         GtkTreePath *path;
63
64         int nr_selected = gtk_tree_selection_count_selected_rows(selection);
65
66         if (selected_dives) {
67                 g_list_foreach (selected_dives, (GFunc) gtk_tree_path_free, NULL);
68                 g_list_free (selected_dives);
69         }
70         selected_dives = gtk_tree_selection_get_selected_rows(selection, NULL);
71
72         switch (nr_selected) {
73         case 0: /* keep showing the last selected dive */
74                 return;
75         case 1: 
76                 /* just pick that dive as selected */
77                 amount_selected = 1;
78                 path = g_list_nth_data(selected_dives, 0);
79                 if (gtk_tree_model_get_iter(model, &iter, path)) {
80                         gtk_tree_model_get_value(model, &iter, DIVE_INDEX, &value);
81                         selected_dive = g_value_get_int(&value);
82                         repaint_dive();
83                 }
84                 return;
85         default: /* multiple selections - what now? At this point I
86                   * don't want to change the selected dive unless
87                   * there is exactly one dive selected; not sure this
88                   * is the most intuitive solution.
89                   * I do however want to keep around which dives have
90                   * been selected */
91                 amount_selected = g_list_length(selected_dives);
92                 process_selected_dives(selected_dives, model);
93                 repaint_dive();
94                 return;
95         }
96 }
97
98 const char *star_strings[] = {
99         ZERO_STARS,
100         ONE_STARS,
101         TWO_STARS,
102         THREE_STARS,
103         FOUR_STARS,
104         FIVE_STARS
105 };
106
107 static void star_data_func(GtkTreeViewColumn *col,
108                            GtkCellRenderer *renderer,
109                            GtkTreeModel *model,
110                            GtkTreeIter *iter,
111                            gpointer data)
112 {
113         int nr_stars;
114         char buffer[40];
115
116         gtk_tree_model_get(model, iter, DIVE_RATING, &nr_stars, -1);
117         if (nr_stars < 0 || nr_stars > 5)
118                 nr_stars = 0;
119         snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]);
120         g_object_set(renderer, "text", buffer, NULL);
121 }
122
123 static void date_data_func(GtkTreeViewColumn *col,
124                            GtkCellRenderer *renderer,
125                            GtkTreeModel *model,
126                            GtkTreeIter *iter,
127                            gpointer data)
128 {
129         int val;
130         struct tm *tm;
131         time_t when;
132         char buffer[40];
133
134         gtk_tree_model_get(model, iter, DIVE_DATE, &val, -1);
135
136         /* 2038 problem */
137         when = val;
138
139         tm = gmtime(&when);
140         snprintf(buffer, sizeof(buffer),
141                 "%s, %s %d, %d %02d:%02d",
142                 weekday(tm->tm_wday),
143                 monthname(tm->tm_mon),
144                 tm->tm_mday, tm->tm_year + 1900,
145                 tm->tm_hour, tm->tm_min);
146         g_object_set(renderer, "text", buffer, NULL);
147 }
148
149 static void depth_data_func(GtkTreeViewColumn *col,
150                             GtkCellRenderer *renderer,
151                             GtkTreeModel *model,
152                             GtkTreeIter *iter,
153                             gpointer data)
154 {
155         int depth, integer, frac, len;
156         char buffer[40];
157
158         gtk_tree_model_get(model, iter, DIVE_DEPTH, &depth, -1);
159
160         switch (output_units.length) {
161         case METERS:
162                 /* To tenths of meters */
163                 depth = (depth + 49) / 100;
164                 integer = depth / 10;
165                 frac = depth % 10;
166                 if (integer < 20)
167                         break;
168                 if (frac >= 5)
169                         integer++;
170                 frac = -1;
171                 break;
172         case FEET:
173                 integer = mm_to_feet(depth) + 0.5;
174                 frac = -1;
175                 break;
176         default:
177                 return;
178         }
179         len = snprintf(buffer, sizeof(buffer), "%d", integer);
180         if (frac >= 0)
181                 len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
182
183         g_object_set(renderer, "text", buffer, NULL);
184 }
185
186 static void duration_data_func(GtkTreeViewColumn *col,
187                                GtkCellRenderer *renderer,
188                                GtkTreeModel *model,
189                                GtkTreeIter *iter,
190                                gpointer data)
191 {
192         unsigned int sec;
193         char buffer[16];
194
195         gtk_tree_model_get(model, iter, DIVE_DURATION, &sec, -1);
196         snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
197
198         g_object_set(renderer, "text", buffer, NULL);
199 }
200
201 static void temperature_data_func(GtkTreeViewColumn *col,
202                                   GtkCellRenderer *renderer,
203                                   GtkTreeModel *model,
204                                   GtkTreeIter *iter,
205                                   gpointer data)
206 {
207         int value;
208         char buffer[80];
209
210         gtk_tree_model_get(model, iter, DIVE_TEMPERATURE, &value, -1);
211
212         *buffer = 0;
213         if (value) {
214                 double deg;
215                 switch (output_units.temperature) {
216                 case CELSIUS:
217                         deg = mkelvin_to_C(value);
218                         break;
219                 case FAHRENHEIT:
220                         deg = mkelvin_to_F(value);
221                         break;
222                 default:
223                         return;
224                 }
225                 snprintf(buffer, sizeof(buffer), "%.1f", deg);
226         }
227
228         g_object_set(renderer, "text", buffer, NULL);
229 }
230
231 /*
232  * Get "maximal" dive gas for a dive.
233  * Rules:
234  *  - Trimix trumps nitrox (highest He wins, O2 breaks ties)
235  *  - Nitrox trumps air (even if hypoxic)
236  * These are the same rules as the inter-dive sorting rules.
237  */
238 static void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
239 {
240         int i;
241         int maxo2 = -1, maxhe = -1, mino2 = 1000;
242
243         for (i = 0; i < MAX_CYLINDERS; i++) {
244                 cylinder_t *cyl = dive->cylinder + i;
245                 struct gasmix *mix = &cyl->gasmix;
246                 int o2 = mix->o2.permille;
247                 int he = mix->he.permille;
248
249                 if (cylinder_none(cyl))
250                         continue;
251                 if (!o2)
252                         o2 = AIR_PERMILLE;
253                 if (o2 < mino2)
254                         mino2 = o2;
255                 if (he > maxhe)
256                         goto newmax;
257                 if (he < maxhe)
258                         continue;
259                 if (o2 <= maxo2)
260                         continue;
261 newmax:
262                 maxhe = he;
263                 maxo2 = o2;
264         }
265         /* All air? Show/sort as "air"/zero */
266         if (!maxhe && maxo2 == AIR_PERMILLE && mino2 == maxo2)
267                 maxo2 = mino2 = 0;
268         *o2_p = maxo2;
269         *he_p = maxhe;
270         *o2low_p = mino2;
271 }
272
273 static int total_weight(struct dive *dive)
274 {
275         int i, total_grams = 0;
276
277         if (dive)
278                 for (i=0; i< MAX_WEIGHTSYSTEMS; i++)
279                         total_grams += dive->weightsystem[i].weight.grams;
280         return total_grams;
281 }
282
283 static void weight_data_func(GtkTreeViewColumn *col,
284                              GtkCellRenderer *renderer,
285                              GtkTreeModel *model,
286                              GtkTreeIter *iter,
287                              gpointer data)
288 {
289         int indx, decimals;
290         double value;
291         char buffer[80];
292         struct dive *dive;
293
294         gtk_tree_model_get(model, iter, DIVE_INDEX, &indx, -1);
295         dive = get_dive(indx);
296         value = get_weight_units(total_weight(dive), &decimals, NULL);
297         snprintf(buffer, sizeof(buffer), "%.*f", decimals, value);
298
299         g_object_set(renderer, "text", buffer, NULL);
300 }
301
302 static gint nitrox_sort_func(GtkTreeModel *model,
303         GtkTreeIter *iter_a,
304         GtkTreeIter *iter_b,
305         gpointer user_data)
306 {
307         int index_a, index_b;
308         struct dive *a, *b;
309         int a_o2, b_o2;
310         int a_he, b_he;
311         int a_o2low, b_o2low;
312
313         gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1);
314         gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1);
315         a = get_dive(index_a);
316         b = get_dive(index_b);
317         get_dive_gas(a, &a_o2, &a_he, &a_o2low);
318         get_dive_gas(b, &b_o2, &b_he, &b_o2low);
319
320         /* Sort by Helium first, O2 second */
321         if (a_he == b_he) {
322                 if (a_o2 == b_o2)
323                         return a_o2low - b_o2low;
324                 return a_o2 - b_o2;
325         }
326         return a_he - b_he;
327 }
328
329 #define UTF8_ELLIPSIS "\xE2\x80\xA6"
330
331 static void nitrox_data_func(GtkTreeViewColumn *col,
332                              GtkCellRenderer *renderer,
333                              GtkTreeModel *model,
334                              GtkTreeIter *iter,
335                              gpointer data)
336 {
337         int index, o2, he, o2low;
338         char buffer[80];
339         struct dive *dive;
340
341         gtk_tree_model_get(model, iter, DIVE_INDEX, &index, -1);
342         dive = get_dive(index);
343         get_dive_gas(dive, &o2, &he, &o2low);
344         o2 = (o2 + 5) / 10;
345         he = (he + 5) / 10;
346         o2low = (o2low + 5) / 10;
347
348         if (he)
349                 snprintf(buffer, sizeof(buffer), "%d/%d", o2, he);
350         else if (o2)
351                 if (o2 == o2low)
352                         snprintf(buffer, sizeof(buffer), "%d", o2);
353                 else
354                         snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
355         else
356                 strcpy(buffer, "air");
357
358         g_object_set(renderer, "text", buffer, NULL);
359 }
360
361 /* Render the SAC data (integer value of "ml / min") */
362 static void sac_data_func(GtkTreeViewColumn *col,
363                           GtkCellRenderer *renderer,
364                           GtkTreeModel *model,
365                           GtkTreeIter *iter,
366                           gpointer data)
367 {
368         int value;
369         const char *fmt;
370         char buffer[16];
371         double sac;
372
373         gtk_tree_model_get(model, iter, DIVE_SAC, &value, -1);
374
375         if (!value) {
376                 g_object_set(renderer, "text", "", NULL);
377                 return;
378         }
379
380         sac = value / 1000.0;
381         switch (output_units.volume) {
382         case LITER:
383                 fmt = "%4.1f";
384                 break;
385         case CUFT:
386                 fmt = "%4.2f";
387                 sac = ml_to_cuft(sac * 1000);
388                 break;
389         }
390         snprintf(buffer, sizeof(buffer), fmt, sac);
391
392         g_object_set(renderer, "text", buffer, NULL);
393 }
394
395 /* Render the OTU data (integer value of "OTU") */
396 static void otu_data_func(GtkTreeViewColumn *col,
397                           GtkCellRenderer *renderer,
398                           GtkTreeModel *model,
399                           GtkTreeIter *iter,
400                           gpointer data)
401 {
402         int value;
403         char buffer[16];
404
405         gtk_tree_model_get(model, iter, DIVE_OTU, &value, -1);
406
407         if (!value) {
408                 g_object_set(renderer, "text", "", NULL);
409                 return;
410         }
411
412         snprintf(buffer, sizeof(buffer), "%d", value);
413
414         g_object_set(renderer, "text", buffer, NULL);
415 }
416
417 /* calculate OTU for a dive */
418 static int calculate_otu(struct dive *dive)
419 {
420         int i;
421         double otu = 0.0;
422
423         for (i = 1; i < dive->samples; i++) {
424                 int t;
425                 double po2;
426                 struct sample *sample = dive->sample + i;
427                 struct sample *psample = sample - 1;
428                 t = sample->time.seconds - psample->time.seconds;
429                 int o2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille;
430                 if (!o2)
431                         o2 = AIR_PERMILLE;
432                 po2 = o2 / 1000.0 * (sample->depth.mm + 10000) / 10000.0;
433                 if (po2 >= 0.5)
434                         otu += pow(po2 - 0.5, 0.83) * t / 30.0;
435         }
436         return otu + 0.5;
437 }
438 /*
439  * Return air usage (in liters).
440  */
441 static double calculate_airuse(struct dive *dive)
442 {
443         double airuse = 0;
444         int i;
445
446         for (i = 0; i < MAX_CYLINDERS; i++) {
447                 pressure_t start, end;
448                 cylinder_t *cyl = dive->cylinder + i;
449                 int size = cyl->type.size.mliter;
450                 double kilo_atm;
451
452                 if (!size)
453                         continue;
454
455                 start = cyl->start.mbar ? cyl->start : cyl->sample_start;
456                 end = cyl->end.mbar ? cyl->end : cyl->sample_end;
457                 kilo_atm = (to_ATM(start) - to_ATM(end)) / 1000.0;
458
459                 /* Liters of air at 1 atm == milliliters at 1k atm*/
460                 airuse += kilo_atm * size;
461         }
462         return airuse;
463 }
464
465 static int calculate_sac(struct dive *dive)
466 {
467         double airuse, pressure, sac;
468         int duration, i;
469
470         airuse = calculate_airuse(dive);
471         if (!airuse)
472                 return 0;
473         if (!dive->duration.seconds)
474                 return 0;
475
476         /* find and eliminate long surface intervals */
477         duration = dive->duration.seconds;
478         for (i = 0; i < dive->samples; i++) {
479                 if (dive->sample[i].depth.mm < 100) { /* less than 10cm */
480                         int end = i + 1;
481                         while (end < dive->samples && dive->sample[end].depth.mm < 100)
482                                 end++;
483                         /* we only want the actual surface time during a dive */
484                         if (end < dive->samples) {
485                                 end--;
486                                 duration -= dive->sample[end].time.seconds -
487                                                 dive->sample[i].time.seconds;
488                                 i = end + 1;
489                         }
490                 }
491         }
492         /* Mean pressure in atm: 1 atm per 10m */
493         pressure = 1 + (dive->meandepth.mm / 10000.0);
494         sac = airuse / pressure * 60 / duration;
495
496         /* milliliters per minute.. */
497         return sac * 1000;
498 }
499
500 void update_cylinder_related_info(struct dive *dive)
501 {
502         if (dive != NULL) {
503                 dive->sac = calculate_sac(dive);
504                 dive->otu = calculate_otu(dive);
505         }
506 }
507
508 static void get_string(char **str, const char *s)
509 {
510         int len;
511         char *n;
512
513         if (!s)
514                 s = "";
515         len = strlen(s);
516         if (len > 60)
517                 len = 60;
518         n = malloc(len+1);
519         memcpy(n, s, len);
520         n[len] = 0;
521         *str = n;
522 }
523
524 static void get_location(struct dive *dive, char **str)
525 {
526         get_string(str, dive->location);
527 }
528
529 static void get_cylinder(struct dive *dive, char **str)
530 {
531         get_string(str, dive->cylinder[0].type.description);
532 }
533
534 /*
535  * Set up anything that could have changed due to editing
536  * of dive information
537  */
538 static void fill_one_dive(struct dive *dive,
539                           GtkTreeModel *model,
540                           GtkTreeIter *iter)
541 {
542         char *location, *cylinder;
543
544         get_cylinder(dive, &cylinder);
545         get_location(dive, &location);
546
547         gtk_list_store_set(GTK_LIST_STORE(model), iter,
548                 DIVE_NR, dive->number,
549                 DIVE_LOCATION, location,
550                 DIVE_CYLINDER, cylinder,
551                 DIVE_RATING, dive->rating,
552                 DIVE_SAC, dive->sac,
553                 DIVE_OTU, dive->otu,
554                 DIVE_TOTALWEIGHT, total_weight(dive),
555                 -1);
556 }
557
558 static gboolean set_one_dive(GtkTreeModel *model,
559                              GtkTreePath *path,
560                              GtkTreeIter *iter,
561                              gpointer data)
562 {
563         GValue value = {0, };
564         struct dive *dive;
565
566         /* Get the dive number */
567         gtk_tree_model_get_value(model, iter, DIVE_INDEX, &value);
568         dive = get_dive(g_value_get_int(&value));
569         if (!dive)
570                 return TRUE;
571         if (data && dive != data)
572                 return FALSE;
573
574         fill_one_dive(dive, model, iter);
575         return dive == data;
576 }
577
578 void flush_divelist(struct dive *dive)
579 {
580         GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
581
582         gtk_tree_model_foreach(model, set_one_dive, dive);
583 }
584
585 void set_divelist_font(const char *font)
586 {
587         PangoFontDescription *font_desc = pango_font_description_from_string(font);
588         gtk_widget_modify_font(dive_list.tree_view, font_desc);
589         pango_font_description_free(font_desc);
590 }
591
592 void update_dive_list_units(void)
593 {
594         const char *unit;
595         GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
596
597         (void) get_depth_units(0, NULL, &unit);
598         gtk_tree_view_column_set_title(dive_list.depth, unit);
599
600         (void) get_temp_units(0, &unit);
601         gtk_tree_view_column_set_title(dive_list.temperature, unit);
602
603         (void) get_weight_units(0, NULL, &unit);
604         gtk_tree_view_column_set_title(dive_list.totalweight, unit);
605
606         gtk_tree_model_foreach(model, set_one_dive, NULL);
607 }
608
609 void update_dive_list_col_visibility(void)
610 {
611         gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
612         gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
613         gtk_tree_view_column_set_visible(dive_list.totalweight, visible_cols.totalweight);
614         gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
615         gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
616         gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
617         return;
618 }
619
620 static void fill_dive_list(void)
621 {
622         int i;
623         GtkTreeIter iter;
624         GtkListStore *store;
625
626         store = GTK_LIST_STORE(dive_list.model);
627
628         i = dive_table.nr;
629         while (--i >= 0) {
630                 struct dive *dive = dive_table.dives[i];
631
632                 update_cylinder_related_info(dive);
633                 gtk_list_store_append(store, &iter);
634                 gtk_list_store_set(store, &iter,
635                         DIVE_INDEX, i,
636                         DIVE_NR, dive->number,
637                         DIVE_DATE, dive->when,
638                         DIVE_DEPTH, dive->maxdepth,
639                         DIVE_DURATION, dive->duration.seconds,
640                         DIVE_LOCATION, "location",
641                         DIVE_TEMPERATURE, dive->watertemp.mkelvin,
642                         DIVE_TOTALWEIGHT, 0,
643                         DIVE_SAC, 0,
644                         -1);
645         }
646
647         update_dive_list_units();
648         if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
649                 GtkTreeSelection *selection;
650                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
651                 gtk_tree_selection_select_iter(selection, &iter);
652         }
653 }
654
655 void dive_list_update_dives(void)
656 {
657         gtk_list_store_clear(GTK_LIST_STORE(dive_list.model));
658         fill_dive_list();
659         repaint_dive();
660 }
661
662 static struct divelist_column {
663         const char *header;
664         data_func_t data;
665         sort_func_t sort;
666         unsigned int flags;
667         int *visible;
668 } dl_column[] = {
669         [DIVE_NR] = { "#", NULL, NULL, ALIGN_RIGHT | UNSORTABLE },
670         [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
671         [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
672         [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
673         [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
674         [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
675         [DIVE_TOTALWEIGHT] = { "lbs", weight_data_func, NULL, ALIGN_RIGHT, &visible_cols.totalweight },
676         [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
677         [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
678         [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
679         [DIVE_OTU] = { "OTU", otu_data_func, NULL, 0, &visible_cols.otu },
680         [DIVE_LOCATION] = { "Location", NULL, NULL, ALIGN_LEFT },
681 };
682
683
684 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col)
685 {
686         int index = col - &dl_column[0];
687         const char *title = col->header;
688         data_func_t data_func = col->data;
689         sort_func_t sort_func = col->sort;
690         unsigned int flags = col->flags;
691         int *visible = col->visible;
692         GtkWidget *tree_view = dl->tree_view;
693         GtkListStore *model = dl->model;
694         GtkTreeViewColumn *ret;
695
696         if (visible && !*visible)
697                 flags |= INVISIBLE;
698         ret = tree_view_column(tree_view, index, title, data_func, flags);
699         if (sort_func)
700                 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), index, sort_func, NULL, NULL);
701         return ret;
702 }
703
704 /*
705  * This is some crazy crap. The only way to get default focus seems
706  * to be to grab focus as the widget is being shown the first time.
707  */
708 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
709 {
710         gtk_widget_grab_focus(tree_view);
711 }
712
713 static void row_activated_cb(GtkTreeView *tree_view,
714                         GtkTreePath *path,
715                         GtkTreeViewColumn *column,
716                         GtkTreeModel *model)
717 {
718         int index;
719         GtkTreeIter iter;
720
721         if (!gtk_tree_model_get_iter(model, &iter, path))
722                 return;
723         gtk_tree_model_get(model, &iter, DIVE_INDEX, &index, -1);
724         edit_dive_info(get_dive(index));
725 }
726
727 void add_dive_cb(GtkWidget *menuitem, gpointer data)
728 {
729         struct dive *dive;
730
731         dive = alloc_dive();
732         if (add_new_dive(dive)) {
733                 record_dive(dive);
734                 report_dives(TRUE);
735                 return;
736         }
737         free(dive);
738 }
739
740 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
741 {
742         GtkWidget *menu, *menuitem;
743
744         menu = gtk_menu_new();
745         menuitem = gtk_menu_item_new_with_label("Add dive");
746         g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), model);
747         gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
748         gtk_widget_show_all(menu);
749
750         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
751                 button, gtk_get_current_event_time());
752 }
753
754 static void popup_menu_cb(GtkTreeView *tree_view,
755                         GtkTreeModel *model)
756 {
757         popup_divelist_menu(tree_view, model, 0);
758 }
759
760 static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, GtkTreeModel *model)
761 {
762         /* Right-click? Bring up the menu */
763         if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3) {
764                 popup_divelist_menu(GTK_TREE_VIEW(treeview), model, 3);
765                 return TRUE;
766         }
767         return FALSE;
768 }
769
770 GtkWidget *dive_list_create(void)
771 {
772         GtkTreeSelection  *selection;
773
774         dive_list.model = gtk_list_store_new(DIVELIST_COLUMNS,
775                                 G_TYPE_INT,                     /* index */
776                                 G_TYPE_INT,                     /* nr */
777                                 G_TYPE_INT,                     /* Date */
778                                 G_TYPE_INT,                     /* Star rating */
779                                 G_TYPE_INT,                     /* Depth */
780                                 G_TYPE_INT,                     /* Duration */
781                                 G_TYPE_INT,                     /* Temperature */
782                                 G_TYPE_INT,                     /* Total weight */
783                                 G_TYPE_STRING,                  /* Cylinder */
784                                 G_TYPE_INT,                     /* Nitrox */
785                                 G_TYPE_INT,                     /* SAC */
786                                 G_TYPE_INT,                     /* OTU */
787                                 G_TYPE_STRING                   /* Location */
788                                 );
789         dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
790         set_divelist_font(divelist_font);
791
792         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
793
794         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
795         gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
796
797         dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR);
798         dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE);
799         dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING);
800         dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH);
801         dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION);
802         dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE);
803         dive_list.totalweight = divelist_column(&dive_list, dl_column + DIVE_TOTALWEIGHT);
804         dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER);
805         dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX);
806         dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC);
807         dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU);
808         dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION);
809
810         fill_dive_list();
811
812         g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
813                                           "search-column", DIVE_LOCATION,
814                                           "rules-hint", TRUE,
815                                           NULL);
816
817         g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
818         g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), dive_list.model);
819         g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), dive_list.model);
820         g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), dive_list.model);
821         g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), dive_list.model);
822
823         dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
824         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
825                                GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
826         gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
827
828         dive_list.changed = 0;
829
830         return dive_list.container_widget;
831 }
832
833 void mark_divelist_changed(int changed)
834 {
835         dive_list.changed = changed;
836 }
837
838 int unsaved_changes()
839 {
840         return dive_list.changed;
841 }