[gnome-documents] selections: first pass at implementing a "selection" mode



commit c47df070689d586bfedfbd79b35dc6000ba3448f
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Thu Oct 27 17:44:39 2011 -0400

    selections: first pass at implementing a "selection" mode
    
    The "selection" mode is entered with a button on the main toolbar or by
    right clicking on any item. An OSD toolbar will appear when one or more
    items are selected, offering an accessible context-menu like list of
    actions (which are not yet wired).

 src/Makefile-js.am         |    2 +-
 src/application.js         |    4 +-
 src/embed.js               |    1 +
 src/iconView.js            |   14 ++-
 src/lib/gd-utils.c         |   47 ++++++++
 src/lib/gd-utils.h         |    2 +
 src/listView.js            |   11 ++-
 src/mainToolbar.js         |  100 +++++++++++++++++-
 src/mainWindow.js          |   49 +++++++--
 src/selectionController.js |   55 ---------
 src/selections.js          |  261 ++++++++++++++++++++++++++++++++++++++++++++
 src/sidebar.js             |   15 +++-
 src/view.js                |   92 +++++++++-------
 13 files changed, 534 insertions(+), 119 deletions(-)
---
diff --git a/src/Makefile-js.am b/src/Makefile-js.am
index 25fce67..a0828e6 100644
--- a/src/Makefile-js.am
+++ b/src/Makefile-js.am
@@ -22,7 +22,7 @@ dist_js_DATA = \
     preview.js \
     query.js \
     searchbar.js \
-    selectionController.js \
+    selections.js \
     sidebar.js \
     sources.js \
     spinnerBox.js \
diff --git a/src/application.js b/src/application.js
index e42b53d..b5d4d6f 100644
--- a/src/application.js
+++ b/src/application.js
@@ -46,7 +46,7 @@ const MainWindow = imports.mainWindow;
 const OffsetController = imports.offsetController;
 const Path = imports.path;
 const Query = imports.query;
-const SelectionController = imports.selectionController;
+const Selections = imports.selections;
 const Sources = imports.sources;
 const TrackerController = imports.trackerController;
 const Tweener = imports.util.tweener;
@@ -144,7 +144,7 @@ Application.prototype = {
                         }
 
                         Global.sourceManager = new Sources.SourceManager();
-                        Global.selectionController = new SelectionController.SelectionController();
+                        Global.selectionController = new Selections.SelectionController();
                         Global.queryBuilder = new Query.QueryBuilder();
                         Global.changeMonitor = new ChangeMonitor.TrackerChangeMonitor();
                         Global.collectionManager = new Collections.CollectionManager();
diff --git a/src/embed.js b/src/embed.js
index f737eeb..9eb4b4f 100644
--- a/src/embed.js
+++ b/src/embed.js
@@ -54,6 +54,7 @@ ViewEmbed.prototype  = {
         this._viewSettingsId = 0;
 
         this.widget = new Gtk.Grid({ orientation: Gtk.Orientation.VERTICAL });
+        this.actor = new GtkClutter.Actor({ contents: this.widget });
 
         this._toolbar = new MainToolbar.MainToolbar();
         this.widget.add(this._toolbar.widget);
diff --git a/src/iconView.js b/src/iconView.js
index 8531ce8..f9d7fbc 100644
--- a/src/iconView.js
+++ b/src/iconView.js
@@ -46,16 +46,10 @@ IconView.prototype = {
         this.widget.item_width = _VIEW_ITEM_WIDTH;
         this.widget.column_spacing = _VIEW_COLUMN_SPACING;
         this.widget.margin = _VIEW_MARGIN;
-        this.widget.set_selection_mode(Gtk.SelectionMode.MULTIPLE);
 
         this.widget.connect('item-activated',
                             Lang.bind(this, this._onItemActivated));
 
-        this.widget.connect('button-press-event',
-                            Lang.bind(this, this._onButtonPressEvent));
-
-        this.widget.show();
-
         // chain up to the parent
         View.View.prototype._init.call(this);
     },
@@ -76,6 +70,14 @@ IconView.prototype = {
         return this.widget.get_path_at_pos(position[0], position[1]);
     },
 
+    setSelectionMode: function(mode) {
+        this.getSelectionObject().set_selection_mode(mode);
+    },
+
+    setSingleClickMode: function(mode) {
+        Gd.gtk_icon_view_set_activate_on_single_click(this.widget, mode);
+    },
+
     scrollToPath: function(path) {
         this.widget.scroll_to_path(path, false, 0, 0);
     },
diff --git a/src/lib/gd-utils.c b/src/lib/gd-utils.c
index a0aaba1..e9eedd8 100644
--- a/src/lib/gd-utils.c
+++ b/src/lib/gd-utils.c
@@ -262,6 +262,53 @@ gd_gtk_tree_view_set_activate_on_single_click (GtkTreeView *tree_view,
 	}
 }
 
