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;
34 GList *dive_trip_list;
35 gboolean autogroup = FALSE;
37 const char *tripflag_names[NUM_TRIPFLAGS] = { "TF_NONE", "NOTRIP", "INTRIP" };
40 * The dive list has the dive data in both string format (for showing)
41 * and in "raw" format (for sorting purposes)
45 DIVE_NR, /* int: dive->nr */
46 DIVE_DATE, /* time_t: dive->when */
47 DIVE_RATING, /* int: 0-5 stars */
48 DIVE_DEPTH, /* int: dive->maxdepth in mm */
49 DIVE_DURATION, /* int: in seconds */
50 DIVE_TEMPERATURE, /* int: in mkelvin */
51 DIVE_TOTALWEIGHT, /* int: in grams */
52 DIVE_SUIT, /* "wet, 3mm" */
54 DIVE_NITROX, /* int: dummy */
55 DIVE_SAC, /* int: in ml/min */
56 DIVE_OTU, /* int: in OTUs */
57 DIVE_LOCATION, /* "2nd Cathedral, Lanai" */
62 static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path,
63 GtkTreeIter *iter, gpointer data)
66 int idx, nr, duration;
69 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, DIVE_DURATION, &duration, DIVE_LOCATION, &location, -1);
70 printf("entry #%d : nr %d duration %d location %s ", idx, nr, duration, location);
73 printf("tripflag %d\n", dive->tripflag);
75 printf("without matching dive\n");
82 static void dump_model(GtkListStore *store)
84 gtk_tree_model_foreach(GTK_TREE_MODEL(store), dump_model_entry, NULL);
88 #if DEBUG_SELECTION_TRACKING
89 void dump_selection(void)
94 printf("currently selected are %d dives:", amount_selected);
95 for_each_dive(i, dive) {
103 /* when subsurface starts we want to have the last dive selected. So we simply
104 walk to the first leaf (and skip the summary entries - which have negative
106 static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx)
111 while (*diveidx < 0) {
112 memcpy(&parent, iter, sizeof(parent));
113 tpath = gtk_tree_model_get_path(model, &parent);
114 if (!gtk_tree_model_iter_children(model, iter, &parent))
115 /* we should never have a parent without child */
117 if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath))
118 gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
119 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, DIVE_INDEX, diveidx, -1);
123 /* make sure that if we expand a summary row that is selected, the children show
124 up as selected, too */
125 void row_expanded_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data)
128 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
129 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
131 if (!gtk_tree_model_iter_children(model, &child, iter))
138 gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1);
139 dive = get_dive(idx);
142 gtk_tree_selection_select_iter(selection, &child);
144 gtk_tree_selection_unselect_iter(selection, &child);
145 } while (gtk_tree_model_iter_next(model, &child));
148 static int selected_children(GtkTreeModel *model, GtkTreeIter *iter)
152 if (!gtk_tree_model_iter_children(model, &child, iter))
159 gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1);
160 dive = get_dive(idx);
164 } while (gtk_tree_model_iter_next(model, &child));
168 /* Make sure that if we collapse a summary row with any selected children, the row
169 shows up as selected too */
170 void row_collapsed_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data)
172 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
173 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
175 if (selected_children(model, iter))
176 gtk_tree_selection_select_iter(selection, iter);
179 static GList *selection_changed = NULL;
182 * This is called _before_ the selection is changed, for every single entry;
184 * We simply create a list of all changed entries, and make sure that the
185 * group entries go at the end of the list.
187 gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model,
188 GtkTreePath *path, gboolean was_selected, gpointer userdata)
190 GtkTreeIter iter, *p;
192 if (!gtk_tree_model_get_iter(model, &iter, path))
195 /* Add the group entries to the end */
196 p = gtk_tree_iter_copy(&iter);
197 if (gtk_tree_model_iter_has_child(model, p))
198 selection_changed = g_list_append(selection_changed, p);
200 selection_changed = g_list_prepend(selection_changed, p);
204 static void select_dive(struct dive *dive, int selected)
206 if (dive->selected != selected) {
207 amount_selected += selected ? 1 : -1;
208 dive->selected = selected;
213 * This gets called when a dive group has changed selection.
215 static void select_dive_group(GtkTreeModel *model, GtkTreeSelection *selection, GtkTreeIter *iter, int selected)
220 if (selected == selected_children(model, iter))
223 if (!gtk_tree_model_iter_children(model, &child, iter))
230 gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1);
231 if (first && selected)
234 dive = get_dive(idx);
235 if (dive->selected == selected)
238 select_dive(dive, selected);
240 gtk_tree_selection_select_iter(selection, &child);
242 gtk_tree_selection_unselect_iter(selection, &child);
243 } while (gtk_tree_model_iter_next(model, &child));
247 * This gets called _after_ the selections have changed, for each entry that
248 * may have changed. Check if the gtk selection state matches our internal
249 * selection state to verify.
251 * The group entries are at the end, this guarantees that we have handled
252 * all the dives before we handle groups.
254 static void check_selection_cb(GtkTreeIter *iter, GtkTreeSelection *selection)
256 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
258 int idx, gtk_selected;
260 gtk_tree_model_get(model, iter,
263 dive = get_dive(idx);
264 gtk_selected = gtk_tree_selection_iter_is_selected(selection, iter);
266 select_dive_group(model, selection, iter, gtk_selected);
268 select_dive(dive, gtk_selected);
272 gtk_tree_iter_free(iter);
275 /* this is called when gtk thinks that the selection has changed */
276 static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model)
278 GList *changed = selection_changed;
280 selection_changed = NULL;
281 g_list_foreach(changed, (GFunc) check_selection_cb, selection);
282 g_list_free(changed);
283 #if DEBUG_SELECTION_TRACKING
287 process_selected_dives();
291 const char *star_strings[] = {
300 static void star_data_func(GtkTreeViewColumn *col,
301 GtkCellRenderer *renderer,
309 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_RATING, &nr_stars, -1);
313 if (nr_stars < 0 || nr_stars > 5)
315 snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]);
317 g_object_set(renderer, "text", buffer, NULL);
320 static void date_data_func(GtkTreeViewColumn *col,
321 GtkCellRenderer *renderer,
331 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &val, DIVE_NR, &nr, -1);
338 snprintf(buffer, sizeof(buffer),
339 "Trip %s, %s %d, %d (%d dive%s)",
340 weekday(tm->tm_wday),
341 monthname(tm->tm_mon),
342 tm->tm_mday, tm->tm_year + 1900,
343 nr, nr > 1 ? "s" : "");
345 snprintf(buffer, sizeof(buffer),
346 "%s, %s %d, %d %02d:%02d",
347 weekday(tm->tm_wday),
348 monthname(tm->tm_mon),
349 tm->tm_mday, tm->tm_year + 1900,
350 tm->tm_hour, tm->tm_min);
352 g_object_set(renderer, "text", buffer, NULL);
355 static void depth_data_func(GtkTreeViewColumn *col,
356 GtkCellRenderer *renderer,
361 int depth, integer, frac, len, idx;
364 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1);
369 switch (output_units.length) {
371 /* To tenths of meters */
372 depth = (depth + 49) / 100;
373 integer = depth / 10;
382 integer = mm_to_feet(depth) + 0.5;
388 len = snprintf(buffer, sizeof(buffer), "%d", integer);
390 len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
392 g_object_set(renderer, "text", buffer, NULL);
395 static void duration_data_func(GtkTreeViewColumn *col,
396 GtkCellRenderer *renderer,
405 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1);
409 snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
411 g_object_set(renderer, "text", buffer, NULL);
414 static void temperature_data_func(GtkTreeViewColumn *col,
415 GtkCellRenderer *renderer,
423 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1);
426 if (idx >= 0 && value) {
428 switch (output_units.temperature) {
430 deg = mkelvin_to_C(value);
433 deg = mkelvin_to_F(value);
438 snprintf(buffer, sizeof(buffer), "%.1f", deg);
441 g_object_set(renderer, "text", buffer, NULL);
444 static void nr_data_func(GtkTreeViewColumn *col,
445 GtkCellRenderer *renderer,
453 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1);
457 snprintf(buffer, sizeof(buffer), "%d", nr);
458 g_object_set(renderer, "text", buffer, NULL);
462 * Get "maximal" dive gas for a dive.
464 * - Trimix trumps nitrox (highest He wins, O2 breaks ties)
465 * - Nitrox trumps air (even if hypoxic)
466 * These are the same rules as the inter-dive sorting rules.
468 static void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
471 int maxo2 = -1, maxhe = -1, mino2 = 1000;
473 for (i = 0; i < MAX_CYLINDERS; i++) {
474 cylinder_t *cyl = dive->cylinder + i;
475 struct gasmix *mix = &cyl->gasmix;
476 int o2 = mix->o2.permille;
477 int he = mix->he.permille;
479 if (cylinder_none(cyl))
495 /* All air? Show/sort as "air"/zero */
496 if (!maxhe && maxo2 == AIR_PERMILLE && mino2 == maxo2)
503 static int total_weight(struct dive *dive)
505 int i, total_grams = 0;
508 for (i=0; i< MAX_WEIGHTSYSTEMS; i++)
509 total_grams += dive->weightsystem[i].weight.grams;
513 static void weight_data_func(GtkTreeViewColumn *col,
514 GtkCellRenderer *renderer,
524 gtk_tree_model_get(model, iter, DIVE_INDEX, &indx, -1);
525 dive = get_dive(indx);
526 value = get_weight_units(total_weight(dive), &decimals, NULL);
530 snprintf(buffer, sizeof(buffer), "%.*f", decimals, value);
532 g_object_set(renderer, "text", buffer, NULL);
535 static gint nitrox_sort_func(GtkTreeModel *model,
540 int index_a, index_b;
544 int a_o2low, b_o2low;
546 gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1);
547 gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1);
548 a = get_dive(index_a);
549 b = get_dive(index_b);
550 get_dive_gas(a, &a_o2, &a_he, &a_o2low);
551 get_dive_gas(b, &b_o2, &b_he, &b_o2low);
553 /* Sort by Helium first, O2 second */
556 return a_o2low - b_o2low;
562 #define UTF8_ELLIPSIS "\xE2\x80\xA6"
564 static void nitrox_data_func(GtkTreeViewColumn *col,
565 GtkCellRenderer *renderer,
570 int idx, o2, he, o2low;
574 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
579 dive = get_dive(idx);
580 get_dive_gas(dive, &o2, &he, &o2low);
583 o2low = (o2low + 5) / 10;
586 snprintf(buffer, sizeof(buffer), "%d/%d", o2, he);
589 snprintf(buffer, sizeof(buffer), "%d", o2);
591 snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
593 strcpy(buffer, "air");
595 g_object_set(renderer, "text", buffer, NULL);
598 /* Render the SAC data (integer value of "ml / min") */
599 static void sac_data_func(GtkTreeViewColumn *col,
600 GtkCellRenderer *renderer,
610 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1);
612 if (idx < 0 || !value) {
617 sac = value / 1000.0;
618 switch (output_units.volume) {
624 sac = ml_to_cuft(sac * 1000);
627 snprintf(buffer, sizeof(buffer), fmt, sac);
629 g_object_set(renderer, "text", buffer, NULL);
632 /* Render the OTU data (integer value of "OTU") */
633 static void otu_data_func(GtkTreeViewColumn *col,
634 GtkCellRenderer *renderer,
642 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1);
644 if (idx < 0 || !value)
647 snprintf(buffer, sizeof(buffer), "%d", value);
649 g_object_set(renderer, "text", buffer, NULL);
652 /* calculate OTU for a dive */
653 static int calculate_otu(struct dive *dive)
658 for (i = 1; i < dive->samples; i++) {
661 struct sample *sample = dive->sample + i;
662 struct sample *psample = sample - 1;
663 t = sample->time.seconds - psample->time.seconds;
664 int o2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille;
667 po2 = o2 / 1000.0 * (sample->depth.mm + 10000) / 10000.0;
669 otu += pow(po2 - 0.5, 0.83) * t / 30.0;
674 * Return air usage (in liters).
676 static double calculate_airuse(struct dive *dive)
681 for (i = 0; i < MAX_CYLINDERS; i++) {
682 pressure_t start, end;
683 cylinder_t *cyl = dive->cylinder + i;
684 int size = cyl->type.size.mliter;
690 start = cyl->start.mbar ? cyl->start : cyl->sample_start;
691 end = cyl->end.mbar ? cyl->end : cyl->sample_end;
692 kilo_atm = (to_ATM(start) - to_ATM(end)) / 1000.0;
694 /* Liters of air at 1 atm == milliliters at 1k atm*/
695 airuse += kilo_atm * size;
700 static int calculate_sac(struct dive *dive)
702 double airuse, pressure, sac;
705 airuse = calculate_airuse(dive);
708 if (!dive->duration.seconds)
711 /* find and eliminate long surface intervals */
712 duration = dive->duration.seconds;
713 for (i = 0; i < dive->samples; i++) {
714 if (dive->sample[i].depth.mm < 100) { /* less than 10cm */
716 while (end < dive->samples && dive->sample[end].depth.mm < 100)
718 /* we only want the actual surface time during a dive */
719 if (end < dive->samples) {
721 duration -= dive->sample[end].time.seconds -
722 dive->sample[i].time.seconds;
727 /* Mean pressure in atm: 1 atm per 10m */
728 pressure = 1 + (dive->meandepth.mm / 10000.0);
729 sac = airuse / pressure * 60 / duration;
731 /* milliliters per minute.. */
735 void update_cylinder_related_info(struct dive *dive)
738 dive->sac = calculate_sac(dive);
739 dive->otu = calculate_otu(dive);
743 static void get_string(char **str, const char *s)
759 static void get_location(struct dive *dive, char **str)
761 get_string(str, dive->location);
764 static void get_cylinder(struct dive *dive, char **str)
766 get_string(str, dive->cylinder[0].type.description);
769 static void get_suit(struct dive *dive, char **str)
771 get_string(str, dive->suit);
775 * Set up anything that could have changed due to editing
776 * of dive information; we need to do this for both models,
777 * so we simply call set_one_dive again with the non-current model
779 /* forward declaration for recursion */
780 static gboolean set_one_dive(GtkTreeModel *model,
785 static void fill_one_dive(struct dive *dive,
789 char *location, *cylinder, *suit;
790 GtkTreeStore *othermodel;
792 get_cylinder(dive, &cylinder);
793 get_location(dive, &location);
794 get_suit(dive, &suit);
796 gtk_tree_store_set(GTK_TREE_STORE(model), iter,
797 DIVE_NR, dive->number,
798 DIVE_LOCATION, location,
799 DIVE_CYLINDER, cylinder,
800 DIVE_RATING, dive->rating,
803 DIVE_TOTALWEIGHT, total_weight(dive),
811 if (model == GTK_TREE_MODEL(dive_list.treemodel))
812 othermodel = dive_list.listmodel;
814 othermodel = dive_list.treemodel;
815 if (othermodel != dive_list.model)
817 gtk_tree_model_foreach(GTK_TREE_MODEL(othermodel), set_one_dive, dive);
820 static gboolean set_one_dive(GtkTreeModel *model,
828 /* Get the dive number */
829 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
832 dive = get_dive(idx);
835 if (data && dive != data)
838 fill_one_dive(dive, model, iter);
842 void flush_divelist(struct dive *dive)
844 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
846 gtk_tree_model_foreach(model, set_one_dive, dive);
849 void set_divelist_font(const char *font)
851 PangoFontDescription *font_desc = pango_font_description_from_string(font);
852 gtk_widget_modify_font(dive_list.tree_view, font_desc);
853 pango_font_description_free(font_desc);
856 void update_dive_list_units(void)
859 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
861 (void) get_depth_units(0, NULL, &unit);
862 gtk_tree_view_column_set_title(dive_list.depth, unit);
864 (void) get_temp_units(0, &unit);
865 gtk_tree_view_column_set_title(dive_list.temperature, unit);
867 (void) get_weight_units(0, NULL, &unit);
868 gtk_tree_view_column_set_title(dive_list.totalweight, unit);
870 gtk_tree_model_foreach(model, set_one_dive, NULL);
873 void update_dive_list_col_visibility(void)
875 gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
876 gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
877 gtk_tree_view_column_set_visible(dive_list.totalweight, visible_cols.totalweight);
878 gtk_tree_view_column_set_visible(dive_list.suit, visible_cols.suit);
879 gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
880 gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
881 gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
885 static void fill_dive_list(void)
888 GtkTreeIter iter, parent_iter, *parent_ptr = NULL;
889 GtkTreeStore *liststore, *treestore;
890 struct dive *last_trip = NULL;
892 struct dive *dive_trip = NULL;
894 /* if we have pre-existing trips, start on the last one */
895 trip = g_list_last(dive_trip_list);
897 treestore = GTK_TREE_STORE(dive_list.treemodel);
898 liststore = GTK_TREE_STORE(dive_list.listmodel);
902 struct dive *dive = get_dive(i);
904 /* make sure we display the first date of the trip in previous summary */
905 if (dive_trip && parent_ptr) {
906 gtk_tree_store_set(treestore, parent_ptr,
907 DIVE_NR, dive_trip->number,
908 DIVE_DATE, dive_trip->when,
909 DIVE_LOCATION, dive_trip->location,
912 /* the dive_trip info might have been killed by a previous UNGROUPED dive */
914 dive_trip = DIVE_TRIP(trip);
915 /* tripflag defines how dives are handled;
916 * TF_NONE "not handled yet" - create time based group if autogroup == TRUE
917 * NO_TRIP "set as no group" - simply leave at top level
918 * IN_TRIP "use the trip with the largest trip time (when) that is <= this dive"
920 if (UNGROUPED_DIVE(dive)) {
921 /* first dives that go to the top level */
924 } else if (autogroup && !DIVE_IN_TRIP(dive)) {
925 if ( ! dive_trip || ! DIVE_FITS_TRIP(dive, dive_trip)) {
926 /* allocate new trip - all fields default to 0
927 and get filled in further down */
928 dive_trip = alloc_dive();
929 dive_trip_list = insert_trip(dive_trip, dive_trip_list);
930 trip = FIND_TRIP(dive_trip, dive_trip_list);
932 } else { /* either the dive has a trip or we aren't creating trips */
933 if (! (trip && DIVE_FITS_TRIP(dive, DIVE_TRIP(trip)))) {
934 GList *last_trip = trip;
935 trip = PREV_TRIP(trip, dive_trip_list);
936 if (! (trip && DIVE_FITS_TRIP(dive, DIVE_TRIP(trip)))) {
937 /* we could get here if there are no trips in the XML file
938 * and we aren't creating trips, either.
939 * Otherwise we need to create a new trip */
941 dive_trip = alloc_dive();
942 dive_trip_list = insert_trip(dive_trip, dive_trip_list);
943 trip = FIND_TRIP(dive_trip, dive_trip_list);
945 /* let's go back to the last valid trip */
949 dive_trip = trip->data;
950 dive_trip->number = 0;
954 /* update dive_trip to include this dive, increase number of dives in
955 the trip and update location if necessary */
957 dive->tripflag = IN_TRIP;
959 dive_trip->when = dive->when;
960 if (!dive_trip->location && dive->location)
961 dive_trip->location = dive->location;
962 if (dive_trip != last_trip) {
963 last_trip = dive_trip;
964 /* create trip entry */
965 gtk_tree_store_append(treestore, &parent_iter, NULL);
966 parent_ptr = &parent_iter;
967 /* a duration of 0 (and negative index) identifies a group */
968 gtk_tree_store_set(treestore, parent_ptr,
970 DIVE_NR, dive_trip->number,
971 DIVE_DATE, dive_trip->when,
972 DIVE_LOCATION, dive_trip->location,
979 update_cylinder_related_info(dive);
980 gtk_tree_store_append(treestore, &iter, parent_ptr);
981 gtk_tree_store_set(treestore, &iter,
983 DIVE_NR, dive->number,
984 DIVE_DATE, dive->when,
985 DIVE_DEPTH, dive->maxdepth,
986 DIVE_DURATION, dive->duration.seconds,
987 DIVE_LOCATION, dive->location,
988 DIVE_RATING, dive->rating,
989 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
992 gtk_tree_store_append(liststore, &iter, NULL);
993 gtk_tree_store_set(liststore, &iter,
995 DIVE_NR, dive->number,
996 DIVE_DATE, dive->when,
997 DIVE_DEPTH, dive->maxdepth,
998 DIVE_DURATION, dive->duration.seconds,
999 DIVE_LOCATION, dive->location,
1000 DIVE_RATING, dive->rating,
1001 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
1002 DIVE_TOTALWEIGHT, 0,
1003 DIVE_SUIT, dive->suit,
1008 /* make sure we display the first date of the trip in previous summary */
1009 if (parent_ptr && dive_trip)
1010 gtk_tree_store_set(treestore, parent_ptr,
1011 DIVE_NR, dive_trip->number,
1012 DIVE_DATE, dive_trip->when,
1013 DIVE_LOCATION, dive_trip->location,
1015 update_dive_list_units();
1016 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
1017 GtkTreeSelection *selection;
1019 /* select the last dive (and make sure it's an actual dive that is selected) */
1020 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
1021 first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive);
1022 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1023 gtk_tree_selection_select_iter(selection, &iter);
1027 void dive_list_update_dives(void)
1029 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.treemodel));
1030 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.listmodel));
1035 static struct divelist_column {
1042 [DIVE_NR] = { "#", nr_data_func, NULL, ALIGN_RIGHT | UNSORTABLE },
1043 [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
1044 [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
1045 [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
1046 [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
1047 [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
1048 [DIVE_TOTALWEIGHT] = { "lbs", weight_data_func, NULL, ALIGN_RIGHT, &visible_cols.totalweight },
1049 [DIVE_SUIT] = { "Suit", NULL, NULL, ALIGN_LEFT, &visible_cols.suit },
1050 [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
1051 [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
1052 [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
1053 [DIVE_OTU] = { "OTU", otu_data_func, NULL, 0, &visible_cols.otu },
1054 [DIVE_LOCATION] = { "Location", NULL, NULL, ALIGN_LEFT },
1058 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col)
1060 int index = col - &dl_column[0];
1061 const char *title = col->header;
1062 data_func_t data_func = col->data;
1063 sort_func_t sort_func = col->sort;
1064 unsigned int flags = col->flags;
1065 int *visible = col->visible;
1066 GtkWidget *tree_view = dl->tree_view;
1067 GtkTreeStore *treemodel = dl->treemodel;
1068 GtkTreeStore *listmodel = dl->listmodel;
1069 GtkTreeViewColumn *ret;
1071 if (visible && !*visible)
1073 ret = tree_view_column(tree_view, index, title, data_func, flags);
1075 /* the sort functions are needed in the corresponding models */
1076 if (index == DIVE_DATE)
1077 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treemodel), index, sort_func, NULL, NULL);
1079 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(listmodel), index, sort_func, NULL, NULL);
1085 * This is some crazy crap. The only way to get default focus seems
1086 * to be to grab focus as the widget is being shown the first time.
1088 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
1090 gtk_widget_grab_focus(tree_view);
1094 * Double-clicking on a group entry will expand a collapsed group
1097 static void collapse_expand(GtkTreeView *tree_view, GtkTreePath *path)
1099 if (!gtk_tree_view_row_expanded(tree_view, path))
1100 gtk_tree_view_expand_row(tree_view, path, FALSE);
1102 gtk_tree_view_collapse_row(tree_view, path);
1106 /* Double-click on a dive list */
1107 static void row_activated_cb(GtkTreeView *tree_view,
1109 GtkTreeViewColumn *column,
1115 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path))
1118 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &index, -1);
1119 /* a negative index is special for the "group by date" entries */
1121 collapse_expand(tree_view, path);
1124 edit_dive_info(get_dive(index));
1127 void add_dive_cb(GtkWidget *menuitem, gpointer data)
1131 dive = alloc_dive();
1132 if (add_new_dive(dive)) {
1140 void edit_dive_cb(GtkWidget *menuitem, gpointer data)
1142 edit_multi_dive_info(NULL);
1145 static void expand_all_cb(GtkWidget *menuitem, GtkTreeView *tree_view)
1147 gtk_tree_view_expand_all(tree_view);
1150 static void collapse_all_cb(GtkWidget *menuitem, GtkTreeView *tree_view)
1152 gtk_tree_view_collapse_all(tree_view);
1155 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
1157 GtkWidget *menu, *menuitem, *image;
1158 char editlabel[] = "Edit dives";
1160 menu = gtk_menu_new();
1161 menuitem = gtk_image_menu_item_new_with_label("Add dive");
1162 image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
1163 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
1164 g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL);
1165 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1166 if (amount_selected) {
1167 if (amount_selected == 1)
1168 editlabel[strlen(editlabel) - 1] = '\0';
1169 menuitem = gtk_menu_item_new_with_label(editlabel);
1170 g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_cb), model);
1171 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1173 menuitem = gtk_menu_item_new_with_label("Expand all");
1174 g_signal_connect(menuitem, "activate", G_CALLBACK(expand_all_cb), tree_view);
1175 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1176 menuitem = gtk_menu_item_new_with_label("Collapse all");
1177 g_signal_connect(menuitem, "activate", G_CALLBACK(collapse_all_cb), tree_view);
1178 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1179 gtk_widget_show_all(menu);
1181 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
1182 button, gtk_get_current_event_time());
1185 static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata)
1187 popup_divelist_menu(tree_view, GTK_TREE_MODEL(dive_list.model), 0);
1190 static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
1192 /* Right-click? Bring up the menu */
1193 if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
1194 popup_divelist_menu(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(dive_list.model), 3);
1200 /* we need to have a temporary copy of the selected dives while
1201 switching model as the selection_cb function keeps getting called
1202 when gtk_tree_selection_select_path is called. We also need to
1203 keep copies of the sort order so we can restore that as well after
1204 switching models. */
1205 static gboolean second_call = FALSE;
1206 static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, };
1207 static int lastcol = DIVE_DATE;
1209 /* Check if this dive was selected previously and select it again in the new model;
1210 * This is used after we switch models to maintain consistent selections.
1211 * We always return FALSE to iterate through all dives */
1212 static gboolean set_selected(GtkTreeModel *model, GtkTreePath *path,
1213 GtkTreeIter *iter, gpointer data)
1215 GtkTreeSelection *selection = GTK_TREE_SELECTION(data);
1219 gtk_tree_model_get(model, iter,
1224 if (gtk_tree_model_iter_children(model, &child, iter))
1225 gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1);
1227 dive = get_dive(idx);
1228 selected = dive && dive->selected;
1230 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path);
1231 gtk_tree_selection_select_path(selection, path);
1237 static void update_column_and_order(int colid)
1239 /* Careful: the index into treecolumns is off by one as we don't have a
1240 tree_view column for DIVE_INDEX */
1241 GtkTreeViewColumn **treecolumns = &dive_list.nr;
1243 /* this will trigger a second call into sort_column_change_cb,
1244 so make sure we don't start an infinite recursion... */
1246 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, sortorder[colid]);
1247 gtk_tree_view_column_set_sort_order(treecolumns[colid - 1], sortorder[colid]);
1248 second_call = FALSE;
1251 /* If the sort column is date (default), show the tree model.
1252 For every other sort column only show the list model.
1253 If the model changed, inform the new model of the chosen sort column and make
1254 sure the same dives are still selected.
1256 The challenge with this function is that once we change the model
1257 we also need to change the sort column again (as it was changed in
1258 the other model) and that causes this function to be called
1259 recursively - so we need to catch that.
1261 static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data)
1265 GtkTreeStore *currentmodel = dive_list.model;
1270 gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order);
1271 if(colid == lastcol) {
1272 /* we just changed sort order */
1273 sortorder[colid] = order;
1278 if(colid == DIVE_DATE)
1279 dive_list.model = dive_list.treemodel;
1281 dive_list.model = dive_list.listmodel;
1282 if (dive_list.model != currentmodel) {
1283 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1285 gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model));
1286 update_column_and_order(colid);
1287 gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), set_selected, selection);
1289 if (order != sortorder[colid]) {
1290 update_column_and_order(colid);
1295 GtkWidget *dive_list_create(void)
1297 GtkTreeSelection *selection;
1299 dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1300 G_TYPE_INT, /* index */
1301 G_TYPE_INT, /* nr */
1302 G_TYPE_INT, /* Date */
1303 G_TYPE_INT, /* Star rating */
1304 G_TYPE_INT, /* Depth */
1305 G_TYPE_INT, /* Duration */
1306 G_TYPE_INT, /* Temperature */
1307 G_TYPE_INT, /* Total weight */
1308 G_TYPE_STRING, /* Suit */
1309 G_TYPE_STRING, /* Cylinder */
1310 G_TYPE_INT, /* Nitrox */
1311 G_TYPE_INT, /* SAC */
1312 G_TYPE_INT, /* OTU */
1313 G_TYPE_STRING /* Location */
1315 dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1316 G_TYPE_INT, /* index */
1317 G_TYPE_INT, /* nr */
1318 G_TYPE_INT, /* Date */
1319 G_TYPE_INT, /* Star rating */
1320 G_TYPE_INT, /* Depth */
1321 G_TYPE_INT, /* Duration */
1322 G_TYPE_INT, /* Temperature */
1323 G_TYPE_INT, /* Total weight */
1324 G_TYPE_STRING, /* Suit */
1325 G_TYPE_STRING, /* Cylinder */
1326 G_TYPE_INT, /* Nitrox */
1327 G_TYPE_INT, /* SAC */
1328 G_TYPE_INT, /* OTU */
1329 G_TYPE_STRING /* Location */
1331 dive_list.model = dive_list.treemodel;
1332 dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
1333 set_divelist_font(divelist_font);
1335 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1337 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
1338 gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
1340 dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR);
1341 dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE);
1342 dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING);
1343 dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH);
1344 dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION);
1345 dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE);
1346 dive_list.totalweight = divelist_column(&dive_list, dl_column + DIVE_TOTALWEIGHT);
1347 dive_list.suit = divelist_column(&dive_list, dl_column + DIVE_SUIT);
1348 dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER);
1349 dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX);
1350 dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC);
1351 dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU);
1352 dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION);
1356 g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
1357 "search-column", DIVE_LOCATION,
1361 g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
1362 g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL);
1363 g_signal_connect(dive_list.tree_view, "row-expanded", G_CALLBACK(row_expanded_cb), NULL);
1364 g_signal_connect(dive_list.tree_view, "row-collapsed", G_CALLBACK(row_collapsed_cb), NULL);
1365 g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL);
1366 g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL);
1367 g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), dive_list.model);
1368 g_signal_connect(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1369 g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1371 gtk_tree_selection_set_select_function(selection, modify_selection_cb, NULL, NULL);
1373 dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
1374 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
1375 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1376 gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
1378 dive_list.changed = 0;
1380 return dive_list.container_widget;
1383 void mark_divelist_changed(int changed)
1385 dive_list.changed = changed;
1388 int unsaved_changes()
1390 return dive_list.changed;