2 /* this creates the UI for the dive list -
3 * controlled through the following interfaces:
5 * void flush_divelist(struct dive *dive)
6 * GtkWidget dive_list_create(void)
7 * void dive_list_update_dives(void)
8 * void update_dive_list_units(void)
9 * void set_divelist_font(const char *font)
10 * void mark_divelist_changed(int changed)
11 * int unsaved_changes()
22 #include "display-gtk.h"
26 GtkWidget *container_widget;
27 GtkTreeStore *model, *listmodel, *treemodel;
28 GtkTreeViewColumn *nr, *date, *stars, *depth, *duration, *location;
29 GtkTreeViewColumn *temperature, *cylinder, *totalweight, *suit, *nitrox, *sac, *otu;
33 static struct DiveList dive_list;
36 * The dive list has the dive data in both string format (for showing)
37 * and in "raw" format (for sorting purposes)
41 DIVE_NR, /* int: dive->nr */
42 DIVE_DATE, /* time_t: dive->when */
43 DIVE_RATING, /* int: 0-5 stars */
44 DIVE_DEPTH, /* int: dive->maxdepth in mm */
45 DIVE_DURATION, /* int: in seconds */
46 DIVE_TEMPERATURE, /* int: in mkelvin */
47 DIVE_TOTALWEIGHT, /* int: in grams */
48 DIVE_SUIT, /* "wet, 3mm" */
50 DIVE_NITROX, /* int: dummy */
51 DIVE_SAC, /* int: in ml/min */
52 DIVE_OTU, /* int: in OTUs */
53 DIVE_LOCATION, /* "2nd Cathedral, Lanai" */
57 /* magic numbers that indicate (as negative values) model entries that
58 * are summary entries for a divetrip */
62 static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path,
63 GtkTreeIter *iter, gpointer data)
66 int idx, nr, rating, depth;
68 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, DIVE_RATING, &rating, DIVE_DEPTH, &depth, DIVE_LOCATION, &location, -1);
69 printf("entry #%d : nr %d rating %d depth %d location %s \n", idx, nr, rating, depth, location);
75 static void dump_model(GtkListStore *store)
77 gtk_tree_model_foreach(GTK_TREE_MODEL(store), dump_model_entry, NULL);
81 static GList *selected_dives;
82 static int *selectiontracker;
84 /* when subsurface starts we want to have the last dive selected. So we simply
85 walk to the first leaf (and skip the summary entries - which have negative
87 static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx)
92 while (*diveidx < 0) {
93 memcpy(&parent, iter, sizeof(parent));
94 tpath = gtk_tree_model_get_path(model, &parent);
95 if (!gtk_tree_model_iter_children(model, iter, &parent))
96 /* we should never have a parent without child */
98 if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath))
99 gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
100 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, DIVE_INDEX, diveidx, -1);
104 /* if we click on a summary dive, we actually want to select / unselect
105 all the dives "below" it */
106 static void select_children(GtkTreeModel *model, GtkTreeSelection * selection,
107 GtkTreeIter *iter, gboolean was_selected)
113 memcpy(&parent, iter, sizeof(parent));
115 tpath = gtk_tree_model_get_path(model, &parent);
116 if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath))
117 gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
119 nr_children = gtk_tree_model_iter_n_children(model, &parent);
120 for (i = 0; i < nr_children; i++) {
121 gtk_tree_model_iter_nth_child(model, iter, &parent, i);
123 gtk_tree_selection_unselect_iter(selection, iter);
125 gtk_tree_selection_select_iter(selection, iter);
129 /* this is called _before_ the selection is changed, for every single entry;
130 * we simply have it call down the tree to make sure that summary items toggle
132 gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model,
133 GtkTreePath *path, gboolean was_selected, gpointer userdata)
138 if (gtk_tree_model_get_iter(model, &iter, path)) {
139 gtk_tree_model_get(model, &iter, DIVE_INDEX, &dive_idx, -1);
141 select_children(model, selection, &iter, was_selected);
144 /* allow this selection to proceed */
148 /* this is called when gtk thinks that the selection has changed */
149 static void selection_cb(GtkTreeSelection *selection, gpointer userdata)
154 int nr_selected = gtk_tree_selection_count_selected_rows(selection);
156 if (selected_dives) {
157 g_list_foreach (selected_dives, (GFunc) gtk_tree_path_free, NULL);
158 g_list_free (selected_dives);
160 selected_dives = gtk_tree_selection_get_selected_rows(selection, NULL);
161 selectiontracker = realloc(selectiontracker, nr_selected * sizeof(int));
163 switch (nr_selected) {
164 case 0: /* there is no clear way to figure out which dive to show */
169 /* just pick that dive as selected */
171 path = g_list_nth_data(selected_dives, 0);
172 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path)) {
173 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
174 /* due to the way this callback gets invoked it is possible that
175 in the process of unselecting a summary dive we get here with
176 just one summary dive selected - ignore that case */
177 if (selected_dive < 0) {
181 selectiontracker[0] = selected_dive;
185 default: /* multiple selections - what now?
186 * We don't change the selected dive unless there is exactly one dive selected; not sure this
187 * is the most intuitive solution.
188 * The dives that have been selected are processed */
189 amount_selected = g_list_length(selected_dives);
190 process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model));
196 const char *star_strings[] = {
205 static void star_data_func(GtkTreeViewColumn *col,
206 GtkCellRenderer *renderer,
214 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_RATING, &nr_stars, -1);
218 if (nr_stars < 0 || nr_stars > 5)
220 snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]);
222 g_object_set(renderer, "text", buffer, NULL);
225 static void date_data_func(GtkTreeViewColumn *col,
226 GtkCellRenderer *renderer,
236 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &val, -1);
244 snprintf(buffer, sizeof(buffer),
245 "Trip %s, %s %d, %d",
246 weekday(tm->tm_wday),
247 monthname(tm->tm_mon),
248 tm->tm_mday, tm->tm_year + 1900);
251 snprintf(buffer, sizeof(buffer),
252 "%s, %s %d, %d %02d:%02d",
253 weekday(tm->tm_wday),
254 monthname(tm->tm_mon),
255 tm->tm_mday, tm->tm_year + 1900,
256 tm->tm_hour, tm->tm_min);
258 g_object_set(renderer, "text", buffer, NULL);
261 static void depth_data_func(GtkTreeViewColumn *col,
262 GtkCellRenderer *renderer,
267 int depth, integer, frac, len, idx;
270 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1);
275 switch (output_units.length) {
277 /* To tenths of meters */
278 depth = (depth + 49) / 100;
279 integer = depth / 10;
288 integer = mm_to_feet(depth) + 0.5;
294 len = snprintf(buffer, sizeof(buffer), "%d", integer);
296 len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
298 g_object_set(renderer, "text", buffer, NULL);
301 static void duration_data_func(GtkTreeViewColumn *col,
302 GtkCellRenderer *renderer,
311 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1);
315 snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
317 g_object_set(renderer, "text", buffer, NULL);
320 static void temperature_data_func(GtkTreeViewColumn *col,
321 GtkCellRenderer *renderer,
329 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1);
332 if (idx >= 0 && value) {
334 switch (output_units.temperature) {
336 deg = mkelvin_to_C(value);
339 deg = mkelvin_to_F(value);
344 snprintf(buffer, sizeof(buffer), "%.1f", deg);
347 g_object_set(renderer, "text", buffer, NULL);
350 static void nr_data_func(GtkTreeViewColumn *col,
351 GtkCellRenderer *renderer,
359 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1);
363 snprintf(buffer, sizeof(buffer), "%d", nr);
364 g_object_set(renderer, "text", buffer, NULL);
368 * Get "maximal" dive gas for a dive.
370 * - Trimix trumps nitrox (highest He wins, O2 breaks ties)
371 * - Nitrox trumps air (even if hypoxic)
372 * These are the same rules as the inter-dive sorting rules.
374 static void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
377 int maxo2 = -1, maxhe = -1, mino2 = 1000;
379 for (i = 0; i < MAX_CYLINDERS; i++) {
380 cylinder_t *cyl = dive->cylinder + i;
381 struct gasmix *mix = &cyl->gasmix;
382 int o2 = mix->o2.permille;
383 int he = mix->he.permille;
385 if (cylinder_none(cyl))
401 /* All air? Show/sort as "air"/zero */
402 if (!maxhe && maxo2 == AIR_PERMILLE && mino2 == maxo2)
409 static int total_weight(struct dive *dive)
411 int i, total_grams = 0;
414 for (i=0; i< MAX_WEIGHTSYSTEMS; i++)
415 total_grams += dive->weightsystem[i].weight.grams;
419 static void weight_data_func(GtkTreeViewColumn *col,
420 GtkCellRenderer *renderer,
430 gtk_tree_model_get(model, iter, DIVE_INDEX, &indx, -1);
431 dive = get_dive(indx);
432 value = get_weight_units(total_weight(dive), &decimals, NULL);
436 snprintf(buffer, sizeof(buffer), "%.*f", decimals, value);
438 g_object_set(renderer, "text", buffer, NULL);
441 static gint nitrox_sort_func(GtkTreeModel *model,
446 int index_a, index_b;
450 int a_o2low, b_o2low;
452 gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1);
453 gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1);
454 a = get_dive(index_a);
455 b = get_dive(index_b);
456 get_dive_gas(a, &a_o2, &a_he, &a_o2low);
457 get_dive_gas(b, &b_o2, &b_he, &b_o2low);
459 /* Sort by Helium first, O2 second */
462 return a_o2low - b_o2low;
468 #define UTF8_ELLIPSIS "\xE2\x80\xA6"
470 static void nitrox_data_func(GtkTreeViewColumn *col,
471 GtkCellRenderer *renderer,
476 int idx, o2, he, o2low;
480 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
485 dive = get_dive(idx);
486 get_dive_gas(dive, &o2, &he, &o2low);
489 o2low = (o2low + 5) / 10;
492 snprintf(buffer, sizeof(buffer), "%d/%d", o2, he);
495 snprintf(buffer, sizeof(buffer), "%d", o2);
497 snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
499 strcpy(buffer, "air");
501 g_object_set(renderer, "text", buffer, NULL);
504 /* Render the SAC data (integer value of "ml / min") */
505 static void sac_data_func(GtkTreeViewColumn *col,
506 GtkCellRenderer *renderer,
516 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1);
518 if (idx < 0 || !value) {
523 sac = value / 1000.0;
524 switch (output_units.volume) {
530 sac = ml_to_cuft(sac * 1000);
533 snprintf(buffer, sizeof(buffer), fmt, sac);
535 g_object_set(renderer, "text", buffer, NULL);
538 /* Render the OTU data (integer value of "OTU") */
539 static void otu_data_func(GtkTreeViewColumn *col,
540 GtkCellRenderer *renderer,
548 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1);
550 if (idx < 0 || !value)
553 snprintf(buffer, sizeof(buffer), "%d", value);
555 g_object_set(renderer, "text", buffer, NULL);
558 /* calculate OTU for a dive */
559 static int calculate_otu(struct dive *dive)
564 for (i = 1; i < dive->samples; i++) {
567 struct sample *sample = dive->sample + i;
568 struct sample *psample = sample - 1;
569 t = sample->time.seconds - psample->time.seconds;
570 int o2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille;
573 po2 = o2 / 1000.0 * (sample->depth.mm + 10000) / 10000.0;
575 otu += pow(po2 - 0.5, 0.83) * t / 30.0;
580 * Return air usage (in liters).
582 static double calculate_airuse(struct dive *dive)
587 for (i = 0; i < MAX_CYLINDERS; i++) {
588 pressure_t start, end;
589 cylinder_t *cyl = dive->cylinder + i;
590 int size = cyl->type.size.mliter;
596 start = cyl->start.mbar ? cyl->start : cyl->sample_start;
597 end = cyl->end.mbar ? cyl->end : cyl->sample_end;
598 kilo_atm = (to_ATM(start) - to_ATM(end)) / 1000.0;
600 /* Liters of air at 1 atm == milliliters at 1k atm*/
601 airuse += kilo_atm * size;
606 static int calculate_sac(struct dive *dive)
608 double airuse, pressure, sac;
611 airuse = calculate_airuse(dive);
614 if (!dive->duration.seconds)
617 /* find and eliminate long surface intervals */
618 duration = dive->duration.seconds;
619 for (i = 0; i < dive->samples; i++) {
620 if (dive->sample[i].depth.mm < 100) { /* less than 10cm */
622 while (end < dive->samples && dive->sample[end].depth.mm < 100)
624 /* we only want the actual surface time during a dive */
625 if (end < dive->samples) {
627 duration -= dive->sample[end].time.seconds -
628 dive->sample[i].time.seconds;
633 /* Mean pressure in atm: 1 atm per 10m */
634 pressure = 1 + (dive->meandepth.mm / 10000.0);
635 sac = airuse / pressure * 60 / duration;
637 /* milliliters per minute.. */
641 void update_cylinder_related_info(struct dive *dive)
644 dive->sac = calculate_sac(dive);
645 dive->otu = calculate_otu(dive);
649 static void get_string(char **str, const char *s)
665 static void get_location(struct dive *dive, char **str)
667 get_string(str, dive->location);
670 static void get_cylinder(struct dive *dive, char **str)
672 get_string(str, dive->cylinder[0].type.description);
675 static void get_suit(struct dive *dive, char **str)
677 get_string(str, dive->suit);
681 * Set up anything that could have changed due to editing
682 * of dive information; we need to do this for both models,
683 * so we simply call set_one_dive again with the non-current model
685 /* forward declaration for recursion */
686 static gboolean set_one_dive(GtkTreeModel *model,
691 static void fill_one_dive(struct dive *dive,
695 char *location, *cylinder, *suit;
696 GtkTreeStore *othermodel;
698 get_cylinder(dive, &cylinder);
699 get_location(dive, &location);
700 get_suit(dive, &suit);
702 gtk_tree_store_set(GTK_TREE_STORE(model), iter,
703 DIVE_NR, dive->number,
704 DIVE_LOCATION, location,
705 DIVE_CYLINDER, cylinder,
706 DIVE_RATING, dive->rating,
709 DIVE_TOTALWEIGHT, total_weight(dive),
717 if (model == GTK_TREE_MODEL(dive_list.treemodel))
718 othermodel = dive_list.listmodel;
720 othermodel = dive_list.treemodel;
721 if (othermodel != dive_list.model)
723 gtk_tree_model_foreach(GTK_TREE_MODEL(othermodel), set_one_dive, dive);
726 static gboolean set_one_dive(GtkTreeModel *model,
734 /* Get the dive number */
735 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
738 dive = get_dive(idx);
741 if (data && dive != data)
744 fill_one_dive(dive, model, iter);
748 void flush_divelist(struct dive *dive)
750 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
752 gtk_tree_model_foreach(model, set_one_dive, dive);
755 void set_divelist_font(const char *font)
757 PangoFontDescription *font_desc = pango_font_description_from_string(font);
758 gtk_widget_modify_font(dive_list.tree_view, font_desc);
759 pango_font_description_free(font_desc);
762 void update_dive_list_units(void)
765 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
767 (void) get_depth_units(0, NULL, &unit);
768 gtk_tree_view_column_set_title(dive_list.depth, unit);
770 (void) get_temp_units(0, &unit);
771 gtk_tree_view_column_set_title(dive_list.temperature, unit);
773 (void) get_weight_units(0, NULL, &unit);
774 gtk_tree_view_column_set_title(dive_list.totalweight, unit);
776 gtk_tree_model_foreach(model, set_one_dive, NULL);
779 void update_dive_list_col_visibility(void)
781 gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
782 gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
783 gtk_tree_view_column_set_visible(dive_list.totalweight, visible_cols.totalweight);
784 gtk_tree_view_column_set_visible(dive_list.suit, visible_cols.suit);
785 gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
786 gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
787 gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
791 /* random heuristic - not diving in three days implies new dive trip */
792 #define TRIP_THRESHOLD 3600*24*3
793 static int new_group(struct dive *dive, struct dive **last_dive, time_t *tm_date)
798 struct dive *ldive = *last_dive;
799 if (abs(dive->when - ldive->when) < TRIP_THRESHOLD) {
806 struct tm *tm1 = gmtime(&dive->when);
810 *tm_date = mktime(tm1);
815 static void fill_dive_list(void)
818 GtkTreeIter iter, parent_iter;
819 GtkTreeStore *liststore, *treestore;
820 struct dive *last_dive = NULL;
821 struct dive *first_trip_dive = NULL;
822 struct dive *last_trip_dive = NULL;
825 treestore = GTK_TREE_STORE(dive_list.treemodel);
826 liststore = GTK_TREE_STORE(dive_list.listmodel);
830 struct dive *dive = dive_table.dives[i];
832 if (new_group(dive, &last_dive, &dive_date))
834 /* make sure we display the first date of the trip in previous summary */
835 if (first_trip_dive && last_trip_dive && last_trip_dive->when < first_trip_dive->when)
836 gtk_tree_store_set(treestore, &parent_iter,
837 DIVE_DATE, last_trip_dive->when,
838 DIVE_LOCATION, last_trip_dive->location,
840 first_trip_dive = dive;
842 gtk_tree_store_append(treestore, &parent_iter, NULL);
843 gtk_tree_store_set(treestore, &parent_iter,
844 DIVE_INDEX, -NEW_TRIP,
846 DIVE_DATE, dive_date,
847 DIVE_LOCATION, dive->location,
852 last_trip_dive = dive;
853 update_cylinder_related_info(dive);
854 gtk_tree_store_append(treestore, &iter, &parent_iter);
855 gtk_tree_store_set(treestore, &iter,
857 DIVE_NR, dive->number,
858 DIVE_DATE, dive->when,
859 DIVE_DEPTH, dive->maxdepth,
860 DIVE_DURATION, dive->duration.seconds,
861 DIVE_LOCATION, dive->location,
862 DIVE_RATING, dive->rating,
863 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
866 gtk_tree_store_append(liststore, &iter, NULL);
867 gtk_tree_store_set(liststore, &iter,
869 DIVE_NR, dive->number,
870 DIVE_DATE, dive->when,
871 DIVE_DEPTH, dive->maxdepth,
872 DIVE_DURATION, dive->duration.seconds,
873 DIVE_LOCATION, dive->location,
874 DIVE_RATING, dive->rating,
875 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
877 DIVE_SUIT, dive->suit,
882 update_dive_list_units();
883 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
884 GtkTreeSelection *selection;
886 /* select the last dive (and make sure it's an actual dive that is selected) */
887 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
888 first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive);
889 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
890 gtk_tree_selection_select_iter(selection, &iter);
891 selectiontracker = realloc(selectiontracker, sizeof(int));
892 *selectiontracker = selected_dive;
896 void dive_list_update_dives(void)
898 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.treemodel));
899 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.listmodel));
904 static struct divelist_column {
911 [DIVE_NR] = { "#", nr_data_func, NULL, ALIGN_RIGHT | UNSORTABLE },
912 [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
913 [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
914 [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
915 [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
916 [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
917 [DIVE_TOTALWEIGHT] = { "lbs", weight_data_func, NULL, ALIGN_RIGHT, &visible_cols.totalweight },
918 [DIVE_SUIT] = { "Suit", NULL, NULL, ALIGN_LEFT, &visible_cols.suit },
919 [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
920 [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
921 [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
922 [DIVE_OTU] = { "OTU", otu_data_func, NULL, 0, &visible_cols.otu },
923 [DIVE_LOCATION] = { "Location", NULL, NULL, ALIGN_LEFT },
927 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col)
929 int index = col - &dl_column[0];
930 const char *title = col->header;
931 data_func_t data_func = col->data;
932 sort_func_t sort_func = col->sort;
933 unsigned int flags = col->flags;
934 int *visible = col->visible;
935 GtkWidget *tree_view = dl->tree_view;
936 GtkTreeStore *treemodel = dl->treemodel;
937 GtkTreeStore *listmodel = dl->listmodel;
938 GtkTreeViewColumn *ret;
940 if (visible && !*visible)
942 ret = tree_view_column(tree_view, index, title, data_func, flags);
944 /* the sort functions are needed in the corresponding models */
945 if (index == DIVE_DATE)
946 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treemodel), index, sort_func, NULL, NULL);
948 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(listmodel), index, sort_func, NULL, NULL);
954 * This is some crazy crap. The only way to get default focus seems
955 * to be to grab focus as the widget is being shown the first time.
957 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
959 gtk_widget_grab_focus(tree_view);
962 static void row_activated_cb(GtkTreeView *tree_view,
964 GtkTreeViewColumn *column,
970 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path))
972 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &index, -1);
973 /* a negative index is special for the "group by date" entries */
975 edit_dive_info(get_dive(index));
978 void add_dive_cb(GtkWidget *menuitem, gpointer data)
983 if (add_new_dive(dive)) {
991 void edit_dive_cb(GtkWidget *menuitem, gpointer data)
993 edit_multi_dive_info(amount_selected, selectiontracker);
996 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
998 GtkWidget *menu, *menuitem;
999 char editlabel[] = "Edit dives";
1001 menu = gtk_menu_new();
1002 menuitem = gtk_menu_item_new_with_label("Add dive");
1003 g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL);
1004 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1005 if (amount_selected) {
1006 if (amount_selected == 1)
1007 editlabel[strlen(editlabel) - 1] = '\0';
1008 menuitem = gtk_menu_item_new_with_label(editlabel);
1009 g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_cb), model);
1010 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
1012 gtk_widget_show_all(menu);
1014 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
1015 button, gtk_get_current_event_time());
1018 static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata)
1020 popup_divelist_menu(tree_view, GTK_TREE_MODEL(dive_list.model), 0);
1023 static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
1025 /* Right-click? Bring up the menu */
1026 if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
1027 popup_divelist_menu(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(dive_list.model), 3);
1033 /* we need to have a temporary copy of the selected dives while
1034 switching model as the selection_cb function keeps getting called
1035 when gtk_tree_selection_select_path is called. We also need to
1036 keep copies of the sort order so we can restore that as well after
1037 switching models. */
1038 static int *oldselection;
1039 static int old_nr_selected;
1040 static gboolean second_call = FALSE;
1041 static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, };
1042 static int lastcol = DIVE_DATE;
1044 /* Check if this dive was selected previously and select it again in the new model;
1045 * This is used after we switch models to maintain consistent selections.
1046 * We always return FALSE to iterate through all dives */
1047 static gboolean select_selected(GtkTreeModel *model, GtkTreePath *path,
1048 GtkTreeIter *iter, gpointer data)
1051 GtkTreeSelection *selection = GTK_TREE_SELECTION(data);
1053 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
1054 for (i = 0; i < old_nr_selected; i++)
1055 if (oldselection[i] == idx) {
1056 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path);
1057 gtk_tree_selection_select_path(selection, path);
1065 static void update_column_and_order(int colid)
1067 /* Careful: the index into treecolumns is off by one as we don't have a
1068 tree_view column for DIVE_INDEX */
1069 GtkTreeViewColumn **treecolumns = &dive_list.nr;
1071 /* this will trigger a second call into sort_column_change_cb,
1072 so make sure we don't start an infinite recursion... */
1074 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, sortorder[colid]);
1075 gtk_tree_view_column_set_sort_order(treecolumns[colid - 1], sortorder[colid]);
1076 second_call = FALSE;
1079 /* If the sort column is date (default), show the tree model.
1080 For every other sort column only show the list model.
1081 If the model changed, inform the new model of the chosen sort column and make
1082 sure the same dives are still selected.
1084 The challenge with this function is that once we change the model
1085 we also need to change the sort column again (as it was changed in
1086 the other model) and that causes this function to be called
1087 recursively - so we need to catch that.
1089 static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data)
1093 GtkTreeStore *currentmodel = dive_list.model;
1098 gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order);
1099 if(colid == lastcol) {
1100 /* we just changed sort order */
1101 sortorder[colid] = order;
1106 if(colid == DIVE_DATE)
1107 dive_list.model = dive_list.treemodel;
1109 dive_list.model = dive_list.listmodel;
1110 if (dive_list.model != currentmodel) {
1111 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1113 /* remember what is currently selected, switch models and reselect the selected rows */
1114 old_nr_selected = amount_selected;
1115 oldselection = malloc(old_nr_selected * sizeof(int));
1116 if (amount_selected)
1117 memcpy(oldselection, selectiontracker, amount_selected * sizeof(int));
1118 gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model));
1120 update_column_and_order(colid);
1122 if (old_nr_selected) {
1123 /* we need to select all the dives that were selected */
1124 /* this is fundamentally an n^2 algorithm as implemented - YUCK */
1125 gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), select_selected, selection);
1128 if (order != sortorder[colid]) {
1129 update_column_and_order(colid);
1134 GtkWidget *dive_list_create(void)
1136 GtkTreeSelection *selection;
1138 dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1139 G_TYPE_INT, /* index */
1140 G_TYPE_INT, /* nr */
1141 G_TYPE_INT, /* Date */
1142 G_TYPE_INT, /* Star rating */
1143 G_TYPE_INT, /* Depth */
1144 G_TYPE_INT, /* Duration */
1145 G_TYPE_INT, /* Temperature */
1146 G_TYPE_INT, /* Total weight */
1147 G_TYPE_STRING, /* Suit */
1148 G_TYPE_STRING, /* Cylinder */
1149 G_TYPE_INT, /* Nitrox */
1150 G_TYPE_INT, /* SAC */
1151 G_TYPE_INT, /* OTU */
1152 G_TYPE_STRING /* Location */
1154 dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1155 G_TYPE_INT, /* index */
1156 G_TYPE_INT, /* nr */
1157 G_TYPE_INT, /* Date */
1158 G_TYPE_INT, /* Star rating */
1159 G_TYPE_INT, /* Depth */
1160 G_TYPE_INT, /* Duration */
1161 G_TYPE_INT, /* Temperature */
1162 G_TYPE_INT, /* Total weight */
1163 G_TYPE_STRING, /* Suit */
1164 G_TYPE_STRING, /* Cylinder */
1165 G_TYPE_INT, /* Nitrox */
1166 G_TYPE_INT, /* SAC */
1167 G_TYPE_INT, /* OTU */
1168 G_TYPE_STRING /* Location */
1170 dive_list.model = dive_list.treemodel;
1171 dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
1172 set_divelist_font(divelist_font);
1174 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1176 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
1177 gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
1179 dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR);
1180 dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE);
1181 dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING);
1182 dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH);
1183 dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION);
1184 dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE);
1185 dive_list.totalweight = divelist_column(&dive_list, dl_column + DIVE_TOTALWEIGHT);
1186 dive_list.suit = divelist_column(&dive_list, dl_column + DIVE_SUIT);
1187 dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER);
1188 dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX);
1189 dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC);
1190 dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU);
1191 dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION);
1195 g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
1196 "search-column", DIVE_LOCATION,
1200 g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
1201 g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL);
1202 g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL);
1203 g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL);
1204 g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), NULL);
1205 g_signal_connect(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1206 g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1208 gtk_tree_selection_set_select_function(selection, modify_selection_cb, NULL, NULL);
1210 dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
1211 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
1212 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1213 gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
1215 dive_list.changed = 0;
1217 return dive_list.container_widget;
1220 void mark_divelist_changed(int changed)
1222 dive_list.changed = changed;
1225 int unsaved_changes()
1227 return dive_list.changed;