]> git.tdb.fi Git - ext/subsurface.git/blobdiff - divelist.c
Fix profile and average depth for freedives
[ext/subsurface.git] / divelist.c
index a4221f7b95b6dd5b410fa42c46eeb62aab4b57d0..a773b60179674084a2dfe251e5537bf37285e64d 100644 (file)
@@ -26,11 +26,15 @@ struct DiveList {
        GtkWidget    *container_widget;
        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;
 };
 
 static struct DiveList dive_list;
+GList *dive_trip_list;
+gboolean autogroup = FALSE;
+
+const char *tripflag_names[NUM_TRIPFLAGS] = { "TF_NONE", "NOTRIP", "INTRIP" };
 
 /*
  * The dive list has the dive data in both string format (for showing)
@@ -44,6 +48,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,21 +58,22 @@ enum {
        DIVELIST_COLUMNS
 };
 
-/* magic numbers that indicate (as negative values) model entries that
- * are summary entries for day / month / year */
-#define NEW_DAY 1
-#define NEW_MON 2
-#define NEW_YR  3
-
 #ifdef DEBUG_MODEL
 static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path,
                                GtkTreeIter *iter, gpointer data)
 {
        char *location;
-       int idx, nr, rating, depth;
+       int idx, nr, duration;
+       struct dive *dive;
+
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, DIVE_DURATION, &duration, DIVE_LOCATION, &location, -1);
+       printf("entry #%d : nr %d duration %d location %s ", idx, nr, duration, location);
+       dive = get_dive(idx);
+       if (dive)
+               printf("tripflag %d\n", dive->tripflag);
+       else
+               printf("without matching dive\n");
 
-       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;
@@ -78,8 +85,20 @@ static void dump_model(GtkListStore *store)
 }
 #endif
 
-static GList *selected_dives;
-static int *selectiontracker;
+#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
@@ -101,96 +120,172 @@ static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx)
        }
 }
 
-/* if we click on a summary dive, we actually want to select / unselect
-   all the dives "below" it */
-static void select_children(GtkTreeModel *model, GtkTreeSelection * selection,
-                       GtkTreeIter *iter, gboolean was_selected)
+/* 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)
 {
-       int i, nr_children;
-       GtkTreeIter parent;
-       GtkTreePath *tpath;
+       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;
 
-       memcpy(&parent, iter, sizeof(parent));
+       do {
+               int idx;
+               struct dive *dive;
 
-       tpath = gtk_tree_model_get_path(model, &parent);
-       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(model, &child, DIVE_INDEX, &idx, -1);
+               dive = get_dive(idx);
 
-       nr_children = gtk_tree_model_iter_n_children(model, &parent);
-       for (i = 0; i < nr_children; i++) {
-               gtk_tree_model_iter_nth_child(model, iter, &parent, i);
-               if (was_selected)
-                       gtk_tree_selection_unselect_iter(selection, iter);
+               if (dive->selected)
+                       gtk_tree_selection_select_iter(selection, &child);
                else
-                       gtk_tree_selection_select_iter(selection, iter);
-       }
+                       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);
 }
 
-/* this is called _before_ the selection is changed, for every single entry;
- * we simply have it call down the tree to make sure that summary items toggle
- * their children */
+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;
-       int dive_idx;
+       GtkTreeIter iter, *p;
 
-       if (gtk_tree_model_get_iter(model, &iter, path)) {
-               gtk_tree_model_get(model, &iter, DIVE_INDEX, &dive_idx, -1);
-               if (dive_idx < 0) {
-                       select_children(model, selection, &iter, was_selected);
-               }
-       }
-       /* allow this selection to proceed */
+       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;
 }
 
