]> git.tdb.fi Git - ext/subsurface.git/commitdiff
Merge branch 'master' into freediving-tweaks
authorMaximilian Güntner <maximilian.guentner@gmail.com>
Tue, 21 Aug 2012 23:15:41 +0000 (01:15 +0200)
committerMaximilian Güntner <maximilian.guentner@gmail.com>
Tue, 21 Aug 2012 23:15:41 +0000 (01:15 +0200)
Signed-off-by: Maximilian Güntner <maximilian.guentner@gmail.com>
20 files changed:
Makefile
cochran.c
display-gtk.h
dive.c
dive.h
divelist.c
equipment.c
file.c
gtk-gui.c
info.c
libdivecomputer.c
libdivecomputer.h
macos.c
main.c
parse-xml.c
print.c
profile.c
save-xml.c
statistics.c
xslt/jdivelog2subsurface.xslt

index 20dff4c857f11d5d9286245e9d43c8a5d6b63aa0..5bef6f2b0f2c9950242b960a42c54bcd98a7356e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -51,19 +51,19 @@ libdc-usr64 := $(wildcard /usr/lib64/libdivecomputer.a)
 
 ifneq ($(strip $(libdc-local)),)
        LIBDIVECOMPUTERDIR = /usr/local
-       LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include/libdivecomputer
+       LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include
        LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib/libdivecomputer.a
 else ifneq ($(strip $(libdc-local64)),)
        LIBDIVECOMPUTERDIR = /usr/local
-       LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include/libdivecomputer
+       LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include
        LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib64/libdivecomputer.a
 else ifneq ($(strip $(libdc-usr)),)
        LIBDIVECOMPUTERDIR = /usr
-       LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include/libdivecomputer
+       LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include
        LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib/libdivecomputer.a
 else ifneq ($(strip $(libdc-usr64)),)
        LIBDIVECOMPUTERDIR = /usr
-       LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include/libdivecomputer
+       LIBDIVECOMPUTERINCLUDES = -I$(LIBDIVECOMPUTERDIR)/include
        LIBDIVECOMPUTERARCHIVE = $(LIBDIVECOMPUTERDIR)/lib64/libdivecomputer.a
 else
 $(error Cannot find libdivecomputer - please edit Makefile)
index 360f2eb7c9d836a1f77688821f6ad33872442b51..933e1de1fcac5f95b36498d76693a62b1db525e1 100644 (file)
--- a/cochran.c
+++ b/cochran.c
@@ -150,6 +150,64 @@ static void parse_cochran_header(const char *filename,
        free(buf);
 }
 
+/*
+ * Cochran export files show that depths seem to be in
+ * quarter feet (rounded up to tenths).
+ *
+ * Temperature seems to be exported in Fahrenheit.
+ *
+ * Cylinder pressure seems to be in multiples of 4 psi.
+ *
+ * The data seems to be some byte-stream where the pattern
+ * appears to be that the two high bits indicate type of
+ * data.
+ *
+ * For '00', the low six bits seem to be positive
+ * values with a distribution towards zero, probably depth
+ * deltas. '0 0' exists, but is very rare ("surface"?). 63
+ * exists, but is rare.
+ *
+ * For '01', the low six bits seem to be a signed binary value,
+ * with the most common being 0, and 1 and -1 (63) being the
+ * next most common values.
+ *
+ * NOTE! Don's CAN data is different. It shows the reverse pattern
+ * for 00 and 01 above: 00 looks like signed data, with 01 looking
+ * like unsigned data.
+ *
+ * For '10', there seems to be another positive value distribution,
+ * but unlike '00' the value 0 is common, and I see examples of 63
+ * too ("overflow"?) and a spike at '7'.
+ *
+ * Again, Don's data is different.
+ *
+ * The values for '11' seem to be some exception case. Possibly
+ * overflow handling, possibly warning events. It doesn't have
+ * any clear distribution: values 0, 1, 16, 33, 35, 48, 51, 55
+ * and 63 are common.
+ *
+ * For David and Don's data, '01' is the most common, with '00'
+ * and '10' not uncommon. '11' is two orders of magnitude less
+ * common.
+ *
+ * For Alex, '00' is the most common, with 01 about a third as
+ * common, and 02 a third of that. 11 is least common.
+ *
+ * There clearly are variations in the format here. And Alex has
+ * a different data offset than Don/David too (see the #ifdef DON).
+ * Christ. Maybe I've misread the patterns entirely.
+ */
+static void cochran_profile_write(const unsigned char *buf, int size)
+{
+       int i;
+
+       for (i = 0; i < size; i++) {
+               unsigned char c = buf[i];
+               printf("%d %d\n",
+                       c >> 6, c & 0x3f);
+       }
+}
+
 static void parse_cochran_dive(const char *filename, int dive,
                const unsigned char *decode, unsigned mod,
                const unsigned char *in, unsigned size)
@@ -187,6 +245,7 @@ static void parse_cochran_dive(const char *filename, int dive,
 
        printf("\n%s, dive %d\n\n", filename, dive);
        cochran_debug_write(filename, buf, size);
+       cochran_profile_write(buf + offset, size - offset);
 
        free(buf);
 }
index efbf3fd87d09814d5845edd677fbfe8df9161e67..dd7e2c4a0d97a8522ce84d4a4c02d6efbb5f9d67 100644 (file)
@@ -14,6 +14,8 @@ typedef struct {
 typedef struct {
        gboolean cylinder;
        gboolean temperature;
+       gboolean totalweight;
+       gboolean suit;
        gboolean nitrox;
        gboolean sac;
        gboolean otu;
@@ -29,9 +31,11 @@ typedef enum {
 
 #if defined __APPLE__
 #define CTRLCHAR "<Meta>"
+#define SHIFTCHAR "<Shift>"
 #define PREFERENCE_ACCEL "<Meta>comma"
 #else
 #define CTRLCHAR "<Control>"
+#define SHIFTCHAR "<Shift>"
 #define PREFERENCE_ACCEL NULL
 #endif
 
@@ -54,6 +58,7 @@ extern const char *divelist_font;
 extern void set_divelist_font(const char *);
 
 extern void import_dialog(GtkWidget *, gpointer);
+extern void add_dive_cb(GtkWidget *, gpointer);
 extern void report_error(GError* error);
 extern int process_ui_events(void);
 extern void update_progressbar(progressbar_t *progress, double value);
@@ -62,16 +67,17 @@ extern void update_progressbar_text(progressbar_t *progress, const char *text);
 extern GtkWidget *dive_profile_widget(void);
 extern GtkWidget *dive_info_frame(void);
 extern GtkWidget *extended_dive_info_widget(void);
-extern GtkWidget *equipment_widget(void);
+extern GtkWidget *equipment_widget(int w_idx);
 extern GtkWidget *single_stats_widget(void);
 extern GtkWidget *total_stats_widget(void);
-extern GtkWidget *cylinder_list_widget(void);
+extern GtkWidget *cylinder_list_widget(int w_idx);
+extern GtkWidget *weightsystem_list_widget(int w_idx);
 
 extern GtkWidget *dive_list_create(void);
 
 unsigned int amount_selected;
 
-extern void process_selected_dives(GList *, GtkTreeModel *);
+extern void process_selected_dives(void);
 
 typedef void (*data_func_t)(GtkTreeViewColumn *col,
                            GtkCellRenderer *renderer,
diff --git a/dive.c b/dive.c
index 9f57aed58146554969901198ac761fc888904e18..aee09d53a8b37796fc6f2fa0e448f92102be745e 100644 (file)
--- a/dive.c
+++ b/dive.c
@@ -120,6 +120,28 @@ double get_depth_units(unsigned int mm, int *frac, const char **units)
        return d;
 }
 
+double get_weight_units(unsigned int grams, int *frac, const char **units)
+{
+       int decimals;
+       double value;
+       const char* unit;
+
+       if (output_units.weight == LBS) {
+               value = grams_to_lbs(grams);
+               unit = "lbs";
+               decimals = 0;
+       } else {
+               value = grams / 1000.0;
+               unit = "kg";
+               decimals = 1;
+       }
+       if (frac)
+               *frac = decimals;
+       if (units)
+               *units = unit;
+       return value;
+}
+
 struct dive *alloc_dive(void)
 {
        const int initial_samples = 5;
@@ -450,8 +472,20 @@ struct dive *fixup_dive(struct dive *dive)
                        cyl->sample_end.mbar = 0;
                }
        }
-       if (end < 0)
+       if (end < 0) {
+               /* Assume an ascent/descent rate of 9 m/min */
+               int depth = dive->maxdepth.mm;
+               int asc_desc_time = depth*60/9000;
+               int duration = dive->duration.seconds;
+
+               /* Protect against insane dives - make mean be half of max */
+               if (duration <= asc_desc_time) {
+                       duration = 2;
+                       asc_desc_time = 1;
+               }
+               dive->meandepth.mm = depth*(duration-asc_desc_time)/duration;
                return dive;
+       }
 
        update_duration(&dive->duration, end - start);
        if (start != end)
@@ -464,6 +498,7 @@ struct dive *fixup_dive(struct dive *dive)
        add_people(dive->buddy);
        add_people(dive->divemaster);
        add_location(dive->location);
+       add_suit(dive->suit);
        for (i = 0; i < MAX_CYLINDERS; i++) {
                cylinder_t *cyl = dive->cylinder + i;
                add_cylinder_description(&cyl->type);
@@ -472,6 +507,10 @@ struct dive *fixup_dive(struct dive *dive)
                if (same_rounded_pressure(cyl->sample_end, cyl->end))
                        cyl->end.mbar = 0;
        }
+       for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) {
+               weightsystem_t *ws = dive->weightsystem + i;
+               add_weightsystem_description(ws);
+       }
 
        return dive;
 }
@@ -677,6 +716,7 @@ struct dive *try_to_merge(struct dive *a, struct dive *b)
        MERGE_TXT(res, a, b, buddy);
        MERGE_TXT(res, a, b, divemaster);
        MERGE_MAX(res, a, b, rating);
+       MERGE_TXT(res, a, b, suit);
        MERGE_MAX(res, a, b, number);
        MERGE_MAX(res, a, b, maxdepth.mm);
        res->meandepth.mm = 0;
diff --git a/dive.h b/dive.h
index 4d60a77a2c173e64f22f10374cb6f48d2121ab17..ab854e37e6c6d49bfe43b6533322cd9facd11c4f 100644 (file)
--- a/dive.h
+++ b/dive.h
@@ -92,13 +92,17 @@ typedef struct {
        const char *description;        /* "integrated", "belt", "ankle" */
 } weightsystem_t;
 
-extern int cylinder_none(void *_data);
-extern int weightsystem_none(void *_data);
+extern gboolean cylinder_none(void *_data);
+extern gboolean no_cylinders(cylinder_t *cyl);
+extern gboolean cylinders_equal(cylinder_t *cyl1, cylinder_t *cyl2);
+extern gboolean no_weightsystems(weightsystem_t *ws);
+extern gboolean weightsystems_equal(weightsystem_t *ws1, weightsystem_t *ws2);
 
 extern int get_pressure_units(unsigned int mb, const char **units);
 extern double get_depth_units(unsigned int mm, int *frac, const char **units);
-extern double get_volume_units(unsigned int mm, int *frac, const char **units);
-extern double get_temp_units(unsigned int mm, const char **units);
+extern double get_volume_units(unsigned int ml, int *frac, const char **units);
+extern double get_temp_units(unsigned int mk, const char **units);
+extern double get_weight_units(unsigned int grams, int *frac, const char **units);
 
 static inline double grams_to_lbs(int grams)
 {
@@ -125,21 +129,31 @@ static inline double mm_to_feet(int mm)
        return mm * 0.00328084;
 }
 
+static inline unsigned long feet_to_mm(double feet)
+{
+       return feet * 304.8 + 0.5;
+}
+
 static inline int to_feet(depth_t depth)
 {
        return mm_to_feet(depth.mm) + 0.5;
 }
 
-static double mkelvin_to_C(int mkelvin)
+static inline double mkelvin_to_C(int mkelvin)
 {
        return (mkelvin - 273150) / 1000.0;
 }
 
-static double mkelvin_to_F(int mkelvin)
+static inline double mkelvin_to_F(int mkelvin)
 {
        return mkelvin * 9 / 5000.0 - 459.670;
 }
 
+static inline unsigned long F_to_mkelvin(double f)
+{
+       return (f-32) * 1000 / 1.8 + 273150.5;
+}
+
 static inline int to_C(temperature_t temp)
 {
        if (!temp.mkelvin)
@@ -165,6 +179,12 @@ static inline double psi_to_bar(double psi)
 {
        return psi / 14.5037738;
 }
+
+static inline unsigned long psi_to_mbar(double psi)
+{
+       return psi_to_bar(psi)*1000 + 0.5;
+}
+
 static inline int to_PSI(pressure_t pressure)
 {
        return pressure.mbar * 0.0145037738 + 0.5;
@@ -211,9 +231,12 @@ struct event {
 
 #define MAX_CYLINDERS (8)
 #define MAX_WEIGHTSYSTEMS (4)
+#define W_IDX_PRIMARY 0
+#define W_IDX_SECONDARY 1
 
 struct dive {
        int number;
+       int selected;
        time_t when;
        char *location;
        char *notes;
@@ -226,6 +249,7 @@ struct dive {
        temperature_t airtemp, watertemp;
        cylinder_t cylinder[MAX_CYLINDERS];
        weightsystem_t weightsystem[MAX_WEIGHTSYSTEMS];
+       char *suit;
        int sac, otu;
        struct event *events;
        int samples, alloc_samples;
@@ -266,11 +290,21 @@ extern int selected_dive;
 
 static inline struct dive *get_dive(unsigned int nr)
 {
-       if (nr >= dive_table.nr)
+       if (nr >= dive_table.nr || nr < 0)
                return NULL;
        return dive_table.dives[nr];
 }
 
+/*
+ * Iterate over each dive, with the first parameter being the index
+ * iterator variable, and the second one being the dive one.
+ *
+ * I don't think anybody really wants the index, and we could make
+ * it local to the for-loop, but that would make us requires C99.
+ */
+#define for_each_dive(_i,_x) \
+       for ((_i) = 0; ((_x) = get_dive(_i)) != NULL; (_i)++)
+
 extern void parse_xml_init(void);
 extern void parse_xml_buffer(const char *url, const char *buf, int size, GError **error);
 extern void set_filename(const char *filename);
@@ -283,7 +317,7 @@ extern xmlDoc *test_xslt_transforms(xmlDoc *doc);
 
 extern void show_dive_info(struct dive *);
 
-extern void show_dive_equipment(struct dive *);
+extern void show_dive_equipment(struct dive *, int w_idx);
 
 extern void show_dive_stats(struct dive *);
 
@@ -322,12 +356,16 @@ extern void exit_ui(void);
 extern void report_error(GError* error);
 
 extern void add_cylinder_description(cylinder_type_t *);
+extern void add_weightsystem_description(weightsystem_t *);
 extern void add_people(const char *string);
 extern void add_location(const char *string);
+extern void add_suit(const char *string);
 extern void remember_event(const char *eventname);
 extern void evn_foreach(void (*callback)(const char *, int *, void *), void *data);
 
+extern int add_new_dive(struct dive *dive);
 extern int edit_dive_info(struct dive *dive);
+extern int edit_multi_dive_info(struct dive *single_dive);
 extern void dive_list_update_dives(void);
 extern void flush_divelist(struct dive *dive);
 
index 2054ca017ca0b41b8d6642e22ec09aa2e223f687..30bd2d8e97b9024383e3e961457bae46ae06d356 100644 (file)
@@ -24,9 +24,9 @@
 struct DiveList {
        GtkWidget    *tree_view;
        GtkWidget    *container_widget;
-       GtkListStore *model;
+       GtkTreeStore *model, *listmodel, *treemodel;
        GtkTreeViewColumn *nr, *date, *stars, *depth, *duration, *location;
-       GtkTreeViewColumn *temperature, *cylinder, *nitrox, *sac, *otu;
+       GtkTreeViewColumn *temperature, *cylinder, *totalweight, *suit, *nitrox, *sac, *otu;
        int changed;
 };
 
@@ -44,6 +44,8 @@ enum {
        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 */
@@ -52,46 +54,231 @@ enum {
        DIVELIST_COLUMNS
 };
 
-static GList *selected_dives;
+/* magic numbers that indicate (as negative values) model entries that
+ * are summary entries for a divetrip */
+#define NEW_TRIP 1
 
-static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model)
+#ifdef DEBUG_MODEL
+static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path,
+                               GtkTreeIter *iter, gpointer data)
 {
-       GtkTreeIter iter;
-       GValue value = {0, };
-       GtkTreePath *path;
+       char *location;
+       int idx, nr, rating, depth;
+
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, DIVE_RATING, &rating, DIVE_DEPTH, &depth, DIVE_LOCATION, &location, -1);
+       printf("entry #%d : nr %d rating %d depth %d location %s \n", idx, nr, rating, depth, location);
+       free(location);
+
+       return FALSE;
+}
+
+static void dump_model(GtkListStore *store)
+{
+       gtk_tree_model_foreach(GTK_TREE_MODEL(store), dump_model_entry, NULL);
+}
+#endif
 
-       int nr_selected = gtk_tree_selection_count_selected_rows(selection);
+#if DEBUG_SELECTION_TRACKING
+void dump_selection(void)
+{
+       int i;
+       struct dive *dive;
 
-       if (selected_dives) {
-               g_list_foreach (selected_dives, (GFunc) gtk_tree_path_free, NULL);
-               g_list_free (selected_dives);
+       printf("currently selected are %d dives:", amount_selected);
+       for_each_dive(i, dive) {
+               if (dive->selected)
+                       printf(" %d", i);
        }
-       selected_dives = gtk_tree_selection_get_selected_rows(selection, NULL);
+       printf("\n");
+}
+#endif
 
-       switch (nr_selected) {
-       case 0: /* keep showing the last selected dive */
+/* 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 */
+                       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_model_get(GTK_TREE_MODEL(model), iter, DIVE_INDEX, diveidx, -1);
+       }
+}
+
+/* make sure that if we expand a summary row that is selected, the children show
+   up as selected, too */
+void row_expanded_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data)
+{
+       GtkTreeIter child;
+       GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
+       GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
+
+       if (!gtk_tree_model_iter_children(model, &child, iter))
                return;
-       case 1: 
-               /* just pick that dive as selected */
-               amount_selected = 1;
-               path = g_list_nth_data(selected_dives, 0);
-               if (gtk_tree_model_get_iter(model, &iter, path)) {
-                       gtk_tree_model_get_value(model, &iter, DIVE_INDEX, &value);
-                       selected_dive = g_value_get_int(&value);
-                       repaint_dive();
-               }
+
+       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));
+}
+
+static int selected_children(GtkTreeModel *model, GtkTreeIter *iter)
+{
+       GtkTreeIter child;
+
+       if (!gtk_tree_model_iter_children(model, &child, iter))
+               return FALSE;
+
+       do {
+               int idx;
+               struct dive *dive;
+
+               gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1);
+               dive = get_dive(idx);
+
+               if (dive->selected)
+                       return TRUE;
+       } while (gtk_tree_model_iter_next(model, &child));
+       return FALSE;
+}
+
+/* Make sure that if we collapse a summary row with any selected children, the row
+   shows up as selected too */
+void row_collapsed_cb(GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, gpointer data)
+{
+       GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
+       GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
+
+       if (selected_children(model, iter))
+               gtk_tree_selection_select_iter(selection, iter);
+}
+
+static GList *selection_changed = NULL;
+
+/*
+ * This is called _before_ the selection is changed, for every single entry;
+ *
+ * We simply create a list of all changed entries, and make sure that the
+ * group entries go at the end of the list.
+ */
+gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model,
+                       GtkTreePath *path, gboolean was_selected, gpointer userdata)
+{
+       GtkTreeIter iter, *p;
+
+       if (!gtk_tree_model_get_iter(model, &iter, path))
+               return TRUE;
+
+       /* Add the group entries to the end */
+       p = gtk_tree_iter_copy(&iter);
+       if (gtk_tree_model_iter_has_child(model, p))
+               selection_changed = g_list_append(selection_changed, p);
+       else
+               selection_changed = g_list_prepend(selection_changed, p);
+       return TRUE;
+}
+
+static void select_dive(struct dive *dive, int selected)
+{
+       if (dive->selected != selected) {
+               amount_selected += selected ? 1 : -1;
+               dive->selected = selected;
+       }
+}
+
+/*
+ * This gets called when a dive group has changed selection.
+ */
+static void select_dive_group(GtkTreeModel *model, GtkTreeSelection *selection, GtkTreeIter *iter, int selected)
+{
+       int first = 1;
+       GtkTreeIter child;
+
+       if (selected == selected_children(model, iter))
                return;
-       default: /* multiple selections - what now? At this point I
-                 * don't want to change the selected dive unless
-                 * there is exactly one dive selected; not sure this
-                 * is the most intuitive solution.
-                 * I do however want to keep around which dives have
-                 * been selected */
-               amount_selected = g_list_length(selected_dives);
-               process_selected_dives(selected_dives, model);
-               repaint_dive();
+
+       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);
+               if (first && selected)
+                       selected_dive = idx;
+               first = 0;
+               dive = get_dive(idx);
+               if (dive->selected == selected)
+                       continue;
+
+               select_dive(dive, selected);
+               if (selected)
+                       gtk_tree_selection_select_iter(selection, &child);
+               else
+                       gtk_tree_selection_unselect_iter(selection, &child);
+       } while (gtk_tree_model_iter_next(model, &child));
+}
+
+/*
+ * This gets called _after_ the selections have changed, for each entry that
+ * may have changed. Check if the gtk selection state matches our internal
+ * selection state to verify.
+ *
+ * The group entries are at the end, this guarantees that we have handled
+ * all the dives before we handle groups.
+ */
+static void check_selection_cb(GtkTreeIter *iter, GtkTreeSelection *selection)
+{
+       GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
+       struct dive *dive;
+       int idx, gtk_selected;
+
+       gtk_tree_model_get(model, iter,
+               DIVE_INDEX, &idx,
+               -1);
+       dive = get_dive(idx);
+       gtk_selected = gtk_tree_selection_iter_is_selected(selection, iter);
+       if (idx < 0)
+               select_dive_group(model, selection, iter, gtk_selected);
+       else {
+               select_dive(dive, gtk_selected);
+               if (gtk_selected)
+                       selected_dive = idx;
        }
+       gtk_tree_iter_free(iter);
+}
+
+/* this is called when gtk thinks that the selection has changed */
+static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model)
+{
+       GList *changed = selection_changed;
+
+       selection_changed = NULL;
+       g_list_foreach(changed, (GFunc) check_selection_cb, selection);
+       g_list_free(changed);
+#if DEBUG_SELECTION_TRACKING
+       dump_selection();
+#endif
+
+       process_selected_dives();
+       repaint_dive();
 }
 
 const char *star_strings[] = {
@@ -109,13 +296,17 @@ static void star_data_func(GtkTreeViewColumn *col,
                           GtkTreeIter *iter,
                           gpointer data)
 {
-       int nr_stars;
+       int nr_stars, idx;
        char buffer[40];
 
-       gtk_tree_model_get(model, iter, DIVE_RATING, &nr_stars, -1);
-       if (nr_stars < 0 || nr_stars > 5)
-               nr_stars = 0;
-       snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]);
+       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);
 }
 
