[gnome-shell/wip/paging-release2: 1/9] appDisplay, IconGrid: Added pagination



commit c9ab4b306bd371b8e292942c2336a9781f9107f9
Author: Carlos Soriano <carlos soriano89 gmail com>
Date:   Mon Aug 12 19:22:03 2013 +0200

    appDisplay, IconGrid: Added pagination
    
    https://bugzilla.gnome.org/show_bug.cgi?id=706081

 data/theme/gnome-shell.css |    4 +-
 js/ui/appDisplay.js        |  631 +++++++++++++++++++++++++++++++++-----------
 js/ui/iconGrid.js          |  326 +++++++++++++++++++----
 3 files changed, 742 insertions(+), 219 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 84768b1..1387b2b 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -899,10 +899,10 @@ StScrollBar StButton#vhandle:active {
 
 
 .search-display > StBoxLayout,
-.all-apps > StBoxLayout,
+.all-apps,
 .frequent-apps > StBoxLayout {
     /* horizontal padding to make sure scrollbars or dash don't overlap content */
-    padding: 0px 88px;
+    padding: 0px 88px 10px 88px;
 }
 
 .app-folder-icon {
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 9132d18..1a98694 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -30,10 +30,14 @@ const Util = imports.misc.util;
 const MAX_APPLICATION_WORK_MILLIS = 75;
 const MENU_POPUP_TIMEOUT = 600;
 const MAX_COLUMNS = 6;
+const MIN_COLUMNS = 4;
+const MIN_ROWS = 4;
 
 const INACTIVE_GRID_OPACITY = 77;
+const INACTIVE_GRID_OPACITY_ANIMATION_TIME = 0.15;
 const FOLDER_SUBICON_FRACTION = .4;
 
+const PAGE_SWITCH_TIME = 0.25;
 
 // Recursively load a GMenuTreeDirectory; we could put this in ShellAppSystem too
 function _loadCategory(dir, view) {
@@ -60,11 +64,9 @@ const AlphabeticalView = new Lang.Class({
 
     _init: function() {
         this._grid = new IconGrid.IconGrid({ xAlign: St.Align.MIDDLE,
-                                             columnLimit: MAX_COLUMNS });
-
+                                                                     columnLimit: MAX_COLUMNS });
         // Standard hack for ClutterBinLayout
         this._grid.actor.x_expand = true;
-
         this._items = {};
         this._allItems = [];
     },
@@ -111,127 +113,169 @@ const AlphabeticalView = new Lang.Class({
     }
 });
 
-const FolderView = new Lang.Class({
-    Name: 'FolderView',
+const PaginatedAlphabeticalView = new Lang.Class({
+    Name: 'PaginatedAlphabeticalView',
     Extends: AlphabeticalView,
 
     _init: function() {
-        this.parent();
-        this.actor = this._grid.actor;
-    },
+         this._grid = new IconGrid.PaginatedIconGrid({ xAlign: St.Align.MIDDLE,
+                                                                                      columnLimit: 
MAX_COLUMNS });
+        // Standard hack for ClutterBinLayout
+        this._grid.actor.x_expand = true;
+        this._items = {};
+        this._allItems = [];
+    }
+});
 
-    _getItemId: function(item) {
-        return item.get_id();
-    },
+const PaginationScrollView = new Lang.Class({
+    Name: 'PaginationScrollView',
+    Extends: St.Bin,
 
-    _createItemIcon: function(item) {
-        return new AppIcon(item);
+    _init: function(parent, params) {
+        params['reactive'] = true;
+        this.parent(params);
+        this._parent = parent;
     },
 
-    _compareItems: function(a, b) {
-        return a.compare_by_name(b);
+   vfunc_get_preferred_height: function (forWidht) {
+        return [0, 0];
     },
 
-    addApp: function(app) {
-        this._addItem(app);
+    vfunc_get_preferred_width: function(forHeight) {
+        return [0, 0];
     },
 
-    createFolderIcon: function(size) {
-        let icon = new St.Widget({ layout_manager: new Clutter.BinLayout(),
-                                   style_class: 'app-folder-icon',
-                                   width: size, height: size });
-        let subSize = Math.floor(FOLDER_SUBICON_FRACTION * size);
-
-        let aligns = [ Clutter.ActorAlign.START, Clutter.ActorAlign.END ];
-        for (let i = 0; i < Math.min(this._allItems.length, 4); i++) {
-            let texture = this._allItems[i].create_icon_texture(subSize);
-            let bin = new St.Bin({ child: texture,
-                                   x_expand: true, y_expand: true });
-            bin.set_x_align(aligns[i % 2]);
-            bin.set_y_align(aligns[Math.floor(i / 2)]);
-            icon.add_actor(bin);
-        }
-
-        return icon;
-    }
-});
-
-const AllViewLayout = new Lang.Class({
-    Name: 'AllViewLayout',
-    Extends: Clutter.BinLayout,
-
-    vfunc_get_preferred_height: function(container, forWidth) {
-        let minBottom = 0;
-        let naturalBottom = 0;
-
-        for (let child = container.get_first_child();
-             child;
-             child = child.get_next_sibling()) {
-            let childY = child.y;
-            let [childMin, childNatural] = child.get_preferred_height(forWidth);
-
-            if (childMin + childY > minBottom)
-                minBottom = childMin + childY;
-
-            if (childNatural + childY > naturalBottom)
-                naturalBottom = childNatural + childY;
-        }
-        return [minBottom, naturalBottom];
+    vfunc_allocate: function(box, flags) {
+        box = this.get_parent().allocation;
+        box = this.get_theme_node().get_content_box(box);
+        this.set_allocation(box, flags);        
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+        let childBox = new Clutter.ActorBox();
+        childBox.x1 = 0;
+        childBox.y1 = 0;
+        childBox.x2 = availWidth;
+        childBox.y2 = availHeight;   
+        let child = this.get_child();
+        child.allocate(childBox, flags);
+        this._parent.updateAdjustment(availHeight);
     }
 });
 
 const AllView = new Lang.Class({
     Name: 'AllView',
-    Extends: AlphabeticalView,
-
+    Extends: PaginatedAlphabeticalView,
+   
     _init: function() {
         this.parent();
-
-        this._grid.actor.y_align = Clutter.ActorAlign.START;
-        this._grid.actor.y_expand = true;
-
-        let box = new St.BoxLayout({ vertical: true });
-        this._stack = new St.Widget({ layout_manager: new AllViewLayout() });
+        this._paginationView = new PaginationScrollView(this, {style_class: 'all-apps'});
+        let layout = new Clutter.BinLayout();
+        this.actor = new St.Widget({ layout_manager: layout, 
+                                                          x_expand:true, y_expand:true });
+        layout.add(this._paginationView, Clutter.ActorAlign.CENTER,  Clutter.ActorAlign.CENTER);
+        this._grid.connect('n-pages-changed', Lang.bind(this, this._updatedNPages));
+
+        this._folderIcons = [];
+
+        this._stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
+        this._box = new St.BoxLayout({ vertical: true });        
+        this._verticalAdjustment = new St.Adjustment();
+        this._horizontalAdjustment = new St.Adjustment();
+        this._box.set_adjustments(this._horizontalAdjustment, this._verticalAdjustment);
+        
+        this._grid._viewForPageSize = this._paginationView;
+        this._currentPage = 0;
         this._stack.add_actor(this._grid.actor);
         this._eventBlocker = new St.Widget({ x_expand: true, y_expand: true });
-        this._stack.add_actor(this._eventBlocker);
-        box.add(this._stack, { y_align: St.Align.START, expand: true });
-
-        this.actor = new St.ScrollView({ x_fill: true,
-                                         y_fill: false,
-                                         y_align: St.Align.START,
-                                         x_expand: true,
-                                         y_expand: true,
-                                         overlay_scrollbars: true,
-                                         style_class: 'all-apps vfade' });
-        this.actor.add_actor(box);
-        this.actor.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
-        let action = new Clutter.PanAction({ interpolate: true });
-        action.connect('pan', Lang.bind(this, this._onPan));
-        this.actor.add_action(action);
+        this._stack.add_actor(this._eventBlocker, {x_align:St.Align.MIDDLE});
+
+        this._box.add_actor(this._stack);
+        this._paginationView.add_actor(this._box);
+
+        this._paginationView.connect('scroll-event', Lang.bind(this, this._onScroll));
 
         this._clickAction = new Clutter.ClickAction();
         this._clickAction.connect('clicked', Lang.bind(this, function() {
             if (!this._currentPopup)
                 return;
-
             let [x, y] = this._clickAction.get_coords();
             let actor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
             if (!this._currentPopup.actor.contains(actor))
                 this._currentPopup.popdown();
         }));
         this._eventBlocker.add_action(this._clickAction);
+        // When the number of pages change (i.e. when changing screen resolution or during clutter false 
allocations)
+        // we have to tell pagination that the adjustment is not correct (since the allocated size of 
pagination changed)
+        // For that problem we return to the first page of pagination.
+        this.invalidatePagination = false;
+    },
+
+    _updatedNPages: function(iconGrid, nPages) {
+        // We don't need a relayout because we already done it at iconGrid
+        // when pages are calculated (and then the signal is emitted before that)");
+        this.invalidatePagination = true;
+    },
+
+    goToPage: function(pageNumber) {
+        if(pageNumber < this._grid.nPages() && pageNumber >= 0) {
+            this._currentPage = pageNumber;
+            let params = { value: this._grid.getPagePosition(this._currentPage)[1],
+                                     time: PAGE_SWITCH_TIME,
+                                     transition: 'easeOutQuad'
+                                   };
+            Tweener.addTween(this._verticalAdjustment, params);
+        }
     },
 
-    _onPan: function(action) {
-        this._clickAction.release();
+    _onScroll: function(actor, event) {
+        let direction = event.get_scroll_direction();
+        let nextPage;
+        if (direction == Clutter.ScrollDirection.UP)
+            if(this._currentPage > 0) {
+                nextPage = this._currentPage - 1;
+                this.goToPage(nextPage);
+            }
+        if (direction == Clutter.ScrollDirection.DOWN)
+            if(this._currentPage < (this._grid.nPages() - 1)) {
+                nextPage = this._currentPage + 1;
+                this.goToPage(nextPage);
+            }
+    },
 
-        let [dist, dx, dy] = action.get_motion_delta(0);
-        let adjustment = this.actor.vscroll.adjustment;
-        adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
+    _onKeyRelease: function(actor, event) {
+        if (event.get_key_symbol() == Clutter.KEY_Up) {
+            this.goToNextPage();
+            return true;
+        } else if(event.get_key_symbol() == Clutter.KEY_Down) {
+            this.goToPreviousPage();
+            return true;
+        }
         return false;
     },
 
+    addApp: function(app) {
+       this._addItem(app);
+    },
+
+    addFolder: function(dir) {
+       this._addItem(dir);
+    },
+
+    addFolderPopup: function(popup) {
+        this._stack.add_actor(popup.actor);
+        popup.connect('open-state-changed', Lang.bind(this,
+            function(popup, isOpen) {
+                this._eventBlocker.reactive = isOpen;
+                this._currentPopup = isOpen ? popup : null;
+                this._updateIconOpacities(isOpen);
+            }));
+    },
+
+    removeAll: function() {
+        this._folderIcons = [];
+        this.parent();
+    },
+
     _getItemId: function(item) {
         if (item instanceof Shell.App)
             return item.get_id();
@@ -244,9 +288,11 @@ const AllView = new Lang.Class({
     _createItemIcon: function(item) {
         if (item instanceof Shell.App)
             return new AppIcon(item);
-        else if (item instanceof GMenu.TreeDirectory)
-            return new FolderIcon(item, this);
-        else
+        else if (item instanceof GMenu.TreeDirectory) {
+            let folderIcon = new FolderIcon(item, this);
+            this._folderIcons.push(folderIcon);
+            return folderIcon;
+        } else
             return null;
     },
 
@@ -258,46 +304,55 @@ const AllView = new Lang.Class({
         return (nameA > nameB) ? 1 : (nameA < nameB ? -1 : 0);
     },
 
-    addApp: function(app) {
-        let appIcon = this._addItem(app);
-        if (appIcon)
-            appIcon.actor.connect('key-focus-in',
-                                  Lang.bind(this, this._ensureIconVisible));
-    },
-
-    addFolder: function(dir) {
-        let folderIcon = this._addItem(dir);
-        if (folderIcon)
-            folderIcon.actor.connect('key-focus-in',
-                                     Lang.bind(this, this._ensureIconVisible));
-    },
-
-    addFolderPopup: function(popup) {
-        this._stack.add_actor(popup.actor);
-        popup.connect('open-state-changed', Lang.bind(this,
-            function(popup, isOpen) {
-                this._eventBlocker.reactive = isOpen;
-                this._currentPopup = isOpen ? popup : null;
-                this._updateIconOpacities(isOpen);
-                if (isOpen) {
-                    this._ensureIconVisible(popup.actor);
-                    this._grid.actor.y = popup.parentOffset;
-                } else {
-                    this._grid.actor.y = 0;
-                }
-            }));
-    },
-
-    _ensureIconVisible: function(icon) {
-        Util.ensureActorVisibleInScrollView(this.actor, icon);
+    updateAdjustment: function(availHeight) {
+        this._verticalAdjustment.page_size = availHeight;
+        this._verticalAdjustment.upper = this._stack.height;
+        if(this.invalidatePagination)
+            this.goToPage(0);
+        this.invalidatePagination = false;
     },
 
     _updateIconOpacities: function(folderOpen) {
         for (let id in this._items) {
-            if (folderOpen && !this._items[id].actor.checked)
-                this._items[id].actor.opacity = INACTIVE_GRID_OPACITY;
-            else
-                this._items[id].actor.opacity = 255;
+            if (folderOpen && !this._items[id].actor.checked) {
+                let params = { opacity: INACTIVE_GRID_OPACITY,
+                        time: INACTIVE_GRID_OPACITY_ANIMATION_TIME,
+                        transition: 'easeOutQuad'
+                       };
+                Tweener.addTween(this._items[id].actor, params);
+            }
+            else {
+                let params = { opacity: 255,
+                        time: INACTIVE_GRID_OPACITY_ANIMATION_TIME,
+                        transition: 'easeOutQuad'
+                       };
+                Tweener.addTween(this._items[id].actor, params);
+            }
+        }
+    },
+
+    onUpdatedDisplaySize: function(width, height) {
+        let box = new Clutter.ActorBox();
+        box.x1 = 0;
+        box.x2 = width;
+        box.y1 = 0;
+        box.y2 = height;
+        box = this.actor.get_theme_node().get_content_box(box);
+        box = this._paginationView.get_theme_node().get_content_box(box);
+        box = this._grid.actor.get_theme_node().get_content_box(box);
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+
+        // Update grid dinamyc spacing based on display width
+        let spacing = this._grid.maxSpacingForWidthHeight(availWidth, availHeight, MIN_COLUMNS, MIN_ROWS, 
true);
+        this._grid.top_padding = spacing;
+        this._grid.bottom_padding = spacing;
+        this._grid.left_padding = spacing;
+        this._grid.right_padding = spacing;
+        this._grid.setSpacing(spacing);
+        // Update folder views
+        for(let id in this._folderIcons) {
+            this._folderIcons[id].onUpdatedDisplaySize(availWidth, availHeight);
         }
     }
 });
@@ -328,6 +383,24 @@ const FrequentView = new Lang.Class({
             let appIcon = new AppIcon(mostUsed[i]);
             this._grid.addItem(appIcon.actor, -1);
         }
+    },
+
+    onUpdatedDisplaySize: function(width, height) {
+        let box = new Clutter.ActorBox();
+        box.x1 = 0;
+        box.x2 = width;
+        box.y1 = 0;
+        box.y2 = height;
+        box = this.actor.get_theme_node().get_content_box(box);
+        box = this._grid.actor.get_theme_node().get_content_box(box);
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+        let spacing = this._grid.maxSpacingForWidthHeight(availWidth, availHeight, MIN_COLUMNS, MIN_ROWS, 
true);
+        this._grid.top_padding = spacing;
+        this._grid.bottom_padding = spacing;
+        this._grid.left_padding = spacing;
+        this._grid.right_padding = spacing;
+        this._grid.setSpacing(spacing);
     }
 });
 
@@ -361,6 +434,32 @@ const ControlsBoxLayout = Lang.Class({
     }
 });
 
+const ViewStackLayout = new Lang.Class({
+    Name: 'ViewStackLayout',
+    Extends: Clutter.BinLayout,
+    
+    vfunc_allocate: function (actor, box, flags) {        
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+        // Prepare children of all views for the upcomming allocation, calculate all
+        // the needed values in the responsive design we are trying to emulate
+        this.emit('allocated-size-changed', availWidth, availHeight);
+        this.parent(actor, box, flags);
+    },
+    
+    vfunc_set_container: function(container) {
+        if(this._styleChangedId) {
+            this._container.disconnect(this._styleChangedId);
+            this._styleChangedId = 0;
+        }
+        if(container != null)
+            this._styleChangedId = container.connect('style-changed', Lang.bind(this,
+                    function() { this.spacing = this._container.get_theme_node().get_length('spacing'); }));
+        this._container = container;
+    }
+});
+Signals.addSignalMethods(ViewStackLayout.prototype);
+
 const AppDisplay = new Lang.Class({
     Name: 'AppDisplay',
 
@@ -396,20 +495,21 @@ const AppDisplay = new Lang.Class({
                                  x_expand: true });
         this._views[Views.ALL] = { 'view': view, 'control': button };
 
-        this.actor = new St.BoxLayout({ style_class: 'app-display',
-                                        vertical: true,
-                                        x_expand: true, y_expand: true });
+        this.actor = new St.Widget({ style_class: 'app-display',
+                                                          x_expand: true, y_expand: true });
+        this._viewStackLayout = new ViewStackLayout();
+        this.actor.set_layout_manager(new Clutter.BoxLayout({vertical: true}));
+        this._viewStackLayout.connect('allocated-size-changed', Lang.bind(this, this._onUpdatedDisplaySize));
 
-        this._viewStack = new St.Widget({ layout_manager: new Clutter.BinLayout(),
-                                          x_expand: true, y_expand: true });
-        this.actor.add(this._viewStack, { expand: true });
+        this._viewStack = new St.Widget({ x_expand: true, y_expand: true });
+        this._viewStack.set_layout_manager(this._viewStackLayout);
 
+        this.actor.add_actor(this._viewStack, { expand: true });
         let layout = new ControlsBoxLayout({ homogeneous: true });
         this._controls = new St.Widget({ style_class: 'app-view-controls',
                                          layout_manager: layout });
         layout.hookup_style(this._controls);
-        this.actor.add(new St.Bin({ child: this._controls }));
-
+        this.actor.add_actor(new St.Bin({ child: this._controls }));
 
         for (let i = 0; i < this._views.length; i++) {
             this._viewStack.add_actor(this._views[i].view.actor);
@@ -429,9 +529,9 @@ const AppDisplay = new Lang.Class({
         // our real contents
         this._focusDummy = new St.Bin({ can_focus: true });
         this._viewStack.add_actor(this._focusDummy);
-
         this._allAppsWorkId = Main.initializeDeferredWork(this.actor, Lang.bind(this, 
this._redisplayAllApps));
         this._frequentAppsWorkId = Main.initializeDeferredWork(this.actor, Lang.bind(this, 
this._redisplayFrequentApps));
+        
     },
 
     _showView: function(activeIndex) {
@@ -509,6 +609,20 @@ const AppDisplay = new Lang.Class({
             if (focused)
                 this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
         }
+    },
+
+    _onUpdatedDisplaySize: function(actor, width, height) {
+        let box = new Clutter.ActorBox();
+        box.x1 = 0;
+        box.x2 = width;
+        box.y1 = 0;
+        box.y2 = height;
+        box = this._viewStack.get_theme_node().get_content_box(box);
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+        for (let i = 0; i < this._views.length; i++) {
+            this._views[i].view.onUpdatedDisplaySize(availWidth, availHeight);
+        }
     }
 });
 
@@ -568,12 +682,119 @@ const AppSearchProvider = new Lang.Class({
     }
 });
 
+const FolderView = new Lang.Class({
+    Name: 'FolderView',
+    Extends: AlphabeticalView,
+
+    _init: function() {
+        this.parent({ xAlign: St.Align.MIDDLE});
+        // If it not expand, the parent doesn't take into account its preferred_width when allocating
+        // the second time it allocates, so we apply the "Standard hack for ClutterBinLayout"
+        this._grid.actor.x_expand = true;
+
+        this.actor = new St.ScrollView({ overlay_scrollbars: true });
+        this.actor.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
+        this._box = new St.BoxLayout({ vertical: true, reactive: true });
+        this._widget = new St.Widget({ layout_manager: new Clutter.BinLayout() });
+        this._widget.add_child(this._grid.actor);
+        this._box.add_actor(this._widget);
+        this.actor.add_actor(this._box);
+    },
+
+    _getItemId: function(item) {
+        return item.get_id();
+    },
+
+    _createItemIcon: function(item) {
+        return new AppIcon(item);
+    },
+
+    _compareItems: function(a, b) {
+        return a.compare_by_name(b);
+    },
+
+    addApp: function(app) {
+        this._addItem(app);
+    },
+
+    createFolderIcon: function(size) {
+        let icon = new St.Widget({ layout_manager: new Clutter.BinLayout(),
+                                   style_class: 'app-folder-icon',
+                                   width: size, height: size });
+        let subSize = Math.floor(FOLDER_SUBICON_FRACTION * size);
+
+        let aligns = [ Clutter.ActorAlign.START, Clutter.ActorAlign.END ];
+        for (let i = 0; i < Math.min(this._allItems.length, 4); i++) {
+            let texture = this._allItems[i].create_icon_texture(subSize);
+            let bin = new St.Bin({ child: texture,
+                                   x_expand: true, y_expand: true });
+            bin.set_x_align(aligns[i % 2]);
+            bin.set_y_align(aligns[Math.floor(i / 2)]);
+            icon.add_actor(bin);
+        }
+
+        return icon;
+    },
+
+    onUpdatedDisplaySize: function(width, height) {
+        this._appDisplayWidth = width;
+        this._appDisplayHeight = height;
+        // Update grid dinamyc spacing based on display width
+        let itemWidth = this._grid._hItemSize * MAX_COLUMNS;
+        let emptyArea = width - itemWidth;
+        let spacing;
+        spacing = Math.max(this._grid._spacing, emptyArea / ( 2 *  MAX_COLUMNS));
+        spacing = Math.round(spacing);
+        this._grid.setSpacing(spacing);
+    },
+
+    _containerBox: function() {
+        let pageBox = new Clutter.ActorBox();
+        pageBox.x1 = 0;
+        pageBox.y1 = 0;
+        pageBox.x2 = this._appDisplayWidth;
+        pageBox.y2 = this._appDisplayHeight;
+        return this.actor.get_theme_node().get_content_box(pageBox);
+    },
+
+    usedWidth: function() {
+        let box = this._containerBox();
+        let availWidthPerPage = box.x2 - box.x1;
+        let maxUsedWidth = this._grid.usedWidth(availWidthPerPage);
+        return maxUsedWidth;
+    },
+
+    usedHeight: function() {
+        // Then calculate the real maxUsedHeight
+        return this._grid.usedHeightForNRows(this.nRowsDisplayedAtOnce());
+    },   
+
+    nRowsDisplayedAtOnce: function() {
+        let box = this._containerBox();
+        let availHeightPerPage = box.y2 - box.y1;
+        let availWidthPerPage = box.x2 - box.x1;
+        let maxRowsDisplayedAtOnce = this.maxRowsDisplayedAtOnce();
+        let usedRows = this._grid.nUsedRows(availWidthPerPage);
+        usedRows = usedRows <= maxRowsDisplayedAtOnce ? usedRows : maxRowsDisplayedAtOnce;
+        return usedRows;
+    },
+
+    maxRowsDisplayedAtOnce: function() {
+        let box = this._containerBox();
+        let availHeightPerPage = box.y2 - box.y1;
+        let availWidthPerPage = box.x2 - box.x1;
+        let maxRowsPerPage = this._grid.rowsForHeight(availHeightPerPage);
+        //Then, we can only show that rows least one.
+        maxRowsPerPage -= 1;
+        return maxRowsPerPage;
+    }
+});
+
 const FolderIcon = new Lang.Class({
     Name: 'FolderIcon',
 
     _init: function(dir, parentView) {
         this._dir = dir;
-        this._parentView = parentView;
 
         this.actor = new St.Button({ style_class: 'app-well-app app-folder',
                                      button_mask: St.ButtonMask.ONE,
@@ -582,6 +803,7 @@ const FolderIcon = new Lang.Class({
                                      x_fill: true,
                                      y_fill: true });
         this.actor._delegate = this;
+        this._parentView = parentView;
 
         let label = this._dir.get_name();
         this.icon = new IconGrid.BaseIcon(label,
@@ -590,7 +812,6 @@ const FolderIcon = new Lang.Class({
         this.actor.label_actor = this.icon.label;
 
         this.view = new FolderView();
-        this.view.actor.reactive = false;
         _loadCategory(dir, this.view);
         this.view.loadGrid();
 
@@ -607,39 +828,126 @@ const FolderIcon = new Lang.Class({
     },
 
     _createIcon: function(size) {
-        return this.view.createFolderIcon(size);
+        return this.view.createFolderIcon(size, this);
+    },
+
+    _updatePopupPosition: function() {
+        if(this._popup) {
+            // Position the popup above or below the source icon
+            if (this._side == St.Side.BOTTOM) {
+                let closeButtonOffset = -this._popup.closeButton.translation_y;
+                let y = this.actor.y - this._popup.actor.fixed_height;
+                let yWithButton = y - closeButtonOffset;
+                this._popup.parentOffset = yWithButton < 0 ? -yWithButton : 0;
+                this._popup.actor.y = Math.max(y, closeButtonOffset);
+            } else {
+                this._popup.actor.y = this.actor.y + this.actor.height;
+            }
+        }
+    },
+
+    _popUpWidth: function() {
+        return this.view.usedWidth();
+    },
+
+    _popUpHeight: function() {
+        /*
+         * To maintain the grid of the collection aligned to the main grid, we have to
+         * make the same spacing to each element of the collection as the main grid has, except
+         * for the last row which has to take less space, since the grid of collection is inside a view with 
padding (the popup)
+         * and, the arrow of the popup is rising some pixels the collection, we have to calculate how much 
real spacing
+         * we have to let under/above the last/first arrow to make let the collection grid aligned with the 
main grid
+         */
+        let arrowHeight = this._popup._boxPointer.actor.get_theme_node().get_length('-arrow-rise');
+        let popupPadding = this._popup._boxPointer.bin.get_theme_node().get_length('padding');
+        //It will be negative value, so we have to rest it, instead of plust it.
+        let closeButtonOverlap = 
this._popup.closeButton.get_theme_node().get_length('-shell-close-overlap-y');
+        let closeButtonHeight = this._popup.closeButton.height;
+        let usedHeight = this.view.usedHeight();
+        // If we want it corrected aligned with the main grid the calculation will be: usedHeight - 
popupPadding - arrowHeight
+        // but, if we do that and the popup needs all the height, the popup will remain outside the 
allocation and then clipped. so:
+        if(this.view.nRowsDisplayedAtOnce() == this.view.maxRowsDisplayedAtOnce())
+            usedHeight = usedHeight - popupPadding * 2  - arrowHeight + closeButtonOverlap;
+        else
+            usedHeight =  usedHeight - popupPadding - arrowHeight;
+        return usedHeight;
+        
     },
 
     _ensurePopup: function() {
-        if (this._popup)
+        if(this._popup){
             return;
-
-        let spaceTop = this.actor.y;
-        let spaceBottom = this._parentView.actor.height - (this.actor.y + this.actor.height);
-        let side = spaceTop > spaceBottom ? St.Side.BOTTOM : St.Side.TOP;
-
-        this._popup = new AppFolderPopup(this, side);
-        this._parentView.addFolderPopup(this._popup);
-
-        // Position the popup above or below the source icon
-        if (side == St.Side.BOTTOM) {
-            this._popup.actor.show();
-            let closeButtonOffset = -this._popup.closeButton.translation_y;
-            let y = this.actor.y - this._popup.actor.height;
-            let yWithButton = y - closeButtonOffset;
-            this._popup.parentOffset = yWithButton < 0 ? -yWithButton : 0;
-            this._popup.actor.y = Math.max(y, closeButtonOffset);
-            this._popup.actor.hide();
         } else {
-            this._popup.actor.y = this.actor.y + this.actor.height;
-        }
-
-        this._popup.connect('open-state-changed', Lang.bind(this,
-            function(popup, isOpen) {
+            let absoluteActorYPosition = this.actor.get_transformed_position()[1];
+            let spaceTop = absoluteActorYPosition;
+            let spaceBottom = this.actor.get_stage().height - (absoluteActorYPosition + this.actor.height);
+            this._side = spaceTop > spaceBottom ? St.Side.BOTTOM : St.Side.TOP;
+            this._popup = new AppFolderPopup(this, this._side);
+            this._parentView.addFolderPopup(this._popup);
+            /**
+             * Why we need that: AppDiplay update width for the spacing for all
+             * views Allview and frequent view and folder views calcualte spacing
+             * with the items of icongrid with harcoded values
+             * 
+             * Open overview, then iconSizes changes in allview and frequent view
+             * icongrids, which is the actors who are added to the main AppDisplay.
+             * Then a relayout occurs. AppDiplay update width for the spacing for
+             * all views Allview and frequent view and folder views calcualte
+             * spacing with the items of icongrid, which allview and frequetn view
+             * has the new values, but folderview has the hardcoded values, since
+             * folderview icongrid is not still added to the main Actor, and then,
+             * they didn't emitted style changed signal with new valuesw of item
+             * sizes. Then, frequent view and all view has correct spacing and item
+             * size values, and fodler view has incorrect size and spacing values.
+             * Then, we click icon folder, a folderIcon popup is created and added
+             * to the parent actor, then the style changes, and item size changes,
+             * but spacing is the old one. Then, we calculate the position of the
+             * popup, but, the required height is with the old spacing and new item
+             * sizes, so the height is bigger, then the position is bad. Then,
+             * appDisplay allocate all views updating spacing, and set the good
+             * spacing to folder view, then allocate the folder view, but the
+             * positoon of the boxpointer is already calcualted with the old
+             * spacing, so the boxpointer is displaced.
+             * 
+             * Solution: ensure style of the grid just after we add it to the parent
+             * and before the calculation of the position.
+             */
+            this.view._grid.actor.ensure_style();
+            this.view.onUpdatedDisplaySize(this._displayWidth, this._displayHeight);
+
+            /*
+             * Always make the grid (and therefore the boxpointer) to be the max
+             * width it can be if it use full icon rows, althougth there's less
+             * icons than necesary to full the row. In that manner the popup will be
+             * more eye pleasant, fulling the parent view
+             */
+            this.view.actor.set_width(this._popUpWidth());
+
+            /*
+             * A folder view can only be, at a maximum, one row less than the parent
+             * view, so calculate the maximum rows it can have, and then deduct one,
+             * then calculate the maxUsedHeigth and the current Used height, if it
+             * is more, strech to the maxUsedHeight
+             */
+            let usedHeight = this._popUpHeight();
+            this.view.actor.set_height(this._popUpHeight());
+
+            this._updatePopupPosition();
+
+            this._popup.connect('open-state-changed', Lang.bind(this,
+                    function(popup, isOpen) {
                 if (!isOpen)
                     this.actor.checked = false;
             }));
+        }
     },
+
+    onUpdatedDisplaySize: function(width, height) {
+        this._displayWidth = width;
+        this._displayHeight = height;
+        this.view.onUpdatedDisplaySize(width, height);
+    },
+
 });
 
 const AppFolderPopup = new Lang.Class({
@@ -669,6 +977,7 @@ const AppFolderPopup = new Lang.Class({
                                                      { style_class: 'app-folder-popup-bin',
                                                        x_fill: true,
                                                        y_fill: true,
+                                                       x_expand: true,
                                                        x_align: St.Align.START });
 
         this._boxPointer.actor.style_class = 'app-folder-popup';
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index 416e659..2fa981e 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -4,6 +4,7 @@ const Clutter = imports.gi.Clutter;
 const Shell = imports.gi.Shell;
 const St = imports.gi.St;
 
+const Signals = imports.signals;
 const Lang = imports.lang;
 const Params = imports.misc.params;
 
@@ -182,10 +183,8 @@ const IconGrid = new Lang.Class({
         this._colLimit = params.columnLimit;
         this._xAlign = params.xAlign;
         this._fillParent = params.fillParent;
-
         this.actor = new St.BoxLayout({ style_class: 'icon-grid',
-                                        vertical: true });
-
+                                                                vertical: true });
         // Pulled from CSS, but hardcode some defaults here
         this._spacing = 0;
         this._hItemSize = this._vItemSize = ICON_SIZE;
@@ -208,7 +207,7 @@ const IconGrid = new Lang.Class({
         let nColumns = this._colLimit ? Math.min(this._colLimit,
                                                  nChildren)
                                       : nChildren;
-        let totalSpacing = Math.max(0, nColumns - 1) * this._spacing;
+        let totalSpacing = Math.max(0, nColumns - 1) * this.getSpacing();
         // Kind of a lie, but not really an issue right now.  If
         // we wanted to support some sort of hidden/overflow that would
         // need higher level design
@@ -216,27 +215,17 @@ const IconGrid = new Lang.Class({
         alloc.natural_size = nColumns * this._hItemSize + totalSpacing;
     },
 
-    _getVisibleChildren: function() {
-        let children = this._grid.get_children();
-        children = children.filter(function(actor) {
-            return actor.visible;
-        });
-        return children;
-    },
-
     _getPreferredHeight: function (grid, forWidth, alloc) {
         if (this._fillParent)
             // Ignore all size requests of children and request a size of 0;
             // later we'll allocate as many children as fit the parent
             return;
-
         let children = this._getVisibleChildren();
         let nColumns, spacing;
         if (forWidth < 0) {
             nColumns = children.length;
-            spacing = this._spacing;
         } else {
-            [nColumns, , spacing] = this._computeLayout(forWidth);
+            [nColumns, ] = this._computeLayout(forWidth);
         }
 
         let nRows;
@@ -246,14 +235,14 @@ const IconGrid = new Lang.Class({
             nRows = 0;
         if (this._rowLimit)
             nRows = Math.min(nRows, this._rowLimit);
-        let totalSpacing = Math.max(0, nRows - 1) * spacing;
+        let totalSpacing = Math.max(0, nRows - 1) * this.getSpacing();
         let height = nRows * this._vItemSize + totalSpacing;
         alloc.min_size = height;
         alloc.natural_size = height;
     },
 
     _allocate: function (grid, box, flags) {
-        if (this._fillParent) {
+        if(this._fillParent) {
             // Reset the passed in box to fill the parent
             let parentBox = this.actor.get_parent().allocation;
             let gridBox = this.actor.get_theme_node().get_content_box(parentBox);
@@ -263,8 +252,8 @@ const IconGrid = new Lang.Class({
         let children = this._getVisibleChildren();
         let availWidth = box.x2 - box.x1;
         let availHeight = box.y2 - box.y1;
-
-        let [nColumns, usedWidth, spacing] = this._computeLayout(availWidth);
+        let spacing = this.getSpacing();
+        let [nColumns, usedWidth] = this._computeLayout(availWidth);        
 
         let leftPadding;
         switch(this._xAlign) {
@@ -282,41 +271,25 @@ const IconGrid = new Lang.Class({
         let y = box.y1;
         let columnIndex = 0;
         let rowIndex = 0;
+
         for (let i = 0; i < children.length; i++) {
-            let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight]
-                = children[i].get_preferred_size();
-
-            /* Center the item in its allocation horizontally */
-            let width = Math.min(this._hItemSize, childNaturalWidth);
-            let childXSpacing = Math.max(0, width - childNaturalWidth) / 2;
-            let height = Math.min(this._vItemSize, childNaturalHeight);
-            let childYSpacing = Math.max(0, height - childNaturalHeight) / 2;
-
-            let childBox = new Clutter.ActorBox();
-            if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
-                let _x = box.x2 - (x + width);
-                childBox.x1 = Math.floor(_x - childXSpacing);
-            } else {
-                childBox.x1 = Math.floor(x + childXSpacing);
+            let childBox = this._calculateChildrenBox(children[i], x, y, box);
+            if(children[i].translate_y) {
+                childBox.y1 += children[i].translate_y;
+                childBox.y2 += children[i].translate_y;
             }
-            childBox.y1 = Math.floor(y + childYSpacing);
-            childBox.x2 = childBox.x1 + width;
-            childBox.y2 = childBox.y1 + height;
-
             if (this._rowLimit && rowIndex >= this._rowLimit ||
-                this._fillParent && childBox.y2 > availHeight) {
+                   this._fillParent && childBox.y2 >= availHeight) {
                 this._grid.set_skip_paint(children[i], true);
             } else {
                 children[i].allocate(childBox, flags);
                 this._grid.set_skip_paint(children[i], false);
             }
-
             columnIndex++;
             if (columnIndex == nColumns) {
                 columnIndex = 0;
                 rowIndex++;
             }
-
             if (columnIndex == 0) {
                 y += this._vItemSize + spacing;
                 x = box.x1 + leftPadding;
@@ -326,25 +299,33 @@ const IconGrid = new Lang.Class({
         }
     },
 
-    childrenInRow: function(rowWidth) {
-        return this._computeLayout(rowWidth)[0];
-    },
+    _calculateChildrenBox: function(child, x, y, box) {
+        let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight]
+        = child.get_preferred_size();
 
-    getRowLimit: function() {
-        return this._rowLimit;
+        /* Center the item in its allocation horizontally */
+        let width = Math.min(this._hItemSize, childNaturalWidth);
+        let childXSpacing = Math.max(0, width - childNaturalWidth) / 2;
+        let height = Math.min(this._vItemSize, childNaturalHeight);
+        let childYSpacing = Math.max(0, height - childNaturalHeight) / 2;
+
+        let childBox = new Clutter.ActorBox();
+        if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
+            let _x = box.x2 - (x + width);
+            childBox.x1 = Math.floor(_x - childXSpacing);
+        } else {
+            childBox.x1 = Math.floor(x + childXSpacing);
+        }
+        childBox.y1 = Math.floor(y + childYSpacing);
+        childBox.x2 = childBox.x1 + width;
+        childBox.y2 = childBox.y1 + height;
+        return childBox;
     },
 
     _computeLayout: function (forWidth) {
         let nColumns = 0;
         let usedWidth = 0;
-        let spacing = this._spacing;
-
-        if (this._colLimit) {
-            let itemWidth = this._hItemSize * this._colLimit;
-            let emptyArea = forWidth - itemWidth;
-            spacing = Math.max(this._spacing, emptyArea / (2 * this._colLimit));
-            spacing = Math.round(spacing);
-        }
+        let spacing = this.getSpacing();
 
         while ((this._colLimit == null || nColumns < this._colLimit) &&
                (usedWidth + this._hItemSize <= forWidth)) {
@@ -354,8 +335,7 @@ const IconGrid = new Lang.Class({
 
         if (nColumns > 0)
             usedWidth -= spacing;
-
-        return [nColumns, usedWidth, spacing];
+        return [nColumns, usedWidth];
     },
 
     _onStyleChanged: function() {
@@ -366,6 +346,62 @@ const IconGrid = new Lang.Class({
         this._grid.queue_relayout();
     },
 
+    _getVisibleChildren: function() {
+        let children = this._grid.get_children();
+        children = children.filter(function(actor) {
+            return actor.visible;
+        });
+        return children;
+    },
+
+    childrenInRow: function(rowWidth) {
+        return this._computeLayout(rowWidth)[0];
+    },
+
+    getRowLimit: function() {
+        return this._rowLimit;
+    },
+
+    nUsedRows: function(forWidth) {
+        let children = this._getVisibleChildren();
+        let nColumns;
+        if (forWidth < 0) {
+            nColumns = children.length;
+        } else {
+            [nColumns, ] = this._computeLayout(forWidth);
+        }
+        
+        let nRows;
+        if (nColumns > 0)
+            nRows = Math.ceil(children.length / nColumns);
+        else
+            nRows = 0;
+        if (this._rowLimit)
+            nRows = Math.min(nRows, this._rowLimit);
+        return nRows;
+    },
+
+    rowsForHeight: function(forHeight) {
+        let spacePerRow = this._vItemSize + this.getSpacing();
+        let rowsPerPage = Math.floor(forHeight / spacePerRow);
+        // Check if deleting spacing from bottom there's enough space for another row
+        let spaceWithOneMoreRow = (rowsPerPage + 1) * spacePerRow - this.getSpacing();
+        rowsPerPage = spaceWithOneMoreRow <= forHeight? rowsPerPage + 1 : rowsPerPage;
+        return rowsPerPage;
+    },
+
+    usedHeightForNRows: function(nRows) {
+        let spacePerRow = this._vItemSize + this.getSpacing();
+        return spacePerRow * nRows;
+    },
+
+    usedWidth: function(forWidth) {
+        let childrenInRow = this.childrenInRow(forWidth);
+        let usedWidth = childrenInRow  * (this._hItemSize + this.getSpacing());
+        usedWidth -= this.getSpacing();
+        return usedWidth;
+    },
+
     removeAll: function() {
         this._grid.destroy_all_children();
     },
@@ -383,5 +419,183 @@ const IconGrid = new Lang.Class({
 
     visibleItemsCount: function() {
         return this._grid.get_n_children() - this._grid.get_n_skip_paint();
+    },
+
+    setSpacing: function(spacing) {
+            this._fixedSpacing = spacing;
+    },
+
+    getSpacing: function() {
+        return this._fixedSpacing ? this._fixedSpacing : this._spacing;
+    },
+
+     /**
+     * This functions is intended to use before iconGrid allocation, to know how much spacing can we have at 
the grid
+     * but also to set manually the top/bottom rigth/left padding accordnly to the spacing calculated here.
+     * To take into account the spacing also for before the first row and for after the last row mark 
usingSurroundingSpacing true
+     * This function doesn't take into account the dynamic padding rigth now, since in fact we want to 
calculate also that.
+     */
+    maxSpacingForWidthHeight: function(availWidth, availHeight, minColumns, minRows, 
usingSurroundingSpacing) {
+        // Maximum spacing will be the icon item size. It doesn't make any sense to have more spacing than 
items.
+        let maxSpacing = Math.floor(Math.min(this._vItemSize, this._hItemSize));
+        let minEmptyVerticalArea = (availHeight - minRows * this._vItemSize);
+        let minEmptyHorizontalArea = (availWidth - minColumns * this._hItemSize);
+        let spacing;
+        if(usingSurroundingSpacing) {
+            // minRows + 1 because we want to put spacing before the first row, so it is like we have one 
more row
+            // to divide the empty space
+            let maxSpacingForRows = Math.floor(minEmptyVerticalArea / (minRows +1));
+            let maxSpacingForColumns = Math.floor(minEmptyHorizontalArea / (minColumns +1));
+            let spacingToEnsureMinimums = Math.min(maxSpacingForRows, maxSpacingForColumns);
+            let spacingNotTooBig = Math.min(spacingToEnsureMinimums, maxSpacing);
+            spacing = Math.max(this._spacing, spacingNotTooBig);
+        } else {
+            if(minRows == 1) {
+                let maxSpacingForRows = Math.floor(minEmptyVerticalArea / minRows);
+                let maxSpacingForColumns = Math.floor(minEmptyHorizontalArea / minColumns);
+            } else {
+                let maxSpacingForRows = Math.floor(minEmptyVerticalArea / (minRows - 1));
+                let maxSpacingForColumns = Math.floor(minEmptyHorizontalArea / (minColumns - 1));
+            }
+            let spacingToEnsureMinimums = Math.min(maxSpacingForRows, maxSpacingForColumns);
+            let spacingNotTooBig = Math.min(spacingToEnsureMinimums, maxSpacing);
+            spacing = Math.max(this._spacing, spacingNotTooBig); 
+        }
+        return spacing;
     }
 });
+Signals.addSignalMethods(IconGrid.prototype);
+
+const PaginatedIconGrid = new Lang.Class({
+    Name: 'PaginatedIconGrid',
+    Extends: IconGrid,
+
+    _init: function(params) {
+        this.parent(params);
+        this._nPages = 0;
+        //Set this variable properly before allocate function is called
+        this._viewForPageSize = null;
+    },
+
+    _getPreferredHeight: function (grid, forWidth, alloc) {
+        if(this._nPages) {
+            alloc.min_size = this.usedHeightPerPage() * this._nPages + this._spaceBetweenPagesTotal;
+            alloc.natural_size = this.usedHeightPerPage() * this._nPages + this._spaceBetweenPagesTotal;
+            return;
+        }
+        this.parent(grid, forWidth, alloc);
+    },
+
+    _allocate: function (grid, box, flags) {
+        if(this._fillParent) {
+            // Reset the passed in box to fill the parent
+            let parentBox = this.actor.get_parent().allocation;
+            let gridBox = this.actor.get_theme_node().get_content_box(parentBox);
+            box = this._grid.get_theme_node().get_content_box(gridBox);
+        }
+        let children = this._getVisibleChildren();
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+        let spacing = this.getSpacing();
+        let [nColumns, usedWidth] = this._computeLayout(availWidth);
+        
+        // ScrollView height
+        let parentBox = this._viewForPageSize.allocation;
+        let gridBox = this.actor.get_theme_node().get_content_box(parentBox);
+        let customBox = this._grid.get_theme_node().get_content_box(gridBox);
+        let availWidth = customBox.x2 - customBox.x1;
+        let availHeightPerPage = customBox.y2 - customBox.y1;
+        let nRows;
+        if (nColumns > 0)
+            nRows = Math.ceil(children.length / nColumns);
+        else
+            nRows = 0;
+        if (this._rowLimit)
+            nRows = Math.min(nRows, this._rowLimit);
+        let oldHeightUsedPerPage = this.usedHeightPerPage();
+        let oldNPages = this._nPages;
+        this._calculatePaginationValues(availHeightPerPage, nColumns, nRows);
+        // Take into account when the number of pages changed (then the height of the entire grid changed 
for sure)
+        // and also when the spacing is changed, sure the hegiht per page changed and the entire grid height 
changes, althougt
+        // maybe the number of pages doesn't change
+        if(oldNPages != this._nPages || oldHeightUsedPerPage != this.usedHeightPerPage()) {
+            this.emit('n-pages-changed', this._nPages);
+        }
+
+        let leftPadding;
+        switch(this._xAlign) {
+            case St.Align.START:
+                leftPadding = 0;
+                break;
+            case St.Align.MIDDLE:
+                leftPadding = Math.floor((availWidth - usedWidth) / 2);
+                break;
+            case St.Align.END:
+                leftPadding = availWidth - usedWidth;
+        }
+
+        let x = box.x1 + leftPadding;
+        let y = box.y1;
+        let columnIndex = 0;
+        let rowIndex = 0;
+
+        for (let i = 0; i < children.length; i++) {
+            let childBox = this._calculateChildrenBox(children[i], x, y, box);
+            if(children[i].translate_y) {
+                childBox.y1 += children[i].translate_y;
+                childBox.y2 += children[i].translate_y;
+            }
+            children[i].allocate(childBox, flags);
+            this._grid.set_skip_paint(children[i], false);
+            
+            columnIndex++;
+            if (columnIndex == nColumns) {
+                columnIndex = 0;
+                rowIndex++;
+            }
+            if (columnIndex == 0) {
+                y += this._vItemSize + spacing;
+                if((i + 1) % this._childrenPerPage == 0) {
+                    y+= this._spaceBetweenPages - spacing;
+                }
+                x = box.x1 + leftPadding;
+            } else {
+                x += this._hItemSize + spacing;
+            }
+        }
+    },
+
+    _calculatePaginationValues: function (availHeightPerPage, nColumns, nRows) {
+        let spacing = this.getSpacing();
+        this._spacePerRow = this._vItemSize + spacing;
+        this._rowsPerPage = Math.floor(availHeightPerPage / this._spacePerRow);
+        // Check if deleting spacing from bottom there's enough space for another row
+        let spaceWithOneMoreRow = (this._rowsPerPage + 1) * this._spacePerRow - spacing;
+        this._rowsPerPage = spaceWithOneMoreRow <= availHeightPerPage? this._rowsPerPage + 1 : 
this._rowsPerPage;
+        this._nPages = Math.ceil(nRows / this._rowsPerPage);
+        this._spaceBetweenPages = availHeightPerPage - this.usedHeightPerPage();
+        this._spaceBetweenPagesTotal = this._spaceBetweenPages * this._nPages;
+        this._childrenPerPage = nColumns * this._rowsPerPage;
+    },
+
+    usedHeightPerPage: function() {
+        return this._rowsPerPage * this._spacePerRow - this.getSpacing();
+    },
+
+    nPages: function() {
+        return this._nPages;
+    },
+
+    getPagePosition: function(pageNumber) {
+        if(!this._nPages)
+            return [0,0];
+        if(pageNumber < 0 || pageNumber > this._nPages) {
+            throw new Error('Invalid page number ' + pageNumber);
+        }
+        let childBox = this._getVisibleChildren()[pageNumber *
+                        this._childrenPerPage].get_allocation_box();
+
+        return [childBox.x1, childBox.y1];
+    }
+});
+


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