[gnome-documents] preview: add a first implementation of find as you type



commit 7d613c5018c4a104bb81a0d1cda35935fdd415e5
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Wed Jul 18 17:59:58 2012 -0400

    preview: add a first implementation of find as you type
    
    - Split a PreviewToolbar subclass from MainToolbar, and have embed
      replace the toolbar with it in preview mode
    - Add a PreviewSearchbar subclass of Searchbar that triggers an
      EvJobFind in the EvView
    - Hook up event handling to MainWindow

 src/embed.js       |   27 ++++-
 src/lib/gd-utils.c |   10 ++
 src/lib/gd-utils.h |    5 +
 src/mainToolbar.js |  277 +++++++++++++++++++++-------------------------------
 src/mainWindow.js  |   19 ++--
 src/preview.js     |  166 +++++++++++++++++++++++++++++++
 6 files changed, 324 insertions(+), 180 deletions(-)
---
diff --git a/src/embed.js b/src/embed.js
index 6460d65..30393c5 100644
--- a/src/embed.js
+++ b/src/embed.js
@@ -65,11 +65,6 @@ const Embed = new Lang.Class({
         this._overlayLayout.add(this._contentsActor,
             Clutter.BinAlignment.FILL, Clutter.BinAlignment.FILL);
 
-        // pack the toolbar
-        this._toolbar = new MainToolbar.OverviewToolbar(this._overlayLayout);
-        this._contentsActor.add_actor(this._toolbar.actor);
-        this._contentsLayout.set_fill(this._toolbar.actor, true, false);
-
         // pack the main GtkNotebook and a spinnerbox in a BinLayout, so that
         // we can easily bring them front/back
         this._viewLayout = new Clutter.BinLayout();
@@ -229,6 +224,15 @@ const Embed = new Lang.Class({
         if (this._preview)
             this._preview.setModel(null);
 
+        if (this._toolbar)
+            this._toolbar.actor.destroy();
+
+        // pack the toolbar
+        this._toolbar = new MainToolbar.OverviewToolbar(this._overlayLayout);
+        this._contentsLayout.pack_start = true;
+        this._contentsActor.add_actor(this._toolbar.actor);
+        this._contentsLayout.set_fill(this._toolbar.actor, true, false);
+
         this._spinnerBox.moveOut();
         this._errorBox.moveOut();
 
@@ -236,11 +240,24 @@ const Embed = new Lang.Class({
     },
 
     _prepareForPreview: function() {
+        if (this._toolbar)
+            this._toolbar.actor.destroy();
+
+        // pack the toolbar
+        this._toolbar = new Preview.PreviewToolbar(this._preview);
+        this._contentsLayout.pack_start = true;
+        this._contentsActor.add_actor(this._toolbar.actor);
+        this._contentsLayout.set_fill(this._toolbar.actor, true, false);
+
         this._notebook.set_current_page(this._previewPage);
     },
 
     _setError: function(primary, secondary) {
         this._errorBox.update(primary, secondary);
         this._errorBox.moveIn();
+    },
+
+    getMainToolbar: function(event) {
+        return this._toolbar;
     }
 });
diff --git a/src/lib/gd-utils.c b/src/lib/gd-utils.c
index 164de3d..e3b2eff 100644
--- a/src/lib/gd-utils.c
+++ b/src/lib/gd-utils.c
@@ -649,3 +649,13 @@ gd_create_variant_from_pixbuf (GdkPixbuf *pixbuf)
                                                     g_object_ref (pixbuf)));
   return g_variant_ref_sink (variant);
 }
+
+void
+gd_ev_view_find_changed (EvView *view,
+                         EvJobFind *job,
+                         gint page)
+{
+  ev_view_find_changed (view,
+                        ev_job_find_get_results (job),
+                        page);
+}
diff --git a/src/lib/gd-utils.h b/src/lib/gd-utils.h
index d82e800..601cbe6 100644
--- a/src/lib/gd-utils.h
+++ b/src/lib/gd-utils.h
@@ -23,6 +23,7 @@
 #define __GD_UTILS_H__
 
 #include <gtk/gtk.h>