-/* this is called when gtk thinks that the selection has changed */
-static void selection_cb(GtkTreeSelection *selection, gpointer userdata)
+static void select_dive(struct dive *dive, int selected)
 {
-       GtkTreeIter iter;
-       GtkTreePath *path;
-
-       int nr_selected = gtk_tree_selection_count_selected_rows(selection);
-
-       if (selected_dives) {
-               g_list_foreach (selected_dives, (GFunc) gtk_tree_path_free, NULL);
-               g_list_free (selected_dives);
+       if (dive->selected != selected) {
+               amount_selected += selected ? 1 : -1;
+               dive->selected = selected;
        }
-       selected_dives = gtk_tree_selection_get_selected_rows(selection, NULL);
-       selectiontracker = realloc(selectiontracker, nr_selected * sizeof(int));
+}
 
-       switch (nr_selected) {
-       case 0: /* there is no clear way to figure out which dive to show */
-               amount_selected = 0;
-               selected_dive = -1;
-               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(GTK_TREE_MODEL(dive_list.model), &iter, path)) {
-                       gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
-                       /* due to the way this callback gets invoked it is possible that
-                          in the process of unselecting a summary dive we get here with
-                          just one summary dive selected - ignore that case */
-                       if (selected_dive < 0) {
-                               amount_selected = 0;
-                               return;
-                       }
-                       selectiontracker[0] = selected_dive;
-                       repaint_dive();
-               }
+/*
+ * This gets called when a dive group has changed selection.
+ */
+static void select_dive_group(GtkTreeModel *model, GtkTreeSelection *selection, GtkTreeIter *iter, int selected)
+{
+       int first = 1;
+       GtkTreeIter child;
+
+       if (selected == selected_children(model, iter))
                return;
-       default: /* multiple selections - what now?
-                 * We don't change the selected dive unless there is exactly one dive selected; not sure this
-                 * is the most intuitive solution.
-                 * The dives that have been selected are processed */
-               amount_selected = g_list_length(selected_dives);
-               process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model));
-               repaint_dive();
+
+       if (!gtk_tree_model_iter_children(model, &child, iter))
                return;
