]> git.tdb.fi Git - ext/subsurface.git/blob - divelist.c
Fixed another memory leak
[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         if (value == 0.0)
298                 *buffer = '\0';
299         else
300                 snprintf(buffer, sizeof(buffer), "%.*f", decimals, value);
301
302         g_object_set(renderer, "text", buffer, NULL);
303 }
304
305 static gint nitrox_sort_func(GtkTreeModel *model,
306         GtkTreeIter *iter_a,
307         GtkTreeIter *iter_b,
308         gpointer user_data)
309 {
310         int index_a, index_b;
311         struct dive *a, *b;
312         int a_o2, b_o2;
313         int a_he, b_he;
314         int a_o2low, b_o2low;
315
316         gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1);
317         gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1);
318         a = get_dive(index_a);
319         b = get_dive(index_b);
320         get_dive_gas(a, &a_o2, &a_he, &a_o2low);
321         get_dive_gas(b, &b_o2, &b_he, &b_o2low);
322
323         /* Sort by Helium first, O2 second */
324         if (a_he == b_he) {
325                 if (a_o2 == b_o2)
326                         return a_o2low - b_o2low;
327                 return a_o2 - b_o2;
328         }
329         return a_he - b_he;
330 }
331
332 #define UTF8_ELLIPSIS "\xE2\x80\xA6"
333
334 static void nitrox_data_func(GtkTreeViewColumn *col,
335                              GtkCellRenderer *renderer,
336                              GtkTreeModel *model,
337                              GtkTreeIter *iter,
338                              gpointer data)
339 {
340         int index, o2, he, o2low;
341         char buffer[80];
342         struct dive *dive;
343
344         gtk_tree_model_get(model, iter, DIVE_INDEX, &index, -1);
345         dive = get_dive(index);
346         get_dive_gas(dive, &o2, &he, &o2low);
347         o2 = (o2 + 5) / 10;
348         he = (he + 5) / 10;
349         o2low = (o2low + 5) / 10;
350
351         if (he)
352                 snprintf(buffer, sizeof(buffer), "%d/%d", o2, he);
353         else if (o2)
354                 if (o2 == o2low)
355                         snprintf(buffer, sizeof(buffer), "%d", o2);
356                 else
357                         snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
358         else
359                 strcpy(buffer, "air");
360
361         g_object_set(renderer, "text", buffer, NULL);
362 }
363
364 /* Render the SAC data (integer value of "ml / min") */
365 static void sac_data_func(GtkTreeViewColumn *col,
366                           GtkCellRenderer *renderer,
367                           GtkTreeModel *model,
368                           GtkTreeIter *iter,
369                           gpointer data)
370 {
371         int value;
372         const char *fmt;
373         char buffer[16];
374         double sac;
375
376         gtk_tree_model_get(model, iter, DIVE_SAC, &value, -1);
377
378         if (!value) {
379                 g_object_set(renderer, "text", "", NULL);
380                 return;
381         }
382
383         sac = value / 1000.0;
384         switch (output_units.volume) {
385         case LITER:
386                 fmt = "%4.1f";
387                 break;
388         case CUFT:
389                 fmt = "%4.2f";
390                 sac = ml_to_cuft(sac * 1000);
391                 break;
392         }
393         snprintf(buffer, sizeof(buffer), fmt, sac);
394
395         g_object_set(renderer, "text", buffer, NULL);
396 }
397
398 /* Render the OTU data (integer value of "OTU") */
399 static void otu_data_func(GtkTreeViewColumn *col,
400                           GtkCellRenderer *renderer,
401                           GtkTreeModel *model,
402                           GtkTreeIter *iter,
403                           gpointer data)
404 {
405         int value;
406         char buffer[16];
407
408         gtk_tree_model_get(model, iter, DIVE_OTU, &value, -1);
409
410         if (!value) {
411                 g_object_set(renderer, "text", "", NULL);
412                 return;
413         }
414
415         snprintf(buffer, sizeof(buffer), "%d", value);
416
417         g_object_set(renderer, "text", buffer, NULL);
418 }
419
420 /* calculate OTU for a dive */
421 static int calculate_otu(struct dive *dive)
422 {
423         int i;
424         double otu = 0.0;
425
426         for (i = 1; i < dive->samples; i++) {
427                 int t;
428                 double po2;
429                 struct sample *sample = dive->sample + i;
430                 struct sample *psample = sample - 1;
431                 t = sample->time.seconds - psample->time.seconds;
432                 int o2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille;
433                 if (!o2)
434                         o2 = AIR_PERMILLE;
435                 po2 = o2 / 1000.0 * (sample->depth.mm + 10000) / 10000.0;
436                 if (po2 >= 0.5)
437                         otu += pow(po2 - 0.5, 0.83) * t / 30.0;
438         }
439         return otu + 0.5;
440 }
441 /*
442  * Return air usage (in liters).
443  */
444 static double calculate_airuse(struct dive *dive)
445 {
446         double airuse = 0;
447         int i;
448
449         for (i = 0; i < MAX_CYLINDERS; i++) {
450                 pressure_t start, end;
451                 cylinder_t *cyl = dive->cylinder + i;
452                 int size = cyl->type.size.mliter;
453                 double kilo_atm;
454
455                 if (!size)
456                         continue;
457
458                 start = cyl->start.mbar ? cyl->start : cyl->sample_start;
459                 end = cyl->end.mbar ? cyl->end : cyl->sample_end;
460                 kilo_atm = (to_ATM(start) - to_ATM(end)) / 1000.0;
461
462                 /* Liters of air at 1 atm == milliliters at 1k atm*/
463                 airuse += kilo_atm * size;
464         }
465         return airuse;
466 }
467
468 static int calculate_sac(struct dive *dive)
469 {
470         double airuse, pressure, sac;
471         int duration, i;
472
473         airuse = calculate_airuse(dive);
474         if (!airuse)
475                 return 0;
476         if (!dive->duration.seconds)
477                 return 0;
478
479         /* find and eliminate long surface intervals */
480         duration = dive->duration.seconds;
481         for (i = 0; i < dive->samples; i++) {
482                 if (dive->sample[i].depth.mm < 100) { /* less than 10cm */
483                         int end = i + 1;
484                         while (end < dive->samples && dive->sample[end].depth.mm < 100)
485                                 end++;
486                         /* we only want the actual surface time during a dive */
487                         if (end < dive->samples) {
488                                 end--;
489                                 duration -= dive->sample[end].time.seconds -
490                                                 dive->sample[i].time.seconds;
491                                 i = end + 1;
492                         }
493                 }
494         }
495         /* Mean pressure in atm: 1 atm per 10m */
496         pressure = 1 + (dive->meandepth.mm / 10000.0);
497         sac = airuse / pressure * 60 / duration;
498
499         /* milliliters per minute.. */
500         return sac * 1000;
501 }
502
503 void update_cylinder_related_info(struct dive *dive)
504 {
505         if (dive != NULL) {
506                 dive->sac = calculate_sac(dive);
507                 dive->otu = calculate_otu(dive);
508         }
509 }
510
511 static void get_string(char **str, const char *s)
512 {
513         int len;
514         char *n;
515
516         if (!s)
517                 s = "";
518         len = strlen(s);
519         if (len > 60)
520                 len = 60;
521         n = malloc(len+1);
522         memcpy(n, s, len);
523         n[len] = 0;
524         *str = n;
525 }
526
527 static void get_location(struct dive *dive, char **str)
528 {
529         get_string(str, dive->location);
530 }
531
532 static void get_cylinder(struct dive *dive, char **str)
533 {
534         get_string(str, dive->cylinder[0].type.description);
535 }
536
537 /*
538  * Set up anything that could have changed due to editing
539  * of dive information
540  */
541 static void fill_one_dive(struct dive *dive,
542                           GtkTreeModel *model,
543                           GtkTreeIter *iter)
544 {
545         char *location, *cylinder;
546
547         get_cylinder(dive, &cylinder);
548         get_location(dive, &location);
549
550         gtk_list_store_set(GTK_LIST_STORE(model), iter,
551                 DIVE_NR, dive->number,
552                 DIVE_LOCATION, location,
553                 DIVE_CYLINDER, cylinder,
554                 DIVE_RATING, dive->rating,
555                 DIVE_SAC, dive->sac,
556                 DIVE_OTU, dive->otu,
557                 DIVE_TOTALWEIGHT, total_weight(dive),
558                 -1);
559
560         free(location);
561         free(cylinder);
562 }
563
564 static gboolean set_one_dive(GtkTreeModel *model,
565                              GtkTreePath *path,
566                              GtkTreeIter *iter,
567                              gpointer data)
568 {
569         GValue value = {0, };
570         struct dive *dive;
571
572         /* Get the dive number */
573         gtk_tree_model_get_value(model, iter, DIVE_INDEX, &value);
574         dive = get_dive(g_value_get_int(&value));
575         if (!dive)
576                 return TRUE;
577         if (data && dive != data)
578                 return FALSE;
579
580         fill_one_dive(dive, model, iter);
581         return dive == data;
582 }
583
584 void flush_divelist(struct dive *dive)
585 {
586         GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
587
588         gtk_tree_model_foreach(model, set_one_dive, dive);
589 }
590
591 void set_divelist_font(const char *font)
592 {
593         PangoFontDescription *font_desc = pango_font_description_from_string(font);
594         gtk_widget_modify_font(dive_list.tree_view, font_desc);
595         pango_font_description_free(font_desc);
596 }
597
598 void update_dive_list_units(void)
599 {
600         const char *unit;
601         GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
602
603         (void) get_depth_units(0, NULL, &unit);
604         gtk_tree_view_column_set_title(dive_list.depth, unit);
605
606         (void) get_temp_units(0, &unit);
607         gtk_tree_view_column_set_title(dive_list.temperature, unit);
608
609         (void) get_weight_units(0, NULL, &unit);
610         gtk_tree_view_column_set_title(dive_list.totalweight, unit);
611
612         gtk_tree_model_foreach(model, set_one_dive, NULL);
613 }
614
615 void update_dive_list_col_visibility(void)
616 {
617         gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
618         gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
619         gtk_tree_view_column_set_visible(dive_list.totalweight, visible_cols.totalweight);
620         gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
621         gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
622         gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
623         return;
624 }
625
626 static void fill_dive_list(void)
627 {
628         int i;
629         GtkTreeIter iter;
630         GtkListStore *store;
631
632         store = GTK_LIST_STORE(dive_list.model);
633
634         i = dive_table.nr;
635         while (--i >= 0) {
636                 struct dive *dive = dive_table.dives[i];
637
638                 update_cylinder_related_info(dive);
639                 gtk_list_store_append(store, &iter);
640                 gtk_list_store_set(store, &iter,
641                         DIVE_INDEX, i,
642                         DIVE_NR, dive->number,
643                         DIVE_DATE, dive->when,
644                         DIVE_DEPTH, dive->maxdepth,
645                         DIVE_DURATION, dive->duration.seconds,
646                         DIVE_LOCATION, "location",
647                         DIVE_TEMPERATURE, dive->watertemp.mkelvin,
648                         DIVE_TOTALWEIGHT, 0,
649                         DIVE_SAC, 0,
650                         -1);
651         }
652
653         update_dive_list_units();
654         if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
655                 GtkTreeSelection *selection;
656                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
657                 gtk_tree_selection_select_iter(selection, &iter);
658         }
659 }
660
661 void dive_list_update_dives(void)
662 {
663         gtk_list_store_clear(GTK_LIST_STORE(dive_list.model));
664         fill_dive_list();
665         repaint_dive();
666 }
667
668 static struct divelist_column {
669         const char *header;
670         data_func_t data;
671         sort_func_t sort;
672         unsigned int flags;
673         int *visible;
674 } dl_column[] = {
675         [DIVE_NR] = { "#", NULL, NULL, ALIGN_RIGHT | UNSORTABLE },
676         [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
677         [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
678         [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
679         [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
680         [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
681         [DIVE_TOTALWEIGHT] = { "lbs", weight_data_func, NULL, ALIGN_RIGHT, &visible_cols.totalweight },
682         [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
683         [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
684         [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
685         [DIVE_OTU] = { "OTU", otu_data_func, NULL, 0, &visible_cols.otu },
686         [DIVE_LOCATION] = { "Location", NULL, NULL, ALIGN_LEFT },
687 };
688
689
690 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col)
691 {
692         int index = col - &dl_column[0];
693         const char *title = col->header;
694         data_func_t data_func = col->data;
695         sort_func_t sort_func = col->sort;
696         unsigned int flags = col->flags;
697         int *visible = col->visible;
698         GtkWidget *tree_view = dl->tree_view;
699         GtkListStore *model = dl->model;
700         GtkTreeViewColumn *ret;
701
702         if (visible && !*visible)
703                 flags |= INVISIBLE;
704         ret = tree_view_column(tree_view, index, title, data_func, flags);
705         if (sort_func)
706                 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), index, sort_func, NULL, NULL);
707         return ret;
708 }
709
710 /*
711  * This is some crazy crap. The only way to get default focus seems
712  * to be to grab focus as the widget is being shown the first time.
713  */
714 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
715 {
716         gtk_widget_grab_focus(tree_view);
717 }
718
719 static void row_activated_cb(GtkTreeView *tree_view,
720                         GtkTreePath *path,
721                         GtkTreeViewColumn *column,
722                         GtkTreeModel *model)
723 {
724         int index;
725         GtkTreeIter iter;
726
727         if (!gtk_tree_model_get_iter(model, &iter, path))
728                 return;
729         gtk_tree_model_get(model, &iter, DIVE_INDEX, &index, -1);
730         edit_dive_info(get_dive(index));
731 }
732
733 void add_dive_cb(GtkWidget *menuitem, gpointer data)
734 {
735         struct dive *dive;
736
737         dive = alloc_dive();
738         if (add_new_dive(dive)) {
739                 record_dive(dive);
740                 report_dives(TRUE);
741                 return;
742         }
743         free(dive);
744 }
745
746 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
747 {
748         GtkWidget *menu, *menuitem;
749
750         menu = gtk_menu_new();
751         menuitem = gtk_menu_item_new_with_label("Add dive");
752         g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), model);
753         gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
754         gtk_widget_show_all(menu);
755
756         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
757                 button, gtk_get_current_event_time());
758 }
759
760 static void popup_menu_cb(GtkTreeView *tree_view,
761                         GtkTreeModel *model)
762 {
763         popup_divelist_menu(tree_view, model, 0);
764 }
765
766 static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, GtkTreeModel *model)
767 {
768         /* Right-click? Bring up the menu */
769         if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3) {
770                 popup_divelist_menu(GTK_TREE_VIEW(treeview), model, 3);
771                 return TRUE;
772         }
773         return FALSE;
774 }
775
776 GtkWidget *dive_list_create(void)
777 {
778         GtkTreeSelection  *selection;
779
780         dive_list.model = gtk_list_store_new(DIVELIST_COLUMNS,
781                                 G_TYPE_INT,                     /* index */
782                                 G_TYPE_INT,                     /* nr */
783                                 G_TYPE_INT,                     /* Date */
784                                 G_TYPE_INT,                     /* Star rating */
785                                 G_TYPE_INT,                     /* Depth */
786                                 G_TYPE_INT,                     /* Duration */
787                                 G_TYPE_INT,                     /* Temperature */
788                                 G_TYPE_INT,                     /* Total weight */
789                                 G_TYPE_STRING,                  /* Cylinder */
790                                 G_TYPE_INT,                     /* Nitrox */
791                                 G_TYPE_INT,                     /* SAC */
792                                 G_TYPE_INT,                     /* OTU */
793                                 G_TYPE_STRING                   /* Location */
794                                 );
795         dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
796         set_divelist_font(divelist_font);
797
798         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
799
800         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
801         gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
802
803         dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR);
804         dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE);
805         dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING);
806         dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH);
807         dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION);
808         dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE);
809         dive_list.totalweight = divelist_column(&dive_list, dl_column + DIVE_TOTALWEIGHT);
810         dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER);
811         dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX);
812         dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC);
813         dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU);
814         dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION);
815
816         fill_dive_list();
817
818         g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
819                                           "search-column", DIVE_LOCATION,
820                                           "rules-hint", TRUE,
821                                           NULL);
822
823         g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
824         g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), dive_list.model);
825         g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), dive_list.model);
826         g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), dive_list.model);
827         g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), dive_list.model);
828
829         dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
830         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
831                                GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
832         gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
833
834         dive_list.changed = 0;
835
836         return dive_list.container_widget;
837 }
838
839 void mark_divelist_changed(int changed)
840 {
841         dive_list.changed = changed;
842 }
843
844 int unsaved_changes()
845 {
846         return dive_list.changed;
847 }