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, *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 */
48 DIVE_NITROX, /* int: dummy */
49 DIVE_SAC, /* int: in ml/min */
50 DIVE_OTU, /* int: in OTUs */
51 DIVE_LOCATION, /* "2nd Cathedral, Lanai" */
55 /* magic numbers that indicate (as negative values) model entries that
56 * are summary entries for a divetrip */
60 static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path,
61 GtkTreeIter *iter, gpointer data)
64 int idx, nr, rating, depth;
66 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, DIVE_RATING, &rating, DIVE_DEPTH, &depth, DIVE_LOCATION, &location, -1);
67 printf("entry #%d : nr %d rating %d depth %d location %s \n", idx, nr, rating, depth, location);
73 static void dump_model(GtkListStore *store)
75 gtk_tree_model_foreach(GTK_TREE_MODEL(store), dump_model_entry, NULL);
79 static GList *selected_dives;
80 static int *selectiontracker;
81 static int st_size = 0;
83 gboolean is_in_st(int idx, int *atpos)
87 for (i = 0; i < amount_selected; i++)
88 if (selectiontracker[i] == idx) {
96 #if DEBUG_SELECTION_TRACKING
97 void dump_selection(void)
101 printf("currently selected are ");
102 for (i = 0; i < amount_selected; i++)
103 printf("%d ", selectiontracker[i]);
108 void track_select(int idx)
113 #if DEBUG_SELECTION_TRACKING
114 printf("add %d to selection of %d entries\n", idx, amount_selected);
116 if (is_in_st(idx, NULL))
118 if (amount_selected >= st_size) {
119 selectiontracker = realloc(selectiontracker, dive_table.nr * sizeof(int));
120 st_size = dive_table.nr;
122 selectiontracker[amount_selected] = idx;
124 if (amount_selected == 1)
126 #if DEBUG_SELECTION_TRACKING
127 printf("increased amount_selected to %d\n", amount_selected);
132 void track_unselect(int idx)
137 #if DEBUG_SELECTION_TRACKING
138 printf("remove %d from selection of %d entries\n", idx, amount_selected);
142 if (! is_in_st(idx, &atpos))
144 memmove(selectiontracker + atpos,
145 selectiontracker + atpos + 1,
146 (amount_selected - atpos - 1) * sizeof(int));
148 #if DEBUG_SELECTION_TRACKING
149 printf("removed %d at pos %d and decreased amount_selected to %d\n", idx, atpos, amount_selected);
154 /* when subsurface starts we want to have the last dive selected. So we simply
155 walk to the first leaf (and skip the summary entries - which have negative
157 static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx)
162 while (*diveidx < 0) {
163 memcpy(&parent, iter, sizeof(parent));
164 tpath = gtk_tree_model_get_path(model, &parent);
165 if (!gtk_tree_model_iter_children(model, iter, &parent))
166 /* we should never have a parent without child */
168 if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath))
169 gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
170 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, DIVE_INDEX, diveidx, -1);
171 track_select(*diveidx);
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 unexpand = FALSE;
185 memcpy(&parent, iter, sizeof(parent));
187 tpath = gtk_tree_model_get_path(model, &parent);
189 /* stupid gtk doesn't allow us to select rows that are invisible; so if the
190 user clicks on a row that isn't expanded, we briefly expand it, select the
191 children, and then unexpand it again */
192 if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath)) {
194 gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
196 nr_children = gtk_tree_model_iter_n_children(model, &parent);
197 for (i = 0; i < nr_children; i++) {
198 gtk_tree_model_iter_nth_child(model, iter, &parent, i);
200 gtk_tree_selection_unselect_iter(selection, iter);
202 gtk_tree_selection_select_iter(selection, iter);
205 gtk_tree_view_collapse_row(GTK_TREE_VIEW(dive_list.tree_view), tpath);
208 /* make sure that if we expand a summary row that is selected, the children show
209 up as selected, too */
210 void row_expanded_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data)
212 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
214 if (gtk_tree_selection_path_is_selected(selection, path))
215 select_children(GTK_TREE_MODEL(dive_list.model), selection, iter, FALSE);
218 /* this is called _before_ the selection is changed, for every single entry;
219 * we simply have it call down the tree to make sure that summary items toggle
221 gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model,
222 GtkTreePath *path, gboolean was_selected, gpointer userdata)
227 if (gtk_tree_model_get_iter(model, &iter, path)) {
228 gtk_tree_model_get(model, &iter, DIVE_INDEX, &dive_idx, -1);
229 /* turns out we need to move the selectiontracker here */
231 track_unselect(dive_idx);
233 track_select(dive_idx);
235 select_children(model, selection, &iter, was_selected);
238 /* allow this selection to proceed */
242 /* this is called when gtk thinks that the selection has changed */
243 static void selection_cb(GtkTreeSelection *selection, gpointer userdata)
245 process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model));
249 const char *star_strings[] = {
258 static void star_data_func(GtkTreeViewColumn *col,
259 GtkCellRenderer *renderer,
267 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_RATING, &nr_stars, -1);
271 if (nr_stars < 0 || nr_stars > 5)
273 snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]);
275 g_object_set(renderer, "text", buffer, NULL);
278 static void date_data_func(GtkTreeViewColumn *col,
279 GtkCellRenderer *renderer,
289 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &val, -1);
297 snprintf(buffer, sizeof(buffer),
298 "Trip %s, %s %d, %d",
299 weekday(tm->tm_wday),
300 monthname(tm->tm_mon),
301 tm->tm_mday, tm->tm_year + 1900);
304 snprintf(buffer, sizeof(buffer),
305 "%s, %s %d, %d %02d:%02d",
306 weekday(tm->tm_wday),
307 monthname(tm->tm_mon),
308 tm->tm_mday, tm->tm_year + 1900,
309 tm->tm_hour, tm->tm_min);
311 g_object_set(renderer, "text", buffer, NULL);
314 static void depth_data_func(GtkTreeViewColumn *col,
315 GtkCellRenderer *renderer,
320 int depth, integer, frac, len, idx;
323 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1);
328 switch (output_units.length) {
330 /* To tenths of meters */
331 depth = (depth + 49) / 100;
332 integer = depth / 10;
341 integer = mm_to_feet(depth) + 0.5;
347 len = snprintf(buffer, sizeof(buffer), "%d", integer);
349 len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
351 g_object_set(renderer, "text", buffer, NULL);
354 static void duration_data_func(GtkTreeViewColumn *col,
355 GtkCellRenderer *renderer,
364 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1);
368 snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
370 g_object_set(renderer, "text", buffer, NULL);
373 static void temperature_data_func(GtkTreeViewColumn *col,
374 GtkCellRenderer *renderer,
382 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1);
385 if (idx >= 0 && value) {
387 switch (output_units.temperature) {
389 deg = mkelvin_to_C(value);
392 deg = mkelvin_to_F(value);
397 snprintf(buffer, sizeof(buffer), "%.1f", deg);
400 g_object_set(renderer, "text", buffer, NULL);
403 static void nr_data_func(GtkTreeViewColumn *col,
404 GtkCellRenderer *renderer,
412 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1);
416 snprintf(buffer, sizeof(buffer), "%d", nr);
417 g_object_set(renderer, "text", buffer, NULL);
421 * Get "maximal" dive gas for a dive.
423 * - Trimix trumps nitrox (highest He wins, O2 breaks ties)
424 * - Nitrox trumps air (even if hypoxic)
425 * These are the same rules as the inter-dive sorting rules.
427 static void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
430 int maxo2 = -1, maxhe = -1, mino2 = 1000;
432 for (i = 0; i < MAX_CYLINDERS; i++) {
433 cylinder_t *cyl = dive->cylinder + i;
434 struct gasmix *mix = &cyl->gasmix;
435 int o2 = mix->o2.permille;
436 int he = mix->he.permille;
438 if (cylinder_none(cyl))
454 /* All air? Show/sort as "air"/zero */
455 if (!maxhe && maxo2 == AIR_PERMILLE && mino2 == maxo2)
462 static gint nitrox_sort_func(GtkTreeModel *model,
467 int index_a, index_b;
471 int a_o2low, b_o2low;
473 gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1);
474 gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1);
475 a = get_dive(index_a);
476 b = get_dive(index_b);
477 get_dive_gas(a, &a_o2, &a_he, &a_o2low);
478 get_dive_gas(b, &b_o2, &b_he, &b_o2low);
480 /* Sort by Helium first, O2 second */
483 return a_o2low - b_o2low;
489 #define UTF8_ELLIPSIS "\xE2\x80\xA6"
491 static void nitrox_data_func(GtkTreeViewColumn *col,
492 GtkCellRenderer *renderer,
497 int idx, o2, he, o2low;
501 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
506 dive = get_dive(idx);
507 get_dive_gas(dive, &o2, &he, &o2low);
510 o2low = (o2low + 5) / 10;
513 snprintf(buffer, sizeof(buffer), "%d/%d", o2, he);
516 snprintf(buffer, sizeof(buffer), "%d", o2);
518 snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
520 strcpy(buffer, "air");
522 g_object_set(renderer, "text", buffer, NULL);
525 /* Render the SAC data (integer value of "ml / min") */
526 static void sac_data_func(GtkTreeViewColumn *col,
527 GtkCellRenderer *renderer,
537 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1);
539 if (idx < 0 || !value) {
544 sac = value / 1000.0;
545 switch (output_units.volume) {
551 sac = ml_to_cuft(sac * 1000);
554 snprintf(buffer, sizeof(buffer), fmt, sac);
556 g_object_set(renderer, "text", buffer, NULL);
559 /* Render the OTU data (integer value of "OTU") */
560 static void otu_data_func(GtkTreeViewColumn *col,
561 GtkCellRenderer *renderer,
569 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1);
571 if (idx < 0 || !value)
574 snprintf(buffer, sizeof(buffer), "%d", value);
576 g_object_set(renderer, "text", buffer, NULL);
579 /* calculate OTU for a dive */
580 static int calculate_otu(struct dive *dive)
585 for (i = 1; i < dive->samples; i++) {
588 struct sample *sample = dive->sample + i;
589 struct sample *psample = sample - 1;
590 t = sample->time.seconds - psample->time.seconds;
591 int o2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille;
594 po2 = o2 / 1000.0 * (sample->depth.mm + 10000) / 10000.0;
596 otu += pow(po2 - 0.5, 0.83) * t / 30.0;
601 * Return air usage (in liters).
603 static double calculate_airuse(struct dive *dive)
608 for (i = 0; i < MAX_CYLINDERS; i++) {
609 pressure_t start, end;
610 cylinder_t *cyl = dive->cylinder + i;
611 int size = cyl->type.size.mliter;
617 start = cyl->start.mbar ? cyl->start : cyl->sample_start;
618 end = cyl->end.mbar ? cyl->end : cyl->sample_end;
619 kilo_atm = (to_ATM(start) - to_ATM(end)) / 1000.0;
621 /* Liters of air at 1 atm == milliliters at 1k atm*/
622 airuse += kilo_atm * size;
627 static int calculate_sac(struct dive *dive)
629 double airuse, pressure, sac;
632 airuse = calculate_airuse(dive);
635 if (!dive->duration.seconds)
638 /* find and eliminate long surface intervals */
639 duration = dive->duration.seconds;
640 for (i = 0; i < dive->samples; i++) {
641 if (dive->sample[i].depth.mm < 100) { /* less than 10cm */
643 while (end < dive->samples && dive->sample[end].depth.mm < 100)
645 /* we only want the actual surface time during a dive */
646 if (end < dive->samples) {
648 duration -= dive->sample[end].time.seconds -
649 dive->sample[i].time.seconds;
654 /* Mean pressure in atm: 1 atm per 10m */
655 pressure = 1 + (dive->meandepth.mm / 10000.0);
656 sac = airuse / pressure * 60 / duration;
658 /* milliliters per minute.. */
662 void update_cylinder_related_info(struct dive *dive)
665 dive->sac = calculate_sac(dive);
666 dive->otu = calculate_otu(dive);
670 static void get_string(char **str, const char *s)
686 static void get_location(struct dive *dive, char **str)
688 get_string(str, dive->location);
691 static void get_cylinder(struct dive *dive, char **str)
693 get_string(str, dive->cylinder[0].type.description);
697 * Set up anything that could have changed due to editing
698 * of dive information; we need to do this for both models,
699 * so we simply call set_one_dive again with the non-current model
701 /* forward declaration for recursion */
702 static gboolean set_one_dive(GtkTreeModel *model,
707 static void fill_one_dive(struct dive *dive,
711 char *location, *cylinder;
712 GtkTreeStore *othermodel;
714 get_cylinder(dive, &cylinder);
715 get_location(dive, &location);
717 gtk_tree_store_set(GTK_TREE_STORE(model), iter,
718 DIVE_NR, dive->number,
719 DIVE_LOCATION, location,
720 DIVE_CYLINDER, cylinder,
721 DIVE_RATING, dive->rating,
725 if (model == GTK_TREE_MODEL(dive_list.treemodel))
726 othermodel = dive_list.listmodel;
728 othermodel = dive_list.treemodel;
729 if (othermodel != dive_list.model)
731 gtk_tree_model_foreach(GTK_TREE_MODEL(othermodel), set_one_dive, dive);
734 static gboolean set_one_dive(GtkTreeModel *model,
742 /* Get the dive number */
743 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
746 dive = get_dive(idx);
749 if (data && dive != data)
752 fill_one_dive(dive, model, iter);
756 void flush_divelist(struct dive *dive)
758 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
760 gtk_tree_model_foreach(model, set_one_dive, dive);
763 void set_divelist_font(const char *font)
765 PangoFontDescription *font_desc = pango_font_description_from_string(font);
766 gtk_widget_modify_font(dive_list.tree_view, font_desc);
767 pango_font_description_free(font_desc);
770 void update_dive_list_units(void)
773 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
775 (void) get_depth_units(0, NULL, &unit);
776 gtk_tree_view_column_set_title(dive_list.depth, unit);
778 (void) get_temp_units(0, &unit);
779 gtk_tree_view_column_set_title(dive_list.temperature, unit);
781 gtk_tree_model_foreach(model, set_one_dive, NULL);
784 void update_dive_list_col_visibility(void)
786 gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
787 gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
788 gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
789 gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
790 gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
794 /* random heuristic - not diving in three days implies new dive trip */
795 #define TRIP_THRESHOLD 3600*24*3
796 static int new_group(struct dive *dive, struct dive **last_dive, time_t *tm_date)
801 struct dive *ldive = *last_dive;
802 if (abs(dive->when - ldive->when) < TRIP_THRESHOLD) {
809 struct tm *tm1 = gmtime(&dive->when);
813 *tm_date = mktime(tm1);
818 static void fill_dive_list(void)
821 GtkTreeIter iter, parent_iter;
822 GtkTreeStore *liststore, *treestore;
823 struct dive *last_dive = NULL;
824 struct dive *first_trip_dive = NULL;
825 struct dive *last_trip_dive = NULL;
828 treestore = GTK_TREE_STORE(dive_list.treemodel);
829 liststore = GTK_TREE_STORE(dive_list.listmodel);
833 struct dive *dive = dive_table.dives[i];
835 if (new_group(dive, &last_dive, &dive_date))
837 /* make sure we display the first date of the trip in previous summary */
838 if (first_trip_dive && last_trip_dive && last_trip_dive->when < first_trip_dive->when)
839 gtk_tree_store_set(treestore, &parent_iter,
840 DIVE_DATE, last_trip_dive->when,
841 DIVE_LOCATION, last_trip_dive->location,
843 first_trip_dive = dive;
845 gtk_tree_store_append(treestore, &parent_iter, NULL);
846 gtk_tree_store_set(treestore, &parent_iter,
847 DIVE_INDEX, -NEW_TRIP,
849 DIVE_DATE, dive_date,
850 DIVE_LOCATION, dive->location,
855 last_trip_dive = dive;
856 update_cylinder_related_info(dive);
857 gtk_tree_store_append(treestore, &iter, &parent_iter);
858 gtk_tree_store_set(treestore, &iter,
860 DIVE_NR, dive->number,
861 DIVE_DATE, dive->when,
862 DIVE_DEPTH, dive->maxdepth,
863 DIVE_DURATION, dive->duration.seconds,
864 DIVE_LOCATION, dive->location,
865 DIVE_RATING, dive->rating,
866 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
869 gtk_tree_store_append(liststore, &iter, NULL);
870 gtk_tree_store_set(liststore, &iter,
872 DIVE_NR, dive->number,
873 DIVE_DATE, dive->when,
874 DIVE_DEPTH, dive->maxdepth,
875 DIVE_DURATION, dive->duration.seconds,
876 DIVE_LOCATION, dive->location,
877 DIVE_RATING, dive->rating,
878 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
883 update_dive_list_units();
884 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
885 GtkTreeSelection *selection;
887 /* select the last dive (and make sure it's an actual dive that is selected) */
888 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
889 first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive);
890 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
891 gtk_tree_selection_select_iter(selection, &iter);
895 void dive_list_update_dives(void)
897 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.treemodel));
898 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.listmodel));
903 static struct divelist_column {
910 [DIVE_NR] = { "#", nr_data_func, NULL, ALIGN_RIGHT | UNSORTABLE },
911 [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
912 [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
913 [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
914 [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
915 [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
916 [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
917 [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
918 [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
919 [DIVE_OTU] = { "OTU", otu_data_func, NULL, 0, &visible_cols.otu },
920 [DIVE_LOCATION] = { "Location", NULL, NULL, ALIGN_LEFT },
924 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col)
926 int index = col - &dl_column[0];
927 const char *title = col->header;
928 data_func_t data_func = col->data;
929 sort_func_t sort_func = col->sort;
930 unsigned int flags = col->flags;
931 int *visible = col->visible;
932 GtkWidget *tree_view = dl->tree_view;
933 GtkTreeStore *treemodel = dl->treemodel;
934 GtkTreeStore *listmodel = dl->listmodel;
935 GtkTreeViewColumn *ret;
937 if (visible && !*visible)
939 ret = tree_view_column(tree_view, index, title, data_func, flags);
941 /* the sort functions are needed in the corresponding models */
942 if (index == DIVE_DATE)
943 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treemodel), index, sort_func, NULL, NULL);
945 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(listmodel), index, sort_func, NULL, NULL);
951 * This is some crazy crap. The only way to get default focus seems
952 * to be to grab focus as the widget is being shown the first time.
954 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
956 gtk_widget_grab_focus(tree_view);
959 static void row_activated_cb(GtkTreeView *tree_view,
961 GtkTreeViewColumn *column,
967 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path))
969 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &index, -1);
970 /* a negative index is special for the "group by date" entries */
972 edit_dive_info(get_dive(index));
975 void add_dive_cb(GtkWidget *menuitem, gpointer data)
980 if (add_new_dive(dive)) {
988 void edit_dive_cb(GtkWidget *menuitem, gpointer data)
990 edit_multi_dive_info(amount_selected, selectiontracker);
993 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
995 GtkWidget *menu, *menuitem;
996 char editlabel[] = "Edit dives";
998 menu = gtk_menu_new();
999 menuitem = gtk_menu_item_new_with_label("Add dive");
1000 g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL);
1001 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1002 if (amount_selected) {
1003 if (amount_selected == 1)
1004 editlabel[strlen(editlabel) - 1] = '\0';
1005 menuitem = gtk_menu_item_new_with_label(editlabel);
1006 g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_cb), model);
1007 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1009 gtk_widget_show_all(menu);
1011 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
1012 button, gtk_get_current_event_time());
1015 static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata)
1017 popup_divelist_menu(tree_view, GTK_TREE_MODEL(dive_list.model), 0);
1020 static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
1022 /* Right-click? Bring up the menu */
1023 if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
1024 popup_divelist_menu(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(dive_list.model), 3);
1030 /* we need to have a temporary copy of the selected dives while
1031 switching model as the selection_cb function keeps getting called
1032 when gtk_tree_selection_select_path is called. We also need to
1033 keep copies of the sort order so we can restore that as well after
1034 switching models. */
1035 static int *oldselection;
1036 static int old_nr_selected;
1037 static gboolean second_call = FALSE;
1038 static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, };
1039 static int lastcol = DIVE_DATE;
1041 /* Check if this dive was selected previously and select it again in the new model;
1042 * This is used after we switch models to maintain consistent selections.
1043 * We always return FALSE to iterate through all dives */
1044 static gboolean select_selected(GtkTreeModel *model, GtkTreePath *path,
1045 GtkTreeIter *iter, gpointer data)
1048 GtkTreeSelection *selection = GTK_TREE_SELECTION(data);
1050 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
1051 for (i = 0; i < old_nr_selected; i++)
1052 if (oldselection[i] == idx) {
1053 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path);
1054 gtk_tree_selection_select_path(selection, path);
1062 static void update_column_and_order(int colid)
1064 /* Careful: the index into treecolumns is off by one as we don't have a
1065 tree_view column for DIVE_INDEX */
1066 GtkTreeViewColumn **treecolumns = &dive_list.nr;
1068 /* this will trigger a second call into sort_column_change_cb,
1069 so make sure we don't start an infinite recursion... */
1071 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, sortorder[colid]);
1072 gtk_tree_view_column_set_sort_order(treecolumns[colid - 1], sortorder[colid]);
1073 second_call = FALSE;
1076 /* If the sort column is date (default), show the tree model.
1077 For every other sort column only show the list model.
1078 If the model changed, inform the new model of the chosen sort column and make
1079 sure the same dives are still selected.
1081 The challenge with this function is that once we change the model
1082 we also need to change the sort column again (as it was changed in
1083 the other model) and that causes this function to be called
1084 recursively - so we need to catch that.
1086 static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data)
1090 GtkTreeStore *currentmodel = dive_list.model;
1095 gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order);
1096 if(colid == lastcol) {
1097 /* we just changed sort order */
1098 sortorder[colid] = order;
1103 if(colid == DIVE_DATE)
1104 dive_list.model = dive_list.treemodel;
1106 dive_list.model = dive_list.listmodel;
1107 if (dive_list.model != currentmodel) {
1108 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1110 /* remember what is currently selected, switch models and reselect the selected rows */
1111 old_nr_selected = amount_selected;
1112 oldselection = malloc(old_nr_selected * sizeof(int));
1113 if (amount_selected)
1114 memcpy(oldselection, selectiontracker, amount_selected * sizeof(int));
1115 gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model));
1117 update_column_and_order(colid);
1119 if (old_nr_selected) {
1120 /* we need to select all the dives that were selected */
1121 /* this is fundamentally an n^2 algorithm as implemented - YUCK */
1122 gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), select_selected, selection);
1125 if (order != sortorder[colid]) {
1126 update_column_and_order(colid);
1131 GtkWidget *dive_list_create(void)
1133 GtkTreeSelection *selection;
1135 dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1136 G_TYPE_INT, /* index */
1137 G_TYPE_INT, /* nr */
1138 G_TYPE_INT, /* Date */
1139 G_TYPE_INT, /* Star rating */
1140 G_TYPE_INT, /* Depth */
1141 G_TYPE_INT, /* Duration */
1142 G_TYPE_INT, /* Temperature */
1143 G_TYPE_STRING, /* Cylinder */
1144 G_TYPE_INT, /* Nitrox */
1145 G_TYPE_INT, /* SAC */
1146 G_TYPE_INT, /* OTU */
1147 G_TYPE_STRING /* Location */
1149 dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1150 G_TYPE_INT, /* index */
1151 G_TYPE_INT, /* nr */
1152 G_TYPE_INT, /* Date */
1153 G_TYPE_INT, /* Star rating */
1154 G_TYPE_INT, /* Depth */
1155 G_TYPE_INT, /* Duration */
1156 G_TYPE_INT, /* Temperature */
1157 G_TYPE_STRING, /* Cylinder */
1158 G_TYPE_INT, /* Nitrox */
1159 G_TYPE_INT, /* SAC */
1160 G_TYPE_INT, /* OTU */
1161 G_TYPE_STRING /* Location */
1163 dive_list.model = dive_list.treemodel;
1164 dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
1165 set_divelist_font(divelist_font);
1167 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1169 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
1170 gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
1172 dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR);
1173 dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE);
1174 dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING);
1175 dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH);
1176 dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION);
1177 dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE);
1178 dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER);
1179 dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX);
1180 dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC);
1181 dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU);
1182 dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION);
1186 g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
1187 "search-column", DIVE_LOCATION,
1191 g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
1192 g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL);
1193 g_signal_connect(dive_list.tree_view, "row-expanded", G_CALLBACK(row_expanded_cb), NULL);
1194 g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL);
1195 g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL);
1196 g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), NULL);
1197 g_signal_connect(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1198 g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1200 gtk_tree_selection_set_select_function(selection, modify_selection_cb, NULL, NULL);
1202 dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
1203 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
1204 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1205 gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
1207 dive_list.changed = 0;
1209 return dive_list.container_widget;
1212 void mark_divelist_changed(int changed)
1214 dive_list.changed = changed;
1217 int unsaved_changes()
1219 return dive_list.changed;