[evince/85-index-autoexpansion-makes-index-cluttered-and-unusable-after-prolonged-reading: 86/86] sidebar outline: close auto-expanded rows and remember state



commit 45abf6a7f251b24606bd544ba6b227feec22f8f8
Author: Nelson Benítez León <nbenitezl gmail com>
Date:   Thu Aug 16 22:33:56 2018 +0200

    sidebar outline: close auto-expanded rows and remember state
    
    This commit adds the following features:
    
    - Remember and restore state for rows that were manually
      collapsed and expanded by the user. To save that information
      we use two new metadata keys 'index-expand' and 'index-collapse'.
    
      Metadata key 'index-expand' is a string containing the GtkTreePath's
      that the user has explicitly expanded, except those already marked
      expanded by the pdf producer data. The string format is
      "|path1|path2|path3|" (starting and ending in pipe). A case with
      only one element would be "|path1|". A case with no elements would be
      the empty string "". This is to facilitate the search of the paths.
    
      Metadata key 'index-collapse' is a string containing the GtkTreePath's
      that the pdf producer data had them marked as expanded but the user
      has explicitly collapsed them. The string format is the same as in
      'index_expand'.
    
    - When scrolling through document, take the chance to collapse rows
      that have been automatically expanded previously. This avoids
      cluttering the tree view with too many entries when navigating
      documents that have heavily indexed outlines.
    
    Issue #85

 shell/ev-sidebar-links.c | 311 ++++++++++++++++++++++++++++++++++++++++++++---
 shell/ev-utils.c         |  31 +++++
 shell/ev-utils.h         |   1 +
 shell/ev-window.c        |   8 ++
 shell/ev-window.h        |   2 +
 5 files changed, 339 insertions(+), 14 deletions(-)
---
diff --git a/shell/ev-sidebar-links.c b/shell/ev-sidebar-links.c
index deb9a82b..afac7653 100644
--- a/shell/ev-sidebar-links.c
+++ b/shell/ev-sidebar-links.c
@@ -75,6 +75,7 @@ static void job_finished_callback                     (EvJobLinks     *job,
                                                         EvSidebarLinks *sidebar_links);
 static void ev_sidebar_links_set_current_page           (EvSidebarLinks *sidebar_links,
                                                         gint            current_page);
+static void sidebar_collapse_recursive                  (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);
@@ -389,6 +390,226 @@ button_press_cb (GtkWidget *treeview,
        return FALSE;
 }
 