@@ -125,23 +316,34 @@ static void date_data_func(GtkTreeViewColumn *col,
                           GtkTreeIter *iter,
                           gpointer data)
 {
-       int val;
+       int val, idx, nr;
        struct tm *tm;
        time_t when;
        char buffer[40];
 
-       gtk_tree_model_get(model, iter, DIVE_DATE, &val, -1);
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &val, DIVE_NR, &nr, -1);
 
        /* 2038 problem */
        when = val;
 
        tm = gmtime(&when);
-       snprintf(buffer, sizeof(buffer),
-               "%s, %s %d, %d %02d:%02d",
-               weekday(tm->tm_wday),
-               monthname(tm->tm_mon),
-               tm->tm_mday, tm->tm_year + 1900,
-               tm->tm_hour, tm->tm_min);
+       switch(idx) {
+       case -NEW_TRIP:
+               snprintf(buffer, sizeof(buffer),
+                       "Trip %s, %s %d, %d (%d dive%s)",
+                       weekday(tm->tm_wday),
+                       monthname(tm->tm_mon),
+                       tm->tm_mday, tm->tm_year + 1900,
+                       nr, nr > 1 ? "s" : "");
+               break;
+       default:
+               snprintf(buffer, sizeof(buffer),
+                       "%s, %s %d, %d %02d:%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);
 }
 
@@ -151,34 +353,37 @@ static void depth_data_func(GtkTreeViewColumn *col,
                            GtkTreeIter *iter,
                            gpointer data)
 {
-       int depth, integer, frac, len;
+       int depth, integer, frac, len, idx;
        char buffer[40];
 
-       gtk_tree_model_get(model, iter, DIVE_DEPTH, &depth, -1);
-
-       switch (output_units.length) {
-       case METERS:
-               /* To tenths of meters */
-               depth = (depth + 49) / 100;
-               integer = depth / 10;
-               frac = depth % 10;
-               if (integer < 20)
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1);
+
+       if (idx < 0) {
+               *buffer = '\0';
+       } else {
+               switch (output_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;
-               if (frac >= 5)
-                       integer++;
-               frac = -1;
-               break;
-       case FEET:
-               integer = mm_to_feet(depth) + 0.5;
-               frac = -1;
-               break;
-       default:
-               return;
+               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);
        }
-       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);
 }
 
@@ -189,10 +394,14 @@ static void duration_data_func(GtkTreeViewColumn *col,
                               gpointer data)
 {
        unsigned int sec;
+       int idx;
        char buffer[16];
 
-       gtk_tree_model_get(model, iter, DIVE_DURATION, &sec, -1);
-       snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
+       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);
 }
@@ -203,13 +412,13 @@ static void temperature_data_func(GtkTreeViewColumn *col,
                                  GtkTreeIter *iter,
                                  gpointer data)
 {
-       int value;
+       int value, idx;
        char buffer[80];
 
-       gtk_tree_model_get(model, iter, DIVE_TEMPERATURE, &value, -1);
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1);
 
        *buffer = 0;
-       if (value) {
+       if (idx >= 0 && value) {
                double deg;
                switch (output_units.temperature) {
                case CELSIUS:
@@ -227,6 +436,23 @@ static void temperature_data_func(GtkTreeViewColumn *col,
        g_object_set(renderer, "text", buffer, NULL);
 }
 
+static void nr_data_func(GtkTreeViewColumn *col,
+                          GtkCellRenderer *renderer,
+                          GtkTreeModel *model,
+                          GtkTreeIter *iter,
+                          gpointer data)
+{
+       int idx, nr;
+       char buffer[40];
+
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1);
+       if (idx < 0)
+               *buffer = '\0';
+       else
+               snprintf(buffer, sizeof(buffer), "%d", nr);
+       g_object_set(renderer, "text", buffer, NULL);
+}
+
 /*
  * Get "maximal" dive gas for a dive.
  * Rules:
@@ -269,6 +495,38 @@ newmax:
        *o2low_p = mino2;
 }
 
+static int total_weight(struct dive *dive)
+{
+       int i, total_grams = 0;
+
+       if (dive)
+               for (i=0; i< MAX_WEIGHTSYSTEMS; i++)
+                       total_grams += dive->weightsystem[i].weight.grams;
+       return total_grams;
+}
+
+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,
@@ -304,12 +562,16 @@ static void nitrox_data_func(GtkTreeViewColumn *col,
                             GtkTreeIter *iter,
                             gpointer data)
 {
-       int index, o2, he, o2low;
+       int idx, o2, he, o2low;
        char buffer[80];
        struct dive *dive;
 
-       gtk_tree_model_get(model, iter, DIVE_INDEX, &index, -1);
-       dive = get_dive(index);
+       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;
@@ -324,7 +586,7 @@ static void nitrox_data_func(GtkTreeViewColumn *col,
                        snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
        else
                strcpy(buffer, "air");
-
+exit:
        g_object_set(renderer, "text", buffer, NULL);
 }
 
@@ -335,16 +597,16 @@ static void sac_data_func(GtkTreeViewColumn *col,
                          GtkTreeIter *iter,
                          gpointer data)
 {
-       int value;
+       int value, idx;
        const char *fmt;
        char buffer[16];
        double sac;
 
-       gtk_tree_model_get(model, iter, DIVE_SAC, &value, -1);
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1);
 
-       if (!value) {
-               g_object_set(renderer, "text", "", NULL);
-               return;
+       if (idx < 0 || !value) {
+               *buffer = '\0';
+               goto exit;
        }
 
        sac = value / 1000.0;
@@ -358,7 +620,7 @@ static void sac_data_func(GtkTreeViewColumn *col,
                break;
        }
        snprintf(buffer, sizeof(buffer), fmt, sac);
-
+exit:
        g_object_set(renderer, "text", buffer, NULL);
 }
 
@@ -369,17 +631,15 @@ static void otu_data_func(GtkTreeViewColumn *col,
                          GtkTreeIter *iter,
                          gpointer data)
 {
-       int value;
+       int value, idx;
        char buffer[16];
 
-       gtk_tree_model_get(model, iter, DIVE_OTU, &value, -1);
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1);
 
-       if (!value) {
-               g_object_set(renderer, "text", "", NULL);
-               return;
-       }
-
-       snprintf(buffer, sizeof(buffer), "%d", value);
+       if (idx < 0 || !value)
+               *buffer = '\0';
+       else
+               snprintf(buffer, sizeof(buffer), "%d", value);
 
        g_object_set(renderer, "text", buffer, NULL);
 }
@@ -501,27 +761,55 @@ static void get_cylinder(struct dive *dive, char **str)
        get_string(str, dive->cylinder[0].type.description);
 }
 
+static void get_suit(struct dive *dive, char **str)
+{
+       get_string(str, dive->suit);
+}
+
 /*
  * Set up anything that could have changed due to editing
- * of dive information
+ * 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;
+       char *location, *cylinder, *suit;
+       GtkTreeStore *othermodel;
 
        get_cylinder(dive, &cylinder);
        get_location(dive, &location);
+       get_suit(dive, &suit);
 
-       gtk_list_store_set(GTK_LIST_STORE(model), iter,
+       gtk_tree_store_set(GTK_TREE_STORE(model), iter,
                DIVE_NR, dive->number,
                DIVE_LOCATION, location,
                DIVE_CYLINDER, cylinder,
                DIVE_RATING, dive->rating,
                DIVE_SAC, dive->sac,
                DIVE_OTU, dive->otu,
+               DIVE_TOTALWEIGHT, total_weight(dive),
+               DIVE_SUIT, suit,
                -1);
+
+       free(location);
+       free(cylinder);
+       free(suit);
+
+       if (model == GTK_TREE_MODEL(dive_list.treemodel))
+               othermodel = dive_list.listmodel;
+       else
+               othermodel = dive_list.treemodel;
+       if (othermodel != dive_list.model)
+               /* recursive call */
+               gtk_tree_model_foreach(GTK_TREE_MODEL(othermodel), set_one_dive, dive);
 }
 
 static gboolean set_one_dive(GtkTreeModel *model,
@@ -529,12 +817,14 @@ static gboolean set_one_dive(GtkTreeModel *model,
                             GtkTreeIter *iter,
                             gpointer data)
 {
-       GValue value = {0, };
+       int idx;
        struct dive *dive;
 
        /* Get the dive number */
-       gtk_tree_model_get_value(model, iter, DIVE_INDEX, &value);
-       dive = get_dive(g_value_get_int(&value));
+       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)
@@ -569,6 +859,9 @@ void update_dive_list_units(void)
        (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);
 }
 
@@ -576,41 +869,125 @@ void update_dive_list_col_visibility(void)
 {
        gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
        gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
+       gtk_tree_view_column_set_visible(dive_list.totalweight, visible_cols.totalweight);
+       gtk_tree_view_column_set_visible(dive_list.suit, visible_cols.suit);
        gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
        gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
        gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
        return;
 }
 
+/* random heuristic - not diving in three days implies new dive trip */
+#define TRIP_THRESHOLD 3600*24*3
+static int new_group(struct dive *dive, struct dive **last_dive, time_t *tm_date)
+{
+       if (!last_dive)
+               return TRUE;
+       if (*last_dive) {
+               struct dive *ldive = *last_dive;
+               if (abs(dive->when - ldive->when) < TRIP_THRESHOLD) {
+                       *last_dive = dive;
+                       return FALSE;
+               }
+       }
+       *last_dive = dive;
+       if (tm_date) {
+               struct tm *tm1 = gmtime(&dive->when);
+               tm1->tm_sec = 0;
+               tm1->tm_min = 0;
+               tm1->tm_hour = 0;
+               *tm_date = mktime(tm1);
+       }
+       return TRUE;
+}
+
 static void fill_dive_list(void)
 {
-       int i;
-       GtkTreeIter iter;
-       GtkListStore *store;
+       int i, group_size;
+       GtkTreeIter iter, parent_iter;
+       GtkTreeStore *liststore, *treestore;
+       struct dive *last_dive = NULL;
+       struct dive *last_trip_dive = NULL;
+       const char *last_location = NULL;
+       time_t dive_date;
 
-       store = GTK_LIST_STORE(dive_list.model);
+       treestore = GTK_TREE_STORE(dive_list.treemodel);
+       liststore = GTK_TREE_STORE(dive_list.listmodel);
 
        i = dive_table.nr;
        while (--i >= 0) {
                struct dive *dive = dive_table.dives[i];
 
+               if (new_group(dive, &last_dive, &dive_date))
+               {
+                       /* make sure we display the first date of the trip in previous summary */
+                       if (last_trip_dive)
+                               gtk_tree_store_set(treestore, &parent_iter,
+                                       DIVE_NR, group_size,
+                                       DIVE_DATE, last_trip_dive->when,
+                                       DIVE_LOCATION, last_location,
+                                       -1);
+
+                       gtk_tree_store_append(treestore, &parent_iter, NULL);
+                       gtk_tree_store_set(treestore, &parent_iter,
+                                       DIVE_INDEX, -NEW_TRIP,
+                                       DIVE_NR, 1,
+                                       DIVE_TEMPERATURE, 0,
+                                       DIVE_SAC, 0,
+                                       -1);
+
+                       group_size = 0;
+                       /* This might be NULL */
+                       last_location = dive->location;
+               }
+               group_size++;
+               last_trip_dive = dive;
+               if (dive->location)
+                       last_location = dive->location;
                update_cylinder_related_info(dive);
-               gtk_list_store_append(store, &iter);
-               gtk_list_store_set(store, &iter,
+               gtk_tree_store_append(treestore, &iter, &parent_iter);
+               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, "location",
+                       DIVE_LOCATION, dive->location,
+                       DIVE_RATING, dive->rating,
                        DIVE_TEMPERATURE, dive->watertemp.mkelvin,
                        DIVE_SAC, 0,
                        -1);
+               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_RATING, dive->rating,
+                       DIVE_TEMPERATURE, dive->watertemp.mkelvin,
+                       DIVE_TOTALWEIGHT, 0,
+                       DIVE_SUIT, dive->suit,
+                       DIVE_SAC, 0,
+                       -1);
        }
 
+       /* make sure we display the first date of the trip in previous summary */
+       if (last_trip_dive)
+               gtk_tree_store_set(treestore, &parent_iter,
+                               DIVE_NR, group_size,
+                               DIVE_DATE, last_trip_dive->when,
+                               DIVE_LOCATION, last_location,
+                               -1);
+
        update_dive_list_units();
        if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
                GtkTreeSelection *selection;
+
+               /* select the last dive (and make sure it's an actual dive that is selected) */
+               gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
+               first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive);
                selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
                gtk_tree_selection_select_iter(selection, &iter);
        }
@@ -618,7 +995,8 @@ static void fill_dive_list(void)
 
 void dive_list_update_dives(void)
 {
-       gtk_list_store_clear(GTK_LIST_STORE(dive_list.model));
+       gtk_tree_store_clear(GTK_TREE_STORE(dive_list.treemodel));
+       gtk_tree_store_clear(GTK_TREE_STORE(dive_list.listmodel));
        fill_dive_list();
        repaint_dive();
 }