+static gboolean 
+icon_view_button_press_callback (GtkWidget *icon_view,
+				 GdkEventButton *event,
+				 gpointer data)
+{
+  GtkTreePath *path;
+
+  if (event->button == 1 && event->type == GDK_BUTTON_PRESS) {
+    path = gtk_icon_view_get_path_at_pos (GTK_ICON_VIEW (icon_view),
+                                          event->x, event->y);
+
+    if (path != NULL) {
+      gtk_icon_view_item_activated (GTK_ICON_VIEW (icon_view), path);
+      gtk_tree_path_free (path);
+    }
+  }
+
+  return FALSE;
+}
+
+void
+gd_gtk_icon_view_set_activate_on_single_click (GtkIconView *icon_view,
+                                               gboolean should_activate)
+{
+  guint button_press_id;
+
+  button_press_id = GPOINTER_TO_UINT 
+    (g_object_get_data (G_OBJECT (icon_view), 
+                        "gd-icon-view-activate"));
+
+  if (button_press_id && !should_activate) {
+    g_signal_handler_disconnect (icon_view, button_press_id);
+    g_object_set_data (G_OBJECT (icon_view), 
+                       "gd-icon-view-activate", 
+                       NULL);
+  } else if (!button_press_id && should_activate) {
+    button_press_id = 
+      g_signal_connect (icon_view,
+                        "button_press_event",
+                        G_CALLBACK (icon_view_button_press_callback),
+                        NULL);
+    g_object_set_data (G_OBJECT (icon_view), 
+                       "gd-icon-view-activate", 
+                       GUINT_TO_POINTER (button_press_id));
+  }
+}
+
 /* utility to stretch a frame to the desired size */
 
 static void
diff --git a/src/lib/gd-utils.h b/src/lib/gd-utils.h
index ba11267..fca249d 100644
--- a/src/lib/gd-utils.h
+++ b/src/lib/gd-utils.h
@@ -57,6 +57,8 @@ gboolean gd_queue_thumbnail_job_for_file_finish (GAsyncResult *res);
 
 void gd_gtk_tree_view_set_activate_on_single_click (GtkTreeView *tree_view,
                                                     gboolean should_activate);
