[gnome-documents] all: add a controller for item selection



commit 5c90ddadd071068af9638e27879c778d101acf2a
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Wed Aug 24 19:36:35 2011 -0400

    all: add a controller for item selection
    
    This allows further cleanups, and make the selection permanent across
    model, filter or view mode changes.

 src/Makefile-js.am         |    1 +
 src/application.js         |    3 ++
 src/docFactory.js          |   16 ++++++++++
 src/global.js              |    2 +
 src/iconView.js            |   43 ++++++++--------------------
 src/listView.js            |   49 +++++++++-----------------------
 src/loadMore.js            |   10 +++++-
 src/mainWindow.js          |    9 +----
 src/selectionController.js |   52 ++++++++++++++++++++++++++++++++++
 src/trackerModel.js        |   25 ++++++++++++----
 src/utils.js               |   13 ++++++++
 src/view.js                |   67 ++++++++++++++++++++++++++-----------------
 12 files changed, 181 insertions(+), 109 deletions(-)
---
diff --git a/src/Makefile-js.am b/src/Makefile-js.am
index 23585b2..efa6c44 100644
--- a/src/Makefile-js.am
+++ b/src/Makefile-js.am
@@ -13,6 +13,7 @@ dist_js_DATA = \
     mainWindow.js \
     offsetController.js \
     preview.js \
+    selectionController.js \
     sidebar.js \
     sources.js \
     spinnerBox.js \
diff --git a/src/application.js b/src/application.js
index 3c58c90..d2330fb 100644
--- a/src/application.js
+++ b/src/application.js
@@ -37,6 +37,7 @@ const Main = imports.main;
 const MainWindow = imports.mainWindow;
 const OffsetController = imports.offsetController;
 const Path = imports.path;
+const SelectionController = imports.selectionController;
 const Sources = imports.sources;
 const TrackerModel = imports.trackerModel;
 
@@ -124,6 +125,8 @@ Application.prototype = {
     },
 
     _onSourceManagerCreated: function() {
+        Global.selectionController = new SelectionController.SelectionController();
+        Global.model = new TrackerModel.TrackerModel(Global.connection);
         this._mainWindow = new MainWindow.MainWindow();
         this.activate();
     },
diff --git a/src/docFactory.js b/src/docFactory.js
index 111aeeb..38bc5ba 100644
--- a/src/docFactory.js
+++ b/src/docFactory.js
@@ -27,6 +27,7 @@ const Gtk = imports.gi.Gtk;
 const Lang = imports.lang;
 const Signals = imports.signals;
 
+const Global = imports.global;
 const TrackerModel = imports.trackerModel;
 const Utils = imports.utils;
 
@@ -52,6 +53,18 @@ DocCommon.prototype = {
 
         // overridden in subclasses
         this.uri = null;
+
+        this._refreshIconId =
+            Global.settings.connect('changed::list-view',
+                                    Lang.bind(this, this.refreshIcon));
+    },
+
+    refreshIcon: function() {
+        // fallback
+    },
+
+    destroy: function() {
+        Global.settings.disconnect(this._refreshIconId);
     }
 };
 Signals.addSignalMethods(DocCommon.prototype);
@@ -70,7 +83,10 @@ LocalDocument.prototype = {
 
         // overridden
         this.uri = cursor.get_string(TrackerModel.TrackerColumns.URI)[0];
+        this.refreshIcon();
+    },
 
