2 /* this creates the UI for the dive list -
3 * controlled through the following interfaces:
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()
22 #include "display-gtk.h"
26 GtkWidget *container_widget;
27 GtkTreeStore *model, *listmodel, *treemodel;
28 GtkTreeViewColumn *nr, *date, *stars, *depth, *duration, *location;
29 GtkTreeViewColumn *temperature, *cylinder, *totalweight, *suit, *nitrox, *sac, *otu;
33 static struct DiveList dive_list;
36 * The dive list has the dive data in both string format (for showing)
37 * and in "raw" format (for sorting purposes)
41 DIVE_NR, /* int: dive->nr */
42 DIVE_DATE, /* time_t: dive->when */
43 DIVE_RATING, /* int: 0-5 stars */
44 DIVE_DEPTH, /* int: dive->maxdepth in mm */
45 DIVE_DURATION, /* int: in seconds */
46 DIVE_TEMPERATURE, /* int: in mkelvin */
47 DIVE_TOTALWEIGHT, /* int: in grams */
48 DIVE_SUIT, /* "wet, 3mm" */
50 DIVE_NITROX, /* int: dummy */
51 DIVE_SAC, /* int: in ml/min */
52 DIVE_OTU, /* int: in OTUs */
53 DIVE_LOCATION, /* "2nd Cathedral, Lanai" */
57 /* magic numbers that indicate (as negative values) model entries that
58 * are summary entries for a divetrip */
62 static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path,
63 GtkTreeIter *iter, gpointer data)
66 int idx, nr, rating, depth;
68 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, DIVE_RATING, &rating, DIVE_DEPTH, &depth, DIVE_LOCATION, &location, -1);
69 printf("entry #%d : nr %d rating %d depth %d location %s \n", idx, nr, rating, depth, location);
75 static void dump_model(GtkListStore *store)
77 gtk_tree_model_foreach(GTK_TREE_MODEL(store), dump_model_entry, NULL);
81 static GList *selected_dives;
82 static int st_size = 0;
84 gboolean is_in_st(int idx, int *atpos)
88 for (i = 0; i < amount_selected; i++)
89 if (selectiontracker[i] == idx) {
97 #if DEBUG_SELECTION_TRACKING
98 void dump_selection(void)
102 printf("currently selected are ");
103 for (i = 0; i < amount_selected; i++)
104 printf("%d ", selectiontracker[i]);
109 void track_select(int idx)
114 #if DEBUG_SELECTION_TRACKING
115 printf("add %d to selection of %d entries\n", idx, amount_selected);
117 if (is_in_st(idx, NULL))
119 if (amount_selected >= st_size) {
120 selectiontracker = realloc(selectiontracker, dive_table.nr * sizeof(int));
121 st_size = dive_table.nr;
123 selectiontracker[amount_selected] = idx;
125 if (amount_selected == 1)
127 #if DEBUG_SELECTION_TRACKING
128 printf("increased amount_selected to %d\n", amount_selected);
133 void track_unselect(int idx)
138 #if DEBUG_SELECTION_TRACKING
139 printf("remove %d from selection of %d entries\n", idx, amount_selected);
143 if (! is_in_st(idx, &atpos))
145 memmove(selectiontracker + atpos,
146 selectiontracker + atpos + 1,
147 (amount_selected - atpos - 1) * sizeof(int));
149 #if DEBUG_SELECTION_TRACKING
150 printf("removed %d at pos %d and decreased amount_selected to %d\n", idx, atpos, amount_selected);
155 /* when subsurface starts we want to have the last dive selected. So we simply
156 walk to the first leaf (and skip the summary entries - which have negative
158 static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx)
163 while (*diveidx < 0) {
164 memcpy(&parent, iter, sizeof(parent));
165 tpath = gtk_tree_model_get_path(model, &parent);
166 if (!gtk_tree_model_iter_children(model, iter, &parent))
167 /* we should never have a parent without child */
169 if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath))
170 gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
171 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, DIVE_INDEX, diveidx, -1);
175 /* if we click on a summary dive, we actually want to select / unselect
176 all the dives "below" it */
177 static void select_children(GtkTreeModel *model, GtkTreeSelection * selection,
178 GtkTreeIter *iter, gboolean was_selected)
181 gboolean expanded = FALSE;
185 memcpy(&parent, iter, sizeof(parent));
187 tpath = gtk_tree_model_get_path(model, &parent);
188 expanded = gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath);
189 nr_children = gtk_tree_model_iter_n_children(model, &parent);
190 for (i = 0; i < nr_children; i++) {
191 gtk_tree_model_iter_nth_child(model, iter, &parent, i);
193 /* if the parent is expanded, just (un)select the children and we'll
194 track their selection status in the callback
195 otherwise just change the selection status directly without
199 gtk_tree_selection_unselect_iter(selection, iter);
201 gtk_tree_selection_select_iter(selection, iter);
204 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
213 /* make sure that if we expand a summary row that is selected, the children show
214 up as selected, too */
215 void row_expanded_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data)
217 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
219 if (gtk_tree_selection_path_is_selected(selection, path))
220 select_children(GTK_TREE_MODEL(dive_list.model), selection, iter, FALSE);
223 /* this is called _before_ the selection is changed, for every single entry;
224 * we simply have it call down the tree to make sure that summary items toggle
226 gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model,
227 GtkTreePath *path, gboolean was_selected, gpointer userdata)
232 if (gtk_tree_model_get_iter(model, &iter, path)) {
233 gtk_tree_model_get(model, &iter, DIVE_INDEX, &dive_idx, -1);
234 /* turns out we need to move the selectiontracker here */
236 #if DEBUG_SELECTION_TRACKING
237 printf("modify_selection_cb with idx %d (according to gtk was %sselected) - ",
238 dive_idx, was_selected ? "" : "un");
242 track_unselect(dive_idx);
244 track_select(dive_idx);
246 select_children(model, selection, &iter, was_selected);
249 /* allow this selection to proceed */
253 /* this is called when gtk thinks that the selection has changed */
254 static void selection_cb(GtkTreeSelection *selection, gpointer userdata)
256 process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model));
260 const char *star_strings[] = {
269 static void star_data_func(GtkTreeViewColumn *col,
270 GtkCellRenderer *renderer,
278 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_RATING, &nr_stars, -1);
282 if (nr_stars < 0 || nr_stars > 5)
284 snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]);
286 g_object_set(renderer, "text", buffer, NULL);
289 static void date_data_func(GtkTreeViewColumn *col,
290 GtkCellRenderer *renderer,
300 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &val, -1);
308 snprintf(buffer, sizeof(buffer),
309 "Trip %s, %s %d, %d",
310 weekday(tm->tm_wday),
311 monthname(tm->tm_mon),
312 tm->tm_mday, tm->tm_year + 1900);
315 snprintf(buffer, sizeof(buffer),
316 "%s, %s %d, %d %02d:%02d",
317 weekday(tm->tm_wday),
318 monthname(tm->tm_mon),
319 tm->tm_mday, tm->tm_year + 1900,
320 tm->tm_hour, tm->tm_min);
322 g_object_set(renderer, "text", buffer, NULL);
325 static void depth_data_func(GtkTreeViewColumn *col,
326 GtkCellRenderer *renderer,
331 int depth, integer, frac, len, idx;
334 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1);
339 switch (output_units.length) {
341 /* To tenths of meters */
342 depth = (depth + 49) / 100;
343 integer = depth / 10;
352 integer = mm_to_feet(depth) + 0.5;
358 len = snprintf(buffer, sizeof(buffer), "%d", integer);
360 len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
362 g_object_set(renderer, "text", buffer, NULL);
365 static void duration_data_func(GtkTreeViewColumn *col,
366 GtkCellRenderer *renderer,
375 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1);
379 snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
381 g_object_set(renderer, "text", buffer, NULL);
384 static void temperature_data_func(GtkTreeViewColumn *col,
385 GtkCellRenderer *renderer,
393 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1);
396 if (idx >= 0 && value) {
398 switch (output_units.temperature) {
400 deg = mkelvin_to_C(value);
403 deg = mkelvin_to_F(value);
408 snprintf(buffer, sizeof(buffer), "%.1f", deg);
411 g_object_set(renderer, "text", buffer, NULL);
414 static void nr_data_func(GtkTreeViewColumn *col,
415 GtkCellRenderer *renderer,
423 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1);
427 snprintf(buffer, sizeof(buffer), "%d", nr);
428 g_object_set(renderer, "text", buffer, NULL);
432 * Get "maximal" dive gas for a dive.
434 * - Trimix trumps nitrox (highest He wins, O2 breaks ties)
435 * - Nitrox trumps air (even if hypoxic)
436 * These are the same rules as the inter-dive sorting rules.
438 static void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
441 int maxo2 = -1, maxhe = -1, mino2 = 1000;
443 for (i = 0; i < MAX_CYLINDERS; i++) {
444 cylinder_t *cyl = dive->cylinder + i;
445 struct gasmix *mix = &cyl->gasmix;
446 int o2 = mix->o2.permille;
447 int he = mix->he.permille;
449 if (cylinder_none(cyl))
465 /* All air? Show/sort as "air"/zero */
466 if (!maxhe && maxo2 == AIR_PERMILLE && mino2 == maxo2)
473 static int total_weight(struct dive *dive)
475 int i, total_grams = 0;
478 for (i=0; i< MAX_WEIGHTSYSTEMS; i++)
479 total_grams += dive->weightsystem[i].weight.grams;
483 static void weight_data_func(GtkTreeViewColumn *col,
484 GtkCellRenderer *renderer,
494 gtk_tree_model_get(model, iter, DIVE_INDEX, &indx, -1);
495 dive = get_dive(indx);
496 value = get_weight_units(total_weight(dive), &decimals, NULL);
500 snprintf(buffer, sizeof(buffer), "%.*f", decimals, value);
502 g_object_set(renderer, "text", buffer, NULL);
505 static gint nitrox_sort_func(GtkTreeModel *model,
510 int index_a, index_b;
514 int a_o2low, b_o2low;
516 gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1);
517 gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1);
518 a = get_dive(index_a);
519 b = get_dive(index_b);
520 get_dive_gas(a, &a_o2, &a_he, &a_o2low);
521 get_dive_gas(b, &b_o2, &b_he, &b_o2low);
523 /* Sort by Helium first, O2 second */
526 return a_o2low - b_o2low;
532 #define UTF8_ELLIPSIS "\xE2\x80\xA6"
534 static void nitrox_data_func(GtkTreeViewColumn *col,
535 GtkCellRenderer *renderer,
540 int idx, o2, he, o2low;
544 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
549 dive = get_dive(idx);
550 get_dive_gas(dive, &o2, &he, &o2low);
553 o2low = (o2low + 5) / 10;
556 snprintf(buffer, sizeof(buffer), "%d/%d", o2, he);
559 snprintf(buffer, sizeof(buffer), "%d", o2);
561 snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
563 strcpy(buffer, "air");
565 g_object_set(renderer, "text", buffer, NULL);
568 /* Render the SAC data (integer value of "ml / min") */
569 static void sac_data_func(GtkTreeViewColumn *col,
570 GtkCellRenderer *renderer,
580 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1);
582 if (idx < 0 || !value) {
587 sac = value / 1000.0;
588 switch (output_units.volume) {
594 sac = ml_to_cuft(sac * 1000);
597 snprintf(buffer, sizeof(buffer), fmt, sac);
599 g_object_set(renderer, "text", buffer, NULL);
602 /* Render the OTU data (integer value of "OTU") */
603 static void otu_data_func(GtkTreeViewColumn *col,
604 GtkCellRenderer *renderer,
612 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1);
614 if (idx < 0 || !value)
617 snprintf(buffer, sizeof(buffer), "%d", value);
619 g_object_set(renderer, "text", buffer, NULL);
622 /* calculate OTU for a dive */
623 static int calculate_otu(struct dive *dive)
628 for (i = 1; i < dive->samples; i++) {
631 struct sample *sample = dive->sample + i;
632 struct sample *psample = sample - 1;
633 t = sample->time.seconds - psample->time.seconds;
634 int o2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille;
637 po2 = o2 / 1000.0 * (sample->depth.mm + 10000) / 10000.0;
639 otu += pow(po2 - 0.5, 0.83) * t / 30.0;
644 * Return air usage (in liters).
646 static double calculate_airuse(struct dive *dive)
651 for (i = 0; i < MAX_CYLINDERS; i++) {
652 pressure_t start, end;
653 cylinder_t *cyl = dive->cylinder + i;
654 int size = cyl->type.size.mliter;
660 start = cyl->start.mbar ? cyl->start : cyl->sample_start;
661 end = cyl->end.mbar ? cyl->end : cyl->sample_end;
662 kilo_atm = (to_ATM(start) - to_ATM(end)) / 1000.0;
664 /* Liters of air at 1 atm == milliliters at 1k atm*/
665 airuse += kilo_atm * size;
670 static int calculate_sac(struct dive *dive)
672 double airuse, pressure, sac;
675 airuse = calculate_airuse(dive);
678 if (!dive->duration.seconds)
681 /* find and eliminate long surface intervals */
682 duration = dive->duration.seconds;
683 for (i = 0; i < dive->samples; i++) {
684 if (dive->sample[i].depth.mm < 100) { /* less than 10cm */
686 while (end < dive->samples && dive->sample[end].depth.mm < 100)
688 /* we only want the actual surface time during a dive */
689 if (end < dive->samples) {
691 duration -= dive->sample[end].time.seconds -
692 dive->sample[i].time.seconds;
697 /* Mean pressure in atm: 1 atm per 10m */
698 pressure = 1 + (dive->meandepth.mm / 10000.0);
699 sac = airuse / pressure * 60 / duration;
701 /* milliliters per minute.. */
705 void update_cylinder_related_info(struct dive *dive)
708 dive->sac = calculate_sac(dive);
709 dive->otu = calculate_otu(dive);
713 static void get_string(char **str, const char *s)
729 static void get_location(struct dive *dive, char **str)
731 get_string(str, dive->location);
734 static void get_cylinder(struct dive *dive, char **str)
736 get_string(str, dive->cylinder[0].type.description);
739 static void get_suit(struct dive *dive, char **str)
741 get_string(str, dive->suit);
745 * Set up anything that could have changed due to editing
746 * of dive information; we need to do this for both models,
747 * so we simply call set_one_dive again with the non-current model
749 /* forward declaration for recursion */
750 static gboolean set_one_dive(GtkTreeModel *model,
755 static void fill_one_dive(struct dive *dive,
759 char *location, *cylinder, *suit;
760 GtkTreeStore *othermodel;
762 get_cylinder(dive, &cylinder);
763 get_location(dive, &location);
764 get_suit(dive, &suit);
766 gtk_tree_store_set(GTK_TREE_STORE(model), iter,
767 DIVE_NR, dive->number,
768 DIVE_LOCATION, location,
769 DIVE_CYLINDER, cylinder,
770 DIVE_RATING, dive->rating,
773 DIVE_TOTALWEIGHT, total_weight(dive),
781 if (model == GTK_TREE_MODEL(dive_list.treemodel))
782 othermodel = dive_list.listmodel;
784 othermodel = dive_list.treemodel;
785 if (othermodel != dive_list.model)
787 gtk_tree_model_foreach(GTK_TREE_MODEL(othermodel), set_one_dive, dive);
790 static gboolean set_one_dive(GtkTreeModel *model,
798 /* Get the dive number */
799 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
802 dive = get_dive(idx);
805 if (data && dive != data)
808 fill_one_dive(dive, model, iter);
812 void flush_divelist(struct dive *dive)
814 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
816 gtk_tree_model_foreach(model, set_one_dive, dive);
819 void set_divelist_font(const char *font)
821 PangoFontDescription *font_desc = pango_font_description_from_string(font);
822 gtk_widget_modify_font(dive_list.tree_view, font_desc);
823 pango_font_description_free(font_desc);
826 void update_dive_list_units(void)
829 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
831 (void) get_depth_units(0, NULL, &unit);
832 gtk_tree_view_column_set_title(dive_list.depth, unit);
834 (void) get_temp_units(0, &unit);
835 gtk_tree_view_column_set_title(dive_list.temperature, unit);
837 (void) get_weight_units(0, NULL, &unit);
838 gtk_tree_view_column_set_title(dive_list.totalweight, unit);
840 gtk_tree_model_foreach(model, set_one_dive, NULL);
843 void update_dive_list_col_visibility(void)
845 gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
846 gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
847 gtk_tree_view_column_set_visible(dive_list.totalweight, visible_cols.totalweight);
848 gtk_tree_view_column_set_visible(dive_list.suit, visible_cols.suit);
849 gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
850 gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
851 gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
855 /* random heuristic - not diving in three days implies new dive trip */
856 #define TRIP_THRESHOLD 3600*24*3
857 static int new_group(struct dive *dive, struct dive **last_dive, time_t *tm_date)
862 struct dive *ldive = *last_dive;
863 if (abs(dive->when - ldive->when) < TRIP_THRESHOLD) {
870 struct tm *tm1 = gmtime(&dive->when);
874 *tm_date = mktime(tm1);
879 static void fill_dive_list(void)
882 GtkTreeIter iter, parent_iter;
883 GtkTreeStore *liststore, *treestore;
884 struct dive *last_dive = NULL;
885 struct dive *first_trip_dive = NULL;
886 struct dive *last_trip_dive = NULL;
889 treestore = GTK_TREE_STORE(dive_list.treemodel);
890 liststore = GTK_TREE_STORE(dive_list.listmodel);
894 struct dive *dive = dive_table.dives[i];
896 if (new_group(dive, &last_dive, &dive_date))
898 /* make sure we display the first date of the trip in previous summary */
899 if (first_trip_dive && last_trip_dive && last_trip_dive->when < first_trip_dive->when)
900 gtk_tree_store_set(treestore, &parent_iter,
901 DIVE_DATE, last_trip_dive->when,
902 DIVE_LOCATION, last_trip_dive->location,
904 first_trip_dive = dive;
906 gtk_tree_store_append(treestore, &parent_iter, NULL);
907 gtk_tree_store_set(treestore, &parent_iter,
908 DIVE_INDEX, -NEW_TRIP,
910 DIVE_DATE, dive_date,
911 DIVE_LOCATION, dive->location,
916 last_trip_dive = dive;
917 update_cylinder_related_info(dive);
918 gtk_tree_store_append(treestore, &iter, &parent_iter);
919 gtk_tree_store_set(treestore, &iter,
921 DIVE_NR, dive->number,
922 DIVE_DATE, dive->when,
923 DIVE_DEPTH, dive->maxdepth,
924 DIVE_DURATION, dive->duration.seconds,
925 DIVE_LOCATION, dive->location,
926 DIVE_RATING, dive->rating,
927 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
930 gtk_tree_store_append(liststore, &iter, NULL);
931 gtk_tree_store_set(liststore, &iter,
933 DIVE_NR, dive->number,
934 DIVE_DATE, dive->when,
935 DIVE_DEPTH, dive->maxdepth,
936 DIVE_DURATION, dive->duration.seconds,
937 DIVE_LOCATION, dive->location,
938 DIVE_RATING, dive->rating,
939 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
941 DIVE_SUIT, dive->suit,
946 update_dive_list_units();
947 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
948 GtkTreeSelection *selection;
950 /* select the last dive (and make sure it's an actual dive that is selected) */
951 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
952 first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive);
953 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
954 gtk_tree_selection_select_iter(selection, &iter);
958 void dive_list_update_dives(void)
960 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.treemodel));
961 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.listmodel));
966 static struct divelist_column {
973 [DIVE_NR] = { "#", nr_data_func, NULL, ALIGN_RIGHT | UNSORTABLE },
974 [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
975 [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
976 [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
977 [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
978 [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
979 [DIVE_TOTALWEIGHT] = { "lbs", weight_data_func, NULL, ALIGN_RIGHT, &visible_cols.totalweight },
980 [DIVE_SUIT] = { "Suit", NULL, NULL, ALIGN_LEFT, &visible_cols.suit },
981 [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
982 [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
983 [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
984 [DIVE_OTU] = { "OTU", otu_data_func, NULL, 0, &visible_cols.otu },
985 [DIVE_LOCATION] = { "Location", NULL, NULL, ALIGN_LEFT },
989 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col)
991 int index = col - &dl_column[0];
992 const char *title = col->header;
993 data_func_t data_func = col->data;
994 sort_func_t sort_func = col->sort;
995 unsigned int flags = col->flags;
996 int *visible = col->visible;
997 GtkWidget *tree_view = dl->tree_view;
998 GtkTreeStore *treemodel = dl->treemodel;
999 GtkTreeStore *listmodel = dl->listmodel;
1000 GtkTreeViewColumn *ret;
1002 if (visible && !*visible)
1004 ret = tree_view_column(tree_view, index, title, data_func, flags);
1006 /* the sort functions are needed in the corresponding models */
1007 if (index == DIVE_DATE)
1008 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treemodel), index, sort_func, NULL, NULL);
1010 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(listmodel), index, sort_func, NULL, NULL);
1016 * This is some crazy crap. The only way to get default focus seems
1017 * to be to grab focus as the widget is being shown the first time.
1019 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
1021 gtk_widget_grab_focus(tree_view);
1024 static void row_activated_cb(GtkTreeView *tree_view,
1026 GtkTreeViewColumn *column,
1032 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path))
1034 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &index, -1);
1035 /* a negative index is special for the "group by date" entries */
1037 edit_dive_info(get_dive(index));
1040 void add_dive_cb(GtkWidget *menuitem, gpointer data)
1044 dive = alloc_dive();
1045 if (add_new_dive(dive)) {
1053 void edit_dive_cb(GtkWidget *menuitem, gpointer data)
1055 edit_multi_dive_info(amount_selected, selectiontracker);
1058 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
1060 GtkWidget *menu, *menuitem;
1061 char editlabel[] = "Edit dives";
1063 menu = gtk_menu_new();
1064 menuitem = gtk_menu_item_new_with_label("Add dive");
1065 g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL);
1066 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1067 if (amount_selected) {
1068 if (amount_selected == 1)
1069 editlabel[strlen(editlabel) - 1] = '\0';
1070 menuitem = gtk_menu_item_new_with_label(editlabel);
1071 g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_cb), model);
1072 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1074 gtk_widget_show_all(menu);
1076 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
1077 button, gtk_get_current_event_time());
1080 static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata)
1082 popup_divelist_menu(tree_view, GTK_TREE_MODEL(dive_list.model), 0);
1085 static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
1087 /* Right-click? Bring up the menu */
1088 if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
1089 popup_divelist_menu(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(dive_list.model), 3);
1095 /* we need to have a temporary copy of the selected dives while
1096 switching model as the selection_cb function keeps getting called
1097 when gtk_tree_selection_select_path is called. We also need to
1098 keep copies of the sort order so we can restore that as well after
1099 switching models. */
1100 static int *oldselection;
1101 static int old_nr_selected;
1102 static gboolean second_call = FALSE;
1103 static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, };
1104 static int lastcol = DIVE_DATE;
1106 /* Check if this dive was selected previously and select it again in the new model;
1107 * This is used after we switch models to maintain consistent selections.
1108 * We always return FALSE to iterate through all dives */
1109 static gboolean select_selected(GtkTreeModel *model, GtkTreePath *path,
1110 GtkTreeIter *iter, gpointer data)
1113 GtkTreeSelection *selection = GTK_TREE_SELECTION(data);
1115 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
1116 for (i = 0; i < old_nr_selected; i++)
1117 if (oldselection[i] == idx) {
1118 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path);
1119 gtk_tree_selection_select_path(selection, path);
1127 static void update_column_and_order(int colid)
1129 /* Careful: the index into treecolumns is off by one as we don't have a
1130 tree_view column for DIVE_INDEX */
1131 GtkTreeViewColumn **treecolumns = &dive_list.nr;
1133 /* this will trigger a second call into sort_column_change_cb,
1134 so make sure we don't start an infinite recursion... */
1136 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, sortorder[colid]);
1137 gtk_tree_view_column_set_sort_order(treecolumns[colid - 1], sortorder[colid]);
1138 second_call = FALSE;
1141 /* If the sort column is date (default), show the tree model.
1142 For every other sort column only show the list model.
1143 If the model changed, inform the new model of the chosen sort column and make
1144 sure the same dives are still selected.
1146 The challenge with this function is that once we change the model
1147 we also need to change the sort column again (as it was changed in
1148 the other model) and that causes this function to be called
1149 recursively - so we need to catch that.
1151 static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data)
1155 GtkTreeStore *currentmodel = dive_list.model;
1160 gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order);
1161 if(colid == lastcol) {
1162 /* we just changed sort order */
1163 sortorder[colid] = order;
1168 if(colid == DIVE_DATE)
1169 dive_list.model = dive_list.treemodel;
1171 dive_list.model = dive_list.listmodel;
1172 if (dive_list.model != currentmodel) {
1173 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1175 /* remember what is currently selected, switch models and reselect the selected rows */
1176 old_nr_selected = amount_selected;
1177 oldselection = malloc(old_nr_selected * sizeof(int));
1178 if (amount_selected)
1179 memcpy(oldselection, selectiontracker, amount_selected * sizeof(int));
1180 gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model));
1182 update_column_and_order(colid);
1184 if (old_nr_selected) {
1185 /* we need to select all the dives that were selected */
1186 /* this is fundamentally an n^2 algorithm as implemented - YUCK */
1187 gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), select_selected, selection);
1190 if (order != sortorder[colid]) {
1191 update_column_and_order(colid);
1196 GtkWidget *dive_list_create(void)
1198 GtkTreeSelection *selection;
1200 dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1201 G_TYPE_INT, /* index */
1202 G_TYPE_INT, /* nr */
1203 G_TYPE_INT, /* Date */
1204 G_TYPE_INT, /* Star rating */
1205 G_TYPE_INT, /* Depth */
1206 G_TYPE_INT, /* Duration */
1207 G_TYPE_INT, /* Temperature */
1208 G_TYPE_INT, /* Total weight */
1209 G_TYPE_STRING, /* Suit */
1210 G_TYPE_STRING, /* Cylinder */
1211 G_TYPE_INT, /* Nitrox */
1212 G_TYPE_INT, /* SAC */
1213 G_TYPE_INT, /* OTU */
1214 G_TYPE_STRING /* Location */
1216 dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1217 G_TYPE_INT, /* index */
1218 G_TYPE_INT, /* nr */
1219 G_TYPE_INT, /* Date */
1220 G_TYPE_INT, /* Star rating */
1221 G_TYPE_INT, /* Depth */
1222 G_TYPE_INT, /* Duration */
1223 G_TYPE_INT, /* Temperature */
1224 G_TYPE_INT, /* Total weight */
1225 G_TYPE_STRING, /* Suit */
1226 G_TYPE_STRING, /* Cylinder */
1227 G_TYPE_INT, /* Nitrox */
1228 G_TYPE_INT, /* SAC */
1229 G_TYPE_INT, /* OTU */
1230 G_TYPE_STRING /* Location */
1232 dive_list.model = dive_list.treemodel;
1233 dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
1234 set_divelist_font(divelist_font);
1236 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1238 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
1239 gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
1241 dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR);
1242 dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE);
1243 dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING);
1244 dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH);
1245 dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION);
1246 dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE);
1247 dive_list.totalweight = divelist_column(&dive_list, dl_column + DIVE_TOTALWEIGHT);
1248 dive_list.suit = divelist_column(&dive_list, dl_column + DIVE_SUIT);
1249 dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER);
1250 dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX);
1251 dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC);
1252 dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU);
1253 dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION);
1257 g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
1258 "search-column", DIVE_LOCATION,
1262 g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
1263 g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL);
1264 g_signal_connect(dive_list.tree_view, "row-expanded", G_CALLBACK(row_expanded_cb), NULL);
1265 g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL);
1266 g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL);
1267 g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), NULL);
1268 g_signal_connect(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1269 g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1271 gtk_tree_selection_set_select_function(selection, modify_selection_cb, NULL, NULL);
1273 dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
1274 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
1275 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1276 gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
1278 dive_list.changed = 0;
1280 return dive_list.container_widget;
1283 void mark_divelist_changed(int changed)
1285 dive_list.changed = changed;
1288 int unsaved_changes()
1290 return dive_list.changed;