+void gd_gtk_icon_view_set_activate_on_single_click (GtkIconView *icon_view,
+                                                    gboolean should_activate);
 
 GdkPixbuf * gd_embed_image_in_frame (GdkPixbuf *source_image,
                                      GdkPixbuf *frame_image,
diff --git a/src/listView.js b/src/listView.js
index c1a314b..a8ed48c 100644
--- a/src/listView.js
+++ b/src/listView.js
@@ -46,9 +46,6 @@ ListView.prototype = {
 
         this.widget.show();
 
-        let selection = this.widget.get_selection();
-        selection.set_mode(Gtk.SelectionMode.MULTIPLE);
-
         // chain up to the parent
         View.View.prototype._init.call(this);
     },
@@ -77,6 +74,14 @@ ListView.prototype = {
         this.widget.scroll_to_cell(path, null, false, 0, 0);
     },
 
+    setSelectionMode: function(mode) {
+        this.getSelectionObject().set_mode(mode);
+    },
+
+    setSingleClickMode: function(mode) {
+        Gd.gtk_tree_view_set_activate_on_single_click(this.widget, mode);
+    },
+
     createRenderers: function() {
         let col = new Gtk.TreeViewColumn();
         this.widget.append_column(col);
diff --git a/src/mainToolbar.js b/src/mainToolbar.js
index 2a007dd..c5a1ef9 100644
--- a/src/mainToolbar.js
+++ b/src/mainToolbar.js
@@ -41,10 +41,14 @@ MainToolbar.prototype = {
     _init: function() {
         this._model = null;
         this._whereId = 0;
+        this._selectionChangedId = 0;
 
         this.widget = new Gtk.Toolbar({ icon_size: Gtk.IconSize.MENU });
         this.widget.get_style_context().add_class(Gtk.STYLE_CLASS_MENUBAR);
 
+        this._selectionModeId =
+            Global.selectionController.connect('selection-mode-changed',
+                                               Lang.bind(this, this._onSelectionModeChanged));
         this._windowModeId =
             Global.modeController.connect('window-mode-changed',
                                           Lang.bind(this, this._onWindowModeChanged));
@@ -59,12 +63,67 @@ MainToolbar.prototype = {
             this._whereId = 0;
         }
 
+        if (this._selectionChangedId != 0) {
+            Global.selectionController.disconnect(this._selectionChangedId);
+            this._selectionChangedId = 0;
+        }
+
         this.widget.foreach(Lang.bind(this, function(widget) {
             widget.destroy();
         }));
     },
 
+    _updateSelectionLabel: function() {
+        let length = Global.selectionController.getSelection().length;
+
+        if (length == 0)
+            this._selectionLabel.set_markup('<i>' + _("Click on items to select them") + '</i>');
+        else
+            this._selectionLabel.set_markup('<b>' + _("%d selected").format(length) + '</b>');
+    },
+
+    _populateForSelectionMode: function() {
+        this.widget.set_style(Gtk.ToolbarStyle.TEXT);
+
+        let selectAll = new Gtk.ToolButton({ stock_id: 'gtk-select-all' });
+        selectAll.get_style_context().add_class('raised');
+        this.widget.insert(selectAll, 0);
+
+        selectAll.connect('clicked', Lang.bind(this,
+            function() {
+                Global.selectionController.selectAll();
+            }));
+
+        this._selectionLabel = new Gtk.Label();
+
+        let labelItem = new Gtk.ToolItem({ child: this._selectionLabel });
+        labelItem.set_expand(true);
+        this.widget.insert(labelItem, 1);
+
+        let cancel = new Gtk.ToolButton({ stock_id: 'gtk-cancel' });
+        cancel.get_style_context().add_class('raised');
+        this.widget.insert(cancel, 2);
+
+        cancel.connect('clicked', Lang.bind(this,
+            function() {
+                Global.selectionController.setSelectionMode(false);
+            }));
+
+        let sizeGroup = new Gtk.SizeGroup();
+        sizeGroup.add_widget(cancel);
+        sizeGroup.add_widget(selectAll);
+
+        this._selectionChangedId =
+            Global.selectionController.connect('selection-changed',
+                                               Lang.bind(this, this._updateSelectionLabel));
+        this._updateSelectionLabel();
+
+        this.widget.show_all();
+    },
+
     _populateForOverview: function() {
+        this.widget.set_style(Gtk.ToolbarStyle.ICONS);
+
         let iconView = new Gtk.ToggleButton({ child: new Gtk.Image({ icon_name: 'view-grid-symbolic',
                                                                      pixel_size: 16 }) });
         iconView.get_style_context().add_class('linked');
@@ -97,6 +156,22 @@ MainToolbar.prototype = {
         item2.add(this._whereLabel);
         this.widget.insert(item2, 1);
 
+        let separator = new Gtk.SeparatorToolItem({ draw: false });
+        separator.set_expand(true);
+        this.widget.insert(separator, 2);
+
+        let item3 = new Gtk.ToggleToolButton({ icon_name: 'emblem-default-symbolic' });
+        item3.get_style_context().add_class('raised');
+        this.widget.insert(item3, 3);
+
+        item3.connect('toggled', Lang.bind(this,
+            function(button) {
+                let isToggled = button.get_active();
+                Global.selectionController.setSelectionMode(isToggled);
+            }));
+        // set initial state
+        item3.set_active(Global.selectionController.getSelectionMode());
+
         this._whereId =
             Global.sideFilterController.connect('changed',
                                                 Lang.bind(this, this._onSideFilterChanged));
@@ -106,6 +181,8 @@ MainToolbar.prototype = {
     },
 
     _populateForPreview: function(model) {
+        this.widget.set_style(Gtk.ToolbarStyle.ICONS);
+
         let back = new Gtk.ToolButton({ icon_name: 'go-previous-symbolic' });
         back.get_style_context().add_class('raised');
         this.widget.insert(back, 0);
@@ -186,6 +263,18 @@ MainToolbar.prototype = {
             this._populateForPreview();
     },
 
+    _onSelectionModeChanged: function(controller, mode) {
+        if (Global.modeController.getWindowMode() != WindowMode.WindowMode.OVERVIEW)
+            return;
+
+        this._clearToolbar();
+
+        if (mode)
+            this._populateForSelectionMode();
+        else
+            this._populateForOverview();
+    },
+
     setModel: function(model) {
         if (!model)
             return;
@@ -200,7 +289,16 @@ MainToolbar.prototype = {
     },
 
     destroy: function() {
-        Global.modeController.disconnect(this._windowModeId);
+        if (this._windowModeId != 0) {
+            Global.modeController.disconnect(this._windowModeId);
+            this._windowModeId = 0;
+        }
+
+        if (this._selectionModeId != 0) {
+            Global.selectionController.disconnect(this._selectionModeId);
+            this._selectionModeId = 0;
+        }
+
         this.widget.destroy();
     }
 };
diff --git a/src/mainWindow.js b/src/mainWindow.js
index 9b53cd3..8d645fc 100644
--- a/src/mainWindow.js
+++ b/src/mainWindow.js
@@ -31,6 +31,7 @@ const Mainloop = imports.mainloop;
 const Embed = imports.embed;
 const Global = imports.global;
 const Searchbar = imports.searchbar;
+const Selections = imports.selections;
 const Sidebar = imports.sidebar;
 const Utils = imports.utils;
 const WindowMode = imports.windowMode;
@@ -38,6 +39,7 @@ const WindowMode = imports.windowMode;
 const _ = imports.gettext.gettext;
 
 const _CONFIGURE_ID_TIMEOUT = 100; // msecs
+const _OSD_TOOLBAR_SPACING = 60;
 
 function MainWindow() {
     this._init();
@@ -106,21 +108,46 @@ MainWindow.prototype = {
         this._clutterBox.add_actor(this._searchbar.actor);
         this._clutterBoxLayout.set_fill(this._searchbar.actor, true, false);
 
-        // second child: the actual sidebar + embed, filling both axis
-        // and expanding
-        this._grid = new Gtk.Grid({ orientation: Gtk.Orientation.HORIZONTAL });
-        this._gridActor = new GtkClutter.Actor({ contents: this._grid });
-        this._clutterBox.add_actor(this._gridActor);
-        this._clutterBoxLayout.set_expand(this._gridActor, true);
-        this._clutterBoxLayout.set_fill(this._gridActor, true, true);
+        // second child: an horizontal layout box which will
+        // contain the sidebar and the embed
+        this._horizLayout = new Clutter.BoxLayout();
+        this._horizBox = new Clutter.Box({ layout_manager: this._horizLayout });
+        this._clutterBox.add_actor(this._horizBox);
+        this._clutterBoxLayout.set_expand(this._horizBox, true);
+        this._clutterBoxLayout.set_fill(this._horizBox, true, true);
 
+        // create the sidebar and pack it as a first child into the
+        // horizontal box
         this._sidebar = new Sidebar.Sidebar();
-        this._grid.add(this._sidebar.widget);
+        this._horizBox.add_actor(this._sidebar.actor);
+        this._horizLayout.set_fill(this._sidebar.actor, false, true);
 
+        // create the embed and pack it as the second child into
+        // the horizontal box
         this._embed = new Embed.ViewEmbed();
-        this._grid.add(this._embed.widget);
+        this._horizBox.add_actor(this._embed.actor);
+        this._horizLayout.set_expand(this._embed.actor, true);
+        this._horizLayout.set_fill(this._embed.actor, true, true);
+
+        // create the OSD toolbar for selected items, it's hidden by default
+        this._selectionToolbar = new Selections.SelectionToolbar();
+        this._selectionToolbar.actor.add_constraint(
+            new Clutter.AlignConstraint({ align_axis: Clutter.AlignAxis.X_AXIS,
+                                          source: this._embed.actor,
+                                          factor: 0.50 }));
+        let yConstraint =
+            new Clutter.BindConstraint({ source: this._embed.actor,
+                                         coordinate: Clutter.BindCoordinate.Y,
+                                         offset: this._embed.actor.height - _OSD_TOOLBAR_SPACING });
+        this._selectionToolbar.actor.add_constraint(yConstraint);
+
+        // refresh the constraint offset when the height of the embed actor changes
+        this._embed.actor.connect("notify::height", Lang.bind(this,
+            function() {
+                yConstraint.set_offset(this._embed.actor.height - _OSD_TOOLBAR_SPACING);
+            }));
 
-        this._grid.show_all();
+        Global.stage.add_actor(this._selectionToolbar.actor);
     },
 
     _saveWindowGeometry: function() {
diff --git a/src/selections.js b/src/selections.js
new file mode 100644
index 0000000..21ef32f
--- /dev/null
+++ b/src/selections.js
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * Gnome Documents 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.
+ *
+ * Gnome Documents 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 Gnome Documents; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Author: Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+const Gtk = imports.gi.Gtk;
+const GtkClutter = imports.gi.GtkClutter;
+const _ = imports.gettext.gettext;
+
+const Global = imports.global;
+const Tweener = imports.util.tweener;
+
+const Lang = imports.lang;
+const Signals = imports.signals;
+
+function SelectionController() {
+    this._init();
+};
+
+SelectionController.prototype = {
+    _init: function() {
+        this._selection = [];
+        this._selectionMode = false;
+    },
+
+    selectAll: function() {
+        this.emit('select-all');
+    },
+
+    setSelection: function(selection) {
+        if (this._isFreezed)
+            return;
+
+        if (!selection)
+            this._selection = [];
+        else
+            this._selection = selection;
+
+        this.emit('selection-changed', this._selection);
+    },
+
+    getSelection: function() {
+        return this._selection;
+    },
+
+    freezeSelection: function(freeze) {
+        if (freeze == this._isFreezed)
+            return;
+
+        this._isFreezed = freeze;
+
+        if (!this._isFreezed)
+            this.emit('selection-check');
+    },
+
+    setSelectionMode: function(setting) {
+        if (this._selectionMode == setting)
+            return;
+
+        this._selectionMode = setting;
+        this.emit('selection-mode-changed', this._selectionMode);
+    },
+
+    getSelectionMode: function() {
+        return this._selectionMode;
+    }
+};
+Signals.addSignalMethods(SelectionController.prototype);
+
+function SelectionToolbar() {
+    this._init();
+}
+
+SelectionToolbar.prototype = {
+    _init: function() {
+        this._itemListeners = {};
+        this._insideRefresh = false;
+
+        this.widget = new Gtk.Toolbar({ show_arrow: false,
+                                        icon_size: Gtk.IconSize.MENU });
+        this.actor = new GtkClutter.Actor({ contents: this.widget,
+                                            show_on_set_parent: false,
+                                            opacity: 0 });
+
+        this._toolbarFavorite = new Gtk.ToggleToolButton({ icon_name: 'emblem-favorite-symbolic' });
+        this.widget.insert(this._toolbarFavorite, 0);
+        this._toolbarFavorite.connect('clicked', Lang.bind(this, this._onToolbarFavorite));
+
+        let separator = new Gtk.SeparatorToolItem();
+        separator.show();
+        this.widget.insert(separator, 1);
+
+        this._toolbarOpen = new Gtk.ToolButton({ icon_name: 'document-open-symbolic' });
+        this.widget.insert(this._toolbarOpen, 2);
+        this._toolbarOpen.connect('clicked', Lang.bind(this, this._onToolbarOpen));
+
+        this.widget.show();
+
+        Global.selectionController.connect('selection-mode-changed',
+                                           Lang.bind(this, this._onSelectionModeChanged));
+        Global.selectionController.connect('selection-changed',
+                                           Lang.bind(this, this._onSelectionChanged));
+    },
+
+    _onSelectionModeChanged: function(controller, mode) {
+        if (mode)
+            this._onSelectionChanged();
+        else
+            this._fadeOut();
+    },
+
+    _onSelectionChanged: function() {
+        if (!Global.selectionController.getSelectionMode())
+            return;
+
+        let selection = Global.selectionController.getSelection();
+        this._setItemListeners(selection);
+
+        if (selection.length > 0) {
+            this._setItemVisibility();
+            this._fadeIn();
+        } else {
+            this._fadeOut();
+        }
+    },
+
+    _setItemListeners: function(selection) {
+        for (idx in this._itemListeners) {
+            let doc = this._itemListeners[idx];
+            doc.disconnect(idx);
+            delete this._itemListeners[idx];
+        }
+
+        selection.forEach(Lang.bind(this,
+            function(urn) {
+                let doc = Global.documentManager.getItemById(urn);
+                let id = doc.connect('info-updated', Lang.bind(this, this._setItemVisibility));
+                this._itemListeners[id] = doc;
+            }));
+    },
+
+    _setItemVisibility: function() {
+        let apps = [];
+        let favCount = 0;
+        let showFavorite = true;
+
+        this._insideRefresh = true;
+
+        let selection = Global.selectionController.getSelection();
+        selection.forEach(Lang.bind(this,
+            function(urn) {
+                let doc = Global.documentManager.getItemById(urn);
+
+                if (doc.favorite)
+                    favCount++;
+
+                if (apps.indexOf(doc.defaultAppName) == -1)
+                    apps.push(doc.defaultAppName);
+            }));
+
+        showFavorite &= ((favCount == 0) || (favCount == selection.length));
+
+        let openLabel = null;
+        if (apps.length == 1) {
+            // Translators: this is the Open action in a context menu
+            openLabel = _("Open with %s").format(apps[0]);
+        } else {
+            // Translators: this is the Open action in a context menu
+            openLabel = _("Open");
+        }
+
+        this._toolbarOpen.set_tooltip_text(openLabel);
+        this._toolbarOpen.show();
+
+        if (showFavorite) {
+            let isFavorite = (favCount == selection.length);
+            let favoriteLabel = '';
+
+            if (isFavorite) {
+                favoriteLabel = _("Remove from favorites");
+                this._toolbarFavorite.set_active(true);
+                this._toolbarFavorite.get_style_context().add_class('favorite');
+            } else {
+                favoriteLabel = _("Add to favorites");
+                this._toolbarFavorite.set_active(false);
+                this._toolbarFavorite.get_style_context().remove_class('favorite');
+            }
+
+            this._toolbarFavorite.reset_style();
+            this._toolbarFavorite.set_tooltip_text(favoriteLabel);
+            this._toolbarFavorite.show();
+        } else {
+            this._toolbarFavorite.hide();
+        }
+
+        this._insideRefresh = false;
+    },
+
+    _onToolbarOpen: function(widget) {
+        let selection = Global.selectionController.getSelection();
+
+        selection.forEach(Lang.bind(this,
+            function(urn) {
+                let doc = Global.documentManager.getItemById(urn);
+                doc.open(widget.get_screen(), Gtk.get_current_event_time());
+            }));
+    },
+
+    _onToolbarFavorite: function(widget) {
+        if (this._insideRefresh)
+            return;
+
+        let selection = Global.selectionController.getSelection();
+
+        selection.forEach(Lang.bind(this,
+            function(urn) {
+                let doc = Global.documentManager.getItemById(urn);
+                doc.setFavorite(!doc.favorite);
+            }));
+    },
+
+    _fadeIn: function() {
+        if (this.actor.opacity != 0)
+            return;
+
+        this.actor.opacity = 0;
+        this.actor.show();
+
+        Tweener.addTween(this.actor,
+            { opacity: 255,
+              time: 0.30,
+              transition: 'easeOutQuad' });
+    },
+
+    _fadeOut: function() {
+        Tweener.addTween(this.actor,
+            { opacity: 0,
+              time: 0.30,
+              transition: 'easeOutQuad',
+              onComplete: function() {
+                  this.actor.hide();
+              },
+              onCompleteScope: this });
+    }
+};
diff --git a/src/sidebar.js b/src/sidebar.js
index 2627376..103346d 100644
--- a/src/sidebar.js
+++ b/src/sidebar.js
@@ -19,8 +19,10 @@
  *
  */
 
+const Clutter = imports.gi.Clutter;
 const Gd = imports.gi.Gd;
 const Gtk = imports.gi.Gtk;
+const GtkClutter = imports.gi.GtkClutter;
 const Pango = imports.gi.Pango;
 const _ = imports.gettext.gettext;
 
@@ -29,6 +31,7 @@ const Signals = imports.signals;
 
 const Global = imports.global;
 const Sources = imports.sources;
+const Tweener = imports.util.tweener;
 const WindowMode = imports.windowMode;
 
 const _SIDEBAR_WIDTH_REQUEST = 240;
@@ -404,6 +407,8 @@ Sidebar.prototype = {
         this.widget = new Gtk.Notebook({ show_tabs: false });
         this.widget.get_style_context().add_class(Gtk.STYLE_CLASS_SIDEBAR);
 
+        this.actor = new GtkClutter.Actor({ contents: this.widget });
+
         this._sourceView = new Sources.SourceView();
         this.widget.insert_page(this._sourceView.widget, null, _SIDEBAR_SOURCES_PAGE);
         this._sourceView.connect('source-clicked',
@@ -429,6 +434,14 @@ Sidebar.prototype = {
     },
 
     _onWindowModeChanged: function(controller, mode) {
-        this.widget.set_visible(mode == WindowMode.WindowMode.OVERVIEW);
+        if (mode == WindowMode.WindowMode.PREVIEW) {
+            Tweener.addTween(this.actor, { width: 0,
+                                           time: 0.15,
+                                           transition: 'easeInQuad' });
+        } else if (mode == WindowMode.WindowMode.OVERVIEW) {
+            Tweener.addTween(this.actor, { width: this.widget.get_preferred_width()[1],
+                                           time: 0.15,
+                                           transition: 'easeOutQuad' });
+        }
     }
 };
diff --git a/src/view.js b/src/view.js
index 0705664..021faea 100644
--- a/src/view.js
+++ b/src/view.js
@@ -110,23 +110,34 @@ View.prototype = {
     _init: function() {
         this._selectedURNs = null;
 
+        // setup the model
         this._treeModel = Global.documentManager.getModel().model;
         this.widget.set_model(this._treeModel);
 
+        // setup selections
+        this.setSingleClickMode(true);
+
+        this.setSelectionMode(Gtk.SelectionMode.SINGLE);
+        this._selectionModeId =
+            Global.selectionController.connect('selection-mode-changed',
+                                               Lang.bind(this, this._onSelectionModeChanged));
+        this._selectionControllerId =
+            Global.selectionController.connect('selection-check',
+                                               Lang.bind(this, this._updateSelection));
+        this._selectAllId =
+            Global.selectionController.connect('select-all',
+                                               Lang.bind(this, this._onSelectAll));
+
         this.widget.connect('destroy', Lang.bind(this,
             function() {
                 Global.selectionController.disconnect(this._selectionControllerId);
+                Global.selectionController.disconnect(this._selectionModeId);
+                Global.selectionController.disconnect(this._selectAllId);
             }));
-        this.widget.connect('button-release-event', Lang.bind(this, this._onButtonReleaseEvent));
         this.widget.connect('button-press-event', Lang.bind(this, this._onButtonPressEvent));
 
         this.createRenderers();
 
-        this._selectionController = Global.selectionController;
-        this._selectionControllerId =
-            this._selectionController.connect('selection-check',
-                                              Lang.bind(this, this._updateSelection));
-
         // HACK: give the view some time to setup the scrolled window
         // allocation, as updateSelection() might call scrollToPath().
         // Is there anything better we can do here?
@@ -137,11 +148,13 @@ View.prototype = {
             }));
 
         this.connectToSelectionChanged(Lang.bind(this, this._onSelectionChanged));
+
+        this.widget.show();
     },
 
     _updateSelection: function() {
         let selectionObject = this.getSelectionObject();
-        let selected = this._selectionController.getSelection().slice(0);
+        let selected = Global.selectionController.getSelection().slice(0);
 
         if (!selected.length)
             return;
@@ -169,7 +182,24 @@ View.prototype = {
             }));
     },
 
+    _onSelectAll: function() {
+        this.getSelectionObject().select_all();
+    },
+
+    _onSelectionModeChanged: function(controller, selectionMode) {
+        // setup the GtkSelectionMode of the view according to whether or not
+        // the view is in "selection mode"
+        if (selectionMode) {
+            this.setSingleClickMode(false);
+            this.setSelectionMode(Gtk.SelectionMode.MULTIPLE);
+        } else {
+            this.setSingleClickMode(true);
+            this.setSelectionMode(Gtk.SelectionMode.SINGLE);
+        }
+    },
+
     _onSelectionChanged: function() {
+        // update the selection on the controller when the view signals a change
         let selectedURNs = Utils.getURNsFromPaths(this.getSelection(),
                                                   this._treeModel);
         Global.selectionController.setSelection(selectedURNs);
@@ -177,45 +207,29 @@ View.prototype = {
 
     _onButtonPressEvent: function(widget, event) {
         let button = event.get_button()[1];
-        if (button != 3)
-            return false;
-
-        let coords = [ event.get_coords()[1] , event.get_coords()[2] ];
-        let path = this.getPathAtPos(coords);
-
-        let selection = Global.selectionController.getSelection();
+        let enteredMode = false;
 
-        if (path) {
-            let urn = Utils.getURNFromPath(path, this._treeModel);
-
-            if (selection.indexOf(urn) == -1)
-                this.getSelectionObject().unselect_all();
-
-            this.getSelectionObject().select_path(path);
+        if (!Global.selectionController.getSelectionMode()) {
+            if (button == 3) {
+                Global.selectionController.setSelectionMode(true);
+                enteredMode = true;
+            } else {
+                return false;
+            }
         }
 
-        return true;
-    },
-
-    _onButtonReleaseEvent: function(view, event) {
-        let button = event.get_button()[1];
         let coords = [ event.get_coords()[1] , event.get_coords()[2] ];
-        let timestamp = event.get_time();
-
-        if (button != 3)
-            return false;
-
         let path = this.getPathAtPos(coords);
 
-        if (!path)
-            return false;
-
-        let iter = this._treeModel.get_iter(path)[1];
-
-        let urn = this._treeModel.get_value(iter, Documents.ModelColumns.URN);
-        let menu = new ContextMenu(Global.selectionController.getSelection());
+        if (path) {
+            let selectionObj = this.getSelectionObject();
+            let isSelected = selectionObj.path_is_selected(path);
 
-        menu.widget.popup_for_device(null, null, null, null, null, null, button, timestamp);
+            if (isSelected && !enteredMode)
+                selectionObj.unselect_path(path);
+            else if (!isSelected)
+                selectionObj.select_path(path);
+        }
 
         return true;
     },



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