@@ -630,12 +1008,14 @@ static struct divelist_column {
        unsigned int flags;
        int *visible;
 } dl_column[] = {
-       [DIVE_NR] = { "#", NULL, NULL, ALIGN_RIGHT | UNSORTABLE },
+       [DIVE_NR] = { "#", nr_data_func, NULL, ALIGN_RIGHT | UNSORTABLE },
        [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
        [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
        [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
        [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
        [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
+       [DIVE_TOTALWEIGHT] = { "lbs", weight_data_func, NULL, ALIGN_RIGHT, &visible_cols.totalweight },
+       [DIVE_SUIT] = { "Suit", NULL, NULL, ALIGN_LEFT, &visible_cols.suit },
        [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
        [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
        [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
@@ -653,14 +1033,20 @@ static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_c
        unsigned int flags = col->flags;
        int *visible = col->visible;
        GtkWidget *tree_view = dl->tree_view;
-       GtkListStore *model = dl->model;
+       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)
-               gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), index, sort_func, NULL, NULL);
+       if (sort_func) {
+               /* the sort functions are needed in the corresponding models */
+               if (index == DIVE_DATE)
+                       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;
 }
 
@@ -673,25 +1059,213 @@ 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,
-                       GtkTreeModel *model)
+                       gpointer userdata)
 {
        int index;
        GtkTreeIter iter;
 
-       if (!gtk_tree_model_get_iter(model, &iter, path))
+       if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path))
                return;
-       gtk_tree_model_get(model, &iter, DIVE_INDEX, &index, -1);
+
+       gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &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));
 }
 
+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);
+               return;
+       }
+       free(dive);
+}
+
+void edit_dive_cb(GtkWidget *menuitem, gpointer data)
+{
+       edit_multi_dive_info(NULL);
+}
+
+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);
+}
+
+static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
+{
+       GtkWidget *menu, *menuitem, *image;
+       char editlabel[] = "Edit dives";
+
+       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 (amount_selected) {
+               if (amount_selected == 1)
+                       editlabel[strlen(editlabel) - 1] = '\0';
+               menuitem = gtk_menu_item_new_with_label(editlabel);
+               g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_cb), model);
+               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, GTK_TREE_MODEL(dive_list.model), 0);
+}
+
+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), GTK_TREE_MODEL(dive_list.model), 3);
+               return TRUE;
+       }
+       return FALSE;
+}
+
+/* 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_DATE;
+
+/* 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) {
+               GtkTreeIter child;
+               if (gtk_tree_model_iter_children(model, &child, iter))
+                       gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1);
+       }
+       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 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;
+}
+
+/* If the sort column is date (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;
+
+       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_DATE)
+               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), GTK_TREE_MODEL(dive_list.model));
+               update_column_and_order(colid);
+               gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), set_selected, selection);
+       } else {
+               if (order != sortorder[colid]) {
+                       update_column_and_order(colid);
+               }
+       }
+}
+
 GtkWidget *dive_list_create(void)
 {
        GtkTreeSelection  *selection;
 
-       dive_list.model = gtk_list_store_new(DIVELIST_COLUMNS,
+       dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS,
                                G_TYPE_INT,                     /* index */
                                G_TYPE_INT,                     /* nr */
                                G_TYPE_INT,                     /* Date */
@@ -699,12 +1273,31 @@ GtkWidget *dive_list_create(void)
                                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_STRING                   /* Location */
                                );
+       dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS,
+                               G_TYPE_INT,                     /* index */
+                               G_TYPE_INT,                     /* nr */
+                               G_TYPE_INT,                     /* 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_STRING                   /* Location */
+                               );
+       dive_list.model = dive_list.treemodel;
        dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
        set_divelist_font(divelist_font);
 
@@ -719,6 +1312,8 @@ GtkWidget *dive_list_create(void)
        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);
@@ -733,8 +1328,16 @@ GtkWidget *dive_list_create(void)
                                          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), dive_list.model);
+       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),
index b74d85b7b6509ab910620c3c2ffe9d8b2e91f099..43bb29d593ae6009d81db0e291adf60af2f26a1c 100644 (file)
@@ -2,10 +2,10 @@
 /* creates the UI for the equipment page -
  * controlled through the following interfaces:
  *
- * void show_dive_equipment(struct dive *dive)
+ * void show_dive_equipment(struct dive *dive, int w_idx)
  *
  * called from gtk-ui:
- * GtkWidget *equipment_widget(void)
+ * GtkWidget *equipment_widget(int w_idx)
  */
 #include <stdio.h>
 #include <string.h>
@@ -40,10 +40,11 @@ enum {
 struct equipment_list {
        int max_index;
        GtkListStore *model;
+       GtkTreeView *tree_view;
        GtkWidget *edit, *add, *del;
 };
 
-static struct equipment_list cylinder_list, weightsystem_list;
+static struct equipment_list cylinder_list[2], weightsystem_list[2];
 
 
 struct cylinder_widget {
@@ -308,13 +309,15 @@ static GtkTreeIter *add_weightsystem_type(const char *desc, int weight, GtkTreeI
        model = GTK_TREE_MODEL(weightsystem_model);
        gtk_tree_model_foreach(model, match_desc, (void *)desc);
 
-       if (!found_match) {
-               GtkListStore *store = GTK_LIST_STORE(model);
-
-               gtk_list_store_append(store, iter);
-               gtk_list_store_set(store, iter,
-                       0, desc,
-                       1, weight,
+       if (found_match) {
+               gtk_list_store_set(GTK_LIST_STORE(model), found_match,
+                               WS_WEIGHT, weight,
+                               -1);
+       } else if (desc && desc[0]) {
+               gtk_list_store_append(GTK_LIST_STORE(model), iter);
+               gtk_list_store_set(GTK_LIST_STORE(model), iter,
+                       WS_DESC, desc,
+                       WS_WEIGHT, weight,
                        -1);
                return iter;
        }
@@ -425,7 +428,7 @@ static void show_weightsystem(weightsystem_t *ws, struct ws_widget *weightsystem
        set_weight_weight_spinbutton(weightsystem_widget, ws->weight.grams);
 }
 
-int cylinder_none(void *_data)
+gboolean cylinder_none(void *_data)
 {
        cylinder_t *cyl = _data;
        return  !cyl->type.size.mliter &&
@@ -439,12 +442,77 @@ int cylinder_none(void *_data)
                !cyl->end.mbar;
 }
 
-int weightsystem_none(void *_data)
+gboolean no_cylinders(cylinder_t *cyl)
+{
+       int i;
+
+       for (i = 0; i < MAX_CYLINDERS; i++)
+               if (!cylinder_none(cyl + i))
+                       return FALSE;
+       return TRUE;
+}
+
+/* descriptions are equal if they are both NULL or both non-NULL
+   and the same text */
+gboolean description_equal(const char *desc1, const char *desc2)
+{
+               return ((! desc1 && ! desc2) ||
+                       (desc1 && desc2 && strcmp(desc1, desc2) == 0));
+}
+
+/* when checking for the same cylinder we want the size and description to match
+   but don't compare the start and end pressures */
+static gboolean one_cylinder_equal(cylinder_t *cyl1, cylinder_t *cyl2)
+{
+       return cyl1->type.size.mliter == cyl2->type.size.mliter &&
+               cyl1->type.workingpressure.mbar == cyl2->type.workingpressure.mbar &&
+               cyl1->gasmix.o2.permille == cyl2->gasmix.o2.permille &&
+               cyl1->gasmix.he.permille == cyl2->gasmix.he.permille &&
+               description_equal(cyl1->type.description, cyl2->type.description);
+}
+
+gboolean cylinders_equal(cylinder_t *cyl1, cylinder_t *cyl2)
+{
+       int i;
+
+       for (i = 0; i < MAX_CYLINDERS; i++)
+               if (!one_cylinder_equal(cyl1 + i, cyl2 + i))
+                       return FALSE;
+       return TRUE;
+}
+
+static gboolean weightsystem_none(void *_data)
 {
        weightsystem_t *ws = _data;
        return !ws->weight.grams && !ws->description;
 }
 
+gboolean no_weightsystems(weightsystem_t *ws)
+{
+       int i;
+
+       for (i = 0; i < MAX_WEIGHTSYSTEMS; i++)
+               if (!weightsystem_none(ws + i))
+                       return FALSE;
+       return TRUE;
+}
+
+static gboolean one_weightsystem_equal(weightsystem_t *ws1, weightsystem_t *ws2)
+{
+       return ws1->weight.grams == ws2->weight.grams &&
+               description_equal(ws1->description, ws2->description);
+}
+
+gboolean weightsystems_equal(weightsystem_t *ws1, weightsystem_t *ws2)
+{
+       int i;
+
+       for (i = 0; i < MAX_WEIGHTSYSTEMS; i++)
+               if (!one_weightsystem_equal(ws1 + i, ws2 + i))
+                       return FALSE;
+       return TRUE;
+}
+
 static void set_one_cylinder(void *_data, GtkListStore *model, GtkTreeIter *iter)
 {
        cylinder_t *cyl = _data;
@@ -490,7 +558,7 @@ static void *ws_ptr(struct dive *dive, int idx)
 static void show_equipment(struct dive *dive, int max,
                        struct equipment_list *equipment_list,
                        void*(*ptr_function)(struct dive*, int),
-                       int(*none_function)(void *),
+                       gboolean(*none_function)(void *),
                        void(*set_one_function)(void*, GtkListStore*, GtkTreeIter *))
 {
        int i, used;
@@ -519,11 +587,11 @@ static void show_equipment(struct dive *dive, int max,
        }
 }
 
-void show_dive_equipment(struct dive *dive)
+void show_dive_equipment(struct dive *dive, int w_idx)
 {
-       show_equipment(dive, MAX_CYLINDERS, &cylinder_list,
+       show_equipment(dive, MAX_CYLINDERS, &cylinder_list[w_idx],
                &cyl_ptr, &cylinder_none, &set_one_cylinder);
-       show_equipment(dive, MAX_WEIGHTSYSTEMS, &weightsystem_list,
+       show_equipment(dive, MAX_WEIGHTSYSTEMS, &weightsystem_list[w_idx],
                &ws_ptr, &weightsystem_none, &set_one_weightsystem);
 }
 
@@ -623,6 +691,7 @@ static void record_weightsystem_changes(weightsystem_t *ws, struct ws_widget *we
        GtkComboBox *box;
        int grams;
        double value;
+       GtkTreeIter iter;
 
        /* Ignore uninitialized cylinder widgets */
        box = weightsystem_widget->description;
@@ -638,6 +707,7 @@ static void record_weightsystem_changes(weightsystem_t *ws, struct ws_widget *we
                grams = value * 1000;
        ws->weight.grams = grams;
        ws->description = desc;
+       add_weightsystem_type(desc, grams, &iter);
 }
 
 /*
@@ -744,8 +814,6 @@ static struct ws_info {
        const char *name;
        int grams;
 } ws_info[100] = {
-       /* Need an empty entry for the no weight system case */
-       { "", },
        { "integrated", 0 },
        { "belt", 0 },
        { "ankle", 0 },
@@ -1061,11 +1129,12 @@ static int get_model_index(GtkListStore *model, GtkTreeIter *iter)
        return index;
 }
 
-static void edit_cb(GtkButton *button, GtkTreeView *tree_view)
+static void edit_cb(GtkButton *button, int w_idx)
 {
        int index;
        GtkTreeIter iter;
-       GtkListStore *model = cylinder_list.model;
+       GtkListStore *model = cylinder_list[w_idx].model;
+       GtkTreeView *tree_view = cylinder_list[w_idx].tree_view;
        GtkTreeSelection *selection;
        cylinder_t cyl;
 
@@ -1083,11 +1152,12 @@ static void edit_cb(GtkButton *button, GtkTreeView *tree_view)
        repaint_dive();
 }
 
-static void add_cb(GtkButton *button, GtkTreeView *tree_view)
+static void add_cb(GtkButton *button, int w_idx)
 {
-       int index = cylinder_list.max_index;
+       int index = cylinder_list[w_idx].max_index;
        GtkTreeIter iter;
-       GtkListStore *model = cylinder_list.model;
+       GtkListStore *model = cylinder_list[w_idx].model;
+       GtkTreeView *tree_view = cylinder_list[w_idx].tree_view;
        GtkTreeSelection *selection;
        cylinder_t cyl;
 
@@ -1100,15 +1170,16 @@ static void add_cb(GtkButton *button, GtkTreeView *tree_view)
        selection = gtk_tree_view_get_selection(tree_view);
        gtk_tree_selection_select_iter(selection, &iter);
 
-       cylinder_list.max_index++;
-       gtk_widget_set_sensitive(cylinder_list.add, cylinder_list.max_index < MAX_CYLINDERS);
+       cylinder_list[w_idx].max_index++;
+       gtk_widget_set_sensitive(cylinder_list[w_idx].add, cylinder_list[w_idx].max_index < MAX_CYLINDERS);
 }
 
-static void del_cb(GtkButton *button, GtkTreeView *tree_view)
+static void del_cb(GtkButton *button, int w_idx)
 {
        int index, nr;
        GtkTreeIter iter;
-       GtkListStore *model = cylinder_list.model;
+       GtkListStore *model = cylinder_list[w_idx].model;
+       GtkTreeView *tree_view = cylinder_list[w_idx].tree_view;
        GtkTreeSelection *selection;
        struct dive *dive;
        cylinder_t *cyl;
@@ -1125,27 +1196,28 @@ static void del_cb(GtkButton *button, GtkTreeView *tree_view)
        if (!dive)
                return;
        cyl = dive->cylinder + index;
-       nr = cylinder_list.max_index - index - 1;
+       nr = cylinder_list[w_idx].max_index - index - 1;
 
        gtk_list_store_remove(model, &iter);
 
-       cylinder_list.max_index--;
+       cylinder_list[w_idx].max_index--;
        memmove(cyl, cyl+1, nr*sizeof(*cyl));
        memset(cyl+nr, 0, sizeof(*cyl));
 
        mark_divelist_changed(TRUE);
        flush_divelist(dive);
 
-       gtk_widget_set_sensitive(cylinder_list.edit, 0);
-       gtk_widget_set_sensitive(cylinder_list.del, 0);
-       gtk_widget_set_sensitive(cylinder_list.add, 1);
+       gtk_widget_set_sensitive(cylinder_list[w_idx].edit, 0);
+       gtk_widget_set_sensitive(cylinder_list[w_idx].del, 0);
+       gtk_widget_set_sensitive(cylinder_list[w_idx].add, 1);
 }
 
-static void ws_edit_cb(GtkButton *button, GtkTreeView *tree_view)
+static void ws_edit_cb(GtkButton *button, int w_idx)
 {
        int index;
        GtkTreeIter iter;
-       GtkListStore *model = weightsystem_list.model;
+       GtkListStore *model = weightsystem_list[w_idx].model;
+       GtkTreeView *tree_view = weightsystem_list[w_idx].tree_view;
        GtkTreeSelection *selection;
        weightsystem_t ws;
 
@@ -1163,11 +1235,12 @@ static void ws_edit_cb(GtkButton *button, GtkTreeView *tree_view)
        repaint_dive();
 }
 
-static void ws_add_cb(GtkButton *button, GtkTreeView *tree_view)
+static void ws_add_cb(GtkButton *button, int w_idx)
 {
-       int index = weightsystem_list.max_index;
+       int index = weightsystem_list[w_idx].max_index;
        GtkTreeIter iter;
-       GtkListStore *model = weightsystem_list.model;
+       GtkListStore *model = weightsystem_list[w_idx].model;
+       GtkTreeView *tree_view = weightsystem_list[w_idx].tree_view;
        GtkTreeSelection *selection;
        weightsystem_t ws;
 
@@ -1180,15 +1253,16 @@ static void ws_add_cb(GtkButton *button, GtkTreeView *tree_view)
        selection = gtk_tree_view_get_selection(tree_view);
        gtk_tree_selection_select_iter(selection, &iter);
 
-       weightsystem_list.max_index++;
-       gtk_widget_set_sensitive(weightsystem_list.add, weightsystem_list.max_index < MAX_WEIGHTSYSTEMS);
+       weightsystem_list[w_idx].max_index++;
+       gtk_widget_set_sensitive(weightsystem_list[w_idx].add, weightsystem_list[w_idx].max_index < MAX_WEIGHTSYSTEMS);
 }
 
-static void ws_del_cb(GtkButton *button, GtkTreeView *tree_view)
+static void ws_del_cb(GtkButton *button, int w_idx)
 {
        int index, nr;
        GtkTreeIter iter;
-       GtkListStore *model = weightsystem_list.model;
+       GtkListStore *model = weightsystem_list[w_idx].model;
+       GtkTreeView *tree_view = weightsystem_list[w_idx].tree_view;
        GtkTreeSelection *selection;
        struct dive *dive;
        weightsystem_t *ws;
@@ -1205,20 +1279,20 @@ static void ws_del_cb(GtkButton *button, GtkTreeView *tree_view)
        if (!dive)
                return;
        ws = dive->weightsystem + index;
-       nr = weightsystem_list.max_index - index - 1;
+       nr = weightsystem_list[w_idx].max_index - index - 1;
 
        gtk_list_store_remove(model, &iter);
 
-       weightsystem_list.max_index--;
+       weightsystem_list[w_idx].max_index--;
        memmove(ws, ws+1, nr*sizeof(*ws));
        memset(ws+nr, 0, sizeof(*ws));
 
        mark_divelist_changed(TRUE);
        flush_divelist(dive);
 
-       gtk_widget_set_sensitive(weightsystem_list.edit, 0);
-       gtk_widget_set_sensitive(weightsystem_list.del, 0);
-       gtk_widget_set_sensitive(weightsystem_list.add, 1);
+       gtk_widget_set_sensitive(weightsystem_list[w_idx].edit, 0);
+       gtk_widget_set_sensitive(weightsystem_list[w_idx].del, 0);
+       gtk_widget_set_sensitive(weightsystem_list[w_idx].add, 1);
 }
 
 static GtkListStore *create_tank_size_model(void)
@@ -1338,33 +1412,33 @@ static void selection_cb(GtkTreeSelection *selection, struct equipment_list *lis
 static void row_activated_cb(GtkTreeView *tree_view,
                        GtkTreePath *path,
                        GtkTreeViewColumn *column,
-                       GtkTreeModel *model)
+                       int w_idx)
 {
-       edit_cb(NULL, tree_view);
+       edit_cb(NULL, w_idx);
 }
 
 static void ws_row_activated_cb(GtkTreeView *tree_view,
                        GtkTreePath *path,
                        GtkTreeViewColumn *column,
-                       GtkTreeModel *model)
+                       int w_idx)
 {
-       ws_edit_cb(NULL, tree_view);
+       ws_edit_cb(NULL, w_idx);
 }
 
-GtkWidget *cylinder_list_widget(void)
+GtkWidget *cylinder_list_widget(int w_idx)
 {
-       GtkListStore *model = cylinder_list.model;
+       GtkListStore *model = cylinder_list[w_idx].model;
        GtkWidget *tree_view;
        GtkTreeSelection *selection;
 
        tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
        gtk_widget_set_can_focus(tree_view, FALSE);
 
-       g_signal_connect(tree_view, "row-activated", G_CALLBACK(row_activated_cb), model);
+       g_signal_connect(tree_view, "row-activated", G_CALLBACK(row_activated_cb), GINT_TO_POINTER(w_idx));
 
        selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
        gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
-       g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), &cylinder_list);
+       g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), &cylinder_list[w_idx]);
 
        g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
                                          "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH,
@@ -1380,19 +1454,19 @@ GtkWidget *cylinder_list_widget(void)
        return tree_view;
 }
 
-GtkWidget *weightsystem_list_widget(void)
+GtkWidget *weightsystem_list_widget(int w_idx)
 {
-       GtkListStore *model = weightsystem_list.model;
+       GtkListStore *model = weightsystem_list[w_idx].model;
        GtkWidget *tree_view;
        GtkTreeSelection *selection;
 
        tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
        gtk_widget_set_can_focus(tree_view, FALSE);
-       g_signal_connect(tree_view, "row-activated", G_CALLBACK(ws_row_activated_cb), model);
+       g_signal_connect(tree_view, "row-activated", G_CALLBACK(ws_row_activated_cb), GINT_TO_POINTER(w_idx));
 
        selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
        gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
-       g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), &weightsystem_list);
+       g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), &weightsystem_list[w_idx]);
 
        g_object_set(G_OBJECT(tree_view), "headers-visible", TRUE,
                                          "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_BOTH,
@@ -1405,7 +1479,7 @@ GtkWidget *weightsystem_list_widget(void)
        return tree_view;
 }
 
