]> git.tdb.fi Git - ext/subsurface.git/blobdiff - divelist.c
Merge branch 'misc-fixes' of git://github.com/DataBeaver/subsurface
[ext/subsurface.git] / divelist.c
index 27d24243baa1e37cdda00c0c35dbf18a5fe0e2d9..5d6915256c5f08a2dea140fdd3ed884d4ed416ac 100644 (file)
@@ -24,9 +24,9 @@
 struct DiveList {
        GtkWidget    *tree_view;
        GtkWidget    *container_widget;
-       GtkListStore *model;
+       GtkTreeStore *model, *listmodel, *treemodel;
        GtkTreeViewColumn *nr, *date, *stars, *depth, *duration, *location;
-       GtkTreeViewColumn *temperature, *cylinder, *nitrox, *sac, *otu;
+       GtkTreeViewColumn *temperature, *cylinder, *totalweight, *suit, *nitrox, *sac, *otu;
        int changed;
 };
 
@@ -44,6 +44,8 @@ enum {
        DIVE_DEPTH,             /* int: dive->maxdepth in mm */
        DIVE_DURATION,          /* int: in seconds */
        DIVE_TEMPERATURE,       /* int: in mkelvin */
+       DIVE_TOTALWEIGHT,       /* int: in grams */
+       DIVE_SUIT,              /* "wet, 3mm" */
        DIVE_CYLINDER,
        DIVE_NITROX,            /* int: dummy */
        DIVE_SAC,               /* int: in ml/min */
@@ -52,48 +54,199 @@ enum {
        DIVELIST_COLUMNS
 };
 
+/* magic numbers that indicate (as negative values) model entries that
+ * are summary entries for a divetrip */
+#define NEW_TRIP 1
+
+#ifdef DEBUG_MODEL
+static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path,
+                               GtkTreeIter *iter, gpointer data)
+{
+       char *location;
+       int idx, nr, rating, depth;
+
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, DIVE_RATING, &rating, DIVE_DEPTH, &depth, DIVE_LOCATION, &location, -1);
+       printf("entry #%d : nr %d rating %d depth %d location %s \n", idx, nr, rating, depth, location);
+       free(location);
+
+       return FALSE;
+}
+
+static void dump_model(GtkListStore *store)
+{
+       gtk_tree_model_foreach(GTK_TREE_MODEL(store), dump_model_entry, NULL);
+}
+#endif
+
 static GList *selected_dives;
+static int st_size = 0;
 
-static void selection_cb(GtkTreeSelection *selection, GtkTreeModel *model)
+gboolean is_in_st(int idx, int *atpos)
 {
-       GtkTreeIter iter;
-       GValue value = {0, };
-       GtkTreePath *path;
+       int i;
 
-       int nr_selected = gtk_tree_selection_count_selected_rows(selection);
+       for (i = 0; i < amount_selected; i++)
+               if (selectiontracker[i] == idx) {
+                       if (atpos)
+                               *atpos = i;
+                       return TRUE;
+               }
+       return FALSE;
+}
 
