[evince/outline-collapse-all] Outline: be able to collapse/expand all entries




commit d9b5cf26e8217d59974b299bddc25f0117380622
Author: Nelson Benítez León <nbenitezl gmail com>
Date:   Sun Apr 18 18:34:05 2021 -0400

    Outline: be able to collapse/expand all entries
    
    This commit adds three new context menu actions
    to the Outline tree view:
    
    1) Collapse all tree
    2) Expand all tree
    3) Expand all under this element
    
    Number 3) is like 2) but restricted to the
    children of the currently selected row. We
    insensitivize this action in the menu when
    we don't have any grandchildren.
    
    All three actions properly update the metadata
    keys "index-expand" and "index-collapse" so
    the state of the tree view is reinstated in
    future Evince invocations.
    
    Fixes #736

 shell/ev-sidebar-links.c | 385 +++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 359 insertions(+), 26 deletions(-)
---
diff --git a/shell/ev-sidebar-links.c b/shell/ev-sidebar-links.c
index 8780ec52..9aa7d428 100644
--- a/shell/ev-sidebar-links.c
+++ b/shell/ev-sidebar-links.c
@@ -76,12 +76,19 @@ static void job_finished_callback                   (EvJobLinks     *job,
 static void ev_sidebar_links_set_current_page           (EvSidebarLinks *sidebar_links,
                                                         gint            current_page);
 static void sidebar_collapse_recursive                  (EvSidebarLinks *sidebar_links);
+static void collapse_all_cb                            (GtkWidget      *menuitem,
+                                                        EvSidebarLinks *sidebar_links);
+static void expand_all_cb                              (GtkWidget      *menuitem,
+                                                        EvSidebarLinks *sidebar_links);
+static void expand_all_under_selected_item_cb          (GtkWidget      *menuitem,
+                                                        EvSidebarLinks *sidebar_links);
 static void ev_sidebar_links_page_iface_init           (EvSidebarPageInterface *iface);
 static gboolean ev_sidebar_links_support_document      (EvSidebarPage  *sidebar_page,
                                                         EvDocument     *document);
 static const gchar* ev_sidebar_links_get_label                 (EvSidebarPage *sidebar_page);
 
 static guint signals[N_SIGNALS];
+static GtkWidget *menu_item_expand_under;
 
 G_DEFINE_TYPE_EXTENDED (EvSidebarLinks, 
                         ev_sidebar_links, 
@@ -342,6 +349,27 @@ build_popup_menu (EvSidebarLinks *sidebar)
        gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
        g_signal_connect (item, "activate",
                          G_CALLBACK (print_section_cb), sidebar);
+
+       item = gtk_menu_item_new_with_mnemonic(_("Collapse all tree"));
+       gtk_widget_show (item);
+       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+       g_signal_connect (item, "activate",
+                         G_CALLBACK (collapse_all_cb), sidebar);
+
+       item = gtk_menu_item_new_with_mnemonic(_("Expand all tree"));
+       gtk_widget_show (item);
+       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+       g_signal_connect (item, "activate",
+                         G_CALLBACK (expand_all_cb), sidebar);
+
+       item = gtk_menu_item_new_with_mnemonic(_("Expand all under this element"));
+       gtk_widget_show (item);
+       menu_item_expand_under = item; /* save it to sensitivize later */
+       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+       g_signal_connect (item, "activate",
+                         G_CALLBACK (expand_all_under_selected_item_cb), sidebar);
+
+
        gtk_menu_attach_to_widget (GTK_MENU (menu), sidebar->priv->tree_view, NULL);
 
        return GTK_MENU (menu);
@@ -356,12 +384,44 @@ popup_menu_cb (GtkWidget *treeview, EvSidebarLinks *sidebar)
        gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
 }
 
+static void
+check_menu_sensitivity (GtkTreeView *treeview,
+                       GtkTreePath *selected_path,
+                       EvSidebarLinks *sidebar)
+{
+       GtkTreeModel *model;
+       GtkTreeIter iter;
+       GtkTreeIter parent;
+       gboolean expand_under_sensitive = FALSE;
+
+       model = sidebar->priv->model;
+       gtk_tree_model_get_iter (model, &parent, selected_path);
+
+       if (gtk_tree_model_iter_children (model, &iter, &parent)) {
+               do {
+                       if (gtk_tree_model_iter_has_child (model, &iter)) {
+                               expand_under_sensitive = TRUE;
+                               break;
+                       }
+               } while (gtk_tree_model_iter_next (model, &iter));
+       }
+
+       gtk_widget_set_sensitive (menu_item_expand_under, expand_under_sensitive);
+}
+
 static gboolean
 button_press_cb (GtkWidget *treeview,
                  GdkEventButton *event,
                  EvSidebarLinks *sidebar)
 {
+       GtkMenu *menu;
        GtkTreePath *path = NULL;
+       GList *menus = gtk_menu_get_for_attach_widget (treeview);
+       if (menus && GTK_IS_MENU (menus->data)) {
+               menu = GTK_MENU (menus->data);
+       } else {
+               menu = build_popup_menu (sidebar);
+       }
 
        if (event->button == 3) {
                if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview),
@@ -371,7 +431,8 @@ button_press_cb (GtkWidget *treeview,
                                                   NULL, NULL, NULL)) {
                        gtk_tree_view_set_cursor (GTK_TREE_VIEW (treeview),
                                                  path, NULL, FALSE);
-                       gtk_menu_popup_at_pointer (build_popup_menu (sidebar),
+                       check_menu_sensitivity (GTK_TREE_VIEW (treeview), path, sidebar);
+                       gtk_menu_popup_at_pointer (menu,
                                                   (GdkEvent *) event);
                        gtk_tree_path_free (path);
 
@@ -382,6 +443,80 @@ button_press_cb (GtkWidget *treeview,
        return FALSE;
 }
 
+/**
+ * remove_path_descendants:
+ * @metadata_index: the contents of either "index-expand" or "index-collapse" metadata elements.
+ * @path: Path entry to search and remove its descendants.
+ *
+ * Searches for descendants of @path in @metadata_index and removes them.
+ *
+ * Returns: New string with descendants removed, or %NULL if no descendants were found.
+ */
+static gchar *
+remove_path_descendants (const gchar *metadata_index, GtkTreePath *path)
+{
+       gchar *path_str, *path_token, *tmp, *haystack, *ret;
+       gboolean first_iteration;
+
+       ret = NULL;
+       path_str = gtk_tree_path_to_string (path);
+       path_token = g_strconcat ("|", path_str, ":", NULL);
+       tmp = g_strstr_len (metadata_index, -1, path_token);
+       haystack = (gchar *) metadata_index;
+       for (first_iteration = TRUE; tmp; tmp = g_strstr_len (haystack, -1, path_token)) {
+               gchar *needle, *separator;
+
+               separator = g_strstr_len (tmp + 1, -1, "|");
+               needle = g_strndup (tmp, (gssize) (separator - tmp));
+               ret = ev_str_replace (haystack, needle, "");
+               if (!strcmp (ret, "|")) {
+                       g_free (ret);
+                       ret = g_strdup ("");
+               }
+               if (!first_iteration)
+                       g_free (haystack);
+               else
+                       first_iteration = FALSE;
+               haystack = ret;
+               g_free (needle);
+       }
+
+       g_free (path_token);
+       g_free (path_str);
+
+       return ret;
+}
+
+/**
+ * remove_path:
+ * @metadata_index: the contents of either "index-expand" or "index-collapse" metadata elements.
+ * @path: Path entry to remove from @metadata_index
+ *
+ * Returns: New string which is @metadata_index with @path removed, or %NULL if @path was not found.
+ */
+static gchar *
+remove_path (const gchar *metadata_index, GtkTreePath *path)
+{
+       gchar *path_str, *path_token;
+       gchar *ret;
+
+       ret = NULL;
+       path_str = gtk_tree_path_to_string (path);
+       path_token = g_strconcat ("|", path_str, "|", NULL);
+
+       if (g_strstr_len (metadata_index, -1, path_token)) {
+               ret = ev_str_replace (metadata_index, path_token, "|");
+               if (!strcmp (ret, "|")) {
+                       g_free (ret);
+                       ret = g_strdup ("");
+               }
+       }
+       g_free (path_str);
+       g_free (path_token);
+
+       return ret;
+}
+
 /* Metadata keys 'index-expand' and 'index-collapse' are explained
  * in the main comment of row_collapsed_cb() function. */
 static gboolean
@@ -394,7 +529,7 @@ row_expanded_cb (GtkTreeView  *tree_view,
        EvSidebarLinksPrivate *priv;
        GtkWidget *window;
        EvMetadata *metadata;
-       gchar *path, *path_token, *index_collapse, *index_expand, *new_index;
+       gchar *index_collapse, *index_expand, *new_index;
        gboolean expand;
 
        ev_sidebar_links = EV_SIDEBAR_LINKS (data);
@@ -409,18 +544,11 @@ row_expanded_cb (GtkTreeView  *tree_view,
        if (metadata == NULL)
                return GDK_EVENT_PROPAGATE;
 
-       path = gtk_tree_path_to_string (expanded_path);
-       path_token = g_strconcat ("|", path, "|", NULL);
-       g_free (path);
        index_collapse = NULL;
+       /* If expanded row is in 'index_collapse' we remove it from there. */
        if (ev_metadata_get_string (metadata, "index-collapse", &index_collapse)) {
-               /* If expanded row is in 'index_collapse' we remove it from there. */
-               if (g_strstr_len (index_collapse, -1, path_token)) {
-                       new_index = ev_str_replace (index_collapse, path_token, "|");
-                       if (!strcmp (new_index, "|")) {
-                               g_free (new_index);
-                               new_index = g_strdup ("");
-                       }
+               new_index = remove_path (index_collapse, expanded_path);
+               if (new_index) {
                        ev_metadata_set_string (metadata, "index-collapse", new_index);
                        g_free (new_index);
                }
@@ -432,7 +560,12 @@ row_expanded_cb (GtkTreeView  *tree_view,
        /* if it's already marked "expand" by the pdf producer, we'll use that
         * and so no need to add it to 'index_expand' */
        if (!expand) {
+               gchar *path, *path_token;
+
+               path = gtk_tree_path_to_string (expanded_path);
+               path_token = g_strconcat ("|", path, "|", NULL);
                index_expand = NULL;
+
                if (ev_metadata_get_string (metadata, "index-expand", &index_expand)) {
                        /* If it's not in 'index_expand' we add it */
                        if (g_strstr_len (index_expand, -1, path_token) == NULL) {
@@ -446,8 +579,10 @@ row_expanded_cb (GtkTreeView  *tree_view,
                        }
                } else
                        ev_metadata_set_string (metadata, "index-expand", path_token);
+
+               g_free (path_token);
+               g_free (path);
        }
-       g_free (path_token);
 
        return GDK_EVENT_PROPAGATE;
 }
@@ -471,7 +606,7 @@ row_collapsed_cb (GtkTreeView *tree_view,
        EvMetadata *metadata;
        EvSidebarLinks *ev_sidebar_links;
        EvSidebarLinksPrivate *priv;
-       gchar *path, *path_token, *index_expand, *index_collapse, *new_index;
+       gchar *index_expand, *index_collapse, *new_index;
        gboolean expand;
 
        ev_sidebar_links = EV_SIDEBAR_LINKS (data);
@@ -485,20 +620,25 @@ row_collapsed_cb (GtkTreeView *tree_view,
        if (metadata == NULL)
                return GDK_EVENT_PROPAGATE;
 
-       path = gtk_tree_path_to_string (collapsed_path);
-       path_token = g_strconcat ("|", path, "|", NULL);
-       g_free (path);
-
        index_expand = NULL;
-       /* If collapsed row is in 'index_expand' we remove it from there */
+       /* If collapsed row is in 'index_expand' we remove it from there and also its descendants rows */
        if (ev_metadata_get_string (metadata, "index-expand", &index_expand)) {
-               new_index = ev_str_replace (index_expand, path_token, "|");
-               if (!strcmp (new_index, "|")) {
+               gchar *tmp;
+               new_index = remove_path (index_expand, collapsed_path);
+               if (new_index) {
+                       tmp = remove_path_descendants (new_index, collapsed_path);
+                       if (tmp) {
+                               ev_metadata_set_string (metadata, "index-expand", tmp);
+                               g_free (tmp);
+                       }
                        g_free (new_index);
-                       new_index = g_strdup ("");
+               } else {
+                       tmp = remove_path_descendants (index_expand, collapsed_path);
+                       if (tmp) {
+                               ev_metadata_set_string (metadata, "index-expand", tmp);
+                               g_free (tmp);
+                       }
                }
-               ev_metadata_set_string (metadata, "index-expand", new_index);
-               g_free (new_index);
        }
 
        gtk_tree_model_get (priv->model, collapsed_iter,
@@ -507,6 +647,9 @@ row_collapsed_cb (GtkTreeView *tree_view,
        /* We only add the collapsed row to 'index_collapse' if the row
         * was marked expanded by the pdf producer data. */
        if (expand) {
+               gchar *path, *path_token;
+               path = gtk_tree_path_to_string (collapsed_path);
+               path_token = g_strconcat ("|", path, "|", NULL);
                index_collapse = NULL;
                if (ev_metadata_get_string (metadata, "index-collapse", &index_collapse)) {
                        /* If collapsed row is not in 'index_collapse' we add it. */
@@ -522,8 +665,10 @@ row_collapsed_cb (GtkTreeView *tree_view,
                }
                else
                        ev_metadata_set_string (metadata, "index-collapse", path_token);
+
+               g_free (path);
+               g_free (path_token);
        }
-       g_free (path_token);
 
        return GDK_EVENT_PROPAGATE;
 }
@@ -603,6 +748,194 @@ sidebar_collapse_recursive (EvSidebarLinks *sidebar_links)
        g_signal_handlers_unblock_by_func (priv->tree_view, row_collapsed_cb, sidebar_links);
 }
 
+static void
+collapse_all_recursive (GtkTreeView  *tree_view,
+                       GtkTreeModel *model,
+                       GtkTreeIter  *parent,
+                       GString *index_collapse)
+{
+       GtkTreePath *path;
+       gchar *path_str;
+       GtkTreeIter iter;
+       gboolean expand;
+
+       if (gtk_tree_model_iter_children (model, &iter, parent)) {
+               do {
+                       if (!gtk_tree_model_iter_has_child (model, &iter))
+                               continue;
+
+                       gtk_tree_model_get (model, &iter,
+                                           EV_DOCUMENT_LINKS_COLUMN_EXPAND, &expand,
+                                           -1);
+
+                       path = gtk_tree_model_get_path (model, &iter);
+                       if (gtk_tree_view_row_expanded (tree_view, path))
+                               gtk_tree_view_collapse_row (tree_view, path);
+
+                       if (expand && index_collapse) {
+                               path_str = gtk_tree_path_to_string (path);
+                               g_string_append (index_collapse, path_str);
+                               g_string_append (index_collapse, "|");
+                               g_free (path_str);
+                       }
+                       gtk_tree_path_free (path);
+
+                       collapse_all_recursive (tree_view, model, &iter, index_collapse);
+               } while (gtk_tree_model_iter_next (model, &iter));
+       }
+}
+
+static void
+collapse_all_cb (GtkWidget *menuitem, EvSidebarLinks *sidebar_links)
+{
+       EvSidebarLinksPrivate *priv;
+       GtkWidget *window;
+       EvMetadata *metadata;
+       GString *index_collapse = NULL;
+
+       priv = sidebar_links->priv;
+       window = gtk_widget_get_toplevel (GTK_WIDGET (sidebar_links));
+       if (EV_IS_WINDOW (window)) {
+               metadata = ev_window_get_metadata (EV_WINDOW (window));
+               if (metadata) {
+                       ev_metadata_set_string (metadata, "index-expand", "");
+                       index_collapse = g_string_sized_new (4096);
+                       g_string_append (index_collapse, "|");
+               }
+       }
+       g_signal_handlers_block_by_func (priv->tree_view, row_collapsed_cb, sidebar_links);
+       collapse_all_recursive (GTK_TREE_VIEW (priv->tree_view), priv->model, NULL, index_collapse);
+       g_signal_handlers_unblock_by_func (priv->tree_view, row_collapsed_cb, sidebar_links);
+
+       if (index_collapse && strcmp (index_collapse->str, "|")) {
+               ev_metadata_set_string (metadata, "index-collapse", index_collapse->str);
+               g_string_free (index_collapse, TRUE);
+       } else if (index_collapse) {
+               ev_metadata_set_string (metadata, "index-collapse", "");
+               g_string_free (index_collapse, TRUE);
+       }
+}
+
+static void
+expand_all_recursive (GtkTreeView  *tree_view,
+                     GtkTreeModel *model,
+                     GtkTreeIter  *parent,
+                     GString *index_expand,
+                     gboolean index_expand_started_empty)
+{
+       GtkTreePath *path;
+       gchar *path_str;
+       GtkTreeIter iter;
+       gboolean expand;
+       gboolean already_added;
+
+       if (gtk_tree_model_iter_children (model, &iter, parent)) {
+               do {
+                       if (!gtk_tree_model_iter_has_child (model, &iter))
+                               continue;
+
+                       gtk_tree_model_get (model, &iter,
+                                           EV_DOCUMENT_LINKS_COLUMN_EXPAND, &expand,
+                                           -1);
+
+                       path = gtk_tree_model_get_path (model, &iter);
+                       if (!gtk_tree_view_row_expanded (tree_view, path))
+                               gtk_tree_view_expand_row (tree_view, path, FALSE);
+
+                       if (!expand && index_expand) {
+                               already_added = FALSE;
+                               path_str = gtk_tree_path_to_string (path);
+                               if (!index_expand_started_empty) {
+                                       gchar *path_token;
+                                       path_token = g_strconcat ("|", path_str, "|", NULL);
+
+                                       if (g_strstr_len (index_expand->str, -1, path_token))
+                                               already_added = TRUE;
+
+                                       g_free (path_token);
+                               }
+
+                               if (!already_added) {
+                                       g_string_append (index_expand, path_str);
+                                       g_string_append (index_expand, "|");
+                               }
+                               g_free (path_str);
+                       }
+                       gtk_tree_path_free (path);
+
+                       expand_all_recursive (tree_view, model, &iter, index_expand, 
index_expand_started_empty);
+               } while (gtk_tree_model_iter_next (model, &iter));
+       }
+}
+
+static void
+expand_all_cb (GtkWidget *menuitem, EvSidebarLinks *sidebar_links)
+{
+       EvSidebarLinksPrivate *priv;
+       GtkWidget *window;
+       EvMetadata *metadata;
+       GString *index_expand = NULL;
+
+       priv = sidebar_links->priv;
+       window = gtk_widget_get_toplevel (GTK_WIDGET (sidebar_links));
+       if (EV_IS_WINDOW (window)) {
+               metadata = ev_window_get_metadata (EV_WINDOW (window));
+               if (metadata) {
+                       ev_metadata_set_string (metadata, "index-collapse", "");
+                       index_expand = g_string_sized_new (4096);
+                       g_string_append (index_expand, "|");
+               }
+       }
+       g_signal_handlers_block_by_func (priv->tree_view, row_expanded_cb, sidebar_links);
+       expand_all_recursive (GTK_TREE_VIEW (priv->tree_view), priv->model, NULL, index_expand, TRUE);
+       g_signal_handlers_unblock_by_func (priv->tree_view, row_expanded_cb, sidebar_links);
+
+       if (index_expand && strcmp (index_expand->str, "|")) {
+               ev_metadata_set_string (metadata, "index-expand", index_expand->str);
+               g_string_free (index_expand, TRUE);
+       } else if (index_expand) {
+               ev_metadata_set_string (metadata, "index-expand", "");
+               g_string_free (index_expand, TRUE);
+       }
+}
+
+static void
+expand_all_under_selected_item_cb (GtkWidget *menuitem, EvSidebarLinks *sidebar_links)
+{
+       EvSidebarLinksPrivate *priv;
+       GtkWidget *window;
+       EvMetadata *metadata;
+       GtkTreeSelection *selection;
+       GtkTreeModel *model;
+       GtkTreeIter iter;
+       GString *index_expand = NULL;
+       gchar *index_expand_chars;
+
+       priv = sidebar_links->priv;
+       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view));
+
+       if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+               return;
+
+       window = gtk_widget_get_toplevel (GTK_WIDGET (sidebar_links));
+       if (EV_IS_WINDOW (window)) {
+               metadata = ev_window_get_metadata (EV_WINDOW (window));
+               if (metadata) {
+                       if (ev_metadata_get_string (metadata, "index-expand", &index_expand_chars))
+                               index_expand = g_string_new (index_expand_chars);
+               }
+       }
+       g_signal_handlers_block_by_func (priv->tree_view, row_expanded_cb, sidebar_links);
+       expand_all_recursive (GTK_TREE_VIEW (priv->tree_view), model, NULL, index_expand, FALSE);
+       g_signal_handlers_unblock_by_func (priv->tree_view, row_expanded_cb, sidebar_links);
+
+       if (index_expand && strcmp (index_expand->str, index_expand_chars))
+               ev_metadata_set_string (metadata, "index-expand", index_expand->str);
+
+       if (index_expand)
+               g_string_free (index_expand, TRUE);
+}
+
 static void
 ev_sidebar_links_construct (EvSidebarLinks *ev_sidebar_links)
 {
@@ -826,7 +1159,7 @@ expand_open_links (GtkTreeView  *tree_view,
                                path_token = g_strconcat ("|", path_str, "|", NULL);
 
                                if (g_strstr_len (index_expand, -1, path_token))
-                                       gtk_tree_view_expand_row (tree_view, path, FALSE);
+                                       gtk_tree_view_expand_to_path (tree_view, path);
 
                                g_free (path_str);
                                g_free (path_token);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]