+/* Metadata keys 'index-expand' and 'index-collapse' are explained
+ * in the main comment of row_collapsed_cb() function. */
+static gboolean
+row_expanded_cb (GtkTreeView  *tree_view,
+                GtkTreeIter  *expanded_iter,
+                GtkTreePath  *expanded_path,
+                gpointer      data)
+{
+       EvSidebarLinks *ev_sidebar_links;
+       EvSidebarLinksPrivate *priv;
+       GtkWidget *window;
+       EvMetadata *metadata;
+       gchar *path, *path_token, *index_collapse, *index_expand, *new_index;
+       gboolean expand;
+
+       ev_sidebar_links = EV_SIDEBAR_LINKS (data);
+       priv = ev_sidebar_links->priv;
+
+       window = gtk_widget_get_toplevel (GTK_WIDGET (ev_sidebar_links));
+       if (!EV_IS_WINDOW (window)) {
+               g_warning ("Could not find EvWindow metadata, index_{expand,collapse} metadata won't be 
saved");
+               return GDK_EVENT_PROPAGATE;
+       }
+       metadata = ev_window_get_metadata (EV_WINDOW (window));
+       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 (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 ("");
+                       }
+                       ev_metadata_set_string (metadata, "index-collapse", new_index);
+                       g_free (new_index);
+               }
+       }
+
+       gtk_tree_model_get (priv->model, expanded_iter,
+                           EV_DOCUMENT_LINKS_COLUMN_EXPAND, &expand,
+                           -1);
+       /* 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) {
+               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) {
+                               if (!strcmp (index_expand, ""))
+                                       new_index = g_strconcat (index_expand, path_token, NULL);
+                               else
+                                       new_index = g_strconcat (index_expand, path_token + 1, NULL);
+
+                               ev_metadata_set_string (metadata, "index-expand", new_index);
+                               g_free (new_index);
+                       }
+               } else
+                       ev_metadata_set_string (metadata, "index-expand", path_token);
+       }
+       g_free (path_token);
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+/* Metadata key 'index-expand' is a string containing the GtkTreePath's that the user
+ * has explicitly expanded, except those already marked expanded by the pdf producer
+ * data. The string is like "|path1|path2|path3|" (starting and ending in pipe).
+ * A case with only one element would be "|path1|". Case with no elements would be
+ * the empty string "". This is to facilitate the search of the paths.
+ *
+ * Metadata key 'index-collapse' is a string containing the GtkTreePath's that the
+ * pdf producer data had them marked as expanded but the user has explicitly collapsed
+ * them. The string format is the same as in 'index-expand'. */
+static gboolean
+row_collapsed_cb (GtkTreeView *tree_view,
+                 GtkTreeIter *collapsed_iter,
+                 GtkTreePath *collapsed_path,
+                 gpointer     data)
+{
+       GtkWidget *window;
+       EvMetadata *metadata;
+       EvSidebarLinks *ev_sidebar_links;
+       EvSidebarLinksPrivate *priv;
+       gchar *path, *path_token, *index_expand, *index_collapse, *new_index;
+       gboolean expand;
+
+       ev_sidebar_links = EV_SIDEBAR_LINKS (data);
+       priv = ev_sidebar_links->priv;
+       window = gtk_widget_get_toplevel (GTK_WIDGET (ev_sidebar_links));
+       if (!EV_IS_WINDOW (window)) {
+               g_warning ("Could not find EvWindow metadata, index_{expand,collapse} metadata won't be 
saved");
+               return GDK_EVENT_PROPAGATE;
+       }
+       metadata = ev_window_get_metadata (EV_WINDOW (window));
+       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 (ev_metadata_get_string (metadata, "index-expand", &index_expand)) {
+               new_index = ev_str_replace (index_expand, path_token, "|");
+               if (!strcmp (new_index, "|")) {
+                       g_free (new_index);
+                       new_index = g_strdup ("");
+               }
+               ev_metadata_set_string (metadata, "index-expand", new_index);
+               g_free (new_index);
+       }
+
+       gtk_tree_model_get (priv->model, collapsed_iter,
+                           EV_DOCUMENT_LINKS_COLUMN_EXPAND, &expand,
+                           -1);
+       /* We only add the collapsed row to 'index_collapse' if the row
+        * was marked expanded by the pdf producer data. */
+       if (expand) {
+               index_collapse = NULL;
+               if (ev_metadata_get_string (metadata, "index-collapse", &index_collapse)) {
+                       /* If collapsed row is not in 'index_collapse' we add it. */
+                       if (g_strstr_len (index_collapse, -1, path_token) == NULL) {
+                               if (!strcmp (index_expand, ""))
+                                       new_index = g_strconcat (index_collapse, path_token, NULL);
+                               else
+                                       new_index = g_strconcat (index_collapse, path_token + 1, NULL);
+
+                               ev_metadata_set_string (metadata, "index-collapse", new_index);
+                               g_free (new_index);
+                       }
+               }
+               else
+                       ev_metadata_set_string (metadata, "index-collapse", path_token);
+       }
+       g_free (path_token);
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+/* This function recursively collapses all rows except those marked EXPAND
+ * by the pdf producer and those expanded explicitly by the user (which
+ * are stored in metadata "index-expand" key).
+ *
+ * The final purpose is to close all rows that were automatically expanded
+ * by ev_sidebar_links_set_current_page() function (which automatically
+ * expands rows according to current page in view).
+ *
+ * As the 'collapse' action is only meaningful in rows that have children,
+ * we only traverse those. */
+static void
+collapse_recursive (GtkTreeView  *tree_view,
+                   GtkTreeModel *model,
+                   GtkTreeIter  *parent,
+                   const gchar  *index_expand)
+{
+       GtkTreePath *path;
+       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);
+                       if (expand)
+                               continue;
+
+                       path = gtk_tree_model_get_path (model, &iter);
+                       if (gtk_tree_view_row_expanded (tree_view, path)) {
+                               if (index_expand == NULL)
+                                       gtk_tree_view_collapse_row (tree_view, path);
+                               else {
+                                       gchar *path_str, *path_token;
+
+                                       path_str = gtk_tree_path_to_string (path);
+                                       path_token = g_strconcat ("|", path_str, "|", NULL);
+
+                                       if (!g_strstr_len (index_expand, -1, path_token))
+                                               gtk_tree_view_collapse_row (tree_view, path);
+
+                                       g_free (path_str);
+                                       g_free (path_token);
+                               }
+                       }
+                       gtk_tree_path_free (path);
+                       collapse_recursive (tree_view, model, &iter, index_expand);
+               } while (gtk_tree_model_iter_next (model, &iter));
+       }
+}
+
+static void
+sidebar_collapse_recursive (EvSidebarLinks *sidebar_links)
+{
+       EvSidebarLinksPrivate *priv;
+       GtkWidget *window;
+       EvMetadata *metadata;
+       gchar *index_expand;
+
+       priv = sidebar_links->priv;
+       window = gtk_widget_get_toplevel (GTK_WIDGET (sidebar_links));
+       index_expand = NULL;
+       if (EV_IS_WINDOW (window)) {
+               metadata = ev_window_get_metadata (EV_WINDOW (window));
+               if (metadata)
+                       ev_metadata_get_string (metadata, "index-expand", &index_expand);
+       }
+       g_signal_handlers_block_by_func (priv->tree_view, row_collapsed_cb, sidebar_links);
+       collapse_recursive (GTK_TREE_VIEW (priv->tree_view), priv->model, NULL, index_expand);
+       g_signal_handlers_unblock_by_func (priv->tree_view, row_collapsed_cb, sidebar_links);
+}
 
 static void
 ev_sidebar_links_construct (EvSidebarLinks *ev_sidebar_links)