-static GtkWidget *cylinder_list_create(void)
+static GtkWidget *cylinder_list_create(int w_idx)
 {
        GtkListStore *model;
 
@@ -1418,11 +1492,11 @@ static GtkWidget *cylinder_list_create(void)
                G_TYPE_INT,             /* CYL_O2: permille */
                G_TYPE_INT              /* CYL_HE: permille */
                );
-       cylinder_list.model = model;
-       return cylinder_list_widget();
+       cylinder_list[w_idx].model = model;
+       return cylinder_list_widget(w_idx);
 }
 
-static GtkWidget *weightsystem_list_create(void)
+static GtkWidget *weightsystem_list_create(int w_idx)
 {
        GtkListStore *model;
 
@@ -1430,11 +1504,11 @@ static GtkWidget *weightsystem_list_create(void)
                G_TYPE_STRING,          /* WS_DESC: utf8 */
                G_TYPE_INT              /* WS_WEIGHT: grams */
                );
-       weightsystem_list.model = model;
-       return weightsystem_list_widget();
+       weightsystem_list[w_idx].model = model;
+       return weightsystem_list_widget(w_idx);
 }
 
-GtkWidget *equipment_widget(void)
+GtkWidget *equipment_widget(int w_idx)
 {
        GtkWidget *vbox, *hbox, *frame, *framebox, *tree_view;
        GtkWidget *add, *del, *edit;
@@ -1442,14 +1516,17 @@ GtkWidget *equipment_widget(void)
        vbox = gtk_vbox_new(FALSE, 3);
 
        /*
-        * We create the cylinder size model at startup, since
-        * we're going to share it across all cylinders and all
-        * dives. So if you add a new cylinder type in one dive,
-        * it will show up when you edit the cylinder types for
-        * another dive.
+        * We create the cylinder size (and weightsystem) models
+        * at startup for the primary cylinder / weightsystem widget,
+        * since we're going to share it across all cylinders and all
+        * dives. So if you add a new cylinder type or weightsystem in
+        * one dive, it will show up when you edit the cylinder types
+        * or weightsystems for another dive.
         */
-       cylinder_model = create_tank_size_model();
-       tree_view = cylinder_list_create();
+       if (w_idx == W_IDX_PRIMARY)
+               cylinder_model = create_tank_size_model();
+       tree_view = cylinder_list_create(w_idx);
+       cylinder_list[w_idx].tree_view = GTK_TREE_VIEW(tree_view);
 
        hbox = gtk_hbox_new(FALSE, 3);
        gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
@@ -1475,19 +1552,21 @@ GtkWidget *equipment_widget(void)
        gtk_box_pack_start(GTK_BOX(hbox), add, FALSE, FALSE, 0);
        gtk_box_pack_start(GTK_BOX(hbox), del, FALSE, FALSE, 0);
 
-       cylinder_list.edit = edit;
-       cylinder_list.add = add;
-       cylinder_list.del = del;
+       cylinder_list[w_idx].edit = edit;
+       cylinder_list[w_idx].add = add;
+       cylinder_list[w_idx].del = del;
 
-       g_signal_connect(edit, "clicked", G_CALLBACK(edit_cb), tree_view);
-       g_signal_connect(add, "clicked", G_CALLBACK(add_cb), tree_view);
-       g_signal_connect(del, "clicked", G_CALLBACK(del_cb), tree_view);
+       g_signal_connect(edit, "clicked", G_CALLBACK(edit_cb), GINT_TO_POINTER(w_idx));
+       g_signal_connect(add, "clicked", G_CALLBACK(add_cb), GINT_TO_POINTER(w_idx));
+       g_signal_connect(del, "clicked", G_CALLBACK(del_cb), GINT_TO_POINTER(w_idx));
 
        hbox = gtk_hbox_new(FALSE, 3);
        gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
 
-       weightsystem_model = create_weightsystem_model();
-       tree_view = weightsystem_list_create();
+       if (w_idx == W_IDX_PRIMARY)
+               weightsystem_model = create_weightsystem_model();
+       tree_view = weightsystem_list_create(w_idx);
+       weightsystem_list[w_idx].tree_view = GTK_TREE_VIEW(tree_view);
 
        frame = gtk_frame_new("Weight");
        gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, FALSE, 3);
@@ -1510,13 +1589,13 @@ GtkWidget *equipment_widget(void)
        gtk_box_pack_start(GTK_BOX(hbox), add, FALSE, FALSE, 0);
        gtk_box_pack_start(GTK_BOX(hbox), del, FALSE, FALSE, 0);
 
-       weightsystem_list.edit = edit;
-       weightsystem_list.add = add;
-       weightsystem_list.del = del;
+       weightsystem_list[w_idx].edit = edit;
+       weightsystem_list[w_idx].add = add;
+       weightsystem_list[w_idx].del = del;
 
-       g_signal_connect(edit, "clicked", G_CALLBACK(ws_edit_cb), tree_view);
-       g_signal_connect(add, "clicked", G_CALLBACK(ws_add_cb), tree_view);
-       g_signal_connect(del, "clicked", G_CALLBACK(ws_del_cb), tree_view);
+       g_signal_connect(edit, "clicked", G_CALLBACK(ws_edit_cb), GINT_TO_POINTER(w_idx));
+       g_signal_connect(add, "clicked", G_CALLBACK(ws_add_cb), GINT_TO_POINTER(w_idx));
+       g_signal_connect(del, "clicked", G_CALLBACK(ws_del_cb), GINT_TO_POINTER(w_idx));
 
        return vbox;
 }
diff --git a/file.c b/file.c
index aff1d51f2d2c87b470f2b61b23d6c8b945870f73..e0163909e9160f59f1f74ea667e50ec52684ef30 100644 (file)
--- a/file.c
+++ b/file.c
@@ -10,7 +10,7 @@
 
 static int readfile(const char *filename, struct memblock *mem)
 {
-       int ret, fd = open(filename, O_RDONLY);
+       int ret, fd;
        struct stat st;
        char *buf;
 
@@ -94,6 +94,125 @@ static int try_to_open_suunto(const char *filename, struct memblock *mem, GError
        return success;
 }
 
+static time_t parse_date(const char *date)
+{
+       int hour, min, sec;
+       struct tm tm;
+       char *p;
+
+       memset(&tm, 0, sizeof(tm));
+       tm.tm_mday = strtol(date, &p, 10);
+       if (tm.tm_mday < 1 || tm.tm_mday > 31)
+               return 0;
+       for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
+               if (!memcmp(p, monthname(tm.tm_mon), 3))
+                       break;
+       }
+       if (tm.tm_mon > 11)
+               return 0;
+       date = p+3;
+       tm.tm_year = strtol(date, &p, 10);
+       if (date == p)
+               return 0;
+       if (tm.tm_year < 70)
+               tm.tm_year += 2000;
+       if (tm.tm_year < 100)
+               tm.tm_year += 1900;
+       if (sscanf(p, "%d:%d:%d", &hour, &min, &sec) != 3)
+               return 0;
+       tm.tm_hour = hour;
+       tm.tm_min = min;
+       tm.tm_sec = sec;
+       return utc_mktime(&tm);
+}
+
+enum csv_format {
+       CSV_DEPTH, CSV_TEMP, CSV_PRESSURE
+};
+
+static void add_sample_data(struct sample *sample, enum csv_format type, double val)
+{
+       switch (type) {
+       case CSV_DEPTH:
+               sample->depth.mm = feet_to_mm(val);
+               break;
+       case CSV_TEMP:
+               sample->temperature.mkelvin = F_to_mkelvin(val);
+               break;
+       case CSV_PRESSURE:
+               sample->cylinderpressure.mbar = psi_to_mbar(val*4);
+               break;
+       }
+}
+
+/*
+ * Cochran comma-separated values: depth in feet, temperature in F, pressure in psi.
+ *
+ * They start with eight comma-separated fields like:
+ *
+ *   filename: {C:\Analyst4\can\T036785.can},{C:\Analyst4\can\K031892.can}
+ *   divenr: %d
+ *   datetime: {03Sep11 16:37:22},{15Dec11 18:27:02}
+ *   ??: 1
+ *   serialnr??: {CCI134},{CCI207}
+ *   computer??: {GeminiII},{CommanderIII}
+ *   computer??: {GeminiII},{CommanderIII}
+ *   ??: 1
+ *
+ * Followed by the data values (all comma-separated, all one long line).
+ */
+static int try_to_open_csv(const char *filename, struct memblock *mem, enum csv_format type)
+{
+       char *p = mem->buffer;
+       char *header[8];
+       int i, time;
+       time_t date;
+       struct dive *dive;
+
+       for (i = 0; i < 8; i++) {
+               header[i] = p;
+               p = strchr(p, ',');
+               if (!p)
+                       return 0;
+               p++;
+       }
+
+       date = parse_date(header[2]);
+       if (!date)
+               return 0;
+
+       dive = alloc_dive();
+       dive->when = date;
+       dive->number = atoi(header[1]);
+
+       time = 0;
+       for (;;) {
+               char *end;
+               double val;
+               struct sample *sample;
+
+               errno = 0;
+               val = strtod(p,&end);
+               if (end == p)
+                       break;
+               if (errno)
+                       break;
+
+               sample = prepare_sample(&dive);
+               sample->time.seconds = time;
+               add_sample_data(sample, type, val);
+               finish_sample(dive);
+
+               time++;
+               dive->duration.seconds = time;
+               if (*end != ',')
+                       break;
+               p = end+1;
+       }
+       record_dive(dive);
+       return 1;
+}
+
 static int open_by_filename(const char *filename, const char *fmt, struct memblock *mem, GError **error)
 {
        /* Suunto Dive Manager files: SDE */
@@ -104,6 +223,14 @@ static int open_by_filename(const char *filename, const char *fmt, struct memblo
        if (!strcasecmp(fmt, "CAN"))
                return try_to_open_cochran(filename, mem, error);
 
+       /* Cochran export comma-separated-value files */
+       if (!strcasecmp(fmt, "DPT"))
+               return try_to_open_csv(filename, mem, CSV_DEPTH);
+       if (!strcasecmp(fmt, "TMP"))
+               return try_to_open_csv(filename, mem, CSV_TEMP);
+       if (!strcasecmp(fmt, "HP1"))
+               return try_to_open_csv(filename, mem, CSV_PRESSURE);
+
        return 0;
 }
 
index 4b9f002af60d5967c30338e607ed229e0b9fbd78..f25fbaa53f6588a7968219784190d0cdcf4fff26 100644 (file)
--- a/gtk-gui.c
+++ b/gtk-gui.c
@@ -21,6 +21,8 @@ GtkWidget *main_vbox;
 GtkWidget *error_info_bar;
 GtkWidget *error_label;
 GtkWidget *vpane, *hpane;
+GtkWidget *notebook;
+
 int        error_count;
 extern char zoomed_plot;
 
@@ -32,12 +34,14 @@ static GtkWidget *dive_profile;
 
 visible_cols_t visible_cols = {TRUE, FALSE};
 
-static const char *default_dive_computer;
+static const char *default_dive_computer_vendor;
+static const char *default_dive_computer_product;
 static const char *default_dive_computer_device;
 
-static int is_default_dive_computer(const char *name)
+static int is_default_dive_computer(const char *vendor, const char *product)
 {
-       return default_dive_computer && !strcmp(name, default_dive_computer);
+       return default_dive_computer_vendor && !strcmp(vendor, default_dive_computer_vendor) &&
+               default_dive_computer_product && !strcmp(product, default_dive_computer_product);
 }
 
 static int is_default_dive_computer_device(const char *name)
@@ -45,14 +49,18 @@ static int is_default_dive_computer_device(const char *name)
        return default_dive_computer_device && !strcmp(name, default_dive_computer_device);
 }
 
-static void set_default_dive_computer(const char *name)
+static void set_default_dive_computer(const char *vendor, const char *product)
 {
-       if (!name || !*name)
+       if (!vendor || !*vendor)
+               return;
+       if (!product || !*product)
                return;
-       if (is_default_dive_computer(name))
+       if (is_default_dive_computer(vendor, product))
                return;
-       default_dive_computer = name;
-       subsurface_set_conf("dive_computer", PREF_STRING, name);
+       default_dive_computer_vendor = vendor;
+       default_dive_computer_product = product;
+       subsurface_set_conf("dive_computer_vendor", PREF_STRING, vendor);
+       subsurface_set_conf("dive_computer_product", PREF_STRING, product);
 }
 
 static void set_default_dive_computer_device(const char *name)
@@ -165,48 +173,75 @@ static void file_open(GtkWidget *w, gpointer data)
        gtk_widget_destroy(dialog);
 }
 
-static void file_save(GtkWidget *w, gpointer data)
+static void file_save_as(GtkWidget *w, gpointer data)
 {
        GtkWidget *dialog;
-       dialog = gtk_file_chooser_dialog_new("Save File",
+       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 (!existing_filename) {
-               gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
-       } else
-               gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog), existing_filename);
 
+       gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), existing_filename);
        if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
-               char *filename;
                filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+       }
+       gtk_widget_destroy(dialog);
+
+       if (filename){
                save_dives(filename);
+               set_filename(filename);
                g_free(filename);
                mark_divelist_changed(FALSE);
        }
-       gtk_widget_destroy(dialog);
 }
 
-static void ask_save_changes()
+static void file_save(GtkWidget *w, gpointer data)
+{
+       if (!existing_filename)
+               return file_save_as(w, data);
+
+       save_dives(existing_filename);
+       mark_divelist_changed(FALSE);
+}
+
+static gboolean ask_save_changes()
 {
        GtkWidget *dialog, *label, *content;
+       gboolean quit = TRUE;
        dialog = gtk_dialog_new_with_buttons("Save Changes?",
                GTK_WINDOW(main_window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
                GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
                GTK_STOCK_NO, GTK_RESPONSE_NO,
+               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                NULL);
        content = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
-       label = gtk_label_new ("You have unsaved changes\nWould you like to save those before exiting the program?");
+
+       if (!existing_filename){
+               label = gtk_label_new (
+                       "You have unsaved changes\nWould you like to save those before exiting the program?");
+       } else {
+               char *label_text = (char*) malloc(sizeof(char) * (92 + strlen(existing_filename)));
+               sprintf(label_text,
+                       "You have unsaved changes to file: %s \nWould you like to save those before exiting the program?",
+                       existing_filename);
+               label = gtk_label_new (label_text);
+               g_free(label_text);
+       }
        gtk_container_add (GTK_CONTAINER (content), label);
        gtk_widget_show_all (dialog);
        gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
-       if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+       gint outcode = gtk_dialog_run(GTK_DIALOG(dialog));
+       if (outcode == GTK_RESPONSE_ACCEPT) {
                file_save(NULL,NULL);
+       } else if (outcode == GTK_RESPONSE_CANCEL) {
+               quit = FALSE;
        }
        gtk_widget_destroy(dialog);
+       return quit;
 }
 
 static gboolean on_delete(GtkWidget* w, gpointer data)
@@ -214,10 +249,15 @@ static gboolean on_delete(GtkWidget* w, gpointer data)
        /* Make sure to flush any modified dive data */
        update_dive(NULL);
 
+       gboolean quit = TRUE;
        if (unsaved_changes())
-               ask_save_changes();
+               quit = ask_save_changes();
 
-       return FALSE; /* go ahead, kill the program, we're good now */
+       if (quit){
+               return FALSE; /* go ahead, kill the program, we're good now */
+       } else {
+               return TRUE; /* We are not leaving */
+       }
 }
 
 static void on_destroy(GtkWidget* w, gpointer data)
@@ -230,9 +270,13 @@ static void quit(GtkWidget *w, gpointer data)
        /* Make sure to flush any modified dive data */
        update_dive(NULL);
 
+       gboolean quit = TRUE;
        if (unsaved_changes())
