summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--display.h8
-rw-r--r--dive.h9
-rw-r--r--divelist.c248
-rw-r--r--equipment.c68
-rw-r--r--gtk-gui.c16
-rw-r--r--info.c28
-rw-r--r--packaging/windows/subsurface.nsi265
-rw-r--r--print.c16
-rw-r--r--profile.c38
-rw-r--r--save-xml.c2
-rw-r--r--statistics.c301
12 files changed, 827 insertions, 174 deletions
diff --git a/Makefile b/Makefile
index 5bef6f2b0..8c7717b34 100644
--- a/Makefile
+++ b/Makefile
@@ -209,4 +209,4 @@ doc:
$(MAKE) -C Documentation doc
clean:
- rm -f $(OBJS) *~ $(NAME)
+ rm -f $(OBJS) *~ $(NAME) $(NAME).exe
diff --git a/display.h b/display.h
index ef52e99f5..82d12682b 100644
--- a/display.h
+++ b/display.h
@@ -3,6 +3,10 @@
#include <cairo.h>
+#define DPI_SCREEN 72.0
+#define SCALE_SCREEN 1.0
+#define SCALE_PRINT (1.0 / DPI_SCREEN)
+
extern void repaint_dive(void);
extern void do_print(void);
@@ -22,7 +26,9 @@ struct graphics_context {
double topy, bottomy;
};
-extern void plot(struct graphics_context *gc, cairo_rectangle_int_t *drawing_area, struct dive *dive);
+typedef enum { SC_SCREEN, SC_PRINT } scale_mode_t;
+
+extern void plot(struct graphics_context *gc, cairo_rectangle_t *drawing_area, struct dive *dive, scale_mode_t scale);
extern void init_profile_background(struct graphics_context *gc);
extern void attach_tooltip(int x, int y, int w, int h, const char *text);
diff --git a/dive.h b/dive.h
index 177897099..5637edf9f 100644
--- a/dive.h
+++ b/dive.h
@@ -263,6 +263,10 @@ struct dive {
struct sample sample[];
};
+/* this is a global spot for a temporary dive structure that we use to
+ * be able to edit a dive without unintended side effects */
+extern struct dive edit_dive;
+
extern GList *dive_trip_list;
extern gboolean autogroup;
/* random threashold: three days without diving -> new trip
@@ -272,6 +276,7 @@ extern gboolean autogroup;
#define UNGROUPED_DIVE(_dive) ((_dive)->tripflag == NO_TRIP)
#define DIVE_IN_TRIP(_dive) ((_dive)->tripflag == IN_TRIP)
+#define DIVE_NEEDS_TRIP(_dive) ((_dive)->tripflag == TF_NONE)
#define NEXT_TRIP(_entry) ((_entry) ? g_list_next(_entry) : (dive_trip_list))
#define PREV_TRIP(_entry) ((_entry) ? g_list_previous(_entry) : g_list_last(dive_trip_list))
#define DIVE_TRIP(_trip) ((struct dive *)(_trip)->data)
@@ -396,6 +401,8 @@ extern void clear_equipment_widgets(void);
extern void show_dive_stats(struct dive *);
extern void clear_stats_widgets(void);
+extern void show_yearly_stats(void);
+
extern void update_dive(struct dive *new_dive);
extern void save_dives(const char *filename);
@@ -467,4 +474,6 @@ extern const char *existing_filename;
extern const char *subsurface_default_filename(void);
#define AIR_PERMILLE 209
+#define FRACTION(n,x) ((unsigned)(n)/(x)),((unsigned)(n)%(x))
+
#endif /* DIVE_H */
diff --git a/divelist.c b/divelist.c
index c52a94191..aedcdf03e 100644
--- a/divelist.c
+++ b/divelist.c
@@ -65,6 +65,9 @@ enum {
DIVELIST_COLUMNS
};
+static void turn_dive_into_trip(GtkTreePath *path);
+static void merge_dive_into_trip_above_cb(GtkWidget *menuitem, GtkTreePath *path);
+
#ifdef DEBUG_MODEL
static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path,
GtkTreeIter *iter, gpointer data)
@@ -136,6 +139,39 @@ static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx)
}
}
+static GtkTreePath *path_match;
+
+static gboolean match_dive(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
+{
+ int idx;
+ struct dive *dive = data;
+
+ gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
+ if (idx >= 0)
+ if (get_dive(idx)->when == dive->when) {
+ path_match = gtk_tree_path_copy(path);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static GtkTreePath *get_path_from(struct dive *dive)
+{
+ gtk_tree_model_foreach(TREEMODEL(dive_list), match_dive, dive);
+ return path_match;
+}
+
+static struct dive *dive_from_path(GtkTreePath *path)
+{
+ GtkTreeIter iter;
+ int idx;
+
+ gtk_tree_model_get_iter(MODEL(dive_list), &iter, path);
+ gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1);
+ return get_dive(idx);
+
+}
+
/* 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)
@@ -927,6 +963,49 @@ static struct dive *create_and_hookup_trip_from_dive(struct dive *dive)
return dive_trip;
}
+/* check that a dive should be in a trip starting at 'when'
+ * first the regular check (dive is before the trip start, but within the
+ * threshold)
+ * then for dives that are after the trip start we walk back to the dive
+ * that starts at when and check on the way that there is no ungrouped
+ * dive and no break beyond the 3 day threshold between dives that
+ * haven't already been assigned to this trip */
+static gboolean dive_can_be_in_trip(int idx, struct dive *dive_trip)
+{
+ struct dive *dive, *pdive;
+ int i = idx;
+ time_t when = dive_trip->when;
+
+ dive = get_dive(idx);
+ /* if the dive is before the trip start but within the threshold
+ * then just accept it, otherwise reject it */
+ if (dive->when < when) {
+ if (DIVE_FITS_TRIP(dive, dive_trip))
+ return TRUE;
+ else
+ return FALSE;
+ }
+
+ while (--i >= 0) {
+ pdive = get_dive(i);
+ /* an ungrouped dive cannot be in the middle of a trip
+ * also, if there are two consecutive dives that are too far apart
+ * that aren't both already labeled as 'in trip' (which shows that
+ * this was manually done so we shouldn't override that) */
+ if ( UNGROUPED_DIVE(pdive) ||
+ (! (DIVE_IN_TRIP(pdive) && DIVE_IN_TRIP(dive)) &&
+ dive->when - pdive->when > TRIP_THRESHOLD)) {
+ return FALSE;
+ }
+ if (pdive->when == when)
+ /* done - we have reached the first dive in the trip */
+ return TRUE;
+ dive = pdive;
+ }
+ /* we should never get here */
+ return TRUE;
+}
+
static void fill_dive_list(void)
{
int i;
@@ -965,17 +1044,27 @@ static void fill_dive_list(void)
/* first dives that go to the top level */
parent_ptr = NULL;
dive_trip = NULL;
- } else if (autogroup && !DIVE_IN_TRIP(dive)) {
- if ( ! dive_trip || ! DIVE_FITS_TRIP(dive, dive_trip)) {
- /* allocate new trip - all fields default to 0
- and get filled in further down */
- dive_trip = create_and_hookup_trip_from_dive(dive);
- dive_trip->tripflag = IN_TRIP; /* this marks an autogen trip */
- trip = FIND_TRIP(dive_trip->when);
+ } else if (autogroup && DIVE_NEEDS_TRIP(dive)){
+ /* if we already have existing trips there are up to two trips that this
+ * could potentially be part of. Let's try the one we are on, first */
+ if (! (dive_trip && dive_can_be_in_trip(i, dive_trip))) {
+ /* there could be a better trip in our list already */
+ trip = find_matching_trip(dive->when);
+ if (! (trip && dive_can_be_in_trip(i, DIVE_TRIP(trip)))) {
+ /* seems like neither of these trips work, so create
+ * a new one; all fields default to 0 and get filled
+ * in further down */
+ parent_ptr = NULL;
+ dive_trip = create_and_hookup_trip_from_dive(dive);
+ dive_trip->tripflag = IN_TRIP;
+ trip = FIND_TRIP(dive_trip->when);
+ }
+ if (trip)
+ dive_trip = DIVE_TRIP(trip);
}
} else if (DIVE_IN_TRIP(dive)) {
- trip = find_matching_trip(dive->when);
- dive_trip = DIVE_TRIP(trip);
+ trip = find_matching_trip(dive->when);
+ dive_trip = DIVE_TRIP(trip);
} else {
/* dive is not in a trip and we aren't autogrouping */
dive_trip = NULL;
@@ -1188,13 +1277,7 @@ void edit_selected_dives_cb(GtkWidget *menuitem, gpointer data)
void edit_dive_from_path_cb(GtkWidget *menuitem, GtkTreePath *path)
{
- GtkTreeIter iter;
- int idx;
- struct dive *dive;
-
- gtk_tree_model_get_iter(MODEL(dive_list), &iter, path);
- gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1);
- dive = get_dive(idx);
+ struct dive *dive = dive_from_path(path);
edit_multi_dive_info(dive);
}
@@ -1310,6 +1393,48 @@ static GtkTreeIter *move_dive_between_trips(GtkTreeIter *dive_iter, GtkTreeIter
return new_iter;
}
+/* this gets called when we are on a top level dive and we know that the previous
+ * top level node is a trip; if multiple consecutive dives are selected, they are
+ * all merged into the previous trip*/
+static void merge_dive_into_trip_above_cb(GtkWidget *menuitem, GtkTreePath *path)
+{
+ int idx;
+ GtkTreeIter dive_iter, trip_iter, prev_iter;
+ GtkTreePath *trip_path;
+ struct dive *dive, *prev_dive;
+
+ /* get the path and iter for the trip and the last dive of that trip */
+ trip_path = gtk_tree_path_copy(path);
+ (void)gtk_tree_path_prev(trip_path);
+ gtk_tree_model_get_iter(MODEL(dive_list), &trip_iter, trip_path);
+ gtk_tree_model_get_iter(MODEL(dive_list), &dive_iter, path);
+ gtk_tree_model_iter_nth_child(MODEL(dive_list), &prev_iter, &trip_iter,
+ gtk_tree_model_iter_n_children(MODEL(dive_list), &trip_iter) - 1);
+ gtk_tree_model_get(MODEL(dive_list), &dive_iter, DIVE_INDEX, &idx, -1);
+ dive = get_dive(idx);
+ gtk_tree_model_get(MODEL(dive_list), &prev_iter, DIVE_INDEX, &idx, -1);
+ prev_dive = get_dive(idx);
+ /* add the dive to the trip */
+ for (;;) {
+ dive->divetrip = prev_dive->divetrip;
+ dive->tripflag = IN_TRIP;
+ (void)move_dive_between_trips(&dive_iter, NULL, &trip_iter, NULL, TRUE);
+ prev_dive = dive;
+ /* by merging the dive into the trip above the path now points to the next
+ top level entry. If that iter exists, it's also a dive and both this dive
+ and that next dive are selected, continue merging dives into the trip */
+ if (!gtk_tree_model_get_iter(MODEL(dive_list), &dive_iter, path))
+ break;
+ gtk_tree_model_get(MODEL(dive_list), &dive_iter, DIVE_INDEX, &idx, -1);
+ if (idx < 0)
+ break;
+ dive = get_dive(idx);
+ if (!dive->selected || !prev_dive->selected)
+ break;
+ }
+ mark_divelist_changed(TRUE);
+}
+
static void turn_dive_into_trip(GtkTreePath *path)
{
GtkTreeIter iter, *newiter, newparent;
@@ -1390,10 +1515,25 @@ static void insert_trip_before(GtkTreePath *path)
static void insert_trip_before_cb(GtkWidget *menuitem, GtkTreePath *path)
{
/* is this splitting a trip or turning a dive into a trip? */
- if (gtk_tree_path_get_depth(path) == 2)
+ if (gtk_tree_path_get_depth(path) == 2) {
insert_trip_before(path);
- else
+ } else { /* this is a top level dive */
+ struct dive *dive, *next_dive;
+ GtkTreePath *next_path;
+
+ dive = dive_from_path(path);
turn_dive_into_trip(path);
+ /* if the dive was selected and the next dive was selected, too,
+ * then all of them should be part of the new trip */
+ if (dive->selected) {
+ next_path = gtk_tree_path_copy(path);
+ gtk_tree_path_next(next_path);
+ next_dive = dive_from_path(next_path);
+ if (next_dive && next_dive->selected)
+ merge_dive_into_trip_above_cb(menuitem, next_path);
+ }
+ }
+ mark_divelist_changed(TRUE);
}
static void remove_from_trip(GtkTreePath *path)
@@ -1492,6 +1632,7 @@ static void remove_from_trip_cb(GtkWidget *menuitem, GtkTreePath *path)
/* just remove the dive the mouse pointer is on */
remove_from_trip(path);
}
+ mark_divelist_changed(TRUE);
}
void remove_trip(GtkTreePath *trippath, gboolean force_no_trip)
@@ -1540,6 +1681,7 @@ void remove_trip(GtkTreePath *trippath, gboolean force_no_trip)
void remove_trip_cb(GtkWidget *menuitem, GtkTreePath *trippath)
{
remove_trip(trippath, TRUE);
+ mark_divelist_changed(TRUE);
}
void merge_trips_cb(GtkWidget *menuitem, GtkTreePath *trippath)
@@ -1570,6 +1712,55 @@ void merge_trips_cb(GtkWidget *menuitem, GtkTreePath *trippath)
free(DIVE_TRIP(trip));
delete_trip(trip);
gtk_tree_store_remove(STORE(dive_list), &thistripiter);
+ mark_divelist_changed(TRUE);
+}
+
+/* this gets called with path pointing to a dive, either in the top level
+ * or as part of a trip */
+static void delete_dive_cb(GtkWidget *menuitem, GtkTreePath *path)
+{
+ GtkTreeIter iter;
+ int idx, i;
+ struct dive *dive, *pdive, *ndive;
+ GtkTreeView *tree_view = GTK_TREE_VIEW(dive_list.tree_view);
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view);
+
+ gtk_tree_model_get_iter(MODEL(dive_list), &iter, path);
+ gtk_tree_model_get(MODEL(dive_list), &iter, DIVE_INDEX, &idx, -1);
+ dive = get_dive(idx);
+ if (dive->divetrip) {
+ /* we could be displaying the list model, in which case we can't find out
+ * if this is part of a trip and the only dive in that trip from the model,
+ * so let's do this the manual way */
+ pdive = get_dive(idx - 1);
+ ndive = get_dive(idx + 1);
+ if (! (pdive && pdive->divetrip == dive->divetrip) &&
+ ! (ndive && ndive->divetrip == dive->divetrip)) {
+ /* if this is the only dive in the trip, remove the trip - the
+ * dive list update below will deal with making sure the treemodel
+ * is correct */
+ GList *trip = find_matching_trip(dive->when);
+ delete_trip(trip);
+ free(dive->divetrip);
+ }
+ }
+ /* simply remove the dive and recreate the divelist
+ * (we can't just manipulate the tree_view as the indices for dives change) */
+ for (i = idx; i < dive_table.nr - 1; i++)
+ dive_table.dives[i] = dive_table.dives[i+1];
+ dive_table.nr--;
+ free(dive);
+ dive_list_update_dives();
+ /* now make sure the same dives stay selected and if necessary their trips are expanded */
+ for_each_dive(i, dive) {
+ if (dive->selected) {
+ GtkTreePath *path = get_path_from(dive);
+ if (MODEL(dive_list) == TREEMODEL(dive_list))
+ gtk_tree_view_expand_to_path(tree_view, path);
+ gtk_tree_selection_select_path(selection, path);
+ }
+ }
+ mark_divelist_changed(TRUE);
}
static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button, GdkEventButton *event)
@@ -1622,6 +1813,9 @@ static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int
g_signal_connect(menuitem, "activate", G_CALLBACK(remove_trip_cb), path);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
} else {
+ menuitem = gtk_menu_item_new_with_label("Delete Dive");
+ g_signal_connect(menuitem, "activate", G_CALLBACK(delete_dive_cb), path);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
dive = get_dive(idx);
/* if we right click on selected dive(s), edit those */
if (dive->selected) {
@@ -1638,14 +1832,24 @@ static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int
}
/* only offer trip editing options when we are displaying the tree model */
if (dive_list.model == dive_list.treemodel) {
- int depth;
- int *indices = gtk_tree_path_get_indices_with_depth(path, &depth);
-
+ int depth = gtk_tree_path_get_depth(path);
+ int *indices = gtk_tree_path_get_indices(path);
+ /* top level dive or child dive that is not the first child */
if (depth == 1 || indices[1] > 0) {
- menuitem = gtk_menu_item_new_with_label("Add new trip above");
+ menuitem = gtk_menu_item_new_with_label("Create new trip above");
g_signal_connect(menuitem, "activate", G_CALLBACK(insert_trip_before_cb), path);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
}
+ prevpath = gtk_tree_path_copy(path);
+ /* top level dive with a trip right before it */
+ if (depth == 1 &&
+ gtk_tree_path_prev(prevpath) &&
+ gtk_tree_model_get_iter(MODEL(dive_list), &previter, prevpath) &&
+ gtk_tree_model_iter_n_children(model, &previter)) {
+ menuitem = gtk_menu_item_new_with_label("Add to trip above");
+ g_signal_connect(menuitem, "activate", G_CALLBACK(merge_dive_into_trip_above_cb), path);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ }
if (DIVE_IN_TRIP(dive)) {
if (dive->selected && amount_selected > 1)
menuitem = gtk_menu_item_new_with_label("Remove selected dives from trip");
diff --git a/equipment.c b/equipment.c
index e81302c92..adfb02989 100644
--- a/equipment.c
+++ b/equipment.c
@@ -46,6 +46,7 @@ struct equipment_list {
static struct equipment_list cylinder_list[2], weightsystem_list[2];
+struct dive edit_dive;
struct cylinder_widget {
int index, changed;
@@ -55,6 +56,7 @@ struct cylinder_widget {
GtkSpinButton *size, *pressure;
GtkWidget *start, *end, *pressure_button;
GtkWidget *o2, *he, *gasmix_button;
+ int w_idx;
};
struct ws_widget {
@@ -63,6 +65,7 @@ struct ws_widget {
GtkWidget *hbox;
GtkComboBox *description;
GtkSpinButton *weight;
+ int w_idx;
};
/* we want bar - so let's not use our unit functions */
@@ -206,7 +209,14 @@ static void cylinder_cb(GtkComboBox *combo_box, gpointer data)
GtkTreeModel *model = gtk_combo_box_get_model(combo_box);
int ml, mbar;
struct cylinder_widget *cylinder = data;
- cylinder_t *cyl = current_dive->cylinder + cylinder->index;
+ struct dive *dive;
+ cylinder_t *cyl;
+
+ if (cylinder->w_idx == W_IDX_PRIMARY)
+ dive = current_dive;
+ else
+ dive = &edit_dive;
+ cyl = dive->cylinder + cylinder->index;
/* Did the user set it to some non-standard value? */
if (!get_active_item(combo_box, &iter, cylinder_model)) {
@@ -244,7 +254,13 @@ static void weight_cb(GtkComboBox *combo_box, gpointer data)
GtkTreeModel *model = gtk_combo_box_get_model(combo_box);
int weight;
struct ws_widget *ws_widget = data;
- weightsystem_t *ws = current_dive->weightsystem + ws_widget->index;
+ struct dive *dive;
+ weightsystem_t *ws;
+ if (ws_widget->w_idx == W_IDX_PRIMARY)
+ dive = current_dive;
+ else
+ dive = &edit_dive;
+ ws = dive->weightsystem + ws_widget->index;
/* Did the user set it to some non-standard value? */
if (!get_active_item(combo_box, &iter, weightsystem_model)) {
@@ -1047,7 +1063,7 @@ static void ws_widget(GtkWidget *vbox, struct ws_widget *ws_widget, GtkListStore
ws_widget->weight = GTK_SPIN_BUTTON(widget);
}
-static int edit_cylinder_dialog(int index, cylinder_t *cyl)
+static int edit_cylinder_dialog(int index, cylinder_t *cyl, int w_idx)
{
int success;
GtkWidget *dialog, *vbox;
@@ -1055,9 +1071,13 @@ static int edit_cylinder_dialog(int index, cylinder_t *cyl)
struct dive *dive;
cylinder.index = index;
+ cylinder.w_idx = w_idx;
cylinder.changed = 0;
- dive = current_dive;
+ if (w_idx == W_IDX_PRIMARY)
+ dive = current_dive;
+ else
+ dive = &edit_dive;
if (!dive)
return 0;
*cyl = dive->cylinder[index];
@@ -1079,9 +1099,11 @@ static int edit_cylinder_dialog(int index, cylinder_t *cyl)
if (success) {
record_cylinder_changes(cyl, &cylinder);
dive->cylinder[index] = *cyl;
- mark_divelist_changed(TRUE);
- update_cylinder_related_info(dive);
- flush_divelist(dive);
+ if (w_idx == W_IDX_PRIMARY) {
+ mark_divelist_changed(TRUE);
+ update_cylinder_related_info(dive);
+ flush_divelist(dive);
+ }
}
gtk_widget_destroy(dialog);
@@ -1089,7 +1111,7 @@ static int edit_cylinder_dialog(int index, cylinder_t *cyl)
return success;
}
-static int edit_weightsystem_dialog(int index, weightsystem_t *ws)
+static int edit_weightsystem_dialog(int index, weightsystem_t *ws, int w_idx)
{
int success;
GtkWidget *dialog, *vbox;
@@ -1097,9 +1119,13 @@ static int edit_weightsystem_dialog(int index, weightsystem_t *ws)
struct dive *dive;
weightsystem_widget.index = index;
+ weightsystem_widget.w_idx = w_idx;
weightsystem_widget.changed = 0;
- dive = current_dive;
+ if (w_idx == W_IDX_PRIMARY)
+ dive = current_dive;
+ else
+ dive = &edit_dive;
if (!dive)
return 0;
*ws = dive->weightsystem[index];
@@ -1121,8 +1147,10 @@ static int edit_weightsystem_dialog(int index, weightsystem_t *ws)
if (success) {
record_weightsystem_changes(ws, &weightsystem_widget);
dive->weightsystem[index] = *ws;
- mark_divelist_changed(TRUE);
- flush_divelist(dive);
+ if (w_idx == W_IDX_PRIMARY) {
+ mark_divelist_changed(TRUE);
+ flush_divelist(dive);
+ }
}
gtk_widget_destroy(dialog);
@@ -1158,7 +1186,7 @@ static void edit_cb(GtkButton *button, int w_idx)
return;
index = get_model_index(model, &iter);
- if (!edit_cylinder_dialog(index, &cyl))
+ if (!edit_cylinder_dialog(index, &cyl, w_idx))
return;
set_one_cylinder(&cyl, model, &iter);
@@ -1174,7 +1202,7 @@ static void add_cb(GtkButton *button, int w_idx)
GtkTreeSelection *selection;
cylinder_t cyl;
- if (!edit_cylinder_dialog(index, &cyl))
+ if (!edit_cylinder_dialog(index, &cyl, w_idx))
return;
gtk_list_store_append(model, &iter);
@@ -1205,7 +1233,10 @@ static void del_cb(GtkButton *button, int w_idx)
index = get_model_index(model, &iter);
- dive = current_dive;
+ if (w_idx == W_IDX_PRIMARY)
+ dive = current_dive;
+ else
+ dive = &edit_dive;
if (!dive)
return;
cyl = dive->cylinder + index;
@@ -1241,7 +1272,7 @@ static void ws_edit_cb(GtkButton *button, int w_idx)
return;
index = get_model_index(model, &iter);
- if (!edit_weightsystem_dialog(index, &ws))
+ if (!edit_weightsystem_dialog(index, &ws, w_idx))
return;
set_one_weightsystem(&ws, model, &iter);
@@ -1257,7 +1288,7 @@ static void ws_add_cb(GtkButton *button, int w_idx)
GtkTreeSelection *selection;
weightsystem_t ws;
- if (!edit_weightsystem_dialog(index, &ws))
+ if (!edit_weightsystem_dialog(index, &ws, w_idx))
return;
gtk_list_store_append(model, &iter);
@@ -1288,7 +1319,10 @@ static void ws_del_cb(GtkButton *button, int w_idx)
index = get_model_index(model, &iter);
- dive = current_dive;
+ if (w_idx == W_IDX_PRIMARY)
+ dive = current_dive;
+ else
+ dive = &edit_dive;
if (!dive)
return;
ws = dive->weightsystem + index;
diff --git a/gtk-gui.c b/gtk-gui.c
index c440eb2e7..26039fbc0 100644
--- a/gtk-gui.c
+++ b/gtk-gui.c
@@ -816,10 +816,10 @@ static void about_dialog(GtkWidget *w, gpointer data)
gtk_show_about_dialog(NULL,
"program-name", "SubSurface",
- "comments", "Half-arsed divelog software in C",
+ "comments", "Multi-platform divelog software in C",
"license", "GPLv2",
"version", VERSION_STRING,
- "copyright", "Linus Torvalds 2011",
+ "copyright", "Linus Torvalds, Dirk Hohndel, and others, 2011, 2012",
"logo-icon-name", "subsurface",
/* Must be last: */
logo_property, logo,
@@ -878,6 +878,7 @@ static GtkActionEntry menu_items[] = {
{ "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) },
+ { "YearlyStats", NULL, "Yearly Statistics", NULL, NULL, G_CALLBACK(show_yearly_stats) },
{ "SelectEvents", NULL, "SelectEvents", NULL, NULL, G_CALLBACK(selectevents_dialog) },
{ "Quit", GTK_STOCK_QUIT, NULL, CTRLCHAR "Q", NULL, G_CALLBACK(quit) },
{ "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_dialog) },
@@ -915,6 +916,7 @@ static const gchar* ui_string = " \
<menuitem name=\"Renumber\" action=\"Renumber\" /> \
<menuitem name=\"Autogroup\" action=\"Autogroup\" /> \
<menuitem name=\"Toggle Zoom\" action=\"ToggleZoom\" /> \
+ <menuitem name=\"YearlyStats\" action=\"YearlyStats\" /> \
<menu name=\"View\" action=\"ViewMenuAction\"> \
<menuitem name=\"List\" action=\"ViewList\" /> \
<menuitem name=\"Profile\" action=\"ViewProfile\" /> \
@@ -1094,7 +1096,7 @@ void exit_ui(void)
}
typedef struct {
- cairo_rectangle_int_t rect;
+ cairo_rectangle_t rect;
const char *text;
} tooltip_record_t;
@@ -1103,7 +1105,7 @@ static int tooltips;
void attach_tooltip(int x, int y, int w, int h, const char *text)
{
- cairo_rectangle_int_t *rect;
+ cairo_rectangle_t *rect;
tooltip_rects = realloc(tooltip_rects, (tooltips + 1) * sizeof(tooltip_record_t));
rect = &tooltip_rects[tooltips].rect;
rect->x = x;
@@ -1121,7 +1123,7 @@ static gboolean profile_tooltip (GtkWidget *widget, gint x, gint y,
gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data)
{
int i;
- cairo_rectangle_int_t *drawing_area = user_data;
+ cairo_rectangle_t *drawing_area = user_data;
gint tx = x - drawing_area->x; /* get transformed coordinates */
gint ty = y - drawing_area->y;
@@ -1139,7 +1141,7 @@ static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer
{
struct dive *dive = current_dive;
struct graphics_context gc = { .printer = 0 };
- static cairo_rectangle_int_t drawing_area;
+ static cairo_rectangle_t drawing_area;
/* the drawing area gives TOTAL width * height - x,y is used as the topx/topy offset
* so effective drawing area is width-2x * height-2y */
@@ -1160,7 +1162,7 @@ static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer
tooltip_rects = NULL;
}
tooltips = 0;
- plot(&gc, &drawing_area, dive);
+ plot(&gc, &drawing_area, dive, SC_SCREEN);
}
cairo_destroy(gc.cr);
diff --git a/info.c b/info.c
index a4d4d4179..3f7bbcd58 100644
--- a/info.c
+++ b/info.c
@@ -504,7 +504,7 @@ 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)
+static void save_equipment_data(struct dive *dive)
{
if (dive) {
memcpy(remember_cyl, dive->cylinder, CYL_BYTES);
@@ -606,13 +606,19 @@ int edit_multi_dive_info(struct dive *single_dive)
}
}
}
-
- dive_info_widget(vbox, master, &info, multi);
- show_dive_equipment(master, W_IDX_SECONDARY);
- save_equipment_data(master);
+ /* edit a temporary copy of the master dive;
+ * edit_dive is a global dive structure that is modified by the
+ * cylinder / weightsystem dialogs if we open W_IDX_SECONDARY
+ * edit widgets as we do here */
+ memcpy(&edit_dive, master, sizeof(struct dive));
+
+ dive_info_widget(vbox, &edit_dive, &info, multi);
+ show_dive_equipment(&edit_dive, W_IDX_SECONDARY);
+ save_equipment_data(&edit_dive);
gtk_widget_show_all(dialog);
success = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT;
if (success) {
+ mark_divelist_changed(TRUE);
/* Update the non-current selected dives first */
if (!single_dive) {
int i;
@@ -622,19 +628,23 @@ int edit_multi_dive_info(struct dive *single_dive)
if (dive == master || !dive->selected)
continue;
/* copy all "info" fields */
- save_dive_info_changes(dive, master, &info);
+ save_dive_info_changes(dive, &edit_dive, &info);
/* copy the cylinders / weightsystems */
- update_equipment_data(dive, master);
+ update_equipment_data(dive, &edit_dive);
/* this is extremely inefficient... it loops through all
dives to find the right one - but we KNOW the index already */
+ update_cylinder_related_info(dive);
flush_divelist(dive);
}
}
/* Update the master dive last! */
- save_dive_info_changes(master, master, &info);
- update_equipment_data(master, master);
+ save_dive_info_changes(master, &edit_dive, &info);
+ update_equipment_data(master, &edit_dive);
+ update_cylinder_related_info(master);
flush_divelist(master);
+ process_selected_dives();
+ update_dive(master);
}
gtk_widget_destroy(dialog);
diff --git a/packaging/windows/subsurface.nsi b/packaging/windows/subsurface.nsi
index 280991f69..1828c118a 100644
--- a/packaging/windows/subsurface.nsi
+++ b/packaging/windows/subsurface.nsi
@@ -1,105 +1,174 @@
-# this installer creator needs to be run with
+#
+# Subsurface NSIS installer script
+#
+# This installer creator needs to be run with:
# makensis subsurface.nsi
#
-# it assumes that packaging/windows/dll is a symlink to
+# It assumes that packaging/windows/dll is a symlink to
# the directory in which the required Windows DLLs are installed
# (in my case that's /usr/i686-w64-mingw32/sys-root/mingw/bin)
#
-# define the name of the installer
-outfile "subsurface-installer.exe"
-Name subsurface
-
-# some data for the package to identify itself
-VIProductVersion "1.1.9.0"
-VIAddVersionKey ProductName subsurface
-VIAddVersionKey FileDescription "subsurface diving log program"
-VIAddVersionKey LegalCopyright "GPL v.2"
-VIAddVersionKey ProductVersion "1.1"
-VIAddVersionKey FileVersion "1.1"
-
-# icon to use for the installer
-Icon .\subsurface.ico
-
-# the installer needs to be run with admin privileges
-RequestExecutionLevel admin
-
-# pop up a little dialog that tells the user that we're about to
-# install subsurface
-Function .onInit
- MessageBox MB_YESNO "This will install subsurface. Do you wish to continue?" IDYES gogogo
- Abort
- gogogo:
-FunctionEnd
-
-# define the directory to install to, the desktop in this case as specified
-# by the predefined $DESKTOP variable
-installDir "$PROGRAMFILES\subsurface"
-
-# default section
+
+#--------------------------------
+# Include Modern UI
+
+ !include "MUI2.nsh"
+
+#--------------------------------
+# General
+
+ # Program version
+ !define SUBSURFACE_VERSION "1.2"
+
+ # VIProductVersion requires version in x.x.x.x format
+ !define SUBSURFACE_VIPRODUCTVERSION "1.2.0.0"
+
+ # Installer name and filename
+ Name "Subsurface"
+ Caption "Subsurface ${SUBSURFACE_VERSION} Setup"
+ OutFile "subsurface-${SUBSURFACE_VERSION}.exe"
+
+ # Icon to use for the installer
+ !define MUI_ICON "subsurface.ico"
+
+ # Default installation folder
+ InstallDir "$PROGRAMFILES\Subsurface"
+
+ # Get installation folder from registry if available
+ InstallDirRegKey HKCU "Software\Subsurface" ""
+
+ # Request application privileges
+ RequestExecutionLevel user
+
+#--------------------------------
+# Version information
+
+ VIProductVersion "${SUBSURFACE_VIPRODUCTVERSION}"
+ VIAddVersionKey "ProductName" "Subsurface"
+ VIAddVersionKey "FileDescription" "Subsurface - an open source dive log program."
+ VIAddVersionKey "FileVersion" "${SUBSURFACE_VERSION}"
+ VIAddVersionKey "LegalCopyright" "GPL v.2"
+ VIAddVersionKey "ProductVersion" "${SUBSURFACE_VERSION}"
+
+#--------------------------------
+# Settings
+
+ # Show a warn on aborting installation
+ !define MUI_ABORTWARNING
+
+ # Defines the target start menu folder
+ !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU"
+ !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\Subsurface"
+ !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
+
+#--------------------------------
+# Variables
+
+ Var StartMenuFolder
+
+#--------------------------------
+# Pages
+
+ # Installer pages
+ !insertmacro MUI_PAGE_LICENSE "..\..\gpl-2.0.txt"
+ !insertmacro MUI_PAGE_DIRECTORY
+ !insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder
+ !insertmacro MUI_PAGE_INSTFILES
+
+ # Uninstaller pages
+ !insertmacro MUI_UNPAGE_CONFIRM
+ !insertmacro MUI_UNPAGE_INSTFILES
+
+#--------------------------------
+# Languages
+
+ !insertmacro MUI_LANGUAGE "English"
+
+#--------------------------------
+# Default installer section
+
Section
-# define the output path for this file
-setOutPath $INSTDIR
-
-SetShellVarContext all
-
-# create directory in the Start menu
-CreateDirectory "$SMPROGRAMS\subsurface"
-
-# create Start menu shortcut
-createShortCut "$SMPROGRAMS\subsurface\subsurface.lnk" "$INSTDIR\subsurface.exe"
-
-#create uninstaller and corresponding shortcut in Start menu
-writeUninstaller "$INSTDIR\subsurface-uninstall.exe"
-createShortCut "$SMPROGRAMS\subsurface\uninstall-subsurface.lnk" "$INSTDIR\subsurface-uninstall.exe"
-
-# define what to install and place it in the output path
-file /oname=subsurface.exe ../../subsurface.exe
-file /oname=subsurface.ico subsurface.ico
-file /oname=subsurface.svg ../../subsurface.svg
-file /oname=libatk-1.0-0.dll dll/libatk-1.0-0.dll
-file /oname=libcairo-2.dll dll/libcairo-2.dll
-file /oname=libdivecomputer-0.dll dll\libdivecomputer-0.dll
-file /oname=libffi-5.dll dll\libffi-5.dll
-file /oname=libfontconfig-1.dll dll\libfontconfig-1.dll
-file /oname=libfreetype-6.dll dll\libfreetype-6.dll
-file /oname=libgdk_pixbuf-2.0-0.dll dll\libgdk_pixbuf-2.0-0.dll
-file /oname=libgdk-win32-2.0-0.dll dll\libgdk-win32-2.0-0.dll
-file /oname=libgio-2.0-0.dll dll\libgio-2.0-0.dll
-file /oname=libglib-2.0-0.dll dll\libglib-2.0-0.dll
-file /oname=libgmodule-2.0-0.dll dll\libgmodule-2.0-0.dll
-file /oname=libgobject-2.0-0.dll dll\libgobject-2.0-0.dll
-file /oname=libgthread-2.0-0.dll dll\libgthread-2.0-0.dll
-file /oname=libgtk-win32-2.0-0.dll dll\libgtk-win32-2.0-0.dll
-file /oname=libintl-8.dll dll\libintl-8.dll
-file /oname=libjasper-1.dll dll\libjasper-1.dll
-file /oname=libjpeg-8.dll dll\libjpeg-8.dll
-file /oname=libpango-1.0-0.dll dll\libpango-1.0-0.dll
-file /oname=libpangocairo-1.0-0.dll dll\libpangocairo-1.0-0.dll
-file /oname=libpangoft2-1.0-0.dll dll\libpangoft2-1.0-0.dll
-file /oname=libpangowin32-1.0-0.dll dll\libpangowin32-1.0-0.dll
-file /oname=libpixman-1-0.dll dll\libpixman-1-0.dll
-file /oname=libpng15-15.dll dll\libpng15-15.dll
-file /oname=libtiff-5.dll dll\libtiff-5.dll
-file /oname=libxml2-2.dll dll\libxml2-2.dll
-file /oname=libxslt-1.dll dll\libxslt-1.dll
-file /oname=pthreadGC2.dll dll\pthreadGC2.dll
-file /oname=zlib1.dll dll\zlib1.dll
-file /oname=libusb-1.0.dll dll\libusb-1.0.dll
-file /oname=SuuntoSDM.xslt ../../xslt/SuuntoSDM.xslt
-file /oname=jdivelog2subsurface.xslt ../../xslt/jdivelog2subsurface.xslt
-sectionEnd
-
-section "uninstall"
- SetShellVarContext all
- delete "$INSTDIR\subsurface-uninstall.exe"
- delete "$INSTDIR\*.*"
- RMDir "$INSTDIR"
- delete "$SMPROGRAMS\subsurface\uninstall-subsurface.lnk"
- delete "$SMPROGRAMS\subsurface\subsurface.lnk"
- RMDir "$SMPROGRAMS\subsurface"
-
- MessageBox MB_YESNO "Do you wish to keep subsurface's registry settings?" IDYES end
- DeleteRegKey HKCU "SOFTWARE\subsurface"
- end:
-sectionEnd
+ # Installation path
+ SetOutPath "$INSTDIR"
+
+ # Delete any already installed DLLs to avoid buildup of various
+ # versions of the same library when upgrading
+ Delete "$INSTDIR\*.dll"
+
+ # Files to include in installer
+ file /oname=subsurface.exe ..\..\subsurface.exe
+ file /oname=subsurface.ico subsurface.ico
+ file /oname=subsurface.svg ..\..\subsurface.svg
+ file /oname=libatk-1.0-0.dll dll\libatk-1.0-0.dll
+ file /oname=libcairo-2.dll dll\libcairo-2.dll
+ file /oname=libdivecomputer-0.dll dll\libdivecomputer-0.dll
+ file /oname=libffi-6.dll dll\libffi-6.dll
+ file /oname=libfontconfig-1.dll dll\libfontconfig-1.dll
+ file /oname=libfreetype-6.dll dll\libfreetype-6.dll
+ file /oname=libgdk_pixbuf-2.0-0.dll dll\libgdk_pixbuf-2.0-0.dll
+ file /oname=libgdk-win32-2.0-0.dll dll\libgdk-win32-2.0-0.dll
+ file /oname=libgio-2.0-0.dll dll\libgio-2.0-0.dll
+ file /oname=libglib-2.0-0.dll dll\libglib-2.0-0.dll
+ file /oname=libgmodule-2.0-0.dll dll\libgmodule-2.0-0.dll
+ file /oname=libgobject-2.0-0.dll dll\libgobject-2.0-0.dll
+ file /oname=libgthread-2.0-0.dll dll\libgthread-2.0-0.dll
+ file /oname=libgtk-win32-2.0-0.dll dll\libgtk-win32-2.0-0.dll
+ file /oname=libintl-8.dll dll\libintl-8.dll
+ file /oname=libjasper-1.dll dll\libjasper-1.dll
+ file /oname=libjpeg-62.dll dll\libjpeg-62.dll
+ file /oname=libpango-1.0-0.dll dll\libpango-1.0-0.dll
+ file /oname=libpangocairo-1.0-0.dll dll\libpangocairo-1.0-0.dll
+ file /oname=libpangoft2-1.0-0.dll dll\libpangoft2-1.0-0.dll
+ file /oname=libpangowin32-1.0-0.dll dll\libpangowin32-1.0-0.dll
+ file /oname=libpixman-1-0.dll dll\libpixman-1-0.dll
+ file /oname=libpng15-15.dll dll\libpng15-15.dll
+ file /oname=libtiff-3.dll dll\libtiff-3.dll
+ file /oname=libxml2-2.dll dll\libxml2-2.dll
+ file /oname=libxslt-1.dll dll\libxslt-1.dll
+ file /oname=pthreadGC2.dll dll\pthreadGC2.dll
+ file /oname=zlib1.dll dll\zlib1.dll
+ file /oname=libusb-1.0.dll dll\libusb-1.0.dll
+ file /oname=SuuntoSDM.xslt ..\..\xslt\SuuntoSDM.xslt
+ file /oname=jdivelog2subsurface.xslt ..\..\xslt\jdivelog2subsurface.xslt
+ file /oname=iconv.dll dll\iconv.dll
+
+ # Store installation folder in registry
+ WriteRegStr HKCU "Software\Subsurface" "" $INSTDIR
+
+ # Create shortcuts
+ !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
+ CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
+ CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Subsurface.lnk" "$INSTDIR\subsurface.exe"
+ CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall Subsurface.lnk" "$INSTDIR\Uninstall.exe"
+ !insertmacro MUI_STARTMENU_WRITE_END
+
+ # Create the uninstaller
+ WriteUninstaller "$INSTDIR\Uninstall.exe"
+
+SectionEnd
+
+#--------------------------------
+# Uninstaller section
+
+Section "Uninstall"
+
+ # Delete installed files
+ Delete "$INSTDIR\*.dll"
+ Delete "$INSTDIR\*.xslt"
+ Delete "$INSTDIR\subsurface.exe"
+ Delete "$INSTDIR\subsurface.ico"
+ Delete "$INSTDIR\subsurface.svg"
+ Delete "$INSTDIR\Uninstall.exe"
+ RMDir "$INSTDIR"
+
+ # Remove shortcuts
+ !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder
+ Delete "$SMPROGRAMS\$StartMenuFolder\Subsurface.lnk"
+ Delete "$SMPROGRAMS\$StartMenuFolder\Uninstall Subsurface.lnk"
+ RMDir "$SMPROGRAMS\$StartMenuFolder"
+
+ # Remove registry entries
+ DeleteRegKey /ifempty HKCU "Software\Subsurface"
+
+SectionEnd
diff --git a/print.c b/print.c
index b30a8d029..12504b13d 100644
--- a/print.c
+++ b/print.c
@@ -34,7 +34,7 @@ static struct dive *get_dive_for_printing(int idx)
static void set_font(PangoLayout *layout, PangoFontDescription *font,
double size, int align)
{
- pango_font_description_set_size(font, size * PANGO_SCALE);
+ pango_font_description_set_size(font, size * PANGO_SCALE * SCALE_PRINT);
pango_layout_set_font_description(layout, font);
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
pango_layout_set_alignment(layout, align);
@@ -149,7 +149,8 @@ static void show_dive_text(struct dive *dive, cairo_t *cr, double w,
static void show_table_header(cairo_t *cr, double w, double h,
PangoFontDescription *font)
{
- int maxwidth, maxheight, colwidth, i, curwidth;
+ int i;
+ double maxwidth, maxheight, colwidth, curwidth;
PangoLayout *layout;
char headers[7][80]= { "Dive#", "Date", "Depth", "Time", "Master",
"Buddy", "Location" };
@@ -190,7 +191,8 @@ static void show_dive_table(struct dive *dive, cairo_t *cr, double w,
{
double depth;
const char *unit;
- int len, decimals, maxwidth, maxheight, colwidth, curwidth;
+ int len, decimals;
+ double maxwidth, maxheight, colwidth, curwidth;
PangoLayout *layout;
struct tm *tm;
char buffer[160], divenr[20];
@@ -245,7 +247,7 @@ static void show_dive_table(struct dive *dive, cairo_t *cr, double w,
pango_layout_set_justify(layout, 1);
pango_cairo_show_layout(cr, layout);
curwidth = curwidth + (colwidth / 2);
-
+
// Col 4: Time
len = snprintf(buffer, sizeof(buffer),
"%d min",(dive->duration.seconds+59) / 60);
@@ -284,13 +286,13 @@ static void show_dive_table(struct dive *dive, cairo_t *cr, double w,
static void show_dive_profile(struct dive *dive, cairo_t *cr, double w,
double h)
{
- cairo_rectangle_int_t drawing_area = { w/20.0, h/20.0, w, h};
+ cairo_rectangle_t drawing_area = { w/20.0, h/20.0, w, h};
struct graphics_context gc = {
.printer = 1,
.cr = cr
};
cairo_save(cr);
- plot(&gc, &drawing_area, dive);
+ plot(&gc, &drawing_area, dive, SC_PRINT);
cairo_restore(cr);
}
@@ -533,7 +535,7 @@ void do_print(void)
repaint_dive();
print = gtk_print_operation_new();
- gtk_print_operation_set_unit(print, GTK_UNIT_POINTS);
+ gtk_print_operation_set_unit(print, GTK_UNIT_INCH);
if (settings != NULL)
gtk_print_operation_set_print_settings(print, settings);
g_signal_connect(print, "create-custom-widget", G_CALLBACK(print_dialog), NULL);
diff --git a/profile.c b/profile.c
index 0039867ef..3a3df043c 100644
--- a/profile.c
+++ b/profile.c
@@ -16,6 +16,11 @@
int selected_dive = 0;
char zoomed_plot = 0;
+static double plot_scale = SCALE_SCREEN;
+
+#define cairo_set_line_width_scaled(cr, w) \
+ cairo_set_line_width((cr), (w) * plot_scale);
+
typedef enum { STABLE, SLOW, MODERATE, FAST, CRAZY } velocity_t;
/* Plot info with smoothing, velocity indication
@@ -245,7 +250,7 @@ static void plot_text(struct graphics_context *gc, const text_render_options_t *
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
- cairo_set_font_size(cr, tro->size);
+ cairo_set_font_size(cr, tro->size * plot_scale);
cairo_font_extents(cr, &fe);
cairo_text_extents(cr, buffer, &extents);
dx = tro->hpos * extents.width + extents.x_bearing;
@@ -485,7 +490,7 @@ static void plot_depth_profile(struct graphics_context *gc, struct plot_info *pi
gc->leftx = 0; gc->rightx = maxtime;
gc->topy = 0; gc->bottomy = 1.0;
set_source_rgba(gc, TIME_GRID);
- cairo_set_line_width(gc->cr, 2);
+ cairo_set_line_width_scaled(gc->cr, 2);
for (i = incr; i < maxtime; i += incr) {
move_to(gc, i, 0);
@@ -542,13 +547,13 @@ static void plot_depth_profile(struct graphics_context *gc, struct plot_info *pi
gc->topy = 0; gc->bottomy = maxdepth;
cairo_pattern_t *pat;
- pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, 256.0);
+ pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, 256.0 * plot_scale);
pattern_add_color_stop_rgba (gc, pat, 1, DEPTH_BOTTOM);
pattern_add_color_stop_rgba (gc, pat, 0, DEPTH_TOP);
cairo_set_source(gc->cr, pat);
cairo_pattern_destroy(pat);
- cairo_set_line_width(gc->cr, 2);
+ cairo_set_line_width_scaled(gc->cr, 2);
entry = pi->entry;
move_to(gc, 0, 0);
@@ -655,7 +660,7 @@ static void plot_temperature_profile(struct graphics_context *gc, struct plot_in
if (!setup_temperature_limits(gc, pi))
return;
- cairo_set_line_width(gc->cr, 2);
+ cairo_set_line_width_scaled(gc->cr, 2);
set_source_rgba(gc, TEMP_PLOT);
for (i = 0; i < pi->nr; i++) {
struct plot_data *entry = pi->entry + i;
@@ -730,7 +735,7 @@ static void plot_cylinder_pressure(struct graphics_context *gc, struct plot_info
if (!get_cylinder_pressure_range(gc, pi))
return;
- cairo_set_line_width(gc->cr, 2);
+ cairo_set_line_width_scaled(gc->cr, 2);
for (i = 0; i < pi->nr; i++) {
int mbar;
@@ -1374,13 +1379,28 @@ static struct plot_info *create_plot_info(struct dive *dive, int nr_samples, str
return analyze_plot_info(pi);
}
-void plot(struct graphics_context *gc, cairo_rectangle_int_t *drawing_area, struct dive *dive)
+static void plot_set_scale(scale_mode_t scale)
+{
+ switch (scale) {
+ default:
+ case SC_SCREEN:
+ plot_scale = SCALE_SCREEN;
+ break;
+ case SC_PRINT:
+ plot_scale = SCALE_PRINT;
+ break;
+ }
+}
+
+void plot(struct graphics_context *gc, cairo_rectangle_t *drawing_area, struct dive *dive, scale_mode_t scale)
{
struct plot_info *pi;
static struct sample fake[4];
struct sample *sample = dive->sample;
int nr = dive->samples;
+ plot_set_scale(scale);
+
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. */
@@ -1401,7 +1421,7 @@ void plot(struct graphics_context *gc, cairo_rectangle_int_t *drawing_area, stru
pi = create_plot_info(dive, nr, sample);
cairo_translate(gc->cr, drawing_area->x, drawing_area->y);
- cairo_set_line_width(gc->cr, 1);
+ cairo_set_line_width_scaled(gc->cr, 1);
cairo_set_line_cap(gc->cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_join(gc->cr, CAIRO_LINE_JOIN_ROUND);
@@ -1435,7 +1455,7 @@ void plot(struct graphics_context *gc, cairo_rectangle_int_t *drawing_area, stru
gc->topy = 0; gc->bottomy = 1.0;
set_source_rgba(gc, BOUNDING_BOX);
- cairo_set_line_width(gc->cr, 1);
+ cairo_set_line_width_scaled(gc->cr, 1);
move_to(gc, 0, 0);
line_to(gc, 0, 1);
line_to(gc, 1, 1);
diff --git a/save-xml.c b/save-xml.c
index 3bea2adfa..7ce0afbc1 100644
--- a/save-xml.c
+++ b/save-xml.c
@@ -7,8 +7,6 @@
#include "dive.h"
-#define FRACTION(n,x) ((unsigned)(n)/(x)),((unsigned)(n)%(x))
-
static void show_milli(FILE *f, const char *pre, int value, const char *unit, const char *post)
{
int i;
diff --git a/statistics.c b/statistics.c
index 674efbb94..5e1c8804a 100644
--- a/statistics.c
+++ b/statistics.c
@@ -13,6 +13,7 @@
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
+#include <gdk/gdkkeysyms.h>
#include "dive.h"
#include "display.h"
@@ -54,6 +55,7 @@ typedef struct {
static total_stats_widget_t stats_w;
typedef struct {
+ int period;
duration_t total_time;
/* avg_time is simply total_time / nr -- let's not keep this */
duration_t shortest_time;
@@ -74,7 +76,31 @@ typedef struct {
static stats_t stats;
static stats_t stats_selection;
-
+static stats_t *stats_monthly = NULL;
+static stats_t *stats_yearly = NULL;
+
+GtkWidget *yearly_tree = NULL;
+
+enum {
+ YEAR,
+ DIVES,
+ TOTAL_TIME,
+ AVERAGE_TIME,
+ SHORTEST_TIME,
+ LONGEST_TIME,
+ AVG_DEPTH,
+ MIN_DEPTH,
+ MAX_DEPTH,
+ AVG_SAC,
+ MIN_SAC,
+ MAX_SAC,
+ AVG_TEMP,
+ MIN_TEMP,
+ MAX_TEMP,
+ N_COLUMNS
+};
+
+static char * get_time_string(int seconds, int maxdays);
static void process_dive(struct dive *dp, stats_t *stats)
{
@@ -117,10 +143,234 @@ static void process_dive(struct dive *dp, stats_t *stats)
}
}
+static void init_tree()
+{
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+ GtkTreeStore *store;
+ int i;
+ PangoFontDescription *font_desc = pango_font_description_from_string(divelist_font);
+
+ gtk_widget_modify_font(yearly_tree, font_desc);
+ pango_font_description_free(font_desc);
+
+ renderer = gtk_cell_renderer_text_new ();
+ char *columns[] = {
+ "Year\n > Month", "#", "Duration\nTotal", "\nAverage",
+ "\nShortest", "\nLongest", "Depth\nAverage", "\nMinimum",
+ "\nMaximum", "SAC\nAverage", "\nMinimum", "\nMaximum", "Temperature\nAverage",
+ "\nMinimum", "\nMaximum" };
+
+ /* Add all the columns to the tree view */
+ for (i = 0; i < N_COLUMNS; ++i) {
+ column = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(column, columns[i]);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(yearly_tree), column);
+ renderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(column, renderer, TRUE);
+ gtk_tree_view_column_add_attribute(column, renderer, "text", i);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ }
+
+ /* Field types */
+ store = gtk_tree_store_new (
+ N_COLUMNS, // Columns in structure
+ G_TYPE_STRING, // Period (year or month)
+ G_TYPE_STRING, // Number of dives
+ G_TYPE_STRING, // Total duration
+ G_TYPE_STRING, // Average dive duation
+ G_TYPE_STRING, // Shortest dive
+ G_TYPE_STRING, // Longest dive
+ G_TYPE_STRING, // Average depth
+ G_TYPE_STRING, // Shallowest dive
+ G_TYPE_STRING, // Deepest dive
+ G_TYPE_STRING, // Average air consumption (SAC)
+ G_TYPE_STRING, // Minimum SAC
+ G_TYPE_STRING, // Maximum SAC
+ G_TYPE_STRING, // Average temperature
+ G_TYPE_STRING, // Minimum temperature
+ G_TYPE_STRING // Maximum temperature
+ );
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (yearly_tree), GTK_TREE_MODEL (store));
+ g_object_unref (store);
+}
+
+static void add_row_to_tree(GtkTreeStore *store, char *value, int index, GtkTreeIter *row_iter, GtkTreeIter *parent)
+{
+ gtk_tree_store_append(store, row_iter, parent);
+ gtk_tree_store_set(store, row_iter, index, value, -1);
+}
+
+static void add_cell_to_tree(GtkTreeStore *store, char *value, int index, GtkTreeIter *parent)
+{
+ gtk_tree_store_set(store, parent, index, value, -1);
+}
+static char * get_minutes(int seconds)
+{
+ static char buf[80];
+ snprintf(buf, sizeof(buf), "%d:%.2d", FRACTION(seconds, 60));
+ return buf;
+}
+
+static void add_cell(GtkTreeStore *store, GtkTreeIter *parent, unsigned int val, int cell, gboolean depth_not_volume)
+{
+ double value;
+ int decimals;
+ const char *unit;
+ char value_str[40];
+
+ if (depth_not_volume) {
+ value = get_depth_units(val, &decimals, &unit);
+ snprintf(value_str, sizeof(value_str), "%.*f %s", decimals, value, unit);
+ } else {
+ value = get_volume_units(val, &decimals, &unit);
+ snprintf(value_str, sizeof(value_str), "%.*f %s/min", decimals, value, unit);
+ }
+ add_cell_to_tree(store, value_str, cell, parent);
+}
+
+static void process_interval_stats(stats_t stats_interval, GtkTreeIter *parent, GtkTreeIter *row)
+{
+ double value;
+ const char *unit;
+ char value_str[40];
+ GtkTreeStore *store;
+
+ store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(yearly_tree)));
+
+ /* Year or month */
+ snprintf(value_str, sizeof(value_str), "%d", stats_interval.period);
+ add_row_to_tree(store, value_str, 0, row, parent);
+ /* Dives */
+ snprintf(value_str, sizeof(value_str), "%d", stats_interval.selection_size);
+ add_cell_to_tree(store, value_str, 1, row);
+ /* Total duration */
+ add_cell_to_tree(store, get_time_string(stats_interval.total_time.seconds, 0), 2, row);
+ /* Average dive duration */
+ add_cell_to_tree(store, get_minutes(stats_interval.total_time.seconds / stats_interval.selection_size), 3, row);
+ /* Shortest duration */
+ add_cell_to_tree(store, get_minutes(stats_interval.shortest_time.seconds), 4, row);
+ /* Longest duration */
+ add_cell_to_tree(store, get_minutes(stats_interval.longest_time.seconds), 5, row);
+ /* Average depth */
+ add_cell(store, row, stats_interval.avg_depth.mm, 6, TRUE);
+ /* Smallest maximum depth */
+ add_cell(store, row, stats_interval.min_depth.mm, 7, TRUE);
+ /* Deepest maximum depth */
+ add_cell(store, row, stats_interval.max_depth.mm, 8, TRUE);
+ /* Average air consumption */
+ add_cell(store, row, stats_interval.avg_sac.mliter, 9, FALSE);
+ /* Smallest average air consumption */
+ add_cell(store, row, stats_interval.min_sac.mliter, 10, FALSE);
+ /* Biggest air consumption */
+ add_cell(store, row, stats_interval.max_sac.mliter, 11, FALSE);
+ /* Average water temperature */
+ value = get_temp_units(stats_interval.min_temp, &unit);
+ if (stats_interval.combined_temp && stats_interval.combined_count) {
+ snprintf(value_str, sizeof(value_str), "%.1f %s", stats_interval.combined_temp / (stats_interval.combined_count * 1.0), unit);
+ add_cell_to_tree(store, value_str, 12, row);
+ } else {
+ add_cell_to_tree(store, "", 12, row);
+ }
+ /* Coldest water temperature */
+ snprintf(value_str, sizeof(value_str), "%.1f %s\t", value, unit);
+ add_cell_to_tree(store, value_str, 13, row);
+ /* Warmest water temperature */
+ value = get_temp_units(stats_interval.max_temp, &unit);
+ snprintf(value_str, sizeof(value_str), "%.1f %s", value, unit);
+ add_cell_to_tree(store, value_str, 14, row);
+}
+
+void clear_statistics()
+{
+ GtkTreeStore *store;
+
+ store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(yearly_tree)));
+ gtk_tree_store_clear(store);
+ yearly_tree = NULL;
+}
+
+static gboolean on_delete(GtkWidget *window, GdkEvent *event, gpointer data)
+{
+ clear_statistics();
+ gtk_widget_destroy(window);
+ return TRUE;
+}
+
+static void key_press_event(GtkWidget *window, GdkEventKey *event, gpointer data)
+{
+ if ((event->string != NULL && event->keyval == GDK_Escape) ||
+ (event->string != NULL && event->keyval == GDK_w && event->state & GDK_CONTROL_MASK)) {
+ clear_statistics();
+ gtk_widget_destroy(window);
+ }
+}
+
+void update_yearly_stats()
+{
+ int i, j, combined_months, month = 0;
+ GtkTreeIter year_iter, month_iter;
+ GtkTreeStore *store;
+
+ store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(yearly_tree)));
+ gtk_tree_store_clear(store);
+
+ for (i = 0; stats_yearly != NULL && stats_yearly[i].period; ++i) {
+ process_interval_stats(stats_yearly[i], NULL, &year_iter);
+ combined_months = 0;
+
+ for (j = 0; combined_months < stats_yearly[i].selection_size; ++j) {
+ combined_months += stats_monthly[month].selection_size;
+ process_interval_stats(stats_monthly[month++], &year_iter, &month_iter);
+ }
+ }
+}
+
+void show_yearly_stats()
+{
+ GtkWidget *window;
+ GtkWidget *sw;
+
+ if (yearly_tree)
+ return;
+
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ yearly_tree = gtk_tree_view_new ();
+
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
+ gtk_window_set_default_size(GTK_WINDOW(window), 640, 480);
+ gtk_window_set_title(GTK_WINDOW(window), "Yearly Statistics");
+ gtk_container_set_border_width(GTK_CONTAINER(window), 5);
+ GTK_WINDOW(window)->allow_shrink = TRUE;
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_ETCHED_IN);
+
+ gtk_container_add (GTK_CONTAINER (sw), yearly_tree);
+ gtk_container_add (GTK_CONTAINER (window), sw);
+
+ /* Display the yearly statistics on top level
+ * Monthly statistics are available by expanding a year */
+ init_tree();
+ update_yearly_stats();
+
+ g_signal_connect (G_OBJECT (window), "key_press_event", G_CALLBACK (key_press_event), NULL);
+ g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK (on_delete), NULL);
+ gtk_widget_show_all(window);
+}
+
static void process_all_dives(struct dive *dive, struct dive **prev_dive)
{
int idx;
struct dive *dp;
+ struct tm *tm;
+ int current_year = 0;
+ int current_month = 0;
+ int year_iter = 0;
+ int month_iter = 0;
+ int prev_month = 0, prev_year = 0;
+ unsigned int size;
*prev_dive = NULL;
memset(&stats, 0, sizeof(stats));
@@ -129,6 +379,23 @@ static void process_all_dives(struct dive *dive, struct dive **prev_dive)
stats.min_depth.mm = dive_table.dives[0]->maxdepth.mm;
stats.selection_size = dive_table.nr;
}
+
+ /* allocate sufficient space to hold the worst
+ * case (one dive per year or all dives during
+ * one month) for yearly and monthly statistics*/
+
+ if (stats_yearly != NULL) {
+ free(stats_yearly);
+ free(stats_monthly);
+ }
+ size = sizeof(stats_t) * (dive_table.nr + 1);
+ stats_yearly = malloc(size);
+ stats_monthly = malloc(size);
+ if (!stats_yearly || !stats_monthly)
+ return;
+ memset(stats_yearly, 0, size);
+ memset(stats_monthly, 0, size);
+
/* this relies on the fact that the dives in the dive_table
* are in chronological order */
for (idx = 0; idx < dive_table.nr; idx++) {
@@ -139,7 +406,39 @@ static void process_all_dives(struct dive *dive, struct dive **prev_dive)
*prev_dive = dive_table.dives[idx-1];
}
process_dive(dp, &stats);
+
+ /* yearly statistics */
+ tm = gmtime(&dp->when);
+ if (current_year == 0)
+ current_year = tm->tm_year + 1900;
+
+ if (current_year != tm->tm_year + 1900) {
+ current_year = tm->tm_year + 1900;
+ process_dive(dp, &(stats_yearly[++year_iter]));
+ } else
+ process_dive(dp, &(stats_yearly[year_iter]));
+
+ stats_yearly[year_iter].selection_size++;
+ stats_yearly[year_iter].period = current_year;
+
+ /* monthly statistics */
+ if (current_month == 0) {
+ current_month = tm->tm_mon + 1;
+ } else {
+ if (current_month != tm->tm_mon + 1)
+ current_month = tm->tm_mon + 1;
+ if (prev_month != current_month || prev_year != current_year)
+ month_iter++;
+ }
+
+ process_dive(dp, &(stats_monthly[month_iter]));
+ stats_monthly[month_iter].selection_size++;
+ stats_monthly[month_iter].period = current_month;
+ prev_month = current_month;
+ prev_year = current_year;
}
+ if (yearly_tree)
+ update_yearly_stats();
}
/* make sure we skip the selected summary entries */