[gnome-shell/wip/paging-release: 6/12] IconGrid, AppDisplay: New implementation to allow pagination and collections work fine



commit dfaed10eea6911e791c93612de8fcfb991897044
Author: Carlos Soriano <carlos soriano89 gmail com>
Date:   Fri Jun 28 19:39:29 2013 +0200

    IconGrid, AppDisplay: New implementation to allow pagination and collections work fine

 data/theme/gnome-shell.css |    3 +-
 js/ui/appDisplay.js        |  599 +++++++++++++++++++++++++++++---------------
 js/ui/boxpointer.js        |    2 +-
 js/ui/iconGrid.js          |  220 +++++++++--------
 4 files changed, 513 insertions(+), 311 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index b08cbcf..c1be71b 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -881,7 +881,6 @@ StScrollBar StButton#vhandle:active {
 }
 
 .app-display {
-    padding: 8px;
     spacing: 20px;
 }
 
@@ -902,7 +901,7 @@ StScrollBar StButton#vhandle:active {
 .all-apps,
 .frequent-apps > StBoxLayout{
     /* horizontal padding to make sure scrollbars or dash don't overlap content */
-    padding: 10px 88px;
+    padding: 0px 88px 10px 88px;
 }
 
 .pages-icon-indicator {
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 3117e28..cb67ce4 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -32,6 +32,7 @@ const MENU_POPUP_TIMEOUT = 600;
 const MAX_COLUMNS = 6;
 
 const INACTIVE_GRID_OPACITY = 77;
+const INACTIVE_GRID_OPACITY_ANIMATION_TIME = 0.15;
 const FOLDER_SUBICON_FRACTION = .4;
 
 const MAX_APPS_PAGES = 20;
@@ -118,86 +119,6 @@ const AlphabeticalView = new Lang.Class({
     }
 });
 
-const FolderView = new Lang.Class({
-    Name: 'FolderView',
-
-    _init: function() {
-        this._grid = new IconGrid.IconGrid({ xAlign: St.Align.MIDDLE,
-            columnLimit: MAX_COLUMNS });
-     
-        this.actor = this._grid.actor;
-        // Standard hack for ClutterBinLayout
-        this._grid.actor.x_expand = true;
-
-        this._items = {};
-        this._allItems = [];
-    },
-
-    _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;
-    },
-    
-    removeAll: function() {
-        this._grid.removeAll();
-        this._items = {};
-        this._allItems = [];
-    },
-
-    _addItem: function(item) {
-        let id = this._getItemId(item);
-        if (this._items[id] !== undefined)
-            return null;
-
-        let itemIcon = this._createItemIcon(item);
-        this._allItems.push(item);
-        this._items[id] = itemIcon;
-
-        return itemIcon;
-    },
-
-    loadGrid: function() {
-        this._allItems.sort(this._compareItems);
-
-        for (let i = 0; i < this._allItems.length; i++) {
-            let id = this._getItemId(this._allItems[i]);
-            if (!id)
-                continue;
-            this._grid.addItem(this._items[id].actor);
-        }
-    }
-});
-
 const AppPages = new Lang.Class({
     Name: 'AppPages',
     Extends: AlphabeticalView,
@@ -206,6 +127,7 @@ const AppPages = new Lang.Class({
         this.parent();
         this.actor = this._grid.actor;
         this._parent = parent;
+        this._folderIcons = [];
     },
 
     _getItemId: function(item) {
@@ -220,9 +142,11 @@ const AppPages = 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;
     },
 
@@ -233,7 +157,26 @@ const AppPages = new Lang.Class({
         let nameB = GLib.utf8_collate_key(itemB.get_name(), -1);
         return (nameA > nameB) ? 1 : (nameA < nameB ? -1 : 0);
     },
-   
+    
+    updateIconOpacities: function(folderOpen) {
+        for (let id in this._items) {
+            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);
+            }
+        }
+    },
+    
     addItem: function(item) {
         this._addItem(item);
     },
@@ -246,31 +189,65 @@ const AppPages = new Lang.Class({
         return this._grid.getPagePosition(pageNumber);
     },
     
-    setGridParentSize: function(size) {
-        this._grid._parentSize = size;
+    setViewForPageSize: function(view) {
+        this._grid._viewForPageSize= view;
     },
     
     addFolderPopup: function(popup) {
         this._parent.addFolderPopup(popup);
+    },
+
+    removeAll: function() {
+        this._folderIcons = [];
+        this.parent();
+    },
+    
+    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);
+        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(width, height);
+        }
     }
 });