-       if (selected_dives) {
-               g_list_foreach (selected_dives, (GFunc) gtk_tree_path_free, NULL);
-               g_list_free (selected_dives);
-       }
-       selected_dives = gtk_tree_selection_get_selected_rows(selection, NULL);
+#if DEBUG_SELECTION_TRACKING
+void dump_selection(void)
+{
+       int i;
 
-       switch (nr_selected) {
-       case 0: /* keep showing the last selected dive */
+       printf("currently selected are ");
+       for (i = 0; i < amount_selected; i++)
+               printf("%d ", selectiontracker[i]);
+       printf("\n");
+}
+#endif
+
+void track_select(int idx)
+{
+       if (idx < 0)
                return;
-       case 1: 
-               /* just pick that dive as selected */
-               amount_selected = 1;
-               path = g_list_nth_data(selected_dives, 0);
-               if (gtk_tree_model_get_iter(model, &iter, path)) {
-                       gtk_tree_model_get_value(model, &iter, DIVE_INDEX, &value);
-                       selected_dive = g_value_get_int(&value);
-                       repaint_dive();
-               }
+
+#if DEBUG_SELECTION_TRACKING
+       printf("add %d to selection of %d entries\n", idx, amount_selected);
+#endif
+       if (is_in_st(idx, NULL))
                return;
-       default: /* multiple selections - what now? At this point I
-                 * don't want to change the selected dive unless
-                 * there is exactly one dive selected; not sure this
-                 * is the most intuitive solution.
-                 * I do however want to keep around which dives have
-                 * been selected */
-               amount_selected = g_list_length(selected_dives);
-               process_selected_dives(selected_dives, model);
-               repaint_dive();
+       if (amount_selected >= st_size) {
+               selectiontracker = realloc(selectiontracker, dive_table.nr * sizeof(int));
+               st_size = dive_table.nr;
+       }
+       selectiontracker[amount_selected] = idx;
+       amount_selected++;
+       if (amount_selected == 1)
+               selected_dive = idx;
+#if DEBUG_SELECTION_TRACKING
+       printf("increased amount_selected to %d\n", amount_selected);
+       dump_selection();
+#endif
+}
+
+void track_unselect(int idx)
+{
+       if (idx < 0)
+               return;
+
+#if DEBUG_SELECTION_TRACKING
+       printf("remove %d from selection of %d entries\n", idx, amount_selected);
+#endif
+       int atpos;
+
+       if (! is_in_st(idx, &atpos))
                return;
+       memmove(selectiontracker + atpos,
+               selectiontracker + atpos + 1,
+               (amount_selected - atpos - 1) * sizeof(int));
+       amount_selected--;
+#if DEBUG_SELECTION_TRACKING
+       printf("removed %d at pos %d and decreased amount_selected to %d\n", idx, atpos, amount_selected);
+       dump_selection();
+#endif
+}
+
+/* when subsurface starts we want to have the last dive selected. So we simply
+   walk to the first leaf (and skip the summary entries - which have negative
+   DIVE_INDEX) */
+static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx)
+{
+       GtkTreeIter parent;
+       GtkTreePath *tpath;
+
+       while (*diveidx < 0) {
+               memcpy(&parent, iter, sizeof(parent));
+               tpath = gtk_tree_model_get_path(model, &parent);
+               if (!gtk_tree_model_iter_children(model, iter, &parent))
+                       /* we should never have a parent without child */
+                       return;
+               if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath))
+                       gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
+               gtk_tree_model_get(GTK_TREE_MODEL(model), iter, DIVE_INDEX, diveidx, -1);
+               track_select(*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)
+{
+       int i, nr_children;
+       gboolean unexpand = FALSE;
+       GtkTreeIter parent;
+       GtkTreePath *tpath;
+
+       memcpy(&parent, iter, sizeof(parent));
+
+       tpath = gtk_tree_model_get_path(model, &parent);
+
+       /* stupid gtk doesn't allow us to select rows that are invisible; so if the
+          user clicks on a row that isn't expanded, we briefly expand it, select the
+          children, and then unexpand it again */
+       if(!gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath)) {
+               unexpand = TRUE;
+               gtk_tree_view_expand_row(GTK_TREE_VIEW(dive_list.tree_view), tpath, FALSE);
+       }
+       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);
+               else
+                       gtk_tree_selection_select_iter(selection, iter);
+       }
+       if (unexpand)
+               gtk_tree_view_collapse_row(GTK_TREE_VIEW(dive_list.tree_view), tpath);
+}
+
+/* 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)
+{
+       GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
+
+       if (gtk_tree_selection_path_is_selected(selection, path))
+               select_children(GTK_TREE_MODEL(dive_list.model), selection, iter, FALSE);
+}
+
+/* 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 */
+gboolean modify_selection_cb(GtkTreeSelection *selection, GtkTreeModel *model,
+                       GtkTreePath *path, gboolean was_selected, gpointer userdata)
+{
+       GtkTreeIter iter;
+       int dive_idx;
+
+       if (gtk_tree_model_get_iter(model, &iter, path)) {
+               gtk_tree_model_get(model, &iter, DIVE_INDEX, &dive_idx, -1);
+               /* turns out we need to move the selectiontracker here */
+               if (was_selected)
+                       track_unselect(dive_idx);
+               else
+                       track_select(dive_idx);
+               if (dive_idx < 0) {
+                       select_children(model, selection, &iter, was_selected);
+               }
+       }
+       /* allow this selection to proceed */
+       return TRUE;
+}
+
+/* this is called when gtk thinks that the selection has changed */
+static void selection_cb(GtkTreeSelection *selection, gpointer userdata)
+{
+       process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model));
+       repaint_dive();
+}
+
 const char *star_strings[] = {
        ZERO_STARS,
        ONE_STARS,
@@ -109,13 +262,17 @@ static void star_data_func(GtkTreeViewColumn *col,
                           GtkTreeIter *iter,
                           gpointer data)
 {
-       int nr_stars;
+       int nr_stars, idx;
        char buffer[40];
 
-       gtk_tree_model_get(model, iter, DIVE_RATING, &nr_stars, -1);
-       if (nr_stars < 0 || nr_stars > 5)
-               nr_stars = 0;
-       snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]);
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_RATING, &nr_stars, -1);
+       if (idx < 0) {
+               *buffer = '\0';
+       } else {
+               if (nr_stars < 0 || nr_stars > 5)
+                       nr_stars = 0;
+               snprintf(buffer, sizeof(buffer), "%s", star_strings[nr_stars]);
+       }
        g_object_set(renderer, "text", buffer, NULL);
 }
 
