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