]> git.tdb.fi Git - ext/subsurface.git/blobdiff - divelist.c
Stop relying on gtk to track which dives are selected
[ext/subsurface.git] / divelist.c
index ea59b08758528b7aac5acf121902285111653db1..19ab600e8d6dec95121eb69b9221b4a74dc6dfab 100644 (file)
@@ -53,10 +53,8 @@ enum {
 };
 
 /* 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
+ * are summary entries for a divetrip */
+#define NEW_TRIP 1
 
 #ifdef DEBUG_MODEL
 static gboolean dump_model_entry(GtkTreeModel *model, GtkTreePath *path,
@@ -80,11 +78,82 @@ static void dump_model(GtkListStore *store)
 
 static GList *selected_dives;
 static int *selectiontracker;
+static int st_size = 0;
 
-/* if we are sorting by date and are using a tree model, we don't want the selection
-   to be a summary entry, but instead the first child below that entry. So we descend
-   down the tree until we find a leaf (entry with non-negative index)
- */
+gboolean is_in_st(int idx, int *atpos)
+{
+       int i;
+
+       for (i = 0; i < amount_selected; i++)
+               if (selectiontracker[i] == idx) {
+                       if (atpos)
+                               *atpos = i;
+                       return TRUE;
+               }
+       return FALSE;
+}
+
+#if DEBUG_SELECTION_TRACKING
+void dump_selection(void)
+{
+       int i;
+
+       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;
+
+#if DEBUG_SELECTION_TRACKING
+       printf("add %d to selection of %d entries\n", idx, amount_selected);
+#endif
+       if (is_in_st(idx, NULL))
+               return;
+       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;
@@ -96,58 +165,85 @@ static void first_leaf(GtkTreeModel *model, GtkTreeIter *iter, int *diveidx)
                if (!gtk_tree_model_iter_children(model, iter, &parent))
                        /* we should never have a parent without child */
                        return;
-               /* clicking on a parent should toggle expand status */
-               if(gtk_tree_view_row_expanded(GTK_TREE_VIEW(dive_list.tree_view), tpath))
-                       gtk_tree_view_collapse_row(GTK_TREE_VIEW(dive_list.tree_view), tpath);
-               else
+               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);
        }
 }
 
-static void selection_cb(GtkTreeSelection *selection, gpointer userdata)
+/* 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)
 {
-       GtkTreeIter iter;
-       GtkTreePath *path;
+       int i, nr_children;
+       gboolean unexpand = FALSE;
+       GtkTreeIter parent;
+       GtkTreePath *tpath;
+
+       memcpy(&parent, iter, sizeof(parent));
 
-       int nr_selected = gtk_tree_selection_count_selected_rows(selection);
+       tpath = gtk_tree_model_get_path(model, &parent);
 
-       if (selected_dives) {
-               g_list_foreach (selected_dives, (GFunc) gtk_tree_path_free, NULL);
-               g_list_free (selected_dives);
+       /* 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);
        }
-       selected_dives = gtk_tree_selection_get_selected_rows(selection, NULL);
-       selectiontracker = realloc(selectiontracker, nr_selected * sizeof(int));
+       if (unexpand)
+               gtk_tree_view_collapse_row(GTK_TREE_VIEW(dive_list.tree_view), tpath);
+}
 
-       switch (nr_selected) {
-       case 0: /* keep showing the last selected dive */
-               return;
-       case 1: 
-               /* just pick that dive as selected */
-               amount_selected = 1;
-               path = g_list_nth_data(selected_dives, 0);
-               if (gtk_tree_model_get_iter(GTK_TREE_MODEL(dive_list.model), &iter, path)) {
-                       gtk_tree_model_get(GTK_TREE_MODEL(dive_list.model), &iter, DIVE_INDEX, &selected_dive, -1);
-                       /* make sure we're not on a summary entry */
-                       first_leaf (GTK_TREE_MODEL(dive_list.model), &iter, &selected_dive);
-                       selectiontracker[0] = selected_dive;
-                       repaint_dive();
+/* 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);
                }
-               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 */
-                /* TODO:
-                   this also does not handle the case if a summary row is selected;
-                   We should iterate and select all dives under that row */
-               amount_selected = g_list_length(selected_dives);
-               process_selected_dives(selected_dives, selectiontracker, GTK_TREE_MODEL(dive_list.model));
-               repaint_dive();
-               return;
        }
+       /* 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[] = {
@@ -197,23 +293,13 @@ static void date_data_func(GtkTreeViewColumn *col,
 
        tm = gmtime(&when);
        switch(idx) {
-       case -NEW_DAY:
+       case -NEW_TRIP:
                snprintf(buffer, sizeof(buffer),
-                       "%s, %s %d, %d",
+                       "Trip %s, %s %d, %d",
                        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:
                snprintf(buffer, sizeof(buffer),
                        "%s, %s %d, %d %02d:%02d",
@@ -705,22 +791,20 @@ void update_dive_list_col_visibility(void)
        return;
 }
 
-static int new_date(struct dive *dive, struct dive **last_dive, const int flag, time_t *tm_date)
+/* 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;
-               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))
+               if (abs(dive->when - ldive->when) < TRIP_THRESHOLD) {
+                       *last_dive = dive;
                        return FALSE;
+               }
        }
-       if (flag == NEW_DAY)
-               *last_dive = dive;
+       *last_dive = dive;
        if (tm_date) {
                struct tm *tm1 = gmtime(&dive->when);
                tm1->tm_sec = 0;
@@ -733,10 +817,12 @@ static int new_date(struct dive *dive, struct dive **last_dive, const int flag,
 
 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;
        GtkTreeStore *liststore, *treestore;
        struct dive *last_dive = NULL;
+       struct dive *first_trip_dive = NULL;
+       struct dive *last_trip_dive = NULL;
        time_t dive_date;
 
        treestore = GTK_TREE_STORE(dive_list.treemodel);
@@ -746,23 +832,29 @@ static void fill_dive_list(void)
        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,
+               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_tree_store_append(treestore, &iter, parents[NEW_DAY]);
+               gtk_tree_store_append(treestore, &iter, &parent_iter);
                gtk_tree_store_set(treestore, &iter,
                        DIVE_INDEX, i,
                        DIVE_NR, dive->number,
@@ -792,13 +884,11 @@ static void fill_dive_list(void)
        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);
-               /* make sure it's an actual dive that is selected */
                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;
        }
 }
 
@@ -895,14 +985,27 @@ 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;
+       char editlabel[] = "Edit dives";
 
        menu = gtk_menu_new();
        menuitem = gtk_menu_item_new_with_label("Add dive");
        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,
@@ -1087,12 +1190,15 @@ 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, "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),
                               GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);