@@ -125,23 +282,33 @@ static void date_data_func(GtkTreeViewColumn *col,
                           GtkTreeIter *iter,
                           gpointer data)
 {
-       int val;
+       int val, idx;
        struct tm *tm;
        time_t when;
        char buffer[40];
 
-       gtk_tree_model_get(model, iter, DIVE_DATE, &val, -1);
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DATE, &val, -1);
 
        /* 2038 problem */
        when = val;
 
        tm = gmtime(&when);
-       snprintf(buffer, sizeof(buffer),
-               "%s, %s %d, %d %02d:%02d",
-               weekday(tm->tm_wday),
-               monthname(tm->tm_mon),
-               tm->tm_mday, tm->tm_year + 1900,
-               tm->tm_hour, tm->tm_min);
+       switch(idx) {
+       case -NEW_TRIP:
+               snprintf(buffer, sizeof(buffer),
+                       "Trip %s, %s %d, %d",
+                       weekday(tm->tm_wday),
+                       monthname(tm->tm_mon),
+                       tm->tm_mday, tm->tm_year + 1900);
+               break;
+       default:
+               snprintf(buffer, sizeof(buffer),
+                       "%s, %s %d, %d %02d:%02d",
+                       weekday(tm->tm_wday),
+                       monthname(tm->tm_mon),
+                       tm->tm_mday, tm->tm_year + 1900,
+                       tm->tm_hour, tm->tm_min);
+       }
        g_object_set(renderer, "text", buffer, NULL);
 }
 
@@ -151,34 +318,37 @@ static void depth_data_func(GtkTreeViewColumn *col,
                            GtkTreeIter *iter,
                            gpointer data)
 {
-       int depth, integer, frac, len;
+       int depth, integer, frac, len, idx;
        char buffer[40];
 
-       gtk_tree_model_get(model, iter, DIVE_DEPTH, &depth, -1);
-
-       switch (output_units.length) {
-       case METERS:
-               /* To tenths of meters */
-               depth = (depth + 49) / 100;
-               integer = depth / 10;
-               frac = depth % 10;
-               if (integer < 20)
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DEPTH, &depth, -1);
+
+       if (idx < 0) {
+               *buffer = '\0';
+       } else {
+               switch (output_units.length) {
+               case METERS:
+                       /* To tenths of meters */
+                       depth = (depth + 49) / 100;
+                       integer = depth / 10;
+                       frac = depth % 10;
+                       if (integer < 20)
+                               break;
+                       if (frac >= 5)
+                               integer++;
+                       frac = -1;
                        break;
-               if (frac >= 5)
-                       integer++;
-               frac = -1;
-               break;
-       case FEET:
-               integer = mm_to_feet(depth) + 0.5;
-               frac = -1;
-               break;
-       default:
-               return;
+               case FEET:
+                       integer = mm_to_feet(depth) + 0.5;
+                       frac = -1;
+                       break;
+               default:
+                       return;
+               }
+               len = snprintf(buffer, sizeof(buffer), "%d", integer);
+               if (frac >= 0)
+                       len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
        }
-       len = snprintf(buffer, sizeof(buffer), "%d", integer);
-       if (frac >= 0)
-               len += snprintf(buffer+len, sizeof(buffer)-len, ".%d", frac);
-
        g_object_set(renderer, "text", buffer, NULL);
 }
 
@@ -189,10 +359,14 @@ static void duration_data_func(GtkTreeViewColumn *col,
                               gpointer data)
 {
        unsigned int sec;
+       int idx;
        char buffer[16];
 
-       gtk_tree_model_get(model, iter, DIVE_DURATION, &sec, -1);
-       snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_DURATION, &sec, -1);
+       if (idx < 0)
+               *buffer = '\0';
+       else
+               snprintf(buffer, sizeof(buffer), "%d:%02d", sec / 60, sec % 60);
 
        g_object_set(renderer, "text", buffer, NULL);
 }
@@ -203,13 +377,13 @@ static void temperature_data_func(GtkTreeViewColumn *col,
                                  GtkTreeIter *iter,
                                  gpointer data)
 {
-       int value;
+       int value, idx;
        char buffer[80];
 
-       gtk_tree_model_get(model, iter, DIVE_TEMPERATURE, &value, -1);
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_TEMPERATURE, &value, -1);
 
        *buffer = 0;
