| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package spark.components.gridClasses |
| { |
| import flash.geom.Rectangle; |
| import flash.utils.Dictionary; |
| |
| import mx.collections.IList; |
| import mx.core.IFactory; |
| import mx.core.IVisualElement; |
| import mx.core.mx_internal; |
| import mx.events.CollectionEvent; |
| import mx.events.CollectionEventKind; |
| |
| import spark.components.DataGrid; |
| import spark.components.Grid; |
| import spark.components.GridColumnHeaderGroup; |
| import spark.components.Group; |
| import spark.components.supportClasses.GroupBase; |
| import spark.layouts.supportClasses.LayoutBase; |
| |
| use namespace mx_internal; |
| |
| [ExcludeClass] |
| |
| /** |
| * @private |
| * Virtual horizontal layout for each column header view Group. This is not a general |
| * purpose layout class, it's only intended for column header view Groups. |
| * |
| * This layout tracks the layout of the corresponding GridView. |
| * |
| */ |
| public class GridHeaderViewLayout extends LayoutBase |
| { |
| /** |
| * Constructor. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.5 |
| * @productversion Flex 4.5 |
| */ |
| public function GridHeaderViewLayout() |
| { |
| super(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Internal variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Layers for header renderers and separators. |
| */ |
| private var rendererLayer:Group; |
| private var overlayLayer:Group; |
| |
| /** |
| * @private |
| * Cached header renderer heights maintained by the measure() method, |
| * and the current content height. |
| */ |
| private const rendererHeights:Array = []; |
| private var maxRendererHeight:Number = 0; |
| |
| /** |
| * @private |
| * Bounds of all currently visible header renderers. |
| */ |
| private const visibleRenderersBounds:Rectangle = new Rectangle(); |
| |
| /** |
| * @private |
| * Currently visible header renderers. |
| */ |
| private const visibleHeaderRenderers:Vector.<IGridItemRenderer> = new Vector.<IGridItemRenderer>(); |
| |
| /** |
| * @private |
| * Currently visible header separators. |
| */ |
| private const visibleHeaderSeparators:Vector.<IVisualElement> = new Vector.<IVisualElement>(); |
| |
| /** |
| * @private |
| * The elements available for reuse aka the "free list". Maps from an IFactory |
| * to a list of the elements that have been allocated by that factory and then freed. |
| * The list is represented by a Vector.<IVisualElement>. |
| */ |
| private var freeElementMap:Dictionary = new Dictionary(); |
| |
| /** |
| * @private |
| * Records the IFactory used to allocate a Element so that freeVisualElement() |
| * can find it again. |
| */ |
| private var elementToFactoryMap:Dictionary = new Dictionary(); |
| |
| //--------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //--------------------------------------------------------------- |
| |
| //---------------------------------- |
| // columns |
| //---------------------------------- |
| |
| private var _columnsView:IList; |
| |
| /** |
| * @private |
| * Returns a cached reference to gridView.columns. A local reference is kept so |
| * that we can remove the colllection change handler if the columns list changes. |
| */ |
| private function get columnsView():IList |
| { |
| const gridView:GridView = this.gridView; |
| const newColumns:IList = (gridView) ? gridView.gridViewLayout.columnsView : null; |
| |
| if (newColumns != _columnsView) |
| { |
| if (_columnsView) |
| _columnsView.removeEventListener(CollectionEvent.COLLECTION_CHANGE, columns_collectionChangeHandler); |
| |
| _columnsView = newColumns; |
| |
| if (_columnsView) |
| _columnsView.addEventListener(CollectionEvent.COLLECTION_CHANGE, columns_collectionChangeHandler); |
| } |
| |
| return _columnsView; |
| } |
| |
| //---------------------------------- |
| // grid (private read-only) |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private function get grid():Grid |
| { |
| const view:GridView = this.gridView; |
| return (view) ? view.parent as Grid : null; |
| } |
| |
| //---------------------------------- |
| // gridColumnHeaderGroup |
| //---------------------------------- |
| |
| private var _gridColumnHeaderGroup:GridColumnHeaderGroup = null; |
| |
| /** |
| * The GridColumnHeaderGroup whose columns this header view is associated with. |
| * |
| * This property is set by GridColumnHeaderGroup. |
| */ |
| public function get gridColumnHeaderGroup():GridColumnHeaderGroup |
| { |
| return _gridColumnHeaderGroup |
| } |
| |
| /** |
| * @private |
| */ |
| public function set gridColumnHeaderGroup(value:GridColumnHeaderGroup):void |
| { |
| if (value == _gridColumnHeaderGroup) |
| return; |
| |
| _gridColumnHeaderGroup = value; |
| } |
| |
| //---------------------------------- |
| // gridView |
| //---------------------------------- |
| |
| private var _gridView:GridView = null; |
| |
| /** |
| * The GridView whose columns this header view is associated with. |
| * |
| * This property is set by GridColumnHeaderGroup. |
| */ |
| public function get gridView():GridView |
| { |
| return _gridView |
| } |
| |
| /** |
| * @private |
| */ |
| public function set gridView(value:GridView):void |
| { |
| if (value == _gridView) |
| return; |
| |
| _gridView = value; |
| } |
| |
| //--------------------------------------------------------------- |
| // |
| // Overridden methods |
| // |
| //--------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| override public function set target(value:GroupBase):void |
| { |
| super.target = value; |
| |
| const group:Group = value as Group; |
| if (!group) |
| return; |
| |
| rendererLayer = new Group(); |
| rendererLayer.layout = new LayoutBase(); |
| group.addElement(rendererLayer); |
| |
| overlayLayer = new Group(); |
| overlayLayer.layout = new LayoutBase(); |
| group.addElement(overlayLayer); |
| } |
| |
| /** |
| * @private |
| */ |
| override public function get useVirtualLayout():Boolean |
| { |
| return true; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function set useVirtualLayout(value:Boolean):void |
| { |
| } |
| |
| /** |
| * @private |
| * Clear everything. |
| */ |
| override public function clearVirtualLayoutCache():void |
| { |
| freeRenderers(visibleHeaderRenderers); |
| visibleHeaderRenderers.length = 0; |
| |
| freeVisualElements(visibleHeaderSeparators); |
| visibleHeaderSeparators.length = 0; |
| |
| rendererHeights.length = 0; |
| visibleRenderersBounds.setEmpty(); |
| elementToFactoryMap = new Dictionary(); |
| freeElementMap = new Dictionary(); |
| |
| if (gridColumnHeaderGroup) |
| gridColumnHeaderGroup.visibleSortIndicatorIndices = null; |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function scrollPositionChanged():void |
| { |
| const target:GroupBase = this.target; |
| if (!target) |
| return; |
| |
| super.scrollPositionChanged(); // sets target's scrollRect |
| |
| // Only invalidate if we're clipping and scrollR extends outside visibleRenderersBounds |
| const scrollR:Rectangle = target.scrollRect; |
| if (scrollR && !visibleRenderersBounds.containsRect(scrollR)) |
| target.invalidateDisplayList(); |
| } |
| |
| /** |
| * @private |
| */ |
| override public function measure():void |
| { |
| const target:GroupBase = this.target; |
| if (!target) |
| return; |
| |
| updateRendererHeights(); |
| |
| const measuredWidth:Number = Math.max(0, target.minWidth); |
| const measuredHeight:Number = Math.max(maxRendererHeight, target.minHeight); |
| |
| target.measuredWidth = Math.ceil(measuredWidth); |
| target.measuredHeight = Math.ceil(measuredHeight); |
| } |
| |
| /** |
| * @private |
| */ |
| override public function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void |
| { |
| const target:GroupBase = this.target; |
| const gridColumnHeaderGroup:GridColumnHeaderGroup = this.gridColumnHeaderGroup; |
| const gridView:GridView = this.gridView; |
| |
| if (!target || !gridColumnHeaderGroup || !gridView) |
| return; |
| |
| // TBD explain about Grid relative column indices... |
| |
| const visibleColumnIndices:Vector.<int> = gridView.gridViewLayout.getVisibleColumnIndices(); |
| const visibleColumnCount:int = visibleColumnIndices.length; |
| const firstVisibleColumnIndex:int = (visibleColumnCount > 0) ? visibleColumnIndices[0] : -1; |
| const lastVisibleColumnIndex:int = (visibleColumnCount > 0) ? visibleColumnIndices[visibleColumnCount - 1] : -1; |
| |
| const oldRenderers:Array = []; |
| const rendererLayer:Group = this.rendererLayer; |
| const overlayLayer:Group = this.overlayLayer; |
| const columnSeparatorFactory:IFactory = gridColumnHeaderGroup.columnSeparator; |
| |
| var renderer:IGridItemRenderer; |
| var separator:IVisualElement; |
| var column:GridColumn; |
| var columnIndex:int = -1; |
| |
| // Add all of the renderers whose column is still visible to oldRenderers and free the rest |
| |
| for each (renderer in visibleHeaderRenderers) |
| { |
| column = renderer.column; |
| columnIndex = (column) ? column.columnIndex : -1; |
| |
| if ((visibleColumnIndices.indexOf(columnIndex) != -1) && (oldRenderers[columnIndex] == null)) |
| oldRenderers[columnIndex] = renderer; |
| else |
| freeRenderer(renderer); |
| } |
| visibleHeaderRenderers.length = 0; |
| |
| // Add all of the separators to the free-list, since laying them out is cheap. |
| |
| freeVisualElements(visibleHeaderSeparators); |
| visibleHeaderSeparators.length = 0; |
| |
| // Layout the header renderers and update the CHB's content size |
| // The loop below is written in terms of Grid - not GridView - column indices, |
| // and terminates when we reach GridColumnCount. |
| |
| const gridColumns:IList = grid.columns; // TBD what if grid.columns is null? |
| const gridViewLayout:GridViewLayout = gridView.layout as GridViewLayout; |
| const gridColumnCount:int = gridViewLayout.viewColumnIndex + gridViewLayout.columnsView.length; |
| |
| const rendererY:Number = 0; |
| const rendererHeight:Number = unscaledHeight; |
| const maxRendererX:Number = target.horizontalScrollPosition + unscaledWidth; |
| |
| var visibleLeft:Number = 0; |
| var visibleRight:Number = 0; |
| |
| // This isn't quite as simple as: |
| // for each (var columnIndex:int in visibleColumnIndices) |
| // since the GridColumnHeaderGroup may be wider than the grid because it |
| // spans the vertical scrollbar. If it does, we may need to display |
| // additional column headers (usually one). |
| |
| for (var index:int = 0; /* termination conditions below */; index++) |
| { |
| if (index < visibleColumnIndices.length) |
| columnIndex = visibleColumnIndices[index]; |
| else |
| columnIndex = grid.getNextVisibleColumnIndex(columnIndex); |
| |
| if (columnIndex < 0 || columnIndex >= gridColumnCount) |
| break; |
| |
| column = gridColumns.getItemAt(columnIndex) as GridColumn; |
| |
| // reuse or create a new renderer |
| |
| renderer = oldRenderers[columnIndex]; |
| delete oldRenderers[columnIndex]; |
| if (!renderer) |
| { |
| var factory:IFactory = column.headerRenderer; |
| if (!factory) |
| factory = gridColumnHeaderGroup.headerRenderer; |
| renderer = allocateVisualElement(factory) as IGridItemRenderer; |
| } |
| visibleHeaderRenderers.push(renderer); |
| |
| // initialize the renderer |
| |
| initializeItemRenderer(renderer, columnIndex, column, true); |
| if (renderer.parent != rendererLayer) |
| rendererLayer.addElement(renderer); |
| |
| // layout the renderer |
| |
| var isLastColumn:Boolean = columnIndex == lastVisibleColumnIndex; |
| var headerViewColumnIndex:int = columnIndex - gridViewLayout.viewColumnIndex; |
| var rendererX:Number = gridViewLayout.gridDimensionsView.getCellX(0, headerViewColumnIndex); |
| var rendererWidth:Number = grid.getColumnWidth(columnIndex); |
| |
| if (isLastColumn) |
| rendererWidth = horizontalScrollPosition + unscaledWidth - rendererX - 1; // TODO: this is a temporary hack |
| |
| renderer.setLayoutBoundsSize(rendererWidth, rendererHeight); |
| renderer.setLayoutBoundsPosition(rendererX, rendererY); |
| |
| if (index == 0) |
| visibleLeft = rendererX; |
| visibleRight = rendererX + rendererWidth; |
| |
| renderer.prepare(!createdVisualElement); |
| |
| if (isLastColumn || ((rendererX + rendererWidth) >= maxRendererX)) |
| break; |
| |
| // allocate and layout a column separator |
| |
| if (columnSeparatorFactory && !isLastColumn) |
| { |
| separator = allocateVisualElement(columnSeparatorFactory); |
| visibleHeaderSeparators.push(separator); |
| separator.visible = true; |
| if (separator.parent != overlayLayer) |
| overlayLayer.addElement(separator); |
| |
| var separatorWidth:Number = separator.getPreferredBoundsWidth(); |
| var separatorX:Number = rendererX + rendererWidth; |
| separator.setLayoutBoundsSize(separatorWidth, rendererHeight); |
| separator.setLayoutBoundsPosition(separatorX, rendererY); |
| } |
| } |
| |
| target.setContentSize(grid.contentWidth, rendererHeight); |
| |
| visibleRenderersBounds.left = visibleLeft; |
| visibleRenderersBounds.right = visibleRight = 0; |
| visibleRenderersBounds.top = rendererY; |
| visibleRenderersBounds.height = rendererHeight; |
| |
| // We may have created new renderers or changed their visibility. Force |
| // validation to avoid a display list flash. |
| |
| target.validateNow(); |
| |
| // Update the renderer heights cache. |
| // Invalidates the target's size if the maxRendererHeight has changed. |
| |
| updateRendererHeights(true); |
| } |
| |
| //--------------------------------------------------------------- |
| // |
| // Public methods |
| // |
| //--------------------------------------------------------------- |
| |
| /** |
| * Returns the column index corresponding to the specified coordinates, |
| * or -1 if the coordinates are out of bounds. The coordinates are |
| * resolved with respect to the column header view layout target. |
| * |
| * <p>If all of the columns or rows for the grid have not yet been scrolled |
| * into view, the returned index may only be an approximation, |
| * based on all of the columns' <code>typicalItem</code>s.</p> |
| * |
| * @param x The pixel's x coordinate relative to the target column header view |
| * @param y The pixel's y coordinate relative to the target column header view |
| * @return the index of the column or -1 if the coordinates are out of bounds. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.0 |
| * @productversion Flex 4.5 |
| */ |
| public function getHeaderIndexAt(x:Number, y:Number):int |
| { |
| var headerIndex:int = -1; |
| var globalPoint:Point = gridColumnHeaderGroup.localToGlobal(new Point(x, y)); |
| |
| if(!gridColumnHeaderGroup.areCoordinatesOverLeftPadding(globalPoint)) |
| { |
| var paddingLeftStyle:Number = gridColumnHeaderGroup.getStyle("paddingLeft"); |
| var paddingLeft:Number = isNaN(paddingLeftStyle) ? 0 : paddingLeftStyle; |
| |
| headerIndex = gridView.gridViewLayout.gridDimensionsView.getColumnIndexAt(x - paddingLeft + horizontalScrollPosition, y); |
| |
| if(headerIndex == -1 && gridColumnHeaderGroup.containsGlobalCoordinates(globalPoint)) |
| { |
| //then the point is either over the right padding, over the width of the vertical |
| //scroll bar, or over the space between the last separator and the width of the grid |
| headerIndex = grid.getPreviousVisibleColumnIndex(columnsView.length); |
| } |
| } |
| |
| return headerIndex; |
| } |
| |
| /** |
| * Returns the column separator index corresponding to the specified |
| * coordinates, or -1 if the coordinates don't overlap a separator. The |
| * coordinates are resolved with respect to the GridColumnHeaderGroup layout target. |
| * |
| * <p>A separator is considered to "overlap" the specified location if the |
| * x coordinate is within <code>separatorMouseWidth</code> of separator's |
| * horizontal midpoint.</p> |
| * |
| * <p>The separator index is the same as the index of the column on the left |
| * (assuming that this component's layoutDirection is "rtl"). That means |
| * that all column headers are flanked by two separators, except for the first |
| * visible column, which just has a separator on the right, and the last visible |
| * column, which just has a separator on the left.</p> |
| * |
| * <p>If all of the columns or rows for the grid have not yet been scrolled |
| * into view, the returned index may only be an approximation, |
| * based on all of the columns' <code>typicalItem</code>s.</p> |
| * |
| * @param x The pixel's x coordinate relative to the columnHeaderGroup |
| * @param y The pixel's y coordinate relative to the columnHeaderGroup |
| * @return the index of the column or -1 if the coordinates don't overlap a separator. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.0 |
| * @productversion Flex 4.5 |
| */ |
| public function getSeparatorIndexAt(x:Number, y:Number):int |
| { |
| const gdv:GridDimensionsView = gridView.gridViewLayout.gridDimensionsView; |
| const columnIndex:int = gdv.getColumnIndexAt(x, y); |
| if (columnIndex == -1) |
| return -1; |
| |
| const isFirstColumn:Boolean = columnIndex == gridView.getNextVisibleColumnIndex(-1); |
| const isLastColumn:Boolean = false; //columnIndex == gridView.getPreviousVisibleColumnIndex(gridView.viewColumnCount); |
| |
| const columnLeft:Number = gdv.getCellX(0, columnIndex); |
| const columnRight:Number = columnLeft + gdv.getColumnWidth(columnIndex); |
| const smw:Number = gridColumnHeaderGroup.getStyle("separatorAffordance"); |
| |
| if (!isFirstColumn && (x > (columnLeft - smw)) && (x < (columnLeft + smw))) |
| return grid.getPreviousVisibleColumnIndex(columnIndex); |
| |
| if (!isLastColumn && (x > (columnRight - smw)) && (x < columnRight + smw)) |
| return columnIndex; |
| |
| return -1; |
| } |
| |
| /** |
| * Returns the current pixel bounds of the specified header (renderer), or null if |
| * no such column exists. Header bounds are reported in GridColumnHeaderGroup coordinates. |
| * |
| * <p>If all of the visible columns preceeding the specified column have not |
| * yet been scrolled into view, the returned bounds may only be an approximation, |
| * based on all of the Grid's <code>typicalItem</code>s.</p> |
| * |
| * @param columnIndex The 0-based index of the column. |
| * @return A <code>Rectangle</code> that represents the column header's pixel bounds, or null. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.0 |
| * @productversion Flex 4.5 |
| */ |
| public function getHeaderBounds(columnIndex:int):Rectangle |
| { |
| const gridColumnHeaderGroup:GridColumnHeaderGroup = this.gridColumnHeaderGroup; |
| const grid:Grid = this.grid; |
| |
| if (!gridColumnHeaderGroup || !grid) |
| return null; |
| |
| const columns:IList = columnsView; |
| const columnsLength:int = (columns) ? columns.length : 0; |
| |
| if (columnIndex >= columnsLength) |
| return null; |
| |
| const column:GridColumn = columns.getItemAt(columnIndex) as GridColumn; |
| if (!column.visible) |
| return null; |
| |
| const paddingLeft:Number = gridColumnHeaderGroup.getStyle("paddingLeft"); |
| const paddingRight:Number = gridColumnHeaderGroup.getStyle("paddingRight"); |
| const paddingTop:Number = gridColumnHeaderGroup.getStyle("paddingTop"); |
| const paddingBottom:Number = gridColumnHeaderGroup.getStyle("paddingBottom"); |
| |
| var isLastColumn:Boolean = columnIndex == grid.getPreviousVisibleColumnIndex(columnsLength); |
| var rendererX:Number = grid.getCellX(0, columnIndex) + paddingLeft; |
| const rendererY:Number = paddingTop; |
| var rendererWidth:Number = grid.getColumnWidth(columnIndex); |
| const rendererHeight:Number = gridColumnHeaderGroup.height - paddingTop - paddingBottom; |
| |
| if (isLastColumn) |
| rendererWidth = horizontalScrollPosition + gridColumnHeaderGroup.width - rendererX - paddingRight; |
| |
| return new Rectangle(rendererX, rendererY, rendererWidth, rendererHeight); |
| } |
| |
| /** |
| * If the requested header renderer is visible, returns a reference to |
| * the header renderer currently displayed for the specified column. |
| * Note that once the returned header renderer is no longer visible it |
| * may be recycled and its properties reset. |
| * |
| * <p>If the requested header renderer is not visible then, |
| * each time this method is called, a new header renderer is created. The |
| * new item renderer is not visible</p> |
| * |
| * <p>The width of the returned renderer is the same as for item renderers |
| * returned by DataGrid/getItemRendererAt().</p> |
| * |
| * @param columnIndex The 0-based column index of the header renderer's column |
| * @return The item renderer or null if the column index is invalid. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.0 |
| * @productversion Flex 4.5 |
| */ |
| public function getHeaderRendererAt(columnIndex:int):IGridItemRenderer |
| { |
| const gridColumnHeaderGroup:GridColumnHeaderGroup = this.gridColumnHeaderGroup; |
| const grid:Grid = this.grid; |
| |
| if (!gridColumnHeaderGroup || !grid || (columnIndex < 0)) |
| return null; |
| |
| // If columnIndex refers to a visible header renderer, return it |
| |
| const rendererLayer:Group = rendererLayer; |
| const visibleColumnIndices:Vector.<int> = grid.getVisibleColumnIndices(); |
| const eltIndex:int = visibleColumnIndices.indexOf(columnIndex); |
| if (eltIndex != -1) |
| { |
| const rendererLayerNumElements:int = rendererLayer.numElements; |
| for (var index:int = 0; index < rendererLayerNumElements; index++) |
| { |
| var elt:IGridItemRenderer = rendererLayer.getElementAt(index) as IGridItemRenderer; |
| if (elt && elt.visible && elt.column && (elt.column.columnIndex == columnIndex)) |
| return elt; |
| } |
| return null; |
| } |
| |
| // create a new renderer |
| |
| const columns:IList = columnsView; |
| if (!columns || (columns.length <= columnIndex)) |
| return null; |
| const column:GridColumn = columns.getItemAt(columnIndex) as GridColumn; |
| if (!column.visible) |
| return null; |
| |
| var factory:IFactory = column.headerRenderer; |
| if (!factory) |
| factory = gridColumnHeaderGroup.headerRenderer; |
| const renderer:IGridItemRenderer = allocateVisualElement(factory) as IGridItemRenderer; |
| |
| rendererLayer.addElement(renderer); |
| |
| // initialize the renderer |
| |
| initializeItemRenderer(renderer, columnIndex, column, renderer.visible); |
| |
| // layout the renderer |
| |
| const paddingLeft:Number = gridColumnHeaderGroup.getStyle("paddingLeft"); |
| const paddingRight:Number = gridColumnHeaderGroup.getStyle("paddingRight"); |
| const paddingTop:Number = gridColumnHeaderGroup.getStyle("paddingTop"); |
| const paddingBottom:Number = gridColumnHeaderGroup.getStyle("paddingBottom"); |
| |
| const isLastColumn:Boolean = columnIndex == grid.getPreviousVisibleColumnIndex(columns.length); |
| const rendererX:Number = grid.getCellX(0, columnIndex) + paddingLeft; |
| const rendererY:Number = paddingTop; |
| const rendererHeight:Number = gridColumnHeaderGroup.height - paddingTop - paddingBottom; |
| var rendererWidth:Number = grid.getColumnWidth(columnIndex); |
| |
| if (isLastColumn) |
| rendererWidth = horizontalScrollPosition + gridColumnHeaderGroup.width - rendererX - paddingRight; |
| |
| renderer.setLayoutBoundsSize(rendererWidth, rendererHeight); |
| renderer.setLayoutBoundsPosition(rendererX, rendererY); |
| |
| rendererLayer.removeElement(renderer); |
| renderer.visible = false; |
| |
| return renderer; |
| } |
| |
| import flash.events.MouseEvent; |
| import flash.geom.Point; |
| import flash.geom.Rectangle; |
| |
| //--------------------------------------------------------------- |
| // |
| // Internal methods, properties |
| // |
| //--------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| private function initializeItemRenderer(renderer:IGridItemRenderer, |
| columnIndex:int, |
| column:GridColumn, |
| visible:Boolean=true):void |
| { |
| renderer.visible = visible; |
| renderer.column = column; |
| renderer.label = column.headerText; |
| |
| const columnHeaderGroup:GridColumnHeaderGroup = this.gridColumnHeaderGroup; |
| |
| const dataGrid:DataGrid = columnHeaderGroup.dataGrid; |
| if (dataGrid) |
| renderer.owner = dataGrid; |
| |
| renderer.hovered = columnIndex == columnHeaderGroup.hoverColumnIndex; |
| renderer.selected = columnIndex == columnHeaderGroup.selectedColumnIndex; |
| renderer.down = columnIndex == columnHeaderGroup.downColumnIndex; |
| } |
| |
| /** |
| * @private |
| * Let the allocateGridElement() caller know if the returned element was |
| * created or recycled. |
| */ |
| private var createdVisualElement:Boolean = false; |
| |
| /** |
| * @private |
| */ |
| private function createVisualElement(factory:IFactory):IVisualElement |
| { |
| createdVisualElement = true; |
| const newElement:IVisualElement = factory.newInstance() as IVisualElement; |
| elementToFactoryMap[newElement] = factory; |
| return newElement; |
| } |
| |
| /** |
| * @private |
| * If the freeElementMap "free list" contains an instance of this factory, then |
| * remove if from the free list and return it, otherwise create a new instance |
| * using createVisualElement(). |
| */ |
| private function allocateVisualElement(factory:IFactory):IVisualElement |
| { |
| createdVisualElement = false; |
| const freeElements:Vector.<IVisualElement> = freeElementMap[factory] as Vector.<IVisualElement>; |
| if (freeElements) |
| { |
| const freeElement:IVisualElement = freeElements.pop(); |
| if (freeElements.length == 0) |
| delete freeElementMap[factory]; |
| if (freeElement) |
| return freeElement; |
| } |
| |
| return createVisualElement(factory); |
| } |
| |
| /** |
| * @private |
| * Move the specified element to the free list after hiding it. Note that we |
| * do not actually remove the element from its parent. |
| */ |
| private function freeVisualElement(element:IVisualElement):void |
| { |
| const factory:IFactory = elementToFactoryMap[element]; |
| |
| var freeElements:Vector.<IVisualElement> = freeElementMap[factory]; |
| if (!freeElements) |
| { |
| freeElements = new Vector.<IVisualElement>(); |
| freeElementMap[factory] = freeElements; |
| } |
| freeElements.push(element); |
| |
| element.visible = false; |
| } |
| |
| private function freeVisualElements(elements:Vector.<IVisualElement>):void |
| { |
| for each (var elt:IVisualElement in elements) |
| freeVisualElement(elt); |
| |
| elements.length = 0; |
| } |
| |
| private function freeRenderer(renderer:IGridItemRenderer):void |
| { |
| freeVisualElement(renderer as IVisualElement); |
| renderer.discard(true); |
| } |
| |
| private function freeRenderers(renderers:Vector.<IGridItemRenderer>):void |
| { |
| for each (var renderer:IGridItemRenderer in renderers) |
| freeRenderer(renderer); |
| |
| renderers.length = 0; |
| } |
| |
| /** |
| * @private |
| * Updates the renderer heights cache and the current max renderer height. |
| * Invalidates the target's size if the max renderer height has changed. |
| * |
| * <p>If the max live renderer height is the same as the max cached height, then |
| * just update the cache. |
| * If the max live renderer height is greater than the max cached height, then |
| * update the cache, cache the new height, and invalidate the target's size if |
| * necessary. |
| * If the max live renderer height is less than the max cached height, then |
| * update the cache, check to see if the cached max height has lowered, and |
| * invalidate the target's size if necessary. |
| */ |
| private function updateRendererHeights(inUpdateDisplayList:Boolean = false):void |
| { |
| const columns:IList = columnsView; |
| rendererHeights.length = (columns) ? columns.length : 0; |
| |
| var newHeight:Number = 0; |
| |
| // update cached renderer heights with live renderer heights. |
| for each (var renderer:IGridItemRenderer in visibleHeaderRenderers) |
| { |
| var preferredHeight:Number = renderer.getPreferredBoundsHeight(); |
| rendererHeights[renderer.column.columnIndex] = preferredHeight; |
| if (preferredHeight > newHeight) |
| newHeight = preferredHeight; |
| } |
| |
| // Do nothing if the heights are the same. |
| if (newHeight == maxRendererHeight) |
| return; |
| |
| if (newHeight < maxRendererHeight) |
| { |
| // If the live renderers' max height is less than the current |
| // max height, check if this also lowers the maxRendererHeight. |
| for each (var rendererHeight:Number in rendererHeights) |
| { |
| if (!isNaN(rendererHeight) && rendererHeight > newHeight) |
| newHeight = rendererHeight; |
| } |
| } |
| |
| maxRendererHeight = newHeight; |
| |
| if (inUpdateDisplayList) // TBD: should be target.invalidateSize()? |
| gridColumnHeaderGroup.invalidateSize(); |
| } |
| |
| |
| /** |
| * @private |
| * Handles changes to the columns IList that might affect the visible sort |
| * indicators. |
| */ |
| private function columns_collectionChangeHandler(event:CollectionEvent):void |
| { |
| // TODO (klin): The cache could be adjusted here too. |
| switch (event.kind) |
| { |
| case CollectionEventKind.ADD: |
| { |
| columns_collectionChangeAdd(event); |
| break; |
| } |
| |
| case CollectionEventKind.REMOVE: |
| { |
| columns_collectionChangeRemove(event); |
| break; |
| } |
| |
| case CollectionEventKind.MOVE: |
| { |
| columns_collectionChangeMove(event); |
| break; |
| } |
| |
| case CollectionEventKind.REPLACE: |
| case CollectionEventKind.UPDATE: |
| { |
| // Do nothing. |
| break; |
| } |
| |
| case CollectionEventKind.REFRESH: |
| case CollectionEventKind.RESET: |
| { |
| clearVirtualLayoutCache(); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * @private |
| * Adjusts the visibleSortIndicatorIndices to the correct columns |
| * after columns are added. |
| */ |
| private function columns_collectionChangeAdd(event:CollectionEvent):void |
| { |
| const itemsLength:int = event.items.length; |
| if (itemsLength <= 0) |
| return; |
| |
| const chg:GridColumnHeaderGroup = gridColumnHeaderGroup; |
| const indices:Vector.<int> = chg.visibleSortIndicatorIndices; |
| const indicesLength:int = indices.length; |
| const startIndex:int = event.location; |
| |
| for (var i:int = 0; i < indicesLength; i++) |
| { |
| if (indices[i] >= startIndex) |
| indices[i] += itemsLength; |
| } |
| chg.visibleSortIndicatorIndices = indices; |
| } |
| |
| /** |
| * @private |
| * Adjusts the visibleSortIndicatorIndices to the correct columns |
| * after columns are removed. |
| * |
| * TBD: Remove the rendererHeights cache entries for the corresponding columns. |
| */ |
| private function columns_collectionChangeRemove(event:CollectionEvent):void |
| { |
| const itemsLength:int = event.items.length; |
| if (itemsLength <= 0) |
| return; |
| |
| const chg:GridColumnHeaderGroup = gridColumnHeaderGroup; |
| const indices:Vector.<int> = chg.visibleSortIndicatorIndices; |
| const indicesLength:int = indices.length; |
| const startIndex:int = event.location; |
| const lastIndex:int = startIndex + itemsLength; |
| const newIndices:Vector.<int> = new Vector.<int>(); |
| var index:int; |
| |
| for each (index in indices) |
| { |
| if (index < startIndex) |
| newIndices.push(index); |
| else if (index >= lastIndex) |
| newIndices.push(index - lastIndex); |
| } |
| chg.visibleSortIndicatorIndices = newIndices; |
| } |
| |
| /** |
| * @private |
| * Adjusts the visibleSortIndicatorIndices to the correct columns |
| * after columns are moved. |
| */ |
| private function columns_collectionChangeMove(event:CollectionEvent):void |
| { |
| const itemsLength:int = event.items.length; |
| if (itemsLength <= 0) |
| return; |
| |
| const gridColumnHeaderGroup:GridColumnHeaderGroup = this.gridColumnHeaderGroup; |
| const indices:Vector.<int> = gridColumnHeaderGroup.visibleSortIndicatorIndices; |
| const indicesLength:int = indices.length; |
| const oldStart:int = event.oldLocation; |
| const oldEnd:int = event.oldLocation + itemsLength; |
| const newStart:int = event.location; |
| const newEnd:int = event.location + itemsLength; |
| var index:int; |
| |
| for (var i:int = 0; i < indicesLength; i++) |
| { |
| index = indices[i]; |
| |
| if (index >= oldStart && index < oldEnd) |
| { |
| // Moved items move up to new position |
| indices[i] = newStart + (index - oldStart); |
| continue; |
| } |
| |
| // Two cases: |
| // 1) New position is greater than old position, so we |
| // decrement their position by the number of moved items. |
| // 2) New position is less than old position, so we |
| // increment their position by the number of moved items. |
| if (newStart > oldStart) |
| { |
| if (index >= oldEnd && index < newEnd) |
| indices[i] -= itemsLength; |
| } |
| else if (newStart < oldStart) |
| { |
| if (index >= newStart && index < oldStart) |
| indices[i] += itemsLength; |
| } |
| } |
| |
| gridColumnHeaderGroup.visibleSortIndicatorIndices = indices; |
| } |
| } |
| } |
| |