[gnome-shell/wip/paging-release: 9/12] New collection implementation, contained inside parent view



commit e70a72070d5f6a719e632ef0ca21651a7876d366
Author: Carlos Soriano <carlos soriano89 gmail com>
Date:   Tue Aug 13 13:20:12 2013 +0200

    New collection implementation, contained inside parent view
    
    testing
    
    Fixed folder view not expading when we change screen resolution from a
    small resolution to a bigger resolution
    
    Fixed special case when we are at last page and only one row is shown
    and we have a folder. We have to not animate here and directly show the
    collection view.
    
    Fixed bad collection view width on small resolutions due to the padding
    and close button of the popup
    
    Fix bad boxpointer width on small resolutiosn due to the boxpointer paddings
    
    Fixed bad calculus of nRows on collection view, since we have to base
    the calculations on parent spacing, not the colection spacing.
    
    AppDisplay: typo
    
    Little clean up
    
    Add comments and "Florian reviews" fixmes
    
    Fixed not updating arrow side when boxpointer doesn't change size (and
    then it doesn't reallocate items and doesn't take into account the new
    updated arrow side) but it changes the arrow side because of the
    resolution change.
    Fixed it queuing a redrawing on the border of the boxpointer
    
    Don't remember collection view scroll positions when between calls
    
    Fixed special folder view case when folder icon is in the last page,
    there's empty rows, but the icon folder has to expand up.
    
    all views are again alphabetical views
    
    AlphabeticalView: space typo
    
    IconGrid: lefPAding to leftEmpySpace

 js/ui/appDisplay.js |  424 +++++++++++++++++++++++++++++++--------------------
 js/ui/iconGrid.js   |  173 +++++++++++++---------
 2 files changed, 359 insertions(+), 238 deletions(-)