-       if (value) {
+       if (idx >= 0 && value) {
                double deg;
                switch (output_units.temperature) {
                case CELSIUS:
@@ -227,6 +401,23 @@ static void temperature_data_func(GtkTreeViewColumn *col,
        g_object_set(renderer, "text", buffer, NULL);
 }
 
+static void nr_data_func(GtkTreeViewColumn *col,
+                          GtkCellRenderer *renderer,
+                          GtkTreeModel *model,
+                          GtkTreeIter *iter,
+                          gpointer data)
+{
+       int idx, nr;
+       char buffer[40];
+
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_NR, &nr, -1);
+       if (idx < 0)
+               *buffer = '\0';
+       else
+               snprintf(buffer, sizeof(buffer), "%d", nr);
+       g_object_set(renderer, "text", buffer, NULL);
+}
+
 /*
  * Get "maximal" dive gas for a dive.
  * Rules:
@@ -269,6 +460,38 @@ newmax:
        *o2low_p = mino2;
 }
 
+static int total_weight(struct dive *dive)
+{
+       int i, total_grams = 0;
+
+       if (dive)
+               for (i=0; i< MAX_WEIGHTSYSTEMS; i++)
+                       total_grams += dive->weightsystem[i].weight.grams;
+       return total_grams;
+}
+
+static void weight_data_func(GtkTreeViewColumn *col,
+                            GtkCellRenderer *renderer,
+                            GtkTreeModel *model,
+                            GtkTreeIter *iter,
+                            gpointer data)
+{
+       int indx, decimals;
+       double value;
+       char buffer[80];
+       struct dive *dive;
+
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &indx, -1);
+       dive = get_dive(indx);
+       value = get_weight_units(total_weight(dive), &decimals, NULL);
+       if (value == 0.0)
+               *buffer = '\0';
+       else
+               snprintf(buffer, sizeof(buffer), "%.*f", decimals, value);
+
+       g_object_set(renderer, "text", buffer, NULL);
+}
+
 static gint nitrox_sort_func(GtkTreeModel *model,
        GtkTreeIter *iter_a,
        GtkTreeIter *iter_b,
@@ -304,12 +527,16 @@ static void nitrox_data_func(GtkTreeViewColumn *col,
                             GtkTreeIter *iter,
                             gpointer data)
 {
-       int index, o2, he, o2low;
+       int idx, o2, he, o2low;
        char buffer[80];
        struct dive *dive;
 
-       gtk_tree_model_get(model, iter, DIVE_INDEX, &index, -1);
-       dive = get_dive(index);
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
+       if (idx < 0) {
+               *buffer = '\0';
+               goto exit;
+       }
+       dive = get_dive(idx);
        get_dive_gas(dive, &o2, &he, &o2low);
        o2 = (o2 + 5) / 10;
        he = (he + 5) / 10;
@@ -324,7 +551,7 @@ static void nitrox_data_func(GtkTreeViewColumn *col,
                        snprintf(buffer, sizeof(buffer), "%d" UTF8_ELLIPSIS "%d", o2low, o2);
        else
                strcpy(buffer, "air");
-
+exit:
        g_object_set(renderer, "text", buffer, NULL);
 }
 
@@ -335,16 +562,16 @@ static void sac_data_func(GtkTreeViewColumn *col,
                          GtkTreeIter *iter,
                          gpointer data)
 {
-       int value;
+       int value, idx;
        const char *fmt;
        char buffer[16];
        double sac;
 
-       gtk_tree_model_get(model, iter, DIVE_SAC, &value, -1);
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_SAC, &value, -1);
 
-       if (!value) {
-               g_object_set(renderer, "text", "", NULL);
-               return;
+       if (idx < 0 || !value) {
+               *buffer = '\0';
+               goto exit;
        }
 
        sac = value / 1000.0;
@@ -358,7 +585,7 @@ static void sac_data_func(GtkTreeViewColumn *col,
                break;
        }
        snprintf(buffer, sizeof(buffer), fmt, sac);
-
+exit:
        g_object_set(renderer, "text", buffer, NULL);
 }
 
@@ -369,17 +596,15 @@ static void otu_data_func(GtkTreeViewColumn *col,
                          GtkTreeIter *iter,
                          gpointer data)
 {
-       int value;
+       int value, idx;
        char buffer[16];
 
-       gtk_tree_model_get(model, iter, DIVE_OTU, &value, -1);
-
-       if (!value) {
-               g_object_set(renderer, "text", "", NULL);
-               return;
-       }
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, DIVE_OTU, &value, -1);
 
-       snprintf(buffer, sizeof(buffer), "%d", value);
+       if (idx < 0 || !value)
+               *buffer = '\0';
+       else
+               snprintf(buffer, sizeof(buffer), "%d", value);
 
        g_object_set(renderer, "text", buffer, NULL);
 }
@@ -501,27 +726,55 @@ static void get_cylinder(struct dive *dive, char **str)
        get_string(str, dive->cylinder[0].type.description);
 }
 
+static void get_suit(struct dive *dive, char **str)
+{
+       get_string(str, dive->suit);
+}
+
 /*
  * Set up anything that could have changed due to editing
- * of dive information
+ * of dive information; we need to do this for both models,
+ * so we simply call set_one_dive again with the non-current model
  */
+/* forward declaration for recursion */
+static gboolean set_one_dive(GtkTreeModel *model,
+                            GtkTreePath *path,
+                            GtkTreeIter *iter,
+                            gpointer data);
+
 static void fill_one_dive(struct dive *dive,
                          GtkTreeModel *model,
                          GtkTreeIter *iter)
 {
-       char *location, *cylinder;
+       char *location, *cylinder, *suit;
+       GtkTreeStore *othermodel;
 
        get_cylinder(dive, &cylinder);
        get_location(dive, &location);
+       get_suit(dive, &suit);
 
-       gtk_list_store_set(GTK_LIST_STORE(model), iter,
+       gtk_tree_store_set(GTK_TREE_STORE(model), iter,
                DIVE_NR, dive->number,
                DIVE_LOCATION, location,
                DIVE_CYLINDER, cylinder,
                DIVE_RATING, dive->rating,
                DIVE_SAC, dive->sac,
                DIVE_OTU, dive->otu,
+               DIVE_TOTALWEIGHT, total_weight(dive),
+               DIVE_SUIT, suit,
                -1);
+
+       free(location);
+       free(cylinder);
+       free(suit);
+
+       if (model == GTK_TREE_MODEL(dive_list.treemodel))
+               othermodel = dive_list.listmodel;
+       else
+               othermodel = dive_list.treemodel;
+       if (othermodel != dive_list.model)
+               /* recursive call */
+               gtk_tree_model_foreach(GTK_TREE_MODEL(othermodel), set_one_dive, dive);
 }
 
 static gboolean set_one_dive(GtkTreeModel *model,
@@ -529,12 +782,14 @@ static gboolean set_one_dive(GtkTreeModel *model,
                             GtkTreeIter *iter,
                             gpointer data)
 {
-       GValue value = {0, };
+       int idx;
        struct dive *dive;
 
        /* Get the dive number */
-       gtk_tree_model_get_value(model, iter, DIVE_INDEX, &value);
-       dive = get_dive(g_value_get_int(&value));
+       gtk_tree_model_get(model, iter, DIVE_INDEX, &idx, -1);
+       if (idx < 0)
+               return FALSE;
+       dive = get_dive(idx);
        if (!dive)
                return TRUE;
        if (data && dive != data)
@@ -569,6 +824,9 @@ void update_dive_list_units(void)
        (void) get_temp_units(0, &unit);
        gtk_tree_view_column_set_title(dive_list.temperature, unit);
 
+       (void) get_weight_units(0, NULL, &unit);
+       gtk_tree_view_column_set_title(dive_list.totalweight, unit);
+
        gtk_tree_model_foreach(model, set_one_dive, NULL);
 }
 
@@ -576,41 +834,112 @@ void update_dive_list_col_visibility(void)
 {
        gtk_tree_view_column_set_visible(dive_list.cylinder, visible_cols.cylinder);
        gtk_tree_view_column_set_visible(dive_list.temperature, visible_cols.temperature);
+       gtk_tree_view_column_set_visible(dive_list.totalweight, visible_cols.totalweight);
+       gtk_tree_view_column_set_visible(dive_list.suit, visible_cols.suit);
        gtk_tree_view_column_set_visible(dive_list.nitrox, visible_cols.nitrox);
        gtk_tree_view_column_set_visible(dive_list.sac, visible_cols.sac);
        gtk_tree_view_column_set_visible(dive_list.otu, visible_cols.otu);
        return;
 }
 
+/* random heuristic - not diving in three days implies new dive trip */
+#define TRIP_THRESHOLD 3600*24*3
+static int new_group(struct dive *dive, struct dive **last_dive, time_t *tm_date)
+{
+       if (!last_dive)
+               return TRUE;
+       if (*last_dive) {
+               struct dive *ldive = *last_dive;
+               if (abs(dive->when - ldive->when) < TRIP_THRESHOLD) {
+                       *last_dive = dive;
+                       return FALSE;
+               }
+       }
+       *last_dive = dive;
+       if (tm_date) {
+               struct tm *tm1 = gmtime(&dive->when);
+               tm1->tm_sec = 0;
+               tm1->tm_min = 0;
+               tm1->tm_hour = 0;
+               *tm_date = mktime(tm1);
+       }
+       return TRUE;
+}
+
 static void fill_dive_list(void)
 {
        int i;
-       GtkTreeIter iter;
-       GtkListStore *store;
+       GtkTreeIter iter, parent_iter;
+       GtkTreeStore *liststore, *treestore;
+       struct dive *last_dive = NULL;
+       struct dive *first_trip_dive = NULL;
+       struct dive *last_trip_dive = NULL;
+       time_t dive_date;
 
-       store = GTK_LIST_STORE(dive_list.model);
+       treestore = GTK_TREE_STORE(dive_list.treemodel);
+       liststore = GTK_TREE_STORE(dive_list.listmodel);
 
        i = dive_table.nr;
        while (--i >= 0) {
                struct dive *dive = dive_table.dives[i];
 
+               if (new_group(dive, &last_dive, &dive_date))
+               {
+                       /* make sure we display the first date of the trip in previous summary */
+                       if (first_trip_dive && last_trip_dive && last_trip_dive->when < first_trip_dive->when)
+                               gtk_tree_store_set(treestore, &parent_iter,
+                                               DIVE_DATE, last_trip_dive->when,
+                                               DIVE_LOCATION, last_trip_dive->location,
+                                               -1);
+                       first_trip_dive = dive;
+
+                       gtk_tree_store_append(treestore, &parent_iter, NULL);
+                       gtk_tree_store_set(treestore, &parent_iter,
+                                       DIVE_INDEX, -NEW_TRIP,
+                                       DIVE_NR, -NEW_TRIP,
+                                       DIVE_DATE, dive_date,
+                                       DIVE_LOCATION, dive->location,
+                                       DIVE_TEMPERATURE, 0,
+                                       DIVE_SAC, 0,
+                                       -1);
+               }
+               last_trip_dive = dive;
                update_cylinder_related_info(dive);
-               gtk_list_store_append(store, &iter);
-               gtk_list_store_set(store, &iter,
+               gtk_tree_store_append(treestore, &iter, &parent_iter);
+               gtk_tree_store_set(treestore, &iter,
                        DIVE_INDEX, i,
                        DIVE_NR, dive->number,
                        DIVE_DATE, dive->when,
                        DIVE_DEPTH, dive->maxdepth,
                        DIVE_DURATION, dive->duration.seconds,
-                       DIVE_LOCATION, "location",
+                       DIVE_LOCATION, dive->location,
+                       DIVE_RATING, dive->rating,
                        DIVE_TEMPERATURE, dive->watertemp.mkelvin,
                        DIVE_SAC, 0,
                        -1);
+               gtk_tree_store_append(liststore, &iter, NULL);
+               gtk_tree_store_set(liststore, &iter,
+                       DIVE_INDEX, i,
+                       DIVE_NR, dive->number,
+                       DIVE_DATE, dive->when,
+                       DIVE_DEPTH, dive->maxdepth,
+                       DIVE_DURATION, dive->duration.seconds,
+                       DIVE_LOCATION, dive->location,
+                       DIVE_RATING, dive->rating,
+                       DIVE_TEMPERATURE, dive->watertemp.mkelvin,
+                       DIVE_TOTALWEIGHT, 0,
+                       DIVE_SUIT, dive->suit,
+                       DIVE_SAC, 0,
+                       -1);
        }
 
        update_dive_list_units();
        if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dive_list.model), &iter)) {
                GtkTreeSelection *selection;
+
+               /* select the last dive (and make sure it's an actual dive that is selected) */
+               gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
+               first_leaf(GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive);
                selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
                gtk_tree_selection_select_iter(selection, &iter);
        }
@@ -618,7 +947,8 @@ static void fill_dive_list(void)
 
 void dive_list_update_dives(void)
 {
-       gtk_list_store_clear(GTK_LIST_STORE(dive_list.model));
+       gtk_tree_store_clear(GTK_TREE_STORE(dive_list.treemodel));
+       gtk_tree_store_clear(GTK_TREE_STORE(dive_list.listmodel));
        fill_dive_list();
        repaint_dive();
 }
@@ -630,12 +960,14 @@ static struct divelist_column {
        unsigned int flags;
        int *visible;
 } dl_column[] = {
-       [DIVE_NR] = { "#", NULL, NULL, ALIGN_RIGHT | UNSORTABLE },
+       [DIVE_NR] = { "#", nr_data_func, NULL, ALIGN_RIGHT | UNSORTABLE },
        [DIVE_DATE] = { "Date", date_data_func, NULL, ALIGN_LEFT },
        [DIVE_RATING] = { UTF8_BLACKSTAR, star_data_func, NULL, ALIGN_LEFT },
        [DIVE_DEPTH] = { "ft", depth_data_func, NULL, ALIGN_RIGHT },
        [DIVE_DURATION] = { "min", duration_data_func, NULL, ALIGN_RIGHT },
        [DIVE_TEMPERATURE] = { UTF8_DEGREE "F", temperature_data_func, NULL, ALIGN_RIGHT, &visible_cols.temperature },
+       [DIVE_TOTALWEIGHT] = { "lbs", weight_data_func, NULL, ALIGN_RIGHT, &visible_cols.totalweight },
+       [DIVE_SUIT] = { "Suit", NULL, NULL, ALIGN_LEFT, &visible_cols.suit },
        [DIVE_CYLINDER] = { "Cyl", NULL, NULL, 0, &visible_cols.cylinder },
        [DIVE_NITROX] = { "O" UTF8_SUBSCRIPT_2 "%", nitrox_data_func, nitrox_sort_func, 0, &visible_cols.nitrox },
        [DIVE_SAC] = { "SAC", sac_data_func, NULL, 0, &visible_cols.sac },
@@ -653,14 +985,20 @@ static GtkTreeViewColumn *divelist_column(struct DiveList *dl, struct divelist_c
        unsigned int flags = col->flags;
        int *visible = col->visible;
        GtkWidget *tree_view = dl->tree_view;
-       GtkListStore *model = dl->model;
+       GtkTreeStore *treemodel = dl->treemodel;
+       GtkTreeStore *listmodel = dl->listmodel;
        GtkTreeViewColumn *ret;
 
        if (visible && !*visible)
                flags |= INVISIBLE;
        ret = tree_view_column(tree_view, index, title, data_func, flags);
-       if (sort_func)
-               gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), index, sort_func, NULL, NULL);
+       if (sort_func) {
+               /* the sort functions are needed in the corresponding models */
+               if (index == DIVE_DATE)
+                       gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(treemodel), index, sort_func, NULL, NULL);
+               else
+                       gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(listmodel), index, sort_func, NULL, NULL);
+       }
        return ret;
 }
 