@@ -454,6 +675,14 @@ ev_sidebar_links_construct (EvSidebarLinks *ev_sidebar_links)
                          "popup_menu",
                          G_CALLBACK (popup_menu_cb),
                          ev_sidebar_links);
+       g_signal_connect (priv->tree_view,
+                         "row-collapsed",
+                         G_CALLBACK (row_collapsed_cb),
+                         ev_sidebar_links);
+       g_signal_connect (priv->tree_view,
+                         "row-expanded",
+                         G_CALLBACK (row_expanded_cb),
+                         ev_sidebar_links);
 }
 
 static void
@@ -499,7 +728,10 @@ ev_sidebar_links_set_current_page (EvSidebarLinks *sidebar_links,
                                   gint            current_page)
 {
        GtkTreeSelection *selection;
+       GtkTreeView *tree_view;
        GtkTreePath *path;
+       GtkTreePath *start_path = NULL;
+       GtkTreePath *end_path = NULL;
        EvSidebarLinkPageSearch search_data;
 
        /* Widget is not currently visible */
@@ -518,18 +750,30 @@ ev_sidebar_links_set_current_page (EvSidebarLinks *sidebar_links,
        if (!path)
                return;
 
-       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (sidebar_links->priv->tree_view));
+       tree_view = GTK_TREE_VIEW (sidebar_links->priv->tree_view);
+       selection = gtk_tree_view_get_selection (tree_view);
 
        g_signal_handler_block (selection, sidebar_links->priv->selection_id);
        g_signal_handler_block (sidebar_links->priv->tree_view, sidebar_links->priv->row_activated_id);
+       g_signal_handlers_block_by_func (sidebar_links->priv->tree_view, row_expanded_cb, sidebar_links);
+
+       /* To mimic previous auto-expand behaviour, let's collapse at the moment when path is 'not
+        * visible', and thus will be revealed and focused at the start of treeview visible range */
+       if (gtk_tree_view_get_visible_range (tree_view, &start_path, &end_path) &&
+           (gtk_tree_path_compare (path, start_path) < 0 || gtk_tree_path_compare (path, end_path) > 0))
+               sidebar_collapse_recursive (sidebar_links);
 
-       gtk_tree_view_expand_to_path (GTK_TREE_VIEW (sidebar_links->priv->tree_view),
-                                     path);
-       gtk_tree_view_set_cursor (GTK_TREE_VIEW (sidebar_links->priv->tree_view),
-                                 path, NULL, FALSE);
+       /* This function also scrolls the tree_view to reveal 'path' */
+       gtk_tree_view_expand_to_path (tree_view, path);
+       /* This functions marks 'path' selected */
+       gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
 
        g_signal_handler_unblock (selection, sidebar_links->priv->selection_id);
        g_signal_handler_unblock (sidebar_links->priv->tree_view, sidebar_links->priv->row_activated_id);
+       g_signal_handlers_unblock_by_func (sidebar_links->priv->tree_view, row_expanded_cb, sidebar_links);
+
+       gtk_tree_path_free (start_path);
+       gtk_tree_path_free (end_path);
 }
 
 static void
