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 static void selection_cb(GtkTreeSelection *selection, gpointer userdata)
89 int nr_selected = gtk_tree_selection_count_selected_rows(selection);
92 g_list_foreach (selected_dives, (GFunc) gtk_tree_path_free, NULL);
93 g_list_free (selected_dives);
95 selected_dives = gtk_tree_selection_get_selected_rows(selection, NULL);
96 selectiontracker = realloc(selectiontracker, nr_selected * sizeof(int));
98 switch (nr_selected) {
99 case 0: /* keep showing the last selected dive */
102 /* just pick that dive as selected */
104 path = g_list_nth_data(selected_dives, 0);
105 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path)) {
106 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
107 /* a negative index means we picked a summary entry;
108 expand that entry and use first real child instead */
109 while (selected_dive < 0) {
112 memcpy(&parent, &iter, sizeof(parent));
113 tpath = gtk_tree_model_get_path(GTK_TREE_MODEL(dive_list.model), &parent);
114 if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(dive_list.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_collapse_row(GTK_TREE_VIEW(dive_list.tree_view), tpath);
120 gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
121 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
123 selectiontracker[0] = selected_dive;
127 default: /* multiple selections - what now? At this point I
128 * don't want to change the selected dive unless
129 * there is exactly one dive selected; not sure this
130 * is the most intuitive solution.
131 * I do however want to keep around which dives have
134 this also does not handle the case if a summary row is selected;
135 We should iterate and select all dives under that row */
136 amount_selected = g_list_length(selected_dives);
137 process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model));
143 const char *star_strings[] = {
152 static void star_data_func(GtkTreeViewColumn *col,
153 GtkCellRenderer *renderer,
161 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_RATING, &nr_stars, -1);
165 if (nr_stars < 0 || nr_stars > 5)
167 snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]);
169 g_object_set(renderer, "text", buffer, NULL);
172 static void date_data_func(GtkTreeViewColumn *col,
173 GtkCellRenderer *renderer,
183 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &val, -1);
191 snprintf(buffer, sizeof(buffer),
193 weekday(tm->tm_wday),
194 monthname(tm->tm_mon),
195 tm->tm_mday, tm->tm_year + 1900);
198 snprintf(buffer, sizeof(buffer),
200 monthname(tm->tm_mon),
204 snprintf(buffer, sizeof(buffer),
205 "%d", tm->tm_year + 1900);
208 snprintf(buffer, sizeof(buffer),
209 "%s, %s %d, %d %02d:%02d",
210 weekday(tm->tm_wday),
211 monthname(tm->tm_mon),
212 tm->tm_mday, tm->tm_year + 1900,
213 tm->tm_hour, tm->tm_min);
215 g_object_set(renderer, "text", buffer, NULL);
218 static void depth_data_func(GtkTreeViewColumn *col,
219 GtkCellRenderer *renderer,
224 int depth, integer, frac, len, idx;
227 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1);
232 switch (output_units.length) {
234 /* To tenths of meters */
235 depth = (depth + 49) / 100;
236 integer = depth / 10;
245 integer = mm_to_feet(depth) + 0.5;
251 len = snprintf(buffer, sizeof(buffer), "%d", integer);
253 len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
255 g_object_set(renderer, "text", buffer, NULL);
258 static void duration_data_func(GtkTreeViewColumn *col,
259 GtkCellRenderer *renderer,
268 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1);
272 snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
274 g_object_set(renderer, "text", buffer, NULL);
277 static void temperature_data_func(GtkTreeViewColumn *col,
278 GtkCellRenderer *renderer,
286 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1);
289 if (idx >= 0 && value) {
291 switch (output_units.temperature) {
293 deg = mkelvin_to_C(value);
296 deg = mkelvin_to_F(value);
301 snprintf(buffer, sizeof(buffer), "%.1f", deg);
304 g_object_set(renderer, "text", buffer, NULL);
307 static void nr_data_func(GtkTreeViewColumn *col,
308 GtkCellRenderer *renderer,
316 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1);
320 snprintf(buffer, sizeof(buffer), "%d", nr);
321 g_object_set(renderer, "text", buffer, NULL);
325 * Get "maximal" dive gas for a dive.
327 * - Trimix trumps nitrox (highest He wins, O2 breaks ties)
328 * - Nitrox trumps air (even if hypoxic)
329 * These are the same rules as the inter-dive sorting rules.
331 static void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
334 int maxo2 = -1, maxhe = -1, mino2 = 1000;
336 for (i = 0; i < MAX_CYLINDERS; i++) {
337 cylinder_t *cyl = dive->cylinder + i;
338 struct gasmix *mix = &cyl->gasmix;
339 int o2 = mix->o2.permille;
340 int he = mix->he.permille;
342 if (cylinder_none(cyl))
358 /* All air? Show/sort as "air"/zero */
359 if (!maxhe && maxo2 == AIR_PERMILLE && mino2 == maxo2)
366 static gint nitrox_sort_func(GtkTreeModel *model,
371 int index_a, index_b;
375 int a_o2low, b_o2low;
377 gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1);
378 gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1);
379 a = get_dive(index_a);
380 b = get_dive(index_b);
381 get_dive_gas(a, &a_o2, &a_he, &a_o2low);
382 get_dive_gas(b, &b_o2, &b_he, &b_o2low);
384 /* Sort by Helium first, O2 second */
387 return a_o2low - b_o2low;
393 #define UTF8_ELLIPSIS "\xE2\x80\xA6"
395 static void nitrox_data_func(GtkTreeViewColumn *col,
396 GtkCellRenderer *renderer,
401 int idx, o2, he, o2low;
405 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
410 dive = get_dive(idx);
411 get_dive_gas(dive, &o2, &he, &o2low);
414 o2low = (o2low + 5) / 10;
417 snprintf(buffer, sizeof(buffer), "%d/%d", o2, he);
420 snprintf(buffer, sizeof(buffer), "%d", o2);
422 snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
424 strcpy(buffer, "air");
426 g_object_set(renderer, "text", buffer, NULL);
429 /* Render the SAC data (integer value of "ml / min") */
430 static void sac_data_func(GtkTreeViewColumn *col,
431 GtkCellRenderer *renderer,
441 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1);
443 if (idx < 0 || !value) {
448 sac = value / 1000.0;
449 switch (output_units.volume) {
455 sac = ml_to_cuft(sac * 1000);
458 snprintf(buffer, sizeof(buffer), fmt, sac);
460 g_object_set(renderer, "text", buffer, NULL);
463 /* Render the OTU data (integer value of "OTU") */
464 static void otu_data_func(GtkTreeViewColumn *col,
465 GtkCellRenderer *renderer,
473 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1);
475 if (idx < 0 || !value)
478 snprintf(buffer, sizeof(buffer), "%d", value);
480 g_object_set(renderer, "text", buffer, NULL);
483 /* calculate OTU for a dive */
484 static int calculate_otu(struct dive *dive)
489 for (i = 1; i < dive->samples; i++) {
492 struct sample *sample = dive->sample + i;
493 struct sample *psample = sample - 1;
494 t = sample->time.seconds - psample->time.seconds;
495 int o2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille;
498 po2 = o2 / 1000.0 * (sample->depth.mm + 10000) / 10000.0;
500 otu += pow(po2 - 0.5, 0.83) * t / 30.0;
505 * Return air usage (in liters).
507 static double calculate_airuse(struct dive *dive)
512 for (i = 0; i < MAX_CYLINDERS; i++) {
513 pressure_t start, end;
514 cylinder_t *cyl = dive->cylinder + i;
515 int size = cyl->type.size.mliter;
521 start = cyl->start.mbar ? cyl->start : cyl->sample_start;
522 end = cyl->end.mbar ? cyl->end : cyl->sample_end;
523 kilo_atm = (to_ATM(start) - to_ATM(end)) / 1000.0;
525 /* Liters of air at 1 atm == milliliters at 1k atm*/
526 airuse += kilo_atm * size;
531 static int calculate_sac(struct dive *dive)
533 double airuse, pressure, sac;
536 airuse = calculate_airuse(dive);
539 if (!dive->duration.seconds)
542 /* find and eliminate long surface intervals */
543 duration = dive->duration.seconds;
544 for (i = 0; i < dive->samples; i++) {
545 if (dive->sample[i].depth.mm < 100) { /* less than 10cm */
547 while (end < dive->samples && dive->sample[end].depth.mm < 100)
549 /* we only want the actual surface time during a dive */
550 if (end < dive->samples) {
552 duration -= dive->sample[end].time.seconds -
553 dive->sample[i].time.seconds;
558 /* Mean pressure in atm: 1 atm per 10m */
559 pressure = 1 + (dive->meandepth.mm / 10000.0);
560 sac = airuse / pressure * 60 / duration;
562 /* milliliters per minute.. */
566 void update_cylinder_related_info(struct dive *dive)
569 dive->sac = calculate_sac(dive);
570 dive->otu = calculate_otu(dive);
574 static void get_string(char **str, const char *s)
590 static void get_location(struct dive *dive, char **str)
592 get_string(str, dive->location);
595 static void get_cylinder(struct dive *dive, char **str)
597 get_string(str, dive->cylinder[0].type.description);
601 * Set up anything that could have changed due to editing
602 * of dive information; we need to do this for both models,
603 * so we simply call set_one_dive again with the non-current model
605 /* forward declaration for recursion */
606 static gboolean set_one_dive(GtkTreeModel *model,
611 static void fill_one_dive(struct dive *dive,
615 char *location, *cylinder;
616 GtkTreeStore *othermodel;
618 get_cylinder(dive, &cylinder);
619 get_location(dive, &location);
621 gtk_tree_store_set(GTK_TREE_STORE(model), iter,
622 DIVE_NR, dive->number,
623 DIVE_LOCATION, location,
624 DIVE_CYLINDER, cylinder,
625 DIVE_RATING, dive->rating,
629 if (model == GTK_TREE_MODEL(dive_list.treemodel))
630 othermodel = dive_list.listmodel;
632 othermodel = dive_list.treemodel;
633 if (othermodel != dive_list.model)
635 gtk_tree_model_foreach(GTK_TREE_MODEL(othermodel), set_one_dive, dive);
638 static gboolean set_one_dive(GtkTreeModel *model,
646 /* Get the dive number */
647 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
650 dive = get_dive(idx);
653 if (data && dive != data)
656 fill_one_dive(dive, model, iter);
660 void flush_divelist(struct dive *dive)
662 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
664 gtk_tree_model_foreach(model, set_one_dive, dive);
667 void set_divelist_font(const char *font)
669 PangoFontDescription *font_desc = pango_font_description_from_string(font);
670 gtk_widget_modify_font(dive_list.tree_view, font_desc);
671 pango_font_description_free(font_desc);
674 void update_dive_list_units(void)
677 GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
679 (void) get_depth_units(0, NULL, &unit);
680 gtk_tree_view_column_set_title(dive_list.depth, unit);
682 (void) get_temp_units(0, &unit);
683 gtk_tree_view_column_set_title(dive_list.temperature, unit);
685 gtk_tree_model_foreach(model, set_one_dive, NULL);
688 void update_dive_list_col_visibility(void)
690 gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
691 gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
692 gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
693 gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
694 gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
698 static int new_date(struct dive *dive, struct dive **last_dive, const int flag, time_t *tm_date)
703 struct dive *ldive = *last_dive;
705 (void) gmtime_r(&dive->when, &tm1);
706 (void) gmtime_r(&ldive->when, &tm2);
707 if (tm1.tm_year == tm2.tm_year &&
708 (tm1.tm_mon == tm2.tm_mon || flag > NEW_MON) &&
709 (tm1.tm_mday == tm2.tm_mday || flag > NEW_DAY))
715 struct tm *tm1 = gmtime(&dive->when);
719 *tm_date = mktime(tm1);
724 static void fill_dive_list(void)
727 GtkTreeIter iter, parent_iter[NEW_YR + 2], *parents[NEW_YR + 2] = {NULL, };
728 GtkTreeStore *liststore, *treestore;
729 struct dive *last_dive = NULL;
732 treestore = GTK_TREE_STORE(dive_list.treemodel);
733 liststore = GTK_TREE_STORE(dive_list.listmodel);
737 struct dive *dive = dive_table.dives[i];
739 for (j = NEW_YR; j >= NEW_DAY; j--) {
740 if (new_date(dive, &last_dive, j, &dive_date))
742 gtk_tree_store_append(treestore, &parent_iter[j], parents[j+1]);
743 parents[j] = &parent_iter[j];
744 gtk_tree_store_set(treestore, parents[j],
747 DIVE_DATE, dive_date,
754 update_cylinder_related_info(dive);
755 gtk_tree_store_append(treestore, &iter, parents[NEW_DAY]);
756 gtk_tree_store_set(treestore, &iter,
758 DIVE_NR, dive->number,
759 DIVE_DATE, dive->when,
760 DIVE_DEPTH, dive->maxdepth,
761 DIVE_DURATION, dive->duration.seconds,
762 DIVE_LOCATION, dive->location,
763 DIVE_RATING, dive->rating,
764 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
767 gtk_tree_store_append(liststore, &iter, NULL);
768 gtk_tree_store_set(liststore, &iter,
770 DIVE_NR, dive->number,
771 DIVE_DATE, dive->when,
772 DIVE_DEPTH, dive->maxdepth,
773 DIVE_DURATION, dive->duration.seconds,
774 DIVE_LOCATION, dive->location,
775 DIVE_RATING, dive->rating,
776 DIVE_TEMPERATURE, dive->watertemp.mkelvin,
781 update_dive_list_units();
782 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
783 GtkTreeSelection *selection;
784 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
785 gtk_tree_selection_select_iter(selection, &iter);
786 selectiontracker = realloc(selectiontracker, sizeof(int));
787 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, selectiontracker, -1);
791 void dive_list_update_dives(void)
793 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.treemodel));
794 gtk_tree_store_clear(GTK_TREE_STORE(dive_list.listmodel));
799 static struct divelist_column {
806 [DIVE_NR] = { "#", nr_data_func, NULL, ALIGN_RIGHT | UNSORTABLE },
807 [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
808 [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
809 [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
810 [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
811 [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
812 [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
813 [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
814 [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
815 [DIVE_OTU] = { "OTU", otu_data_func, NULL, 0, &visible_cols.otu },
816 [DIVE_LOCATION] = { "Location", NULL, NULL, ALIGN_LEFT },
820 static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col)
822 int index = col - &dl_column[0];
823 const char *title = col->header;
824 data_func_t data_func = col->data;
825 sort_func_t sort_func = col->sort;
826 unsigned int flags = col->flags;
827 int *visible = col->visible;
828 GtkWidget *tree_view = dl->tree_view;
829 GtkTreeStore *model = dl->model;
830 GtkTreeViewColumn *ret;
832 if (visible && !*visible)
834 ret = tree_view_column(tree_view, index, title, data_func, flags);
836 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), index, sort_func, NULL, NULL);
841 * This is some crazy crap. The only way to get default focus seems
842 * to be to grab focus as the widget is being shown the first time.
844 static void realize_cb(GtkWidget *tree_view, gpointer userdata)
846 gtk_widget_grab_focus(tree_view);
849 static void row_activated_cb(GtkTreeView *tree_view,
851 GtkTreeViewColumn *column,
857 if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path))
859 gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &index, -1);
860 /* a negative index is special for the "group by date" entries */
862 edit_dive_info(get_dive(index));
865 void add_dive_cb(GtkWidget *menuitem, gpointer data)
870 if (add_new_dive(dive)) {
878 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
880 GtkWidget *menu, *menuitem;
882 menu = gtk_menu_new();
883 menuitem = gtk_menu_item_new_with_label("Add dive");
884 g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL);
885 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
886 gtk_widget_show_all(menu);
888 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
889 button, gtk_get_current_event_time());
892 static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata)
894 popup_divelist_menu(tree_view, GTK_TREE_MODEL(dive_list.model), 0);
897 static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
899 /* Right-click? Bring up the menu */
900 if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
901 popup_divelist_menu(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(dive_list.model), 3);
907 /* we need to have a temporary copy of the selected dives while
908 switching model as the selection_cb function keeps getting called
909 by when gtk_tree_selection_select_path is called. */
910 static int *oldselection;
911 static int old_nr_selected;
913 /* Check if this dive was selected previously and select it again in the new model;
914 * This is used after we switch models to maintain consistent selections.
915 * We always return FALSE to iterate through all dives */
916 static gboolean select_selected(GtkTreeModel *model, GtkTreePath *path,
917 GtkTreeIter *iter, gpointer data)
920 GtkTreeSelection *selection = GTK_TREE_SELECTION(data);
922 gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
923 for (i = 0; i < old_nr_selected; i++)
924 if (oldselection[i] == idx) {
925 gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path);
926 gtk_tree_selection_select_path(selection, path);
934 /* If the sort column is date (default), show the tree model.
935 For every other sort column only show the list model.
936 If the model changed, inform the new model of the chosen sort column and make
937 sure the same dives are still selected. */
938 static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data)
942 GtkTreeStore *currentmodel = dive_list.model;
944 gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order);
945 if(colid == DIVE_DATE)
946 dive_list.model = dive_list.treemodel;
948 dive_list.model = dive_list.listmodel;
949 if (dive_list.model != currentmodel) {
951 we should remember the sort order we had for each column */
952 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
954 /* remember what is currently selected, switch models and reselect the selected rows */
955 old_nr_selected = amount_selected;
956 oldselection = malloc(old_nr_selected * sizeof(int));
957 memcpy(oldselection, selectiontracker, amount_selected * sizeof(int));
959 gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model));
960 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, order);
962 if (old_nr_selected) {
963 /* we need to select all the dives that were selected */
964 /* this is fundamentally an n^2 algorithm as implemented - YUCK */
965 gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), select_selected, selection);
970 GtkWidget *dive_list_create(void)
972 GtkTreeSelection *selection;
974 dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS,
975 G_TYPE_INT, /* index */
977 G_TYPE_INT, /* Date */
978 G_TYPE_INT, /* Star rating */
979 G_TYPE_INT, /* Depth */
980 G_TYPE_INT, /* Duration */
981 G_TYPE_INT, /* Temperature */
982 G_TYPE_STRING, /* Cylinder */
983 G_TYPE_INT, /* Nitrox */
984 G_TYPE_INT, /* SAC */
985 G_TYPE_INT, /* OTU */
986 G_TYPE_STRING /* Location */
988 dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS,
989 G_TYPE_INT, /* index */
991 G_TYPE_INT, /* Date */
992 G_TYPE_INT, /* Star rating */
993 G_TYPE_INT, /* Depth */
994 G_TYPE_INT, /* Duration */
995 G_TYPE_INT, /* Temperature */
996 G_TYPE_STRING, /* Cylinder */
997 G_TYPE_INT, /* Nitrox */
998 G_TYPE_INT, /* SAC */
999 G_TYPE_INT, /* OTU */
1000 G_TYPE_STRING /* Location */
1002 dive_list.model = dive_list.treemodel;
1003 dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
1004 set_divelist_font(divelist_font);
1006 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
1008 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE);
1009 gtk_widget_set_size_request(dive_list.tree_view, 200, 200);
1011 dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR);
1012 dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE);
1013 dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING);
1014 dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH);
1015 dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION);
1016 dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE);
1017 dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER);
1018 dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX);
1019 dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC);
1020 dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU);
1021 dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION);
1025 g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE,
1026 "search-column", DIVE_LOCATION,
1030 g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
1031 g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL);
1032 g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL);
1033 g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL);
1034 g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), NULL);
1035 g_signal_connect(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1036 g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
1038 dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
1039 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),
1040 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1041 gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view);
1043 dive_list.changed = 0;
1045 return dive_list.container_widget;
1048 void mark_divelist_changed(int changed)
1050 dive_list.changed = changed;
1053 int unsaved_changes()
1055 return dive_list.changed;