-               ask_save_changes();
-       gtk_main_quit();
+               quit = ask_save_changes();
+
+       if (quit){
+               gtk_main_quit();
+       }
 }
 
 GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title,
@@ -343,6 +387,8 @@ OPTIONCALLBACK(otu_toggle, visible_cols.otu)
 OPTIONCALLBACK(sac_toggle, visible_cols.sac)
 OPTIONCALLBACK(nitrox_toggle, visible_cols.nitrox)
 OPTIONCALLBACK(temperature_toggle, visible_cols.temperature)
+OPTIONCALLBACK(totalweight_toggle, visible_cols.totalweight)
+OPTIONCALLBACK(suit_toggle, visible_cols.suit)
 OPTIONCALLBACK(cylinder_toggle, visible_cols.cylinder)
 
 static void event_toggle(GtkWidget *w, gpointer _data)
@@ -398,37 +444,47 @@ static void preferences_dialog(GtkWidget *w, gpointer data)
                "lbs",  set_lbs, (output_units.weight == LBS),
                NULL);
 
-       frame = gtk_frame_new("Columns");
+       frame = gtk_frame_new("Show Columns");
        gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
 
        box = gtk_hbox_new(FALSE, 6);
        gtk_container_add(GTK_CONTAINER(frame), box);
 
-       button = gtk_check_button_new_with_label("Show Temp");
+       button = gtk_check_button_new_with_label("Temp");
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.temperature);
        gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6);
        g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(temperature_toggle), NULL);
 
-       button = gtk_check_button_new_with_label("Show Cyl");
+       button = gtk_check_button_new_with_label("Cyl");
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.cylinder);
        gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6);
        g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(cylinder_toggle), NULL);
 
-       button = gtk_check_button_new_with_label("Show O" UTF8_SUBSCRIPT_2 "%");
+       button = gtk_check_button_new_with_label("O" UTF8_SUBSCRIPT_2 "%");
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.nitrox);
        gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6);
        g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(nitrox_toggle), NULL);
 
-       button = gtk_check_button_new_with_label("Show SAC");
+       button = gtk_check_button_new_with_label("SAC");
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.sac);
        gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6);
        g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(sac_toggle), NULL);
 
-       button = gtk_check_button_new_with_label("Show OTU");
+       button = gtk_check_button_new_with_label("OTU");
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.otu);
        gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6);
        g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(otu_toggle), NULL);
 
+       button = gtk_check_button_new_with_label("Weight");
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.totalweight);
+       gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6);
+       g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(totalweight_toggle), NULL);
+
+       button = gtk_check_button_new_with_label("Suit");
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), visible_cols.suit);
+       gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 6);
+       g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(suit_toggle), NULL);
+
        font = gtk_font_button_new_with_font(divelist_font);
        gtk_box_pack_start(GTK_BOX(vbox), font, FALSE, FALSE, 5);
 
@@ -452,6 +508,8 @@ static void preferences_dialog(GtkWidget *w, gpointer data)
                subsurface_set_conf("fahrenheit", PREF_BOOL, BOOL_TO_PTR(output_units.temperature == FAHRENHEIT));
                subsurface_set_conf("lbs", PREF_BOOL, BOOL_TO_PTR(output_units.weight == LBS));
                subsurface_set_conf("TEMPERATURE", PREF_BOOL, BOOL_TO_PTR(visible_cols.temperature));
+               subsurface_set_conf("TOTALWEIGHT", PREF_BOOL, BOOL_TO_PTR(visible_cols.totalweight));
+               subsurface_set_conf("SUIT", PREF_BOOL, BOOL_TO_PTR(visible_cols.suit));
                subsurface_set_conf("CYLINDER", PREF_BOOL, BOOL_TO_PTR(visible_cols.cylinder));
                subsurface_set_conf("NITROX", PREF_BOOL, BOOL_TO_PTR(visible_cols.nitrox));
                subsurface_set_conf("SAC", PREF_BOOL, BOOL_TO_PTR(visible_cols.sac));
@@ -604,11 +662,17 @@ static void view_info(GtkWidget *w, gpointer data)
        gtk_paned_set_position(GTK_PANED(hpane), 65535);
 }
 
-/* Ooh. I don't know how to get the half-way size. So I'm just using random numbers */
 static void view_three(GtkWidget *w, gpointer data)
 {
-       gtk_paned_set_position(GTK_PANED(hpane), 400);
-       gtk_paned_set_position(GTK_PANED(vpane), 200);
+       GtkAllocation alloc;
+       GtkRequisition requisition;
+
+       gtk_widget_get_allocation(hpane, &alloc);
+       gtk_paned_set_position(GTK_PANED(hpane), alloc.width/2);
+       gtk_widget_get_allocation(vpane, &alloc);
+       gtk_widget_size_request(notebook, &requisition);
+       /* pick the requested size for the notebook plus 6 pixels for frame */
+       gtk_paned_set_position(GTK_PANED(vpane), requisition.height + 6);
 }
 
 static void toggle_zoom(GtkWidget *w, gpointer data)
@@ -619,16 +683,18 @@ static void toggle_zoom(GtkWidget *w, gpointer data)
 }
 
 static GtkActionEntry menu_items[] = {
-       { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
-       { "LogMenuAction",  GTK_STOCK_FILE, "Log", NULL, NULL, NULL},
-       { "ViewMenuAction",  GTK_STOCK_FILE, "View", NULL, NULL, NULL},
-       { "FilterMenuAction",  GTK_STOCK_FILE, "Filter", NULL, NULL, NULL},
-       { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
+       { "FileMenuAction", NULL, "File", NULL, NULL, NULL},
+       { "LogMenuAction",  NULL, "Log", NULL, NULL, NULL},
+       { "ViewMenuAction",  NULL, "View", NULL, NULL, NULL},
+       { "FilterMenuAction",  NULL, "Filter", NULL, NULL, NULL},
+       { "HelpMenuAction", NULL, "Help", NULL, NULL, NULL},
        { "OpenFile",       GTK_STOCK_OPEN, NULL,   CTRLCHAR "O", NULL, G_CALLBACK(file_open) },
        { "SaveFile",       GTK_STOCK_SAVE, NULL,   CTRLCHAR "S", NULL, G_CALLBACK(file_save) },
+       { "SaveAsFile",     GTK_STOCK_SAVE_AS, NULL,   SHIFTCHAR CTRLCHAR "S", NULL, G_CALLBACK(file_save_as) },
        { "Print",          GTK_STOCK_PRINT, NULL,  CTRLCHAR "P", NULL, G_CALLBACK(do_print) },
        { "Import",         NULL, "Import", NULL, NULL, G_CALLBACK(import_dialog) },
-       { "Preferences",    NULL, "Preferences", PREFERENCE_ACCEL, NULL, G_CALLBACK(preferences_dialog) },
+       { "AddDive",        GTK_STOCK_ADD, "Add Dive", NULL, NULL, G_CALLBACK(add_dive_cb) },
+       { "Preferences",    GTK_STOCK_PREFERENCES, "Preferences", PREFERENCE_ACCEL, NULL, G_CALLBACK(preferences_dialog) },
        { "Renumber",       NULL, "Renumber", NULL, NULL, G_CALLBACK(renumber_dialog) },
        { "SelectEvents",   NULL, "SelectEvents", NULL, NULL, G_CALLBACK(selectevents_dialog) },
        { "Quit",           GTK_STOCK_QUIT, NULL,   CTRLCHAR "Q", NULL, G_CALLBACK(quit) },
@@ -647,6 +713,7 @@ static const gchar* ui_string = " \
                        <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
                                <menuitem name=\"Open\" action=\"OpenFile\" /> \
                                <menuitem name=\"Save\" action=\"SaveFile\" /> \
+                               <menuitem name=\"Save As\" action=\"SaveAsFile\" /> \
                                <menuitem name=\"Print\" action=\"Print\" /> \
                                <separator name=\"Separator1\"/> \
                                <menuitem name=\"Preferences\" action=\"Preferences\" /> \
@@ -655,6 +722,7 @@ static const gchar* ui_string = " \
                        </menu> \
                        <menu name=\"LogMenu\" action=\"LogMenuAction\"> \
                                <menuitem name=\"Import\" action=\"Import\" /> \
+                               <menuitem name=\"Add Dive\" action=\"AddDive\" /> \
                                <separator name=\"Separator\"/> \
                                <menuitem name=\"Renumber\" action=\"Renumber\" /> \
                                <menuitem name=\"Toggle Zoom\" action=\"ToggleZoom\" /> \
@@ -698,11 +766,11 @@ static void switch_page(GtkNotebook *notebook, gint arg1, gpointer user_data)
 void init_ui(int *argcp, char ***argvp)
 {
        GtkWidget *win;
-       GtkWidget *notebook;
        GtkWidget *nb_page;
        GtkWidget *dive_list;
        GtkWidget *menubar;
        GtkWidget *vbox;
+       GtkWidget *scrolled;
        GdkScreen *screen;
        GtkIconTheme *icon_theme=NULL;
        GtkSettings *settings;
@@ -728,13 +796,16 @@ void init_ui(int *argcp, char ***argvp)
        /* an unset key is FALSE - all these are hidden by default */
        visible_cols.cylinder = PTR_TO_BOOL(subsurface_get_conf("CYLINDER", PREF_BOOL));
        visible_cols.temperature = PTR_TO_BOOL(subsurface_get_conf("TEMPERATURE", PREF_BOOL));
+       visible_cols.totalweight = PTR_TO_BOOL(subsurface_get_conf("TOTALWEIGHT", PREF_BOOL));
+       visible_cols.suit = PTR_TO_BOOL(subsurface_get_conf("SUIT", PREF_BOOL));
        visible_cols.nitrox = PTR_TO_BOOL(subsurface_get_conf("NITROX", PREF_BOOL));
        visible_cols.otu = PTR_TO_BOOL(subsurface_get_conf("OTU", PREF_BOOL));
        visible_cols.sac = PTR_TO_BOOL(subsurface_get_conf("SAC", PREF_BOOL));
 
        divelist_font = subsurface_get_conf("divelist_font", PREF_STRING);
 
-       default_dive_computer = subsurface_get_conf("dive_computer", PREF_STRING);
+       default_dive_computer_vendor = subsurface_get_conf("dive_computer_vendor", PREF_STRING);
+       default_dive_computer_product = subsurface_get_conf("dive_computer_product", PREF_STRING);
        default_dive_computer_device = subsurface_get_conf("dive_computer_device", PREF_STRING);
 
        error_info_bar = NULL;
@@ -771,13 +842,16 @@ void init_ui(int *argcp, char ***argvp)
 
        vpane = gtk_vpaned_new();
        gtk_box_pack_start(GTK_BOX(vbox), vpane, TRUE, TRUE, 3);
-
        hpane = gtk_hpaned_new();
        gtk_paned_add1(GTK_PANED(vpane), hpane);
+       g_signal_connect_after(G_OBJECT(vbox), "realize", G_CALLBACK(view_three), NULL);
 
        /* Notebook for dive info vs profile vs .. */
        notebook = gtk_notebook_new();
-       gtk_paned_add1(GTK_PANED(hpane), notebook);
+       scrolled = gtk_scrolled_window_new(NULL, NULL);
+       gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+       gtk_paned_add1(GTK_PANED(hpane), scrolled);
+       gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled), notebook);
        g_signal_connect(notebook, "switch-page", G_CALLBACK(switch_page), NULL);
 
        /* Create the actual divelist */
@@ -795,7 +869,7 @@ void init_ui(int *argcp, char ***argvp)
        gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new("Dive Notes"));
 
        /* Frame for dive equipment */
-       nb_page = equipment_widget();
+       nb_page = equipment_widget(W_IDX_PRIMARY);
        gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nb_page, gtk_label_new("Equipment"));
 
        /* Frame for single dive statistics */
@@ -924,20 +998,45 @@ static int fill_computer_list(GtkListStore *store)
 {
        int index = -1, i;
        GtkTreeIter iter;
-       struct device_list *list = device_list;
+       dc_iterator_t *iterator = NULL;
+       dc_descriptor_t *descriptor = NULL;
+
+       i = 0;
+       dc_descriptor_iterator(&iterator);
+       while (dc_iterator_next (iterator, &descriptor) == DC_STATUS_SUCCESS) {
+               const char *vendor = dc_descriptor_get_vendor(descriptor);
+               const char *product = dc_descriptor_get_product(descriptor);
 
-       for (list = device_list, i = 0 ; list->name ; list++, i++) {
                gtk_list_store_append(store, &iter);
                gtk_list_store_set(store, &iter,
-                       0, list->name,
-                       1, list->type,
+                       0, descriptor,
                        -1);
-               if (is_default_dive_computer(list->name))
+               if (is_default_dive_computer(vendor, product))
                        index = i;
+               i++;
        }
+       dc_iterator_free(iterator);
        return index;
 }
 
+void render_dive_computer(GtkCellLayout *cell,
+               GtkCellRenderer *renderer,
+               GtkTreeModel *model,
+               GtkTreeIter *iter,
+               gpointer data)
+{
+       char buffer[40];
+       dc_descriptor_t *descriptor = NULL;
+       const char *vendor, *product;
+
+       gtk_tree_model_get(model, iter, 0, &descriptor, -1);
+       vendor = dc_descriptor_get_vendor(descriptor);
+       product = dc_descriptor_get_product(descriptor);
+       snprintf(buffer, sizeof(buffer), "%s %s", vendor, product);
+       g_object_set(renderer, "text", buffer, NULL);
+}
+
+
 static GtkComboBox *dive_computer_selector(GtkWidget *vbox)
 {
        GtkWidget *hbox, *combo_box, *frame;
@@ -948,7 +1047,7 @@ static GtkComboBox *dive_computer_selector(GtkWidget *vbox)
        hbox = gtk_hbox_new(FALSE, 6);
        gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
 
-       model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
+       model = gtk_list_store_new(1, G_TYPE_POINTER);
        default_index = fill_computer_list(model);
 
        frame = gtk_frame_new("Dive computer");
@@ -959,7 +1058,7 @@ static GtkComboBox *dive_computer_selector(GtkWidget *vbox)
 
        renderer = gtk_cell_renderer_text_new();
        gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo_box), renderer, TRUE);
-       gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo_box), renderer, "text", 0, NULL);
+       gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(combo_box), renderer, render_dive_computer, NULL, NULL);
 
        gtk_combo_box_set_active(GTK_COMBO_BOX(combo_box), default_index);
 
@@ -1104,10 +1203,9 @@ repeat:
        gtk_widget_show_all(dialog);
        result = gtk_dialog_run(GTK_DIALOG(dialog));
        switch (result) {
-               int type;
+               dc_descriptor_t *descriptor;
                GtkTreeIter iter;
                GtkTreeModel *model;
-               const char *comp;
                GSList *list;
        case GTK_RESPONSE_ACCEPT:
                /* what happened - did the user pick a file? In that case
@@ -1116,17 +1214,23 @@ repeat:
                        gtk_widget_destroy(info);
                list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(XMLchooser));
                if (g_slist_length(list) == 0) {
+                       const char *vendor, *product;
+
                        if (!gtk_combo_box_get_active_iter(computer, &iter))
                                break;
                        model = gtk_combo_box_get_model(computer);
                        gtk_tree_model_get(model, &iter,
-                                       0, &comp,
-                                       1, &type,
+                                       0, &descriptor,
                                        -1);
-                       devicedata.type = type;
-                       devicedata.name = comp;
+
+                       vendor = dc_descriptor_get_vendor(descriptor);
+                       product = dc_descriptor_get_product(descriptor);
+
+                       devicedata.descriptor = descriptor;
+                       devicedata.vendor = vendor;
+                       devicedata.product = product;
                        devicedata.devname = gtk_entry_get_text(device);
-                       set_default_dive_computer(devicedata.name);
+                       set_default_dive_computer(vendor, product);
                        set_default_dive_computer_device(devicedata.devname);
                        info = import_dive_computer(&devicedata, GTK_DIALOG(dialog));
                        if (info)
@@ -1156,7 +1260,9 @@ void update_progressbar_text(progressbar_t *progress, const char *text)
 
 void set_filename(const char *filename)
 {
-       if (!existing_filename && filename)
+       if (existing_filename)
+               free(existing_filename);
+       existing_filename = NULL;
+       if (filename)
                existing_filename = strdup(filename);
-       return;
 }
diff --git a/info.c b/info.c
index 813d58adcfb0572dc88c278ca13820bd2e0380d3..8db606344e7394ecbe44d5dbc961788d669c0dce 100644 (file)
--- a/info.c
+++ b/info.c
 #include <stdlib.h>
 #include <time.h>
 #include <ctype.h>
+#include <sys/time.h>
 
 #include "dive.h"
 #include "display.h"
 #include "display-gtk.h"
 #include "divelist.h"
 
-static GtkEntry *location, *buddy, *divemaster, *rating;
+static GtkEntry *location, *buddy, *divemaster, *rating, *suit;
 static GtkTextView *notes;
-static GtkListStore *location_list, *people_list, *star_list;
+static GtkListStore *location_list, *people_list, *star_list, *suit_list;
 
 static char *get_text(GtkTextView *view)
 {
@@ -42,16 +43,50 @@ static int text_changed(const char *old, const char *new)
                (!old && strcmp("",new));
 }
 
-static char *get_combo_box_entry_text(GtkComboBoxEntry *combo_box, char **textp)
+static const char *skip_space(const char *str)
+{
+       if (str) {
+               while (isspace(*str))
+                       str++;
+               if (!*str)
+                       str = NULL;
+       }
+       return str;
+}
+
+/*
+ * Get the string from a combo box.
+ *
+ * The "master" string is the string of the current dive - we only consider it
+ * changed if the old string is either empty, or matches that master string.
+ */
+static char *get_combo_box_entry_text(GtkComboBoxEntry *combo_box, char **textp, const char *master)
 {
        char *old = *textp;
+       const char *old_text;
        const gchar *new;
        GtkEntry *entry;
 
+       old_text = skip_space(old);
+       master = skip_space(master);
+
+       /*
+        * If we had a master string, and it doesn't match our old
+        * string, we will always pick the old value (it means that
+        * we're editing another dive's info that already had a
+        * valid value).
+        */
+       if (master && old_text)
+               if (strcmp(master, old_text))
+                       return NULL;
+
        entry = GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo_box)));
        new = gtk_entry_get_text(entry);
        while (isspace(*new))
                new++;