+
 const PaginationScrollView = new Lang.Class({
     Name: 'PaginationScrollView',
-    Extends: St.ScrollView,
+    Extends: St.Bin,
     
-    _init: function(parent) {
-        this.parent();
+    _init: function(parent, params) {
+        params['reactive'] = true;
+        this.parent(params);
+        this._verticalAdjustment = new St.Adjustment();
+        this._horizontalAdjustment = new St.Adjustment();
+
         this._stack = new St.Widget({layout_manager: new Clutter.BinLayout()});        
         this._box = new St.BoxLayout({vertical: true});
         this._pages = new AppPages(this);
+        this._pages.setViewForPageSize(this);
         
         this._stack.add_actor(this._pages.actor);
         this._eventBlocker = new St.Widget({ x_expand: true, y_expand: true });
         this._stack.add_actor(this._eventBlocker, {x_align:St.Align.MIDDLE});
         
         this._box.add_actor(this._stack);
+        this._box.set_adjustments(this._horizontalAdjustment, this._verticalAdjustment);
         this.add_actor(this._box);
-        
+
         this._currentPage = 0;
         this._parent = parent;
         
@@ -300,61 +277,28 @@ const PaginationScrollView = new Lang.Class({
     },
 
    vfunc_get_preferred_height: function (forWidht) {
-        let parentBox = this.get_parent().allocation;
-        let gridBox = this.get_theme_node().get_content_box(parentBox);
-        let availWidth = gridBox.x2 - gridBox.x1;
-        let availHeight = gridBox.y2 - gridBox.y1;
         return [0, 0];
     },
 
     vfunc_get_preferred_width: function(forHeight) {
-        let parentBox = this.get_parent().allocation;
-        let gridBox = this.get_theme_node().get_content_box(parentBox);
-        let availWidth = gridBox.x2 - gridBox.x1;
-        let availHeight = gridBox.y2 - gridBox.y1;
         return [0, 0];
     },
-    
-    vfunc_allocate: function(box, flags) {
-        box = this.get_parent().allocation;
-        this.set_allocation(box, flags);        
-        let availWidth = box.x2 - box.x1;
-        let availHeight = box.y2 - box.y1;
-        let childBox = new Clutter.ActorBox();
-        // Get the boxLayout inside scrollView
-        let child = this.get_children()[2];
-        childBox.x1 = 0;
-        childBox.y1 = 0;
-        childBox.x2 = availWidth;
-        childBox.y2 = availHeight;   
-
-        child.allocate(childBox, flags);
-    },
 
-    vfunc_get_preferred_height: function (container, forWidht) {
-        return [0, 0];
-    },
-
-    vfunc_get_preferred_width: function(container, forHeight) {
-        return [0, 0];
-    },
-
-    vfunc_allocate: function(box, flags) {
+    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();
-        // Get the boxLayout inside scrollView
-        let child = this.get_children()[2];
         childBox.x1 = 0;
         childBox.y1 = 0;
         childBox.x2 = availWidth;
         childBox.y2 = availHeight;   
+        this._box.allocate(childBox, flags);
         
-        this._pages.setGridParentSize([availWidth, availHeight]);
-        child.allocate(childBox, flags);
+        this._verticalAdjustment.page_size = availHeight;
+        this._verticalAdjustment.upper = this._stack.height;
     },
 
     goToPage: function(pageNumber, action) {
@@ -389,7 +333,7 @@ const PaginationScrollView = new Lang.Class({
                            time: time,
                            transition: 'easeOutQuad'
                           };
-            Tweener.addTween(this.vscroll.adjustment, params);
+            Tweener.addTween(this._verticalAdjustment, params);
         }
     },
 
@@ -402,7 +346,7 @@ const PaginationScrollView = new Lang.Class({
     },
 
     _diffToPage: function (pageNumber) {
-        let currentScrollPosition = this.vscroll.adjustment.value;
+        let currentScrollPosition = this._verticalAdjustment.value;
         return Math.abs(currentScrollPosition - this._pages._grid.getPagePosition(pageNumber)[1]);
     },
     
@@ -447,23 +391,14 @@ const PaginationScrollView = new Lang.Class({
                 function(popup, isOpen) {
                     this._eventBlocker.reactive = isOpen;
                     this._currentPopup = isOpen ? popup : null;
-                    this._updateIconOpacities(isOpen);
+                    this._pages.updateIconOpacities(isOpen);
                 }));
     },
-
-    _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;
-        }
-    },
     
     _onPan: function(action) {
         this._clickAction.release();
         let [dist, dx, dy] = action.get_motion_delta(0);
-        let adjustment = this.vscroll.adjustment;
+        let adjustment = this._verticalAdjustment;
         adjustment.value -= (dy / this.height) * adjustment.page_size;
         return false;
     },
@@ -478,6 +413,18 @@ const PaginationScrollView = new Lang.Class({
             }
         } else
             this._parent.goToPage(this._currentPage, action);
+    },
+    
+    onUpdatedDisplaySize: function(width, height) {
+        let box = new Clutter.ActorBox();
+        box.x1 = 0;
+        box.x2 = width;
+        box.y1 = 0;
+        box.y2 = height;
+        box = this.get_theme_node().get_content_box(box);
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+        this._pages.onUpdatedDisplaySize(availWidth, availHeight);
     }
     
 });
