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 #if DEBUG_SELECTION_TRACKING
82 void dump_selection(void)
87 printf("currently selected are %d dives:", amount_selected);
88 for (i = 0; (dive = get_dive(i)) != NULL; i++) {
96 /* when subsurface starts we want to have the last dive selected. So we simply
97 walk to the first leaf (and skip the summary entries - which have negative
99 static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx)
104 while (*diveidx < 0) {
105 memcpy(&parent, iter, sizeof(parent));
106 tpath = gtk_tree_model_get_path(model, &parent);
107 if (!gtk_tree_model_iter_children(model, iter, &parent))
108 /* we should never have a parent without child */
110 if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath))
111 gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
112 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, DIVE_INDEX, diveidx, -1);
116 /* make sure that if we expand a summary row that is selected, the children show
117 up as selected, too */
118 void row_expanded_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data)
121 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
122 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
124 if (!gtk_tree_model_iter_children(model, &child, iter))
131 gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1);
132 dive = get_dive(idx);
135 gtk_tree_selection_select_iter(selection, &child);
137 gtk_tree_selection_unselect_iter(selection, &child);
138 } while (gtk_tree_model_iter_next(model, &child));
141 static GList *selection_changed = NULL;
144 * This is called _before_ the selection is changed, for every single entry;
146 * We simply create a list of all changed entries, and make sure that the
147 * group entries go at the end of the list.
149 gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model,
150 GtkTreePath *path, gboolean was_selected, gpointer userdata)
152 GtkTreeIter iter, *p;
154 if (!gtk_tree_model_get_iter(model, &iter, path))
157 /* Add the group entries to the end */
158 p = gtk_tree_iter_copy(&iter);
159 if (gtk_tree_model_iter_has_child(model, p))
160 selection_changed = g_list_append(selection_changed, p);
162 selection_changed = g_list_prepend(selection_changed, p);
166 static void select_dive(struct dive *dive, int selected)
168 if (dive->selected != selected) {
169 amount_selected += selected ? 1 : -1;
170 dive->selected = selected;
175 * This gets called when a dive group has changed selection.
177 static void select_dive_group(GtkTreeModel *model, GtkTreeSelection *selection, GtkTreeIter *iter, int selected)
182 if (!gtk_tree_model_iter_children(model, &child, iter))
189 gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1);
190 if (first && selected)
193 dive = get_dive(idx);
194 if (dive->selected == selected)
197 select_dive(dive, selected);
199 gtk_tree_selection_select_iter(selection, &child);
201 gtk_tree_selection_unselect_iter(selection, &child);
202 } while (gtk_tree_model_iter_next(model, &child));
206 * This gets called _after_ the selections have changed, for each entry that
207 * may have changed. Check if the gtk selection state matches our internal
208 * selection state to verify.
210 * The group entries are at the end, this guarantees that we have handled
211 * all the dives before we handle groups.
213 static void check_selection_cb(GtkTreeIter *iter, GtkTreeSelection *selection)
215 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
217 int idx, gtk_selected;
219 gtk_tree_model_get(model, iter,
222 dive = get_dive(idx);
223 gtk_selected = gtk_tree_selection_iter_is_selected(selection, iter);
225 select_dive_group(model, selection, iter, gtk_selected);
227 select_dive(dive, gtk_selected);
231 gtk_tree_iter_free(iter);
234 /* this is called when gtk thinks that the selection has changed */
235 static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model)
237 GList *changed = selection_changed;
239 selection_changed = NULL;
240 g_list_foreach(changed, (GFunc) check_selection_cb, selection);
241 g_list_free(changed);
242 #if DEBUG_SELECTION_TRACKING
246 process_selected_dives();
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, DIVE_NR, &nr, -1);
298 snprintf(buffer, sizeof(buffer),
299 "Trip %s, %s %d, %d (%d dive%s)",
300 weekday(tm->tm_wday),
301 monthname(tm->tm_mon),
302 tm->tm_mday, tm->tm_year + 1900,
303 nr, nr > 1 ? "s" : "");
306 snprintf(buffer, sizeof(buffer),
307 "%s, %s %d, %d %02d:%02d",
308 weekday(tm->tm_wday),
309 monthname(tm->tm_mon),
310 tm->tm_mday, tm->tm_year + 1900,
311 tm->tm_hour, tm->tm_min);
313 g_object_set(renderer, "text", buffer, NULL);
316 static void depth_data_func(GtkTreeViewColumn *col,
317 GtkCellRenderer *renderer,
322 int depth, integer, frac, len, idx;
325 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1);
330 switch (output_units.length) {
332 /* To tenths of meters */
333 depth = (depth + 49) / 100;
334 integer = depth / 10;
343 integer = mm_to_feet(depth) + 0.5;
349 len = snprintf(buffer, sizeof(buffer), "%d", integer);
351 len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
353 g_object_set(renderer, "text", buffer, NULL);
356 static void duration_data_func(GtkTreeViewColumn *col,
357 GtkCellRenderer *renderer,
366 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1);
370 snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
372 g_object_set(renderer, "text", buffer, NULL);
375 static void temperature_data_func(GtkTreeViewColumn *col,
376 GtkCellRenderer *renderer,
384 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1);
387 if (idx >= 0 && value) {
389 switch (output_units.temperature) {
391 deg = mkelvin_to_C(value);
394 deg = mkelvin_to_F(value);
399 snprintf(buffer, sizeof(buffer), "%.1f", deg);
402 g_object_set(renderer, "text", buffer, NULL);
405 static void nr_data_func(GtkTreeViewColumn *col,
406 GtkCellRenderer *renderer,
414 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1);
418 snprintf(buffer, sizeof(buffer), "%d", nr);
419 g_object_set(renderer, "text", buffer, NULL);
423 * Get "maximal" dive gas for a dive.
425 * - Trimix trumps nitrox (highest He wins, O2 breaks ties)
426 * - Nitrox trumps air (even if hypoxic)
427 * These are the same rules as the inter-dive sorting rules.
429 static void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
432 int maxo2 = -1, maxhe = -1, mino2 = 1000;
434 for (i = 0; i < MAX_CYLINDERS; i++) {
435 cylinder_t *cyl = dive->cylinder + i;
436 struct gasmix *mix = &cyl->gasmix;
437 int o2 = mix->o2.permille;
438 int he = mix->he.permille;
440 if (cylinder_none(cyl))
456 /* All air? Show/sort as "air"/zero */
457 if (!maxhe && maxo2 == AIR_PERMILLE && mino2 == maxo2)
464 static int total_weight(struct dive *dive)
466 int i, total_grams = 0;
469 for (i=0; i< MAX_WEIGHTSYSTEMS; i++)
470 total_grams += dive->weightsystem[i].weight.grams;
474 static void weight_data_func(GtkTreeViewColumn *col,
475 GtkCellRenderer *renderer,
485 gtk_tree_model_get(model, iter, DIVE_INDEX, &indx, -1);
486 dive = get_dive(indx);
487 value = get_weight_units(total_weight(dive), &decimals, NULL);
491 snprintf(buffer, sizeof(buffer), "%.*f", decimals, value);
493 g_object_set(renderer, "text", buffer, NULL);
496 static gint nitrox_sort_func(GtkTreeModel *model,
501 int index_a, index_b;
505 int a_o2low, b_o2low;
507 gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1);
508 gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1);
509 a = get_dive(index_a);
510 b = get_dive(index_b);
511 get_dive_gas(a, &a_o2, &a_he, &a_o2low);
512 get_dive_gas(b, &b_o2, &b_he, &b_o2low);
514 /* Sort by Helium first, O2 second */
517 return a_o2low - b_o2low;
523 #define UTF8_ELLIPSIS "\xE2\x80\xA6"
525 static void nitrox_data_func(GtkTreeViewColumn *col,
526 GtkCellRenderer *renderer,
531 int idx, o2, he, o2low;
535 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
540 dive = get_dive(idx);
541 get_dive_gas(dive, &o2, &he, &o2low);
544 o2low = (o2low + 5) / 10;
547 snprintf(buffer, sizeof(buffer), "%d/%d", o2, he);
550 snprintf(buffer, sizeof(buffer), "%d", o2);
552 snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
554 strcpy(buffer, "air");
556 g_object_set(renderer, "text", buffer, NULL);
559 /* Render the SAC data (integer value of "ml / min") */
560 static void sac_data_func(GtkTreeViewColumn *col,
561 GtkCellRenderer *renderer,
571 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1);
573 if (idx < 0 || !value) {
578 sac = value / 1000.0;
579 switch (output_units.volume) {
585 sac = ml_to_cuft(sac * 1000);
588 snprintf(buffer, sizeof(buffer), fmt, sac);
590 g_object_set(renderer, "text", buffer, NULL);
593 /* Render the OTU data (integer value of "OTU") */
594 static void otu_data_func(GtkTreeViewColumn *col,
595 GtkCellRenderer *renderer,
603 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1);
605 if (idx < 0 || !value)
608 snprintf(buffer, sizeof(buffer), "%d", value);
610 g_object_set(renderer, "text", buffer, NULL);
613 /* calculate OTU for a dive */
614 static int calculate_otu(struct dive *dive)
619 for (i = 1; i < dive->samples; i++) {
622 struct sample *sample = dive->sample + i;
623 struct sample *psample = sample - 1;
624 t = sample->time.seconds - psample->time.seconds;
625 int o2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille;
628 po2 = o2 / 1000.0 * (sample->depth.mm + 10000) / 10000.0;
630 otu += pow(po2 - 0.5, 0.83) * t / 30.0;
635 * Return air usage (in liters).
637 static double calculate_airuse(struct dive *dive)
642 for (i = 0; i < MAX_CYLINDERS; i++) {
643 pressure_t start, end;
644 cylinder_t *cyl = dive->cylinder + i;
645 int size = cyl->type.size.mliter;
651 start = cyl->start.mbar ? cyl->start : cyl->sample_start;
652 end = cyl->end.mbar ? cyl->end : cyl->sample_end;
653 kilo_atm = (to_ATM(start) - to_ATM(end)) / 1000.0;
655 /* Liters of air at 1 atm == milliliters at 1k atm*/
656 airuse += kilo_atm * size;
661 static int calculate_sac(struct dive *dive)
663 double airuse, pressure, sac;
666 airuse = calculate_airuse(dive);
669 if (!dive->duration.seconds)
672 /* find and eliminate long surface intervals */
673 duration = dive->duration.seconds;
674 for (i = 0; i < dive->samples; i++) {
675 if (dive->sample[i].depth.mm < 100) { /* less than 10cm */
677 while (end < dive->samples && dive->sample[end].depth.mm < 100)
679 /* we only want the actual surface time during a dive */
680 if (end < dive->samples) {
682 duration -= dive->sample[end].time.seconds -
683 dive->sample[i].time.seconds;
688 /* Mean pressure in atm: 1 atm per 10m */
689 pressure = 1 + (dive->meandepth.mm / 10000.0);
690 sac = airuse / pressure * 60 / duration;
692 /* milliliters per minute.. */
696 void update_cylinder_related_info(struct dive *dive)
699 dive->sac = calculate_sac(dive);
700 dive->otu = calculate_otu(dive);
704 static void get_string(char **str, const char *s)
720 static void get_location(struct dive *dive, char **str)
722 get_string(str, dive->location);
725 static void get_cylinder(struct dive *dive, char **str)
727 get_string(str, dive->cylinder[0].type.description);
730 static void get_suit(struct dive *dive, char **str)
732 get_string(str, dive->suit);
736 * Set up anything that could have changed due to editing
737 * of dive information; we need to do this for both models,
738 * so we simply call set_one_dive again with the non-current model
740 /* forward declaration for recursion */
741 static gboolean set_one_dive(GtkTreeModel *model,
746 static void fill_one_dive(struct dive *dive,
750 char *location, *cylinder, *suit;
751 GtkTreeStore *othermodel;
753 get_cylinder(dive, &cylinder);
754 get_location(dive, &location);
755 get_suit(dive, &suit);
757 gtk_tree_store_set(GTK_TREE_STORE(model), iter,
758 DIVE_NR, dive->number,
759 DIVE_LOCATION, location,
760 DIVE_CYLINDER, cylinder,
761 DIVE_RATING, dive->rating,
764 DIVE_TOTALWEIGHT, total_weight(dive),
772 if (model == GTK_TREE_MODEL(dive_list.treemodel))
773 othermodel = dive_list.listmodel;
775 othermodel = dive_list.treemodel;
776 if (othermodel != dive_list.model)
778 gtk_tree_model_foreach(GTK_TREE_MODEL(othermodel), set_one_dive, dive);
781 static gboolean set_one_dive(GtkTreeModel *model,
789 /* Get the dive number */
790 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
793 dive = get_dive(idx);
796 if (data && dive != data)
799 fill_one_dive(dive, model, iter);
803 void flush_divelist(struct dive *dive)
805 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
807 gtk_tree_model_foreach(model, set_one_dive, dive);
810 void set_divelist_font(const char *font)
812 PangoFontDescription *font_desc = pango_font_description_from_string(font);
813 gtk_widget_modify_font(dive_list.tree_view, font_desc);
814 pango_font_description_free(font_desc);
817 void update_dive_list_units(void)
820 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
822 (void) get_depth_units(0, NULL, &unit);
823 gtk_tree_view_column_set_title(dive_list.depth, unit);
825 (void) get_temp_units(0, &unit);
826 gtk_tree_view_column_set_title(dive_list.temperature, unit);
828 (void) get_weight_units(0, NULL, &unit);
829 gtk_tree_view_column_set_title(dive_list.totalweight, unit);
831 gtk_tree_model_foreach(model, set_one_dive, NULL);
834 void update_dive_list_col_visibility(void)
836 gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
837 gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
838 gtk_tree_view_column_set_visible(dive_list.totalweight, visible_cols.totalweight);
839 gtk_tree_view_column_set_visible(dive_list.suit, visible_cols.suit);
840 gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
841 gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
842 gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
846 /* random heuristic - not diving in three days implies new dive trip */
847 #define TRIP_THRESHOLD 3600*24*3
848 static int new_group(struct dive *dive, struct dive **last_dive, time_t *tm_date)
853 struct dive *ldive = *last_dive;
854 if (abs(dive->when - ldive->when) < TRIP_THRESHOLD) {
861 struct tm *tm1 = gmtime(&dive->when);
865 *tm_date = mktime(tm1);
870 static void fill_dive_list(void)
873 GtkTreeIter iter, parent_iter;
874 GtkTreeStore *liststore, *treestore;
875 struct dive *last_dive = NULL;
876 struct dive *last_trip_dive = NULL;
877 const char *last_location = NULL;
880 treestore = GTK_TREE_STORE(dive_list.treemodel);
881 liststore = GTK_TREE_STORE(dive_list.listmodel);
885 struct dive *dive = dive_table.dives[i];
887 if (new_group(dive, &last_dive, &dive_date))
889 /* make sure we display the first date of the trip in previous summary */
891 gtk_tree_store_set(treestore, &parent_iter,
893 DIVE_DATE, last_trip_dive->when,
894 DIVE_LOCATION, last_location,
897 gtk_tree_store_append(treestore, &parent_iter, NULL);
898 gtk_tree_store_set(treestore, &parent_iter,
899 DIVE_INDEX, -NEW_TRIP,
906 /* This might be NULL */
907 last_location = dive->location;
910 last_trip_dive = dive;
912 last_location = dive->location;
913 update_cylinder_related_info(dive);
914 gtk_tree_store_append(treestore, &iter, &parent_iter);
915 gtk_tree_store_set(treestore, &iter,
917 DIVE_NR, dive->number,
918 DIVE_DATE, dive->when,
919 DIVE_DEPTH, dive->maxdepth,
920 DIVE_DURATION, dive->duration.seconds,
921 DIVE_LOCATION, dive->location,
922 DIVE_RATING, dive->rating,
923 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
926 gtk_tree_store_append(liststore, &iter, NULL);
927 gtk_tree_store_set(liststore, &iter,
929 DIVE_NR, dive->number,
930 DIVE_DATE, dive->when,
931 DIVE_DEPTH, dive->maxdepth,
932 DIVE_DURATION, dive->duration.seconds,
933 DIVE_LOCATION, dive->location,
934 DIVE_RATING, dive->rating,
935 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
937 DIVE_SUIT, dive->suit,
942 /* make sure we display the first date of the trip in previous summary */
944 gtk_tree_store_set(treestore, &parent_iter,
946 DIVE_DATE, last_trip_dive->when,
947 DIVE_LOCATION, last_location,
950 update_dive_list_units();
951 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
952 GtkTreeSelection *selection;
954 /* select the last dive (and make sure it's an actual dive that is selected) */
955 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
956 first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive);
957 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
958 gtk_tree_selection_select_iter(selection, &iter);
962 void dive_list_update_dives(void)
964 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.treemodel));
965 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.listmodel));
970 static struct divelist_column {
977 [DIVE_NR] = { "#", nr_data_func, NULL, ALIGN_RIGHT | UNSORTABLE },
978 [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
979 [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
980 [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
981 [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
982 [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
983 [DIVE_TOTALWEIGHT] = { "lbs", weight_data_func, NULL, ALIGN_RIGHT, &visible_cols.totalweight },
984 [DIVE_SUIT] = { "Suit", NULL, NULL, ALIGN_LEFT, &visible_cols.suit },
985 [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
986 [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
987 [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
988 [DIVE_OTU] = { "OTU", otu_data_func, NULL, 0, &visible_cols.otu },
989 [DIVE_LOCATION] = { "Location", NULL, NULL, ALIGN_LEFT },
993 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col)
995 int index = col - &dl_column[0];
996 const char *title = col->header;
997 data_func_t data_func = col->data;
998 sort_func_t sort_func = col->sort;
999 unsigned int flags = col->flags;
1000 int *visible = col->visible;
1001 GtkWidget *tree_view = dl->tree_view;
1002 GtkTreeStore *treemodel = dl->treemodel;
1003 GtkTreeStore *listmodel = dl->listmodel;
1004 GtkTreeViewColumn *ret;
1006 if (visible && !*visible)
1008 ret = tree_view_column(tree_view, index, title, data_func, flags);
1010 /* the sort functions are needed in the corresponding models */
1011 if (index == DIVE_DATE)
1012 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treemodel), index, sort_func, NULL, NULL);
1014 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(listmodel), index, sort_func, NULL, NULL);
1020 * This is some crazy crap. The only way to get default focus seems
1021 * to be to grab focus as the widget is being shown the first time.
1023 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
1025 gtk_widget_grab_focus(tree_view);
1029 * Double-clicking on a group entry will expand a collapsed group
1032 static void collapse_expand(GtkTreeView *tree_view, GtkTreePath *path)
1034 if (!gtk_tree_view_row_expanded(tree_view, path))
1035 gtk_tree_view_expand_row(tree_view, path, FALSE);
1037 gtk_tree_view_collapse_row(tree_view, path);
1041 /* Double-click on a dive list */
1042 static void row_activated_cb(GtkTreeView *tree_view,
1044 GtkTreeViewColumn *column,
1050 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path))
1053 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &index, -1);
1054 /* a negative index is special for the "group by date" entries */
1056 collapse_expand(tree_view, path);
1059 edit_dive_info(get_dive(index));
1062 void add_dive_cb(GtkWidget *menuitem, gpointer data)
1066 dive = alloc_dive();
1067 if (add_new_dive(dive)) {
1075 void edit_dive_cb(GtkWidget *menuitem, gpointer data)
1077 edit_multi_dive_info(-1);
1080 static void expand_all_cb(GtkWidget *menuitem, GtkTreeView *tree_view)
1082 gtk_tree_view_expand_all(tree_view);
1085 static void collapse_all_cb(GtkWidget *menuitem, GtkTreeView *tree_view)
1087 gtk_tree_view_collapse_all(tree_view);
1090 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
1092 GtkWidget *menu, *menuitem, *image;
1093 char editlabel[] = "Edit dives";
1095 menu = gtk_menu_new();
1096 menuitem = gtk_image_menu_item_new_with_label("Add dive");
1097 image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
1098 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
1099 g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL);
1100 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1101 if (amount_selected) {
1102 if (amount_selected == 1)
1103 editlabel[strlen(editlabel) - 1] = '\0';
1104 menuitem = gtk_menu_item_new_with_label(editlabel);
1105 g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_cb), model);
1106 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1108 menuitem = gtk_menu_item_new_with_label("Expand all");
1109 g_signal_connect(menuitem, "activate", G_CALLBACK(expand_all_cb), tree_view);
1110 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1111 menuitem = gtk_menu_item_new_with_label("Collapse all");
1112 g_signal_connect(menuitem, "activate", G_CALLBACK(collapse_all_cb), tree_view);
1113 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1114 gtk_widget_show_all(menu);
1116 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
1117 button, gtk_get_current_event_time());
1120 static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata)
1122 popup_divelist_menu(tree_view, GTK_TREE_MODEL(dive_list.model), 0);
1125 static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
1127 /* Right-click? Bring up the menu */
1128 if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
1129 popup_divelist_menu(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(dive_list.model), 3);
1135 /* we need to have a temporary copy of the selected dives while
1136 switching model as the selection_cb function keeps getting called
1137 when gtk_tree_selection_select_path is called. We also need to
1138 keep copies of the sort order so we can restore that as well after
1139 switching models. */
1140 static gboolean second_call = FALSE;
1141 static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, };
1142 static int lastcol = DIVE_DATE;
1144 /* Check if this dive was selected previously and select it again in the new model;
1145 * This is used after we switch models to maintain consistent selections.
1146 * We always return FALSE to iterate through all dives */
1147 static gboolean set_selected(GtkTreeModel *model, GtkTreePath *path,
1148 GtkTreeIter *iter, gpointer data)
1150 GtkTreeSelection *selection = GTK_TREE_SELECTION(data);
1154 gtk_tree_model_get(model, iter,
1159 if (gtk_tree_model_iter_children(model, &child, iter))
1160 gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1);
1162 dive = get_dive(idx);
1163 selected = dive && dive->selected;
1165 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path);
1166 gtk_tree_selection_select_path(selection, path);
1172 static void update_column_and_order(int colid)
1174 /* Careful: the index into treecolumns is off by one as we don't have a
1175 tree_view column for DIVE_INDEX */
1176 GtkTreeViewColumn **treecolumns = &dive_list.nr;
1178 /* this will trigger a second call into sort_column_change_cb,
1179 so make sure we don't start an infinite recursion... */
1181 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, sortorder[colid]);
1182 gtk_tree_view_column_set_sort_order(treecolumns[colid - 1], sortorder[colid]);
1183 second_call = FALSE;
1186 /* If the sort column is date (default), show the tree model.
1187 For every other sort column only show the list model.
1188 If the model changed, inform the new model of the chosen sort column and make
1189 sure the same dives are still selected.
1191 The challenge with this function is that once we change the model
1192 we also need to change the sort column again (as it was changed in
1193 the other model) and that causes this function to be called
1194 recursively - so we need to catch that.
1196 static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data)
1200 GtkTreeStore *currentmodel = dive_list.model;
1205 gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order);
1206 if(colid == lastcol) {
1207 /* we just changed sort order */
1208 sortorder[colid] = order;
1213 if(colid == DIVE_DATE)
1214 dive_list.model = dive_list.treemodel;
1216 dive_list.model = dive_list.listmodel;
1217 if (dive_list.model != currentmodel) {
1218 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1220 gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model));
1221 update_column_and_order(colid);
1222 gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), set_selected, selection);
1224 if (order != sortorder[colid]) {
1225 update_column_and_order(colid);
1230 GtkWidget *dive_list_create(void)
1232 GtkTreeSelection *selection;
1234 dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1235 G_TYPE_INT, /* index */
1236 G_TYPE_INT, /* nr */
1237 G_TYPE_INT, /* Date */
1238 G_TYPE_INT, /* Star rating */
1239 G_TYPE_INT, /* Depth */
1240 G_TYPE_INT, /* Duration */
1241 G_TYPE_INT, /* Temperature */
1242 G_TYPE_INT, /* Total weight */
1243 G_TYPE_STRING, /* Suit */
1244 G_TYPE_STRING, /* Cylinder */
1245 G_TYPE_INT, /* Nitrox */
1246 G_TYPE_INT, /* SAC */
1247 G_TYPE_INT, /* OTU */
1248 G_TYPE_STRING /* Location */
1250 dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1251 G_TYPE_INT, /* index */
1252 G_TYPE_INT, /* nr */
1253 G_TYPE_INT, /* Date */
1254 G_TYPE_INT, /* Star rating */
1255 G_TYPE_INT, /* Depth */
1256 G_TYPE_INT, /* Duration */
1257 G_TYPE_INT, /* Temperature */
1258 G_TYPE_INT, /* Total weight */
1259 G_TYPE_STRING, /* Suit */
1260 G_TYPE_STRING, /* Cylinder */
1261 G_TYPE_INT, /* Nitrox */
1262 G_TYPE_INT, /* SAC */
1263 G_TYPE_INT, /* OTU */
1264 G_TYPE_STRING /* Location */
1266 dive_list.model = dive_list.treemodel;
1267 dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
1268 set_divelist_font(divelist_font);
1270 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1272 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
1273 gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
1275 dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR);
1276 dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE);
1277 dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING);
1278 dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH);
1279 dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION);
1280 dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE);
1281 dive_list.totalweight = divelist_column(&dive_list, dl_column + DIVE_TOTALWEIGHT);
1282 dive_list.suit = divelist_column(&dive_list, dl_column + DIVE_SUIT);
1283 dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER);
1284 dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX);
1285 dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC);
1286 dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU);
1287 dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION);
1291 g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
1292 "search-column", DIVE_LOCATION,
1296 g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
1297 g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL);
1298 g_signal_connect(dive_list.tree_view, "row-expanded", G_CALLBACK(row_expanded_cb), NULL);
1299 g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL);
1300 g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL);
1301 g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), dive_list.model);
1302 g_signal_connect(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1303 g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1305 gtk_tree_selection_set_select_function(selection, modify_selection_cb, NULL, NULL);
1307 dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
1308 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
1309 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1310 gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
1312 dive_list.changed = 0;
1314 return dive_list.container_widget;
1317 void mark_divelist_changed(int changed)
1319 dive_list.changed = changed;
1322 int unsaved_changes()
1324 return dive_list.changed;