+    refreshIcon: function() {
         this._file = Gio.file_new_for_uri(this.uri);
         this._file.query_info_async(_FILE_ATTRIBUTES,
                                     0, 0, null,
diff --git a/src/global.js b/src/global.js
index 11e1607..769ed7f 100644
--- a/src/global.js
+++ b/src/global.js
@@ -24,3 +24,5 @@ let sourceManager = null;
 let connection = null;
 let settings = null;
 let offsetController = null;
+let selectionController = null;
+let model = null;
diff --git a/src/iconView.js b/src/iconView.js
index 99ac3f3..110a9e4 100644
--- a/src/iconView.js
+++ b/src/iconView.js
@@ -32,16 +32,14 @@ const _VIEW_ITEM_WRAP_WIDTH = 128;
 const _VIEW_COLUMN_SPACING = 20;
 const _VIEW_MARGIN = 16;
 
-function IconView(window) {
-    this._init(window);
+function IconView() {
+    this._init();
 }
 
 IconView.prototype = {
     __proto__: View.View.prototype,
 
-    _init: function(window) {
-        View.View.prototype._init.call(this, window);
-
+    _init: function() {
         this.widget = new Gtk.IconView({ hexpand: true,
                                          vexpand: true });
 
@@ -51,38 +49,21 @@ IconView.prototype = {
         this.widget.set_selection_mode(Gtk.SelectionMode.MULTIPLE);
 
         this.widget.connect('item-activated',
-                          Lang.bind(this, this._onItemActivated));
+                            Lang.bind(this, this._onItemActivated));
 
         this.widget.show();
-    },
 
-    preUpdate: function() {
-        let selection = this.widget.get_selected_items();
-
-        View.View.prototype.preUpdate.call(this, selection);
+        View.View.prototype._init.call(this);
+        this.widget.connect('selection-changed',
+                            Lang.bind(this, this.onSelectionChanged));
     },
 
-    postUpdate: function() {
-        if (!this._selectedURNs)
-            return;
-
-        this._treeModel.foreach(Lang.bind(this,
-            function(model, path, iter) {
-                let urn = this._treeModel.get_value(iter, TrackerModel.ModelColumns.URN);
-                let urnIndex = this._selectedURNs.indexOf(urn);
-
-                if (urnIndex != -1) {
-                    this.widget.select_path(path);
-                    this._selectedURNs.splice(urnIndex, 1);
-                }
-
-                if (this._selectedURNs.length == 0)
-                    return true;
-
-                return false;
-            }));
+    getSelection: function() {
+        return this.getSelectionObject().get_selected_items();
+    },
 
-        View.View.prototype.postUpdate.call(this);
+    getSelectionObject: function() {
+        return this.widget;
     },
 
     createRenderers: function() {
diff --git a/src/listView.js b/src/listView.js
index eb2fb4b..d6d76fe 100644
--- a/src/listView.js
+++ b/src/listView.js
@@ -27,62 +27,41 @@ const TrackerModel = imports.trackerModel;
 const View = imports.view;
 const Lang = imports.lang;
 
-function ListView(window) {
-    this._init(window);
+function ListView() {
+    this._init();
 }
 
 ListView.prototype = {
     __proto__: View.View.prototype,
 
-    _init: function(window) {
-        View.View.prototype._init.call(this, window);
-
+    _init: function() {
         this.widget = new Gtk.TreeView({ hexpand: true,
                                          vexpand: true,
                                          headers_visible: false });
 
-        this.widget.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE);
-
         this.widget.connect('row-activated',
                             Lang.bind(this, this._onItemActivated));
 
         this.widget.show();
+
+        View.View.prototype._init.call(this);
+
+        let selection = this.widget.get_selection();
+        selection.set_mode(Gtk.SelectionMode.MULTIPLE);
+        selection.connect('changed',
+                          Lang.bind(this, this.onSelectionChanged));
     },
 
     _onItemActivated: function(view, path, column) {
         this.activateItem(path);
     },
 
-    preUpdate: function() {
-        let treeSelection = this.widget.get_selection();
-        let selection = this.widget.get_selected_rows();
-
-        View.View.prototype.preUpdate.call(this, selection);
+    getSelectionObject: function() {
+        return this.widget.get_selection();
     },
 
-    postUpdate: function() {
-        if (!this._selectedURNs)
-            return;
-
-        let treeSelection = this.widget.get_selection();
-
-        this._treeModel.foreach(Lang.bind(this,
-            function(model, path, iter) {
-                let urn = this._treeModel.get_value(iter, TrackerModel.ModelColumns.URN);
-                let urnIndex = this._selectedURNs.indexOf(urn);
-
-                if (urnIndex != -1) {
-                    treeSelection.select_path(path);
-                    this._selectedURNs.splice(urnIndex, 1);
-                }
-
-                if (this._selectedURNs.length == 0)
-                    return true;
-
-                return false;
-            }));
-
-        View.View.prototype.postUpdate.call(this);
+    getSelection: function() {
+        return this.getSelectionObject().get_selected_rows()[0];
     },
 
     createRenderers: function() {
diff --git a/src/loadMore.js b/src/loadMore.js
index f0e1c97..f9d5b93 100644
--- a/src/loadMore.js
+++ b/src/loadMore.js
@@ -33,14 +33,20 @@ function LoadMoreButton() {
 LoadMoreButton.prototype = {
     _init: function() {
         this._controller = Global.offsetController;
-        this._controller.connect('item-count-changed',
-                                 Lang.bind(this, this._onItemCountChanged));
+        this._controllerId =
+            this._controller.connect('item-count-changed',
+                                     Lang.bind(this, this._onItemCountChanged));
 
         this.widget = new Gtk.Button();
         this.widget.connect('clicked', Lang.bind(this, function() {
             this._controller.increaseOffset();
         }));
 
+        this.widget.connect('destroy', Lang.bind(this,
+            function() {
+                this._controller.disconnect(this._controllerId);
+            }));
+
         this._onItemCountChanged();
     },
 
diff --git a/src/mainWindow.js b/src/mainWindow.js
index 1b3761e..ee160e8 100644
--- a/src/mainWindow.js
+++ b/src/mainWindow.js
@@ -33,7 +33,6 @@ const Global = imports.global;
 const LoadMore = imports.loadMore;
 const MainToolbar = imports.mainToolbar;
 const Sidebar = imports.sidebar;
-const TrackerModel = imports.trackerModel;
 const IconView = imports.iconView;
 const ListView = imports.listView;
 const Preview = imports.preview;
@@ -67,9 +66,8 @@ MainWindow.prototype = {
         this.window.connect('delete-event',
                             Lang.bind(this, this._onDeleteEvent));
 
-        Global.settings.connect('changed::list-view', Lang.bind(this, function() {
-            this._refreshViewSettings(true);
-        }));
+        Global.settings.connect('changed::list-view',
+                                Lang.bind(this, this._refreshViewSettings));
 
         this._grid = new Gtk.Grid({ orientation: Gtk.Orientation.HORIZONTAL });
         this.window.add(this._grid);
@@ -91,8 +89,6 @@ MainWindow.prototype = {
         this._viewContainer.add(this._scrolledWin);
 
         this._grid.show_all();
-
-        this._model = new TrackerModel.TrackerModel(Global.connection);
         this._prepareForOverview();
     },
 
@@ -128,7 +124,6 @@ MainWindow.prototype = {
 
     _refreshViewSettings: function() {
         this._initView();
-        this.view.setModel(this._model);
     },
 
     _prepareForPreview: function(model, document) {
diff --git a/src/selectionController.js b/src/selectionController.js
new file mode 100644
index 0000000..e245db6
--- /dev/null
+++ b/src/selectionController.js
@@ -0,0 +1,52 @@
+/*
+ * 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 Signals = imports.signals;
+
+function SelectionController() {
+    this._init();
+};
+
+SelectionController.prototype = {
+    _init: function() {
+        this._selection = [];
+    },
+
+    setSelection: function(selection) {
+        if (this._isFreezed)
+            return;
+
+        this._selection = selection;
+        this.emit('selection-changed', this._selection);
+    },
+
+    getSelection: function() {
+        return this._selection;
+    },
+
+    freezeSelection: function(freeze) {
+        this._isFreezed = freeze;
+
+        if (!this._isFreezed)
+            this.emit('selection-check');
+    }
+};
+Signals.addSignalMethods(SelectionController.prototype);
diff --git a/src/trackerModel.js b/src/trackerModel.js
index c9c7f80..3c80e73 100644
--- a/src/trackerModel.js
+++ b/src/trackerModel.js
@@ -192,9 +192,10 @@ function TrackerModel(connection) {
 
 TrackerModel.prototype = {
     _init: function(connection) {
+        this._docs = [];
+
         this._builder = new QueryBuilder();
         this._factory = new DocFactory.DocFactory();
-        Global.settings.connect('changed::list-view', Lang.bind(this, this._refresh));
 
         this.model = Gd.create_list_store();
         this._connection = connection;
@@ -230,7 +231,6 @@ TrackerModel.prototype = {
                 // no changes processed, to avoid uselessly refreshing the view.
                 // That requires support for the Changes feed in libgdata, see
                 // https://bugzilla.gnome.org/show_bug.cgi?id=654652
-                this._emitModelUpdatePending();
                 this._refresh();
 
                 Mainloop.timeout_add_seconds(MINER_REFRESH_TIMEOUT,
@@ -259,6 +259,12 @@ TrackerModel.prototype = {
                 if (objectIter)
                     Gd.store_update_icon(this.model, objectIter, newDoc.pixbuf);
             }));
+
+        this._docs.push(newDoc);
+    },
+
+    _onQueryFinished: function() {
+        Global.selectionController.freezeSelection(false);
     },
 
     _onCursorNext: function(cursor, res) {
@@ -267,12 +273,13 @@ TrackerModel.prototype = {
 
             if (!valid) {
                 // signal the total count update and return
-                this._emitModelUpdateDone();
+                this._onQueryFinished();
                 return;
             }
         } catch (e) {
             // FIXME: error handling
             log('Unable to fetch results from cursor: ' + e.toString());
+            this._onQueryFinished();
 
             return;
         }
@@ -288,6 +295,7 @@ TrackerModel.prototype = {
         } catch (e) {
             // FIXME: error handling
             log('Unable to execute query: ' + e.toString());
+            this._onQueryFinished();
         }
     },
 
@@ -300,12 +308,15 @@ TrackerModel.prototype = {
         this.emit('model-update-done');
     },
 
-    _emitModelUpdatePending: function() {
-        this.emit('model-update-pending');
-    },
-
     _refresh: function() {
+        Global.selectionController.freezeSelection(true);
         this.model.clear();
+
+        this._docs.forEach(function(doc) {
+            doc.destroy();
+        });
+        this._docs = [];
+
         this._performCurrentQuery();
     },
 
diff --git a/src/utils.js b/src/utils.js
index 56e3ae0..0299b20 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -22,6 +22,9 @@
 const Gtk = imports.gi.Gtk;
 
 const Global = imports.global;
+const TrackerModel = imports.trackerModel;
+
+const Lang = imports.lang;
 
 const _ICON_VIEW_SIZE = 128;
 const _LIST_VIEW_SIZE = 48;
@@ -57,3 +60,13 @@ function pixbufFromRdfType(type) {
 
     return pixbuf;
 }
+
+function getURNsFromPaths(paths, model) {
+    return paths.map(Lang.bind(this,
+            function(path) {
+                let iter = model.get_iter(path)[1];
+                let urn = model.get_value(iter, TrackerModel.ModelColumns.URN);
+
+                return urn;
+            }));
+}
diff --git a/src/view.js b/src/view.js
index 0c25dd7..3bf752b 100644
--- a/src/view.js
+++ b/src/view.js
@@ -24,51 +24,64 @@ const Gtk = imports.gi.Gtk;
 const Lang = imports.lang;
 const Signals = imports.signals;
 
+const Global = imports.global;
 const TrackerModel = imports.trackerModel;
+const Utils = imports.utils;
 
-function View(window) {
-    this._init(window);
+function View() {
+    this._init();
 }
 
 View.prototype = {
-    _init: function(window) {
+    _init: function() {
         this._selectedURNs = null;
-        this.window = window;
-    },
-
-    destroy: function() {
-        this.widget.destroy();
-    },
 
-    setModel: function(model) {
-        this.model = model;
-        this.model.connect('model-update-pending', Lang.bind(this,
-            function() {
-                this.preUpdate();
-            }));
-        this.model.connect('model-update-done', Lang.bind(this,
+        this.model = Global.model;
+        this._treeModel = Global.model.model;
+        this.widget.set_model(this._treeModel);
+        this.widget.connect('destroy', Lang.bind(this,
             function() {
-                this.postUpdate();
+                Global.selectionController.disconnect(this._selectionControllerId);
             }));
 
-        this._treeModel = model.model;
-        this.widget.set_model(this._treeModel);
-
         this.createRenderers();
+
+        this._selectionController = Global.selectionController;
+        this._selectionControllerId =
+            this._selectionController.connect('selection-check',
+                                              Lang.bind(this, this._updateSelection));
+
+        this._updateSelection();
     },
 
-    preUpdate: function(selection) {
-        this._selectedURNs = selection.map(Lang.bind(this,
-            function(path) {
-                let iter = this._treeModel.get_iter(path)[1];
+    _updateSelection: function() {
+        let selectionObject = this.getSelectionObject();
+        let selected = this._selectionController.getSelection().slice(0);
+
+        if (!selected.length)
+            return;
+
+        this._treeModel.foreach(Lang.bind(this,
+            function(model, path, iter) {
                 let urn = this._treeModel.get_value(iter, TrackerModel.ModelColumns.URN);
+                let urnIndex = selected.indexOf(urn);
 
-                return urn;
+                if (urnIndex != -1) {
+                    selectionObject.select_path(path);
+                    selected.splice(urnIndex, 1);
+                }
+
+                if (selected.length == 0)
+                    return true;
+
+                return false;
             }));
     },
 
-    postUpdate: function() {
-        this._selectedURNs = null;
+    onSelectionChanged: function() {
+        let selectedURNs = Utils.getURNsFromPaths(this.getSelection(),
+                                                  this._treeModel);
+        Global.selectionController.setSelection(selectedURNs);
     },
 
     // this must be overridden by all implementations



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