diff options
| -rw-r--r-- | libview/ev-view.c | 8 | ||||
| -rw-r--r-- | libview/ev-view.h | 3 | ||||
| -rw-r--r-- | shell/Makefile.am | 2 | ||||
| -rw-r--r-- | shell/eggfindbar.c | 65 | ||||
| -rw-r--r-- | shell/ev-find-sidebar.c | 681 | ||||
| -rw-r--r-- | shell/ev-find-sidebar.h | 63 | ||||
| -rw-r--r-- | shell/ev-window.c | 210 | 
7 files changed, 919 insertions, 113 deletions
| diff --git a/libview/ev-view.c b/libview/ev-view.c index 2e01035e..ebb47e61 100644 --- a/libview/ev-view.c +++ b/libview/ev-view.c @@ -7510,6 +7510,14 @@ ev_view_find_previous (EvView *view)  }  void +ev_view_find_set_result (EvView *view, gint page, gint result) +{ +	ev_document_model_set_page (view->model, page); +	view->find_result = result; +	jump_to_find_result (view); +} + +void  ev_view_find_search_changed (EvView *view)  {  	/* search string has changed, focus on new search result */ diff --git a/libview/ev-view.h b/libview/ev-view.h index ac16211f..ce455a53 100644 --- a/libview/ev-view.h +++ b/libview/ev-view.h @@ -72,6 +72,9 @@ void		ev_view_zoom		  (EvView         *view,  /* Find */  void            ev_view_find_next                 (EvView         *view);  void            ev_view_find_previous             (EvView         *view); +void            ev_view_find_set_result           (EvView         *view, +						   gint            page, +						   gint            result);  void            ev_view_find_search_changed       (EvView         *view);  void     	ev_view_find_set_highlight_search (EvView         *view,  						   gboolean        value); diff --git a/shell/Makefile.am b/shell/Makefile.am index 4fd2ad8f..3f09ea3d 100644 --- a/shell/Makefile.am +++ b/shell/Makefile.am @@ -36,6 +36,8 @@ atril_SOURCES=				\  	ev-bookmark-action.c		\  	ev-application.c		\  	ev-application.h		\ +	ev-find-sidebar.h		\ +	ev-find-sidebar.c		\  	ev-file-monitor.h		\  	ev-file-monitor.c		\  	ev-history.c			\ diff --git a/shell/eggfindbar.c b/shell/eggfindbar.c index 438c5dc7..2988e021 100644 --- a/shell/eggfindbar.c +++ b/shell/eggfindbar.c @@ -39,7 +39,6 @@ struct _EggFindBarPrivate    GtkWidget *find_entry;    GtkWidget *status_label; -  gulong set_focus_handler;    guint case_sensitive : 1;  }; @@ -91,9 +90,6 @@ egg_find_bar_class_init (EggFindBarClass *klass)    object_class->finalize = egg_find_bar_finalize; -  widget_class->show = egg_find_bar_show; -  widget_class->hide = egg_find_bar_hide; -    widget_class->grab_focus = egg_find_bar_grab_focus;    find_bar_signals[NEXT] = @@ -257,27 +253,6 @@ entry_changed_callback (GtkEntry *entry,  }  static void -set_focus_cb (GtkWidget *window, -	      GtkWidget *widget, -	      EggFindBar *bar) -{ -  GtkWidget *wbar = GTK_WIDGET (bar); - -  while (widget != NULL && widget != wbar) -    { -      widget = gtk_widget_get_parent (widget); -    } - -  /* if widget == bar, the new focus widget is in the bar, so we -   * don't deactivate. -   */ -  if (widget != wbar) -    { -      g_signal_emit (bar, find_bar_signals[CLOSE], 0); -    } -} - -static void  egg_find_bar_init (EggFindBar *find_bar)  {    EggFindBarPrivate *priv; @@ -443,46 +418,6 @@ egg_find_bar_get_property (GObject    *object,      }  } -static void -egg_find_bar_show (GtkWidget *widget) -{ -  EggFindBar *bar = EGG_FIND_BAR (widget); -  EggFindBarPrivate *priv = bar->priv; - -  GTK_WIDGET_CLASS (egg_find_bar_parent_class)->show (widget); - -  if (priv->set_focus_handler == 0) -    { -      GtkWidget *toplevel; - -      toplevel = gtk_widget_get_toplevel (widget); - -      priv->set_focus_handler = -	g_signal_connect (toplevel, "set-focus", -			  G_CALLBACK (set_focus_cb), bar); -    } -} - -static void -egg_find_bar_hide (GtkWidget *widget) -{ -  EggFindBar *bar = EGG_FIND_BAR (widget); -  EggFindBarPrivate *priv = bar->priv; - -  if (priv->set_focus_handler != 0) -    { -      GtkWidget *toplevel; - -      toplevel = gtk_widget_get_toplevel (widget); - -      g_signal_handlers_disconnect_by_func -	(toplevel, (void (*)) G_CALLBACK (set_focus_cb), bar); -      priv->set_focus_handler = 0; -    } - -  GTK_WIDGET_CLASS (egg_find_bar_parent_class)->hide (widget); -} -  void  egg_find_bar_grab_focus (GtkWidget *widget)  { diff --git a/shell/ev-find-sidebar.c b/shell/ev-find-sidebar.c new file mode 100644 index 00000000..30718ec7 --- /dev/null +++ b/shell/ev-find-sidebar.c @@ -0,0 +1,681 @@ +/* ev-find-sidebar.c + *  this file is part of evince, a gnome document viewer + * + * Copyright (C) 2013 Carlos Garcia Campos  <[email protected]> + * Copyright (C) 2008 Sergey Pushkin  <[email protected] > + * + * Evince is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Evince is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ev-find-sidebar.h" + +#include "ev-document-text.h" + +#include <string.h> + +typedef struct { +        GtkWidget *tree_view; + +        guint selection_id; +        guint process_matches_idle_id; + +        GtkTreePath *highlighted_result; +        gint         first_match_page; + +        EvJobFind *job; +        gint       job_current_page; +        gint       current_page; +        gint       insert_position; +} EvFindSidebarPrivate; + +enum { +        TEXT_COLUMN, +	PAGE_LABEL_COLUMN, +        PAGE_COLUMN, +        RESULT_COLUMN, + +        N_COLUMNS +}; + +enum { +        RESULT_ACTIVATED, +        N_SIGNALS +}; + +static guint signals[N_SIGNALS]; + +G_DEFINE_TYPE_WITH_PRIVATE (EvFindSidebar, ev_find_sidebar, GTK_TYPE_BOX) + +#define GET_PRIVATE(o) ev_find_sidebar_get_instance_private (o) + +static void +ev_find_sidebar_cancel (EvFindSidebar *sidebar) +{ +        EvFindSidebarPrivate *priv = GET_PRIVATE (sidebar); + +        if (priv->process_matches_idle_id > 0) { +                g_source_remove (priv->process_matches_idle_id); +                priv->process_matches_idle_id = 0; +        } +        g_clear_object (&priv->job); +} + +static void +ev_find_sidebar_dispose (GObject *object) +{ +        EvFindSidebar *sidebar = EV_FIND_SIDEBAR (object); +        EvFindSidebarPrivate *priv = GET_PRIVATE (sidebar); + +        ev_find_sidebar_cancel (sidebar); +        g_clear_pointer (&(priv->highlighted_result), gtk_tree_path_free); + +        G_OBJECT_CLASS (ev_find_sidebar_parent_class)->dispose (object); +} + +static void +ev_find_sidebar_class_init (EvFindSidebarClass *find_sidebar_class) +{ +        GObjectClass *g_object_class = G_OBJECT_CLASS (find_sidebar_class); + +        g_object_class->dispose = ev_find_sidebar_dispose; + +        signals[RESULT_ACTIVATED] = +                g_signal_new ("result-activated", +                              G_TYPE_FROM_CLASS (g_object_class), +                              G_SIGNAL_RUN_LAST, +                              0, NULL, NULL, +                              g_cclosure_marshal_generic, +                              G_TYPE_NONE, 2, +                              G_TYPE_INT, +                              G_TYPE_INT); +} + +static void +ev_find_sidebar_activate_result_at_iter (EvFindSidebar *sidebar, +                                         GtkTreeModel  *model, +                                         GtkTreeIter   *iter) +{ +        EvFindSidebarPrivate *priv = GET_PRIVATE (sidebar); +        gint                  page; +        gint                  result; + + +        if (priv->highlighted_result) +                gtk_tree_path_free (priv->highlighted_result); +        priv->highlighted_result = gtk_tree_model_get_path (model, iter); + +        gtk_tree_model_get (model, iter, +                            PAGE_COLUMN, &page, +                            RESULT_COLUMN, &result, +                            -1); +        g_signal_emit (sidebar, signals[RESULT_ACTIVATED], 0, page - 1, result); +} + +static void +selection_changed_callback (GtkTreeSelection *selection, +                            EvFindSidebar    *sidebar) +{ +        GtkTreeModel *model; +        GtkTreeIter   iter; + +        if (gtk_tree_selection_get_selected (selection, &model, &iter)) +                ev_find_sidebar_activate_result_at_iter (sidebar, model, &iter); +} + +static gboolean +sidebar_tree_button_press_cb (GtkTreeView    *view, +                              GdkEventButton *event, +                              EvFindSidebar  *sidebar) +{ +        EvFindSidebarPrivate *priv = GET_PRIVATE (sidebar); +        GtkTreeModel         *model; +        GtkTreePath          *path; +        GtkTreeIter           iter; + +        gtk_tree_view_get_path_at_pos (view, event->x, event->y, &path, +                                       NULL, NULL, NULL); +        if (!path) +                return FALSE; + +        if (priv->highlighted_result && +            gtk_tree_path_compare (priv->highlighted_result, path) != 0) { +                gtk_tree_path_free (path); +                return FALSE; +        } + +        model = gtk_tree_view_get_model (view); +        gtk_tree_model_get_iter (model, &iter, path); +        gtk_tree_path_free (path); + +        ev_find_sidebar_activate_result_at_iter (sidebar, model, &iter); + +        /* Always return FALSE so the tree view gets the event and can update +         * the selection etc. +         */ +        return FALSE; +} + +static void +ev_find_sidebar_reset_model (EvFindSidebar *sidebar) +{ +        EvFindSidebarPrivate *priv = GET_PRIVATE (sidebar); +        GtkListStore *model; + +        model = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT); +        gtk_tree_view_set_model (GTK_TREE_VIEW (priv->tree_view), +                                 GTK_TREE_MODEL (model)); +        g_object_unref (model); +} + +static void +ev_find_sidebar_init (EvFindSidebar *sidebar) +{ +        EvFindSidebarPrivate *priv; +        GtkWidget            *swindow; +        GtkTreeViewColumn    *column; +        GtkCellRenderer      *renderer; +        GtkTreeSelection     *selection; + +        priv = GET_PRIVATE (sidebar); + +        swindow = gtk_scrolled_window_new (NULL, NULL); +        gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swindow), +                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + +        priv->tree_view = gtk_tree_view_new (); +        ev_find_sidebar_reset_model (sidebar); + +        gtk_tree_view_set_search_column (GTK_TREE_VIEW (priv->tree_view), -1); +        gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->tree_view), FALSE); +        gtk_container_add (GTK_CONTAINER (swindow), priv->tree_view); +        gtk_widget_show (priv->tree_view); + +        gtk_box_pack_start (GTK_BOX (sidebar), swindow, TRUE, TRUE, 0); +        gtk_widget_show (swindow); + +        column = gtk_tree_view_column_new (); +        gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE); +        gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view), column); + +        renderer = (GtkCellRenderer *)g_object_new (GTK_TYPE_CELL_RENDERER_TEXT, +                                                    "ellipsize", +                                                    PANGO_ELLIPSIZE_END, +                                                    NULL); +        gtk_tree_view_column_pack_start (GTK_TREE_VIEW_COLUMN (column), renderer, TRUE); +        gtk_tree_view_column_set_attributes (GTK_TREE_VIEW_COLUMN (column), renderer, +                                             "markup", TEXT_COLUMN, +                                             NULL); + +        renderer = gtk_cell_renderer_text_new (); +        gtk_tree_view_column_pack_end (GTK_TREE_VIEW_COLUMN (column), renderer, FALSE); +        gtk_tree_view_column_set_attributes (GTK_TREE_VIEW_COLUMN (column), renderer, +                                             "text", PAGE_LABEL_COLUMN, +                                             NULL); +        g_object_set (G_OBJECT (renderer), "style", PANGO_STYLE_ITALIC, NULL); + +        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view)); +        priv->selection_id = g_signal_connect (selection, "changed", +                                               G_CALLBACK (selection_changed_callback), +                                               sidebar); +        g_signal_connect (priv->tree_view, "button-press-event", +                          G_CALLBACK (sidebar_tree_button_press_cb), +                          sidebar); +} + +GtkWidget * +ev_find_sidebar_new (void) +{ +        return g_object_new (EV_TYPE_FIND_SIDEBAR, +                             "orientation", GTK_ORIENTATION_VERTICAL, +                             NULL); +} + +static void +ev_find_sidebar_select_highlighted_result (EvFindSidebar *sidebar) +{ +        EvFindSidebarPrivate *priv = GET_PRIVATE (sidebar); +        GtkTreeSelection     *selection; + +        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view)); + +        g_signal_handler_block (selection, priv->selection_id); +        gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->tree_view), priv->highlighted_result, NULL, FALSE); +        g_signal_handler_unblock (selection, priv->selection_id); +} + +static void +ev_find_sidebar_highlight_first_match_of_page (EvFindSidebar *sidebar, +                                               gint           page) +{ +        EvFindSidebarPrivate *priv = GET_PRIVATE (sidebar); +        gint                  index = 0; +        gint                  i; + +        if (!priv->job) +                return; + +        for (i = 0; i < page; i++) +                index += ev_job_find_get_n_results (priv->job, i); + +        if (priv->highlighted_result) +                gtk_tree_path_free (priv->highlighted_result); +        priv->highlighted_result = gtk_tree_path_new_from_indices (index, -1); +        ev_find_sidebar_select_highlighted_result (sidebar); +} + +static gchar * +sanitized_substring (const gchar  *text, +                     gint          start, +                     gint          end) +{ +        const gchar *p; +        const gchar *start_ptr; +        const gchar *end_ptr; +        guint        len = 0; +        gchar       *retval; + +        if (end - start <= 0) +                return NULL; + +        start_ptr = g_utf8_offset_to_pointer (text, start); +        end_ptr = g_utf8_offset_to_pointer (start_ptr, end - start); + +        retval = g_malloc (end_ptr - start_ptr + 1); +        p = start_ptr; + +        while (p != end_ptr) { +                const gchar *next; + +                next = g_utf8_next_char (p); + +                if (next != end_ptr) { +                        GUnicodeBreakType break_type; + +                        break_type = g_unichar_break_type (g_utf8_get_char (p)); +                        if (break_type == G_UNICODE_BREAK_HYPHEN && *next == '\n') { +                                p = g_utf8_next_char (next); +                                continue; +                        } +                } + +                if (*p != '\n') { +                        strncpy (retval + len, p, next - p); +                        len += next - p; +                } else { +                        *(retval + len) = ' '; +                        len++; +                } + +                p = next; +        } + +        if (len == 0) { +                g_free (retval); + +                return NULL; +        } + +        retval[len] = 0; + +        return retval; +} + +static gchar * +get_surrounding_text_markup (const gchar  *text, +                             const gchar  *find_text, +                             gboolean      case_sensitive, +                             PangoLogAttr *log_attrs, +                             gint          log_attrs_length, +                             gint          offset) +{ +        gint   iter; +        gchar *prec = NULL; +        gchar *succ = NULL; +        gchar *match = NULL; +        gchar *markup; +        gint   max_chars; + +        iter = MAX (0, offset - 1); +        while (!log_attrs[iter].is_word_start && iter > 0) +                iter--; + +        prec = sanitized_substring (text, iter, offset); + +        iter = offset; +        offset += g_utf8_strlen (find_text, -1); +        if (!case_sensitive) +                match = g_utf8_substring (text, iter, offset); + +        iter = MIN (log_attrs_length, offset + 1); +        max_chars = MIN (log_attrs_length - 1, iter + 100); +        while (TRUE) { +                gint word = iter; + +                while (!log_attrs[word].is_word_end && word < max_chars) +                        word++; + +                if (word > max_chars) +                        break; + +                iter = word + 1; +        } + +        succ = sanitized_substring (text, offset, iter); + +        markup = g_markup_printf_escaped ("%s<span weight=\"bold\">%s</span>%s", +                                          prec ? prec : "", match ? match : find_text, succ ? succ : ""); +        g_free (prec); +        g_free (succ); +        g_free (match); + +        return markup; +} + +static gchar * +get_page_text (EvDocument   *document, +               EvPage       *page, +               EvRectangle **areas, +               guint        *n_areas) +{ +        gchar   *text; +        gboolean success; + +        ev_document_doc_mutex_lock (); +        text = ev_document_text_get_text (EV_DOCUMENT_TEXT (document), page); +        success = ev_document_text_get_text_layout (EV_DOCUMENT_TEXT (document), page, areas, n_areas); +        ev_document_doc_mutex_unlock (); + +        if (!success) { +                g_free (text); +                return NULL; +        } + +        return text; +} + +static gint +get_match_offset (EvRectangle *areas, +                  guint        n_areas, +                  EvRectangle *match, +                  gint         offset) +{ +        gdouble x, y; +        gint i; + +        x = match->x1; +        y = (match->y1 + match->y2) / 2; + +        i = offset; + +        do { +                EvRectangle *area = areas + i; + +                if (x >= area->x1 && x < area->x2 && +                    y >= area->y1 && y <= area->y2) { +                        return i; +                } + +                i = (i + 1) % n_areas; +        } while (i != offset); + +        return -1; +} + +static gboolean +process_matches_idle (EvFindSidebar *sidebar) +{ +        EvFindSidebarPrivate *priv = GET_PRIVATE (sidebar); +        GtkTreeModel         *model; +        gint                  current_page; +        EvDocument           *document; + +        priv->process_matches_idle_id = 0; + +        if (!ev_job_find_has_results (priv->job)) { +                if (ev_job_is_finished (EV_JOB (priv->job))) +                        g_clear_object (&priv->job); +                return FALSE; +        } + +        document = EV_JOB (priv->job)->document; +        model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->tree_view)); + +        do { +                GList        *matches, *l; +                EvPage       *page; +                gint          result; +                gchar        *page_label; +                gchar        *page_text; +                EvRectangle  *areas = NULL; +                guint         n_areas; +                PangoLogAttr *text_log_attrs; +                gulong        text_log_attrs_length; +                gint          offset; + +                current_page = priv->current_page; +                priv->current_page = (priv->current_page + 1) % priv->job->n_pages; + +                matches = priv->job->pages[current_page]; +                if (!matches) +                        continue; + +                page = ev_document_get_page (document, current_page); +		page_label = ev_document_get_page_label (document, current_page); +                page_text = get_page_text (document, page, &areas, &n_areas); +                g_object_unref (page); +                if (!page_text) +                        continue; + +                text_log_attrs_length = g_utf8_strlen (page_text, -1); +                text_log_attrs = g_new0 (PangoLogAttr, text_log_attrs_length + 1); +                pango_get_log_attrs (page_text, -1, -1, NULL, text_log_attrs, text_log_attrs_length + 1); + +                if (priv->first_match_page == -1) +                        priv->first_match_page = current_page; + +                offset = 0; + +                for (l = matches, result = 0; l; l = g_list_next (l), result++) { +                        EvRectangle *match = (EvRectangle *)l->data; +                        gchar       *markup; +                        GtkTreeIter  iter; +                        gint         new_offset; + +                        new_offset = get_match_offset (areas, n_areas, match, offset); +                        if (new_offset == -1) { +                                g_warning ("No offset found for match \"%s\" at page %d after processing %d results\n", +                                           priv->job->text, current_page, result); +                                /* It may happen that a vertical text match has no corresponding text area, skip +                                 * that but keep iterating to show any other matches in page (issue #1545) */ +                                continue; +                        } +                        offset = new_offset; + +                        if (current_page >= priv->job->start_page) { +                                gtk_list_store_append (GTK_LIST_STORE (model), &iter); +                        } else { +                                gtk_list_store_insert (GTK_LIST_STORE (model), &iter, +                                                       priv->insert_position); +                                priv->insert_position++; +                        } + +                        markup = get_surrounding_text_markup (page_text, +                                                              priv->job->text, +                                                              priv->job->case_sensitive, +                                                              text_log_attrs, +                                                              text_log_attrs_length, +                                                              offset); + +                        gtk_list_store_set (GTK_LIST_STORE (model), &iter, +                                            TEXT_COLUMN, markup, +					    PAGE_LABEL_COLUMN, page_label, +                                            PAGE_COLUMN, current_page + 1, +                                            RESULT_COLUMN, result, +                                            -1); +                        g_free (markup); +                } + +                g_free (page_label); +                g_free (page_text); +                g_free (text_log_attrs); +                g_free (areas); +        } while (current_page != priv->job_current_page); + +        if (ev_job_is_finished (EV_JOB (priv->job)) && priv->current_page == priv->job->start_page) +                ev_find_sidebar_highlight_first_match_of_page (sidebar, priv->first_match_page); + +        return FALSE; +} + +static void +find_job_updated_cb (EvJobFind     *job, +                     gint           page, +                     EvFindSidebar *sidebar) +{ +        EvFindSidebarPrivate *priv = GET_PRIVATE (sidebar); + +        priv->job_current_page = page; +} + +static void +find_job_cancelled_cb (EvJob         *job, +                       EvFindSidebar *sidebar) +{ +        ev_find_sidebar_cancel (sidebar); +} + +void +ev_find_sidebar_start (EvFindSidebar *sidebar, +                       EvJobFind     *job) +{ +        EvFindSidebarPrivate *priv = GET_PRIVATE (sidebar); + +        if (priv->job == job) +                return; + +        ev_find_sidebar_clear (sidebar); +        priv->job = g_object_ref (job); +        g_signal_connect_object (job, "updated", +                                 G_CALLBACK (find_job_updated_cb), +                                 sidebar, 0); +        g_signal_connect_object (job, "cancelled", +                                 G_CALLBACK (find_job_cancelled_cb), +                                 sidebar, 0); +        priv->job_current_page = -1; +        priv->first_match_page = -1; +        priv->current_page = job->start_page; +        priv->insert_position = 0; +} + +void +ev_find_sidebar_restart (EvFindSidebar *sidebar, +                         gint           page) +{ +        EvFindSidebarPrivate *priv = GET_PRIVATE (sidebar); +        gint                  first_match_page = -1; +        gint                  i; + +        if (!priv->job) +                return; + +        for (i = 0; i < priv->job->n_pages; i++) { +                int index; + +                index = page + i; + +                if (index >= priv->job->n_pages) +                        index -= priv->job->n_pages; + +                if (priv->job->pages[index]) { +                        first_match_page = index; +                        break; +                } +        } + +        if (first_match_page != -1) +                ev_find_sidebar_highlight_first_match_of_page (sidebar, first_match_page); +} + +void +ev_find_sidebar_update (EvFindSidebar *sidebar) +{ +        EvFindSidebarPrivate *priv = GET_PRIVATE (sidebar); + +        if (!priv->job) +                return; + +        if (priv->process_matches_idle_id == 0) +                priv->process_matches_idle_id = g_idle_add ((GSourceFunc)process_matches_idle, sidebar); +} + +void +ev_find_sidebar_clear (EvFindSidebar *sidebar) +{ +        EvFindSidebarPrivate *priv = GET_PRIVATE (sidebar); + +        ev_find_sidebar_cancel (sidebar); + +        /* It seems it's more efficient to set a new model in the tree view instead of +         * clearing the model that would emit row-deleted signal for every row in the model +         */ +        ev_find_sidebar_reset_model (sidebar); +        g_clear_pointer (&priv->highlighted_result, gtk_tree_path_free); +} + +void +ev_find_sidebar_previous (EvFindSidebar *sidebar) +{ +        EvFindSidebarPrivate *priv = GET_PRIVATE (sidebar); + +        if (!priv->highlighted_result) +                return; + +        if (!gtk_tree_path_prev (priv->highlighted_result)) { +                GtkTreeModel *model; +                GtkTreeIter   iter; + +                model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->tree_view)); +                gtk_tree_model_get_iter (model, &iter, priv->highlighted_result); +                while (gtk_tree_model_iter_next (model, &iter)) +                        gtk_tree_path_next (priv->highlighted_result); +        } +        ev_find_sidebar_select_highlighted_result (sidebar); +} + +void +ev_find_sidebar_next (EvFindSidebar *sidebar) +{ +        EvFindSidebarPrivate *priv = GET_PRIVATE (sidebar); +        GtkTreeModel         *model; +        GtkTreeIter           iter; + +        if (!priv->highlighted_result) +                return; + +        model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->tree_view)); +        gtk_tree_model_get_iter (model, &iter, priv->highlighted_result); +        if (gtk_tree_model_iter_next (model, &iter)) { +                gtk_tree_path_next (priv->highlighted_result); +        } else { +                gtk_tree_path_free (priv->highlighted_result); +                priv->highlighted_result = gtk_tree_path_new_first (); +        } +        ev_find_sidebar_select_highlighted_result (sidebar); +} + diff --git a/shell/ev-find-sidebar.h b/shell/ev-find-sidebar.h new file mode 100644 index 00000000..61920c6a --- /dev/null +++ b/shell/ev-find-sidebar.h @@ -0,0 +1,63 @@ +/* ev-find-sidebar.h + *  this file is part of evince, a gnome document viewer + * + * Copyright (C) 2013 Carlos Garcia Campos  <[email protected]> + * Copyright (C) 2008 Sergey Pushkin  <[email protected] > + * + * Evince is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Evince is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef EV_FIND_SIDEBAR_H +#define EV_FIND_SIDEBAR_H + +#include <gtk/gtk.h> + +#include "ev-jobs.h" + +G_BEGIN_DECLS + +#define EV_TYPE_FIND_SIDEBAR              (ev_find_sidebar_get_type ()) +#define EV_FIND_SIDEBAR(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), EV_TYPE_FIND_SIDEBAR, EvFindSidebar)) +#define EV_IS_FIND_SIDEBAR(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), EV_TYPE_FIND_SIDEBAR)) +#define EV_FIND_SIDEBAR_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), EV_TYPE_FIND_SIDEBAR, EvFindSidebarClass)) +#define EV_IS_FIND_SIDEBAR_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), EV_TYPE_FIND_SIDEBAR)) +#define EV_FIND_SIDEBAR_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS ((object), EV_TYPE_FIND_SIDEBAR, EvFindSidebarClass)) + +typedef struct _EvFindSidebar EvFindSidebar; +typedef struct _EvFindSidebarClass EvFindSidebarClass; + +struct _EvFindSidebar { +        GtkBox base_instance; +}; + +struct _EvFindSidebarClass { +        GtkBoxClass base_class; +}; + +GType      ev_find_sidebar_get_type (void); +GtkWidget *ev_find_sidebar_new      (void); + +void       ev_find_sidebar_start    (EvFindSidebar *find_sidebar, +                                     EvJobFind     *job); +void       ev_find_sidebar_restart  (EvFindSidebar *find_sidebar, +                                     gint           page); +void       ev_find_sidebar_update   (EvFindSidebar *find_sidebar); +void       ev_find_sidebar_clear    (EvFindSidebar *find_sidebar); +void       ev_find_sidebar_previous (EvFindSidebar *find_sidebar); +void       ev_find_sidebar_next     (EvFindSidebar *find_sidebar); + +G_END_DECLS + +#endif /* EV_FIND_SIDEBAR_H */ diff --git a/shell/ev-window.c b/shell/ev-window.c index 348e62b9..bc1cc464 100644 --- a/shell/ev-window.c +++ b/shell/ev-window.c @@ -47,6 +47,7 @@  #include "egg-toolbars-model.h"  #include "eggfindbar.h" +#include "ev-find-sidebar.h"  #include "ephy-zoom-action.h"  #include "ephy-zoom.h" @@ -144,6 +145,7 @@ struct _EvWindowPrivate {  	GtkWidget *password_view;  	GtkWidget *sidebar_thumbs;  	GtkWidget *sidebar_links; +	GtkWidget *find_sidebar;  	GtkWidget *sidebar_attachments;  	GtkWidget *sidebar_layers;  	GtkWidget *sidebar_annots; @@ -384,6 +386,9 @@ static void    zoom_control_changed_cb                 (EphyZoomAction *action,                                                           float           zoom,                                                           EvWindow       *ev_window); +static void     ev_window_show_find_bar                 (EvWindow         *ev_window); +static void     ev_window_close_find_bar                (EvWindow         *ev_window); +  static gchar *caja_sendto = NULL;  G_DEFINE_TYPE_WITH_PRIVATE (EvWindow, ev_window, GTK_TYPE_APPLICATION_WINDOW) @@ -1733,7 +1738,7 @@ ev_window_setup_document (EvWindow *ev_window)  	if (EV_IS_DOCUMENT_FIND (document)) {  		if (ev_window->priv->search_string &&  		    !EV_WINDOW_IS_PRESENTATION (ev_window)) { -			ev_window_cmd_edit_find (NULL, ev_window); +			ev_window_show_find_bar (ev_window);  			egg_find_bar_set_search_string (EGG_FIND_BAR (ev_window->priv->find_bar), ev_window->priv->search_string);  		} @@ -2487,7 +2492,7 @@ ev_window_open_document (EvWindow       *ev_window,  	if (search_string && EV_IS_DOCUMENT_FIND (document) &&  	    mode != EV_WINDOW_MODE_PRESENTATION) { -		ev_window_cmd_edit_find (NULL, ev_window); +		ev_window_show_find_bar (ev_window);  		egg_find_bar_set_search_string (EGG_FIND_BAR (ev_window->priv->find_bar),  						search_string);  	} @@ -4184,32 +4189,61 @@ ev_window_cmd_edit_select_all (GtkAction *action, EvWindow *ev_window)  }  static void +ev_window_cmd_toggle_find (GtkAction *action, EvWindow *ev_window) +{ +	gboolean show_find_bar; + +	show_find_bar = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); +	if (show_find_bar) +		ev_window_show_find_bar (ev_window); +	else +		ev_window_close_find_bar (ev_window); +} + +static void  ev_window_cmd_edit_find (GtkAction *action, EvWindow *ev_window)  { -	if (ev_window->priv->document == NULL || !EV_IS_DOCUMENT_FIND (ev_window->priv->document)) { -		g_error ("Find action should be insensitive since document doesn't support find"); -		return; -	} +	ev_window_show_find_bar (ev_window); +} -	if (EV_WINDOW_IS_PRESENTATION (ev_window)) -		return; +static void +ev_window_find_previous (EvWindow *ev_window) +{ +	ev_view_find_previous (EV_VIEW (ev_window->priv->view)); +	ev_find_sidebar_previous (EV_FIND_SIDEBAR (ev_window->priv->find_sidebar)); +} -	update_chrome_flag (ev_window, EV_CHROME_FINDBAR, TRUE); -	update_chrome_visibility (ev_window); -	gtk_widget_grab_focus (ev_window->priv->find_bar); +static void +ev_window_find_next (EvWindow *ev_window) +{ +	ev_view_find_next (EV_VIEW (ev_window->priv->view)); +	ev_find_sidebar_next (EV_FIND_SIDEBAR (ev_window->priv->find_sidebar)); +} + +static gboolean +find_next_idle_cb (EvWindow *ev_window) +{ +	ev_window_find_next (ev_window); +	return FALSE;  }  static void  ev_window_cmd_edit_find_next (GtkAction *action, EvWindow *ev_window)  { +	gboolean find_bar_hidden; +  	if (EV_WINDOW_IS_PRESENTATION (ev_window))  		return; -	update_chrome_flag (ev_window, EV_CHROME_FINDBAR, TRUE); -	update_chrome_visibility (ev_window); -	gtk_widget_grab_focus (ev_window->priv->find_bar); +	find_bar_hidden = !gtk_widget_get_visible (ev_window->priv->find_bar); +	ev_window_show_find_bar (ev_window); +  	if (ev_window->priv->document && ev_window->priv->document->iswebdocument == FALSE) { -		ev_view_find_next (EV_VIEW (ev_window->priv->view)); +		/* Use idle to make sure view allocation happens before find */ +		if (find_bar_hidden) +			g_idle_add ((GSourceFunc)find_next_idle_cb, ev_window); +		else +			ev_window_find_next (ev_window);  	}  #if ENABLE_EPUB  	else { @@ -4218,17 +4252,30 @@ ev_window_cmd_edit_find_next (GtkAction *action, EvWindow *ev_window)  #endif  } +static gboolean +find_previous_idle_cb (EvWindow *ev_window) +{ +	ev_window_find_previous (ev_window); +	return FALSE; +} +  static void  ev_window_cmd_edit_find_previous (GtkAction *action, EvWindow *ev_window)  { +	gboolean find_bar_hidden; +  	if (EV_WINDOW_IS_PRESENTATION (ev_window))  		return; -	update_chrome_flag (ev_window, EV_CHROME_FINDBAR, TRUE); -	update_chrome_visibility (ev_window); -	gtk_widget_grab_focus (ev_window->priv->find_bar); +	find_bar_hidden = !gtk_widget_get_visible (ev_window->priv->find_bar); +	ev_window_show_find_bar (ev_window); +  	if (ev_window->priv->document && ev_window->priv->document->iswebdocument == FALSE) { -		ev_view_find_previous (EV_VIEW (ev_window->priv->view)); +		/* Use idle to make sure view allocation happens before find */ +		if (find_bar_hidden) +			g_idle_add ((GSourceFunc)find_previous_idle_cb, ev_window); +		else +			ev_window_find_previous (ev_window);  	}  #if ENABLE_EPUB  	else { @@ -5140,14 +5187,9 @@ ev_window_cmd_escape (GtkAction *action, EvWindow *window)  	widget = gtk_window_get_focus (GTK_WINDOW (window));  	if (widget && gtk_widget_get_ancestor (widget, EGG_TYPE_FIND_BAR)) { -		update_chrome_flag (window, EV_CHROME_FINDBAR, FALSE); -		update_chrome_visibility (window); - -		if (window->priv->view) -			gtk_widget_grab_focus (window->priv->view); +		ev_window_close_find_bar (window);  #if ENABLE_EPUB -		else -			gtk_widget_grab_focus (window->priv->webview); +		gtk_widget_grab_focus (window->priv->webview);  #endif  	} else {  		gboolean fullscreen; @@ -5730,6 +5772,15 @@ attachment_bar_menu_popup_cb (EvSidebarAttachments *attachbar,  }  static void +find_sidebar_result_activated_cb (EvFindSidebar *find_sidebar, +				  gint           page, +				  gint           result, +				  EvWindow      *window) +{ +	ev_view_find_set_result (EV_VIEW (window->priv->view), page, result); +} + +static void  ev_window_update_find_status_message (EvWindow *ev_window)  {  	gchar *message; @@ -5794,6 +5845,7 @@ ev_window_find_job_updated_cb (EvJobFind *job,  				      page);  	}  	ev_window_update_find_status_message (ev_window); +	ev_find_sidebar_update (EV_FIND_SIDEBAR (ev_window->priv->find_sidebar));  }  static void @@ -5821,10 +5873,10 @@ find_bar_previous_cb (EggFindBar *find_bar,  #if ENABLE_EPUB  	if (ev_window->priv->document && ev_window->priv->document->iswebdocument == TRUE ) {  		ev_web_view_find_previous(EV_WEB_VIEW(ev_window->priv->webview)); -	}else +	} else  #endif  	{ -		ev_view_find_previous (EV_VIEW (ev_window->priv->view)); +		ev_window_find_previous (ev_window);  	}  } @@ -5838,7 +5890,7 @@ find_bar_next_cb (EggFindBar *find_bar,  	} else  #endif  	{ -		ev_view_find_next (EV_VIEW (ev_window->priv->view)); +		ev_window_find_next (ev_window);  	}  } @@ -5850,14 +5902,8 @@ find_bar_close_cb (EggFindBar *find_bar,  	if (ev_window->priv->document && ev_window->priv->document->iswebdocument == TRUE ) {  		ev_web_view_find_cancel(EV_WEB_VIEW(ev_window->priv->webview));  	} -	else  #endif -	{ -			ev_view_find_cancel (EV_VIEW (ev_window->priv->view)); -	} -	ev_window_clear_find_job (ev_window); -	update_chrome_flag (ev_window, EV_CHROME_FINDBAR, FALSE); -	update_chrome_visibility (ev_window); +	ev_window_close_find_bar (ev_window);  }  static void @@ -5880,6 +5926,9 @@ ev_window_search_start (EvWindow *ev_window)  							     search_string,  							     egg_find_bar_get_case_sensitive (find_bar)); +		ev_find_sidebar_start (EV_FIND_SIDEBAR (ev_window->priv->find_sidebar), +				       EV_JOB_FIND (ev_window->priv->find_job)); +  		g_signal_connect (ev_window->priv->find_job, "finished",  				  G_CALLBACK (ev_window_find_job_finished_cb),  				  ev_window); @@ -5890,6 +5939,7 @@ ev_window_search_start (EvWindow *ev_window)  	} else {  		ev_window_update_actions (ev_window);  		egg_find_bar_set_status_text (find_bar, NULL); +		ev_find_sidebar_clear (EV_FIND_SIDEBAR (ev_window->priv->find_sidebar));  		if (ev_window->priv->document->iswebdocument == FALSE) {  			gtk_widget_queue_draw (GTK_WIDGET (ev_window->priv->view));  		} @@ -5936,14 +5986,71 @@ find_bar_visibility_changed_cb (EggFindBar *find_bar,  #endif  		ev_window_update_actions (ev_window); -		if (visible) -			ev_window_search_start (ev_window); -		else +		if (!visible)  			egg_find_bar_set_status_text (EGG_FIND_BAR (ev_window->priv->find_bar), NULL);  	}  }  static void +update_toggle_find_action (EvWindow *ev_window, +			   gboolean  active) +{ +	GtkAction *action; + +	action = gtk_action_group_get_action (ev_window->priv->action_group, "EditFind"); +	if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)) == active) +		return; + +	g_signal_handlers_block_by_func (action, G_CALLBACK (ev_window_cmd_toggle_find), ev_window); +	gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), active); +	g_signal_handlers_unblock_by_func (action, G_CALLBACK (ev_window_cmd_toggle_find), ev_window); +} + +static void +ev_window_show_find_bar (EvWindow *ev_window) +{ +	if (gtk_widget_get_visible (ev_window->priv->find_bar)) +		return; + +	if (ev_window->priv->document == NULL || !EV_IS_DOCUMENT_FIND (ev_window->priv->document)) { +		g_error ("Find action should be insensitive since document doesn't support find"); +		return; +	} + +	if (EV_WINDOW_IS_PRESENTATION (ev_window)) +		return; + +	g_object_ref (ev_window->priv->sidebar); +	gtk_container_remove (GTK_CONTAINER (ev_window->priv->hpaned), ev_window->priv->sidebar); +	gtk_paned_pack1 (GTK_PANED (ev_window->priv->hpaned), +			 ev_window->priv->find_sidebar, FALSE, FALSE); +	gtk_widget_show (ev_window->priv->find_sidebar); + +	update_chrome_flag (ev_window, EV_CHROME_FINDBAR, TRUE); +	update_chrome_visibility (ev_window); +	gtk_widget_grab_focus (ev_window->priv->find_bar); +	update_toggle_find_action (ev_window, TRUE); +} + +static void +ev_window_close_find_bar (EvWindow *ev_window) +{ +	if (!gtk_widget_get_visible (ev_window->priv->find_bar)) +		return; + +	g_object_ref (ev_window->priv->find_sidebar); +	gtk_container_remove (GTK_CONTAINER (ev_window->priv->hpaned), +			      ev_window->priv->find_sidebar); +	gtk_paned_pack1 (GTK_PANED (ev_window->priv->hpaned), +			 ev_window->priv->sidebar, FALSE, FALSE); + +	update_chrome_flag (ev_window, EV_CHROME_FINDBAR, FALSE); +	update_chrome_visibility (ev_window); +	gtk_widget_grab_focus (ev_window->priv->view); +	update_toggle_find_action (ev_window, FALSE); +} + +static void  zoom_control_changed_cb (EphyZoomAction *action,  			 float           zoom,  			 EvWindow       *ev_window) @@ -6467,9 +6574,6 @@ static const GtkActionEntry entries[] = {            G_CALLBACK (ev_window_cmd_edit_copy) },   	{ "EditSelectAll", "edit-select-all", N_("Select _All"), "<control>A", NULL,  	  G_CALLBACK (ev_window_cmd_edit_select_all) }, -        { "EditFind", "edit-find", N_("_Find…"), "<control>F", -          N_("Find a word or phrase in the document"), -          G_CALLBACK (ev_window_cmd_edit_find) },  	{ "EditFindNext", NULL, N_("Find Ne_xt"), "<control>G", NULL,  	  G_CALLBACK (ev_window_cmd_edit_find_next) },  	{ "EditFindPrevious", NULL, N_("Find Pre_vious"), "<shift><control>G", NULL, @@ -6624,6 +6728,9 @@ static const GtkToggleActionEntry toggle_entries[] = {  	  N_("Activate or disable caret-navigation"),  	  G_CALLBACK (ev_window_cmd_view_toggle_caret_navigation) }, +	{ "EditFind", GTK_STOCK_FIND, N_("_Find…"), "<control>F", +	  N_("Find a word or phrase in the document"), +	  G_CALLBACK (ev_window_cmd_toggle_find) },  };  /* Popups specific items */ @@ -7116,7 +7223,7 @@ do_action_named (EvWindow *window, EvLinkAction *action)  	} else if (g_ascii_strcasecmp (name, "GoToPage") == 0) {  		ev_window_cmd_focus_page_selector (NULL, window);  	} else if (g_ascii_strcasecmp (name, "Find") == 0) { -		ev_window_cmd_edit_find (NULL, window); +		ev_window_show_find_bar (window);  	} else if (g_ascii_strcasecmp (name, "Close") == 0) {  		ev_window_cmd_file_close_window (NULL, window);  	} else if (g_ascii_strcasecmp (name, "Print") == 0) { @@ -7966,6 +8073,12 @@ ev_window_init (EvWindow *ev_window)  			    FALSE, FALSE, 0);  	gtk_widget_show (ev_window->priv->toolbar); +	/* Find Bar */ +	ev_window->priv->find_bar = egg_find_bar_new (); +	gtk_box_pack_end (GTK_BOX (ev_window->priv->main_box), +			  ev_window->priv->find_bar, +			  FALSE, TRUE, 0); +  	/* Add the main area */  	ev_window->priv->hpaned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);  	g_signal_connect (ev_window->priv->hpaned, @@ -8142,11 +8255,12 @@ ev_window_init (EvWindow *ev_window)  	gtk_widget_show (ev_window->priv->view);  	gtk_widget_show (ev_window->priv->password_view); -	/* Find Bar */ -	ev_window->priv->find_bar = egg_find_bar_new (); -	gtk_box_pack_end (GTK_BOX (ev_window->priv->main_box), -			  ev_window->priv->find_bar, -			  FALSE, TRUE, 0); +	/* Find results sidebar */ +	ev_window->priv->find_sidebar = ev_find_sidebar_new (); +	g_signal_connect (ev_window->priv->find_sidebar, +			  "result-activated", +			  G_CALLBACK (find_sidebar_result_activated_cb), +			  ev_window);  	/* We own a ref on these widgets, as we can swap them in and out */  	g_object_ref (ev_window->priv->view); | 
