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 (i = 0; (dive = get_dive(i)) != NULL; i++) {
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 dive_trip = DIVE_TRIP(trip);
899 treestore = GTK_TREE_STORE(dive_list.treemodel);
900 liststore = GTK_TREE_STORE(dive_list.listmodel);
904 struct dive *dive = get_dive(i);
906 /* make sure we display the first date of the trip in previous summary */
907 if (dive_trip && parent_ptr) {
908 gtk_tree_store_set(treestore, parent_ptr,
909 DIVE_NR, dive_trip->number,
910 DIVE_DATE, dive_trip->when,
911 DIVE_LOCATION, dive_trip->location,
914 /* tripflag defines how dives are handled;
915 * TF_NONE "not handled yet" - create time based group if autogroup == TRUE
916 * NO_TRIP "set as no group" - simply leave at top level
917 * IN_TRIP "use the trip with the largest trip time (when) that is <= this dive"
919 if (UNGROUPED_DIVE(dive)) {
920 /* first dives that go to the top level */
923 } else if (autogroup && !DIVE_IN_TRIP(dive)) {
924 if ( ! dive_trip || ! DIVE_FITS_TRIP(dive, dive_trip)) {
925 /* allocate new trip - all fields default to 0
926 and get filled in further down */
927 dive_trip = alloc_dive();
928 dive_trip_list = INSERT_TRIP(dive_trip, dive_trip_list);
929 trip = FIND_TRIP(dive_trip, dive_trip_list);
931 } else { /* either the dive has a trip or we aren't creating trips */
932 if (! (trip && DIVE_FITS_TRIP(dive, DIVE_TRIP(trip)))) {
933 GList *last_trip = trip;
934 trip = PREV_TRIP(trip, dive_trip_list);
935 if (! (trip && DIVE_FITS_TRIP(dive, DIVE_TRIP(trip)))) {
936 /* we could get here if there are no trips in the XML file
937 * and we aren't creating trips, either.
938 * Otherwise we need to create a new trip */
940 dive_trip = alloc_dive();
941 dive_trip_list = INSERT_TRIP(dive_trip, dive_trip_list);
942 trip = FIND_TRIP(dive_trip, dive_trip_list);
944 /* let's go back to the last valid trip */
948 dive_trip = trip->data;
949 dive_trip->number = 0;
953 /* update dive_trip to include this dive, increase number of dives in
954 the trip and update location if necessary */
956 dive->tripflag = IN_TRIP;
958 dive_trip->when = dive->when;
959 if (!dive_trip->location && dive->location)
960 dive_trip->location = dive->location;
961 if (dive_trip != last_trip) {
962 last_trip = dive_trip;
963 /* create trip entry */
964 gtk_tree_store_append(treestore, &parent_iter, NULL);
965 parent_ptr = &parent_iter;
966 /* a duration of 0 (and negative index) identifies a group */
967 gtk_tree_store_set(treestore, parent_ptr,
969 DIVE_NR, dive_trip->number,
970 DIVE_DATE, dive_trip->when,
971 DIVE_LOCATION, dive_trip->location,
978 update_cylinder_related_info(dive);
979 gtk_tree_store_append(treestore, &iter, parent_ptr);
980 gtk_tree_store_set(treestore, &iter,
982 DIVE_NR, dive->number,
983 DIVE_DATE, dive->when,
984 DIVE_DEPTH, dive->maxdepth,
985 DIVE_DURATION, dive->duration.seconds,
986 DIVE_LOCATION, dive->location,
987 DIVE_RATING, dive->rating,
988 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
991 gtk_tree_store_append(liststore, &iter, NULL);
992 gtk_tree_store_set(liststore, &iter,
994 DIVE_NR, dive->number,
995 DIVE_DATE, dive->when,
996 DIVE_DEPTH, dive->maxdepth,
997 DIVE_DURATION, dive->duration.seconds,
998 DIVE_LOCATION, dive->location,
999 DIVE_RATING, dive->rating,
1000 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
1001 DIVE_TOTALWEIGHT, 0,
1002 DIVE_SUIT, dive->suit,
1007 /* make sure we display the first date of the trip in previous summary */
1008 if (parent_ptr && dive_trip)
1009 gtk_tree_store_set(treestore, parent_ptr,
1010 DIVE_NR, dive_trip->number,
1011 DIVE_DATE, dive_trip->when,
1012 DIVE_LOCATION, dive_trip->location,
1014 update_dive_list_units();
1015 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
1016 GtkTreeSelection *selection;
1018 /* select the last dive (and make sure it's an actual dive that is selected) */
1019 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
1020 first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive);
1021 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1022 gtk_tree_selection_select_iter(selection, &iter);
1026 void dive_list_update_dives(void)
1028 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.treemodel));
1029 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.listmodel));
1034 static struct divelist_column {
1041 [DIVE_NR] = { "#", nr_data_func, NULL, ALIGN_RIGHT | UNSORTABLE },
1042 [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
1043 [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
1044 [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
1045 [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
1046 [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
1047 [DIVE_TOTALWEIGHT] = { "lbs", weight_data_func, NULL, ALIGN_RIGHT, &visible_cols.totalweight },
1048 [DIVE_SUIT] = { "Suit", NULL, NULL, ALIGN_LEFT, &visible_cols.suit },
1049 [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
1050 [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
1051 [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
1052 [DIVE_OTU] = { "OTU", otu_data_func, NULL, 0, &visible_cols.otu },
1053 [DIVE_LOCATION] = { "Location", NULL, NULL, ALIGN_LEFT },
1057 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col)
1059 int index = col - &dl_column[0];
1060 const char *title = col->header;
1061 data_func_t data_func = col->data;
1062 sort_func_t sort_func = col->sort;
1063 unsigned int flags = col->flags;
1064 int *visible = col->visible;
1065 GtkWidget *tree_view = dl->tree_view;
1066 GtkTreeStore *treemodel = dl->treemodel;
1067 GtkTreeStore *listmodel = dl->listmodel;
1068 GtkTreeViewColumn *ret;
1070 if (visible && !*visible)
1072 ret = tree_view_column(tree_view, index, title, data_func, flags);
1074 /* the sort functions are needed in the corresponding models */
1075 if (index == DIVE_DATE)
1076 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treemodel), index, sort_func, NULL, NULL);
1078 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(listmodel), index, sort_func, NULL, NULL);
1084 * This is some crazy crap. The only way to get default focus seems
1085 * to be to grab focus as the widget is being shown the first time.
1087 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
1089 gtk_widget_grab_focus(tree_view);
1093 * Double-clicking on a group entry will expand a collapsed group
1096 static void collapse_expand(GtkTreeView *tree_view, GtkTreePath *path)
1098 if (!gtk_tree_view_row_expanded(tree_view, path))
1099 gtk_tree_view_expand_row(tree_view, path, FALSE);
1101 gtk_tree_view_collapse_row(tree_view, path);
1105 /* Double-click on a dive list */
1106 static void row_activated_cb(GtkTreeView *tree_view,
1108 GtkTreeViewColumn *column,
1114 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path))
1117 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &index, -1);
1118 /* a negative index is special for the "group by date" entries */
1120 collapse_expand(tree_view, path);
1123 edit_dive_info(get_dive(index));
1126 void add_dive_cb(GtkWidget *menuitem, gpointer data)
1130 dive = alloc_dive();
1131 if (add_new_dive(dive)) {
1139 void edit_dive_cb(GtkWidget *menuitem, gpointer data)
1141 edit_multi_dive_info(-1);
1144 static void expand_all_cb(GtkWidget *menuitem, GtkTreeView *tree_view)
1146 gtk_tree_view_expand_all(tree_view);
1149 static void collapse_all_cb(GtkWidget *menuitem, GtkTreeView *tree_view)
1151 gtk_tree_view_collapse_all(tree_view);
1154 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
1156 GtkWidget *menu, *menuitem, *image;
1157 char editlabel[] = "Edit dives";
1159 menu = gtk_menu_new();
1160 menuitem = gtk_image_menu_item_new_with_label("Add dive");
1161 image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
1162 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
1163 g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL);
1164 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1165 if (amount_selected) {
1166 if (amount_selected == 1)
1167 editlabel[strlen(editlabel) - 1] = '\0';
1168 menuitem = gtk_menu_item_new_with_label(editlabel);
1169 g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_cb), model);
1170 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1172 menuitem = gtk_menu_item_new_with_label("Expand all");
1173 g_signal_connect(menuitem, "activate", G_CALLBACK(expand_all_cb), tree_view);
1174 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1175 menuitem = gtk_menu_item_new_with_label("Collapse all");
1176 g_signal_connect(menuitem, "activate", G_CALLBACK(collapse_all_cb), tree_view);
1177 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1178 gtk_widget_show_all(menu);
1180 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
1181 button, gtk_get_current_event_time());
1184 static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata)
1186 popup_divelist_menu(tree_view, GTK_TREE_MODEL(dive_list.model), 0);
1189 static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
1191 /* Right-click? Bring up the menu */
1192 if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
1193 popup_divelist_menu(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(dive_list.model), 3);
1199 /* we need to have a temporary copy of the selected dives while
1200 switching model as the selection_cb function keeps getting called
1201 when gtk_tree_selection_select_path is called. We also need to
1202 keep copies of the sort order so we can restore that as well after
1203 switching models. */
1204 static gboolean second_call = FALSE;
1205 static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, };
1206 static int lastcol = DIVE_DATE;
1208 /* Check if this dive was selected previously and select it again in the new model;
1209 * This is used after we switch models to maintain consistent selections.
1210 * We always return FALSE to iterate through all dives */
1211 static gboolean set_selected(GtkTreeModel *model, GtkTreePath *path,
1212 GtkTreeIter *iter, gpointer data)
1214 GtkTreeSelection *selection = GTK_TREE_SELECTION(data);
1218 gtk_tree_model_get(model, iter,
1223 if (gtk_tree_model_iter_children(model, &child, iter))
1224 gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1);
1226 dive = get_dive(idx);
1227 selected = dive && dive->selected;
1229 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path);
1230 gtk_tree_selection_select_path(selection, path);
1236 static void update_column_and_order(int colid)
1238 /* Careful: the index into treecolumns is off by one as we don't have a
1239 tree_view column for DIVE_INDEX */
1240 GtkTreeViewColumn **treecolumns = &dive_list.nr;
1242 /* this will trigger a second call into sort_column_change_cb,
1243 so make sure we don't start an infinite recursion... */
1245 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, sortorder[colid]);
1246 gtk_tree_view_column_set_sort_order(treecolumns[colid - 1], sortorder[colid]);
1247 second_call = FALSE;
1250 /* If the sort column is date (default), show the tree model.
1251 For every other sort column only show the list model.
1252 If the model changed, inform the new model of the chosen sort column and make
1253 sure the same dives are still selected.
1255 The challenge with this function is that once we change the model
1256 we also need to change the sort column again (as it was changed in
1257 the other model) and that causes this function to be called
1258 recursively - so we need to catch that.
1260 static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data)
1264 GtkTreeStore *currentmodel = dive_list.model;
1269 gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order);
1270 if(colid == lastcol) {
1271 /* we just changed sort order */
1272 sortorder[colid] = order;
1277 if(colid == DIVE_DATE)
1278 dive_list.model = dive_list.treemodel;
1280 dive_list.model = dive_list.listmodel;
1281 if (dive_list.model != currentmodel) {
1282 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1284 gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model));
1285 update_column_and_order(colid);
1286 gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), set_selected, selection);
1288 if (order != sortorder[colid]) {
1289 update_column_and_order(colid);
1294 GtkWidget *dive_list_create(void)
1296 GtkTreeSelection *selection;
1298 dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1299 G_TYPE_INT, /* index */
1300 G_TYPE_INT, /* nr */
1301 G_TYPE_INT, /* Date */
1302 G_TYPE_INT, /* Star rating */
1303 G_TYPE_INT, /* Depth */
1304 G_TYPE_INT, /* Duration */
1305 G_TYPE_INT, /* Temperature */
1306 G_TYPE_INT, /* Total weight */
1307 G_TYPE_STRING, /* Suit */
1308 G_TYPE_STRING, /* Cylinder */
1309 G_TYPE_INT, /* Nitrox */
1310 G_TYPE_INT, /* SAC */
1311 G_TYPE_INT, /* OTU */
1312 G_TYPE_STRING /* Location */
1314 dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1315 G_TYPE_INT, /* index */
1316 G_TYPE_INT, /* nr */
1317 G_TYPE_INT, /* Date */
1318 G_TYPE_INT, /* Star rating */
1319 G_TYPE_INT, /* Depth */
1320 G_TYPE_INT, /* Duration */
1321 G_TYPE_INT, /* Temperature */
1322 G_TYPE_INT, /* Total weight */
1323 G_TYPE_STRING, /* Suit */
1324 G_TYPE_STRING, /* Cylinder */
1325 G_TYPE_INT, /* Nitrox */
1326 G_TYPE_INT, /* SAC */
1327 G_TYPE_INT, /* OTU */
1328 G_TYPE_STRING /* Location */
1330 dive_list.model = dive_list.treemodel;
1331 dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
1332 set_divelist_font(divelist_font);
1334 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1336 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
1337 gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
1339 dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR);
1340 dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE);
1341 dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING);
1342 dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH);
1343 dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION);
1344 dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE);
1345 dive_list.totalweight = divelist_column(&dive_list, dl_column + DIVE_TOTALWEIGHT);
1346 dive_list.suit = divelist_column(&dive_list, dl_column + DIVE_SUIT);
1347 dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER);
1348 dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX);
1349 dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC);
1350 dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU);
1351 dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION);
1355 g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
1356 "search-column", DIVE_LOCATION,
1360 g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
1361 g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL);
1362 g_signal_connect(dive_list.tree_view, "row-expanded", G_CALLBACK(row_expanded_cb), NULL);
1363 g_signal_connect(dive_list.tree_view, "row-collapsed", G_CALLBACK(row_collapsed_cb), NULL);
1364 g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL);
1365 g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL);
1366 g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), dive_list.model);
1367 g_signal_connect(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1368 g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1370 gtk_tree_selection_set_select_function(selection, modify_selection_cb, NULL, NULL);
1372 dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
1373 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
1374 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1375 gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
1377 dive_list.changed = 0;
1379 return dive_list.container_widget;
1382 void mark_divelist_changed(int changed)
1384 dive_list.changed = changed;
1387 int unsaved_changes()
1389 return dive_list.changed;