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);
172 track_select(*diveidx);
176 /* if we click on a summary dive, we actually want to select / unselect
177 all the dives "below" it */
178 static void select_children(GtkTreeModel *model, GtkTreeSelection * selection,
179 GtkTreeIter *iter, gboolean was_selected)
182 gboolean unexpand = FALSE;
186 memcpy(&parent, iter, sizeof(parent));
188 tpath = gtk_tree_model_get_path(model, &parent);
190 /* stupid gtk doesn't allow us to select rows that are invisible; so if the
191 user clicks on a row that isn't expanded, we briefly expand it, select the
192 children, and then unexpand it again */
193 if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath)) {
195 gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
197 nr_children = gtk_tree_model_iter_n_children(model, &parent);
198 for (i = 0; i < nr_children; i++) {
199 gtk_tree_model_iter_nth_child(model, iter, &parent, i);
201 gtk_tree_selection_unselect_iter(selection, iter);
203 gtk_tree_selection_select_iter(selection, iter);
206 gtk_tree_view_collapse_row(GTK_TREE_VIEW(dive_list.tree_view), tpath);
209 /* make sure that if we expand a summary row that is selected, the children show
210 up as selected, too */
211 void row_expanded_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data)
213 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
215 if (gtk_tree_selection_path_is_selected(selection, path))
216 select_children(GTK_TREE_MODEL(dive_list.model), selection, iter, FALSE);
219 /* this is called _before_ the selection is changed, for every single entry;
220 * we simply have it call down the tree to make sure that summary items toggle
222 gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model,
223 GtkTreePath *path, gboolean was_selected, gpointer userdata)
228 if (gtk_tree_model_get_iter(model, &iter, path)) {
229 gtk_tree_model_get(model, &iter, DIVE_INDEX, &dive_idx, -1);
230 /* turns out we need to move the selectiontracker here */
232 track_unselect(dive_idx);
234 track_select(dive_idx);
236 select_children(model, selection, &iter, was_selected);
239 /* allow this selection to proceed */
243 /* this is called when gtk thinks that the selection has changed */
244 static void selection_cb(GtkTreeSelection *selection, gpointer userdata)
246 process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model));
250 const char *star_strings[] = {
259 static void star_data_func(GtkTreeViewColumn *col,
260 GtkCellRenderer *renderer,
268 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_RATING, &nr_stars, -1);
272 if (nr_stars < 0 || nr_stars > 5)
274 snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]);
276 g_object_set(renderer, "text", buffer, NULL);
279 static void date_data_func(GtkTreeViewColumn *col,
280 GtkCellRenderer *renderer,
290 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &val, -1);
298 snprintf(buffer, sizeof(buffer),
299 "Trip %s, %s %d, %d",
300 weekday(tm->tm_wday),
301 monthname(tm->tm_mon),
302 tm->tm_mday, tm->tm_year + 1900);
305 snprintf(buffer, sizeof(buffer),
306 "%s, %s %d, %d %02d:%02d",
307 weekday(tm->tm_wday),
308 monthname(tm->tm_mon),
309 tm->tm_mday, tm->tm_year + 1900,
310 tm->tm_hour, tm->tm_min);
312 g_object_set(renderer, "text", buffer, NULL);
315 static void depth_data_func(GtkTreeViewColumn *col,
316 GtkCellRenderer *renderer,
321 int depth, integer, frac, len, idx;
324 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1);
329 switch (output_units.length) {
331 /* To tenths of meters */
332 depth = (depth + 49) / 100;
333 integer = depth / 10;
342 integer = mm_to_feet(depth) + 0.5;
348 len = snprintf(buffer, sizeof(buffer), "%d", integer);
350 len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
352 g_object_set(renderer, "text", buffer, NULL);
355 static void duration_data_func(GtkTreeViewColumn *col,
356 GtkCellRenderer *renderer,
365 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1);
369 snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
371 g_object_set(renderer, "text", buffer, NULL);
374 static void temperature_data_func(GtkTreeViewColumn *col,
375 GtkCellRenderer *renderer,
383 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1);
386 if (idx >= 0 && value) {
388 switch (output_units.temperature) {
390 deg = mkelvin_to_C(value);
393 deg = mkelvin_to_F(value);
398 snprintf(buffer, sizeof(buffer), "%.1f", deg);
401 g_object_set(renderer, "text", buffer, NULL);
404 static void nr_data_func(GtkTreeViewColumn *col,
405 GtkCellRenderer *renderer,
413 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1);
417 snprintf(buffer, sizeof(buffer), "%d", nr);
418 g_object_set(renderer, "text", buffer, NULL);
422 * Get "maximal" dive gas for a dive.
424 * - Trimix trumps nitrox (highest He wins, O2 breaks ties)
425 * - Nitrox trumps air (even if hypoxic)
426 * These are the same rules as the inter-dive sorting rules.
428 static void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
431 int maxo2 = -1, maxhe = -1, mino2 = 1000;
433 for (i = 0; i < MAX_CYLINDERS; i++) {
434 cylinder_t *cyl = dive->cylinder + i;
435 struct gasmix *mix = &cyl->gasmix;
436 int o2 = mix->o2.permille;
437 int he = mix->he.permille;
439 if (cylinder_none(cyl))
455 /* All air? Show/sort as "air"/zero */
456 if (!maxhe && maxo2 == AIR_PERMILLE && mino2 == maxo2)
463 static int total_weight(struct dive *dive)
465 int i, total_grams = 0;
468 for (i=0; i< MAX_WEIGHTSYSTEMS; i++)
469 total_grams += dive->weightsystem[i].weight.grams;
473 static void weight_data_func(GtkTreeViewColumn *col,
474 GtkCellRenderer *renderer,
484 gtk_tree_model_get(model, iter, DIVE_INDEX, &indx, -1);
485 dive = get_dive(indx);
486 value = get_weight_units(total_weight(dive), &decimals, NULL);
490 snprintf(buffer, sizeof(buffer), "%.*f", decimals, value);
492 g_object_set(renderer, "text", buffer, NULL);
495 static gint nitrox_sort_func(GtkTreeModel *model,
500 int index_a, index_b;
504 int a_o2low, b_o2low;
506 gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1);
507 gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1);
508 a = get_dive(index_a);
509 b = get_dive(index_b);
510 get_dive_gas(a, &a_o2, &a_he, &a_o2low);
511 get_dive_gas(b, &b_o2, &b_he, &b_o2low);
513 /* Sort by Helium first, O2 second */
516 return a_o2low - b_o2low;
522 #define UTF8_ELLIPSIS "\xE2\x80\xA6"
524 static void nitrox_data_func(GtkTreeViewColumn *col,
525 GtkCellRenderer *renderer,
530 int idx, o2, he, o2low;
534 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
539 dive = get_dive(idx);
540 get_dive_gas(dive, &o2, &he, &o2low);
543 o2low = (o2low + 5) / 10;
546 snprintf(buffer, sizeof(buffer), "%d/%d", o2, he);
549 snprintf(buffer, sizeof(buffer), "%d", o2);
551 snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
553 strcpy(buffer, "air");
555 g_object_set(renderer, "text", buffer, NULL);
558 /* Render the SAC data (integer value of "ml / min") */
559 static void sac_data_func(GtkTreeViewColumn *col,
560 GtkCellRenderer *renderer,
570 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1);
572 if (idx < 0 || !value) {
577 sac = value / 1000.0;
578 switch (output_units.volume) {
584 sac = ml_to_cuft(sac * 1000);
587 snprintf(buffer, sizeof(buffer), fmt, sac);
589 g_object_set(renderer, "text", buffer, NULL);
592 /* Render the OTU data (integer value of "OTU") */
593 static void otu_data_func(GtkTreeViewColumn *col,
594 GtkCellRenderer *renderer,
602 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1);
604 if (idx < 0 || !value)
607 snprintf(buffer, sizeof(buffer), "%d", value);
609 g_object_set(renderer, "text", buffer, NULL);
612 /* calculate OTU for a dive */
613 static int calculate_otu(struct dive *dive)
618 for (i = 1; i < dive->samples; i++) {
621 struct sample *sample = dive->sample + i;
622 struct sample *psample = sample - 1;
623 t = sample->time.seconds - psample->time.seconds;
624 int o2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille;
627 po2 = o2 / 1000.0 * (sample->depth.mm + 10000) / 10000.0;
629 otu += pow(po2 - 0.5, 0.83) * t / 30.0;
634 * Return air usage (in liters).
636 static double calculate_airuse(struct dive *dive)
641 for (i = 0; i < MAX_CYLINDERS; i++) {
642 pressure_t start, end;
643 cylinder_t *cyl = dive->cylinder + i;
644 int size = cyl->type.size.mliter;
650 start = cyl->start.mbar ? cyl->start : cyl->sample_start;
651 end = cyl->end.mbar ? cyl->end : cyl->sample_end;
652 kilo_atm = (to_ATM(start) - to_ATM(end)) / 1000.0;
654 /* Liters of air at 1 atm == milliliters at 1k atm*/
655 airuse += kilo_atm * size;
660 static int calculate_sac(struct dive *dive)
662 double airuse, pressure, sac;
665 airuse = calculate_airuse(dive);
668 if (!dive->duration.seconds)
671 /* find and eliminate long surface intervals */
672 duration = dive->duration.seconds;
673 for (i = 0; i < dive->samples; i++) {
674 if (dive->sample[i].depth.mm < 100) { /* less than 10cm */
676 while (end < dive->samples && dive->sample[end].depth.mm < 100)
678 /* we only want the actual surface time during a dive */
679 if (end < dive->samples) {
681 duration -= dive->sample[end].time.seconds -
682 dive->sample[i].time.seconds;
687 /* Mean pressure in atm: 1 atm per 10m */
688 pressure = 1 + (dive->meandepth.mm / 10000.0);
689 sac = airuse / pressure * 60 / duration;
691 /* milliliters per minute.. */
695 void update_cylinder_related_info(struct dive *dive)
698 dive->sac = calculate_sac(dive);
699 dive->otu = calculate_otu(dive);
703 static void get_string(char **str, const char *s)
719 static void get_location(struct dive *dive, char **str)
721 get_string(str, dive->location);
724 static void get_cylinder(struct dive *dive, char **str)
726 get_string(str, dive->cylinder[0].type.description);
729 static void get_suit(struct dive *dive, char **str)
731 get_string(str, dive->suit);
735 * Set up anything that could have changed due to editing
736 * of dive information; we need to do this for both models,
737 * so we simply call set_one_dive again with the non-current model
739 /* forward declaration for recursion */
740 static gboolean set_one_dive(GtkTreeModel *model,
745 static void fill_one_dive(struct dive *dive,
749 char *location, *cylinder, *suit;
750 GtkTreeStore *othermodel;
752 get_cylinder(dive, &cylinder);
753 get_location(dive, &location);
754 get_suit(dive, &suit);
756 gtk_tree_store_set(GTK_TREE_STORE(model), iter,
757 DIVE_NR, dive->number,
758 DIVE_LOCATION, location,
759 DIVE_CYLINDER, cylinder,
760 DIVE_RATING, dive->rating,
763 DIVE_TOTALWEIGHT, total_weight(dive),
771 if (model == GTK_TREE_MODEL(dive_list.treemodel))
772 othermodel = dive_list.listmodel;
774 othermodel = dive_list.treemodel;
775 if (othermodel != dive_list.model)
777 gtk_tree_model_foreach(GTK_TREE_MODEL(othermodel), set_one_dive, dive);
780 static gboolean set_one_dive(GtkTreeModel *model,
788 /* Get the dive number */
789 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
792 dive = get_dive(idx);
795 if (data && dive != data)
798 fill_one_dive(dive, model, iter);
802 void flush_divelist(struct dive *dive)
804 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
806 gtk_tree_model_foreach(model, set_one_dive, dive);
809 void set_divelist_font(const char *font)
811 PangoFontDescription *font_desc = pango_font_description_from_string(font);
812 gtk_widget_modify_font(dive_list.tree_view, font_desc);
813 pango_font_description_free(font_desc);
816 void update_dive_list_units(void)
819 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
821 (void) get_depth_units(0, NULL, &unit);
822 gtk_tree_view_column_set_title(dive_list.depth, unit);
824 (void) get_temp_units(0, &unit);
825 gtk_tree_view_column_set_title(dive_list.temperature, unit);
827 (void) get_weight_units(0, NULL, &unit);
828 gtk_tree_view_column_set_title(dive_list.totalweight, unit);
830 gtk_tree_model_foreach(model, set_one_dive, NULL);
833 void update_dive_list_col_visibility(void)
835 gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
836 gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
837 gtk_tree_view_column_set_visible(dive_list.totalweight, visible_cols.totalweight);
838 gtk_tree_view_column_set_visible(dive_list.suit, visible_cols.suit);
839 gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
840 gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
841 gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
845 /* random heuristic - not diving in three days implies new dive trip */
846 #define TRIP_THRESHOLD 3600*24*3
847 static int new_group(struct dive *dive, struct dive **last_dive, time_t *tm_date)
852 struct dive *ldive = *last_dive;
853 if (abs(dive->when - ldive->when) < TRIP_THRESHOLD) {
860 struct tm *tm1 = gmtime(&dive->when);
864 *tm_date = mktime(tm1);
869 static void fill_dive_list(void)
872 GtkTreeIter iter, parent_iter;
873 GtkTreeStore *liststore, *treestore;
874 struct dive *last_dive = NULL;
875 struct dive *first_trip_dive = NULL;
876 struct dive *last_trip_dive = NULL;
879 treestore = GTK_TREE_STORE(dive_list.treemodel);
880 liststore = GTK_TREE_STORE(dive_list.listmodel);
884 struct dive *dive = dive_table.dives[i];
886 if (new_group(dive, &last_dive, &dive_date))
888 /* make sure we display the first date of the trip in previous summary */
889 if (first_trip_dive && last_trip_dive && last_trip_dive->when < first_trip_dive->when)
890 gtk_tree_store_set(treestore, &parent_iter,
891 DIVE_DATE, last_trip_dive->when,
892 DIVE_LOCATION, last_trip_dive->location,
894 first_trip_dive = dive;
896 gtk_tree_store_append(treestore, &parent_iter, NULL);
897 gtk_tree_store_set(treestore, &parent_iter,
898 DIVE_INDEX, -NEW_TRIP,
900 DIVE_DATE, dive_date,
901 DIVE_LOCATION, dive->location,
906 last_trip_dive = dive;
907 update_cylinder_related_info(dive);
908 gtk_tree_store_append(treestore, &iter, &parent_iter);
909 gtk_tree_store_set(treestore, &iter,
911 DIVE_NR, dive->number,
912 DIVE_DATE, dive->when,
913 DIVE_DEPTH, dive->maxdepth,
914 DIVE_DURATION, dive->duration.seconds,
915 DIVE_LOCATION, dive->location,
916 DIVE_RATING, dive->rating,
917 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
920 gtk_tree_store_append(liststore, &iter, NULL);
921 gtk_tree_store_set(liststore, &iter,
923 DIVE_NR, dive->number,
924 DIVE_DATE, dive->when,
925 DIVE_DEPTH, dive->maxdepth,
926 DIVE_DURATION, dive->duration.seconds,
927 DIVE_LOCATION, dive->location,
928 DIVE_RATING, dive->rating,
929 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
931 DIVE_SUIT, dive->suit,
936 update_dive_list_units();
937 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
938 GtkTreeSelection *selection;
940 /* select the last dive (and make sure it's an actual dive that is selected) */
941 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
942 first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive);
943 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
944 gtk_tree_selection_select_iter(selection, &iter);
948 void dive_list_update_dives(void)
950 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.treemodel));
951 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.listmodel));
956 static struct divelist_column {
963 [DIVE_NR] = { "#", nr_data_func, NULL, ALIGN_RIGHT | UNSORTABLE },
964 [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
965 [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
966 [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
967 [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
968 [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
969 [DIVE_TOTALWEIGHT] = { "lbs", weight_data_func, NULL, ALIGN_RIGHT, &visible_cols.totalweight },
970 [DIVE_SUIT] = { "Suit", NULL, NULL, ALIGN_LEFT, &visible_cols.suit },
971 [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
972 [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
973 [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
974 [DIVE_OTU] = { "OTU", otu_data_func, NULL, 0, &visible_cols.otu },
975 [DIVE_LOCATION] = { "Location", NULL, NULL, ALIGN_LEFT },
979 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col)
981 int index = col - &dl_column[0];
982 const char *title = col->header;
983 data_func_t data_func = col->data;
984 sort_func_t sort_func = col->sort;
985 unsigned int flags = col->flags;
986 int *visible = col->visible;
987 GtkWidget *tree_view = dl->tree_view;
988 GtkTreeStore *treemodel = dl->treemodel;
989 GtkTreeStore *listmodel = dl->listmodel;
990 GtkTreeViewColumn *ret;
992 if (visible && !*visible)
994 ret = tree_view_column(tree_view, index, title, data_func, flags);
996 /* the sort functions are needed in the corresponding models */
997 if (index == DIVE_DATE)
998 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treemodel), index, sort_func, NULL, NULL);
1000 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(listmodel), index, sort_func, NULL, NULL);
1006 * This is some crazy crap. The only way to get default focus seems
1007 * to be to grab focus as the widget is being shown the first time.
1009 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
1011 gtk_widget_grab_focus(tree_view);
1014 static void row_activated_cb(GtkTreeView *tree_view,
1016 GtkTreeViewColumn *column,
1022 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path))
1024 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &index, -1);
1025 /* a negative index is special for the "group by date" entries */
1027 edit_dive_info(get_dive(index));
1030 void add_dive_cb(GtkWidget *menuitem, gpointer data)
1034 dive = alloc_dive();
1035 if (add_new_dive(dive)) {
1043 void edit_dive_cb(GtkWidget *menuitem, gpointer data)
1045 edit_multi_dive_info(amount_selected, selectiontracker);
1048 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
1050 GtkWidget *menu, *menuitem;
1051 char editlabel[] = "Edit dives";
1053 menu = gtk_menu_new();
1054 menuitem = gtk_menu_item_new_with_label("Add dive");
1055 g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL);
1056 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1057 if (amount_selected) {
1058 if (amount_selected == 1)
1059 editlabel[strlen(editlabel) - 1] = '\0';
1060 menuitem = gtk_menu_item_new_with_label(editlabel);
1061 g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_cb), model);
1062 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1064 gtk_widget_show_all(menu);
1066 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
1067 button, gtk_get_current_event_time());
1070 static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata)
1072 popup_divelist_menu(tree_view, GTK_TREE_MODEL(dive_list.model), 0);
1075 static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
1077 /* Right-click? Bring up the menu */
1078 if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
1079 popup_divelist_menu(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(dive_list.model), 3);
1085 /* we need to have a temporary copy of the selected dives while
1086 switching model as the selection_cb function keeps getting called
1087 when gtk_tree_selection_select_path is called. We also need to
1088 keep copies of the sort order so we can restore that as well after
1089 switching models. */
1090 static int *oldselection;
1091 static int old_nr_selected;
1092 static gboolean second_call = FALSE;
1093 static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, };
1094 static int lastcol = DIVE_DATE;
1096 /* Check if this dive was selected previously and select it again in the new model;
1097 * This is used after we switch models to maintain consistent selections.
1098 * We always return FALSE to iterate through all dives */
1099 static gboolean select_selected(GtkTreeModel *model, GtkTreePath *path,
1100 GtkTreeIter *iter, gpointer data)
1103 GtkTreeSelection *selection = GTK_TREE_SELECTION(data);
1105 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
1106 for (i = 0; i < old_nr_selected; i++)
1107 if (oldselection[i] == idx) {
1108 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path);
1109 gtk_tree_selection_select_path(selection, path);
1117 static void update_column_and_order(int colid)
1119 /* Careful: the index into treecolumns is off by one as we don't have a
1120 tree_view column for DIVE_INDEX */
1121 GtkTreeViewColumn **treecolumns = &dive_list.nr;
1123 /* this will trigger a second call into sort_column_change_cb,
1124 so make sure we don't start an infinite recursion... */
1126 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, sortorder[colid]);
1127 gtk_tree_view_column_set_sort_order(treecolumns[colid - 1], sortorder[colid]);
1128 second_call = FALSE;
1131 /* If the sort column is date (default), show the tree model.
1132 For every other sort column only show the list model.
1133 If the model changed, inform the new model of the chosen sort column and make
1134 sure the same dives are still selected.
1136 The challenge with this function is that once we change the model
1137 we also need to change the sort column again (as it was changed in
1138 the other model) and that causes this function to be called
1139 recursively - so we need to catch that.
1141 static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data)
1145 GtkTreeStore *currentmodel = dive_list.model;
1150 gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order);
1151 if(colid == lastcol) {
1152 /* we just changed sort order */
1153 sortorder[colid] = order;
1158 if(colid == DIVE_DATE)
1159 dive_list.model = dive_list.treemodel;
1161 dive_list.model = dive_list.listmodel;
1162 if (dive_list.model != currentmodel) {
1163 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1165 /* remember what is currently selected, switch models and reselect the selected rows */
1166 old_nr_selected = amount_selected;
1167 oldselection = malloc(old_nr_selected * sizeof(int));
1168 if (amount_selected)
1169 memcpy(oldselection, selectiontracker, amount_selected * sizeof(int));
1170 gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model));
1172 update_column_and_order(colid);
1174 if (old_nr_selected) {
1175 /* we need to select all the dives that were selected */
1176 /* this is fundamentally an n^2 algorithm as implemented - YUCK */
1177 gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), select_selected, selection);
1180 if (order != sortorder[colid]) {
1181 update_column_and_order(colid);
1186 GtkWidget *dive_list_create(void)
1188 GtkTreeSelection *selection;
1190 dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1191 G_TYPE_INT, /* index */
1192 G_TYPE_INT, /* nr */
1193 G_TYPE_INT, /* Date */
1194 G_TYPE_INT, /* Star rating */
1195 G_TYPE_INT, /* Depth */
1196 G_TYPE_INT, /* Duration */
1197 G_TYPE_INT, /* Temperature */
1198 G_TYPE_INT, /* Total weight */
1199 G_TYPE_STRING, /* Suit */
1200 G_TYPE_STRING, /* Cylinder */
1201 G_TYPE_INT, /* Nitrox */
1202 G_TYPE_INT, /* SAC */
1203 G_TYPE_INT, /* OTU */
1204 G_TYPE_STRING /* Location */
1206 dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1207 G_TYPE_INT, /* index */
1208 G_TYPE_INT, /* nr */
1209 G_TYPE_INT, /* Date */
1210 G_TYPE_INT, /* Star rating */
1211 G_TYPE_INT, /* Depth */
1212 G_TYPE_INT, /* Duration */
1213 G_TYPE_INT, /* Temperature */
1214 G_TYPE_INT, /* Total weight */
1215 G_TYPE_STRING, /* Suit */
1216 G_TYPE_STRING, /* Cylinder */
1217 G_TYPE_INT, /* Nitrox */
1218 G_TYPE_INT, /* SAC */
1219 G_TYPE_INT, /* OTU */
1220 G_TYPE_STRING /* Location */
1222 dive_list.model = dive_list.treemodel;
1223 dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
1224 set_divelist_font(divelist_font);
1226 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1228 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
1229 gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
1231 dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR);
1232 dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE);
1233 dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING);
1234 dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH);
1235 dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION);
1236 dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE);
1237 dive_list.totalweight = divelist_column(&dive_list, dl_column + DIVE_TOTALWEIGHT);
1238 dive_list.suit = divelist_column(&dive_list, dl_column + DIVE_SUIT);
1239 dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER);
1240 dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX);
1241 dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC);
1242 dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU);
1243 dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION);
1247 g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
1248 "search-column", DIVE_LOCATION,
1252 g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
1253 g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL);
1254 g_signal_connect(dive_list.tree_view, "row-expanded", G_CALLBACK(row_expanded_cb), NULL);
1255 g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL);
1256 g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL);
1257 g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), NULL);
1258 g_signal_connect(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1259 g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1261 gtk_tree_selection_set_select_function(selection, modify_selection_cb, NULL, NULL);
1263 dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
1264 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
1265 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1266 gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
1268 dive_list.changed = 0;
1270 return dive_list.container_widget;
1273 void mark_divelist_changed(int changed)
1275 dive_list.changed = changed;
1278 int unsaved_changes()
1280 return dive_list.changed;