@@ -554,25 +798,49 @@ row_activated_callback (GtkTreeView       *treeview,
 }
 
 static void
-expand_open_links (GtkTreeView *tree_view, GtkTreeModel *model, GtkTreeIter *parent)
+expand_open_links (GtkTreeView  *tree_view,
+                  GtkTreeModel *model,
+                  GtkTreeIter  *parent,
+                  gchar        *index_expand,
+                  gchar        *index_collapse)
 {
        GtkTreeIter iter;
        gboolean expand;
 
        if (gtk_tree_model_iter_children (model, &iter, parent)) {
                do {
+                       GtkTreePath *path;
+                       gchar *path_str, *path_token;
+
                        gtk_tree_model_get (model, &iter,
                                            EV_DOCUMENT_LINKS_COLUMN_EXPAND, &expand,
                                            -1);
+                       path = gtk_tree_model_get_path (model, &iter);
                        if (expand) {
-                               GtkTreePath *path;
-                               
-                               path = gtk_tree_model_get_path (model, &iter);
-                               gtk_tree_view_expand_row (tree_view, path, FALSE);
-                               gtk_tree_path_free (path);
+                               if (index_collapse) {
+                                       path_str = gtk_tree_path_to_string (path);
+                                       path_token = g_strconcat ("|", path_str, "|", NULL);
+
+                                       if (!g_strstr_len (index_collapse, -1, path_token))
+                                               gtk_tree_view_expand_row (tree_view, path, FALSE);
+
+                                       g_free (path_str);
+                                       g_free (path_token);
+                               }
+                               else
+                                       gtk_tree_view_expand_row (tree_view, path, FALSE);
+                       } else if (index_expand) {
+                               path_str = gtk_tree_path_to_string (path);
+                               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);
+
+                               g_free (path_str);
+                               g_free (path_token);
                        }
-
-                       expand_open_links (tree_view, model, &iter);
+                       gtk_tree_path_free (path);
+                       expand_open_links (tree_view, model, &iter, index_expand, index_collapse);
                } while (gtk_tree_model_iter_next (model, &iter));
        }
 }