+#include <evince-view.h>
 
 void gd_queue_thumbnail_job_for_file_async (GFile *file,
                                             GAsyncReadyCallback callback,
@@ -57,5 +58,9 @@ void   gd_entry_focus_hack (GtkWidget *entry,
 
 GVariant *gd_create_variant_from_pixbuf (GdkPixbuf *pixbuf);
 
+void gd_ev_view_find_changed (EvView *view,
+                              EvJobFind *job,
+                              gint page);
+
 #endif /* __GD_UTILS_H__ */
                                   
diff --git a/src/mainToolbar.js b/src/mainToolbar.js
index 4c68740..6ce23f8 100644
--- a/src/mainToolbar.js
+++ b/src/mainToolbar.js
@@ -47,10 +47,6 @@ const MainToolbar = new Lang.Class({
     _init: function() {
         this._model = null;
 
-        this._collBackButton = null;
-        this._collectionId = 0;
-        this._selectionChangedId = 0;
-
         this.widget = new Gd.MainToolbar({ icon_size: Gtk.IconSize.MENU });
         this.widget.get_style_context().add_class(Gtk.STYLE_CLASS_MENUBAR);
         this.widget.show();
@@ -64,6 +60,35 @@ const MainToolbar = new Lang.Class({
                          false, true, false,
                          Clutter.BoxAlignment.CENTER, Clutter.BoxAlignment.START);
 
+        this.createSearchbar();
+    },
+
+    createSearchbar: function() {
+        log('Error: MainToolbar subclasses must implement createSearchbar');
+    },
+
+    handleEvent: function(event) {
+        let res = this._searchbar.handleEvent(event);
+        return res;
+    },
+
+    toggleSearch: function() {
+        this._searchbar.toggle();
+    }
+});
+
+const OverviewToolbar = new Lang.Class({
+    Name: 'OverviewToolbar',
+    Extends: MainToolbar,
+
+    _init: function(overlayLayout) {
+        this._overlayLayout = overlayLayout;
+        this._collBackButton = null;
+        this._collectionId = 0;
+        this._selectionChangedId = 0;
+
+        this.parent();
+
         // setup listeners to mode changes that affect the toolbar layout
         this._searchStringId =
             Global.searchController.connect('search-string-changed',
@@ -80,20 +105,12 @@ const MainToolbar = new Lang.Class({
         this._selectionModeId =
             Global.selectionController.connect('selection-mode-changed',
                                                Lang.bind(this, this._resetToolbarMode));
-        this._windowModeId =
-            Global.modeController.connect('window-mode-changed',
-                                          Lang.bind(this, this._resetToolbarMode));
         this._resetToolbarMode();
 
         this.widget.connect('destroy', Lang.bind(this,
             function() {
                 this._clearStateData();
 
-                if (this._windowModeId != 0) {
-                    Global.modeController.disconnect(this._windowModeId);
-                    this._windowModeId = 0;
-                }
-
                 if (this._selectionModeId != 0) {
                     Global.selectionController.disconnect(this._selectionModeId);
                     this._selectionModeId = 0;
@@ -121,92 +138,54 @@ const MainToolbar = new Lang.Class({
             }));
     },
 
-    _clearStateData: function() {
-        this._model = null;
-        this._collBackButton = null;
-
-        if (this._collectionId != 0) {
-            Global.collectionManager.disconnect(this._collectionId);
-            this._collectionId = 0;
-        }
-
-        if (this._selectionChangedId != 0) {
-            Global.selectionController.disconnect(this._selectionChangedId);
-            this._selectionChangedId = 0;
-        }
-    },
-
-    _clearToolbar: function() {
-        this._clearStateData();
-
-        this.widget.get_style_context().remove_class('documents-selection-mode');
-        this.widget.reset_style();
-        this.widget.clear();
-    },
-
     _setToolbarTitle: function() {
-        let windowMode = Global.modeController.getWindowMode();
         let selectionMode = Global.selectionController.getSelectionMode();
         let activeCollection = Global.collectionManager.getActiveItem();
         let primary = null;
         let detail = null;
 
-        if (windowMode == WindowMode.WindowMode.OVERVIEW) {
-            if (!selectionMode) {
-                if (activeCollection) {
-                    primary = activeCollection.name;
-                } else {
-                    let string = Global.searchController.getString();
-
-                    if (string == '') {
-                        let searchType = Global.searchTypeManager.getActiveItem();
-                        let searchSource = Global.sourceManager.getActiveItem();
-
-                        if (searchType.id != 'all')
-                            primary = searchType.name;
-                        else
-                            primary = _("New and Recent");
-
-                        if (searchSource.id != 'all')
-                            detail = searchSource.name;
-                    } else {
-                        let searchMatch = Global.searchMatchManager.getActiveItem();
-
-                        primary = _("Results for \"%s\"").format(string);
-                        if (searchMatch.id == 'title')
-                            detail = _("filtered by title");
-                        else if (searchMatch.id == 'author')
-                            detail = _("filtered by author");
-                    }
-                }
+        if (!selectionMode) {
+            if (activeCollection) {
+                primary = activeCollection.name;
             } else {
-                let length = Global.selectionController.getSelection().length;
-
-                if (length == 0)
-                    detail = _("Click on items to select them");
-                else
-                    detail = Gettext.ngettext("%d selected",
-                                              "%d selected",
-                                              length).format(length);
-
-                if (activeCollection) {
-                    primary = activeCollection.name;
-                } else if (length != 0) {
-                    primary = detail;
-                    detail = null;
-                }
-            }
-        } else if (windowMode == WindowMode.WindowMode.PREVIEW) {
-            let doc = Global.documentManager.getActiveItem();
-            primary = doc.name;
+                let string = Global.searchController.getString();
+
+                if (string == '') {
+                    let searchType = Global.searchTypeManager.getActiveItem();
+                    let searchSource = Global.sourceManager.getActiveItem();
 
-            if (this._model) {
-                let curPage, totPages;
+                    if (searchType.id != 'all')
+                        primary = searchType.name;
+                    else
+                        primary = _("New and Recent");
 
-                curPage = this._model.get_page();
-                totPages = this._model.get_document().get_n_pages();
+                    if (searchSource.id != 'all')
+                        detail = searchSource.name;
+                } else {
+                    let searchMatch = Global.searchMatchManager.getActiveItem();
+
+                    primary = _("Results for \"%s\"").format(string);
+                    if (searchMatch.id == 'title')
+                        detail = _("filtered by title");
+                    else if (searchMatch.id == 'author')
+                        detail = _("filtered by author");
+                }
+            }
+        } else {
+            let length = Global.selectionController.getSelection().length;
 
-                detail = _("%d of %d").format(curPage + 1, totPages);
+            if (length == 0)
+                detail = _("Click on items to select them");
+            else
+                detail = Gettext.ngettext("%d selected",
+                                          "%d selected",
+                                          length).format(length);
+
+            if (activeCollection) {
+                primary = activeCollection.name;
+            } else if (length != 0) {
+                primary = detail;
+                detail = null;
             }
         }
 
@@ -233,26 +212,23 @@ const MainToolbar = new Lang.Class({
                                                Lang.bind(this, this._setToolbarTitle));
     },
 
-    _populateForPreview: function(model) {
-        //back button, on the left of the toolbar
-        let iconName =
-            (this.widget.get_direction() == Gtk.TextDirection.RTL) ?
-            'go-next-symbolic' : 'go-previous-symbolic';
-
-        let backButton =
-            this.widget.add_button(iconName, _("Back"), true);
-        backButton.connect('clicked', Lang.bind(this,
-            function() {
-                Global.documentManager.setActiveItem(null);
-            }));
+    _onActiveCollectionChanged: function() {
+        let item = Global.collectionManager.getActiveItem();
 
-        // menu button, on the right of the toolbar
-        let menuModel = new Gio.Menu();
-        menuModel.append_item(Gio.MenuItem.new(_("Open"), 'app.open-current'));
-        menuModel.append_item(Gio.MenuItem.new(_("Print"), 'app.print-current'));
+        if (item && !this._collBackButton) {
+            this._collBackButton =
+                this.widget.add_button('go-previous-symbolic', _("Back"), true);
+            this._collBackButton.connect('clicked', Lang.bind(this,
+                function() {
+                    Global.collectionManager.setActiveItem(null);
+                }));
+        } else if (!item && this._collBackButton) {
+            this._collBackButton.destroy();
+            this._collBackButton = null;
+        }
 
-        let menuButton = this.widget.add_menu('emblem-system-symbolic', null, false);
-        menuButton.set_menu_model(menuModel);
+        this._setToolbarTitle();
+        this._searchbar.hide();
     },
 
     _populateForOverview: function() {
@@ -270,87 +246,56 @@ const MainToolbar = new Lang.Class({
         this._onActiveCollectionChanged();
     },
 
-    _onActiveCollectionChanged: function() {
-        let item = Global.collectionManager.getActiveItem();
+    _clearStateData: function() {
+        this._collBackButton = null;
 
-        if (item && !this._collBackButton) {
-            this._collBackButton =
-                this.widget.add_button('go-previous-symbolic', _("Back"), true);
-            this._collBackButton.connect('clicked', Lang.bind(this,
-                function() {
-                    Global.collectionManager.setActiveItem(null);
-                }));
-        } else if (!item && this._collBackButton) {
-            this._collBackButton.destroy();
-            this._collBackButton = null;
+        if (this._collectionId != 0) {
+            Global.collectionManager.disconnect(this._collectionId);
+            this._collectionId = 0;
         }
 
-        this._setToolbarTitle();
-        this.searchbar.hide();
+        if (this._selectionChangedId != 0) {
+            Global.selectionController.disconnect(this._selectionChangedId);
+            this._selectionChangedId = 0;
+        }
     },
 
-    _resetToolbarMode: function() {
-        this._clearToolbar();
-
-        let windowMode = Global.modeController.getWindowMode();
-        if (windowMode == WindowMode.WindowMode.OVERVIEW) {
-            let selectionMode = Global.selectionController.getSelectionMode();
-            if (selectionMode)
-                this._populateForSelectionMode();
-            else
-                this._populateForOverview();
-        } else if (windowMode == WindowMode.WindowMode.PREVIEW) {
-            this._populateForPreview();
-        }
+    _clearToolbar: function() {
+        this._clearStateData();
 
-        this._setToolbarTitle();
-        this.widget.show_all();
+        this.widget.get_style_context().remove_class('documents-selection-mode');
+        this.widget.reset_style();
+        this.widget.clear();
     },
 
-    setModel: function(model) {
-        if (!model)
-            return;
+    _resetToolbarMode: function() {
+        this._clearToolbar();
 
-        this._model = model;
-        this._model.connect('page-changed', Lang.bind(this,
-            function() {
-                this._setToolbarTitle();
-            }));
+        let selectionMode = Global.selectionController.getSelectionMode();
+        if (selectionMode)
+            this._populateForSelectionMode();
+        else
+            this._populateForOverview();
 
         this._setToolbarTitle();
-    }
-});
-
-const OverviewToolbar = new Lang.Class({
-    Name: 'OverviewToolbar',
-    Extends: MainToolbar,
+        this.widget.show_all();
 
-    _init: function(overlayLayout) {
-        this.parent();
+        if (Global.searchController.getString() != '')
+            this._searchbar.show();
+    },
 
+    createSearchbar: function() {
         // create the dropdown for the search bar, it's hidden by default
         let dropdown = new Searchbar.Dropdown();
-        overlayLayout.add(dropdown.actor,
+        this._overlayLayout.add(dropdown.actor,
             Clutter.BinAlignment.CENTER, Clutter.BinAlignment.FIXED);
         dropdown.actor.add_constraint(
             new Clutter.BindConstraint({ source: this.toolbarActor,
                                          coordinate: Clutter.BindCoordinate.Y }));
 
-        this.searchbar = new Searchbar.OverviewSearchbar(dropdown);
+        this._searchbar = new Searchbar.OverviewSearchbar(dropdown);
         this.layout.pack_start = true;
-        this.layout.pack(this.searchbar.actor, false, true, false,
+        this.layout.pack(this._searchbar.actor, false, true, false,
                          Clutter.BoxAlignment.CENTER, Clutter.BoxAlignment.START);
-    },
-
-    _resetToolbarMode: function() {
-        this.parent();
-
-        let mode = Global.modeController.getWindowMode();
-
-        if (mode == WindowMode.WindowMode.PREVIEW)
-            this.searchbar.hide();
-        else if (mode == WindowMode.WindowMode.OVERVIEW &&
-                 Global.searchController.getString() != '')
-            this.searchbar.show();
     }
 });
diff --git a/src/mainWindow.js b/src/mainWindow.js
index 7125a0b..1c8a7b6 100644
--- a/src/mainWindow.js
+++ b/src/mainWindow.js
@@ -140,6 +140,16 @@ const MainWindow = new Lang.Class({
     },
 
     _onKeyPressEvent: function(widget, event) {
+        let toolbar = this._embed.getMainToolbar();
+
+        if (Utils.isSearchEvent(event)) {
+            toolbar.toggleSearch();
+            return true;
+        }
+
+        if (toolbar.handleEvent(event))
+            return true;
+
         if (Global.modeController.getWindowMode() == WindowMode.WindowMode.PREVIEW)
             return this._handleKeyPreview(event);
         else
@@ -156,7 +166,6 @@ const MainWindow = new Lang.Class({
             ((state & Gdk.ModifierType.MOD1_MASK) != 0 &&
              (direction == Gtk.TextDirection.LTR && keyval == Gdk.KEY_Left) ||
              (direction == Gtk.TextDirection.RTL && keyval == Gdk.KEY_Right)) ||
-            keyval == Gdk.KEY_BackSpace ||
             keyval == Gdk.KEY_Back) {
             Global.documentManager.setActiveItem(null);
             return true;
@@ -168,14 +177,6 @@ const MainWindow = new Lang.Class({
     _handleKeyOverview: function(event) {
         let keyval = event.get_keyval()[1];
 
-        if (Utils.isSearchEvent(event)) {
-            this._embed._toolbar.searchbar.toggle();
-            return true;
-        }
-
-        if (this._embed._toolbar.searchbar.deliverEvent(event))
-            return true;
-
         if (Global.selectionController.getSelectionMode() &&
             keyval == Gdk.KEY_Escape) {
             Global.selectionController.setSelectionMode(false);
diff --git a/src/preview.js b/src/preview.js
index 1fc397d..124f5b1 100644
--- a/src/preview.js
+++ b/src/preview.js
@@ -23,8 +23,10 @@ const Clutter = imports.gi.Clutter;
 const EvView = imports.gi.EvinceView;
 const Gd = imports.gi.Gd;
 const Gdk = imports.gi.Gdk;
+const Gio = imports.gi.Gio;
 const Gtk = imports.gi.Gtk;
 const GtkClutter = imports.gi.GtkClutter;
+const _ = imports.gettext.gettext;
 
 const Lang = imports.lang;
 const Mainloop = imports.mainloop;
@@ -32,6 +34,7 @@ const Mainloop = imports.mainloop;
 const Global = imports.global;
 const Tweener = imports.util.tweener;
 const MainToolbar = imports.mainToolbar;
+const Searchbar = imports.searchbar;
 const View = imports.view;
 
 const _FULLSCREEN_TOOLBAR_TIMEOUT = 2; // seconds
@@ -312,3 +315,166 @@ const PreviewFullscreenToolbar = new Lang.Class({
                            transition: 'easeOutQuad' });
     }
 });
+
+const PreviewToolbar = new Lang.Class({
+    Name: 'PreviewToolbar',
+    Extends: MainToolbar.MainToolbar,
+
+    _init: function(previewView) {
+        this._previewView = previewView;
+
+        this.parent();
+
+        // back button, on the left of the toolbar
+        let iconName =
+            (this.widget.get_direction() == Gtk.TextDirection.RTL) ?
+            'go-next-symbolic' : 'go-previous-symbolic';
+        let backButton =
+            this.widget.add_button(iconName, _("Back"), true);
+        backButton.connect('clicked', Lang.bind(this,
+            function() {
+                Global.documentManager.setActiveItem(null);
+            }));
+
+        // menu button, on the right of the toolbar
+        let menuModel = new Gio.Menu();
+        menuModel.append_item(Gio.MenuItem.new(_("Open"), 'app.open-current'));
+        menuModel.append_item(Gio.MenuItem.new(_("Print"), 'app.print-current'));
+
+        let menuButton = this.widget.add_menu('emblem-system-symbolic', null, false);
+        menuButton.set_menu_model(menuModel);
+
+        this._setToolbarTitle();
+        this.widget.show_all();
+    },
+
+    createSearchbar: function() {
+        this._searchbar = new PreviewSearchbar(this._previewView);
+        this.layout.pack_start = false;
+        this.layout.pack(this._searchbar.actor, false, true, false,
+                         Clutter.BoxAlignment.CENTER, Clutter.BoxAlignment.START);
+    },
+
+    _setToolbarTitle: function() {
+        let doc = Global.documentManager.getActiveItem();
+        let primary = doc.name;
+        let detail = null;
+
+        if (this._model) {
+            let curPage, totPages;
+
+            curPage = this._model.get_page();
+            totPages = this._model.get_document().get_n_pages();
+
+            detail = _("%d of %d").format(curPage + 1, totPages);
+        }
+
+        if (detail)
+            detail = '(' + detail + ')';
+
+        this.widget.set_labels(primary, detail);
+    },
+
+    setModel: function(model) {
+        if (!model)
+            return;
+
+        this._model = model;
+        this._model.connect('page-changed', Lang.bind(this,
+            function() {
+                this._setToolbarTitle();
+            }));
+
+        this._setToolbarTitle();
+    }
+});
+
+const PreviewSearchbar = new Lang.Class({
+    Name: 'PreviewSearchbar',
+    Extends: Searchbar.Searchbar,
+
+    _init: function(previewView) {
+        this.parent();
+
+        this._previewView = previewView;
+    },
+
+    createSearchWidgets: function() {
+        this._searchContainer = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL,
+                                              spacing: 6 });
+
+        this._searchEntry = new Gtk.SearchEntry({ hexpand: true });
+        this._searchEntry.connect('activate', Lang.bind(this, this._searchNext));
+        this._searchContainer.add(this._searchEntry);
+
+        let controlsBox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL });
+        controlsBox.get_style_context().add_class('linked');
+        controlsBox.get_style_context().add_class('raised');
+        this._searchContainer.add(controlsBox);
+
+        let prev = new Gtk.Button();
+        prev.connect('clicked', Lang.bind(this, this._searchPrev));
+        prev.set_image(new Gtk.Image({ icon_name: 'go-up-symbolic',
+                                       icon_size: Gtk.IconSize.MENU,
+                                       margin: 2 }));
+        prev.set_tooltip_text(_("Find Previous"));
+        controlsBox.add(prev);
+
+        let next = new Gtk.Button();
+        next.connect('clicked', Lang.bind(this, this._searchNext));
+        next.set_image(new Gtk.Image({ icon_name: 'go-down-symbolic',
+                                       icon_size: Gtk.IconSize.MENU,
+                                       margin: 2 }));
+        next.set_tooltip_text(_("Find Next"));
+        controlsBox.add(next);
+    },
+
+    entryChanged: function() {
+        this._previewView.view.find_search_changed();
+        this._startSearch();
+    },
+
+    show: function() {
+        this.parent();
+
+        this._previewView.view.find_set_highlight_search(true);
+        this._startSearch();
+    },
+
+    hide: function() {
+        this.parent();
+
+        this._previewView.view.find_set_highlight_search(false);
+    },
+
+    _startSearch: function() {
+        let model = this._previewView.getModel();
+        if (!model)
+            return;
+
+        let str = this._searchEntry.get_text();
+        if (!str)
+            return;
+
+        let evDoc = model.get_document();
+        let job = EvView.JobFind.new(evDoc, model.get_page(), evDoc.get_n_pages(),
+                                     str, false);
+        job.connect('updated', Lang.bind(this, this._onSearchJobUpdated));
+
+        job.scheduler_push_job(EvView.JobPriority.PRIORITY_NONE);
+    },
+
+    _searchPrev: function() {
+        this._previewView.view.find_previous();
+    },
+
+    _searchNext: function() {
+        this._previewView.view.find_next();
+    },
+
+    _onSearchJobUpdated: function(job, page) {
+        // FIXME: ev_job_find_get_results() returns a GList **
+        // and thus is not introspectable
+        Gd.ev_view_find_changed(this._previewView.view, job, page);
+    }
+});



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