@@ -676,15 +1014,17 @@ static void realize_cb(GtkWidget *tree_view, gpointer userdata)
 static void row_activated_cb(GtkTreeView *tree_view,
                        GtkTreePath *path,
                        GtkTreeViewColumn *column,
-                       GtkTreeModel *model)
+                       gpointer userdata)
 {
        int index;
        GtkTreeIter iter;
 
-       if (!gtk_tree_model_get_iter(model, &iter, path))
+       if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path))
                return;
-       gtk_tree_model_get(model, &iter, DIVE_INDEX, &index, -1);
-       edit_dive_info(get_dive(index));
+       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));
 }
 
 void add_dive_cb(GtkWidget *menuitem, gpointer data)
@@ -700,43 +1040,172 @@ void add_dive_cb(GtkWidget *menuitem, gpointer data)
        free(dive);
 }
 
+void edit_dive_cb(GtkWidget *menuitem, gpointer data)
+{
+       edit_multi_dive_info(amount_selected, selectiontracker);
+}
+
 static void popup_divelist_menu(GtkTreeView *tree_view, GtkTreeModel *model, int button)
 {
        GtkWidget *menu, *menuitem, *image;
+       char editlabel[] = "Edit dives";
 
        menu = gtk_menu_new();
        menuitem = gtk_image_menu_item_new_with_label("Add dive");
        image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
        gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
-       g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), model);
+       g_signal_connect(menuitem, "activate", G_CALLBACK(add_dive_cb), NULL);
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+       if (amount_selected) {
+               if (amount_selected == 1)
+                       editlabel[strlen(editlabel) - 1] = '\0';
+               menuitem = gtk_menu_item_new_with_label(editlabel);
+               g_signal_connect(menuitem, "activate", G_CALLBACK(edit_dive_cb), model);
+               gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+       }
        gtk_widget_show_all(menu);
 
        gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
                button, gtk_get_current_event_time());
 }
 
