diff --git a/Makefile b/Makefile
index 20dff4c85..5bef6f2b0 100644
--- 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)
diff --git a/cochran.c b/cochran.c
index 360f2eb7c..933e1de1f 100644
--- 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);
}
diff --git a/display-gtk.h b/display-gtk.h
index efbf3fd87..dd7e2c4a0 100644
--- a/display-gtk.h
+++ b/display-gtk.h
@@ -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 ""
+#define SHIFTCHAR ""
#define PREFERENCE_ACCEL "comma"
#else
#define CTRLCHAR ""
+#define SHIFTCHAR ""
#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 9f57aed58..aee09d53a 100644
--- 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 4d60a77a2..ab854e37e 100644
--- 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);
diff --git a/divelist.c b/divelist.c
index 2054ca017..30bd2d8e9 100644
--- a/divelist.c
+++ b/divelist.c
@@ -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
+#ifdef DEBUG_MODEL
+static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path,
+ GtkTreeIter *iter, gpointer data)
+{
+ 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
+
+#if DEBUG_SELECTION_TRACKING
+void dump_selection(void)
+{
+ int i;
+ struct dive *dive;
+
+ printf("currently selected are %d dives:", amount_selected);
+ for_each_dive(i, dive) {
+ if (dive->selected)
+ printf(" %d", i);
+ }
+ printf("\n");
+}
+#endif
+
+/* when subsurface starts we want to have the last dive selected. So we simply
+ walk to the first leaf (and skip the summary entries - which have negative
+ DIVE_INDEX) */
+static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx)
+{
+ GtkTreeIter parent;
+ GtkTreePath *tpath;
+
+ while (*diveidx < 0) {
+ memcpy(&parent, iter, sizeof(parent));
+ tpath = gtk_tree_model_get_path(model, &parent);
+ if (!gtk_tree_model_iter_children(model, iter, &parent))
+ /* we should never have a parent without child */
+ 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;
+
+ 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;
+
+ 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)
{
- GtkTreeIter iter;
- GValue value = {0, };
- GtkTreePath *path;
+ GList *changed = selection_changed;
- int nr_selected = gtk_tree_selection_count_selected_rows(selection);
+ selection_changed = NULL;
+ g_list_foreach(changed, (GFunc) check_selection_cb, selection);
+ g_list_free(changed);
+#if DEBUG_SELECTION_TRACKING
+ dump_selection();
+#endif
- if (selected_dives) {
- g_list_foreach (selected_dives, (GFunc) gtk_tree_path_free, NULL);
- g_list_free (selected_dives);
- }
- selected_dives = gtk_tree_selection_get_selected_rows(selection, NULL);
-
- switch (nr_selected) {
- case 0: /* keep showing the last selected dive */
- 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();
- }
- 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();
- return;
- }
+ 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);
+ gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, 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)
+ 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),
diff --git a/equipment.c b/equipment.c
index b74d85b7b..43bb29d59 100644
--- a/equipment.c
+++ b/equipment.c
@@ -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
#include
@@ -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 aff1d51f2..e0163909e 100644
--- 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;
}
diff --git a/gtk-gui.c b/gtk-gui.c
index 4b9f002af..f25fbaa53 100644
--- 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 (is_default_dive_computer(name))
+ if (!product || !*product)
return;
- default_dive_computer = name;
- subsurface_set_conf("dive_computer", PREF_STRING, name);
+ if (is_default_dive_computer(vendor, product))
+ return;
+ 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 = " \
\