---
diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js
index 6ebaffe..a49360e 100644
--- a/js/ui/appDisplay.js
+++ b/js/ui/appDisplay.js
@@ -30,6 +30,8 @@ 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.40;
@@ -67,10 +69,14 @@ const AlphabeticalView = new Lang.Class({
     Name: 'AlphabeticalView',
     Abstract: true,
 
-    _init: function() {
-        this._grid = new IconGrid.IconGrid({ xAlign: St.Align.MIDDLE,
-                                             usePagination: true,
-                                             columnLimit: MAX_COLUMNS });
+    _init: function(gridParams) {
+        gridParams = Params.parse(gridParams,  { xAlign: St.Align.MIDDLE,
+                                                 columnLimit: MAX_COLUMNS,
+                                                 minRows: MIN_ROWS,
+                                                 minColumns: MIN_COLUMNS,
+                                                 usePagination: false,
+                                                 useSurroundingSpacing: false });
+        this._grid = new IconGrid.IconGrid(gridParams);
 
         // Standard hack for ClutterBinLayout
         this._grid.actor.x_expand = true;
@@ -126,7 +132,8 @@ const AppPages = new Lang.Class({
     Extends: AlphabeticalView,
    
     _init: function(parent) {
-        this.parent();
+        this.parent({ usePagination: true,
+                      useSurroundingSpacing: true });
         this.actor = this._grid.actor;
         this._parent = parent;
         this._folderIcons = [];
@@ -200,6 +207,10 @@ const AppPages = new Lang.Class({
         this._parent.addFolderPopup(popup);
     },
 
+    removeFolderPopUp: function(popup) {
+        this._parent.removeFolderPopUp(popup);
+    },
+
     /**
      * Pan view with items to make space for the folder view.
      * @param folderNVisibleRowsAtOnce this parameter tell how many rows the folder view has, but,
@@ -253,11 +264,26 @@ const AppPages = new Lang.Class({
                 panViewUpNRows = folderNVisibleRowsAtOnce - rowsDown.length - emptyRows;
             }
         }
-        this._panViewForFolderView(rowsUp, rowsDown, panViewUpNRows, panViewDownNRows, iconActor);
+        // Especial case, last page and no rows below the icon of the folder, no rows down neither rows up,
+        // we call directly the popup
         this.updateIconOpacities(true);
+        if(panViewDownNRows > 0 && rowsDown.length == 0 && rowsUp.length == 0) {
+            this.displayingPopup = true;
+            this._popupExpansionNeeded = false;
+            iconActor.onCompleteMakeSpaceForPopUp();            
+        } else {
+            this._popupExpansionNeeded = true;
+            this._panViewForFolderView(rowsUp, rowsDown, panViewUpNRows, panViewDownNRows, iconActor);
+        }    
     },
     
     returnSpaceToOriginalPosition: function() {
+        this.updateIconOpacities(false);
+        if(!this._popupExpansionNeeded) {
+            this.displayingPopup = false;
+            return;
+        }
+        
         if(this._translatedRows) {
             for(let rowId in this._translatedRows) {
                 for(let childrenId in this._translatedRows[rowId]) {
@@ -273,7 +299,6 @@ const AppPages = new Lang.Class({
                 }
             }
         }
-        this.updateIconOpacities(false);
     },
     
     _panViewForFolderView: function(rowsUp, rowsDown, panViewUpNRows, panViewDownNRows, iconActor) {
@@ -289,7 +314,7 @@ const AppPages = new Lang.Class({
                                           onUpdate: function() {this.queue_relayout();},
                                           transition: 'easeInOutQuad' };
                     if((rowId == rowsUp.length - 1) && (childrenId == rowsUp[rowId].length - 1)) {
-                            tweenerParams['onComplete'] = Lang.bind(iconActor, 
iconActor.onCompleteMakeSpaceForPopUp);
+                        tweenerParams['onComplete'] = Lang.bind(iconActor, 
iconActor.onCompleteMakeSpaceForPopUp);
                     }
                     Tweener.addTween(rowsUp[rowId][childrenId], tweenerParams);
                 }
@@ -370,6 +395,9 @@ const PaginationScrollView = new Lang.Class({
         // 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.
+        // In fact it is not necesary with the current state of the gnome-shell since we can only change the 
resolution outside the overview,
+        // so sicne we always start at page 0 of pagination when entering overview,we always already did 
that adjustment. BUT, who knows,
+        // since it is a diferent case from the other, better to take it into account for future changes.
         this.invalidatePagination = false;
         
         this.connect('scroll-event', Lang.bind(this, this._onScroll));
@@ -421,6 +449,7 @@ const PaginationScrollView = new Lang.Class({
         this._verticalAdjustment.page_size = availHeight;
         this._verticalAdjustment.upper = this._stack.height;
         if(this.invalidatePagination) {
+            //FLORIAN REVIEW
             // we can modify our adjustment, so we do that to show the first page, but we can't modify the 
indicators,
             // so we modify it before redraw (we won't see too much flickering at all)
             if(this._pages.nPages() > 1) {
@@ -523,13 +552,18 @@ const PaginationScrollView = new Lang.Class({
     
     addFolderPopup: function(popup) {
         this._stack.add_actor(popup.actor);
-        popup.connect('open-state-changed', Lang.bind(this,
+        popup.openStateId = popup.connect('open-state-changed', Lang.bind(this,
                 function(popup, isOpen) {
                     this._eventBlocker.reactive = isOpen;
                     this._currentPopup = isOpen ? popup : null;
                 }));
     },
     
+    removeFolderPopUp: function(popup) {
+        popup.disconnect(popup.openStateId);
+        this._stack.remove_child(popup.actor);     
+    },
+    
     _onPan: function(action) {
         this._clickAction.release();
         if(this._pages.displayingPopup)
@@ -564,6 +598,7 @@ const PaginationScrollView = new Lang.Class({
         let availWidth = box.x2 - box.x1;
         let availHeight = box.y2 - box.y1;
         this._pages.onUpdatedDisplaySize(availWidth, availHeight);
+        //this.invalidatePagination = true;
     }
     
 });
@@ -712,18 +747,10 @@ const AllView = new Lang.Class({
 
     addApp: function(app) {
        let appIcon = this._paginationView._pages.addItem(app);
-        /*
-         * if (appIcon) appIcon.actor.connect('key-focus-in', Lang.bind(this,
-         * this._ensureIconVisible));
-         */
     },
 
     addFolder: function(dir) {
         let folderIcon = this._paginationView._pages.addItem(dir);
-        /*
-         * if (folderIcon) folderIcon.actor.connect('key-focus-in',
-         * Lang.bind(this, this._ensureIconVisible));
-         */
     },
    
     removeAll: function() {
@@ -735,6 +762,9 @@ const AllView = new Lang.Class({
     },
     
     goToPage: function(index, action) {
+        // Since we call this function also from shown signal of the overview, we can't assure the 
pagination is already calculated
+        // so we first ask pagination if it has some page, if not, we return and that's it. (nevermind, 
because when creating for first time
+        // pagination, that is when it can happens, paginations starts at page 0 already, so no problem here)
         if(!this._paginationView.nPages())
             return;
         // Since it can happens after a relayout, we have to ensure that all is unchecked
@@ -837,9 +867,9 @@ const ControlsBoxLayout = Lang.Class({
 
 const AppDisplayActor = new Lang.Class({
     Name: 'AppDisplayActor',
-    Extends: Clutter.BoxLayout,
+    Extends: Clutter.BinLayout,
     
-    vfunc_allocate: function (actor, box, flags) {
+    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
@@ -898,12 +928,13 @@ const AppDisplay = new Lang.Class({
 
         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._viewStackLayout = new AppDisplayActor();
+        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(),
+        this._viewStack = new St.Widget({ 
                                           x_expand: true, y_expand: true });
+        this._viewStack.set_layout_manager(this._viewStackLayout);
         //FIXME
         this.actor.add_actor(this._viewStack, { expand: true });
         let layout = new ControlsBoxLayout({ homogeneous: true });
@@ -1085,21 +1116,31 @@ const AppSearchProvider = new Lang.Class({
 
 const FolderView = new Lang.Class({
     Name: 'FolderView',
+    Extends: AlphabeticalView,
 
-    _init: function(parentView) {
-        this._grid = new IconGrid.IconGrid({ xAlign: St.Align.MIDDLE,
-            columnLimit: MAX_COLUMNS });
-        this._parentView = parentView;
-
+    _init: function() {
+        this.parent({ useSurroundingSpacing: true });
+
+        /*this._grid = new IconGrid.IconGrid({ xAlign: St.Align.MIDDLE,
+                                             columnLimit: MAX_COLUMNS,
+                                             minRows: MIN_ROWS,
+                                             minColumns: MIN_COLUMNS,
+                                             useSurroundingSpacing: true});*/
+        // 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._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 = [];
+        
+        this._boxPointerOffsets = {};
     },
 
     _getItemId: function(item) {
@@ -1136,46 +1177,27 @@ const FolderView = new Lang.Class({
 
         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);
+        let spacing = this._grid.maxSpacingForWidthHeight(width, height, MIN_COLUMNS, MIN_ROWS, true);
         this._grid.setSpacing(spacing);
+        if(!Object.keys(this._boxPointerOffsets).length)
+            return;
+        //We put the normal padding as spacing as we have in the main grid to do well the calculations for 
used rows, used columns etc, since
+        // it is the behaviour we want to emulate. After that we will put the correct padding from 
calculations of the boxpointer offsets, to ensure
+        //the boxpointer will be contained inside the view
+        this._parentSpacing = spacing;
+        
+        let boxPointerTotalOffset = this._boxPointerOffsets['arrowHeight'] + 
this._boxPointerOffsets['padding'] * 2 + this._boxPointerOffsets['closeButtonOverlap'];
+        let offsetForEachSide = Math.ceil(boxPointerTotalOffset / 2);
+        this._offsetForEachSide = offsetForEachSide;
+        this._grid.top_padding = spacing - offsetForEachSide;
+        this._grid.bottom_padding = spacing - offsetForEachSide;
+        this._grid.left_padding = spacing - offsetForEachSide;
+        this._grid.right_padding = spacing - offsetForEachSide;
     },
     
     _containerBox: function() {
@@ -1190,19 +1212,37 @@ const FolderView = new Lang.Class({
     usedWidth: function() {
         let box = this._containerBox();
         let availWidthPerPage = box.x2 - box.x1;
+        // Since we want to do the calculation of the real width of the grid
+        // taking into account the parent behaviour, we have to substract from the
+        // avail width the padding we subsctratc before to the folder view
+        // in its surrounding spacings
+        availWidthPerPage -= 2 * this._offsetForEachSide;
         let maxUsedWidth = this._grid.usedWidth(availWidthPerPage);
         return maxUsedWidth;
     },
     
     usedHeight: function() {
         // Then calculate the real maxUsedHeight
-        return this._grid.usedHeightForNRows(this.nRowsDisplayedAtOnce());
+        return this._grid.usedHeightForNRows(this.nRowsDisplayedAtOnce()) + this._grid.top_padding + 
this._grid.bottom_padding;
     },   
     
     nRowsDisplayedAtOnce: function() {
         let box = this._containerBox();
         let availHeightPerPage = box.y2 - box.y1;
         let availWidthPerPage = box.x2 - box.x1;
+        global.log("///////////////////////////");
+        //FIXME: if we do that, we really not showing the maximum of rows we can show on collection view
+        // Since we want to do the calculation of the real width of the grid
+        // taking into account the parent behaviour, we have to substract from the
+        // avail width the padding we subsctratc before to the folder view
+        // in its surrounding spacings
+        global.log("Avail height BEF" + availHeightPerPage);
+        availWidthPerPage -= 2 * this._offsetForEachSide;
+        availHeightPerPage -= 2 * this._offsetForEachSide;
+        
+        global.log("Avail height " + availHeightPerPage);
+        global.log("///////////////////////////");
+
         let maxRowsDisplayedAtOnce = this.maxRowsDisplayedAtOnce();
         let usedRows = this._grid.nUsedRows(availWidthPerPage);
         usedRows = usedRows <= maxRowsDisplayedAtOnce ? usedRows : maxRowsDisplayedAtOnce;
@@ -1212,11 +1252,32 @@ const FolderView = new Lang.Class({
     maxRowsDisplayedAtOnce: function() {
         let box = this._containerBox();
         let availHeightPerPage = box.y2 - box.y1;
-        let availWidthPerPage = box.x2 - box.x1;
+      //FIXME: if we do that, we really not showing the maximum of rows we can show on collection view
+        // Since we want to do the calculation of the real width of the grid
+        // taking into account the parent behaviour, we have to substract from the
+        // avail width the padding we subsctratc before to the folder view
+        // in its surrounding spacings
+        global.log("Avail height BEF" + availHeightPerPage);
+        availHeightPerPage -= 2 * this._offsetForEachSide;
         let maxRowsPerPage = this._grid.rowsForHeight(availHeightPerPage);
         //Then, we can only show that rows least one.
         maxRowsPerPage -= 1;
         return maxRowsPerPage;
+    },
+    
+    updateBoxPointerOffsets: function(arrowHeight, padding, closeButtonOverlap) {
+        // We have to ensure the close button doesn't go outside the allocation, so
+        // if we are using all the possible rows, means the boxpointer goes until
+        // the boundary of allocation, and then we have to take into account the close button
+        // overlap and least it from padding
+        this._boxPointerOffsets['arrowHeight'] = arrowHeight;
+        this._boxPointerOffsets['padding'] = padding;
+        this._boxPointerOffsets['closeButtonOverlap'] = closeButtonOverlap;
+        
+    },
+    
+    setScrollToStart: function() {
+        this.actor.vscroll.adjustment.value = 0;
     }
 });
 
@@ -1234,7 +1295,13 @@ const FolderIcon = new Lang.Class({
                                      y_fill: true });
         this.actor._delegate = this;
         this._parentView = parentView;
-
+        // when changing screen resolution or during clutter false allocations
+        // we have to tell folder view that the calculated values of boxpointer arrow side, position, etc 
+        // are not correct (since the allocated size of pagination changed)
+        // For that problem we calculate everything again and apply it maintaining current popup.
+        this.invalidatePopUp = false;
+        this._boxPointerOffsets = {};
+        
         let label = this._dir.get_name();
         this.icon = new IconGrid.BaseIcon(label,
                                           { createIcon: Lang.bind(this, this._createIcon) });
@@ -1248,6 +1315,7 @@ const FolderIcon = new Lang.Class({
         this.actor.connect('clicked', Lang.bind(this,
             function() {
                 this._ensurePopup();
+                this.view.setScrollToStart();
             }));
         this.actor.connect('notify::mapped', Lang.bind(this,
             function() {
@@ -1264,51 +1332,22 @@ const FolderIcon = new Lang.Class({
         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;
-            }
-        }
+    _popUpGridWidth: function() {
+        return this.view.usedWidth();
     },
     
-    _popUpWidth: function() {
-        return this.view.usedWidth();
+    _popUpGridHeight: function() {
+        let usedHeight = this.view.usedHeight();
+        return usedHeight;   
     },
     
     _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;
-        
+        let usedHeight = this.view.usedHeight() + this._boxPointerOffsets['arrowHeight'] + 
this._boxPointerOffsets['padding'] * 2;
+        return usedHeight;   
     },
 
     makeSpaceForPopUp: function() {
-        this._parentView.makeSpaceForPopUp(this, this._side, this.view.nRowsDisplayedAtOnce());
+        this._parentView.makeSpaceForPopUp(this, this._boxPointerArrowside, 
this.view.nRowsDisplayedAtOnce());
     },
     
     returnSpaceToOriginalPosition: function() {
@@ -1319,75 +1358,115 @@ const FolderIcon = new Lang.Class({
         this._popup.popup();
     },
     
+    _calculateBoxPointerArrowSide: function() {
+        let absoluteActorYPosition = this.actor.get_transformed_position()[1];
+        let spaceTop = absoluteActorYPosition;
+        // Be careful, but doesn't matter, we don0t take into account the top panel height etc,
+        // So maybe we put an arrow side "wrong", but anyway, the expanding of colelction view will
+        // do the required space
+        let spaceBottom = this.actor.get_stage().height - (absoluteActorYPosition + this.actor.height);
+        return spaceTop > spaceBottom ? St.Side.BOTTOM : St.Side.TOP;
+    },
+    
+    _updatePopUpSize: function() {
+     // FLORIAN REVIEW
+        /**
+         * 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._boxPointerOffsets['arrowHeight'] = 
this._popup._boxPointer.actor.get_theme_node().get_length('-arrow-rise');
+        this._boxPointerOffsets['padding'] = 
this._popup._boxPointer.bin.get_theme_node().get_length('padding');
+        //It will be negative value, so we have to substract it, instead of plust it.
+        this._boxPointerOffsets['closeButtonOverlap'] = - 
this._popup.closeButton.get_theme_node().get_length('-shell-close-overlap-y');
+
+        this.view.updateBoxPointerOffsets(this._boxPointerOffsets['arrowHeight'], 
this._boxPointerOffsets['padding'], this._boxPointerOffsets['closeButtonOverlap']);
+        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._popUpGridWidth());
+
+        /*
+         * 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
+         */
+        this.view.actor.set_height(this._popUpGridHeight());
+    },
+    
+    _updatePopupPosition: function() {
+        if(this._popup) {
+            // Position the popup above or below the source icon
+            if (this._boxPointerArrowside == St.Side.BOTTOM) {
+                let closeButtonOffset = -this._popup.closeButton.translation_y;
+                // FLORIAN REVIEW
+                // We have to use this function, since this._popup.actor.height not always return a good 
value (32 px??)
+                // and then all this calculation of position fails. To solve this in this function we 
calculate the used height with the grid
+                // since we knoe all of the properties of grid. Then we add the padding, arrowheigth etc of 
boxpointer, and we have the
+                // used height of the popup
+                let y = this.actor.y - this._popUpHeight();
+                let yWithButton = y - closeButtonOffset;
+                this._popup.parentOffset = yWithButton < 0 ? -yWithButton : 0;
+                this._popup.actor.y = Math.max(y, closeButtonOffset);
+                this._popup.actor.y = y
+            } else {
+                this._popup.actor.y = this.actor.y + this.actor.height;
+            }
+        }
+    },
+    
     _ensurePopup: function() {
-        if(this._popup){
+        if(this._popup && !this.invalidatePopUp){
             this.makeSpaceForPopUp();
             return;
         } else {
-            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._popup.actor.fixed_height = this._popup.actor.height;
-
-            
+            this._boxPointerArrowside = this._calculateBoxPointerArrowSide();
+            if(!this._popup) {
+                this._popup = new AppFolderPopup(this, this._boxPointerArrowside);
+                this._parentView.addFolderPopup(this._popup);
+                this._popup.connect('open-state-changed', Lang.bind(this,
+                        function(popup, isOpen) {
+                    if (!isOpen) {
+                        this.actor.checked = false;
+                        this.returnSpaceToOriginalPosition();
+                    }
+                }));
+            } else
+                this._popup.updateBoxPointer(this._boxPointerArrowside);
+            this._updatePopUpSize();
+            this._updatePopupPosition();
             this.makeSpaceForPopUp();
-            this._popup.connect('open-state-changed', Lang.bind(this,
-                    function(popup, isOpen) {
-                if (!isOpen) {
-                    this.actor.checked = false;
-                    this.returnSpaceToOriginalPosition();
-                }
-            }));
+            this.invalidatePopUp = false; 
         }
     },
 
@@ -1395,6 +1474,7 @@ const FolderIcon = new Lang.Class({
         this._displayWidth = width;
         this._displayHeight = height;
         this.view.onUpdatedDisplaySize(width, height);
+        this.invalidatePopUp = true;
     },
 
 });
@@ -1474,7 +1554,7 @@ 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);
+        this._boxPointer.show(BoxPointer.PopupAnimation.FADE | BoxPointer.PopupAnimation.SLIDE);
 
         this._isOpen = true;
         this.emit('open-state-changed', true);
@@ -1484,9 +1564,15 @@ const AppFolderPopup = new Lang.Class({
         if (!this._isOpen)
             return;
 
-        this._boxPointer.hide(BoxPointer.PopupAnimation.FADE);
+        this._boxPointer.hide(BoxPointer.PopupAnimation.FADE | BoxPointer.PopupAnimation.SLIDE);
         this._isOpen = false;
         this.emit('open-state-changed', false);
+    },
+    
+    updateBoxPointer: function (side) {
+        this._arrowSide = side;
+        this._boxPointer._arrowSide = side;
+        this._boxPointer._border.queue_repaint();
     }
 });
 Signals.addSignalMethods(AppFolderPopup.prototype);
diff --git a/js/ui/iconGrid.js b/js/ui/iconGrid.js
index 6d330ff..8191975 100644
--- a/js/ui/iconGrid.js
+++ b/js/ui/iconGrid.js
@@ -187,9 +187,15 @@ const IconGrid = new Lang.Class({
         this._fillParent = params.fillParent;
         this._usePagination = params.usePagination;
         
+        this.top_padding = 0;
+        this.bottom_padding = 0;
+        this.right_padding = 0;
+        this.left_padding = 0;
+        
         if(this._usePagination) {
             this._nPages = 0;
-            //Set this variable properly before allocate function is called
+            // Set this variable properly pointing to the scrollView containing the grid
+            // before allocate function is called
             this._viewForPageSize = null;
             this._firstPagesItems = [];
         }
@@ -206,7 +212,6 @@ const IconGrid = new Lang.Class({
         this._grid.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
         this._grid.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
         this._grid.connect('allocate', Lang.bind(this, this._allocate));
-        
     },
 
     _getPreferredWidth: function (grid, forHeight, alloc) {
@@ -218,14 +223,13 @@ const IconGrid = new Lang.Class({
         let nChildren = this._grid.get_n_children();
         let nColumns = this._colLimit ? Math.min(this._colLimit,
                                                  nChildren)
-                                      : nChildren;
-        let spacing = this._fixedSpacing ? this._fixedSpacing : this._spacing;
-        let totalSpacing = Math.max(0, nColumns - 1) * spacing;
+                                                         : nChildren;
+        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
-        alloc.min_size = this._hItemSize;
-        alloc.natural_size = nColumns * this._hItemSize + totalSpacing;
+        alloc.min_size = this._hItemSize + this.left_padding + this.right_padding;
+        alloc.natural_size = nColumns * this._hItemSize + totalSpacing + this.left_padding + 
this.right_padding;
     },
 
     _getVisibleChildren: function() {
@@ -249,8 +253,7 @@ const IconGrid = new Lang.Class({
         } else {
             [nColumns, ] = this._computeLayout(forWidth);
         }
-        
-        let spacing = this._fixedSpacing ? this._fixedSpacing : this._spacing;
+        let spacing = this.getSpacing();
         
         let nRows;
         if (nColumns > 0)
@@ -260,11 +263,10 @@ const IconGrid = new Lang.Class({
         if (this._rowLimit)
             nRows = Math.min(nRows, this._rowLimit);
         let totalSpacing = Math.max(0, nRows - 1) * spacing;
-        let height = nRows * this._vItemSize + totalSpacing;
-        
+        let height = nRows * this._vItemSize + totalSpacing + this.top_padding + this.bottom_padding;     
         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;
+            alloc.min_size =  this.usedHeightPerPage() * this._nPages + this._spaceBetweenPagesTotal;
+            alloc.natural_size = this.usedHeightPerPage() * this._nPages + this._spaceBetweenPagesTotal;
             return;
         }
         alloc.min_size = height;
@@ -282,14 +284,13 @@ const IconGrid = new Lang.Class({
         let children = this._getVisibleChildren();
         let availWidth = box.x2 - box.x1;
         let availHeight = box.y2 - box.y1;
-        let spacing = this._fixedSpacing ? this._fixedSpacing : this._spacing;
-        let [nColumns, usedWidth] = this._computeLayout(availWidth);        
+        let spacing = this.getSpacing();
+        let [nColumns, usedWidth] = this._computeLayout(availWidth);
         if(this._usePagination) {
-            // ScrollView height
+            // Calculate icongrid box inside the scrollView
             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)
@@ -306,10 +307,6 @@ const IconGrid = new Lang.Class({
             // 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;
@@ -323,13 +320,12 @@ const IconGrid = new Lang.Class({
             case St.Align.END:
                 leftPadding = availWidth - usedWidth;
         }
-
-        let x = box.x1 + leftPadding;
-        let y = box.y1;
+        
+        let x = box.x1 + leftPadding + this.left_padding;
+        let y = box.y1 + this.top_padding;
         let columnIndex = 0;
         let rowIndex = 0;
-        
-        if(children.length > 0) {
+        if(this._usePagination && children.length > 0) {
             this._firstPagesItems = [children[0]];
         }
         for (let i = 0; i < children.length; i++) {
@@ -340,7 +336,7 @@ const IconGrid = new Lang.Class({
             }
             if(!this._usePagination) {
                 if (this._rowLimit && rowIndex >= this._rowLimit ||
-                        this._fillParent && childBox.y2 >= availHeight) {
+                        this._fillParent && childBox.y2 > availHeight - this.bottom_padding) {
                     this._grid.set_skip_paint(children[i], true);
                 } else {
                     children[i].allocate(childBox, flags);
@@ -361,29 +357,30 @@ const IconGrid = new Lang.Class({
                 y += this._vItemSize + spacing;
                 if(this._usePagination) {
                     if((i + 1) % this._childrenPerPage == 0) {
-                        y+= this._spaceBetweenPages;
+                        y+= this._spaceBetweenPages + this.top_padding;
                         if(i < children.length) {
                             this._firstPagesItems.push(children[i+1]);
                         }
                     }
                 }
-                x = box.x1 + leftPadding;
+                x = box.x1 + leftEmptySpace + this.left_padding;
             } else {
                 x += this._hItemSize + spacing;
             }
-        }
-        
+        }       
     },
     
     _calculatePaginationValues: function (availHeightPerPage, nColumns, nRows) {
-        let spacing = this._fixedSpacing ? this._fixedSpacing : this._spacing;
+        let spacing = this.getSpacing();
         this._spacePerRow = this._vItemSize + spacing;
+        // We want to contain the grid inside the parent box with padding
+        availHeightPerPage -= this.top_padding + this.bottom_padding;
         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._spaceBetweenPages = availHeightPerPage - (this._rowsPerPage * (this._vItemSize + spacing) - 
spacing);
         this._spaceBetweenPagesTotal = this._spaceBetweenPages * (this._nPages);
         this._childrenPerPage = nColumns * this._rowsPerPage;
     },
@@ -400,6 +397,7 @@ const IconGrid = new Lang.Class({
     
         let childBox = new Clutter.ActorBox();
         if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
+          //FIXME not defined box????????
             let _x = box.x2 - (x + width);
             childBox.x1 = Math.floor(_x - childXSpacing);
         } else {
@@ -410,7 +408,30 @@ const IconGrid = new Lang.Class({
         childBox.y2 = childBox.y1 + height;
         return childBox;
     },
-    
+
+    _computeLayout: function (forWidth) {
+        let nColumns = 0;
+        let usedWidth = this.left_padding + this.right_padding;
+        let spacing = this.getSpacing();
+        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];
+    },
+
+    _onStyleChanged: function() {
+        let themeNode = this.actor.get_theme_node();
+        this._spacing = themeNode.get_length('spacing');
+        this._hItemSize = themeNode.get_length('-shell-grid-horizontal-item-size') || ICON_SIZE;
+        this._vItemSize = themeNode.get_length('-shell-grid-vertical-item-size') || ICON_SIZE;
+        this._grid.queue_relayout();
+    },
+
     childrenInRow: function(rowWidth) {
         return this._computeLayout(rowWidth)[0];
     },
@@ -418,7 +439,7 @@ const IconGrid = new Lang.Class({
     getRowLimit: function() {
         return this._rowLimit;
     },
-    
+
     nUsedRows: function(forWidth) {
         let children = this._getVisibleChildren();
         let nColumns;
@@ -427,7 +448,6 @@ const IconGrid = new Lang.Class({
         } else {
             [nColumns, ] = this._computeLayout(forWidth);
         }
-        
         let nRows;
         if (nColumns > 0)
             nRows = Math.ceil(children.length / nColumns);
@@ -437,8 +457,9 @@ const IconGrid = new Lang.Class({
             nRows = Math.min(nRows, this._rowLimit);
         return nRows;
     },
-    
+
     rowsForHeight: function(forHeight) {
+        forHeight -= this.top_padding + this.bottom_padding;
         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
@@ -446,40 +467,24 @@ const IconGrid = new Lang.Class({
         rowsPerPage = spaceWithOneMoreRow <= forHeight? rowsPerPage + 1 : rowsPerPage;
         return rowsPerPage;
     },
-    
+
+    /**
+     * Don't take into account paddings
+     */
     usedHeightForNRows: function(nRows) {
         let spacePerRow = this.rowHeight();
-        return spacePerRow * nRows;
+        return spacePerRow * nRows - this.getSpacing();
     },
-    
+
+    usedHeightPerPage: function() {
+        return this._rowsPerPage * this._spacePerRow - this.getSpacing() + this.top_padding + 
this.bottom_padding;
+    },
+
     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._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];
-    },
-
-    _onStyleChanged: function() {
-        let themeNode = this.actor.get_theme_node();
-        this._spacing = themeNode.get_length('spacing');
-        this._hItemSize = themeNode.get_length('-shell-grid-horizontal-item-size') || ICON_SIZE;
-        this._vItemSize = themeNode.get_length('-shell-grid-vertical-item-size') || ICON_SIZE;
-        this._grid.queue_relayout();
+        return usedWidth + this.left_padding + this.right_padding;
     },
 
     removeAll: function() {
@@ -514,20 +519,50 @@ const IconGrid = new Lang.Class({
             throw new Error('Invalid page number ' + pageNumber);
         }
         let childBox = this._firstPagesItems[pageNumber].get_allocation_box();
-        return [childBox.x1, childBox.y1];
+        return [childBox.x1 - this.top_padding, childBox.y1 - this.top_padding];
     },
     
     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;
     },
+    /**
+     * 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;
+    },
     
     pageRows: function(pageNumber) {
         let pagePosition = this.getPagePosition(pageNumber);


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