+       /* If the master string didn't change, don't change other dives either! */
+       if (!text_changed(master,new))
+               return NULL;
        if (!text_changed(old,new))
                return NULL;
        free(old);
@@ -95,6 +130,7 @@ void show_dive_info(struct dive *dive)
        SET_TEXT_VALUE(divemaster);
        SET_TEXT_VALUE(buddy);
        SET_TEXT_VALUE(location);
+       SET_TEXT_VALUE(suit);
        gtk_entry_set_text(rating, star_strings[dive->rating]);
        gtk_text_buffer_set_text(gtk_text_view_get_buffer(notes),
                dive && dive->notes ? dive->notes : "", -1);
@@ -130,17 +166,26 @@ static int delete_dive_info(struct dive *dive)
 
 static void info_menu_edit_cb(GtkMenuItem *menuitem, gpointer user_data)
 {
-       edit_dive_info(current_dive);
+       edit_multi_dive_info(NULL);
 }
 
 static void info_menu_delete_cb(GtkMenuItem *menuitem, gpointer user_data)
 {
+       /* this needs to delete all the selected dives as well, I guess? */
        delete_dive_info(current_dive);
 }
 
-static void add_menu_item(GtkMenu *menu, const char *label, void (*cb)(GtkMenuItem *, gpointer))
+static void add_menu_item(GtkMenu *menu, const char *label, const char *icon, void (*cb)(GtkMenuItem *, gpointer))
 {
-       GtkWidget *item = gtk_menu_item_new_with_label(label);
+       GtkWidget *item;
+       if (icon) {
+               GtkWidget *image;
+               item = gtk_image_menu_item_new_with_label(label);
+               image = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_MENU);
+               gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
+       } else {
+               item = gtk_menu_item_new_with_label(label);
+       }
        g_signal_connect(item, "activate", G_CALLBACK(cb), NULL);
        gtk_widget_show(item); /* Yes, really */
        gtk_menu_prepend(menu, item);
@@ -148,8 +193,8 @@ static void add_menu_item(GtkMenu *menu, const char *label, void (*cb)(GtkMenuIt
 
 static void populate_popup_cb(GtkTextView *entry, GtkMenu *menu, gpointer user_data)
 {
-       add_menu_item(menu, "Delete", info_menu_delete_cb);
-       add_menu_item(menu, "Edit", info_menu_edit_cb);
+       add_menu_item(menu, "Delete", GTK_STOCK_DELETE, info_menu_delete_cb);
+       add_menu_item(menu, "Edit", GTK_STOCK_EDIT, info_menu_edit_cb);
 }
 
 static GtkEntry *text_value(GtkWidget *box, const char *label)
@@ -241,6 +286,8 @@ static gboolean match_string_entry(GtkTreeModel *model, GtkTreePath *path, GtkTr
 
        gtk_tree_model_get(model, iter, 0, &entry, -1);
        cmp = strcmp(entry, string);
+       if (entry)
+               free(entry);
 
        /* Stop. The entry is bigger than the new one */
        if (cmp > 0)
@@ -295,6 +342,11 @@ void add_location(const char *string)
        add_string_list_entry(string, location_list);
 }
 
+void add_suit(const char *string)
+{
+       add_string_list_entry(string, suit_list);
+}
+
 static int get_rating(const char *string)
 {
        int rating_val = 0;
@@ -307,61 +359,69 @@ static int get_rating(const char *string)
 }
 
 struct dive_info {
-       GtkComboBoxEntry *location, *divemaster, *buddy, *rating;
+       GtkComboBoxEntry *location, *divemaster, *buddy, *rating, *suit;
        GtkTextView *notes;
 };
 
-static void save_dive_info_changes(struct dive *dive, struct dive_info *info)
+static void save_dive_info_changes(struct dive *dive, struct dive *master, struct dive_info *info)
 {
        char *old_text, *new_text;
        char *rating_string;
        int changed = 0;
 
-       new_text = get_combo_box_entry_text(info->location, &dive->location);
+       new_text = get_combo_box_entry_text(info->location, &dive->location, master->location);
        if (new_text) {
                add_location(new_text);
                changed = 1;
        }
 
-       new_text = get_combo_box_entry_text(info->divemaster, &dive->divemaster);
+       new_text = get_combo_box_entry_text(info->divemaster, &dive->divemaster, master->divemaster);
        if (new_text) {
                add_people(new_text);
                changed = 1;
        }
 
-       new_text = get_combo_box_entry_text(info->buddy, &dive->buddy);
+       new_text = get_combo_box_entry_text(info->buddy, &dive->buddy, master->buddy);
        if (new_text) {
                add_people(new_text);
                changed = 1;
        }
 
+       new_text = get_combo_box_entry_text(info->suit, &dive->suit, master->suit);
+       if (new_text) {
+               add_suit(new_text);
+               changed = 1;
+       }
+
        rating_string = strdup(star_strings[dive->rating]);
-       new_text = get_combo_box_entry_text(info->rating, &rating_string);
+       new_text = get_combo_box_entry_text(info->rating, &rating_string, star_strings[master->rating]);
        if (new_text) {
                dive->rating = get_rating(rating_string);
                free(rating_string);
                changed =1;
        }
 
-       old_text = dive->notes;
-       dive->notes = get_text(info->notes);
-       if (text_changed(old_text,dive->notes))
-               changed = 1;
-       if (old_text)
-               g_free(old_text);
-
+       if (info->notes) {
+               old_text = dive->notes;
+               dive->notes = get_text(info->notes);
+               if (text_changed(old_text,dive->notes))
+                       changed = 1;
+               if (old_text)
+                       g_free(old_text);
+       }
        if (changed) {
                mark_divelist_changed(TRUE);
                update_dive(dive);
        }
 }
 
-static void dive_info_widget(GtkWidget *box, struct dive *dive, struct dive_info *info)
+static void dive_info_widget(GtkWidget *box, struct dive *dive, struct dive_info *info, gboolean multi)
 {
-       GtkWidget *hbox, *label, *cylinder, *frame;
-       char buffer[80];
+       GtkWidget *hbox, *label, *frame, *equipment;
+       char buffer[80] = "Edit multiple dives";
 
-       divename(buffer, sizeof(buffer), dive);
+       if (!multi)
+               divename(buffer, sizeof(buffer), dive);
        label = gtk_label_new(buffer);
        gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 0);
 
@@ -377,30 +437,149 @@ static void dive_info_widget(GtkWidget *box, struct dive *dive, struct dive_info
        gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0);
 
        info->rating = text_entry(hbox, "Rating", star_list, star_strings[dive->rating]);
+       info->suit = text_entry(hbox, "Suit", suit_list, dive->suit);
 
-       info->notes = text_view(box, "Notes", READ_WRITE);
-       if (dive->notes && *dive->notes)
-               gtk_text_buffer_set_text(gtk_text_view_get_buffer(info->notes), dive->notes, -1);
-
+       /* only show notes if editing a single dive */
+       if (multi) {
+               info->notes = NULL;
+       } else {
+               info->notes = text_view(box, "Notes", READ_WRITE);
+               if (dive->notes && *dive->notes)
+                       gtk_text_buffer_set_text(gtk_text_view_get_buffer(info->notes), dive->notes, -1);
+       }
        hbox = gtk_hbox_new(FALSE, 3);
        gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, TRUE, 0);
 
-       frame = gtk_frame_new("Cylinder");
-       cylinder = cylinder_list_widget();
-       gtk_container_add(GTK_CONTAINER(frame), cylinder);
+       /* create a secondary Equipment widget */
+       frame = gtk_frame_new("Equipment");
+       equipment = equipment_widget(W_IDX_SECONDARY);
+       gtk_container_add(GTK_CONTAINER(frame), equipment);
        gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 0);
 }
 
-int edit_dive_info(struct dive *dive)
+/* we use these to find out if we edited the cylinder or weightsystem entries */
+static cylinder_t remember_cyl[MAX_CYLINDERS];
+static weightsystem_t remember_ws[MAX_WEIGHTSYSTEMS];
+#define CYL_BYTES sizeof(cylinder_t) * MAX_CYLINDERS
+#define WS_BYTES sizeof(weightsystem_t) * MAX_WEIGHTSYSTEMS
+
+void save_equipment_data(struct dive *dive)
+{
+       if (dive) {
+               memcpy(remember_cyl, dive->cylinder, CYL_BYTES);
+               memcpy(remember_ws, dive->weightsystem, WS_BYTES);
+       }
+}
+
+/* the editing happens on the master dive; we copy the equipment
+   data if it has changed in the master dive and the other dive
+   either has no entries for the equipment or the same entries
+   as the master dive had before it was edited */
+void update_equipment_data(struct dive *dive, struct dive *master)
+{
+       if (dive == master)
+               return;
+       if ( ! cylinders_equal(remember_cyl, master->cylinder) &&
+               (no_cylinders(dive->cylinder) ||
+                       cylinders_equal(dive->cylinder, remember_cyl)))
+               memcpy(dive->cylinder, master->cylinder, CYL_BYTES);
+       if (! weightsystems_equal(remember_ws, master->weightsystem) &&
+               (no_weightsystems(dive->weightsystem) ||
+                       weightsystems_equal(dive->weightsystem, remember_ws)))
+               memcpy(dive->weightsystem, master->weightsystem, WS_BYTES);
+}
+
+/* A negative index means "all selected" */
+int edit_multi_dive_info(struct dive *single_dive)
 {
        int success;
        GtkWidget *dialog, *vbox;
        struct dive_info info;
+       struct dive *master;
+
+       dialog = gtk_dialog_new_with_buttons("Dive Info",
+               GTK_WINDOW(main_window),
+               GTK_DIALOG_DESTROY_WITH_PARENT,
+               GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+               GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+               NULL);
 
+       vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+       master = single_dive;
+       if (!master)
+               master = current_dive;
+       dive_info_widget(vbox, master, &info, !single_dive);
+       show_dive_equipment(master, W_IDX_SECONDARY);
+       save_equipment_data(master);
+       gtk_widget_show_all(dialog);
+       success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT;
+       if (success) {
+               /* Update the non-current selected dives first */
+               if (!single_dive) {
+                       int i;
+                       struct dive *dive;
+
+                       for_each_dive(i, dive) {
+                               if (dive == master || !dive->selected)
+                                       continue;
+                               /* copy all "info" fields */
+                               save_dive_info_changes(dive, master, &info);
+                               /* copy the cylinders / weightsystems */
+                               update_equipment_data(dive, master);
+                               /* this is extremely inefficient... it loops through all
+                                  dives to find the right one - but we KNOW the index already */
+                               flush_divelist(dive);
+                       }
+               }
+
+               /* Update the master dive last! */
+               save_dive_info_changes(master, master, &info);
+               update_equipment_data(master, master);
+               flush_divelist(master);
+       }
+       gtk_widget_destroy(dialog);
+
+       return success;
+}
+
+int edit_dive_info(struct dive *dive)
+{
        if (!dive)
                return 0;
+       return edit_multi_dive_info(dive);
+}
 
-       dialog = gtk_dialog_new_with_buttons("Dive Info",
+static GtkWidget *frame_box(GtkWidget *vbox, const char *fmt, ...)
+{
+       va_list ap;
+       char buffer[64];
+       GtkWidget *frame, *hbox;
+
+       va_start(ap, fmt);
+       vsnprintf(buffer, sizeof(buffer), fmt, ap);
+       va_end(ap);
+
+       frame = gtk_frame_new(buffer);
+       gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, TRUE, 0);
+       hbox = gtk_hbox_new(0, 3);
+       gtk_container_add(GTK_CONTAINER(frame), hbox);
+       return hbox;
+}
+
+/* Fixme - should do at least depths too - a dive without a depth is kind of pointless */
+static time_t dive_time_widget(struct dive *dive)
+{
+       GtkWidget *dialog;
+       GtkWidget *cal, *hbox, *vbox, *box;
+       GtkWidget *h, *m;
+       GtkWidget *duration, *depth;
+       GtkWidget *label;
+       guint yval, mval, dval;
+       struct tm tm, *time;
+       int success;
+       double depthinterval, val;
+
+       dialog = gtk_dialog_new_with_buttons("Date and Time",
                GTK_WINDOW(main_window),
                GTK_DIALOG_DESTROY_WITH_PARENT,
                GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
@@ -408,16 +587,107 @@ int edit_dive_info(struct dive *dive)
                NULL);
 
        vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
-       dive_info_widget(vbox, dive, &info);
 
+       /* Calendar hbox */
+       hbox = frame_box(vbox, "Date:");
+       cal = gtk_calendar_new();
+       gtk_box_pack_start(GTK_BOX(hbox), cal, FALSE, TRUE, 0);
+
+       /* Time hbox */
+       hbox = frame_box(vbox, "Time");
+
+       h = gtk_spin_button_new_with_range (0.0, 23.0, 1.0);
+       m = gtk_spin_button_new_with_range (0.0, 59.0, 1.0);
+
+       /*
+        * If we have a dive selected, 'add dive' will default
+        * to one hour after the end of that dive. Otherwise,
+        * we'll just take the current time.
+        */
+       if (amount_selected == 1) {
+               time_t when = current_dive->when;
+               when += current_dive->duration.seconds;
+               when += 60*60;
+               time = gmtime(&when);
+       } else {
+               time_t now;
+               struct timeval tv;
+               gettimeofday(&tv, NULL);
+               now = tv.tv_sec;
+               time = localtime(&now);
+       }
+       gtk_calendar_select_month(GTK_CALENDAR(cal), time->tm_mon, time->tm_year + 1900);
+       gtk_calendar_select_day(GTK_CALENDAR(cal), time->tm_mday);
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(h), time->tm_hour);
+       gtk_spin_button_set_value(GTK_SPIN_BUTTON(m), (time->tm_min / 5)*5);
+
+       gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(h), TRUE);
+       gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(m), TRUE);
+
+       gtk_box_pack_end(GTK_BOX(hbox), m, FALSE, FALSE, 0);
+       label = gtk_label_new(":");
+       gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+       gtk_box_pack_end(GTK_BOX(hbox), h, FALSE, FALSE, 0);
+
+       hbox = gtk_hbox_new(TRUE, 3);
+       gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+
+       /* Duration hbox */
+       box = frame_box(hbox, "Duration (min)");
+       duration = gtk_spin_button_new_with_range (0.0, 1000.0, 1.0);
+       gtk_box_pack_end(GTK_BOX(box), duration, FALSE, FALSE, 0);
+
+       /* Depth box */
+       box = frame_box(hbox, "Depth (%s):", output_units.length == FEET ? "ft" : "m");
+       if (output_units.length == FEET) {
+               depthinterval = 1.0;
+       } else {
+               depthinterval = 0.1;
+       }
+       depth = gtk_spin_button_new_with_range (0.0, 1000.0, depthinterval);
+       gtk_box_pack_end(GTK_BOX(box), depth, FALSE, FALSE, 0);
+
+       /* All done, show it and wait for editing */
        gtk_widget_show_all(dialog);
        success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT;
-       if (success)
-               save_dive_info_changes(dive, &info);
+       if (!success) {
+               gtk_widget_destroy(dialog);
+               return 0;
+       }
+
+       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));
+
+       val = gtk_spin_button_get_value(GTK_SPIN_BUTTON(depth));
+       if (output_units.length == FEET) {
+               dive->maxdepth.mm = feet_to_mm(val);
+       } else {
+               dive->maxdepth.mm = val * 1000 + 0.5;
+       }
+
+       dive->duration.seconds = gtk_spin_button_get_value(GTK_SPIN_BUTTON(duration))*60;
 
        gtk_widget_destroy(dialog);
+       dive->when = utc_mktime(&tm);
 
-       return success;
+       return 1;
+}
+
+int add_new_dive(struct dive *dive)
+{
+       if (!dive)
+               return 0;
+
+       if (!dive_time_widget(dive))
+               return 0;
+
+       return edit_dive_info(dive);
 }
 
 GtkWidget *extended_dive_info_widget(void)
@@ -434,6 +704,7 @@ GtkWidget *extended_dive_info_widget(void)
        add_string_list_entry(THREE_STARS, star_list);
        add_string_list_entry(FOUR_STARS, star_list);
        add_string_list_entry(FIVE_STARS, star_list);
+       suit_list = gtk_list_store_new(1, G_TYPE_STRING);
 
        gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
        location = text_value(vbox, "Location");
@@ -448,6 +719,7 @@ GtkWidget *extended_dive_info_widget(void)
        gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
 
        rating = text_value(hbox, "Rating");
+       suit = text_value(hbox, "Suit");
 
        notes = text_view(vbox, "Notes", READ_ONLY);
        return vbox;
index e59dd6aff7e7f9faef4e55d35ba27d8d9b648c77..e362d1d2ba9ad13f018c5007fb3443acd879b6ea 100644 (file)
@@ -31,90 +31,22 @@ static GError *error(const char *fmt, ...)
        return error;
 }
 