-static void popup_menu_cb(GtkTreeView *tree_view,
-                       GtkTreeModel *model)
+static void popup_menu_cb(GtkTreeView *tree_view, gpointer userdata)
 {
-       popup_divelist_menu(tree_view, model, 0);
+       popup_divelist_menu(tree_view, GTK_TREE_MODEL(dive_list.model), 0);
 }
 
-static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, GtkTreeModel *model)
+static gboolean button_press_cb(GtkWidget *treeview, GdkEventButton *event, gpointer userdata)
 {
        /* Right-click? Bring up the menu */
        if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3) {
-               popup_divelist_menu(GTK_TREE_VIEW(treeview), model, 3);
+               popup_divelist_menu(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(dive_list.model), 3);
                return TRUE;
        }
        return FALSE;
 }
 
+/* we need to have a temporary copy of the selected dives while
+   switching model as the selection_cb function keeps getting called
+   when gtk_tree_selection_select_path is called.  We also need to
+   keep copies of the sort order so we can restore that as well after
+   switching models. */
+static 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;
+
+/* 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,
+                               GtkTreeIter *iter, gpointer data)
+{
+       int i, idx;
+       GtkTreeSelection *selection = GTK_TREE_SELECTION(data);
+
+       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;
+               }
+       return FALSE;
+
+}
+
+static void update_column_and_order(int colid)
+{
+       /* Careful: the index into treecolumns is off by one as we don't have a
+          tree_view column for DIVE_INDEX */
+       GtkTreeViewColumn **treecolumns = &dive_list.nr;
+
+       /* this will trigger a second call into sort_column_change_cb,
+          so make sure we don't start an infinite recursion... */
+       second_call = TRUE;
+       gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dive_list.model), colid, sortorder[colid]);
+       gtk_tree_view_column_set_sort_order(treecolumns[colid - 1], sortorder[colid]);
+       second_call = FALSE;
+}
+
+/* If the sort column is date (default), show the tree model.
+   For every other sort column only show the list model.
+   If the model changed, inform the new model of the chosen sort column and make
+   sure the same dives are still selected.
+
+   The challenge with this function is that once we change the model
+   we also need to change the sort column again (as it was changed in
+   the other model) and that causes this function to be called
+   recursively - so we need to catch that.
+*/
+static void sort_column_change_cb(GtkTreeSortable *treeview, gpointer data)
+{
+       int colid;
+       GtkSortType order;
+       GtkTreeStore *currentmodel = dive_list.model;
+
+       if (second_call)
+               return;
+
+       gtk_tree_sortable_get_sort_column_id(treeview, &colid, &order);
+       if(colid == lastcol) {
+               /* we just changed sort order */
+               sortorder[colid] = order;
+               return;
+       } else {
+               lastcol = colid;
+       }
+       if(colid == DIVE_DATE)
+               dive_list.model = dive_list.treemodel;
+       else
+               dive_list.model = dive_list.listmodel;
+       if (dive_list.model != currentmodel) {
+               GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dive_list.tree_view));
+
+               /* 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);
+               }
+       } else {
+               if (order != sortorder[colid]) {
+                       update_column_and_order(colid);
+               }
+       }
+}
+
 GtkWidget *dive_list_create(void)
 {
        GtkTreeSelection  *selection;
 
-       dive_list.model = gtk_list_store_new(DIVELIST_COLUMNS,
+       dive_list.listmodel = gtk_tree_store_new(DIVELIST_COLUMNS,
+                               G_TYPE_INT,                     /* index */
+                               G_TYPE_INT,                     /* nr */
+                               G_TYPE_INT,                     /* Date */
+                               G_TYPE_INT,                     /* Star rating */
+                               G_TYPE_INT,                     /* Depth */
+                               G_TYPE_INT,                     /* Duration */
+                               G_TYPE_INT,                     /* Temperature */
+                               G_TYPE_INT,                     /* Total weight */
+                               G_TYPE_STRING,                  /* Suit */
+                               G_TYPE_STRING,                  /* Cylinder */
+                               G_TYPE_INT,                     /* Nitrox */
+                               G_TYPE_INT,                     /* SAC */
+                               G_TYPE_INT,                     /* OTU */
+                               G_TYPE_STRING                   /* Location */
+                               );
+       dive_list.treemodel = gtk_tree_store_new(DIVELIST_COLUMNS,
                                G_TYPE_INT,                     /* index */
                                G_TYPE_INT,                     /* nr */
                                G_TYPE_INT,                     /* Date */
@@ -744,12 +1213,15 @@ GtkWidget *dive_list_create(void)
                                G_TYPE_INT,                     /* Depth */
                                G_TYPE_INT,                     /* Duration */
                                G_TYPE_INT,                     /* Temperature */
+                               G_TYPE_INT,                     /* Total weight */
+                               G_TYPE_STRING,                  /* Suit */
                                G_TYPE_STRING,                  /* Cylinder */
                                G_TYPE_INT,                     /* Nitrox */
                                G_TYPE_INT,                     /* SAC */
                                G_TYPE_INT,                     /* OTU */
                                G_TYPE_STRING                   /* Location */
                                );
