+ }
+ g_object_set(renderer, "text", buffer, NULL);
+}
+
+static void depth_data_func(GtkTreeViewColumn *col,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ int depth, integer, frac, len, idx;
+ char buffer[40];
+
+ gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1);
+
+ if (idx < 0) {
+ *buffer = '\0';
+ } else {
+ switch (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;
+ case FEET:
+ integer = mm_to_feet(depth) + 0.5;
+ frac = -1;
+ break;
+ default:
+ return;
+ }
+ len = snprintf(buffer, sizeof(buffer), "%d", integer);
+ if (frac >= 0)
+ len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
+ }
+ g_object_set(renderer, "text", buffer, NULL);
+}
+
+static void duration_data_func(GtkTreeViewColumn *col,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ unsigned int sec;
+ int idx;
+ char buffer[16];
+
+ gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1);
+ if (idx < 0)
+ *buffer = '\0';
+ else
+ snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
+
+ g_object_set(renderer, "text", buffer, NULL);
+}
+
+static void temperature_data_func(GtkTreeViewColumn *col,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ int value, idx;
+ char buffer[80];
+
+ gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1);
+
+ *buffer = 0;
+ if (idx >= 0 && value) {
+ double deg;
+ switch (output_units.temperature) {
+ case CELSIUS:
+ deg = mkelvin_to_C(value);
+ break;
+ case FAHRENHEIT:
+ deg = mkelvin_to_F(value);
+ break;
+ default:
+ return;
+ }
+ snprintf(buffer, sizeof(buffer), "%.1f", deg);
+ }
+
+ g_object_set(renderer, "text", buffer, NULL);
+}
+
+static void 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:
+ * - Trimix trumps nitrox (highest He wins, O2 breaks ties)
+ * - Nitrox trumps air (even if hypoxic)
+ * These are the same rules as the inter-dive sorting rules.
+ */
+static void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p)
+{
+ int i;
+ int maxo2 = -1, maxhe = -1, mino2 = 1000;
+
+ for (i = 0; i < MAX_CYLINDERS; i++) {
+ cylinder_t *cyl = dive->cylinder + i;
+ struct gasmix *mix = &cyl->gasmix;
+ int o2 = mix->o2.permille;
+ int he = mix->he.permille;
+
+ if (cylinder_none(cyl))
+ continue;
+ if (!o2)
+ o2 = AIR_PERMILLE;
+ if (o2 < mino2)
+ mino2 = o2;
+ if (he > maxhe)
+ goto newmax;
+ if (he < maxhe)
+ continue;
+ if (o2 <= maxo2)
+ continue;
+newmax:
+ maxhe = he;
+ maxo2 = o2;
+ }
+ /* All air? Show/sort as "air"/zero */
+ if (!maxhe && maxo2 == AIR_PERMILLE && mino2 == maxo2)
+ maxo2 = mino2 = 0;
+ *o2_p = maxo2;
+ *he_p = maxhe;
+ *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,
+ gpointer user_data)
+{
+ int index_a, index_b;
+ struct dive *a, *b;
+ int a_o2, b_o2;
+ int a_he, b_he;
+ int a_o2low, b_o2low;
+
+ gtk_tree_model_get(model, iter_a, DIVE_INDEX, &index_a, -1);
+ gtk_tree_model_get(model, iter_b, DIVE_INDEX, &index_b, -1);
+ a = get_dive(index_a);
+ b = get_dive(index_b);
+ get_dive_gas(a, &a_o2, &a_he, &a_o2low);
+ get_dive_gas(b, &b_o2, &b_he, &b_o2low);
+
+ /* Sort by Helium first, O2 second */
+ if (a_he == b_he) {
+ if (a_o2 == b_o2)
+ return a_o2low - b_o2low;
+ return a_o2 - b_o2;
+ }
+ return a_he - b_he;
+}
+
+#define UTF8_ELLIPSIS "\xE2\x80\xA6"
+
+static void nitrox_data_func(GtkTreeViewColumn *col,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ int idx, o2, he, o2low;
+ char buffer[80];
+ struct dive *dive;
+
+ gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
+ if (idx < 0) {
+ *buffer = '\0';
+ goto exit;
+ }
+ dive = get_dive(idx);
+ get_dive_gas(dive, &o2, &he, &o2low);
+ o2 = (o2 + 5) / 10;
+ he = (he + 5) / 10;
+ o2low = (o2low + 5) / 10;
+
+ if (he)
+ snprintf(buffer, sizeof(buffer), "%d/%d", o2, he);
+ else if (o2)
+ if (o2 == o2low)
+ snprintf(buffer, sizeof(buffer), "%d", o2);
+ else
+ snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
+ else
+ strcpy(buffer, "air");
+exit:
+ g_object_set(renderer, "text", buffer, NULL);
+}
+
+/* Render the SAC data (integer value of "ml / min") */
+static void sac_data_func(GtkTreeViewColumn *col,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ int value, idx;
+ const char *fmt;
+ char buffer[16];
+ double sac;
+
+ gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1);
+
+ if (idx < 0 || !value) {
+ *buffer = '\0';
+ goto exit;
+ }
+
+ sac = value / 1000.0;
+ switch (output_units.volume) {
+ case LITER:
+ fmt = "%4.1f";
+ break;
+ case CUFT:
+ fmt = "%4.2f";
+ sac = ml_to_cuft(sac * 1000);
+ break;
+ }
+ snprintf(buffer, sizeof(buffer), fmt, sac);
+exit:
+ g_object_set(renderer, "text", buffer, NULL);
+}
+
+/* Render the OTU data (integer value of "OTU") */
+static void otu_data_func(GtkTreeViewColumn *col,
+ GtkCellRenderer *renderer,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ int value, idx;
+ char buffer[16];
+
+ gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1);
+
+ if (idx < 0 || !value)
+ *buffer = '\0';
+ else
+ snprintf(buffer, sizeof(buffer), "%d", value);
+
+ g_object_set(renderer, "text", buffer, NULL);
+}
+
+/* calculate OTU for a dive */
+static int calculate_otu(struct dive *dive)
+{
+ int i;
+ double otu = 0.0;
+
+ for (i = 1; i < dive->samples; i++) {
+ int t;
+ double po2;
+ struct sample *sample = dive->sample + i;
+ struct sample *psample = sample - 1;
+ t = sample->time.seconds - psample->time.seconds;
+ int o2 = dive->cylinder[sample->cylinderindex].gasmix.o2.permille;
+ if (!o2)
+ o2 = AIR_PERMILLE;
+ po2 = o2 / 1000.0 * (sample->depth.mm + 10000) / 10000.0;
+ if (po2 >= 0.5)
+ otu += pow(po2 - 0.5, 0.83) * t / 30.0;
+ }
+ return otu + 0.5;
+}
+/*
+ * Return air usage (in liters).
+ */
+static double calculate_airuse(struct dive *dive)
+{
+ double airuse = 0;
+ int i;
+
+ for (i = 0; i < MAX_CYLINDERS; i++) {
+ pressure_t start, end;
+ cylinder_t *cyl = dive->cylinder + i;
+ int size = cyl->type.size.mliter;
+ double kilo_atm;
+
+ if (!size)
+ continue;
+
+ start = cyl->start.mbar ? cyl->start : cyl->sample_start;
+ end = cyl->end.mbar ? cyl->end : cyl->sample_end;
+ kilo_atm = (to_ATM(start) - to_ATM(end)) / 1000.0;
+
+ /* Liters of air at 1 atm == milliliters at 1k atm*/
+ airuse += kilo_atm * size;
+ }
+ return airuse;
+}
+
+static int calculate_sac(struct dive *dive)
+{
+ double airuse, pressure, sac;
+ int duration, i;
+
+ airuse = calculate_airuse(dive);
+ if (!airuse)
+ return 0;
+ if (!dive->duration.seconds)
+ return 0;
+
+ /* find and eliminate long surface intervals */
+ duration = dive->duration.seconds;
+ for (i = 0; i < dive->samples; i++) {
+ if (dive->sample[i].depth.mm < 100) { /* less than 10cm */
+ int end = i + 1;
+ while (end < dive->samples && dive->sample[end].depth.mm < 100)
+ end++;
+ /* we only want the actual surface time during a dive */
+ if (end < dive->samples) {
+ end--;
+ duration -= dive->sample[end].time.seconds -
+ dive->sample[i].time.seconds;
+ i = end + 1;
+ }
+ }
+ }
+ /* Mean pressure in atm: 1 atm per 10m */
+ pressure = 1 + (dive->meandepth.mm / 10000.0);
+ sac = airuse / pressure * 60 / duration;
+
+ /* milliliters per minute.. */
+ return sac * 1000;
+}
+
+void update_cylinder_related_info(struct dive *dive)
+{
+ if (dive != NULL) {
+ dive->sac = calculate_sac(dive);
+ dive->otu = calculate_otu(dive);
+ }
+}
+
+static void get_string(char **str, const char *s)
+{
+ int len;
+ char *n;
+
+ if (!s)
+ s = "";
+ len = strlen(s);
+ if (len > 60)
+ len = 60;
+ n = malloc(len+1);
+ memcpy(n, s, len);
+ n[len] = 0;
+ *str = n;
+}
+
+static void get_location(struct dive *dive, char **str)
+{
+ get_string(str, dive->location);
+}
+
+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; we need to do this for both models,
+ * so we simply call set_one_dive again with the non-current model
+ */
+/* forward declaration for recursion */
+static gboolean set_one_dive(GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data);
+
+static void fill_one_dive(struct dive *dive,
+ GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ char *location, *cylinder, *suit;
+ GtkTreeStore *othermodel;
+
+ get_cylinder(dive, &cylinder);
+ get_location(dive, &location);
+ get_suit(dive, &suit);
+
+ 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,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ int idx;
+ struct dive *dive;
+
+ /* Get the dive number */
+ gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
+ if (idx < 0)
+ return FALSE;
+ dive = get_dive(idx);
+ if (!dive)
+ return TRUE;
+ if (data && dive != data)
+ return FALSE;
+
+ fill_one_dive(dive, model, iter);
+ return dive == data;
+}
+
+void flush_divelist(struct dive *dive)
+{
+ GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
+
+ gtk_tree_model_foreach(model, set_one_dive, dive);
+}
+
+void set_divelist_font(const char *font)
+{
+ PangoFontDescription *font_desc = pango_font_description_from_string(font);
+ gtk_widget_modify_font(dive_list.tree_view, font_desc);
+ pango_font_description_free(font_desc);
+}
+
+void update_dive_list_units(void)
+{
+ const char *unit;
+ GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
+
+ (void) get_depth_units(0, NULL, &unit);
+ gtk_tree_view_column_set_title(dive_list.depth, unit);
+
+ (void) get_temp_units(0, &unit);
+ gtk_tree_view_column_set_title(dive_list.temperature, unit);
+
+ (void) get_weight_units(0, NULL, &unit);
+ gtk_tree_view_column_set_title(dive_list.totalweight, unit);
+
+ gtk_tree_model_foreach(model, set_one_dive, NULL);
+}
+
+void update_dive_list_col_visibility(void)
+{
+ gtk_tree_view_column_set_visible(dive_list.cylinder, 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;
+}
+
+static void fill_dive_list(void)
+{
+ int i;
+ GtkTreeIter iter, parent_iter, *parent_ptr = NULL;
+ GtkTreeStore *liststore, *treestore;
+ struct dive *last_trip = NULL;
+ GList *trip;
+ struct dive *dive_trip = NULL;
+
+ /* if we have pre-existing trips, start on the last one */
+ trip = g_list_last(dive_trip_list);
+
+ treestore = GTK_TREE_STORE(dive_list.treemodel);
+ liststore = GTK_TREE_STORE(dive_list.listmodel);
+
+ i = dive_table.nr;
+ while (--i >= 0) {
+ struct dive *dive = get_dive(i);
+
+ /* make sure we display the first date of the trip in previous summary */
+ if (dive_trip && parent_ptr) {
+ gtk_tree_store_set(treestore, parent_ptr,
+ DIVE_NR, dive_trip->number,
+ DIVE_DATE, dive_trip->when,
+ DIVE_LOCATION, dive_trip->location,
+ -1);
+ }
+ /* the dive_trip info might have been killed by a previous UNGROUPED dive */
+ if (trip)
+ dive_trip = DIVE_TRIP(trip);
+ /* tripflag defines how dives are handled;
+ * TF_NONE "not handled yet" - create time based group if autogroup == TRUE
+ * NO_TRIP "set as no group" - simply leave at top level
+ * IN_TRIP "use the trip with the largest trip time (when) that is <= this dive"
+ */
+ if (UNGROUPED_DIVE(dive)) {
+ /* 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 = alloc_dive();
+ dive_trip_list = insert_trip(dive_trip, dive_trip_list);
+ trip = FIND_TRIP(dive_trip, dive_trip_list);
+ }
+ } else { /* either the dive has a trip or we aren't creating trips */
+ if (! (trip && DIVE_FITS_TRIP(dive, DIVE_TRIP(trip)))) {
+ GList *last_trip = trip;
+ trip = PREV_TRIP(trip, dive_trip_list);
+ if (! (trip && DIVE_FITS_TRIP(dive, DIVE_TRIP(trip)))) {
+ /* we could get here if there are no trips in the XML file
+ * and we aren't creating trips, either.
+ * Otherwise we need to create a new trip */
+ if (autogroup) {
+ dive_trip = alloc_dive();
+ dive_trip_list = insert_trip(dive_trip, dive_trip_list);
+ trip = FIND_TRIP(dive_trip, dive_trip_list);
+ } else {
+ /* let's go back to the last valid trip */
+ trip = last_trip;
+ }
+ } else {
+ dive_trip = trip->data;
+ dive_trip->number = 0;
+ }
+ }
+ }
+ /* update dive_trip to include this dive, increase number of dives in
+ the trip and update location if necessary */
+ if (dive_trip) {
+ dive->tripflag = IN_TRIP;
+ dive_trip->number++;
+ dive_trip->when = dive->when;
+ if (!dive_trip->location && dive->location)
+ dive_trip->location = dive->location;
+ if (dive_trip != last_trip) {
+ last_trip = dive_trip;
+ /* create trip entry */
+ gtk_tree_store_append(treestore, &parent_iter, NULL);
+ parent_ptr = &parent_iter;
+ /* a duration of 0 (and negative index) identifies a group */
+ gtk_tree_store_set(treestore, parent_ptr,
+ DIVE_INDEX, -1,
+ DIVE_NR, dive_trip->number,
+ DIVE_DATE, dive_trip->when,
+ DIVE_LOCATION, dive_trip->location,
+ DIVE_DURATION, 0,
+ -1);
+ }
+ }
+
+ /* store dive */
+ update_cylinder_related_info(dive);
+ gtk_tree_store_append(treestore, &iter, parent_ptr);
+ gtk_tree_store_set(treestore, &iter,
+ DIVE_INDEX, i,
+ DIVE_NR, dive->number,
+ DIVE_DATE, dive->when,
+ DIVE_DEPTH, dive->maxdepth,
+ DIVE_DURATION, dive->duration.seconds,
+ DIVE_LOCATION, dive->location,
+ DIVE_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,