-static parser_status_t create_parser(device_data_t *devdata, parser_t **parser)
+static dc_status_t create_parser(device_data_t *devdata, dc_parser_t **parser)
 {
-       switch (devdata->type) {
-       case DEVICE_TYPE_SUUNTO_SOLUTION:
-               return suunto_solution_parser_create(parser);
-
-       case DEVICE_TYPE_SUUNTO_EON:
-               return suunto_eon_parser_create(parser, 0);
-
-       case DEVICE_TYPE_SUUNTO_VYPER:
-               if (devdata->devinfo.model == 0x01)
-                       return suunto_eon_parser_create(parser, 1);
-               return suunto_vyper_parser_create(parser);
-
-       case DEVICE_TYPE_SUUNTO_VYPER2:
-       case DEVICE_TYPE_SUUNTO_D9:
-               return suunto_d9_parser_create(parser, devdata->devinfo.model);
-
-       case DEVICE_TYPE_UWATEC_ALADIN:
-       case DEVICE_TYPE_UWATEC_MEMOMOUSE:
-               return uwatec_memomouse_parser_create(parser, devdata->clock.devtime, devdata->clock.systime);
-
-       case DEVICE_TYPE_UWATEC_SMART:
-               return uwatec_smart_parser_create(parser, devdata->devinfo.model, devdata->clock.devtime, devdata->clock.systime);
-
-       case DEVICE_TYPE_REEFNET_SENSUS:
-               return reefnet_sensus_parser_create(parser, devdata->clock.devtime, devdata->clock.systime);
-
-       case DEVICE_TYPE_REEFNET_SENSUSPRO:
-               return reefnet_sensuspro_parser_create(parser, devdata->clock.devtime, devdata->clock.systime);
-
-       case DEVICE_TYPE_REEFNET_SENSUSULTRA:
-               return reefnet_sensusultra_parser_create(parser, devdata->clock.devtime, devdata->clock.systime);
-
-       case DEVICE_TYPE_OCEANIC_VTPRO:
-               return oceanic_vtpro_parser_create(parser);
-
-       case DEVICE_TYPE_OCEANIC_VEO250:
-               return oceanic_veo250_parser_create(parser, devdata->devinfo.model);
-
-       case DEVICE_TYPE_OCEANIC_ATOM2:
-               return oceanic_atom2_parser_create(parser, devdata->devinfo.model);
-
-       case DEVICE_TYPE_MARES_DARWIN:
-               return mares_darwin_parser_create(parser, devdata->devinfo.model);
-
-       case DEVICE_TYPE_MARES_NEMO:
-       case DEVICE_TYPE_MARES_PUCK:
-               return mares_nemo_parser_create(parser, devdata->devinfo.model);
-
-       case DEVICE_TYPE_MARES_ICONHD:
-               return mares_iconhd_parser_create(parser, devdata->devinfo.model);
-
-       case DEVICE_TYPE_HW_OSTC:
-               return hw_ostc_parser_create(parser NOT_FROG);
-
-#ifdef LIBDIVECOMPUTER_SUPPORTS_FROG
-       case DEVICE_TYPE_HW_FROG:
-               return hw_ostc_parser_create(parser, 1);
-#endif
-
-       case DEVICE_TYPE_CRESSI_EDY:
-       case DEVICE_TYPE_ZEAGLE_N2ITION3:
-               return cressi_edy_parser_create(parser, devdata->devinfo.model);
-
-       case DEVICE_TYPE_ATOMICS_COBALT:
-               return atomics_cobalt_parser_create(parser);
-
-       default:
-               return PARSER_STATUS_ERROR;
-       }
+       return dc_parser_new(parser, devdata->device);
 }
 
-static int parse_gasmixes(device_data_t *devdata, struct dive *dive, parser_t *parser, int ngases)
+static int parse_gasmixes(device_data_t *devdata, struct dive *dive, dc_parser_t *parser, int ngases)
 {
        int i;
 
        for (i = 0; i < ngases; i++) {
                int rc;
-               gasmix_t gasmix = {0};
+               dc_gasmix_t gasmix = {0};
                int o2, he;
 
-               rc = parser_get_field(parser, FIELD_TYPE_GASMIX, i, &gasmix);
-               if (rc != PARSER_STATUS_SUCCESS && rc != PARSER_STATUS_UNSUPPORTED)
+               rc = dc_parser_get_field(parser, DC_FIELD_GASMIX, i, &gasmix);
+               if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED)
                        return rc;
 
                if (i >= MAX_CYLINDERS)
@@ -132,10 +64,10 @@ static int parse_gasmixes(device_data_t *devdata, struct dive *dive, parser_t *p
                dive->cylinder[i].gasmix.o2.permille = o2;
                dive->cylinder[i].gasmix.he.permille = he;
        }
-       return PARSER_STATUS_SUCCESS;
+       return DC_STATUS_SUCCESS;
 }
 
