diff options
Diffstat (limited to 'divelist-gtk.c')
-rw-r--r-- | divelist-gtk.c | 2325 |
1 files changed, 2325 insertions, 0 deletions
diff --git a/divelist-gtk.c b/divelist-gtk.c new file mode 100644 index 000000000..8587e11c6 --- /dev/null +++ b/divelist-gtk.c @@ -0,0 +1,2325 @@ +/* divelist-gtk.c */ +/* this creates the UI for the dive list - + * controlled through the following interfaces: + * + * GdkPixbuf *get_gps_icon(void) + * void flush_divelist(struct dive *dive) + * void set_divelist_font(const char *font) + * void dive_list_update_dives(void) + * void update_dive_list_units(void) + * void update_dive_list_col_visibility(void) + * void dive_list_update_dives(void) + * void add_dive_cb(GtkWidget *menuitem, gpointer data) + * gboolean icon_click_cb(GtkWidget *w, GdkEventButton *event, gpointer data) + * void export_all_dives_uddf_cb() + * GtkWidget dive_list_create(void) + * void dive_list_destroy(void) + * void show_and_select_dive(struct dive *dive) + * void select_next_dive(void) + * void select_prev_dive(void) + */ +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <math.h> +#include <glib/gi18n.h> +#include <assert.h> +#ifdef LIBZIP +#include <zip.h> +#endif +#ifdef XSLT +#include <libxslt/transform.h> +#endif + +#include "dive.h" +#include "divelist.h" +#include "display.h" +#include "display-gtk.h" +#include "webservice.h" + +#include <gdk-pixbuf/gdk-pixdata.h> +#include "satellite.h" + +struct DiveList { + GtkWidget *tree_view; + GtkWidget *container_widget; + GtkTreeStore *model, *listmodel, *treemodel; + GtkTreeViewColumn *nr, *date, *stars, *depth, *duration, *location; + GtkTreeViewColumn *temperature, *cylinder, *totalweight, *suit, *nitrox, *sac, *otu, *maxcns; + int changed; +}; + +static struct DiveList dive_list; +#define MODEL(_dl) GTK_TREE_MODEL((_dl).model) +#define TREEMODEL(_dl) GTK_TREE_MODEL((_dl).treemodel) +#define LISTMODEL(_dl) GTK_TREE_MODEL((_dl).listmodel) +#define STORE(_dl) GTK_TREE_STORE((_dl).model) +#define TREESTORE(_dl) GTK_TREE_STORE((_dl).treemodel) +#define LISTSTORE(_dl) GTK_TREE_STORE((_dl).listmodel) + +dive_trip_t *dive_trip_list; +short autogroup = FALSE; +static gboolean in_set_cursor = FALSE; +static gboolean set_selected(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer data); + +/* + * The dive list has the dive data in both string format (for showing) + * and in "raw" format (for sorting purposes) + */ +enum { + DIVE_INDEX = 0, + DIVE_NR, /* int: dive->nr */ + DIVE_DATE, /* timestamp_t: dive->when */ + DIVE_RATING, /* int: 0-5 stars */ + DIVE_DEPTH, /* int: dive->maxdepth in mm */ + DIVE_DURATION, /* int: in seconds */ + DIVE_TEMPERATURE, /* int: in mkelvin */ + DIVE_TOTALWEIGHT, /* int: in grams */ + DIVE_SUIT, /* "wet, 3mm" */ + DIVE_CYLINDER, + DIVE_NITROX, /* int: dummy */ + DIVE_SAC, /* int: in ml/min */ + DIVE_OTU, /* int: in OTUs */ + DIVE_MAXCNS, /* int: in % */ + DIVE_LOCATION, /* "2nd Cathedral, Lanai" */ + DIVE_LOC_ICON, /* pixbuf for gps icon */ + DIVELIST_COLUMNS +}; + +static void merge_dive_into_trip_above_cb(GtkWidget *menuitem, GtkTreePath *path); + +#ifdef DEBUG_MODEL +static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) +{ + char *location; + int idx, nr, duration; + struct dive *dive; + timestamp_t when; + struct tm tm; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, DIVE_DATE, &when, + DIVE_DURATION, &duration, DIVE_LOCATION, &location, -1); + utc_mkdate(when, &tm); + printf("iter %x:%x entry #%d : nr %d @ %04d-%02d-%02d %02d:%02d:%02d duration %d location %s ", + iter->stamp, iter->user_data, idx, nr, + tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, + duration, location); + dive = get_dive(idx); + if (dive) + printf("tripflag %d\n", dive->tripflag); + else + printf("without matching dive\n"); + + free(location); + + return FALSE; +} + +static void dump_model(GtkListStore *store) +{ + gtk_tree_model_foreach(GTK_TREE_MODEL(store), dump_model_entry, NULL); + printf("\n---\n\n"); +} +#endif + +/* when subsurface starts we want to have the last dive selected. So we simply + walk to the first leaf (and skip the summary entries - which have negative + DIVE_INDEX) */ +static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx) +{ + GtkTreeIter parent; + GtkTreePath *tpath; + + while (*diveidx < 0) { + memcpy(&parent, iter, sizeof(parent)); + tpath = gtk_tree_model_get_path(model, &parent); + if (!gtk_tree_model_iter_children(model, iter, &parent)) { + /* we should never have a parent without child */ + gtk_tree_path_free(tpath); + return; + } + if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath)) + gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE); + gtk_tree_path_free(tpath); + gtk_tree_model_get(model, iter, DIVE_INDEX, diveidx, -1); + } +} + +static struct dive *dive_from_path(GtkTreePath *path) +{ + GtkTreeIter iter; + int idx; + + if (gtk_tree_model_get_iter(MODEL(dive_list), &iter, path)) { + gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); + return get_dive(idx); + } else { + return NULL; + } + +} + +static int get_path_index(GtkTreePath *path) +{ + GtkTreeIter iter; + int idx; + + gtk_tree_model_get_iter(MODEL(dive_list), &iter, path); + gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); + return idx; +} + +/* make sure that if we expand a summary row that is selected, the children show + up as selected, too */ +static void row_expanded_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data) +{ + GtkTreeIter child; + GtkTreeModel *model = MODEL(dive_list); + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + dive_trip_t *trip; + + trip = find_trip_by_idx(get_path_index(path)); + if (!trip) + return; + + trip->expanded = 1; + if (!gtk_tree_model_iter_children(model, &child, iter)) + return; + + do { + int idx; + struct dive *dive; + + gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1); + dive = get_dive(idx); + + if (dive->selected) + gtk_tree_selection_select_iter(selection, &child); + else + gtk_tree_selection_unselect_iter(selection, &child); + } while (gtk_tree_model_iter_next(model, &child)); +} + +/* Make sure that if we collapse a summary row with any selected children, the row + shows up as selected too */ +static void row_collapsed_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data) +{ + dive_trip_t *trip; + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + + trip = find_trip_by_idx(get_path_index(path)); + if (!trip) + return; + + trip->expanded = 0; + if (trip_has_selected_dives(trip)) { + gtk_tree_selection_select_iter(selection, iter); + trip->selected = 1; + } +} + +const char *star_strings[] = { + ZERO_STARS, + ONE_STARS, + TWO_STARS, + THREE_STARS, + FOUR_STARS, + FIVE_STARS +}; + +static void star_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int nr_stars, idx; + char buffer[40]; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_RATING, &nr_stars, -1); + if (idx < 0) { + *buffer = '\0'; + } else { + if (nr_stars < 0 || nr_stars > 5) + nr_stars = 0; + snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]); + } + g_object_set(renderer, "text", buffer, NULL); +} + +static void date_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int idx, nr; + struct tm tm; + timestamp_t when; + /* this should be enought for most languages. if not increase the value. */ + char buffer[256]; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &when, -1); + nr = gtk_tree_model_iter_n_children(model, iter); + + utc_mkdate(when, &tm); + if (idx < 0) { + snprintf(buffer, sizeof(buffer), + /*++GETTEXT 60 char buffer weekday, monthname, day of month, year, nr dives */ + ngettext("Trip %1$s, %2$s %3$d, %4$d (%5$d dive)", + "Trip %1$s, %2$s %3$d, %4$d (%5$d dives)", nr), + weekday(tm.tm_wday), + monthname(tm.tm_mon), + tm.tm_mday, tm.tm_year + 1900, + nr); + } else { + snprintf(buffer, sizeof(buffer), + /*++GETTEXT 60 char buffer weekday, monthname, day of month, year, hour:min */ + _("%1$s, %2$s %3$d, %4$d %5$02d:%6$02d"), + weekday(tm.tm_wday), + monthname(tm.tm_mon), + tm.tm_mday, tm.tm_year + 1900, + tm.tm_hour, tm.tm_min); + } + g_object_set(renderer, "text", buffer, NULL); +} + +static void depth_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int depth, integer, frac, len, idx; + char buffer[40]; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1); + + if (idx < 0) { + *buffer = '\0'; + } else { + switch (prefs.units.length) { + case METERS: + /* To tenths of meters */ + depth = (depth + 49) / 100; + integer = depth / 10; + frac = depth % 10; + if (integer < 20) + break; + if (frac >= 5) + integer++; + frac = -1; + break; + case FEET: + integer = mm_to_feet(depth) + 0.5; + frac = -1; + break; + default: + return; + } + len = snprintf(buffer, sizeof(buffer), "%d", integer); + if (frac >= 0) + len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac); + } + g_object_set(renderer, "text", buffer, NULL); +} + +static void duration_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + unsigned int sec; + int idx; + char buffer[40]; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1); + if (idx < 0) + *buffer = '\0'; + else + snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60); + + g_object_set(renderer, "text", buffer, NULL); +} + +static void temperature_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int value, idx; + char buffer[80]; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1); + + *buffer = 0; + if (idx >= 0 && value) { + double deg; + switch (prefs.units.temperature) { + case CELSIUS: + deg = mkelvin_to_C(value); + break; + case FAHRENHEIT: + deg = mkelvin_to_F(value); + break; + default: + return; + } + snprintf(buffer, sizeof(buffer), "%.1f", deg); + } + + g_object_set(renderer, "text", buffer, NULL); +} + +static void gpsicon_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int idx; + GdkPixbuf *icon; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_LOC_ICON, &icon, -1); + g_object_set(renderer, "pixbuf", icon, NULL); +} + +static void nr_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int idx, nr; + char buffer[40]; + struct dive *dive; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1); + if (idx < 0) { + *buffer = '\0'; + } else { + /* make dives that are not in trips stand out */ + dive = get_dive(idx); + if (!DIVE_IN_TRIP(dive)) + snprintf(buffer, sizeof(buffer), "<b>%d</b>", nr); + else + snprintf(buffer, sizeof(buffer), "%d", nr); + } + g_object_set(renderer, "markup", buffer, NULL); +} + +static void weight_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int indx, decimals; + double value; + char buffer[80]; + struct dive *dive; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &indx, -1); + dive = get_dive(indx); + value = get_weight_units(total_weight(dive), &decimals, NULL); + if (value == 0.0) + *buffer = '\0'; + else + snprintf(buffer, sizeof(buffer), "%.*f", decimals, value); + + g_object_set(renderer, "text", buffer, NULL); +} + +static gint nitrox_sort_func(GtkTreeModel *model, + GtkTreeIter *iter_a, + GtkTreeIter *iter_b, + gpointer user_data) +{ + int index_a, index_b; + struct dive *a, *b; + int a_o2, b_o2; + int a_he, b_he; + int a_o2low, b_o2low; + + gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1); + gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1); + a = get_dive(index_a); + b = get_dive(index_b); + get_dive_gas(a, &a_o2, &a_he, &a_o2low); + get_dive_gas(b, &b_o2, &b_he, &b_o2low); + + /* Sort by Helium first, O2 second */ + if (a_he == b_he) { + if (a_o2 == b_o2) + return a_o2low - b_o2low; + return a_o2 - b_o2; + } + return a_he - b_he; +} + +#define UTF8_ELLIPSIS "\xE2\x80\xA6" + +static void nitrox_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int idx, o2, he, o2low; + char buffer[80]; + struct dive *dive; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + if (idx < 0) { + *buffer = '\0'; + goto exit; + } + dive = get_dive(idx); + get_dive_gas(dive, &o2, &he, &o2low); + o2 = (o2 + 5) / 10; + he = (he + 5) / 10; + o2low = (o2low + 5) / 10; + + if (he) + snprintf(buffer, sizeof(buffer), "%d/%d", o2, he); + else if (o2) + if (o2 == o2low) + snprintf(buffer, sizeof(buffer), "%d", o2); + else + snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2); + else + strcpy(buffer, _("air")); +exit: + g_object_set(renderer, "text", buffer, NULL); +} + +/* Render the SAC data (integer value of "ml / min") */ +static void sac_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int value, idx; + const char *fmt; + char buffer[16]; + double sac; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1); + + if (idx < 0 || !value) { + *buffer = '\0'; + goto exit; + } + + sac = value / 1000.0; + switch (prefs.units.volume) { + case LITER: + fmt = "%4.1f"; + break; + case CUFT: + fmt = "%4.2f"; + sac = ml_to_cuft(sac * 1000); + break; + } + snprintf(buffer, sizeof(buffer), fmt, sac); +exit: + g_object_set(renderer, "text", buffer, NULL); +} + +/* Render the OTU data (integer value of "OTU") */ +static void otu_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int value, idx; + char buffer[16]; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1); + + if (idx < 0 || !value) + *buffer = '\0'; + else + snprintf(buffer, sizeof(buffer), "%d", value); + + g_object_set(renderer, "text", buffer, NULL); +} + +/* Render the CNS data (in full %) */ +static void cns_data_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + int value, idx; + char buffer[16]; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_MAXCNS, &value, -1); + + if (idx < 0 || !value) + *buffer = '\0'; + else + snprintf(buffer, sizeof(buffer), "%d%%", value); + + g_object_set(renderer, "text", buffer, NULL); +} + +GdkPixbuf *get_gps_icon(void) +{ + return gdk_pixbuf_from_pixdata(&satellite_pixbuf, TRUE, NULL); +} + +static GdkPixbuf *get_gps_icon_for_dive(struct dive *dive) +{ + if (dive_has_gps_location(dive)) + return get_gps_icon(); + else + return NULL; +} + +/* + * Set up anything that could have changed due to editing + * of dive information; we need to do this for both models, + * so we simply call set_one_dive again with the non-current model + */ +/* forward declaration for recursion */ +static gboolean set_one_dive(GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data); + +static void fill_one_dive(struct dive *dive, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + char *location, *cylinder, *suit; + GtkTreeModel *othermodel; + GdkPixbuf *icon; + + get_cylinder(dive, &cylinder); + get_location(dive, &location); + get_suit(dive, &suit); + icon = get_gps_icon_for_dive(dive); + gtk_tree_store_set(GTK_TREE_STORE(model), iter, + DIVE_NR, dive->number, + DIVE_LOCATION, location, + DIVE_LOC_ICON, icon, + DIVE_CYLINDER, cylinder, + DIVE_RATING, dive->rating, + DIVE_SAC, dive->sac, + DIVE_OTU, dive->otu, + DIVE_MAXCNS, dive->maxcns, + DIVE_TOTALWEIGHT, total_weight(dive), + DIVE_SUIT, suit, + -1); + + if (icon) + g_object_unref(icon); + free(location); + free(cylinder); + free(suit); + + if (model == TREEMODEL(dive_list)) + othermodel = LISTMODEL(dive_list); + else + othermodel = TREEMODEL(dive_list); + if (othermodel != MODEL(dive_list)) + /* recursive call */ + gtk_tree_model_foreach(othermodel, set_one_dive, dive); +} + +static gboolean set_one_dive(GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + int idx; + struct dive *dive; + + /* Get the dive number */ + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + if (idx < 0) + return FALSE; + dive = get_dive(idx); + if (!dive) + return TRUE; + if (data && dive != data) + return FALSE; + + fill_one_dive(dive, model, iter); + return dive == data; +} + +void flush_divelist(struct dive *dive) +{ + GtkTreeModel *model = MODEL(dive_list); + + gtk_tree_model_foreach(model, set_one_dive, dive); +} + +void set_divelist_font(const char *font) +{ + PangoFontDescription *font_desc = pango_font_description_from_string(font); + gtk_widget_modify_font(dive_list.tree_view, font_desc); + pango_font_description_free(font_desc); +} + +void update_dive_list_units(void) +{ + const char *unit; + GtkTreeModel *model = MODEL(dive_list); + + (void) get_depth_units(0, NULL, &unit); + gtk_tree_view_column_set_title(dive_list.depth, unit); + + (void) get_temp_units(0, &unit); + gtk_tree_view_column_set_title(dive_list.temperature, unit); + + (void) get_weight_units(0, NULL, &unit); + gtk_tree_view_column_set_title(dive_list.totalweight, unit); + + gtk_tree_model_foreach(model, set_one_dive, NULL); +} + +void update_dive_list_col_visibility(void) +{ + gtk_tree_view_column_set_visible(dive_list.cylinder, prefs.visible_cols.cylinder); + gtk_tree_view_column_set_visible(dive_list.temperature, prefs.visible_cols.temperature); + gtk_tree_view_column_set_visible(dive_list.totalweight, prefs.visible_cols.totalweight); + gtk_tree_view_column_set_visible(dive_list.suit, prefs.visible_cols.suit); + gtk_tree_view_column_set_visible(dive_list.nitrox, prefs.visible_cols.nitrox); + gtk_tree_view_column_set_visible(dive_list.sac, prefs.visible_cols.sac); + gtk_tree_view_column_set_visible(dive_list.otu, prefs.visible_cols.otu); + gtk_tree_view_column_set_visible(dive_list.maxcns, prefs.visible_cols.maxcns); + return; +} + +static void clear_trip_indexes(void) +{ + dive_trip_t *trip; + + for (trip = dive_trip_list; trip != NULL; trip = trip->next) + trip->index = 0; +} + +/* Select the iter asked for, and set the keyboard focus on it */ +static void go_to_iter(GtkTreeSelection *selection, GtkTreeIter *iter); +static void fill_dive_list(void) +{ + int i, trip_index = 0; + GtkTreeIter iter, parent_iter, lookup, *parent_ptr = NULL; + GtkTreeStore *liststore, *treestore; + GdkPixbuf *icon; + + /* Do we need to create any dive groups automatically? */ + if (autogroup) + autogroup_dives(); + + treestore = TREESTORE(dive_list); + liststore = LISTSTORE(dive_list); + + clear_trip_indexes(); + + i = dive_table.nr; + while (--i >= 0) { + struct dive *dive = get_dive(i); + dive_trip_t *trip = dive->divetrip; + + if (!trip) { + parent_ptr = NULL; + } else if (!trip->index) { + trip->index = ++trip_index; + + /* Create new trip entry */ + gtk_tree_store_append(treestore, &parent_iter, NULL); + parent_ptr = &parent_iter; + + /* a duration of 0 (and negative index) identifies a group */ + gtk_tree_store_set(treestore, parent_ptr, + DIVE_INDEX, -trip_index, + DIVE_DATE, trip->when, + DIVE_LOCATION, trip->location, + DIVE_DURATION, 0, + -1); + } else { + int idx, ok; + GtkTreeModel *model = TREEMODEL(dive_list); + + parent_ptr = NULL; + ok = gtk_tree_model_get_iter_first(model, &lookup); + while (ok) { + gtk_tree_model_get(model, &lookup, DIVE_INDEX, &idx, -1); + if (idx == -trip->index) { + parent_ptr = &lookup; + break; + } + ok = gtk_tree_model_iter_next(model, &lookup); + } + } + + /* store dive */ + update_cylinder_related_info(dive); + gtk_tree_store_append(treestore, &iter, parent_ptr); + icon = get_gps_icon_for_dive(dive); + gtk_tree_store_set(treestore, &iter, + DIVE_INDEX, i, + DIVE_NR, dive->number, + DIVE_DATE, dive->when, + DIVE_DEPTH, dive->maxdepth, + DIVE_DURATION, dive->duration.seconds, + DIVE_LOCATION, dive->location, + DIVE_LOC_ICON, icon, + DIVE_RATING, dive->rating, + DIVE_TEMPERATURE, dive->watertemp.mkelvin, + DIVE_SAC, 0, + -1); + if (icon) + g_object_unref(icon); + gtk_tree_store_append(liststore, &iter, NULL); + gtk_tree_store_set(liststore, &iter, + DIVE_INDEX, i, + DIVE_NR, dive->number, + DIVE_DATE, dive->when, + DIVE_DEPTH, dive->maxdepth, + DIVE_DURATION, dive->duration.seconds, + DIVE_LOCATION, dive->location, + DIVE_LOC_ICON, icon, + DIVE_RATING, dive->rating, + DIVE_TEMPERATURE, dive->watertemp.mkelvin, + DIVE_TOTALWEIGHT, 0, + DIVE_SUIT, dive->suit, + DIVE_SAC, 0, + -1); + } + + update_dive_list_units(); + if (amount_selected == 0 && gtk_tree_model_get_iter_first(MODEL(dive_list), &iter)) { + GtkTreeSelection *selection; + + /* select the last dive (and make sure it's an actual dive that is selected) */ + gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &selected_dive, -1); + first_leaf(MODEL(dive_list), &iter, &selected_dive); + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + go_to_iter(selection, &iter); + } +} + +static void restore_tree_state(void); + +void dive_list_update_dives(void) +{ + dive_table.preexisting = dive_table.nr; + gtk_tree_store_clear(TREESTORE(dive_list)); + gtk_tree_store_clear(LISTSTORE(dive_list)); + fill_dive_list(); + restore_tree_state(); + repaint_dive(); +} + +static gint dive_nr_sort(GtkTreeModel *model, + GtkTreeIter *iter_a, + GtkTreeIter *iter_b, + gpointer user_data) +{ + int idx_a, idx_b; + timestamp_t when_a, when_b; + struct dive *a, *b; + dive_trip_t *tripa = NULL, *tripb = NULL; + + gtk_tree_model_get(model, iter_a, DIVE_INDEX, &idx_a, DIVE_DATE, &when_a, -1); + gtk_tree_model_get(model, iter_b, DIVE_INDEX, &idx_b, DIVE_DATE, &when_b, -1); + + if (idx_a < 0) { + a = NULL; + tripa = find_trip_by_idx(idx_a); + } else { + a = get_dive(idx_a); + if (a) + tripa = a->divetrip; + } + + if (idx_b < 0) { + b = NULL; + tripb = find_trip_by_idx(idx_b); + } else { + b = get_dive(idx_b); + if (b) + tripb = b->divetrip; + } + + /* + * Compare dive dates within the same trip (or when there + * are no trips involved at all). But if we have two + * different trips use the trip dates for comparison + */ + if (tripa != tripb) { + if (tripa) + when_a = tripa->when; + if (tripb) + when_b = tripb->when; + } + return when_a - when_b; +} + + +static struct divelist_column { + const char *header; + data_func_t data; + sort_func_t sort; + unsigned int flags; + int *visible; +} dl_column[] = { + [DIVE_NR] = { "#", nr_data_func, dive_nr_sort, ALIGN_RIGHT }, + [DIVE_DATE] = { N_("Date"), date_data_func, NULL, ALIGN_LEFT }, + [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT }, + [DIVE_DEPTH] = { N_("ft"), depth_data_func, NULL, ALIGN_RIGHT }, + [DIVE_DURATION] = { N_("min"), duration_data_func, NULL, ALIGN_RIGHT }, + [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &prefs.visible_cols.temperature }, + [DIVE_TOTALWEIGHT] = { N_("lbs"), weight_data_func, NULL, ALIGN_RIGHT, &prefs.visible_cols.totalweight }, + [DIVE_SUIT] = { N_("Suit"), NULL, NULL, ALIGN_LEFT, &prefs.visible_cols.suit }, + [DIVE_CYLINDER] = { N_("Cyl"), NULL, NULL, 0, &prefs.visible_cols.cylinder }, + [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &prefs.visible_cols.nitrox }, + [DIVE_SAC] = { N_("SAC"), sac_data_func, NULL, 0, &prefs.visible_cols.sac }, + [DIVE_OTU] = { N_("OTU"), otu_data_func, NULL, 0, &prefs.visible_cols.otu }, + [DIVE_MAXCNS] = { N_("maxCNS"), cns_data_func, NULL, 0, &prefs.visible_cols.maxcns }, + [DIVE_LOCATION] = { N_("Location"), NULL, NULL, ALIGN_LEFT }, +}; + + +static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_column *col) +{ + int index = col - &dl_column[0]; + const char *title = _(col->header); + data_func_t data_func = col->data; + sort_func_t sort_func = col->sort; + unsigned int flags = col->flags; + int *visible = col->visible; + GtkWidget *tree_view = dl->tree_view; + GtkTreeStore *treemodel = dl->treemodel; + GtkTreeStore *listmodel = dl->listmodel; + GtkTreeViewColumn *ret; + + if (visible && !*visible) + flags |= INVISIBLE; + ret = tree_view_column(tree_view, index, title, data_func, flags); + if (sort_func) { + /* the sort functions are needed in the corresponding models */ + if (index == DIVE_NR) + gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treemodel), index, sort_func, NULL, NULL); + else + gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(listmodel), index, sort_func, NULL, NULL); + } + return ret; +} + +/* + * This is some crazy crap. The only way to get default focus seems + * to be to grab focus as the widget is being shown the first time. + */ +static void realize_cb(GtkWidget *tree_view, gpointer userdata) +{ + gtk_widget_grab_focus(tree_view); +} + +/* + * Double-clicking on a group entry will expand a collapsed group + * and vice versa. + */ +static void collapse_expand(GtkTreeView *tree_view, GtkTreePath *path) +{ + if (!gtk_tree_view_row_expanded(tree_view, path)) + gtk_tree_view_expand_row(tree_view, path, FALSE); + else + gtk_tree_view_collapse_row(tree_view, path); + +} + +/* Double-click on a dive list */ +static void row_activated_cb(GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + gpointer userdata) +{ + int index; + GtkTreeIter iter; + + if (!gtk_tree_model_get_iter(MODEL(dive_list), &iter, path)) + return; + + gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &index, -1); + /* a negative index is special for the "group by date" entries */ + if (index < 0) { + collapse_expand(tree_view, path); + return; + } + edit_dive_info(get_dive(index), FALSE); +} + +void add_dive_cb(GtkWidget *menuitem, gpointer data) +{ + struct dive *dive; + + dive = alloc_dive(); + if (add_new_dive(dive)) { + record_dive(dive); + report_dives(TRUE, FALSE); + return; + } + free(dive); +} + +static void edit_trip_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + int idx; + GtkTreeIter iter; + dive_trip_t *dive_trip; + + gtk_tree_model_get_iter(MODEL(dive_list), &iter, path); + gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); + dive_trip = find_trip_by_idx(idx); + if (edit_trip(dive_trip)) + gtk_tree_store_set(STORE(dive_list), &iter, DIVE_LOCATION, dive_trip->location, -1); +} + +static void edit_selected_dives_cb(GtkWidget *menuitem, gpointer data) +{ + edit_multi_dive_info(NULL); +} + +static void edit_dive_from_path_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + struct dive *dive = dive_from_path(path); + + edit_multi_dive_info(dive); +} + +static void edit_dive_when_cb(GtkWidget *menuitem, struct dive *dive) +{ + GtkWidget *dialog, *cal, *h, *m, *timehbox; + timestamp_t when; + + guint yval, mval, dval; + int success; + struct tm tm; + + if (!dive) + return; + + when = dive->when; + utc_mkdate(when, &tm); + dialog = create_date_time_widget(&tm, &cal, &h, &m, &timehbox); + + gtk_widget_show_all(dialog); + success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; + if (!success) { + gtk_widget_destroy(dialog); + return; + } + memset(&tm, 0, sizeof(tm)); + gtk_calendar_get_date(GTK_CALENDAR(cal), &yval, &mval, &dval); + tm.tm_year = yval; + tm.tm_mon = mval; + tm.tm_mday = dval; + tm.tm_hour = gtk_spin_button_get_value(GTK_SPIN_BUTTON(h)); + tm.tm_min = gtk_spin_button_get_value(GTK_SPIN_BUTTON(m)); + + gtk_widget_destroy(dialog); + when = utc_mktime(&tm); + if (dive->when != when) { + /* if this is the only dive in the trip, just change the trip time */ + if (dive->divetrip && dive->divetrip->nrdives == 1) + dive->divetrip->when = when; + /* if this is suddenly before the start of the trip, remove it from the trip */ + else if (dive->divetrip && dive->divetrip->when > when) + remove_dive_from_trip(dive); + else if (find_matching_trip(when) != dive->divetrip) + remove_dive_from_trip(dive); + dive->when = when; + mark_divelist_changed(TRUE); + report_dives(FALSE, FALSE); + dive_list_update_dives(); + } +} + +#if HAVE_OSM_GPS_MAP +static void show_gps_location_cb(GtkWidget *menuitem, struct dive *dive) +{ + show_gps_location(dive, NULL); +} +#endif + +gboolean icon_click_cb(GtkWidget *w, GdkEventButton *event, gpointer data) +{ +#if HAVE_OSM_GPS_MAP + GtkTreePath *path = NULL; + GtkTreeIter iter; + GtkTreeViewColumn *col; + int idx; + struct dive *dive; + + /* left click ? */ + if (event->button == 1 && + gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(dive_list.tree_view), event->x, event->y, &path, &col, NULL, NULL)) { + /* is it the icon column ? (we passed the correct column in when registering the callback) */ + if (col == data) { + gtk_tree_model_get_iter(MODEL(dive_list), &iter, path); + gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); + dive = get_dive(idx); + if (dive && dive_has_gps_location(dive)) + show_gps_location(dive, NULL); + } + if (path) + gtk_tree_path_free(path); + } +#endif + /* keep processing the click */ + return FALSE; +} + +static void save_as_cb(GtkWidget *menuitem, struct dive *dive) +{ + GtkWidget *dialog; + char *filename = NULL; + + dialog = gtk_file_chooser_dialog_new(_("Save File As"), + GTK_WINDOW(main_window), + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + } + gtk_widget_destroy(dialog); + + if (filename){ + save_dives_logic(filename, TRUE); + g_free(filename); + } +} + +static void expand_all_cb(GtkWidget *menuitem, GtkTreeView *tree_view) +{ + gtk_tree_view_expand_all(tree_view); +} + +static void collapse_all_cb(GtkWidget *menuitem, GtkTreeView *tree_view) +{ + gtk_tree_view_collapse_all(tree_view); +} + +/* Move a top-level dive into the trip above it */ +static void merge_dive_into_trip_above_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + int idx; + struct dive *dive; + dive_trip_t *trip; + + idx = get_path_index(path); + dive = get_dive(idx); + + /* Needs to be a dive, and at the top level */ + if (!dive || dive->divetrip) + return; + + /* Find the "trip above". */ + for (;;) { + if (!gtk_tree_path_prev(path)) + return; + idx = get_path_index(path); + trip = find_trip_by_idx(idx); + if (trip) + break; + } + + add_dive_to_trip(dive, trip); + if (dive->selected) { + for_each_dive(idx, dive) { + if (!dive->selected) + continue; + add_dive_to_trip(dive, trip); + } + } + + trip->expanded = 1; + dive_list_update_dives(); + mark_divelist_changed(TRUE); +} + +static void insert_trip_before_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + int idx; + struct dive *dive; + dive_trip_t *trip; + + idx = get_path_index(path); + dive = get_dive(idx); + if (!dive) + return; + trip = create_and_hookup_trip_from_dive(dive); + if (dive->selected) { + for_each_dive(idx, dive) { + if (!dive->selected) + continue; + add_dive_to_trip(dive, trip); + } + } + trip->expanded = 1; + dive_list_update_dives(); + mark_divelist_changed(TRUE); +} + +static void remove_from_trip_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + struct dive *dive; + int idx; + + idx = get_path_index(path); + if (idx < 0) + return; + dive = get_dive(idx); + + if (dive->selected) { + /* remove all the selected dives */ + for_each_dive(idx, dive) { + if (!dive->selected) + continue; + remove_dive_from_trip(dive); + } + } else { + /* just remove the dive the mouse pointer is on */ + remove_dive_from_trip(dive); + } + dive_list_update_dives(); + mark_divelist_changed(TRUE); +} + +static void remove_trip(GtkTreePath *trippath) +{ + int idx, i; + dive_trip_t *trip; + struct dive *dive; + + idx = get_path_index(trippath); + trip = find_trip_by_idx(idx); + if (!trip) + return; + + for_each_dive(i, dive) { + if (dive->divetrip != trip) + continue; + remove_dive_from_trip(dive); + } + + dive_list_update_dives(); + +#ifdef DEBUG_TRIP + dump_trip_list(); +#endif +} + +static void remove_trip_cb(GtkWidget *menuitem, GtkTreePath *trippath) +{ + int success; + GtkWidget *dialog; + + dialog = gtk_dialog_new_with_buttons(_("Remove Trip"), + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + + gtk_widget_show_all(dialog); + success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; + gtk_widget_destroy(dialog); + if (!success) + return; + + remove_trip(trippath); + mark_divelist_changed(TRUE); +} + +static void merge_trips_cb(GtkWidget *menuitem, GtkTreePath *trippath) +{ + GtkTreePath *prevpath; + GtkTreeIter thistripiter, prevtripiter; + GtkTreeModel *tm = MODEL(dive_list); + dive_trip_t *thistrip, *prevtrip; + timestamp_t when; + + /* this only gets called when we are on a trip and there is another trip right before */ + prevpath = gtk_tree_path_copy(trippath); + gtk_tree_path_prev(prevpath); + gtk_tree_model_get_iter(tm, &thistripiter, trippath); + gtk_tree_model_get(tm, &thistripiter, DIVE_DATE, &when, -1); + thistrip = find_matching_trip(when); + gtk_tree_model_get_iter(tm, &prevtripiter, prevpath); + gtk_tree_model_get(tm, &prevtripiter, DIVE_DATE, &when, -1); + prevtrip = find_matching_trip(when); + /* move dives from trip */ + assert(thistrip != prevtrip); + while (thistrip->dives) + add_dive_to_trip(thistrip->dives, prevtrip); + dive_list_update_dives(); + mark_divelist_changed(TRUE); +} + +static gboolean restore_node_state(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + int idx; + struct dive *dive; + dive_trip_t *trip; + GtkTreeView *tree_view = GTK_TREE_VIEW(dive_list.tree_view); + GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view); + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + if (idx < 0) { + trip = find_trip_by_idx(idx); + if (trip && trip->expanded) + gtk_tree_view_expand_row(tree_view, path, FALSE); + if (trip && trip->selected) + gtk_tree_selection_select_iter(selection, iter); + } else { + dive = get_dive(idx); + if (dive && dive->selected) + gtk_tree_selection_select_iter(selection, iter); + } + /* continue foreach */ + return FALSE; +} + +/* restore expanded and selected state */ +static void restore_tree_state(void) +{ + gtk_tree_model_foreach(MODEL(dive_list), restore_node_state, NULL); +} + +/* called when multiple dives are selected and one of these is right-clicked for delete */ +static void delete_selected_dives_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + int i; + struct dive *dive; + int success; + GtkWidget *dialog; + char *dialog_title; + + if (!amount_selected) + return; + if (amount_selected == 1) + dialog_title = _("Delete dive"); + else + dialog_title = _("Delete dives"); + + dialog = gtk_dialog_new_with_buttons(dialog_title, + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + + gtk_widget_show_all(dialog); + success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; + gtk_widget_destroy(dialog); + if (!success) + return; + + /* walk the dive list in chronological order */ + for (i = 0; i < dive_table.nr; i++) { + dive = get_dive(i); + if (!dive) + continue; + if (!dive->selected) + continue; + /* now remove the dive from the table and free it. also move the iterator back, + * so that we don't skip a dive */ + delete_single_dive(i); + i--; + } + dive_list_update_dives(); + + /* if no dives are selected at this point clear the display widgets */ + if (!amount_selected) { + selected_dive = 0; + process_selected_dives(); + clear_stats_widgets(); + clear_equipment_widgets(); + show_dive_info(NULL); + } + mark_divelist_changed(TRUE); +} + +/* this gets called with path pointing to a dive, either in the top level + * or as part of a trip */ +static void delete_dive_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + int idx; + GtkTreeIter iter; + int success; + GtkWidget *dialog; + + dialog = gtk_dialog_new_with_buttons(_("Delete dive"), + GTK_WINDOW(main_window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + NULL); + + gtk_widget_show_all(dialog); + success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT; + gtk_widget_destroy(dialog); + if (!success) + return; + + if (!gtk_tree_model_get_iter(MODEL(dive_list), &iter, path)) + return; + gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); + delete_single_dive(idx); + dive_list_update_dives(); + mark_divelist_changed(TRUE); +} + +#if defined(LIBZIP) && defined(XSLT) +static void export_selected_dives_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + int i; + struct dive *dive; + FILE *f; + char filename[PATH_MAX], *tempfile; + size_t streamsize; + char *membuf; + xmlDoc *doc; + xsltStylesheetPtr xslt = NULL; + xmlDoc *transformed; + struct zip_source *s[dive_table.nr]; + struct zip *zip; + const gchar *tmpdir = g_get_tmp_dir(); + + /* + * Creating a temporary .DLD file to be eventually uploaded to + * divelogs.de. I wonder if this could be done in-memory. + */ + tempfile = g_build_filename(tmpdir, "export.DLD-XXXXXX", NULL); + int fd = g_mkstemp(tempfile); + if (fd != -1) + close(fd); + zip = zip_open(tempfile, ZIP_CREATE, NULL); + + if (!zip) + return; + + if (!amount_selected) + return; + + /* walk the dive list in chronological order */ + for (i = 0; i < dive_table.nr; i++) { + + dive = get_dive(i); + if (!dive) + continue; + if (!dive->selected) + continue; + + f = tmpfile(); + if (!f) + return; + save_dive(f, dive); + fseek(f, 0, SEEK_END); + streamsize = ftell(f); + rewind(f); + membuf = malloc(streamsize + 1); + if (!membuf || !fread(membuf, streamsize, 1, f)) + return; + membuf[streamsize] = 0; + fclose(f); + + /* + * Parse the memory buffer into XML document and + * transform it to divelogs.de format, finally dumping + * the XML into a character buffer. + */ + doc = xmlReadMemory(membuf, strlen(membuf), "divelog", NULL, 0); + if (!doc) + continue; + + free((void *)membuf); + xslt = get_stylesheet("divelogs-export.xslt"); + if (!xslt) + return; + transformed = xsltApplyStylesheet(xslt, doc, NULL); + xsltFreeStylesheet(xslt); + xmlDocDumpMemory(transformed, (xmlChar **) &membuf, (int *)&streamsize); + xmlFreeDoc(doc); + xmlFreeDoc(transformed); + + /* + * Save the XML document into a zip file. + */ + snprintf(filename, PATH_MAX, "%d.xml", i + 1); + s[i] = zip_source_buffer(zip, membuf, streamsize, 1); + if (s[i]) { + int64_t ret = zip_add(zip, filename, s[i]); + if (ret == -1) + fprintf(stderr, "failed to include dive %d\n", i); + } + } + zip_close(zip); + if (divelogde_upload(tempfile)) + g_unlink(tempfile); + else + fprintf(stderr,"upload of %s failed\n", tempfile); + g_free(tempfile); +} +#endif + +#if defined(XSLT) +static void export_dives_uddf(const gboolean selected) +{ + FILE *f; + char *filename = NULL; + size_t streamsize; + char *membuf; + xmlDoc *doc; + xsltStylesheetPtr xslt = NULL; + xmlDoc *transformed; + GtkWidget *dialog; + + dialog = gtk_file_chooser_dialog_new(_("Export As UDDF File"), + GTK_WINDOW(main_window), + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); + } + gtk_widget_destroy(dialog); + + if (!filename) + return; + + /* Save XML to file and convert it into a memory buffer */ + save_dives_logic(filename, selected); + f = fopen(filename, "r"); + fseek(f, 0, SEEK_END); + streamsize = ftell(f); + rewind(f); + + membuf = malloc(streamsize + 1); + if (!membuf || !fread(membuf, streamsize, 1, f)) { + fprintf(stderr, "Failed to read memory buffer\n"); + return; + } + membuf[streamsize] = 0; + fclose(f); + g_unlink(filename); + + /* + * Parse the memory buffer into XML document and + * transform it to UDDF format, finally dumping + * the XML into a character buffer. + */ + doc = xmlReadMemory(membuf, strlen(membuf), "divelog", NULL, 0); + if (!doc) { + fprintf(stderr, "Failed to read XML memory\n"); + return; + } + free((void *)membuf); + + /* Convert to UDDF format */ + xslt = get_stylesheet("uddf-export.xslt"); + if (!xslt) { + fprintf(stderr, "Failed to open UDDF conversion stylesheet\n"); + return; + } + transformed = xsltApplyStylesheet(xslt, doc, NULL); + xsltFreeStylesheet(xslt); + xmlFreeDoc(doc); + + /* Write the transformed XML to file */ + f = g_fopen(filename, "w"); + xmlDocFormatDump(f, transformed, 1); + xmlFreeDoc(transformed); + + fclose(f); + g_free(filename); +} + +static void export_selected_dives_uddf_cb(GtkWidget *menuitem, GtkTreePath *path) +{ + export_dives_uddf(TRUE); +} + +void export_all_dives_uddf_cb() +{ + export_dives_uddf(FALSE); +} +#endif + +static void merge_dives_cb(GtkWidget *menuitem, void *unused) +{ + int i; + struct dive *dive; + + for_each_dive(i, dive) { + if (dive->selected) { + merge_dive_index(i, dive); + return; + } + } +} + +/* Called if there are exactly two selected dives and the dive at idx is one of them */ +static void add_dive_merge_label(int idx, GtkMenuShell *menu) +{ + struct dive *a, *b; + GtkWidget *menuitem; + + /* The other selected dive must be next to it.. */ + a = get_dive(idx); + b = get_dive(idx+1); + if (!b || !b->selected) { + b = a; + a = get_dive(idx-1); + if (!a || !a->selected) + return; + } + + /* .. and they had better be in the same dive trip */ + if (a->divetrip != b->divetrip) + return; + + /* .. and if the surface interval is excessive, you must be kidding us */ + if (b->when > a->when + a->duration.seconds + 30*60) + return; + + /* If so, we can add a "merge dive" menu entry */ + menuitem = gtk_menu_item_new_with_label(_("Merge dives")); + g_signal_connect(menuitem, "activate", G_CALLBACK(merge_dives_cb), NULL); + gtk_menu_shell_append(menu, menuitem); +} + +static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button, GdkEventButton *event) +{ + GtkWidget *menu, *menuitem, *image; + char editplurallabel[] = N_("Edit dives"); + char editsinglelabel[] = N_("Edit dive"); + char *editlabel; + char deleteplurallabel[] = N_("Delete dives"); + char deletesinglelabel[] = N_("Delete dive"); + char *deletelabel; +#if defined(XSLT) + char exportuddflabel[] = N_("Export dive(s) to UDDF"); +#endif +#if defined(LIBZIP) && defined(XSLT) + char exportlabel[] = N_("Export dive(s)"); +#endif + GtkTreePath *path, *prevpath, *nextpath; + GtkTreeIter iter, previter, nextiter; + int idx, previdx, nextidx; + struct dive *dive; + + if (!event || !gtk_tree_view_get_path_at_pos(tree_view, event->x, event->y, &path, NULL, NULL, NULL)) + return; + gtk_tree_model_get_iter(MODEL(dive_list), &iter, path); + gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1); + + menu = gtk_menu_new(); + menuitem = gtk_image_menu_item_new_with_label(_("Add dive")); + image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image); + g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + if (idx < 0) { + /* mouse pointer is on a trip summary entry */ + menuitem = gtk_menu_item_new_with_label(_("Edit Trip Summary")); + g_signal_connect(menuitem, "activate", G_CALLBACK(edit_trip_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + prevpath = gtk_tree_path_copy(path); + if (gtk_tree_path_prev(prevpath) && + gtk_tree_model_get_iter(MODEL(dive_list), &previter, prevpath)) { + gtk_tree_model_get(MODEL(dive_list), &previter, DIVE_INDEX, &previdx, -1); + if (previdx < 0) { + menuitem = gtk_menu_item_new_with_label(_("Merge trip with trip above")); + g_signal_connect(menuitem, "activate", G_CALLBACK(merge_trips_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } + } + nextpath = gtk_tree_path_copy(path); + gtk_tree_path_next(nextpath); + if (gtk_tree_model_get_iter(MODEL(dive_list), &nextiter, nextpath)) { + gtk_tree_model_get(MODEL(dive_list), &nextiter, DIVE_INDEX, &nextidx, -1); + if (nextidx < 0) { + menuitem = gtk_menu_item_new_with_label(_("Merge trip with trip below")); + g_signal_connect(menuitem, "activate", G_CALLBACK(merge_trips_cb), nextpath); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } + } + menuitem = gtk_menu_item_new_with_label(_("Remove Trip")); + g_signal_connect(menuitem, "activate", G_CALLBACK(remove_trip_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } else { + dive = get_dive(idx); + /* if we right click on selected dive(s), edit or delete those */ + if (dive->selected) { + if (amount_selected == 1) { + deletelabel = _(deletesinglelabel); + editlabel = _(editsinglelabel); + menuitem = gtk_menu_item_new_with_label(_("Edit dive date/time")); + g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_when_cb), dive); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } else { + deletelabel = _(deleteplurallabel); + editlabel = _(editplurallabel); + } + menuitem = gtk_menu_item_new_with_label(_("Save as")); + g_signal_connect(menuitem, "activate", G_CALLBACK(save_as_cb), dive); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + menuitem = gtk_menu_item_new_with_label(deletelabel); + g_signal_connect(menuitem, "activate", G_CALLBACK(delete_selected_dives_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + +#if defined(LIBZIP) && defined(XSLT) + menuitem = gtk_menu_item_new_with_label(exportlabel); + g_signal_connect(menuitem, "activate", G_CALLBACK(export_selected_dives_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); +#endif + +#if defined(XSLT) + menuitem = gtk_menu_item_new_with_label(exportuddflabel); + g_signal_connect(menuitem, "activate", G_CALLBACK(export_selected_dives_uddf_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); +#endif + + menuitem = gtk_menu_item_new_with_label(editlabel); + g_signal_connect(menuitem, "activate", G_CALLBACK(edit_selected_dives_cb), NULL); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + /* Two contiguous selected dives? */ + if (amount_selected == 2) + add_dive_merge_label(idx, GTK_MENU_SHELL(menu)); + } else { + menuitem = gtk_menu_item_new_with_label(_("Edit dive date/time")); + g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_when_cb), dive); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + deletelabel = _(deletesinglelabel); + menuitem = gtk_menu_item_new_with_label(deletelabel); + g_signal_connect(menuitem, "activate", G_CALLBACK(delete_dive_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + editlabel = _(editsinglelabel); + menuitem = gtk_menu_item_new_with_label(editlabel); + g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_from_path_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } +#if HAVE_OSM_GPS_MAP + /* Only offer to show on map if it has a location. */ + if (dive_has_gps_location(dive)) { + menuitem = gtk_menu_item_new_with_label(_("Show in map")); + g_signal_connect(menuitem, "activate", G_CALLBACK(show_gps_location_cb), dive); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } +#endif + /* only offer trip editing options when we are displaying the tree model */ + if (dive_list.model == dive_list.treemodel) { + int depth = gtk_tree_path_get_depth(path); + int *indices = gtk_tree_path_get_indices(path); + /* top level dive or child dive that is not the first child */ + if (depth == 1 || indices[1] > 0) { + menuitem = gtk_menu_item_new_with_label(_("Create new trip above")); + g_signal_connect(menuitem, "activate", G_CALLBACK(insert_trip_before_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } + prevpath = gtk_tree_path_copy(path); + /* top level dive with a trip right before it */ + if (depth == 1 && + gtk_tree_path_prev(prevpath) && + gtk_tree_model_get_iter(MODEL(dive_list), &previter, prevpath) && + gtk_tree_model_iter_n_children(model, &previter)) { + menuitem = gtk_menu_item_new_with_label(_("Add to trip above")); + g_signal_connect(menuitem, "activate", G_CALLBACK(merge_dive_into_trip_above_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } + if (DIVE_IN_TRIP(dive)) { + if (dive->selected && amount_selected > 1) + menuitem = gtk_menu_item_new_with_label(_("Remove selected dives from trip")); + else + menuitem = gtk_menu_item_new_with_label(_("Remove dive from trip")); + g_signal_connect(menuitem, "activate", G_CALLBACK(remove_from_trip_cb), path); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + } + } + } + menuitem = gtk_menu_item_new_with_label(_("Expand all")); + g_signal_connect(menuitem, "activate", G_CALLBACK(expand_all_cb), tree_view); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + menuitem = gtk_menu_item_new_with_label(_("Collapse all")); + g_signal_connect(menuitem, "activate", G_CALLBACK(collapse_all_cb), tree_view); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem); + + gtk_widget_show_all(menu); + + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, + button, gtk_get_current_event_time()); +} + +static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata) +{ + popup_divelist_menu(tree_view, MODEL(dive_list), 0, NULL); +} + +static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata) +{ + /* Right-click? Bring up the menu */ + if (event->type == GDK_BUTTON_PRESS && event->button == 3) { + popup_divelist_menu(GTK_TREE_VIEW(treeview), MODEL(dive_list), 3, event); + return TRUE; + } + return FALSE; +} + +/* make sure 'path' is shown in the divelist widget; since set_cursor changes the + * selection to be only 'path' we need to let our selection handling callbacks know + * that we didn't really mean this */ +static void scroll_to_path(GtkTreePath *path) +{ + GtkTreeSelection *selection; + + gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(dive_list.tree_view), path, NULL, FALSE, 0, 0); + in_set_cursor = TRUE; + gtk_tree_view_set_cursor(GTK_TREE_VIEW(dive_list.tree_view), path, NULL, FALSE); + in_set_cursor = FALSE; + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + gtk_tree_model_foreach(MODEL(dive_list), set_selected, selection); + +} + +/* we need to have a temporary copy of the selected dives while + switching model as the selection_cb function keeps getting called + when gtk_tree_selection_select_path is called. We also need to + keep copies of the sort order so we can restore that as well after + switching models. */ +static gboolean second_call = FALSE; +static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, }; +static int lastcol = DIVE_NR; + +/* Check if this dive was selected previously and select it again in the new model; + * This is used after we switch models to maintain consistent selections. + * We always return FALSE to iterate through all dives */ +static gboolean set_selected(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) +{ + GtkTreeSelection *selection = GTK_TREE_SELECTION(data); + int idx, selected; + struct dive *dive; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + if (idx < 0) { + /* this is a trip - restore its state */ + dive_trip_t *trip = find_trip_by_idx(idx); + if (trip && trip->expanded) + gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path); + if (trip && trip->selected) + gtk_tree_selection_select_path(selection, path); + } else { + dive = get_dive(idx); + selected = dive && dive->selected; + if (selected) { + gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path); + gtk_tree_selection_select_path(selection, path); + } + } + return FALSE; +} + +static gboolean scroll_to_this(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) +{ + int idx; + struct dive *dive; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + dive = get_dive(idx); + if (dive == current_dive) { + scroll_to_path(path); + return TRUE; + } + return FALSE; +} + +static void scroll_to_current(GtkTreeModel *model) +{ + if (current_dive) + gtk_tree_model_foreach(model, scroll_to_this, current_dive); +} + +static void update_column_and_order(int colid) +{ + /* Careful: the index into treecolumns is off by one as we don't have a + tree_view column for DIVE_INDEX */ + GtkTreeViewColumn **treecolumns = &dive_list.nr; + + /* this will trigger a second call into sort_column_change_cb, + so make sure we don't start an infinite recursion... */ + second_call = TRUE; + gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, sortorder[colid]); + gtk_tree_view_column_set_sort_order(treecolumns[colid - 1], sortorder[colid]); + second_call = FALSE; + scroll_to_current(GTK_TREE_MODEL(dive_list.model)); +} + +/* If the sort column is nr (default), show the tree model. + For every other sort column only show the list model. + If the model changed, inform the new model of the chosen sort column and make + sure the same dives are still selected. + + The challenge with this function is that once we change the model + we also need to change the sort column again (as it was changed in + the other model) and that causes this function to be called + recursively - so we need to catch that. +*/ +static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data) +{ + int colid; + GtkSortType order; + GtkTreeStore *currentmodel = dive_list.model; + + gtk_widget_grab_focus(dive_list.tree_view); + if (second_call) + return; + + gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order); + if (colid == lastcol) { + /* we just changed sort order */ + sortorder[colid] = order; + return; + } else { + lastcol = colid; + } + if (colid == DIVE_NR) + dive_list.model = dive_list.treemodel; + else + dive_list.model = dive_list.listmodel; + if (dive_list.model != currentmodel) { + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + + gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), MODEL(dive_list)); + update_column_and_order(colid); + gtk_tree_model_foreach(MODEL(dive_list), set_selected, selection); + } else { + if (order != sortorder[colid]) { + update_column_and_order(colid); + } + } +} + +static gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model, + GtkTreePath *path, gboolean was_selected, gpointer userdata) +{ + int idx; + GtkTreeIter iter; + + if (!was_selected || in_set_cursor) + return TRUE; + gtk_tree_model_get_iter(model, &iter, path); + gtk_tree_model_get(model, &iter, DIVE_INDEX, &idx, -1); + if (idx < 0) { + int i; + struct dive *dive; + dive_trip_t *trip = find_trip_by_idx(idx); + if (!trip) + return TRUE; + + trip->selected = 0; + /* If this is expanded, let the gtk selection happen for each dive under it */ + if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), path)) + return TRUE; + /* Otherwise, consider each dive under it deselected */ + for_each_dive(i, dive) { + if (dive->divetrip == trip) + deselect_dive(i); + } + } else { + deselect_dive(idx); + } + return TRUE; +} + +/* This gets called for each selected entry after a selection has changed */ +static void entry_selected(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + int idx; + + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + if (idx < 0) { + int i; + struct dive *dive; + dive_trip_t *trip = find_trip_by_idx(idx); + + if (!trip) + return; + trip->selected = 1; + + /* If this is expanded, let the gtk selection happen for each dive under it */ + if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), path)) { + trip->fixup = 1; + return; + } + + /* Otherwise, consider each dive under it selected */ + for_each_dive(i, dive) { + if (dive->divetrip == trip) + select_dive(i); + } + trip->fixup = 0; + } else { + select_dive(idx); + } +} + +static void update_gtk_selection(GtkTreeSelection *selection, GtkTreeModel *model) +{ + GtkTreeIter iter; + + if (!gtk_tree_model_get_iter_first(model, &iter)) + return; + do { + GtkTreeIter child; + + if (!gtk_tree_model_iter_children(model, &child, &iter)) + continue; + + do { + int idx; + struct dive *dive; + dive_trip_t *trip; + + gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1); + dive = get_dive(idx); + if (!dive || !dive->selected) + break; + trip = dive->divetrip; + if (!trip) + break; + gtk_tree_selection_select_iter(selection, &child); + } while (gtk_tree_model_iter_next(model, &child)); + } while (gtk_tree_model_iter_next(model, &iter)); +} + +/* this is called when gtk thinks that the selection has changed */ +static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model) +{ + int i, fixup; + struct dive *dive; + + gtk_tree_selection_selected_foreach(selection, entry_selected, model); + + /* + * Go through all the dives, if there is a trip that is selected but no + * dives under it are selected, force-select all the dives + */ + + /* First, clear "fixup" for any trip that has selected dives */ + for_each_dive(i, dive) { + dive_trip_t *trip = dive->divetrip; + if (!trip || !trip->fixup) + continue; + if (dive->selected || !trip->selected) + trip->fixup = 0; + } + + /* + * Ok, not fixup is only set for trips that are selected + * but have no selected dives in them. Select all dives + * for such trips. + */ + fixup = 0; + for_each_dive(i, dive) { + dive_trip_t *trip = dive->divetrip; + if (!trip || !trip->fixup) + continue; + fixup = 1; + select_dive(i); + } + + /* + * Ok, we did a forced selection of dives, now we need to update the gtk + * view of what is selected too.. + */ + if (fixup) + update_gtk_selection(selection, model); + +#if DEBUG_SELECTION_TRACKING + dump_selection(); +#endif + + process_selected_dives(); + repaint_dive(); +} + +GtkWidget *dive_list_create(void) +{ + GtkTreeSelection *selection; + + dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS, + G_TYPE_INT, /* index */ + G_TYPE_INT, /* nr */ + G_TYPE_INT64, /* Date */ + G_TYPE_INT, /* Star rating */ + G_TYPE_INT, /* Depth */ + G_TYPE_INT, /* Duration */ + G_TYPE_INT, /* Temperature */ + G_TYPE_INT, /* Total weight */ + G_TYPE_STRING, /* Suit */ + G_TYPE_STRING, /* Cylinder */ + G_TYPE_INT, /* Nitrox */ + G_TYPE_INT, /* SAC */ + G_TYPE_INT, /* OTU */ + G_TYPE_INT, /* MAXCNS */ + G_TYPE_STRING, /* Location */ + GDK_TYPE_PIXBUF /* GPS icon */ + ); + dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS, + G_TYPE_INT, /* index */ + G_TYPE_INT, /* nr */ + G_TYPE_INT64, /* Date */ + G_TYPE_INT, /* Star rating */ + G_TYPE_INT, /* Depth */ + G_TYPE_INT, /* Duration */ + G_TYPE_INT, /* Temperature */ + G_TYPE_INT, /* Total weight */ + G_TYPE_STRING, /* Suit */ + G_TYPE_STRING, /* Cylinder */ + G_TYPE_INT, /* Nitrox */ + G_TYPE_INT, /* SAC */ + G_TYPE_INT, /* OTU */ + G_TYPE_INT, /* MAXCNS */ + G_TYPE_STRING, /* Location */ + GDK_TYPE_PIXBUF /* GPS icon */ + ); + dive_list.model = dive_list.treemodel; + dive_list.tree_view = gtk_tree_view_new_with_model(TREEMODEL(dive_list)); + set_divelist_font(prefs.divelist_font); + + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + + gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_MULTIPLE); + gtk_widget_set_size_request(dive_list.tree_view, 200, 200); + + /* check if utf8 stars are available as a default OS feature */ + if (!subsurface_os_feature_available(UTF8_FONT_WITH_STARS)) + dl_column[3].header = "*"; + + dive_list.nr = divelist_column(&dive_list, dl_column + DIVE_NR); + dive_list.date = divelist_column(&dive_list, dl_column + DIVE_DATE); + dive_list.stars = divelist_column(&dive_list, dl_column + DIVE_RATING); + dive_list.depth = divelist_column(&dive_list, dl_column + DIVE_DEPTH); + dive_list.duration = divelist_column(&dive_list, dl_column + DIVE_DURATION); + dive_list.temperature = divelist_column(&dive_list, dl_column + DIVE_TEMPERATURE); + dive_list.totalweight = divelist_column(&dive_list, dl_column + DIVE_TOTALWEIGHT); + dive_list.suit = divelist_column(&dive_list, dl_column + DIVE_SUIT); + dive_list.cylinder = divelist_column(&dive_list, dl_column + DIVE_CYLINDER); + dive_list.nitrox = divelist_column(&dive_list, dl_column + DIVE_NITROX); + dive_list.sac = divelist_column(&dive_list, dl_column + DIVE_SAC); + dive_list.otu = divelist_column(&dive_list, dl_column + DIVE_OTU); + dive_list.maxcns = divelist_column(&dive_list, dl_column + DIVE_MAXCNS); + dive_list.location = divelist_column(&dive_list, dl_column + DIVE_LOCATION); + gtk_tree_view_column_set_sort_indicator(dive_list.nr, TRUE); + gtk_tree_view_column_set_sort_order(dive_list.nr, GTK_SORT_DESCENDING); + /* now add the GPS icon to the location column */ + tree_view_column_add_pixbuf(dive_list.tree_view, gpsicon_data_func, dive_list.location); + + fill_dive_list(); + + g_object_set(G_OBJECT(dive_list.tree_view), "headers-visible", TRUE, + "search-column", DIVE_LOCATION, + "rules-hint", TRUE, + NULL); + + g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL); + g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL); + g_signal_connect(dive_list.tree_view, "row-expanded", G_CALLBACK(row_expanded_cb), NULL); + g_signal_connect(dive_list.tree_view, "row-collapsed", G_CALLBACK(row_collapsed_cb), NULL); + g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL); + g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL); + g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), dive_list.model); + g_signal_connect(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL); + g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL); + + gtk_tree_selection_set_select_function(selection, modify_selection_cb, NULL, NULL); + + dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_container_add(GTK_CONTAINER(dive_list.container_widget), dive_list.tree_view); + + dive_list.changed = 0; + + return dive_list.container_widget; +} + +void dive_list_destroy(void) +{ + gtk_widget_destroy(dive_list.tree_view); + g_object_unref(dive_list.treemodel); + g_object_unref(dive_list.listmodel); +} + +struct iteridx { + int idx; + GtkTreeIter *iter; +}; + +static gboolean iter_has_idx(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer _data) +{ + struct iteridx *iteridx = _data; + int idx; + /* Get the dive number */ + gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1); + if (idx == iteridx->idx) { + iteridx->iter = gtk_tree_iter_copy(iter); + return TRUE; /* end foreach */ + } + return FALSE; +} + +static GtkTreeIter *get_iter_from_idx(int idx) +{ + struct iteridx iteridx = {idx, }; + gtk_tree_model_foreach(MODEL(dive_list), iter_has_idx, &iteridx); + return iteridx.iter; +} + +static void scroll_to_selected(GtkTreeIter *iter) +{ + GtkTreePath *treepath; + treepath = gtk_tree_model_get_path(MODEL(dive_list), iter); + scroll_to_path(treepath); + gtk_tree_path_free(treepath); +} + +static void go_to_iter(GtkTreeSelection *selection, GtkTreeIter *iter) +{ + gtk_tree_selection_unselect_all(selection); + gtk_tree_selection_select_iter(selection, iter); + scroll_to_selected(iter); +} + +void show_and_select_dive(struct dive *dive) +{ + GtkTreeSelection *selection; + GtkTreeIter *iter; + struct dive *odive; + int i, divenr; + + divenr = get_divenr(dive); + if (divenr < 0) + /* we failed to find the dive */ + return; + iter = get_iter_from_idx(divenr); + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + for_each_dive(i, odive) + odive->selected = FALSE; + amount_selected = 1; + selected_dive = divenr; + dive->selected = TRUE; + go_to_iter(selection, iter); + gtk_tree_iter_free(iter); +} + +void select_next_dive(void) +{ + GtkTreeIter *nextiter, *parent = NULL; + GtkTreeIter *iter = get_iter_from_idx(selected_dive); + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + int idx; + + if (!iter) + return; + nextiter = gtk_tree_iter_copy(iter); + if (!gtk_tree_model_iter_next(MODEL(dive_list), nextiter)) { + if (!gtk_tree_model_iter_parent(MODEL(dive_list), nextiter, iter)) { + /* we're at the last top level node */ + goto free_iter; + } + if (!gtk_tree_model_iter_next(MODEL(dive_list), nextiter)) { + /* last trip */ + goto free_iter; + } + } + gtk_tree_model_get(MODEL(dive_list), nextiter, DIVE_INDEX, &idx, -1); + if (idx < 0) { + /* need the first child */ + parent = gtk_tree_iter_copy(nextiter); + if (! gtk_tree_model_iter_children(MODEL(dive_list), nextiter, parent)) + goto free_iter; + } + go_to_iter(selection, nextiter); +free_iter: + if (nextiter) + gtk_tree_iter_free(nextiter); + if (parent) + gtk_tree_iter_free(parent); + gtk_tree_iter_free(iter); +} + +void select_prev_dive(void) +{ + GtkTreeIter previter, *parent = NULL; + GtkTreeIter *iter = get_iter_from_idx(selected_dive); + GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view)); + GtkTreePath *treepath; + int idx; + + if (!iter) + return; + treepath = gtk_tree_model_get_path(MODEL(dive_list), iter); + if (!gtk_tree_path_prev(treepath)) { + if (!gtk_tree_model_iter_parent(MODEL(dive_list), &previter, iter)) + /* we're at the last top level node */ + goto free_iter; + gtk_tree_path_free(treepath); + treepath = gtk_tree_model_get_path(MODEL(dive_list), &previter); + if (!gtk_tree_path_prev(treepath)) + /* first trip */ + goto free_iter; + if (!gtk_tree_model_get_iter(MODEL(dive_list), &previter, treepath)) + goto free_iter; + } + if (!gtk_tree_model_get_iter(MODEL(dive_list), &previter, treepath)) + goto free_iter; + gtk_tree_model_get(MODEL(dive_list), &previter, DIVE_INDEX, &idx, -1); + if (idx < 0) { + /* need the last child */ + parent = gtk_tree_iter_copy(&previter); + if (! gtk_tree_model_iter_nth_child(MODEL(dive_list), &previter, parent, + gtk_tree_model_iter_n_children(MODEL(dive_list), parent) - 1)) + goto free_iter; + } + go_to_iter(selection, &previter); +free_iter: + gtk_tree_path_free(treepath); + if (parent) + gtk_tree_iter_free(parent); + gtk_tree_iter_free(iter); +} |