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 day / month / year */
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 *selectiontracker;
84 /* when subsurface starts we want to have the last dive selected. So we simply
85 walk to the first leaf (and skip the summary entries - which have negative
87 static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx)
92 while (*diveidx < 0) {
93 memcpy(&parent, iter, sizeof(parent));
94 tpath = gtk_tree_model_get_path(model, &parent);
95 if (!gtk_tree_model_iter_children(model, iter, &parent))
96 /* we should never have a parent without child */
98 if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath))
99 gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
100 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, DIVE_INDEX, diveidx, -1);
104 /* if we click on a summary dive, we actually want to select / unselect
105 all the dives "below" it */
106 static void select_children(GtkTreeModel *model, GtkTreeSelection * selection,
107 GtkTreeIter *iter, gboolean was_selected)
113 memcpy(&parent, iter, sizeof(parent));
115 tpath = gtk_tree_model_get_path(model, &parent);
116 if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath))
117 gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
119 nr_children = gtk_tree_model_iter_n_children(model, &parent);
120 for (i = 0; i < nr_children; i++) {
121 gtk_tree_model_iter_nth_child(model, iter, &parent, i);
123 gtk_tree_selection_unselect_iter(selection, iter);
125 gtk_tree_selection_select_iter(selection, iter);
129 /* this is called _before_ the selection is changed, for every single entry;
130 * we simply have it call down the tree to make sure that summary items toggle
132 gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model,
133 GtkTreePath *path, gboolean was_selected, gpointer userdata)
138 if (gtk_tree_model_get_iter(model, &iter, path)) {
139 gtk_tree_model_get(model, &iter, DIVE_INDEX, &dive_idx, -1);
141 select_children(model, selection, &iter, was_selected);
144 /* allow this selection to proceed */
148 /* this is called when gtk thinks that the selection has changed */
149 static void selection_cb(GtkTreeSelection *selection, gpointer userdata)
154 int nr_selected = gtk_tree_selection_count_selected_rows(selection);
156 if (selected_dives) {
157 g_list_foreach (selected_dives, (GFunc) gtk_tree_path_free, NULL);
158 g_list_free (selected_dives);
160 selected_dives = gtk_tree_selection_get_selected_rows(selection, NULL);
161 selectiontracker = realloc(selectiontracker, nr_selected * sizeof(int));
163 switch (nr_selected) {
164 case 0: /* there is no clear way to figure out which dive to show */
169 /* just pick that dive as selected */
171 path = g_list_nth_data(selected_dives, 0);
172 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path)) {
173 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
174 /* due to the way this callback gets invoked it is possible that
175 in the process of unselecting a summary dive we get here with
176 just one summary dive selected - ignore that case */
177 if (selected_dive < 0) {
181 selectiontracker[0] = selected_dive;
185 default: /* multiple selections - what now?
186 * We don't change the selected dive unless there is exactly one dive selected; not sure this
187 * is the most intuitive solution.
188 * The dives that have been selected are processed */
189 amount_selected = g_list_length(selected_dives);
190 process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model));
196 const char *star_strings[] = {
205 static void star_data_func(GtkTreeViewColumn *col,
206 GtkCellRenderer *renderer,
214 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_RATING, &nr_stars, -1);
218 if (nr_stars < 0 || nr_stars > 5)
220 snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]);
222 g_object_set(renderer, "text", buffer, NULL);
225 static void date_data_func(GtkTreeViewColumn *col,
226 GtkCellRenderer *renderer,
236 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &val, -1);
244 snprintf(buffer, sizeof(buffer),
246 weekday(tm->tm_wday),
247 monthname(tm->tm_mon),
248 tm->tm_mday, tm->tm_year + 1900);
251 snprintf(buffer, sizeof(buffer),
253 monthname(tm->tm_mon),
257 snprintf(buffer, sizeof(buffer),
258 "%d", tm->tm_year + 1900);
261 snprintf(buffer, sizeof(buffer),
262 "%s, %s %d, %d %02d:%02d",
263 weekday(tm->tm_wday),
264 monthname(tm->tm_mon),
265 tm->tm_mday, tm->tm_year + 1900,
266 tm->tm_hour, tm->tm_min);
268 g_object_set(renderer, "text", buffer, NULL);
271 static void depth_data_func(GtkTreeViewColumn *col,
272 GtkCellRenderer *renderer,
277 int depth, integer, frac, len, idx;
280 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1);
285 switch (output_units.length) {
287 /* To tenths of meters */
288 depth = (depth + 49) / 100;
289 integer = depth / 10;
298 integer = mm_to_feet(depth) + 0.5;
304 len = snprintf(buffer, sizeof(buffer), "%d", integer);
306 len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
308 g_object_set(renderer, "text", buffer, NULL);
311 static void duration_data_func(GtkTreeViewColumn *col,
312 GtkCellRenderer *renderer,
321 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1);
325 snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
327 g_object_set(renderer, "text", buffer, NULL);
330 static void temperature_data_func(GtkTreeViewColumn *col,
331 GtkCellRenderer *renderer,
339 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1);
342 if (idx >= 0 && value) {
344 switch (output_units.temperature) {
346 deg = mkelvin_to_C(value);
349 deg = mkelvin_to_F(value);
354 snprintf(buffer, sizeof(buffer), "%.1f", deg);
357 g_object_set(renderer, "text", buffer, NULL);
360 static void nr_data_func(GtkTreeViewColumn *col,
361 GtkCellRenderer *renderer,
369 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1);
373 snprintf(buffer, sizeof(buffer), "%d", nr);
374 g_object_set(renderer, "text", buffer, NULL);
378 * Get "maximal" dive gas for a dive.
380 * - Trimix trumps nitrox (highest He wins, O2 breaks ties)
381 * - Nitrox trumps air (even if hypoxic)
382 * These are the same rules as the inter-dive sorting rules.
384 static void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
387 int maxo2 = -1, maxhe = -1, mino2 = 1000;
389 for (i = 0; i < MAX_CYLINDERS; i++) {
390 cylinder_t *cyl = dive->cylinder + i;
391 struct gasmix *mix = &cyl->gasmix;
392 int o2 = mix->o2.permille;
393 int he = mix->he.permille;
395 if (cylinder_none(cyl))
411 /* All air? Show/sort as "air"/zero */
412 if (!maxhe && maxo2 == AIR_PERMILLE && mino2 == maxo2)
419 static gint nitrox_sort_func(GtkTreeModel *model,
424 int index_a, index_b;
428 int a_o2low, b_o2low;
430 gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1);
431 gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1);
432 a = get_dive(index_a);
433 b = get_dive(index_b);
434 get_dive_gas(a, &a_o2, &a_he, &a_o2low);
435 get_dive_gas(b, &b_o2, &b_he, &b_o2low);
437 /* Sort by Helium first, O2 second */
440 return a_o2low - b_o2low;
446 #define UTF8_ELLIPSIS "\xE2\x80\xA6"
448 static void nitrox_data_func(GtkTreeViewColumn *col,
449 GtkCellRenderer *renderer,
454 int idx, o2, he, o2low;
458 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
463 dive = get_dive(idx);
464 get_dive_gas(dive, &o2, &he, &o2low);
467 o2low = (o2low + 5) / 10;
470 snprintf(buffer, sizeof(buffer), "%d/%d", o2, he);
473 snprintf(buffer, sizeof(buffer), "%d", o2);
475 snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
477 strcpy(buffer, "air");
479 g_object_set(renderer, "text", buffer, NULL);
482 /* Render the SAC data (integer value of "ml / min") */
483 static void sac_data_func(GtkTreeViewColumn *col,
484 GtkCellRenderer *renderer,
494 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1);
496 if (idx < 0 || !value) {
501 sac = value / 1000.0;
502 switch (output_units.volume) {
508 sac = ml_to_cuft(sac * 1000);
511 snprintf(buffer, sizeof(buffer), fmt, sac);
513 g_object_set(renderer, "text", buffer, NULL);
516 /* Render the OTU data (integer value of "OTU") */
517 static void otu_data_func(GtkTreeViewColumn *col,
518 GtkCellRenderer *renderer,
526 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1);
528 if (idx < 0 || !value)
531 snprintf(buffer, sizeof(buffer), "%d", value);
533 g_object_set(renderer, "text", buffer, NULL);
536 /* calculate OTU for a dive */
537 static int calculate_otu(struct dive *dive)
542 for (i = 1; i < dive->samples; i++) {
545 struct sample *sample = dive->sample + i;
546 struct sample *psample = sample - 1;
547 t = sample->time.seconds - psample->time.seconds;
548 int o2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille;
551 po2 = o2 / 1000.0 * (sample->depth.mm + 10000) / 10000.0;
553 otu += pow(po2 - 0.5, 0.83) * t / 30.0;
558 * Return air usage (in liters).
560 static double calculate_airuse(struct dive *dive)
565 for (i = 0; i < MAX_CYLINDERS; i++) {
566 pressure_t start, end;
567 cylinder_t *cyl = dive->cylinder + i;
568 int size = cyl->type.size.mliter;
574 start = cyl->start.mbar ? cyl->start : cyl->sample_start;
575 end = cyl->end.mbar ? cyl->end : cyl->sample_end;
576 kilo_atm = (to_ATM(start) - to_ATM(end)) / 1000.0;
578 /* Liters of air at 1 atm == milliliters at 1k atm*/
579 airuse += kilo_atm * size;
584 static int calculate_sac(struct dive *dive)
586 double airuse, pressure, sac;
589 airuse = calculate_airuse(dive);
592 if (!dive->duration.seconds)
595 /* find and eliminate long surface intervals */
596 duration = dive->duration.seconds;
597 for (i = 0; i < dive->samples; i++) {
598 if (dive->sample[i].depth.mm < 100) { /* less than 10cm */
600 while (end < dive->samples && dive->sample[end].depth.mm < 100)
602 /* we only want the actual surface time during a dive */
603 if (end < dive->samples) {
605 duration -= dive->sample[end].time.seconds -
606 dive->sample[i].time.seconds;
611 /* Mean pressure in atm: 1 atm per 10m */
612 pressure = 1 + (dive->meandepth.mm / 10000.0);
613 sac = airuse / pressure * 60 / duration;
615 /* milliliters per minute.. */
619 void update_cylinder_related_info(struct dive *dive)
622 dive->sac = calculate_sac(dive);
623 dive->otu = calculate_otu(dive);
627 static void get_string(char **str, const char *s)
643 static void get_location(struct dive *dive, char **str)
645 get_string(str, dive->location);
648 static void get_cylinder(struct dive *dive, char **str)
650 get_string(str, dive->cylinder[0].type.description);
654 * Set up anything that could have changed due to editing
655 * of dive information; we need to do this for both models,
656 * so we simply call set_one_dive again with the non-current model
658 /* forward declaration for recursion */
659 static gboolean set_one_dive(GtkTreeModel *model,
664 static void fill_one_dive(struct dive *dive,
668 char *location, *cylinder;
669 GtkTreeStore *othermodel;
671 get_cylinder(dive, &cylinder);
672 get_location(dive, &location);
674 gtk_tree_store_set(GTK_TREE_STORE(model), iter,
675 DIVE_NR, dive->number,
676 DIVE_LOCATION, location,
677 DIVE_CYLINDER, cylinder,
678 DIVE_RATING, dive->rating,
682 if (model == GTK_TREE_MODEL(dive_list.treemodel))
683 othermodel = dive_list.listmodel;
685 othermodel = dive_list.treemodel;
686 if (othermodel != dive_list.model)
688 gtk_tree_model_foreach(GTK_TREE_MODEL(othermodel), set_one_dive, dive);
691 static gboolean set_one_dive(GtkTreeModel *model,
699 /* Get the dive number */
700 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
703 dive = get_dive(idx);
706 if (data && dive != data)
709 fill_one_dive(dive, model, iter);
713 void flush_divelist(struct dive *dive)
715 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
717 gtk_tree_model_foreach(model, set_one_dive, dive);
720 void set_divelist_font(const char *font)
722 PangoFontDescription *font_desc = pango_font_description_from_string(font);
723 gtk_widget_modify_font(dive_list.tree_view, font_desc);
724 pango_font_description_free(font_desc);
727 void update_dive_list_units(void)
730 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
732 (void) get_depth_units(0, NULL, &unit);
733 gtk_tree_view_column_set_title(dive_list.depth, unit);
735 (void) get_temp_units(0, &unit);
736 gtk_tree_view_column_set_title(dive_list.temperature, unit);
738 gtk_tree_model_foreach(model, set_one_dive, NULL);
741 void update_dive_list_col_visibility(void)
743 gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
744 gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
745 gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
746 gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
747 gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
751 static int new_date(struct dive *dive, struct dive **last_dive, const int flag, time_t *tm_date)
756 struct dive *ldive = *last_dive;
758 (void) gmtime_r(&dive->when, &tm1);
759 (void) gmtime_r(&ldive->when, &tm2);
760 if (tm1.tm_year == tm2.tm_year &&
761 (tm1.tm_mon == tm2.tm_mon || flag > NEW_MON) &&
762 (tm1.tm_mday == tm2.tm_mday || flag > NEW_DAY))
768 struct tm *tm1 = gmtime(&dive->when);
772 *tm_date = mktime(tm1);
777 static void fill_dive_list(void)
780 GtkTreeIter iter, parent_iter[NEW_YR + 2], *parents[NEW_YR + 2] = {NULL, };
781 GtkTreeStore *liststore, *treestore;
782 struct dive *last_dive = NULL;
785 treestore = GTK_TREE_STORE(dive_list.treemodel);
786 liststore = GTK_TREE_STORE(dive_list.listmodel);
790 struct dive *dive = dive_table.dives[i];
792 for (j = NEW_YR; j >= NEW_DAY; j--) {
793 if (new_date(dive, &last_dive, j, &dive_date))
795 gtk_tree_store_append(treestore, &parent_iter[j], parents[j+1]);
796 parents[j] = &parent_iter[j];
797 gtk_tree_store_set(treestore, parents[j],
800 DIVE_DATE, dive_date,
807 update_cylinder_related_info(dive);
808 gtk_tree_store_append(treestore, &iter, parents[NEW_DAY]);
809 gtk_tree_store_set(treestore, &iter,
811 DIVE_NR, dive->number,
812 DIVE_DATE, dive->when,
813 DIVE_DEPTH, dive->maxdepth,
814 DIVE_DURATION, dive->duration.seconds,
815 DIVE_LOCATION, dive->location,
816 DIVE_RATING, dive->rating,
817 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
820 gtk_tree_store_append(liststore, &iter, NULL);
821 gtk_tree_store_set(liststore, &iter,
823 DIVE_NR, dive->number,
824 DIVE_DATE, dive->when,
825 DIVE_DEPTH, dive->maxdepth,
826 DIVE_DURATION, dive->duration.seconds,
827 DIVE_LOCATION, dive->location,
828 DIVE_RATING, dive->rating,
829 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
834 update_dive_list_units();
835 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
836 GtkTreeSelection *selection;
838 /* select the last dive (and make sure it's an actual dive that is selected) */
839 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
840 first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive);
841 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
842 gtk_tree_selection_select_iter(selection, &iter);
843 selectiontracker = realloc(selectiontracker, sizeof(int));
844 *selectiontracker = selected_dive;
848 void dive_list_update_dives(void)
850 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.treemodel));
851 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.listmodel));
856 static struct divelist_column {
863 [DIVE_NR] = { "#", nr_data_func, NULL, ALIGN_RIGHT | UNSORTABLE },
864 [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
865 [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
866 [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
867 [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
868 [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
869 [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
870 [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
871 [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
872 [DIVE_OTU] = { "OTU", otu_data_func, NULL, 0, &visible_cols.otu },
873 [DIVE_LOCATION] = { "Location", NULL, NULL, ALIGN_LEFT },
877 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col)
879 int index = col - &dl_column[0];
880 const char *title = col->header;
881 data_func_t data_func = col->data;
882 sort_func_t sort_func = col->sort;
883 unsigned int flags = col->flags;
884 int *visible = col->visible;
885 GtkWidget *tree_view = dl->tree_view;
886 GtkTreeStore *treemodel = dl->treemodel;
887 GtkTreeStore *listmodel = dl->listmodel;
888 GtkTreeViewColumn *ret;
890 if (visible && !*visible)
892 ret = tree_view_column(tree_view, index, title, data_func, flags);
894 /* the sort functions are needed in the corresponding models */
895 if (index == DIVE_DATE)
896 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treemodel), index, sort_func, NULL, NULL);
898 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(listmodel), index, sort_func, NULL, NULL);
904 * This is some crazy crap. The only way to get default focus seems
905 * to be to grab focus as the widget is being shown the first time.
907 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
909 gtk_widget_grab_focus(tree_view);
912 static void row_activated_cb(GtkTreeView *tree_view,
914 GtkTreeViewColumn *column,
920 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path))
922 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &index, -1);
923 /* a negative index is special for the "group by date" entries */
925 edit_dive_info(get_dive(index));
928 void add_dive_cb(GtkWidget *menuitem, gpointer data)
933 if (add_new_dive(dive)) {
941 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
943 GtkWidget *menu, *menuitem;
945 menu = gtk_menu_new();
946 menuitem = gtk_menu_item_new_with_label("Add dive");
947 g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL);
948 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
949 gtk_widget_show_all(menu);
951 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
952 button, gtk_get_current_event_time());
955 static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata)
957 popup_divelist_menu(tree_view, GTK_TREE_MODEL(dive_list.model), 0);
960 static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
962 /* Right-click? Bring up the menu */
963 if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
964 popup_divelist_menu(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(dive_list.model), 3);
970 /* we need to have a temporary copy of the selected dives while
971 switching model as the selection_cb function keeps getting called
972 when gtk_tree_selection_select_path is called. We also need to
973 keep copies of the sort order so we can restore that as well after
975 static int *oldselection;
976 static int old_nr_selected;
977 static gboolean second_call = FALSE;
978 static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, };
979 static int lastcol = DIVE_DATE;
981 /* Check if this dive was selected previously and select it again in the new model;
982 * This is used after we switch models to maintain consistent selections.
983 * We always return FALSE to iterate through all dives */
984 static gboolean select_selected(GtkTreeModel *model, GtkTreePath *path,
985 GtkTreeIter *iter, gpointer data)
988 GtkTreeSelection *selection = GTK_TREE_SELECTION(data);
990 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
991 for (i = 0; i < old_nr_selected; i++)
992 if (oldselection[i] == idx) {
993 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path);
994 gtk_tree_selection_select_path(selection, path);
1002 static void update_column_and_order(int colid)
1004 /* Careful: the index into treecolumns is off by one as we don't have a
1005 tree_view column for DIVE_INDEX */
1006 GtkTreeViewColumn **treecolumns = &dive_list.nr;
1008 /* this will trigger a second call into sort_column_change_cb,
1009 so make sure we don't start an infinite recursion... */
1011 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, sortorder[colid]);
1012 gtk_tree_view_column_set_sort_order(treecolumns[colid - 1], sortorder[colid]);
1013 second_call = FALSE;
1016 /* If the sort column is date (default), show the tree model.
1017 For every other sort column only show the list model.
1018 If the model changed, inform the new model of the chosen sort column and make
1019 sure the same dives are still selected.
1021 The challenge with this function is that once we change the model
1022 we also need to change the sort column again (as it was changed in
1023 the other model) and that causes this function to be called
1024 recursively - so we need to catch that.
1026 static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data)
1030 GtkTreeStore *currentmodel = dive_list.model;
1035 gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order);
1036 if(colid == lastcol) {
1037 /* we just changed sort order */
1038 sortorder[colid] = order;
1043 if(colid == DIVE_DATE)
1044 dive_list.model = dive_list.treemodel;
1046 dive_list.model = dive_list.listmodel;
1047 if (dive_list.model != currentmodel) {
1048 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1050 /* remember what is currently selected, switch models and reselect the selected rows */
1051 old_nr_selected = amount_selected;
1052 oldselection = malloc(old_nr_selected * sizeof(int));
1053 if (amount_selected)
1054 memcpy(oldselection, selectiontracker, amount_selected * sizeof(int));
1055 gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model));
1057 update_column_and_order(colid);
1059 if (old_nr_selected) {
1060 /* we need to select all the dives that were selected */
1061 /* this is fundamentally an n^2 algorithm as implemented - YUCK */
1062 gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), select_selected, selection);
1065 if (order != sortorder[colid]) {
1066 update_column_and_order(colid);
1071 GtkWidget *dive_list_create(void)
1073 GtkTreeSelection *selection;
1075 dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1076 G_TYPE_INT, /* index */
1077 G_TYPE_INT, /* nr */
1078 G_TYPE_INT, /* Date */
1079 G_TYPE_INT, /* Star rating */
1080 G_TYPE_INT, /* Depth */
1081 G_TYPE_INT, /* Duration */
1082 G_TYPE_INT, /* Temperature */
1083 G_TYPE_STRING, /* Cylinder */
1084 G_TYPE_INT, /* Nitrox */
1085 G_TYPE_INT, /* SAC */
1086 G_TYPE_INT, /* OTU */
1087 G_TYPE_STRING /* Location */
1089 dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1090 G_TYPE_INT, /* index */
1091 G_TYPE_INT, /* nr */
1092 G_TYPE_INT, /* Date */
1093 G_TYPE_INT, /* Star rating */
1094 G_TYPE_INT, /* Depth */
1095 G_TYPE_INT, /* Duration */
1096 G_TYPE_INT, /* Temperature */
1097 G_TYPE_STRING, /* Cylinder */
1098 G_TYPE_INT, /* Nitrox */
1099 G_TYPE_INT, /* SAC */
1100 G_TYPE_INT, /* OTU */
1101 G_TYPE_STRING /* Location */
1103 dive_list.model = dive_list.treemodel;
1104 dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
1105 set_divelist_font(divelist_font);
1107 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1109 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
1110 gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
1112 dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR);
1113 dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE);
1114 dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING);
1115 dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH);
1116 dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION);
1117 dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE);
1118 dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER);
1119 dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX);
1120 dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC);
1121 dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU);
1122 dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION);
1126 g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
1127 "search-column", DIVE_LOCATION,
1131 g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
1132 g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL);
1133 g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL);
1134 g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL);
1135 g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), NULL);
1136 g_signal_connect(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1137 g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1139 gtk_tree_selection_set_select_function(selection, modify_selection_cb, NULL, NULL);
1141 dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
1142 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
1143 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1144 gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
1146 dive_list.changed = 0;
1148 return dive_list.container_widget;
1151 void mark_divelist_changed(int changed)
1153 dive_list.changed = changed;
1156 int unsaved_changes()
1158 return dive_list.changed;