+       dive_list.model = dive_list.treemodel;
        dive_list.tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dive_list.model));
        set_divelist_font(divelist_font);
 
@@ -764,6 +1236,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);
@@ -778,10 +1252,15 @@ GtkWidget *dive_list_create(void)
                                          NULL);
 
        g_signal_connect_after(dive_list.tree_view, "realize", G_CALLBACK(realize_cb), NULL);
-       g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), dive_list.model);
-       g_signal_connect(dive_list.tree_view, "button-press-event", G_CALLBACK(button_press_cb), dive_list.model);
-       g_signal_connect(dive_list.tree_view, "popup-menu", G_CALLBACK(popup_menu_cb), dive_list.model);
-       g_signal_connect(selection, "changed", G_CALLBACK(selection_cb), dive_list.model);
+       g_signal_connect(dive_list.tree_view, "row-activated", G_CALLBACK(row_activated_cb), NULL);
+       g_signal_connect(dive_list.tree_view, "row-expanded", G_CALLBACK(row_expanded_cb), NULL);
+       g_signal_connect(dive_list.tree_view, "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(dive_list.listmodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
+       g_signal_connect(dive_list.treemodel, "sort-column-changed", G_CALLBACK(sort_column_change_cb), NULL);
+
+       gtk_tree_selection_set_select_function(selection, modify_selection_cb, NULL, NULL);
 
        dive_list.container_widget = gtk_scrolled_window_new(NULL, NULL);
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dive_list.container_widget),