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;
83 /* when subsurface starts we want to have the last dive selected. So we simply
84 walk to the first leaf (and skip the summary entries - which have negative
86 static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx)
91 while (*diveidx < 0) {
92 memcpy(&parent, iter, sizeof(parent));
93 tpath = gtk_tree_model_get_path(model, &parent);
94 if (!gtk_tree_model_iter_children(model, iter, &parent))
95 /* we should never have a parent without child */
97 if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath))
98 gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
99 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, DIVE_INDEX, diveidx, -1);
103 /* if we click on a summary dive, we actually want to select / unselect
104 all the dives "below" it */
105 static void select_children(GtkTreeModel *model, GtkTreeSelection * selection,
106 GtkTreeIter *iter, gboolean was_selected)
112 memcpy(&parent, iter, sizeof(parent));
114 tpath = gtk_tree_model_get_path(model, &parent);
115 if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath))
116 gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
118 nr_children = gtk_tree_model_iter_n_children(model, &parent);
119 for (i = 0; i < nr_children; i++) {
120 gtk_tree_model_iter_nth_child(model, iter, &parent, i);
122 gtk_tree_selection_unselect_iter(selection, iter);
124 gtk_tree_selection_select_iter(selection, iter);
128 /* this is called _before_ the selection is changed, for every single entry;
129 * we simply have it call down the tree to make sure that summary items toggle
131 gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model,
132 GtkTreePath *path, gboolean was_selected, gpointer userdata)
137 if (gtk_tree_model_get_iter(model, &iter, path)) {
138 gtk_tree_model_get(model, &iter, DIVE_INDEX, &dive_idx, -1);
140 select_children(model, selection, &iter, was_selected);
143 /* allow this selection to proceed */
147 /* this is called when gtk thinks that the selection has changed */
148 static void selection_cb(GtkTreeSelection *selection, gpointer userdata)
153 int nr_selected = gtk_tree_selection_count_selected_rows(selection);
155 if (selected_dives) {
156 g_list_foreach (selected_dives, (GFunc) gtk_tree_path_free, NULL);
157 g_list_free (selected_dives);
159 selected_dives = gtk_tree_selection_get_selected_rows(selection, NULL);
160 selectiontracker = realloc(selectiontracker, nr_selected * sizeof(int));
162 switch (nr_selected) {
163 case 0: /* there is no clear way to figure out which dive to show */
168 /* just pick that dive as selected */
170 path = g_list_nth_data(selected_dives, 0);
171 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path)) {
172 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
173 /* due to the way this callback gets invoked it is possible that
174 in the process of unselecting a summary dive we get here with
175 just one summary dive selected - ignore that case */
176 if (selected_dive < 0) {
180 selectiontracker[0] = selected_dive;
184 default: /* multiple selections - what now?
185 * We don't change the selected dive unless there is exactly one dive selected; not sure this
186 * is the most intuitive solution.
187 * The dives that have been selected are processed */
188 amount_selected = g_list_length(selected_dives);
189 process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model));
195 const char *star_strings[] = {
204 static void star_data_func(GtkTreeViewColumn *col,
205 GtkCellRenderer *renderer,
213 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_RATING, &nr_stars, -1);
217 if (nr_stars < 0 || nr_stars > 5)
219 snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]);
221 g_object_set(renderer, "text", buffer, NULL);
224 static void date_data_func(GtkTreeViewColumn *col,
225 GtkCellRenderer *renderer,
235 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &val, -1);
243 snprintf(buffer, sizeof(buffer),
244 "Trip %s, %s %d, %d",
245 weekday(tm->tm_wday),
246 monthname(tm->tm_mon),
247 tm->tm_mday, tm->tm_year + 1900);
250 snprintf(buffer, sizeof(buffer),
251 "%s, %s %d, %d %02d:%02d",
252 weekday(tm->tm_wday),
253 monthname(tm->tm_mon),
254 tm->tm_mday, tm->tm_year + 1900,
255 tm->tm_hour, tm->tm_min);
257 g_object_set(renderer, "text", buffer, NULL);
260 static void depth_data_func(GtkTreeViewColumn *col,
261 GtkCellRenderer *renderer,
266 int depth, integer, frac, len, idx;
269 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1);
274 switch (output_units.length) {
276 /* To tenths of meters */
277 depth = (depth + 49) / 100;
278 integer = depth / 10;
287 integer = mm_to_feet(depth) + 0.5;
293 len = snprintf(buffer, sizeof(buffer), "%d", integer);
295 len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
297 g_object_set(renderer, "text", buffer, NULL);
300 static void duration_data_func(GtkTreeViewColumn *col,
301 GtkCellRenderer *renderer,
310 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1);
314 snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
316 g_object_set(renderer, "text", buffer, NULL);
319 static void temperature_data_func(GtkTreeViewColumn *col,
320 GtkCellRenderer *renderer,
328 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1);
331 if (idx >= 0 && value) {
333 switch (output_units.temperature) {
335 deg = mkelvin_to_C(value);
338 deg = mkelvin_to_F(value);
343 snprintf(buffer, sizeof(buffer), "%.1f", deg);
346 g_object_set(renderer, "text", buffer, NULL);
349 static void nr_data_func(GtkTreeViewColumn *col,
350 GtkCellRenderer *renderer,
358 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1);
362 snprintf(buffer, sizeof(buffer), "%d", nr);
363 g_object_set(renderer, "text", buffer, NULL);
367 * Get "maximal" dive gas for a dive.
369 * - Trimix trumps nitrox (highest He wins, O2 breaks ties)
370 * - Nitrox trumps air (even if hypoxic)
371 * These are the same rules as the inter-dive sorting rules.
373 static void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
376 int maxo2 = -1, maxhe = -1, mino2 = 1000;
378 for (i = 0; i < MAX_CYLINDERS; i++) {
379 cylinder_t *cyl = dive->cylinder + i;
380 struct gasmix *mix = &cyl->gasmix;
381 int o2 = mix->o2.permille;
382 int he = mix->he.permille;
384 if (cylinder_none(cyl))
400 /* All air? Show/sort as "air"/zero */
401 if (!maxhe && maxo2 == AIR_PERMILLE && mino2 == maxo2)
408 static int total_weight(struct dive *dive)
410 int i, total_grams = 0;
413 for (i=0; i< MAX_WEIGHTSYSTEMS; i++)
414 total_grams += dive->weightsystem[i].weight.grams;
418 static void weight_data_func(GtkTreeViewColumn *col,
419 GtkCellRenderer *renderer,
429 gtk_tree_model_get(model, iter, DIVE_INDEX, &indx, -1);
430 dive = get_dive(indx);
431 value = get_weight_units(total_weight(dive), &decimals, NULL);
435 snprintf(buffer, sizeof(buffer), "%.*f", decimals, value);
437 g_object_set(renderer, "text", buffer, NULL);
440 static gint nitrox_sort_func(GtkTreeModel *model,
445 int index_a, index_b;
449 int a_o2low, b_o2low;
451 gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1);
452 gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1);
453 a = get_dive(index_a);
454 b = get_dive(index_b);
455 get_dive_gas(a, &a_o2, &a_he, &a_o2low);
456 get_dive_gas(b, &b_o2, &b_he, &b_o2low);
458 /* Sort by Helium first, O2 second */
461 return a_o2low - b_o2low;
467 #define UTF8_ELLIPSIS "\xE2\x80\xA6"
469 static void nitrox_data_func(GtkTreeViewColumn *col,
470 GtkCellRenderer *renderer,
475 int idx, o2, he, o2low;
479 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
484 dive = get_dive(idx);
485 get_dive_gas(dive, &o2, &he, &o2low);
488 o2low = (o2low + 5) / 10;
491 snprintf(buffer, sizeof(buffer), "%d/%d", o2, he);
494 snprintf(buffer, sizeof(buffer), "%d", o2);
496 snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
498 strcpy(buffer, "air");
500 g_object_set(renderer, "text", buffer, NULL);
503 /* Render the SAC data (integer value of "ml / min") */
504 static void sac_data_func(GtkTreeViewColumn *col,
505 GtkCellRenderer *renderer,
515 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1);
517 if (idx < 0 || !value) {
522 sac = value / 1000.0;
523 switch (output_units.volume) {
529 sac = ml_to_cuft(sac * 1000);
532 snprintf(buffer, sizeof(buffer), fmt, sac);
534 g_object_set(renderer, "text", buffer, NULL);
537 /* Render the OTU data (integer value of "OTU") */
538 static void otu_data_func(GtkTreeViewColumn *col,
539 GtkCellRenderer *renderer,
547 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1);
549 if (idx < 0 || !value)
552 snprintf(buffer, sizeof(buffer), "%d", value);
554 g_object_set(renderer, "text", buffer, NULL);
557 /* calculate OTU for a dive */
558 static int calculate_otu(struct dive *dive)
563 for (i = 1; i < dive->samples; i++) {
566 struct sample *sample = dive->sample + i;
567 struct sample *psample = sample - 1;
568 t = sample->time.seconds - psample->time.seconds;
569 int o2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille;
572 po2 = o2 / 1000.0 * (sample->depth.mm + 10000) / 10000.0;
574 otu += pow(po2 - 0.5, 0.83) * t / 30.0;
579 * Return air usage (in liters).
581 static double calculate_airuse(struct dive *dive)
586 for (i = 0; i < MAX_CYLINDERS; i++) {
587 pressure_t start, end;
588 cylinder_t *cyl = dive->cylinder + i;
589 int size = cyl->type.size.mliter;
595 start = cyl->start.mbar ? cyl->start : cyl->sample_start;
596 end = cyl->end.mbar ? cyl->end : cyl->sample_end;
597 kilo_atm = (to_ATM(start) - to_ATM(end)) / 1000.0;
599 /* Liters of air at 1 atm == milliliters at 1k atm*/
600 airuse += kilo_atm * size;
605 static int calculate_sac(struct dive *dive)
607 double airuse, pressure, sac;
610 airuse = calculate_airuse(dive);
613 if (!dive->duration.seconds)
616 /* find and eliminate long surface intervals */
617 duration = dive->duration.seconds;
618 for (i = 0; i < dive->samples; i++) {
619 if (dive->sample[i].depth.mm < 100) { /* less than 10cm */
621 while (end < dive->samples && dive->sample[end].depth.mm < 100)
623 /* we only want the actual surface time during a dive */
624 if (end < dive->samples) {
626 duration -= dive->sample[end].time.seconds -
627 dive->sample[i].time.seconds;
632 /* Mean pressure in atm: 1 atm per 10m */
633 pressure = 1 + (dive->meandepth.mm / 10000.0);
634 sac = airuse / pressure * 60 / duration;
636 /* milliliters per minute.. */
640 void update_cylinder_related_info(struct dive *dive)
643 dive->sac = calculate_sac(dive);
644 dive->otu = calculate_otu(dive);
648 static void get_string(char **str, const char *s)
664 static void get_location(struct dive *dive, char **str)
666 get_string(str, dive->location);
669 static void get_cylinder(struct dive *dive, char **str)
671 get_string(str, dive->cylinder[0].type.description);
674 static void get_suit(struct dive *dive, char **str)
676 get_string(str, dive->suit);
680 * Set up anything that could have changed due to editing
681 * of dive information; we need to do this for both models,
682 * so we simply call set_one_dive again with the non-current model
684 /* forward declaration for recursion */
685 static gboolean set_one_dive(GtkTreeModel *model,
690 static void fill_one_dive(struct dive *dive,
694 char *location, *cylinder, *suit;
695 GtkTreeStore *othermodel;
697 get_cylinder(dive, &cylinder);
698 get_location(dive, &location);
699 get_suit(dive, &suit);
701 gtk_tree_store_set(GTK_TREE_STORE(model), iter,
702 DIVE_NR, dive->number,
703 DIVE_LOCATION, location,
704 DIVE_CYLINDER, cylinder,
705 DIVE_RATING, dive->rating,
708 DIVE_TOTALWEIGHT, total_weight(dive),
716 if (model == GTK_TREE_MODEL(dive_list.treemodel))
717 othermodel = dive_list.listmodel;
719 othermodel = dive_list.treemodel;
720 if (othermodel != dive_list.model)
722 gtk_tree_model_foreach(GTK_TREE_MODEL(othermodel), set_one_dive, dive);
725 static gboolean set_one_dive(GtkTreeModel *model,
733 /* Get the dive number */
734 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
737 dive = get_dive(idx);
740 if (data && dive != data)
743 fill_one_dive(dive, model, iter);
747 void flush_divelist(struct dive *dive)
749 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
751 gtk_tree_model_foreach(model, set_one_dive, dive);
754 void set_divelist_font(const char *font)
756 PangoFontDescription *font_desc = pango_font_description_from_string(font);
757 gtk_widget_modify_font(dive_list.tree_view, font_desc);
758 pango_font_description_free(font_desc);
761 void update_dive_list_units(void)
764 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
766 (void) get_depth_units(0, NULL, &unit);
767 gtk_tree_view_column_set_title(dive_list.depth, unit);
769 (void) get_temp_units(0, &unit);
770 gtk_tree_view_column_set_title(dive_list.temperature, unit);
772 (void) get_weight_units(0, NULL, &unit);
773 gtk_tree_view_column_set_title(dive_list.totalweight, unit);
775 gtk_tree_model_foreach(model, set_one_dive, NULL);
778 void update_dive_list_col_visibility(void)
780 gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
781 gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
782 gtk_tree_view_column_set_visible(dive_list.totalweight, visible_cols.totalweight);
783 gtk_tree_view_column_set_visible(dive_list.suit, visible_cols.suit);
784 gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
785 gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
786 gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
790 /* random heuristic - not diving in three days implies new dive trip */
791 #define TRIP_THRESHOLD 3600*24*3
792 static int new_group(struct dive *dive, struct dive **last_dive, time_t *tm_date)
797 struct dive *ldive = *last_dive;
798 if (abs(dive->when - ldive->when) < TRIP_THRESHOLD) {
805 struct tm *tm1 = gmtime(&dive->when);
809 *tm_date = mktime(tm1);
814 static void fill_dive_list(void)
817 GtkTreeIter iter, parent_iter;
818 GtkTreeStore *liststore, *treestore;
819 struct dive *last_dive = NULL;
820 struct dive *first_trip_dive = NULL;
821 struct dive *last_trip_dive = NULL;
824 treestore = GTK_TREE_STORE(dive_list.treemodel);
825 liststore = GTK_TREE_STORE(dive_list.listmodel);
829 struct dive *dive = dive_table.dives[i];
831 if (new_group(dive, &last_dive, &dive_date))
833 /* make sure we display the first date of the trip in previous summary */
834 if (first_trip_dive && last_trip_dive && last_trip_dive->when < first_trip_dive->when)
835 gtk_tree_store_set(treestore, &parent_iter,
836 DIVE_DATE, last_trip_dive->when,
837 DIVE_LOCATION, last_trip_dive->location,
839 first_trip_dive = dive;
841 gtk_tree_store_append(treestore, &parent_iter, NULL);
842 gtk_tree_store_set(treestore, &parent_iter,
843 DIVE_INDEX, -NEW_TRIP,
845 DIVE_DATE, dive_date,
846 DIVE_LOCATION, dive->location,
851 last_trip_dive = dive;
852 update_cylinder_related_info(dive);
853 gtk_tree_store_append(treestore, &iter, &parent_iter);
854 gtk_tree_store_set(treestore, &iter,
856 DIVE_NR, dive->number,
857 DIVE_DATE, dive->when,
858 DIVE_DEPTH, dive->maxdepth,
859 DIVE_DURATION, dive->duration.seconds,
860 DIVE_LOCATION, dive->location,
861 DIVE_RATING, dive->rating,
862 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
865 gtk_tree_store_append(liststore, &iter, NULL);
866 gtk_tree_store_set(liststore, &iter,
868 DIVE_NR, dive->number,
869 DIVE_DATE, dive->when,
870 DIVE_DEPTH, dive->maxdepth,
871 DIVE_DURATION, dive->duration.seconds,
872 DIVE_LOCATION, dive->location,
873 DIVE_RATING, dive->rating,
874 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
876 DIVE_SUIT, dive->suit,
881 update_dive_list_units();
882 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
883 GtkTreeSelection *selection;
885 /* select the last dive (and make sure it's an actual dive that is selected) */
886 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
887 first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive);
888 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
889 gtk_tree_selection_select_iter(selection, &iter);
890 selectiontracker = realloc(selectiontracker, sizeof(int));
891 *selectiontracker = selected_dive;
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_TOTALWEIGHT] = { "lbs", weight_data_func, NULL, ALIGN_RIGHT, &visible_cols.totalweight },
917 [DIVE_SUIT] = { "Suit", NULL, NULL, ALIGN_LEFT, &visible_cols.suit },
918 [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
919 [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
920 [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
921 [DIVE_OTU] = { "OTU", otu_data_func, NULL, 0, &visible_cols.otu },
922 [DIVE_LOCATION] = { "Location", NULL, NULL, ALIGN_LEFT },
926 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col)
928 int index = col - &dl_column[0];
929 const char *title = col->header;
930 data_func_t data_func = col->data;
931 sort_func_t sort_func = col->sort;
932 unsigned int flags = col->flags;
933 int *visible = col->visible;
934 GtkWidget *tree_view = dl->tree_view;
935 GtkTreeStore *treemodel = dl->treemodel;
936 GtkTreeStore *listmodel = dl->listmodel;
937 GtkTreeViewColumn *ret;
939 if (visible && !*visible)
941 ret = tree_view_column(tree_view, index, title, data_func, flags);
943 /* the sort functions are needed in the corresponding models */
944 if (index == DIVE_DATE)
945 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treemodel), index, sort_func, NULL, NULL);
947 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(listmodel), index, sort_func, NULL, NULL);
953 * This is some crazy crap. The only way to get default focus seems
954 * to be to grab focus as the widget is being shown the first time.
956 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
958 gtk_widget_grab_focus(tree_view);
961 static void row_activated_cb(GtkTreeView *tree_view,
963 GtkTreeViewColumn *column,
969 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path))
971 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &index, -1);
972 /* a negative index is special for the "group by date" entries */
974 edit_dive_info(get_dive(index));
977 void add_dive_cb(GtkWidget *menuitem, gpointer data)
982 if (add_new_dive(dive)) {
990 void edit_dive_cb(GtkWidget *menuitem, gpointer data)
992 edit_multi_dive_info(amount_selected, selectiontracker);
995 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
997 GtkWidget *menu, *menuitem;
998 char editlabel[] = "Edit dives";
1000 menu = gtk_menu_new();
1001 menuitem = gtk_menu_item_new_with_label("Add dive");
1002 g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL);
1003 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1004 if (amount_selected) {
1005 if (amount_selected == 1)
1006 editlabel[strlen(editlabel) - 1] = '\0';
1007 menuitem = gtk_menu_item_new_with_label(editlabel);
1008 g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_cb), model);
1009 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1011 gtk_widget_show_all(menu);
1013 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
1014 button, gtk_get_current_event_time());
1017 static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata)
1019 popup_divelist_menu(tree_view, GTK_TREE_MODEL(dive_list.model), 0);
1022 static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
1024 /* Right-click? Bring up the menu */
1025 if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
1026 popup_divelist_menu(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(dive_list.model), 3);
1032 /* we need to have a temporary copy of the selected dives while
1033 switching model as the selection_cb function keeps getting called
1034 when gtk_tree_selection_select_path is called. We also need to
1035 keep copies of the sort order so we can restore that as well after
1036 switching models. */
1037 static int *oldselection;
1038 static int old_nr_selected;
1039 static gboolean second_call = FALSE;
1040 static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, };
1041 static int lastcol = DIVE_DATE;
1043 /* Check if this dive was selected previously and select it again in the new model;
1044 * This is used after we switch models to maintain consistent selections.
1045 * We always return FALSE to iterate through all dives */
1046 static gboolean select_selected(GtkTreeModel *model, GtkTreePath *path,
1047 GtkTreeIter *iter, gpointer data)
1050 GtkTreeSelection *selection = GTK_TREE_SELECTION(data);
1052 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
1053 for (i = 0; i < old_nr_selected; i++)
1054 if (oldselection[i] == idx) {
1055 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path);
1056 gtk_tree_selection_select_path(selection, path);
1064 static void update_column_and_order(int colid)
1066 /* Careful: the index into treecolumns is off by one as we don't have a
1067 tree_view column for DIVE_INDEX */
1068 GtkTreeViewColumn **treecolumns = &dive_list.nr;
1070 /* this will trigger a second call into sort_column_change_cb,
1071 so make sure we don't start an infinite recursion... */
1073 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, sortorder[colid]);
1074 gtk_tree_view_column_set_sort_order(treecolumns[colid - 1], sortorder[colid]);
1075 second_call = FALSE;
1078 /* If the sort column is date (default), show the tree model.
1079 For every other sort column only show the list model.
1080 If the model changed, inform the new model of the chosen sort column and make
1081 sure the same dives are still selected.
1083 The challenge with this function is that once we change the model
1084 we also need to change the sort column again (as it was changed in
1085 the other model) and that causes this function to be called
1086 recursively - so we need to catch that.
1088 static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data)
1092 GtkTreeStore *currentmodel = dive_list.model;
1097 gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order);
1098 if(colid == lastcol) {
1099 /* we just changed sort order */
1100 sortorder[colid] = order;
1105 if(colid == DIVE_DATE)
1106 dive_list.model = dive_list.treemodel;
1108 dive_list.model = dive_list.listmodel;
1109 if (dive_list.model != currentmodel) {
1110 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1112 /* remember what is currently selected, switch models and reselect the selected rows */
1113 old_nr_selected = amount_selected;
1114 oldselection = malloc(old_nr_selected * sizeof(int));
1115 if (amount_selected)
1116 memcpy(oldselection, selectiontracker, amount_selected * sizeof(int));
1117 gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model));
1119 update_column_and_order(colid);
1121 if (old_nr_selected) {
1122 /* we need to select all the dives that were selected */
1123 /* this is fundamentally an n^2 algorithm as implemented - YUCK */
1124 gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), select_selected, selection);
1127 if (order != sortorder[colid]) {
1128 update_column_and_order(colid);
1133 GtkWidget *dive_list_create(void)
1135 GtkTreeSelection *selection;
1137 dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1138 G_TYPE_INT, /* index */
1139 G_TYPE_INT, /* nr */
1140 G_TYPE_INT, /* Date */
1141 G_TYPE_INT, /* Star rating */
1142 G_TYPE_INT, /* Depth */
1143 G_TYPE_INT, /* Duration */
1144 G_TYPE_INT, /* Temperature */
1145 G_TYPE_INT, /* Total weight */
1146 G_TYPE_STRING, /* Suit */
1147 G_TYPE_STRING, /* Cylinder */
1148 G_TYPE_INT, /* Nitrox */
1149 G_TYPE_INT, /* SAC */
1150 G_TYPE_INT, /* OTU */
1151 G_TYPE_STRING /* Location */
1153 dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1154 G_TYPE_INT, /* index */
1155 G_TYPE_INT, /* nr */
1156 G_TYPE_INT, /* Date */
1157 G_TYPE_INT, /* Star rating */
1158 G_TYPE_INT, /* Depth */
1159 G_TYPE_INT, /* Duration */
1160 G_TYPE_INT, /* Temperature */
1161 G_TYPE_INT, /* Total weight */
1162 G_TYPE_STRING, /* Suit */
1163 G_TYPE_STRING, /* Cylinder */
1164 G_TYPE_INT, /* Nitrox */
1165 G_TYPE_INT, /* SAC */
1166 G_TYPE_INT, /* OTU */
1167 G_TYPE_STRING /* Location */
1169 dive_list.model = dive_list.treemodel;
1170 dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
1171 set_divelist_font(divelist_font);
1173 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1175 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
1176 gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
1178 dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR);
1179 dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE);
1180 dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING);
1181 dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH);
1182 dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION);
1183 dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE);
1184 dive_list.totalweight = divelist_column(&dive_list, dl_column + DIVE_TOTALWEIGHT);
1185 dive_list.suit = divelist_column(&dive_list, dl_column + DIVE_SUIT);
1186 dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER);
1187 dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX);
1188 dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC);
1189 dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU);
1190 dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION);
1194 g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
1195 "search-column", DIVE_LOCATION,
1199 g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
1200 g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL);
1201 g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL);
1202 g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL);
1203 g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), NULL);
1204 g_signal_connect(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1205 g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1207 gtk_tree_selection_set_select_function(selection, modify_selection_cb, NULL, NULL);
1209 dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
1210 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
1211 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1212 gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
1214 dive_list.changed = 0;
1216 return dive_list.container_widget;
1219 void mark_divelist_changed(int changed)
1221 dive_list.changed = changed;
1224 int unsaved_changes()
1226 return dive_list.changed;