@@ -574,7 +521,7 @@ const PaginationIndicator = new Lang.Class({
         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'); }));
@@ -590,8 +537,8 @@ const AllView = new Lang.Class({
         this._paginationIndicator = new PaginationIndicator({style_class: 'pages-indicator'});
         this._paginationIndicator._nPages = 0;
         let layout = new Clutter.BinLayout();
-        this.actor = new Shell.GenericContainer({ layout_manager: layout, 
-                                                  x_expand:true, y_expand:true });
+        this.actor = new St.Widget({ layout_manager: layout, 
+                                     x_expand:true, y_expand:true });
         layout.add(this._paginationView, 2,2);
         if(Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
             layout.add(this._paginationIndicator.actor, 2,2);
@@ -604,26 +551,8 @@ const AllView = new Lang.Class({
             }
             this._paginationIndicator.actor.add_actor(indicatorIcon.actor);
         }
-        this.actor.connect('allocate', Lang.bind(this, this._allocate));
-    },
 
-    _allocate: function(widget, box, flags) {
-        let children = this.actor.get_children();
-        this._paginationView.allocate(box, flags);
-        
-        let nPages = this._paginationView.nPages();
-        this._paginationIndicatorLayout._nPages = nPages;
-        let availWidth = box.x2 - box.x1;
-        let availHeight = box.y2 - box.y1;
-        let childBox = new Clutter.ActorBox();
-        let [minWidth, natWidth] = this._paginationIndicator.get_preferred_width(availHeight);
-        childBox.x1 = availWidth - natWidth;
-        childBox.x2 = availWidth;
-        childBox.y1 = 0;
-        childBox.y2 = availHeight;
-
-        this._paginationIndicator.allocate(childBox, flags);
-        
+        this._paginationView._pages._grid.connect('n-pages-changed', Lang.bind(this, this._updatedNPages));
     },
 
     _updatedNPages: function(iconGrid, nPages) {
@@ -660,10 +589,6 @@ const AllView = new Lang.Class({
          * Lang.bind(this, this._ensureIconVisible));
          */
     },
-
-    addFolderPopup: function(popup) {
-        this._paginationView.addFolderPopup(popup);
-    },
    
     removeAll: function() {
         this._paginationView._pages.removeAll();
@@ -772,6 +697,30 @@ const ControlsBoxLayout = Lang.Class({
     }
 });
 
+const AppDisplayActor = new Lang.Class({
+    Name: 'AppDisplayActor',
+    Extends: Clutter.BoxLayout,
+    
+    vfunc_allocate: function (actor, box, flags) {
+        let availWidth = box.x2 - box.x1;
+        let availHeight = box.y2 - box.y1;
+        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(AppDisplayActor.prototype);
+
 const AppDisplay = new Lang.Class({
     Name: 'AppDisplay',
 
@@ -807,21 +756,22 @@ 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,
+        this.actor = new St.Widget({ style_class: 'app-display',
                                         x_expand: true, y_expand: true });
+        this._actorLayout = new AppDisplayActor({vertical: true});
+        this.actor.set_layout_manager(this._actorLayout);
+        this._actorLayout.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 });
-
+        //FIXME
+        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_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);
             this._controls.add_actor(this._views[i].control);
@@ -840,9 +790,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) {
@@ -923,9 +873,16 @@ const AppDisplay = new Lang.Class({
     },
     
     _onUpdatedDisplaySize: function(actor, width, height) {
-        //FIXME
+        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);
+        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(width, height);
+            this._views[i].view.onUpdatedDisplaySize(availWidth, availHeight);
         }
     }
 });
@@ -986,12 +943,148 @@ const AppSearchProvider = new Lang.Class({
     }
 });
 
+const FolderView = new Lang.Class({
+    Name: 'FolderView',
+
+    _init: function(parentView) {
+        this._grid = new IconGrid.IconGrid({ xAlign: St.Align.MIDDLE,
+            columnLimit: MAX_COLUMNS });
+        this._parentView = parentView;
+
+        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);
+        this._items = {};
+        this._allItems = [];
+    },
+
+    _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;
+    },
+    
+    removeAll: function() {
+        this._grid.removeAll();
+        this._items = {};
+        this._allItems = [];
+    },
+
+    _addItem: function(item) {
+        let id = this._getItemId(item);
+        if (this._items[id] !== undefined)
+            return null;
+
+        let itemIcon = this._createItemIcon(item);
+        this._allItems.push(item);
+        this._items[id] = itemIcon;
+
+        return itemIcon;
+    },
+
+    loadGrid: function() {
+        this._allItems.sort(this._compareItems);
+
+        for (let i = 0; i < this._allItems.length; i++) {
+            let id = this._getItemId(this._allItems[i]);
+            if (!id)
+                continue;
+            this._grid.addItem(this._items[id].actor);
+        }
+    },
+    
+    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,
@@ -1000,6 +1093,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,
@@ -1008,7 +1102,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();
 
@@ -1025,39 +1118,135 @@ 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;
+        
     },
 
+    makeSpaceForPopUp: function() {
+        //this._parentView.makeSpaceForPopUp(this._side, rows);
+    },
+    
+    onCompletemakeSpaceForPopUp: function() {
+        this._popup.toggle();
+    },
+    
     _ensurePopup: function() {
-        if (this._popup)
+        if(this._popup){
+            this.makeSpaceForPopUp();
             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({
@@ -1082,6 +1271,7 @@ const AppFolderPopup = new Lang.Class({
                                      y_expand: true,
                                      x_align: Clutter.ActorAlign.CENTER,
                                      y_align: Clutter.ActorAlign.START });
+
         this._boxPointer = new BoxPointer.BoxPointer(this._arrowSide,
                                                      { style_class: 'app-folder-popup-bin',
                                                        x_fill: true,
@@ -1133,10 +1323,8 @@ const AppFolderPopup = new Lang.Class({
 
         this.actor.show();
         this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
-
         this._boxPointer.setArrowActor(this._source.actor);
-        this._boxPointer.show(BoxPointer.PopupAnimation.FADE |
-                              BoxPointer.PopupAnimation.SLIDE);
+        this._boxPointer.show(BoxPointer.PopupAnimation.FADE);
 
         this._isOpen = true;
         this.emit('open-state-changed', true);
@@ -1146,8 +1334,7 @@ const AppFolderPopup = new Lang.Class({
         if (!this._isOpen)
             return;
 
-        this._boxPointer.hide(BoxPointer.PopupAnimation.FADE |
-                              BoxPointer.PopupAnimation.SLIDE);
+        this._boxPointer.hide(BoxPointer.PopupAnimation.FADE);
         this._isOpen = false;
         this.emit('open-state-changed', false);
     }
diff --git a/js/ui/boxpointer.js b/js/ui/boxpointer.js
index 2fd1a67..d6d3f84 100644
--- a/js/ui/boxpointer.js
+++ b/js/ui/boxpointer.js
@@ -219,7 +219,7 @@ const BoxPointer = new Lang.Class({
                 break;
         }
         this.bin.allocate(childBox, flags);
-
+        
         if (this._sourceActor && this._sourceActor.mapped) {
             this._reposition();
             this._updateFlip();
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index 8f4b4d1..048866c 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -3,7 +3,9 @@
 const Clutter = imports.gi.Clutter;
 const Shell = imports.gi.Shell;
 const St = imports.gi.St;
+const Meta = imports.gi.Meta;
 
+const Signals = imports.signals;
 const Lang = imports.lang;
 const Params = imports.misc.params;
 
@@ -187,8 +189,8 @@ const IconGrid = new Lang.Class({
         
         if(this._usePagination) {
             this._nPages = 0;
-            //Set this variable properly before getPreferredHeight function is called
-            this._parentSize = [0, 0];
+            //Set this variable properly before allocate function is called
+            this._viewForPageSize = null;
             this._firstPagesItems = [];
         }
         this.actor = new St.BoxLayout({ style_class: 'icon-grid',
@@ -217,7 +219,8 @@ 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 spacing = this._fixedSpacing ? this._fixedSpacing : this._spacing;
+        let totalSpacing = Math.max(0, nColumns - 1) * spacing;
         // 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
@@ -243,11 +246,12 @@ const IconGrid = new Lang.Class({
         let nColumns, spacing;
         if (forWidth < 0) {
             nColumns = children.length;
-            spacing = this._spacing;
         } else {
-            [nColumns, , spacing] = this._computeLayoutOld(forWidth);
+            [nColumns, ] = this._computeLayout(forWidth);
         }
-
+        
+        let spacing = this._fixedSpacing ? this._fixedSpacing : this._spacing;
+        
         let nRows;
         if (nColumns > 0)
             nRows = Math.ceil(children.length / nColumns);
@@ -258,16 +262,9 @@ const IconGrid = new Lang.Class({
         let totalSpacing = Math.max(0, nRows - 1) * spacing;
         let height = nRows * this._vItemSize + totalSpacing;
         
-        if(this._usePagination) {
-            
-            this._spacePerRow = this._vItemSize + spacing;
-            this._rowsPerPage = Math.floor(this._parentSize[1] / this._spacePerRow);
-            this._nPages = Math.ceil(nRows / this._rowsPerPage);
-            this._spaceBetweenPages = this._parentSize[1] - (this._rowsPerPage * (this._vItemSize + 
spacing));
-            let spaceBetweenPagesTotal = this._spaceBetweenPages * (this._nPages);
-            this._childrenPerPage = nColumns * this._rowsPerPage;
-            alloc.min_size = this._rowsPerPage * this._spacePerRow * this._nPages + spaceBetweenPagesTotal;
-            alloc.natural_size = this._rowsPerPage * this._spacePerRow * this._nPages + 
spaceBetweenPagesTotal;
+        if(this._usePagination && this._nPages) {
+            alloc.min_size = this._rowsPerPage * this._spacePerRow * this._nPages + 
this._spaceBetweenPagesTotal;
+            alloc.natural_size = this._rowsPerPage * this._spacePerRow * this._nPages + 
this._spaceBetweenPagesTotal;
             return;
         }
         alloc.min_size = height;
@@ -281,16 +278,40 @@ const IconGrid = new Lang.Class({
             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 [nColumns, usedWidth, spacing] = this._computeLayoutOld(availWidth);
+        let spacing = this._fixedSpacing ? this._fixedSpacing : this._spacing;
+        let [nColumns, usedWidth] = this._computeLayout(availWidth);        
         if(this._usePagination) {
-            //Recalculate the space between pages with the new spacing
-            this._spaceBetweenPages = this._parentSize[1] - (this._rowsPerPage * (this._vItemSize + 
spacing));
+            // 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);
+                /*Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
+                    this._grid.queue_relayout();
+                    return false;
+                }));*/
+            }
         }
-
         let leftPadding;
         switch(this._xAlign) {
             case St.Align.START:
@@ -312,10 +333,14 @@ const IconGrid = new Lang.Class({
             this._firstPagesItems = [children[0]];
         }
         for (let i = 0; i < children.length; i++) {
-            let childBox = this._calculateChildrenBox(children[i], x, y);
+            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;
+            }
             if(!this._usePagination) {
                 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);
@@ -349,8 +374,21 @@ const IconGrid = new Lang.Class({
         }
         
     },
-
-    _calculateChildrenBox: function(child, x, y) {
+    
+    _calculatePaginationValues: function (availHeightPerPage, nColumns, nRows) {
+        let spacing = this._fixedSpacing ? this._fixedSpacing : this._spacing;
+        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._rowsPerPage * (this._vItemSize + spacing));
+        this._spaceBetweenPagesTotal = this._spaceBetweenPages * (this._nPages);
+        this._childrenPerPage = nColumns * this._rowsPerPage;
+    },
+    
+    _calculateChildrenBox: function(child, x, y, box) {
         let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight]
         = child.get_preferred_size();
 
@@ -374,102 +412,66 @@ const IconGrid = new Lang.Class({
     },
     
     childrenInRow: function(rowWidth) {
-        return this._computeLayout(rowWidth)[0]
+        return this._computeLayout(rowWidth)[0];
     },
 
     getRowLimit: function() {
         return this._rowLimit;
     },
     
-    _computeLayoutOldOld: 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);
-        }
-
-        while ((this._colLimit == null || nColumns < this._colLimit) &&
-               (usedWidth + this._hItemSize <= forWidth)) {
-            usedWidth += this._hItemSize + spacing;
-            nColumns += 1;
+    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)
-            usedWidth -= spacing;
-
-        return [nColumns, usedWidth, spacing];
+            nRows = Math.ceil(children.length / nColumns);
+        else
+            nRows = 0;
+        if (this._rowLimit)
+            nRows = Math.min(nRows, this._rowLimit);
+        return nRows;
     },
     
-    _computeLayoutOld: function (forWidth, forHeight) {
+    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;
+    },
+    
+    _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));
-            // We have to care that new spacing must not change number of rows per page.
-            if(this._usePagination) {
-                let spaceBetweenPages = this._parentSize[1] - (this._rowsPerPage * (this._vItemSize + 
spacing));
-                if(spaceBetweenPages < 0) {
-                    spacing += spaceBetweenPages / this._rowsPerPage;
-                }
-            }
-            spacing = Math.floor(spacing);
-        }
+        let spacing = this._fixedSpacing ? this._fixedSpacing : this._spacing;
 
         while ((this._colLimit == null || nColumns < this._colLimit) &&
                (usedWidth + this._hItemSize <= forWidth)) {
             usedWidth += this._hItemSize + spacing;
             nColumns += 1;
         }
-
-        if (nColumns > 0)
-            usedWidth -= spacing;
-
-        return [nColumns, usedWidth, spacing];
-    
-    },
-    _computeLayoutNew: function (forWidth, forHeight) {
-        let nColumns = 0;
-        let usedWidth = 0;
-        let spacing = this._spacing;
-        
-        let spacePerRow = this._vItemSize + spacing;
-        let rowsPerPage = Math.floor(forHeight / spacePerRow);
-        let itemHeithg = this._vItemSize * rowsPerPage;
-        let emptyHeigthArea = forHeight - itemHeithg;
-        let spacingForHeight = Math.max(this._spacing, emptyHeigthArea / (2 * rowsPerPage));
-        
-        let spacePerColumn = this._hItemSize + spacing;
-        let columnsPerPage;
-        if(this._colLimit) {
-            columnsPerPage = this._colLimit;
-        } else {
-            columnsPerPage = Math.floor(forWidth / spacePerColumn);
-        }
-        if(columnsPerPage == 0) {
-            return [0, 0, this._spacing];
-        }
-        let itemWidth = this._hItemSize * columnsPerPage;
-        let emptyWidthArea = forWidth - itemWidth;
-        let spacingForWidth = Math.max(this._spacing, emptyWidthArea / (2 * columnsPerPage));
-        
-        spacing = Math.max(this._spacing, Math.min(spacingForHeight, spacingForWidth));
-        
-        usedWidth = columnsPerPage * (this._hItemSize + spacing);
-        nColumns = columnsPerPage;
-
         if (nColumns > 0)
             usedWidth -= spacing;
-
-        return [nColumns, usedWidth, spacing];
+        return [nColumns, usedWidth];
     },
 
     _onStyleChanged: function() {
@@ -513,5 +515,19 @@ const IconGrid = new Lang.Class({
         }
         let childBox = this._firstPagesItems[pageNumber].get_allocation_box();
         return [childBox.x1, childBox.y1];
-    }
+    },
+    
+    setSpacing: function(spacing) {
+            this._fixedSpacing = spacing;
+            /*Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
+                this._grid.queue_relayout();
+                return false;
+            }));*/
+    },
+    
+    getSpacing: function() {
+        return this._fixedSpacing ? this._fixedSpacing : this._spacing;
+    },
+    
 });
+Signals.addSignalMethods(IconGrid.prototype);



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