+
+       do {
+               int idx;
+               struct dive *dive;
+
+               gtk_tree_model_get(model, &child, DIVE_INDEX, &idx, -1);
+               if (first && selected)
+                       selected_dive = idx;
+               first = 0;
+               dive = get_dive(idx);
+               if (dive->selected == selected)
+                       continue;
+
+               select_dive(dive, selected);
+               if (selected)
+                       gtk_tree_selection_select_iter(selection, &child);
+               else
+                       gtk_tree_selection_unselect_iter(selection, &child);
+       } while (gtk_tree_model_iter_next(model, &child));
+}
+
+/*
+ * This gets called _after_ the selections have changed, for each entry that
+ * may have changed. Check if the gtk selection state matches our internal
+ * selection state to verify.
+ *
+ * The group entries are at the end, this guarantees that we have handled
+ * all the dives before we handle groups.
+ */
+static void check_selection_cb(GtkTreeIter *iter, GtkTreeSelection *selection)
+{
+       GtkTreeModel *model = GTK_TREE_MODEL(dive_list.model);
+       struct dive *dive;
+       int idx, gtk_selected;
+
+       gtk_tree_model_get(model, iter,
+               DIVE_INDEX, &idx,
+               -1);
+       dive = get_dive(idx);
+       gtk_selected = gtk_tree_selection_iter_is_selected(selection, iter);
+       if (idx < 0)
+               select_dive_group(model, selection, iter, gtk_selected);
+       else {
+               select_dive(dive, gtk_selected);
+               if (gtk_selected)
+                       selected_dive = idx;
        }
+       gtk_tree_iter_free(iter);
+}
+
+/* this is called when gtk thinks that the selection has changed */
+static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model)
+{
+       GList *changed = selection_changed;
+
+       selection_changed = NULL;
+       g_list_foreach(changed, (GFunc) check_selection_cb, selection);
+       g_list_free(changed);
+#if DEBUG_SELECTION_TRACKING
+       dump_selection();
+#endif
+
+       process_selected_dives();
+       repaint_dive();
 }
 
 const char *star_strings[] = {
@@ -228,36 +323,25 @@ static void date_data_func(GtkTreeViewColumn *col,
                           GtkTreeIter *iter,
                           gpointer data)
 {
-       int val, idx;
+       int val, idx, nr;
        struct tm *tm;
        time_t when;
        char buffer[40];
 
-       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, 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);
-       switch(idx) {
-       case -NEW_DAY:
+       if (idx < 0) {
                snprintf(buffer, sizeof(buffer),
-                       "%s, %s %d, %d",
+                       "Trip %s, %s %d, %d (%d dive%s)",
                        weekday(tm->tm_wday),
                        monthname(tm->tm_mon),
-                       tm->tm_mday, tm->tm_year + 1900);
-               break;
-       case -NEW_MON:
-               snprintf(buffer, sizeof(buffer),
-                       "%s %d",
-                       monthname(tm->tm_mon),
-                       tm->tm_year + 1900);
-               break;
-       case -NEW_YR:
-               snprintf(buffer, sizeof(buffer),
-                       "%d", tm->tm_year + 1900);
-               break;
-       default:
+                       tm->tm_mday, tm->tm_year + 1900,
+                       nr, nr > 1 ? "s" : "");
+       } else {
                snprintf(buffer, sizeof(buffer),
                        "%s, %s %d, %d %02d:%02d",
                        weekday(tm->tm_wday),
@@ -416,6 +500,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,
@@ -650,6 +766,11 @@ 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,
@@ -665,11 +786,12 @@ 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_tree_store_set(GTK_TREE_STORE(model), iter,
                DIVE_NR, dive->number,
@@ -678,7 +800,14 @@ static void fill_one_dive(struct dive *dive,
                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
@@ -735,6 +864,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);
 }
 
@@ -742,70 +874,110 @@ 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 int new_date(struct dive *dive, struct dive **last_dive, const int flag, time_t *tm_date)
-{
-       if (!last_dive)
-               return TRUE;
-       if (*last_dive) {
-               struct dive *ldive = *last_dive;
-               struct tm tm1, tm2;
-               (void) gmtime_r(&dive->when, &tm1);
-               (void) gmtime_r(&ldive->when, &tm2);
-               if (tm1.tm_year == tm2.tm_year &&
-                       (tm1.tm_mon == tm2.tm_mon || flag > NEW_MON) &&
-                       (tm1.tm_mday == tm2.tm_mday || flag > NEW_DAY))
-                       return FALSE;
-       }
-       if (flag == NEW_DAY)
-               *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, j;
-       GtkTreeIter iter, parent_iter[NEW_YR + 2], *parents[NEW_YR + 2] = {NULL, };
+       int i;
+       GtkTreeIter iter, parent_iter, *parent_ptr = NULL;
        GtkTreeStore *liststore, *treestore;
-       struct dive *last_dive = NULL;
-       time_t dive_date;
+       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 = dive_table.dives[i];
-
-               for (j = NEW_YR; j >= NEW_DAY; j--) {
-                       if (new_date(dive, &last_dive, j, &dive_date))
-                       {
-                               gtk_tree_store_append(treestore, &parent_iter[j], parents[j+1]);
-                               parents[j] = &parent_iter[j];
-                               gtk_tree_store_set(treestore, parents[j],
-                                               DIVE_INDEX, -j,
-                                               DIVE_NR, -j,
-                                               DIVE_DATE, dive_date,
-                                               DIVE_LOCATION, "",
-                                               DIVE_TEMPERATURE, 0,
-                                               DIVE_SAC, 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, parents[NEW_DAY]);
+               gtk_tree_store_append(treestore, &iter, parent_ptr);
                gtk_tree_store_set(treestore, &iter,
                        DIVE_INDEX, i,
                        DIVE_NR, dive->number,
@@ -827,10 +999,19 @@ static void fill_dive_list(void)
                        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 (parent_ptr && dive_trip)
+               gtk_tree_store_set(treestore, parent_ptr,
+                               DIVE_NR, dive_trip->number,
+                               DIVE_DATE, dive_trip->when,
+                               DIVE_LOCATION, dive_trip->location,
+                               -1);
        update_dive_list_units();
        if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
                GtkTreeSelection *selection;
@@ -840,8 +1021,6 @@ static void fill_dive_list(void)
                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);
-               selectiontracker = realloc(selectiontracker, sizeof(int));
-               *selectiontracker = selected_dive;
        }
 }
 
@@ -866,6 +1045,8 @@ static struct divelist_column {
        [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 },
@@ -909,6 +1090,20 @@ 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,
@@ -919,10 +1114,14 @@ static void row_activated_cb(GtkTreeView *tree_view,
 
        if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path))
                return;
+
        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)
-               edit_dive_info(get_dive(index));
+       if (index < 0) {
+               collapse_expand(tree_view, path);
+               return;
+       }
+       edit_dive_info(get_dive(index));
 }
 
 void add_dive_cb(GtkWidget *menuitem, gpointer data)
@@ -940,16 +1139,28 @@ void add_dive_cb(GtkWidget *menuitem, gpointer data)
 
 void edit_dive_cb(GtkWidget *menuitem, gpointer data)
 {
-       edit_multi_dive_info(amount_selected, selectiontracker);
+       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;
+       GtkWidget *menu, *menuitem, *image;
        char editlabel[] = "Edit dives";
 
        menu = gtk_menu_new();
-       menuitem = gtk_menu_item_new_with_label("Add dive");
+       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) {
@@ -959,6 +1170,12 @@ static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int
                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,
@@ -985,8 +1202,6 @@ static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpoi
    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 int *oldselection;
-static int old_nr_selected;
 static gboolean second_call = FALSE;
 static GtkSortType sortorder[] = { [0 ... DIVELIST_COLUMNS - 1] = GTK_SORT_DESCENDING, };
 static int lastcol = DIVE_DATE;
@@ -994,20 +1209,27 @@ 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 select_selected(GtkTreeModel *model, GtkTreePath *path,
+static gboolean set_selected(GtkTreeModel *model, GtkTreePath *path,
                                GtkTreeIter *iter, gpointer data)
 {
-       int i, idx;
        GtkTreeSelection *selection = GTK_TREE_SELECTION(data);
+       int idx, selected;
+       struct dive *dive;
 
-       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
-       for (i = 0; i < old_nr_selected; i++)
-               if (oldselection[i] == idx) {
-                       gtk_tree_view_expand_to_path(GTK_TREE_VIEW(dive_list.tree_view), path);
-                       gtk_tree_selection_select_path(selection, path);
-
-                       return FALSE;
-               }
+       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;
 
 }
@@ -1060,20 +1282,9 @@ static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data)
        if (dive_list.model != currentmodel) {
                GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
 
-               /* remember what is currently selected, switch models and reselect the selected rows */
-               old_nr_selected = amount_selected;
-               oldselection = malloc(old_nr_selected * sizeof(int));
-               if (amount_selected)
-                       memcpy(oldselection, selectiontracker, amount_selected * sizeof(int));
                gtk_tree_view_set_model(GTK_TREE_VIEW(dive_list.tree_view), GTK_TREE_MODEL(dive_list.model));
-
                update_column_and_order(colid);
-
-               if (old_nr_selected) {
-                       /* we need to select all the dives that were selected */
-                       /* this is fundamentally an n^2 algorithm as implemented - YUCK */
-                       gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), select_selected, selection);
-               }
+               gtk_tree_model_foreach(GTK_TREE_MODEL(dive_list.model), set_selected, selection);
        } else {
                if (order != sortorder[colid]) {
                        update_column_and_order(colid);
@@ -1093,6 +1304,8 @@ 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 */
@@ -1107,6 +1320,8 @@ 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 */
@@ -1128,6 +1343,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);
@@ -1143,9 +1360,11 @@ GtkWidget *dive_list_create(void)
 
        g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
        g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL);
+       g_signal_connect(dive_list.tree_view, "row-expanded", G_CALLBACK(row_expanded_cb), NULL);
+       g_signal_connect(dive_list.tree_view, "row-collapsed", G_CALLBACK(row_collapsed_cb), NULL);
        g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), NULL);
        g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), NULL);
-       g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), 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);