@@ -644,6 +912,10 @@ job_finished_callback (EvJobLinks     *job,
 {
        EvSidebarLinksPrivate *priv = sidebar_links->priv;
        GtkTreeSelection *selection;
+       GtkWidget *window;
+       EvMetadata *metadata;
+       gchar *index_expand = NULL;
+       gchar *index_collapse = NULL;
 
        ev_sidebar_links_set_links_model (sidebar_links, job->model);
 
@@ -652,7 +924,18 @@ job_finished_callback (EvJobLinks     *job,
        g_object_unref (job);
        priv->job = NULL;
 
-       expand_open_links (GTK_TREE_VIEW (priv->tree_view), priv->model, NULL);
+       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_get_string (metadata, "index-expand", &index_expand);
+                       ev_metadata_get_string (metadata, "index-collapse", &index_collapse);
+               }
+       }
+
+       g_signal_handlers_block_by_func (priv->tree_view, row_expanded_cb, sidebar_links);
+       expand_open_links (GTK_TREE_VIEW (priv->tree_view), priv->model, NULL, index_expand, index_collapse);
+       g_signal_handlers_unblock_by_func (priv->tree_view, row_expanded_cb, sidebar_links);
 
        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view));
        gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
diff --git a/shell/ev-utils.c b/shell/ev-utils.c
index 20d0ae68..44b7d61a 100644
--- a/shell/ev-utils.c
+++ b/shell/ev-utils.c
@@ -364,3 +364,34 @@ get_gdk_pixbuf_format_by_extension (const gchar *uri)
        g_slist_free (pixbuf_formats);
        return NULL;
 }
+
+/*
+ * Replace all occurences of substr in str with repl
+ *
+ * @param str a string
+ * @param substr some string to replace
+ * @param repl a replacement string
+ *
+ * @return a newly allocated string with the substr replaced by repl; free with g_free
+ */
+gchar*
+ev_str_replace (const char *str, const char *substr, const char *repl)
+{
+       GString         *gstr;
+       const char      *cur;
+
+       if (str == NULL || substr == NULL || repl == NULL)
+               return NULL;
+
+       gstr = g_string_sized_new (2 * strlen (str));
+
+       for (cur = str; *cur; ++cur) {
+               if (g_str_has_prefix (cur, substr)) {
+                       g_string_append (gstr, repl);
+                       cur += strlen (substr) - 1;
+               } else
+                       g_string_append_c (gstr, *cur);
+       }
+
+       return g_string_free (gstr, FALSE);
+}
diff --git a/shell/ev-utils.h b/shell/ev-utils.h
index dae09613..a58d3d7d 100644
--- a/shell/ev-utils.h
+++ b/shell/ev-utils.h
@@ -39,6 +39,7 @@ void                  ev_gui_menu_position_tree_selection (GtkMenu   *menu,
 
 void                           file_chooser_dialog_add_writable_pixbuf_formats (GtkFileChooser *chooser);
 GdkPixbufFormat*       get_gdk_pixbuf_format_by_extension (const gchar *uri);
+gchar*                  ev_str_replace (const char *str, const char *substr, const char *repl);
 
 G_END_DECLS
 
diff --git a/shell/ev-window.c b/shell/ev-window.c
index a1263541..a9efa807 100644
--- a/shell/ev-window.c
+++ b/shell/ev-window.c
@@ -7557,3 +7557,11 @@ ev_window_focus_view (EvWindow *ev_window)
 
        gtk_widget_grab_focus (ev_window->priv->view);
 }
+
+EvMetadata *
+ev_window_get_metadata (EvWindow *ev_window)
+{
+       g_return_val_if_fail (EV_WINDOW (ev_window), NULL);
+
+       return ev_window->priv->metadata;
+}
diff --git a/shell/ev-window.h b/shell/ev-window.h
index c0c93d2f..9807e663 100644
--- a/shell/ev-window.h
+++ b/shell/ev-window.h
@@ -29,6 +29,7 @@
 #include "ev-link.h"
 #include "ev-history.h"
 #include "ev-document-model.h"
+#include "ev-metadata.h"
 
 G_BEGIN_DECLS
 
@@ -96,6 +97,7 @@ void            ev_window_focus_view                     (EvWindow       *ev_win
 GtkWidget      *ev_window_get_toolbar                   (EvWindow       *ev_window);
 void            ev_window_handle_annot_popup             (EvWindow       *ev_window,
                                                           EvAnnotation   *annot);
+EvMetadata     *ev_window_get_metadata                  (EvWindow       *ev_window);
 
 G_END_DECLS
 


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