-static void handle_event(struct dive *dive, struct sample *sample, parser_sample_value_t value)
+static void handle_event(struct dive *dive, struct sample *sample, dc_sample_value_t value)
 {
        int type, time;
        static const char *events[] = {
@@ -173,7 +105,7 @@ static void handle_event(struct dive *dive, struct sample *sample, parser_sample
 }
 
 void
-sample_cb(parser_sample_type_t type, parser_sample_value_t value, void *userdata)
+sample_cb(dc_sample_type_t type, dc_sample_value_t value, void *userdata)
 {
        int i;
        struct dive **divep = userdata;
@@ -181,40 +113,40 @@ sample_cb(parser_sample_type_t type, parser_sample_value_t value, void *userdata
        struct sample *sample;
 
        /*
-        * We fill in the "previous" sample - except for SAMPLE_TYPE_TIME,
+        * We fill in the "previous" sample - except for DC_SAMPLE_TIME,
         * which creates a new one.
         */
        sample = dive->samples ? dive->sample+dive->samples-1 : NULL;
 
        switch (type) {
-       case SAMPLE_TYPE_TIME:
+       case DC_SAMPLE_TIME:
                sample = prepare_sample(divep);
                sample->time.seconds = value.time;
                finish_sample(*divep);
                break;
-       case SAMPLE_TYPE_DEPTH:
+       case DC_SAMPLE_DEPTH:
                sample->depth.mm = value.depth * 1000 + 0.5;
                break;
-       case SAMPLE_TYPE_PRESSURE:
+       case DC_SAMPLE_PRESSURE:
                sample->cylinderindex = value.pressure.tank;
                sample->cylinderpressure.mbar = value.pressure.value * 1000 + 0.5;
                break;
-       case SAMPLE_TYPE_TEMPERATURE:
+       case DC_SAMPLE_TEMPERATURE:
                sample->temperature.mkelvin = (value.temperature + 273.15) * 1000 + 0.5;
                break;
-       case SAMPLE_TYPE_EVENT:
+       case DC_SAMPLE_EVENT:
                handle_event(dive, sample, value);
                break;
-       case SAMPLE_TYPE_RBT:
+       case DC_SAMPLE_RBT:
                printf("   <rbt>%u</rbt>\n", value.rbt);
                break;
-       case SAMPLE_TYPE_HEARTBEAT:
+       case DC_SAMPLE_HEARTBEAT:
                printf("   <heartbeat>%u</heartbeat>\n", value.heartbeat);
                break;
-       case SAMPLE_TYPE_BEARING:
+       case DC_SAMPLE_BEARING:
                printf("   <bearing>%u</bearing>\n", value.bearing);
                break;
-       case SAMPLE_TYPE_VENDOR:
+       case DC_SAMPLE_VENDOR:
                printf("   <vendor type=\"%u\" size=\"%u\">", value.vendor.type, value.vendor.size);
                for (i = 0; i < value.vendor.size; ++i)
                        printf("%02X", ((unsigned char *) value.vendor.data)[i]);
@@ -238,10 +170,10 @@ static void dev_info(device_data_t *devdata, const char *fmt, ...)
 
 static int import_dive_number = 0;
 
-static int parse_samples(device_data_t *devdata, struct dive **divep, parser_t *parser)
+static int parse_samples(device_data_t *devdata, struct dive **divep, dc_parser_t *parser)
 {
        // Parse the sample data.
-       return parser_samples_foreach(parser, sample_cb, divep);
+       return dc_parser_samples_foreach(parser, sample_cb, divep);
 }
 
 /*
@@ -275,31 +207,31 @@ static int dive_cb(const unsigned char *data, unsigned int size,
        void *userdata)
 {
        int rc;
-       parser_t *parser = NULL;
+       dc_parser_t *parser = NULL;
        device_data_t *devdata = userdata;
        dc_datetime_t dt = {0};
        struct tm tm;
        struct dive *dive;
 
        rc = create_parser(devdata, &parser);
-       if (rc != PARSER_STATUS_SUCCESS) {
-               dev_info(devdata, "Unable to create parser for %s", devdata->name);
+       if (rc != DC_STATUS_SUCCESS) {
+               dev_info(devdata, "Unable to create parser for %s %s", devdata->vendor, devdata->product);
                return rc;
        }
 
-       rc = parser_set_data(parser, data, size);
-       if (rc != PARSER_STATUS_SUCCESS) {
+       rc = dc_parser_set_data(parser, data, size);
+       if (rc != DC_STATUS_SUCCESS) {
                dev_info(devdata, "Error registering the data");
-               parser_destroy(parser);
+               dc_parser_destroy(parser);
                return rc;
        }
 
        import_dive_number++;
        dive = alloc_dive();
-       rc = parser_get_datetime(parser, &dt);
-       if (rc != PARSER_STATUS_SUCCESS && rc != PARSER_STATUS_UNSUPPORTED) {
+       rc = dc_parser_get_datetime(parser, &dt);
+       if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) {
                dev_info(devdata, "Error parsing the datetime");
-               parser_destroy (parser);
+               dc_parser_destroy(parser);
                return rc;
        }
 
@@ -315,49 +247,49 @@ static int dive_cb(const unsigned char *data, unsigned int size,
        dev_info(devdata, "Dive %d: %s %d %04d", import_dive_number,
                monthname(tm.tm_mon), tm.tm_mday, year(tm.tm_year));
        unsigned int divetime = 0;
-       rc = parser_get_field (parser, FIELD_TYPE_DIVETIME, 0, &divetime);
-       if (rc != PARSER_STATUS_SUCCESS && rc != PARSER_STATUS_UNSUPPORTED) {
+       rc = dc_parser_get_field (parser, DC_FIELD_DIVETIME, 0, &divetime);
+       if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) {
                dev_info(devdata, "Error parsing the divetime");
-               parser_destroy(parser);
+               dc_parser_destroy(parser);
                return rc;
        }
        dive->duration.seconds = divetime;
 
        // Parse the maxdepth.
        double maxdepth = 0.0;
-       rc = parser_get_field(parser, FIELD_TYPE_MAXDEPTH, 0, &maxdepth);
-       if (rc != PARSER_STATUS_SUCCESS && rc != PARSER_STATUS_UNSUPPORTED) {
+       rc = dc_parser_get_field(parser, DC_FIELD_MAXDEPTH, 0, &maxdepth);
+       if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) {
                dev_info(devdata, "Error parsing the maxdepth");
-               parser_destroy(parser);
+               dc_parser_destroy(parser);
                return rc;
        }
        dive->maxdepth.mm = maxdepth * 1000 + 0.5;
 
        // Parse the gas mixes.
        unsigned int ngases = 0;
-       rc = parser_get_field(parser, FIELD_TYPE_GASMIX_COUNT, 0, &ngases);
-       if (rc != PARSER_STATUS_SUCCESS && rc != PARSER_STATUS_UNSUPPORTED) {
+       rc = dc_parser_get_field(parser, DC_FIELD_GASMIX_COUNT, 0, &ngases);
+       if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) {
                dev_info(devdata, "Error parsing the gas mix count");
-               parser_destroy(parser);
+               dc_parser_destroy(parser);
                return rc;
        }
 
        rc = parse_gasmixes(devdata, dive, parser, ngases);
-       if (rc != PARSER_STATUS_SUCCESS) {
+       if (rc != DC_STATUS_SUCCESS) {
                dev_info(devdata, "Error parsing the gas mix");
-               parser_destroy(parser);
+               dc_parser_destroy(parser);
                return rc;
        }
 
        // Initialize the sample data.
        rc = parse_samples(devdata, &dive, parser);
-       if (rc != PARSER_STATUS_SUCCESS) {
+       if (rc != DC_STATUS_SUCCESS) {
                dev_info(devdata, "Error parsing the samples");
-               parser_destroy(parser);
+               dc_parser_destroy(parser);
                return rc;
        }
 
-       parser_destroy(parser);
+       dc_parser_destroy(parser);
 
        /* If we already saw this dive, abort. */
        if (find_dive(dive, devdata))
@@ -368,112 +300,41 @@ static int dive_cb(const unsigned char *data, unsigned int size,
 }
 
 
-static device_status_t import_device_data(device_t *device, device_data_t *devicedata)
+static dc_status_t import_device_data(dc_device_t *device, device_data_t *devicedata)
 {
-       return device_foreach(device, dive_cb, devicedata);
+       return dc_device_foreach(device, dive_cb, devicedata);
 }
 
-static device_status_t device_open(const char *devname,
-       device_type_t type,
-       device_t **device)
+static dc_status_t device_open(const char *devname,
+       dc_descriptor_t *descriptor,
+       dc_device_t **device)
 {
-       switch (type) {
-       case DEVICE_TYPE_SUUNTO_SOLUTION:
-               return suunto_solution_device_open(device, devname);
-
-       case DEVICE_TYPE_SUUNTO_EON:
-               return suunto_eon_device_open(device, devname);
-
-       case DEVICE_TYPE_SUUNTO_VYPER:
-               return suunto_vyper_device_open(device, devname);
-
-       case DEVICE_TYPE_SUUNTO_VYPER2:
-               return suunto_vyper2_device_open(device, devname);
-
-       case DEVICE_TYPE_SUUNTO_D9:
-               return suunto_d9_device_open(device, devname);
-
-       case DEVICE_TYPE_UWATEC_ALADIN:
-               return uwatec_aladin_device_open(device, devname);
-
-       case DEVICE_TYPE_UWATEC_MEMOMOUSE:
-               return uwatec_memomouse_device_open(device, devname);
-
-       case DEVICE_TYPE_UWATEC_SMART:
-               return uwatec_smart_device_open(device);
-
-       case DEVICE_TYPE_REEFNET_SENSUS:
-               return reefnet_sensus_device_open(device, devname);
-
-       case DEVICE_TYPE_REEFNET_SENSUSPRO:
-               return reefnet_sensuspro_device_open(device, devname);
-
-       case DEVICE_TYPE_REEFNET_SENSUSULTRA:
-               return reefnet_sensusultra_device_open(device, devname);
-
-       case DEVICE_TYPE_OCEANIC_VTPRO:
-               return oceanic_vtpro_device_open(device, devname);
-
-       case DEVICE_TYPE_OCEANIC_VEO250:
-               return oceanic_veo250_device_open(device, devname);
-
-       case DEVICE_TYPE_OCEANIC_ATOM2:
-               return oceanic_atom2_device_open(device, devname);
-
-       case DEVICE_TYPE_MARES_DARWIN:
-               return mares_darwin_device_open(device, devname, 0); /// last parameter is model type (taken from example), 0 seems to be standard, 1 is DARWIN_AIR => Darwin Air wont work if this is fixed here?
-
-       case DEVICE_TYPE_MARES_NEMO:
-               return mares_nemo_device_open(device, devname);
-
-       case DEVICE_TYPE_MARES_PUCK:
-               return mares_puck_device_open(device, devname);
-
-       case DEVICE_TYPE_MARES_ICONHD:
-               return mares_iconhd_device_open(device, devname);
-
-       case DEVICE_TYPE_HW_OSTC:
-               return hw_ostc_device_open(device, devname);
-
-       case DEVICE_TYPE_CRESSI_EDY:
-               return cressi_edy_device_open(device, devname);
-
-       case DEVICE_TYPE_ZEAGLE_N2ITION3:
-               return zeagle_n2ition3_device_open(device, devname);
-
-       case DEVICE_TYPE_ATOMICS_COBALT:
-               return atomics_cobalt_device_open(device);
-
-       default:
-               return DEVICE_STATUS_ERROR;
-       }
+       return dc_device_open(device, descriptor, devname);
 }
 
 
-static void event_cb(device_t *device, device_event_t event, const void *data, void *userdata)
+static void event_cb(dc_device_t *device, dc_event_type_t event, const void *data, void *userdata)
 {
-       const device_progress_t *progress = data;
-       const device_devinfo_t *devinfo = data;
-       const device_clock_t *clock = data;
+       const dc_event_progress_t *progress = data;
+       const dc_event_devinfo_t *devinfo = data;
+       const dc_event_clock_t *clock = data;
        device_data_t *devdata = userdata;
 
        switch (event) {
-       case DEVICE_EVENT_WAITING:
+       case DC_EVENT_WAITING:
                dev_info(devdata, "Event: waiting for user action");
                break;
-       case DEVICE_EVENT_PROGRESS:
+       case DC_EVENT_PROGRESS:
                update_progressbar(&devdata->progress,
                        (double) progress->current / (double) progress->maximum);
                break;
-       case DEVICE_EVENT_DEVINFO:
-               devdata->devinfo = *devinfo;
+       case DC_EVENT_DEVINFO:
                dev_info(devdata, "model=%u (0x%08x), firmware=%u (0x%08x), serial=%u (0x%08x)",
                        devinfo->model, devinfo->model,
                        devinfo->firmware, devinfo->firmware,
                        devinfo->serial, devinfo->serial);
                break;
-       case DEVICE_EVENT_CLOCK:
-               devdata->clock = *clock;
+       case DC_EVENT_CLOCK:
                dev_info(devdata, "Event: systime=%"PRId64", devtime=%u\n",
                        (uint64_t)clock->systime, clock->devtime);
                break;
@@ -492,36 +353,37 @@ cancel_cb(void *userdata)
 
 static const char *do_libdivecomputer_import(device_data_t *data)
 {
-       device_t *device = NULL;
-       device_status_t rc;
+       dc_device_t *device = NULL;
+       dc_status_t rc;
 
        import_dive_number = 0;
-       rc = device_open(data->devname, data->type, &device);
-       if (rc != DEVICE_STATUS_SUCCESS)
-               return "Unable to open %s (%s)";
+       rc = device_open(data->devname, data->descriptor, &device);
+       if (rc != DC_STATUS_SUCCESS)
+               return "Unable to open %s %s (%s)";
+       data->device = device;
 
        // Register the event handler.
-       int events = DEVICE_EVENT_WAITING | DEVICE_EVENT_PROGRESS | DEVICE_EVENT_DEVINFO | DEVICE_EVENT_CLOCK;
-       rc = device_set_events(device, events, event_cb, data);
-       if (rc != DEVICE_STATUS_SUCCESS) {
-               device_close(device);
+       int events = DC_EVENT_WAITING | DC_EVENT_PROGRESS | DC_EVENT_DEVINFO | DC_EVENT_CLOCK;
+       rc = dc_device_set_events(device, events, event_cb, data);
+       if (rc != DC_STATUS_SUCCESS) {
+               dc_device_close(device);
                return "Error registering the event handler.";
        }
 
        // Register the cancellation handler.
-       rc = device_set_cancel(device, cancel_cb, data);
-       if (rc != DEVICE_STATUS_SUCCESS) {
-               device_close(device);
+       rc = dc_device_set_cancel(device, cancel_cb, data);
+       if (rc != DC_STATUS_SUCCESS) {
+               dc_device_close(device);
                return "Error registering the cancellation handler.";
        }
 
        rc = import_device_data(device, data);
-       if (rc != DEVICE_STATUS_SUCCESS) {
-               device_close(device);
+       if (rc != DC_STATUS_SUCCESS) {
+               dc_device_close(device);
                return "Dive data import error";
        }
 
-       device_close(device);
+       dc_device_close(device);
        return NULL;
 }
 
@@ -548,42 +410,6 @@ GError *do_import(device_data_t *data)
        if (pthread_join(pthread, &retval) < 0)
                retval = "Odd pthread error return";
        if (retval)
-               return error(retval, data->name, data->devname);
+               return error(retval, data->vendor, data->product, data->devname);
        return NULL;
 }
-
-/*
- * Taken from 'example.c' in libdivecomputer.
- *
- * I really wish there was some way to just have
- * libdivecomputer tell us what devices it supports,
- * rather than have the application have to know..
- */
-struct device_list device_list[] = {
-       { "Suunto Solution",    DEVICE_TYPE_SUUNTO_SOLUTION },
-       { "Suunto Eon",         DEVICE_TYPE_SUUNTO_EON },
-       { "Suunto Vyper",       DEVICE_TYPE_SUUNTO_VYPER },
-       { "Suunto Vyper Air",   DEVICE_TYPE_SUUNTO_VYPER2 },
-       { "Suunto D9",          DEVICE_TYPE_SUUNTO_D9 },
-       { "Uwatec Aladin",      DEVICE_TYPE_UWATEC_ALADIN },
-       { "Uwatec Memo Mouse",  DEVICE_TYPE_UWATEC_MEMOMOUSE },
-       { "Uwatec Smart",       DEVICE_TYPE_UWATEC_SMART },
-       { "ReefNet Sensus",     DEVICE_TYPE_REEFNET_SENSUS },
-       { "ReefNet Sensus Pro", DEVICE_TYPE_REEFNET_SENSUSPRO },
-       { "ReefNet Sensus Ultra",DEVICE_TYPE_REEFNET_SENSUSULTRA },
-       { "Oceanic VT Pro",     DEVICE_TYPE_OCEANIC_VTPRO },
-       { "Oceanic Veo250",     DEVICE_TYPE_OCEANIC_VEO250 },
-       { "Oceanic Atom 2",     DEVICE_TYPE_OCEANIC_ATOM2 },
-       { "Mares Darwin, M1, M2, Airlab",       DEVICE_TYPE_MARES_DARWIN },
-       { "Mares Nemo, Excel, Apneist", DEVICE_TYPE_MARES_NEMO },
-       { "Mares Puck, Nemo Air, Nemo Wide",    DEVICE_TYPE_MARES_PUCK },
-       { "Mares Icon HD",      DEVICE_TYPE_MARES_ICONHD },
-       { "OSTC",               DEVICE_TYPE_HW_OSTC },
-#ifdef LIBDIVECOMPUTER_SUPPORTS_FROG
-       { "OSTC Frog",          DEVICE_TYPE_HW_FROG },
-#endif
-       { "Cressi Edy",         DEVICE_TYPE_CRESSI_EDY },
-       { "Zeagle N2iTiON 3",   DEVICE_TYPE_ZEAGLE_N2ITION3 },
-       { "Atomics Cobalt",     DEVICE_TYPE_ATOMICS_COBALT },
-       { NULL }
-};
index c6e99fa732f9c79b38b1432c34fcc4abd77f2d5e..8d77a25beba9230e51e20be293960d9f28f048d3 100644 (file)
@@ -2,17 +2,9 @@
 #define LIBDIVECOMPUTER_H
 
 /* libdivecomputer */
-#include <device.h>
-#include <suunto.h>
-#include <reefnet.h>
-#include <uwatec.h>
-#include <oceanic.h>
-#include <mares.h>
-#include <hw.h>
-#include <cressi.h>
-#include <zeagle.h>
-#include <atomics.h>
-#include <utils.h>
+#include <libdivecomputer/device.h>
+#include <libdivecomputer/parser.h>
+#include <libdivecomputer/utils.h>
 
 /* handling uemis Zurich SDA files */
 #include "uemis.h"
 /* don't forget to include the UI toolkit specific display-XXX.h first
    to get the definition of progressbar_t */
 typedef struct device_data_t {
-       device_type_t type;
-       const char *name, *devname;
+       dc_descriptor_t *descriptor;
+       const char *vendor, *product, *devname;
+       dc_device_t *device;
        progressbar_t progress;
-       device_devinfo_t devinfo;
-       device_clock_t clock;
        int preexisting;
 } device_data_t;
 
-struct device_list {
-       const char *name;
-       device_type_t type;
-};
-
-extern struct device_list device_list[];
 extern GError *do_import(device_data_t *data);
 
 #endif
diff --git a/macos.c b/macos.c
index 931d4fa1e8a7c74faba0c5ff94b39f09470f5fdc..1b7da1ec6192e6143809cda9787a91fafc5045e8 100644 (file)
--- a/macos.c
+++ b/macos.c
@@ -98,10 +98,9 @@ void subsurface_ui_setup(GtkSettings *settings, GtkWidget *menubar,
        gtk_widget_hide (menubar);
        gtk_osxapplication_set_menu_bar(osx_app, GTK_MENU_SHELL(menubar));
 
-       sep = gtk_ui_manager_get_widget(ui_manager, "/MainMenu/FileMenu/Separator3");
-       gtk_widget_destroy(sep);
        sep = gtk_ui_manager_get_widget(ui_manager, "/MainMenu/FileMenu/Separator2");
-       gtk_widget_destroy(sep);
+       if (sep)
+               gtk_widget_destroy(sep);
 
        menu_item = gtk_ui_manager_get_widget(ui_manager, "/MainMenu/FileMenu/Quit");
        gtk_widget_hide (menu_item);
diff --git a/main.c b/main.c
index 60f2902e60f685d86b7c6d601f847b963b66a465..8e579f88c067b78891967684cc80b1469714ffb0 100644 (file)
--- a/main.c
+++ b/main.c
@@ -190,7 +190,7 @@ void update_dive(struct dive *new_dive)
        }
        if (new_dive) {
                show_dive_info(new_dive);
-               show_dive_equipment(new_dive);
+               show_dive_equipment(new_dive, W_IDX_PRIMARY);
                show_dive_stats(new_dive);
        }
        buffered_dive = new_dive;
index 761cc3b41464ed7188f235a645cdff90c3219816..173314dd42161ff28af7c7374bdf80e23ee6a706 100644 (file)
@@ -647,6 +647,7 @@ static int uddf_fill_sample(struct sample *sample, const char *name, int len, ch
        return  MATCH(".divetime", sampletime, &sample->time) ||
                MATCH(".depth", depth, &sample->depth) ||
                MATCH(".temperature", temperature, &sample->temperature) ||
+               MATCH(".tankpressure", pressure, &sample->cylinderpressure) ||
                0;
 }
 
@@ -1099,6 +1100,10 @@ static void try_to_fill_dive(struct dive **divep, const char *name, char *buf)
                return;
        if (MATCH(".location", utf8_string, &dive->location))
                return;
+       if (MATCH(".suit", utf8_string, &dive->suit))
+               return;
+       if (MATCH(".divesuit", utf8_string, &dive->suit))
+               return;
        if (MATCH(".notes", utf8_string, &dive->notes))
                return;
        if (MATCH(".divemaster", utf8_string, &dive->divemaster))
diff --git a/print.c b/print.c
index bdebcfe15634fcf0bdda5fbe51e211ab42130495..0bd3110a7bc67d4fdab31ac483bc93230e7f1a66 100644 (file)
--- a/print.c
+++ b/print.c
@@ -196,6 +196,22 @@ static void begin_print(GtkPrintOperation *operation, gpointer user_data)
 {
 }
 
+static GtkWidget *print_dialog(GtkPrintOperation *operation, gpointer user_data)
+{
+       GtkWidget *vbox, *hbox, *label;
+       gtk_print_operation_set_custom_tab_label(operation, "Dive details");
+
+       vbox = gtk_vbox_new(TRUE, 5);
+       label = gtk_label_new("Print Dive details");
+       gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0);
+       gtk_widget_show_all(vbox);
+       return vbox;
+}
+
+static void print_dialog_apply(GtkPrintOperation *operation, GtkWidget *widget, gpointer user_data)
+{
+}
+
 static GtkPrintSettings *settings = NULL;
 
 void do_print(void)
@@ -210,6 +226,8 @@ void do_print(void)
                gtk_print_operation_set_print_settings(print, settings);
        pages = (dive_table.nr + 5) / 6;
        gtk_print_operation_set_n_pages(print, pages);
+       g_signal_connect(print, "create-custom-widget", G_CALLBACK(print_dialog), NULL);
+       g_signal_connect(print, "custom-widget-apply", G_CALLBACK(print_dialog_apply), NULL);
        g_signal_connect(print, "begin_print", G_CALLBACK(begin_print), NULL);
        g_signal_connect(print, "draw_page", G_CALLBACK(draw_page), NULL);
        res = gtk_print_operation_run(print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
index fedc700b4dd1679703e60b33345199bb00c993cb..97ea9077d480f871436e89940838f962f72a1d3e 100644 (file)
--- a/profile.c
+++ b/profile.c
@@ -1074,16 +1074,18 @@ static void fill_missing_tank_pressures(struct plot_info *pi, pr_track_t **track
                                /* there may be multiple segments - so
                                 * let's assemble the length */
                                nlist = list;
-                               pt = list->pressure_time;
-                               while (!nlist->end) {
-                                       nlist = nlist->next;
-                                       if (!nlist) {
-                                               /* oops - we have no end pressure,
-                                                * so this means this is a tank without
-                                                * gas consumption information */
-                                               break;
+                               if (list) {
+                                       pt = list->pressure_time;
+                                       while (!nlist->end) {
+                                               nlist = nlist->next;
+                                               if (!nlist) {
+                                                       /* oops - we have no end pressure,
+                                                        * so this means this is a tank without
+                                                        * gas consumption information */
+                                                       break;
+                                               }
+                                               pt += nlist->pressure_time;
                                        }
-                                       pt += nlist->pressure_time;
                                }
                                if (!nlist) {
                                        /* just continue without calculating
@@ -1380,12 +1382,15 @@ void plot(struct graphics_context *gc, cairo_rectangle_int_t *drawing_area, stru
        int nr = dive->samples;
 
        if (!nr) {
+               /* The dive has no samples, so create a few fake ones.  This assumes an
+               ascent/descent rate of 9 m/min, which is just below the limit for FAST. */
                int duration = dive->duration.seconds;
                int maxdepth = dive->maxdepth.mm;
+               int asc_desc_time = dive->maxdepth.mm*60/9000;
                sample = fake;
-               fake[1].time.seconds = duration * 0.05;
+               fake[1].time.seconds = asc_desc_time;
                fake[1].depth.mm = maxdepth;
-               fake[2].time.seconds = duration * 0.95;
+               fake[2].time.seconds = duration - asc_desc_time;
                fake[2].depth.mm = maxdepth;
                fake[3].time.seconds = duration * 1.00;
                nr = 4;
index ed55c022d4bf971e1d10802cf38c5399830ea98d..37d6d062eabf836f188b8cfc2bcd0b6feae0c6a4 100644 (file)
@@ -183,6 +183,7 @@ static void save_overview(FILE *f, struct dive *dive)
        show_utf8(f, dive->divemaster, "  <divemaster>","</divemaster>\n");
        show_utf8(f, dive->buddy, "  <buddy>","</buddy>\n");
        show_utf8(f, dive->notes, "  <notes>","</notes>\n");
+       show_utf8(f, dive->suit, "  <suit>","</suit>\n");
 }
 
 static void save_cylinder_info(FILE *f, struct dive *dive)
index 34f487b521398cdc025186462d1ae81e1df63314..b9d2c3b95d19c57f96b2a353e274d9bb4304c301 100644 (file)
@@ -91,6 +91,18 @@ static void process_dive(struct dive *dp, stats_t *stats)
                stats->max_depth.mm = dp->maxdepth.mm;
        if (stats->min_depth.mm == 0 || dp->maxdepth.mm < stats->min_depth.mm)
                stats->min_depth.mm = dp->maxdepth.mm;
+       if (dp->watertemp.mkelvin) {
+               if (stats->min_temp == 0 || dp->watertemp.mkelvin < stats->min_temp)
+                       stats->min_temp = dp->watertemp.mkelvin;
+               if (dp->watertemp.mkelvin > stats->max_temp)
+                       stats->max_temp = dp->watertemp.mkelvin;
+               stats->combined_temp += get_temp_units(dp->watertemp.mkelvin, &unit);
+               stats->combined_count++;
+       }
+
+       /* Maybe we should drop zero-duration dives */
+       if (!dp->duration.seconds)
+               return;
        stats->avg_depth.mm = (1.0 * old_tt * stats->avg_depth.mm +
                        dp->duration.seconds * dp->meandepth.mm) / stats->total_time.seconds;
        if (dp->sac > 2800) { /* less than .1 cuft/min (2800ml/min) is bogus */
@@ -103,14 +115,6 @@ static void process_dive(struct dive *dp, stats_t *stats)
                        stats->min_sac.mliter = dp->sac;
                stats->total_sac_time = sac_time;
        }
-       if (dp->watertemp.mkelvin) {
-               if (stats->min_temp == 0 || dp->watertemp.mkelvin < stats->min_temp)
-                       stats->min_temp = dp->watertemp.mkelvin;
-               if (dp->watertemp.mkelvin > stats->max_temp)
-                       stats->max_temp = dp->watertemp.mkelvin;
-               stats->combined_temp += get_temp_units(dp->watertemp.mkelvin, &unit);
-               stats->combined_count++;
-       }
 }
 
 static void process_all_dives(struct dive *dive, struct dive **prev_dive)
@@ -138,25 +142,22 @@ static void process_all_dives(struct dive *dive, struct dive **prev_dive)
        }
 }
 
-void process_selected_dives(GList *selected_dives, GtkTreeModel *model)
+/* make sure we skip the selected summary entries */
+void process_selected_dives(void)
 {
-       struct dive *dp;
-       unsigned int i;
-       GtkTreeIter iter;
-       GtkTreePath *path;
+       struct dive *dive;
+       unsigned int i, nr;
 
        memset(&stats_selection, 0, sizeof(stats_selection));
-       stats_selection.selection_size = amount_selected;
-
-       for (i = 0; i < amount_selected; ++i) {
-               GValue value = {0, };
-               path = g_list_nth_data(selected_dives, i);
-               if (gtk_tree_model_get_iter(model, &iter, path)) {
-                       gtk_tree_model_get_value(model, &iter, 0, &value);
-                       dp = get_dive(g_value_get_int(&value));
+
+       nr = 0;
+       for_each_dive(i, dive) {
+               if (dive->selected) {
+                       process_dive(dive, &stats_selection);
+                       nr++;
                }
-               process_dive(dp, &stats_selection);
        }
+       stats_selection.selection_size = nr;
 }
 
 static void set_label(GtkWidget *w, const char *fmt, ...)
@@ -266,14 +267,11 @@ static void show_single_dive_stats(struct dive *dive)
 static void show_total_dive_stats(struct dive *dive)
 {
        double value;
-       int decimals;
+       int decimals, seconds;
        const char *unit;
        stats_t *stats_ptr;
 
-       if (amount_selected < 2)
-               stats_ptr = &stats;
-       else
-               stats_ptr = &stats_selection;
+       stats_ptr = &stats_selection;
 
        set_label(stats_w.selection_size, "%d", stats_ptr->selection_size);
        if (stats_ptr->min_temp) {
@@ -287,7 +285,10 @@ static void show_total_dive_stats(struct dive *dive)
                set_label(stats_w.max_temp, "%.1f %s", value, unit);
        }
        set_label(stats_w.total_time, get_time_string(stats_ptr->total_time.seconds, 0));
-       set_label(stats_w.avg_time, get_time_string(stats_ptr->total_time.seconds / stats_ptr->selection_size, 0));
+       seconds = stats_ptr->total_time.seconds;
+       if (stats_ptr->selection_size)
+               seconds /= stats_ptr->selection_size;
+       set_label(stats_w.avg_time, get_time_string(seconds, 0));
        set_label(stats_w.longest_time, get_time_string(stats_ptr->longest_time.seconds, 0));
        set_label(stats_w.shortest_time, get_time_string(stats_ptr->shortest_time.seconds, 0));
        value = get_depth_units(stats_ptr->max_depth.mm, &decimals, &unit);
index e2f40c560c5d7fcc4eb135781835d8cbecc39cc6..6e85cbfbce07e91ceefeca4e7c8a66750a0ffa02 100644 (file)
         </buddy>
       </xsl:if>
 
+      <xsl:if test="Equipment/Suit != ''">
+       <suit>
+          <xsl:value-of select="Equipment/Suit"/>
+       </suit>
+        <xsl:value-of select="Equipment/Suit"/>
+      </xsl:if>
+
+      <xsl:if test="Equipment/Weight != ''">
+        <weightsystem>
+          <xsl:attribute name="weight">
+            <xsl:value-of select="Equipment/Weight"/>
+          </xsl:attribute>
+        </weightsystem>
+      </xsl:if>
+
       <notes>
         <xsl:if test="DiveActivity != ''">
 Diveactivity: <xsl:value-of select="DiveActivity"/>
@@ -73,15 +88,9 @@ Divetype: <xsl:value-of select="DiveType"/>
         </xsl:if>
         <xsl:if test="Equipment/Visibility != ''">
 Visibility: <xsl:value-of select="Equipment/Visibility"/>
-        </xsl:if>
-        <xsl:if test="Equipment/Suit != ''">
-Suit: <xsl:value-of select="Equipment/Suit"/>
         </xsl:if>
         <xsl:if test="Equipment/Gloves != ''">
 Gloves: <xsl:value-of select="Equipment/Gloves"/>
-        </xsl:if>
-        <xsl:if test="Equipment/Weight != ''">
-Weight: <xsl:value-of select="Equipment/Weight"/>
         </xsl:if>
         <xsl:if test="Comment != ''">
 Comment: <xsl:value-of select="Comment"/>