summaryrefslogtreecommitdiffstats
path: root/divelist-gtk.c
diff options
context:
space:
mode:
Diffstat (limited to 'divelist-gtk.c')
-rw-r--r--divelist-gtk.c2325
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);
+}