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, *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 */
48 DIVE_NITROX, /* int: dummy */
49 DIVE_SAC, /* int: in ml/min */
50 DIVE_OTU, /* int: in OTUs */
51 DIVE_LOCATION, /* "2nd Cathedral, Lanai" */
55 /* magic numbers that indicate (as negative values) model entries that
56 * are summary entries for day / month / year */
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 /* if we are sorting by date and are using a tree model, we don't want the selection
85 to be a summary entry, but instead the first child below that entry. So we descend
86 down the tree until we find a leaf (entry with non-negative index)
88 static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx)
93 while (*diveidx < 0) {
94 memcpy(&parent, iter, sizeof(parent));
95 tpath = gtk_tree_model_get_path(model, &parent);
96 if (!gtk_tree_model_iter_children(model, iter, &parent))
97 /* we should never have a parent without child */
99 /* clicking on a parent should toggle expand status */
100 if(gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath))
101 gtk_tree_view_collapse_row(GTK_TREE_VIEW(dive_list.tree_view), tpath);
103 gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
104 gtk_tree_model_get(GTK_TREE_MODEL(model), iter, DIVE_INDEX, diveidx, -1);
108 static void selection_cb(GtkTreeSelection *selection, gpointer userdata)
113 int nr_selected = gtk_tree_selection_count_selected_rows(selection);
115 if (selected_dives) {
116 g_list_foreach (selected_dives, (GFunc) gtk_tree_path_free, NULL);
117 g_list_free (selected_dives);
119 selected_dives = gtk_tree_selection_get_selected_rows(selection, NULL);
120 selectiontracker = realloc(selectiontracker, nr_selected * sizeof(int));
122 switch (nr_selected) {
123 case 0: /* keep showing the last selected dive */
126 /* just pick that dive as selected */
128 path = g_list_nth_data(selected_dives, 0);
129 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path)) {
130 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
131 /* make sure we're not on a summary entry */
132 first_leaf (GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive);
133 selectiontracker[0] = selected_dive;
137 default: /* multiple selections - what now? At this point I
138 * don't want to change the selected dive unless
139 * there is exactly one dive selected; not sure this
140 * is the most intuitive solution.
141 * I do however want to keep around which dives have
144 this also does not handle the case if a summary row is selected;
145 We should iterate and select all dives under that row */
146 amount_selected = g_list_length(selected_dives);
147 process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model));
153 const char *star_strings[] = {
162 static void star_data_func(GtkTreeViewColumn *col,
163 GtkCellRenderer *renderer,
171 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_RATING, &nr_stars, -1);
175 if (nr_stars < 0 || nr_stars > 5)
177 snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]);
179 g_object_set(renderer, "text", buffer, NULL);
182 static void date_data_func(GtkTreeViewColumn *col,
183 GtkCellRenderer *renderer,
193 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &val, -1);
201 snprintf(buffer, sizeof(buffer),
203 weekday(tm->tm_wday),
204 monthname(tm->tm_mon),
205 tm->tm_mday, tm->tm_year + 1900);
208 snprintf(buffer, sizeof(buffer),
210 monthname(tm->tm_mon),
214 snprintf(buffer, sizeof(buffer),
215 "%d", tm->tm_year + 1900);
218 snprintf(buffer, sizeof(buffer),
219 "%s, %s %d, %d %02d:%02d",
220 weekday(tm->tm_wday),
221 monthname(tm->tm_mon),
222 tm->tm_mday, tm->tm_year + 1900,
223 tm->tm_hour, tm->tm_min);
225 g_object_set(renderer, "text", buffer, NULL);
228 static void depth_data_func(GtkTreeViewColumn *col,
229 GtkCellRenderer *renderer,
234 int depth, integer, frac, len, idx;
237 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1);
242 switch (output_units.length) {
244 /* To tenths of meters */
245 depth = (depth + 49) / 100;
246 integer = depth / 10;
255 integer = mm_to_feet(depth) + 0.5;
261 len = snprintf(buffer, sizeof(buffer), "%d", integer);
263 len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
265 g_object_set(renderer, "text", buffer, NULL);
268 static void duration_data_func(GtkTreeViewColumn *col,
269 GtkCellRenderer *renderer,
278 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1);
282 snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
284 g_object_set(renderer, "text", buffer, NULL);
287 static void temperature_data_func(GtkTreeViewColumn *col,
288 GtkCellRenderer *renderer,
296 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1);
299 if (idx >= 0 && value) {
301 switch (output_units.temperature) {
303 deg = mkelvin_to_C(value);
306 deg = mkelvin_to_F(value);
311 snprintf(buffer, sizeof(buffer), "%.1f", deg);
314 g_object_set(renderer, "text", buffer, NULL);
317 static void nr_data_func(GtkTreeViewColumn *col,
318 GtkCellRenderer *renderer,
326 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1);
330 snprintf(buffer, sizeof(buffer), "%d", nr);
331 g_object_set(renderer, "text", buffer, NULL);
335 * Get "maximal" dive gas for a dive.
337 * - Trimix trumps nitrox (highest He wins, O2 breaks ties)
338 * - Nitrox trumps air (even if hypoxic)
339 * These are the same rules as the inter-dive sorting rules.
341 static void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
344 int maxo2 = -1, maxhe = -1, mino2 = 1000;
346 for (i = 0; i < MAX_CYLINDERS; i++) {
347 cylinder_t *cyl = dive->cylinder + i;
348 struct gasmix *mix = &cyl->gasmix;
349 int o2 = mix->o2.permille;
350 int he = mix->he.permille;
352 if (cylinder_none(cyl))
368 /* All air? Show/sort as "air"/zero */
369 if (!maxhe && maxo2 == AIR_PERMILLE && mino2 == maxo2)
376 static gint nitrox_sort_func(GtkTreeModel *model,
381 int index_a, index_b;
385 int a_o2low, b_o2low;
387 gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1);
388 gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1);
389 a = get_dive(index_a);
390 b = get_dive(index_b);
391 get_dive_gas(a, &a_o2, &a_he, &a_o2low);
392 get_dive_gas(b, &b_o2, &b_he, &b_o2low);
394 /* Sort by Helium first, O2 second */
397 return a_o2low - b_o2low;
403 #define UTF8_ELLIPSIS "\xE2\x80\xA6"
405 static void nitrox_data_func(GtkTreeViewColumn *col,
406 GtkCellRenderer *renderer,
411 int idx, o2, he, o2low;
415 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
420 dive = get_dive(idx);
421 get_dive_gas(dive, &o2, &he, &o2low);
424 o2low = (o2low + 5) / 10;
427 snprintf(buffer, sizeof(buffer), "%d/%d", o2, he);
430 snprintf(buffer, sizeof(buffer), "%d", o2);
432 snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
434 strcpy(buffer, "air");
436 g_object_set(renderer, "text", buffer, NULL);
439 /* Render the SAC data (integer value of "ml / min") */
440 static void sac_data_func(GtkTreeViewColumn *col,
441 GtkCellRenderer *renderer,
451 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1);
453 if (idx < 0 || !value) {
458 sac = value / 1000.0;
459 switch (output_units.volume) {
465 sac = ml_to_cuft(sac * 1000);
468 snprintf(buffer, sizeof(buffer), fmt, sac);
470 g_object_set(renderer, "text", buffer, NULL);
473 /* Render the OTU data (integer value of "OTU") */
474 static void otu_data_func(GtkTreeViewColumn *col,
475 GtkCellRenderer *renderer,
483 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1);
485 if (idx < 0 || !value)
488 snprintf(buffer, sizeof(buffer), "%d", value);
490 g_object_set(renderer, "text", buffer, NULL);
493 /* calculate OTU for a dive */
494 static int calculate_otu(struct dive *dive)
499 for (i = 1; i < dive->samples; i++) {
502 struct sample *sample = dive->sample + i;
503 struct sample *psample = sample - 1;
504 t = sample->time.seconds - psample->time.seconds;
505 int o2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille;
508 po2 = o2 / 1000.0 * (sample->depth.mm + 10000) / 10000.0;
510 otu += pow(po2 - 0.5, 0.83) * t / 30.0;
515 * Return air usage (in liters).
517 static double calculate_airuse(struct dive *dive)
522 for (i = 0; i < MAX_CYLINDERS; i++) {
523 pressure_t start, end;
524 cylinder_t *cyl = dive->cylinder + i;
525 int size = cyl->type.size.mliter;
531 start = cyl->start.mbar ? cyl->start : cyl->sample_start;
532 end = cyl->end.mbar ? cyl->end : cyl->sample_end;
533 kilo_atm = (to_ATM(start) - to_ATM(end)) / 1000.0;
535 /* Liters of air at 1 atm == milliliters at 1k atm*/
536 airuse += kilo_atm * size;
541 static int calculate_sac(struct dive *dive)
543 double airuse, pressure, sac;
546 airuse = calculate_airuse(dive);
549 if (!dive->duration.seconds)
552 /* find and eliminate long surface intervals */
553 duration = dive->duration.seconds;
554 for (i = 0; i < dive->samples; i++) {
555 if (dive->sample[i].depth.mm < 100) { /* less than 10cm */
557 while (end < dive->samples && dive->sample[end].depth.mm < 100)
559 /* we only want the actual surface time during a dive */
560 if (end < dive->samples) {
562 duration -= dive->sample[end].time.seconds -
563 dive->sample[i].time.seconds;
568 /* Mean pressure in atm: 1 atm per 10m */
569 pressure = 1 + (dive->meandepth.mm / 10000.0);
570 sac = airuse / pressure * 60 / duration;
572 /* milliliters per minute.. */
576 void update_cylinder_related_info(struct dive *dive)
579 dive->sac = calculate_sac(dive);
580 dive->otu = calculate_otu(dive);
584 static void get_string(char **str, const char *s)
600 static void get_location(struct dive *dive, char **str)
602 get_string(str, dive->location);
605 static void get_cylinder(struct dive *dive, char **str)
607 get_string(str, dive->cylinder[0].type.description);
611 * Set up anything that could have changed due to editing
612 * of dive information; we need to do this for both models,
613 * so we simply call set_one_dive again with the non-current model
615 /* forward declaration for recursion */
616 static gboolean set_one_dive(GtkTreeModel *model,
621 static void fill_one_dive(struct dive *dive,
625 char *location, *cylinder;
626 GtkTreeStore *othermodel;
628 get_cylinder(dive, &cylinder);
629 get_location(dive, &location);
631 gtk_tree_store_set(GTK_TREE_STORE(model), iter,
632 DIVE_NR, dive->number,
633 DIVE_LOCATION, location,
634 DIVE_CYLINDER, cylinder,
635 DIVE_RATING, dive->rating,
639 if (model == GTK_TREE_MODEL(dive_list.treemodel))
640 othermodel = dive_list.listmodel;
642 othermodel = dive_list.treemodel;
643 if (othermodel != dive_list.model)
645 gtk_tree_model_foreach(GTK_TREE_MODEL(othermodel), set_one_dive, dive);
648 static gboolean set_one_dive(GtkTreeModel *model,
656 /* Get the dive number */
657 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
660 dive = get_dive(idx);
663 if (data && dive != data)
666 fill_one_dive(dive, model, iter);
670 void flush_divelist(struct dive *dive)
672 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
674 gtk_tree_model_foreach(model, set_one_dive, dive);
677 void set_divelist_font(const char *font)
679 PangoFontDescription *font_desc = pango_font_description_from_string(font);
680 gtk_widget_modify_font(dive_list.tree_view, font_desc);
681 pango_font_description_free(font_desc);
684 void update_dive_list_units(void)
687 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
689 (void) get_depth_units(0, NULL, &unit);
690 gtk_tree_view_column_set_title(dive_list.depth, unit);
692 (void) get_temp_units(0, &unit);
693 gtk_tree_view_column_set_title(dive_list.temperature, unit);
695 gtk_tree_model_foreach(model, set_one_dive, NULL);
698 void update_dive_list_col_visibility(void)
700 gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
701 gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
702 gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
703 gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
704 gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
708 static int new_date(struct dive *dive, struct dive **last_dive, const int flag, time_t *tm_date)
713 struct dive *ldive = *last_dive;
715 (void) gmtime_r(&dive->when, &tm1);
716 (void) gmtime_r(&ldive->when, &tm2);
717 if (tm1.tm_year == tm2.tm_year &&
718 (tm1.tm_mon == tm2.tm_mon || flag > NEW_MON) &&
719 (tm1.tm_mday == tm2.tm_mday || flag > NEW_DAY))
725 struct tm *tm1 = gmtime(&dive->when);
729 *tm_date = mktime(tm1);
734 static void fill_dive_list(void)
737 GtkTreeIter iter, parent_iter[NEW_YR + 2], *parents[NEW_YR + 2] = {NULL, };
738 GtkTreeStore *liststore, *treestore;
739 struct dive *last_dive = NULL;
742 treestore = GTK_TREE_STORE(dive_list.treemodel);
743 liststore = GTK_TREE_STORE(dive_list.listmodel);
747 struct dive *dive = dive_table.dives[i];
749 for (j = NEW_YR; j >= NEW_DAY; j--) {
750 if (new_date(dive, &last_dive, j, &dive_date))
752 gtk_tree_store_append(treestore, &parent_iter[j], parents[j+1]);
753 parents[j] = &parent_iter[j];
754 gtk_tree_store_set(treestore, parents[j],
757 DIVE_DATE, dive_date,
764 update_cylinder_related_info(dive);
765 gtk_tree_store_append(treestore, &iter, parents[NEW_DAY]);
766 gtk_tree_store_set(treestore, &iter,
768 DIVE_NR, dive->number,
769 DIVE_DATE, dive->when,
770 DIVE_DEPTH, dive->maxdepth,
771 DIVE_DURATION, dive->duration.seconds,
772 DIVE_LOCATION, dive->location,
773 DIVE_RATING, dive->rating,
774 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
777 gtk_tree_store_append(liststore, &iter, NULL);
778 gtk_tree_store_set(liststore, &iter,
780 DIVE_NR, dive->number,
781 DIVE_DATE, dive->when,
782 DIVE_DEPTH, dive->maxdepth,
783 DIVE_DURATION, dive->duration.seconds,
784 DIVE_LOCATION, dive->location,
785 DIVE_RATING, dive->rating,
786 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
791 update_dive_list_units();
792 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
793 GtkTreeSelection *selection;
795 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
796 /* make sure it's an actual dive that is selected */
797 first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive);
798 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
799 gtk_tree_selection_select_iter(selection, &iter);
800 selectiontracker = realloc(selectiontracker, sizeof(int));
801 *selectiontracker = selected_dive;
805 void dive_list_update_dives(void)
807 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.treemodel));
808 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.listmodel));
813 static struct divelist_column {
820 [DIVE_NR] = { "#", nr_data_func, NULL, ALIGN_RIGHT | UNSORTABLE },
821 [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
822 [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
823 [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
824 [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
825 [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
826 [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
827 [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
828 [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
829 [DIVE_OTU] = { "OTU", otu_data_func, NULL, 0, &visible_cols.otu },
830 [DIVE_LOCATION] = { "Location", NULL, NULL, ALIGN_LEFT },
834 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col)
836 int index = col - &dl_column[0];
837 const char *title = col->header;
838 data_func_t data_func = col->data;
839 sort_func_t sort_func = col->sort;
840 unsigned int flags = col->flags;
841 int *visible = col->visible;
842 GtkWidget *tree_view = dl->tree_view;
843 GtkTreeStore *treemodel = dl->treemodel;
844 GtkTreeStore *listmodel = dl->listmodel;
845 GtkTreeViewColumn *ret;
847 if (visible && !*visible)
849 ret = tree_view_column(tree_view, index, title, data_func, flags);
851 /* the sort functions are needed in the corresponding models */
852 if (index == DIVE_DATE)
853 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treemodel), index, sort_func, NULL, NULL);
855 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(listmodel), index, sort_func, NULL, NULL);
861 * This is some crazy crap. The only way to get default focus seems
862 * to be to grab focus as the widget is being shown the first time.
864 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
866 gtk_widget_grab_focus(tree_view);
869 static void row_activated_cb(GtkTreeView *tree_view,
871 GtkTreeViewColumn *column,
877 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path))
879 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &index, -1);
880 /* a negative index is special for the "group by date" entries */
882 edit_dive_info(get_dive(index));
885 void add_dive_cb(GtkWidget *menuitem, gpointer data)
890 if (add_new_dive(dive)) {
898 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
900 GtkWidget *menu, *menuitem;
902 menu = gtk_menu_new();
903 menuitem = gtk_menu_item_new_with_label("Add dive");
904 g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL);
905 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
906 gtk_widget_show_all(menu);
908 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
909 button, gtk_get_current_event_time());
912 static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata)
914 popup_divelist_menu(tree_view, GTK_TREE_MODEL(dive_list.model), 0);
917 static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
919 /* Right-click? Bring up the menu */
920 if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
921 popup_divelist_menu(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(dive_list.model), 3);
927 /* we need to have a temporary copy of the selected dives while
928 switching model as the selection_cb function keeps getting called
929 when gtk_tree_selection_select_path is called. We also need to
930 keep copies of the sort order so we can restore that as well after
932 static int *oldselection;
933 static int old_nr_selected;
934 static gboolean second_call = FALSE;
935 static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, };
936 static int lastcol = DIVE_DATE;
938 /* Check if this dive was selected previously and select it again in the new model;
939 * This is used after we switch models to maintain consistent selections.
940 * We always return FALSE to iterate through all dives */
941 static gboolean select_selected(GtkTreeModel *model, GtkTreePath *path,
942 GtkTreeIter *iter, gpointer data)
945 GtkTreeSelection *selection = GTK_TREE_SELECTION(data);
947 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
948 for (i = 0; i < old_nr_selected; i++)
949 if (oldselection[i] == idx) {
950 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path);
951 gtk_tree_selection_select_path(selection, path);
959 static void update_column_and_order(int colid)
961 /* Careful: the index into treecolumns is off by one as we don't have a
962 tree_view column for DIVE_INDEX */
963 GtkTreeViewColumn **treecolumns = &dive_list.nr;
965 /* this will trigger a second call into sort_column_change_cb,
966 so make sure we don't start an infinite recursion... */
968 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, sortorder[colid]);
969 gtk_tree_view_column_set_sort_order(treecolumns[colid - 1], sortorder[colid]);
973 /* If the sort column is date (default), show the tree model.
974 For every other sort column only show the list model.
975 If the model changed, inform the new model of the chosen sort column and make
976 sure the same dives are still selected.
978 The challenge with this function is that once we change the model
979 we also need to change the sort column again (as it was changed in
980 the other model) and that causes this function to be called
981 recursively - so we need to catch that.
983 static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data)
987 GtkTreeStore *currentmodel = dive_list.model;
992 gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order);
993 if(colid == lastcol) {
994 /* we just changed sort order */
995 sortorder[colid] = order;
1000 if(colid == DIVE_DATE)
1001 dive_list.model = dive_list.treemodel;
1003 dive_list.model = dive_list.listmodel;
1004 if (dive_list.model != currentmodel) {
1005 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1007 /* remember what is currently selected, switch models and reselect the selected rows */
1008 old_nr_selected = amount_selected;
1009 oldselection = malloc(old_nr_selected * sizeof(int));
1010 if (amount_selected)
1011 memcpy(oldselection, selectiontracker, amount_selected * sizeof(int));
1012 gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model));
1014 update_column_and_order(colid);
1016 if (old_nr_selected) {
1017 /* we need to select all the dives that were selected */
1018 /* this is fundamentally an n^2 algorithm as implemented - YUCK */
1019 gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), select_selected, selection);
1022 if (order != sortorder[colid]) {
1023 update_column_and_order(colid);
1028 GtkWidget *dive_list_create(void)
1030 GtkTreeSelection *selection;
1032 dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1033 G_TYPE_INT, /* index */
1034 G_TYPE_INT, /* nr */
1035 G_TYPE_INT, /* Date */
1036 G_TYPE_INT, /* Star rating */
1037 G_TYPE_INT, /* Depth */
1038 G_TYPE_INT, /* Duration */
1039 G_TYPE_INT, /* Temperature */
1040 G_TYPE_STRING, /* Cylinder */
1041 G_TYPE_INT, /* Nitrox */
1042 G_TYPE_INT, /* SAC */
1043 G_TYPE_INT, /* OTU */
1044 G_TYPE_STRING /* Location */
1046 dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS,
1047 G_TYPE_INT, /* index */
1048 G_TYPE_INT, /* nr */
1049 G_TYPE_INT, /* Date */
1050 G_TYPE_INT, /* Star rating */
1051 G_TYPE_INT, /* Depth */
1052 G_TYPE_INT, /* Duration */
1053 G_TYPE_INT, /* Temperature */
1054 G_TYPE_STRING, /* Cylinder */
1055 G_TYPE_INT, /* Nitrox */
1056 G_TYPE_INT, /* SAC */
1057 G_TYPE_INT, /* OTU */
1058 G_TYPE_STRING /* Location */
1060 dive_list.model = dive_list.treemodel;
1061 dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
1062 set_divelist_font(divelist_font);
1064 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1066 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
1067 gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
1069 dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR);
1070 dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE);
1071 dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING);
1072 dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH);
1073 dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION);
1074 dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE);
1075 dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER);
1076 dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX);
1077 dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC);
1078 dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU);
1079 dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION);
1083 g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
1084 "search-column", DIVE_LOCATION,
1088 g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
1089 g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL);
1090 g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL);
1091 g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL);
1092 g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), NULL);
1093 g_signal_connect(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1094 g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1096 dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
1097 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
1098 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1099 gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
1101 dive_list.changed = 0;
1103 return dive_list.container_widget;
1106 void mark_divelist_changed(int changed)
1108 dive_list.changed = changed;
1111 int unsaved_changes()
1113 return dive_list.changed;