| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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.events.Event; |
| import flash.geom.Rectangle; |
| import flash.utils.Dictionary; |
| import flash.utils.getTimer; |
| |
| import mx.collections.IList; |
| import mx.core.ClassFactory; |
| import mx.core.IFactory; |
| import mx.core.IInvalidating; |
| import mx.core.IUITextField; |
| import mx.core.IVisualElement; |
| import mx.core.IVisualElementContainer; |
| import mx.core.Singleton; |
| import mx.core.mx_internal; |
| import mx.events.CollectionEvent; |
| import mx.events.CollectionEventKind; |
| import mx.events.PropertyChangeEvent; |
| import mx.managers.ILayoutManagerClient; |
| import mx.managers.LayoutManager; |
| |
| import spark.collections.SubListView; |
| import spark.components.DataGrid; |
| import spark.components.Grid; |
| import spark.components.supportClasses.GroupBase; |
| import spark.core.IGraphicElement; |
| import spark.layouts.supportClasses.DropLocation; |
| import spark.layouts.supportClasses.LayoutBase; |
| |
| use namespace mx_internal; |
| |
| [ExcludeClass] |
| |
| /** |
| * @private |
| * A virtual two dimensional layout for the Grid class. This is not a general purpose layout, |
| * it's only intended to be use with GridView. |
| */ |
| public class GridViewLayout extends LayoutBase |
| { |
| include "../../core/Version.as"; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * The following variables define the visible part of the grid, where each item |
| * renderer typically displays dataProvider[rowIndex][columns[columnIndex]].dataField. |
| * The index vectors are sorted in increasing order but their items may not be |
| * sequential. |
| */ |
| private var visibleRowIndices:Vector.<int> = new Vector.<int>(0); |
| private var visibleColumnIndices:Vector.<int> = new Vector.<int>(0); |
| |
| /** |
| * @private |
| * The previous values of the corresponding variables. Set by layoutItemRenderers() |
| * and only valid during updateDisplayList(), for a complete relayout. |
| */ |
| private var oldVisibleRowIndices:Vector.<int> = new Vector.<int>(0); |
| private var oldVisibleColumnIndices:Vector.<int> = new Vector.<int>(0); |
| |
| /** |
| * TODO (hmuller): document how do these vectors relate to visibleRow,ColumnIndices |
| */ |
| private var visibleRowBackgrounds:Vector.<IVisualElement> = new Vector.<IVisualElement>(0); |
| private var visibleRowSeparators:Vector.<IVisualElement> = new Vector.<IVisualElement>(0); |
| private var visibleColumnSeparators:Vector.<IVisualElement> = new Vector.<IVisualElement>(0); |
| private var visibleItemRenderers:Vector.<IGridItemRenderer> = new Vector.<IGridItemRenderer>(0); |
| |
| /** |
| * @private |
| * TODO (hmuller): provide documentation |
| */ |
| private var hoverIndicator:IVisualElement = null; |
| private var caretIndicator:IVisualElement = null; |
| private var editorIndicator:IVisualElement = null; |
| |
| /** |
| * @private |
| * The bounding rectangle for all of the visible item renderers. Note that this |
| * rectangle may be larger than the scrollRect, since the first/last rows/columns |
| * of item renderers may only be partially visible. See scrollPositionChanged(). |
| */ |
| private const visibleItemRenderersBounds:Rectangle = new Rectangle(); |
| |
| /** |
| * @private |
| * The viewport's bounding rectangle; often smaller then visibleItemRenderersBounds. |
| * Initialized by updateDisplayList with the current scrollPosition, and grid.width,Height. |
| */ |
| private const visibleGridBounds:Rectangle = new Rectangle(); |
| |
| /** |
| * @private |
| * The elements available for reuse. 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>. |
| * |
| * Updated by allocateGridElement(). |
| */ |
| private const freeElementMap:Dictionary = new Dictionary(); |
| |
| /** |
| * @private |
| * Records the IFactory used to allocate a Element so that free(Element) can find it again. |
| * |
| * Updated by createGridElement(). |
| */ |
| private const elementToFactoryMap:Dictionary = new Dictionary(); |
| |
| /** |
| * @private |
| * Used by scrollPositionChanged() to determine which scroll position properties changed. |
| */ |
| private var oldVerticalScrollPosition:Number = 0; |
| private var oldHorizontalScrollPosition:Number = 0; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Class methods and properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * The static embeddedFontsRegistryExists property is initialized lazily. |
| */ |
| private static var _embeddedFontRegistryExists:Boolean = false; |
| private static var embeddedFontRegistryExistsInitialized:Boolean = false; |
| |
| /** |
| * @private |
| * True if an embedded font registry singleton exists. |
| */ |
| private static function get embeddedFontRegistryExists():Boolean |
| { |
| if (!embeddedFontRegistryExistsInitialized) |
| { |
| embeddedFontRegistryExistsInitialized = true; |
| try |
| { |
| _embeddedFontRegistryExists = Singleton.getInstance("mx.core::IEmbeddedFontRegistry") != null; |
| } |
| catch (e:Error) |
| { |
| _embeddedFontRegistryExists = false; |
| } |
| } |
| |
| return _embeddedFontRegistryExists; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.4 |
| * @productversion Flex 4.5 |
| */ |
| public function GridViewLayout() |
| { |
| super(); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| private function dispatchChangeEvent(type:String):void |
| { |
| if (hasEventListener(type)) |
| dispatchEvent(new Event(type)); |
| } |
| |
| //---------------------------------- |
| // columnsView |
| //---------------------------------- |
| |
| private var _columnsView:SubListView = null; |
| |
| [Bindable("columnsViewChanged")] |
| |
| /** |
| * @default null |
| */ |
| public function get columnsView():SubListView |
| { |
| return _columnsView; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set columnsView(value:SubListView):void |
| { |
| if (value == _columnsView) |
| return; |
| |
| _columnsView = value; |
| dispatchChangeEvent("columnsViewChanged"); |
| } |
| |
| //---------------------------------- |
| // dataProviderView |
| //---------------------------------- |
| |
| private var _dataProviderView:SubListView = null; |
| |
| [Bindable("dataProviderViewChanged")] |
| |
| /** |
| * @default null |
| */ |
| public function get dataProviderView():SubListView |
| { |
| return _dataProviderView; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set dataProviderView(value:SubListView):void |
| { |
| if (value == _dataProviderView) |
| return; |
| |
| _dataProviderView = value; |
| dispatchChangeEvent("dataProviderViewChanged"); |
| } |
| |
| //---------------------------------- |
| // grid |
| //---------------------------------- |
| |
| private var _grid:Grid = null; |
| |
| /** |
| * @private |
| */ |
| public function get grid():Grid |
| { |
| return _grid; |
| } |
| |
| /** |
| * The Grid parent of this layout's target. This property is set by the Grid when the |
| * target GridView is added/removed from the Grid. |
| * |
| */ |
| public function set grid(value:Grid):void |
| { |
| if (_grid == value) |
| return; |
| |
| if (_grid) |
| { |
| _grid.removeEventListener("dataProviderChanged", grid_dataProviderChangedHandler); |
| _grid.removeEventListener("columnsChanged", grid_columnsChangedHandler); |
| } |
| |
| _grid = value; |
| |
| if (_grid) |
| { |
| dataProviderView = new SubListView(grid.dataProvider); |
| columnsView = new SubListView(grid.columns); |
| gridDimensionsView = new GridDimensionsView(grid.gridDimensions); |
| |
| _grid.addEventListener("dataProviderChanged", grid_dataProviderChangedHandler); |
| _grid.addEventListener("columnsChanged", grid_columnsChangedHandler); |
| } |
| else |
| { |
| dataProviderView = null; |
| columnsView = null; |
| gridDimensionsView = null; |
| } |
| } |
| |
| /** |
| * @private |
| * Called when the Grid's dataProvider property is set - not when the dataProvider itself changes. |
| */ |
| private function grid_dataProviderChangedHandler(ignored:Event):void |
| { |
| dataProviderView = new SubListView(grid.dataProvider); |
| dataProviderView.startIndex = viewRowIndex; |
| dataProviderView.count = viewRowCount; |
| } |
| |
| /** |
| * @private |
| * Called when the Grid's column property is set - not when columns are added/removed (etc). |
| */ |
| private function grid_columnsChangedHandler(ignored:Event):void |
| { |
| columnsView = new SubListView(grid.columns); |
| columnsView.startIndex = viewColumnIndex; |
| columnsView.count = viewColumnCount; |
| } |
| |
| //---------------------------------- |
| // gridDimensionsView |
| //---------------------------------- |
| |
| private var _gridDimensionsView:GridDimensionsView = null; |
| |
| [Bindable("gridDimensionsViewChanged")] |
| |
| /** |
| * @default null |
| */ |
| public function get gridDimensionsView():GridDimensionsView |
| { |
| return _gridDimensionsView; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set gridDimensionsView(value:GridDimensionsView):void |
| { |
| if (value == _gridDimensionsView) |
| return; |
| |
| _gridDimensionsView = value; |
| dispatchChangeEvent("gridDimensionsViewChanged"); |
| } |
| |
| //---------------------------------- |
| // horizontalScrollingLocked |
| //---------------------------------- |
| |
| private var _horizontalScrollingLocked:Boolean = false; |
| |
| [Bindable("horizontalScrollingLockedChanged")] |
| |
| /** |
| * @default false |
| */ |
| public function get horizontalScrollingLocked():Boolean |
| { |
| return _horizontalScrollingLocked; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set horizontalScrollingLocked(value:Boolean):void |
| { |
| if (value == _horizontalScrollingLocked) |
| return; |
| |
| _horizontalScrollingLocked = value; |
| dispatchChangeEvent("horizontalScrollingLockedChanged"); |
| } |
| |
| //---------------------------------- |
| // requestedColumnCount |
| //---------------------------------- |
| |
| private var _requestedColumnCount:int = 0; |
| |
| [Bindable("requestedColumnCountChanged")] |
| |
| /** |
| * @default 0 |
| */ |
| public function get requestedColumnCount():int |
| { |
| return _requestedColumnCount; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set requestedColumnCount(value:int):void |
| { |
| if (value == _requestedColumnCount) |
| return; |
| |
| _requestedColumnCount = value; |
| dispatchChangeEvent("requestedColumnCountChanged"); |
| } |
| |
| //---------------------------------- |
| // requestedRowCount |
| //---------------------------------- |
| |
| private var _requestedRowCount:int = 0; |
| |
| [Bindable("requestedRowCountChanged")] |
| |
| /** |
| * @default 0 |
| */ |
| public function get requestedRowCount():int |
| { |
| return _requestedRowCount; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set requestedRowCount(value:int):void |
| { |
| if (value == _requestedRowCount) |
| return; |
| |
| _requestedRowCount = value; |
| dispatchChangeEvent("requestedRowCountChanged"); |
| } |
| |
| //---------------------------------- |
| // useVirtualLayout (override) |
| //---------------------------------- |
| |
| /** |
| * GridLayout only supports virtual layout, the value of this property can not be changed. |
| * |
| * @return true. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.0 |
| * @productversion Flex 4.5 |
| */ |
| override public function get useVirtualLayout():Boolean |
| { |
| return true; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function set useVirtualLayout(value:Boolean):void |
| { |
| } |
| |
| //---------------------------------- |
| // verticalScrollingLocked |
| //---------------------------------- |
| |
| private var _verticalScrollingLocked:Boolean = false; |
| |
| [Bindable("verticalScrollingLockedChanged")] |
| |
| /** |
| * @default false |
| */ |
| public function get verticalScrollingLocked():Boolean |
| { |
| return _verticalScrollingLocked; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set verticalScrollingLocked(value:Boolean):void |
| { |
| if (value == _verticalScrollingLocked) |
| return; |
| |
| _verticalScrollingLocked = value; |
| dispatchChangeEvent("verticalScrollingLockedChanged"); |
| } |
| |
| //---------------------------------- |
| // viewColumnCount |
| //---------------------------------- |
| |
| /** |
| * The number of columns displayed by the target GridView. |
| * |
| * @default -1 |
| */ |
| public function get viewColumnCount():int |
| { |
| return gridDimensionsView.viewColumnCount; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set viewColumnCount(value:int):void |
| { |
| gridDimensionsView.viewColumnCount = value; |
| columnsView.count = value; |
| } |
| |
| //---------------------------------- |
| // viewColumnIndex |
| //---------------------------------- |
| |
| /** |
| * The column index origin of the grid region displayed by the target GridView. |
| * |
| * @default 0 |
| */ |
| public function get viewColumnIndex():int |
| { |
| return gridDimensionsView.viewColumnIndex; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set viewColumnIndex(value:int):void |
| { |
| gridDimensionsView.viewColumnIndex = value; |
| columnsView.startIndex = value; |
| } |
| |
| //---------------------------------- |
| // viewRowCount |
| //---------------------------------- |
| |
| /** |
| * The number of rows displayed by the target GridView. |
| * |
| * @default -1 |
| */ |
| public function get viewRowCount():int |
| { |
| return gridDimensionsView.viewRowCount; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set viewRowCount(value:int):void |
| { |
| gridDimensionsView.viewRowCount = value; |
| dataProviderView.count = value; |
| } |
| |
| //---------------------------------- |
| // viewRowIndex |
| //---------------------------------- |
| |
| /** |
| * The row index origin of the grid region displayed by the target GridView. |
| * |
| * @default 0 |
| */ |
| public function get viewRowIndex():int |
| { |
| return gridDimensionsView.viewRowIndex; |
| } |
| |
| /** |
| * @private |
| */ |
| public function set viewRowIndex(value:int):void |
| { |
| gridDimensionsView.viewRowIndex = value; |
| dataProviderView.startIndex = value; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Method Overrides |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Returns the index where a new item should be inserted if |
| * the user releases the mouse at the specified coordinates |
| * while completing a drag and drop gesture. |
| * |
| * Called by the <code>calculatedDropLocation()</code> method. |
| * |
| * @param x The x coordinate of the drag and drop gesture, in |
| * local coordinates. |
| * |
| * @param y The y coordinate of the drag and drop gesture, in |
| * the drop target's local coordinates. |
| * |
| * @return The drop index or -1 if the drop operation is not available |
| * at the specified coordinates. |
| * |
| * @see #calculateDropLocation() |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11 |
| * @playerversion AIR 3.0 |
| * @productversion Flex 5.0 |
| */ |
| override protected function calculateDropIndex(x:Number, y:Number):int |
| { |
| var rowIndex:int = gridDimensionsView.getRowIndexAt(x, y); |
| if (rowIndex == -1) |
| { |
| rowIndex = gridDimensionsView.rowCount; |
| } |
| else |
| { |
| // If we are closer to the next row then drop on the next row. |
| var bounds:Rectangle = gridDimensionsView.getRowBounds(rowIndex); |
| if (y > (bounds.y + (bounds.height / 2))) |
| rowIndex++; |
| } |
| |
| return rowIndex + gridDimensionsView.viewRowIndex; |
| } |
| |
| /** |
| * Calculates the bounds for the drop indicator that provides visual feedback |
| * to the user of where the items will be inserted at the end of a drag and drop |
| * gesture. |
| * |
| * Called by the <code>showDropIndicator()</code> method. |
| * |
| * @param dropLocation A valid DropLocation object previously returned |
| * by the <code>calculateDropLocation()</code> method. |
| * |
| * @return The bounds for the drop indicator or null. |
| * |
| * @see spark.layouts.supportClasses.DropLocation |
| * @see #calculateDropIndex() |
| * @see #calculateDragScrollDelta() |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 11 |
| * @playerversion AIR 3.0 |
| * @productversion Flex 5.0 |
| */ |
| override protected function calculateDropIndicatorBounds(dropLocation:DropLocation):Rectangle |
| { |
| var rowIndex:int = gridDimensionsView.getRowIndexAt(dropLocation.dropPoint.x, dropLocation.dropPoint.y); |
| if (rowIndex == -1) |
| { |
| // If the last row is visible then put the drop indicator below the last row. |
| if (grid.dataProvider && isCellVisible(grid.dataProvider.length - 1, -1)) |
| rowIndex = grid.dataProvider.length - 1; |
| else |
| return null; |
| } |
| |
| var bounds:Rectangle = gridDimensionsView.getRowBounds(rowIndex); |
| |
| // If we are closer to the next row then put the drop indicator on |
| // the next row. |
| // TODO (dloverin): TBD how the drop indicator should be sized when |
| // rowGap is implemented. |
| // NOTE: The bounds indicator for the MX DataGrid start at the top of |
| // the cell. The top of the drag indicator was moved up by two pixels |
| // so it could be seen when the indicator bounds are below the last row. |
| // The issue is there may not be any space below the last row and the drop |
| // indicator would not be visible if it started at the top of the next cell. |
| if (dropLocation.dropPoint.y > (bounds.top + (bounds.height * 1) / 2)) |
| return new Rectangle(2, bounds.bottom - 2, bounds.width -4, 4); |
| |
| return new Rectangle(2, Math.max(0, bounds.y - 2), bounds.width - 4, 4); |
| } |
| |
| /** |
| * @private |
| * Clear everything. |
| */ |
| override public function clearVirtualLayoutCache():void |
| { |
| freeGridElements(visibleRowBackgrounds); |
| freeGridElements(visibleRowSeparators); |
| visibleRowIndices.length = 0; |
| |
| freeGridElements(visibleColumnSeparators); |
| visibleColumnIndices.length = 0; |
| |
| freeItemRenderers(visibleItemRenderers); |
| |
| clearSelectionIndicators(); |
| |
| freeGridElement(hoverIndicator) |
| hoverIndicator = null; |
| |
| freeGridElement(caretIndicator); |
| caretIndicator = null; |
| |
| freeGridElement(editorIndicator); |
| editorIndicator = null; |
| |
| visibleItemRenderersBounds.setEmpty(); |
| visibleGridBounds.setEmpty(); |
| } |
| |
| /** |
| * @private |
| * This version of the method uses gridDimensions to calcuate the bounds |
| * of the specified cell. The index is the cell's position in the row-major |
| * layout. |
| */ |
| override public function getElementBounds(index:int):Rectangle |
| { |
| const columnsLength:int = gridDimensionsView.columnCount; |
| if (columnsLength == -1) |
| return null; |
| |
| const rowIndex:int = index / columnsLength; |
| const columnIndex:int = index - (rowIndex * columnsLength); |
| return gridDimensionsView.getCellBounds(rowIndex, columnIndex); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function getElementBoundsAboveScrollRect(scrollRect:Rectangle):Rectangle |
| { |
| const y:int = Math.max(0, scrollRect.top - 1); |
| const rowIndex:int = gridDimensionsView.getRowIndexAt(scrollRect.x, y); |
| return gridDimensionsView.getRowBounds(rowIndex); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function getElementBoundsBelowScrollRect(scrollRect:Rectangle):Rectangle |
| { |
| const rowCount:int = gridDimensionsView.rowCount; |
| const maxY:int = Math.max(0, gridDimensionsView.getContentHeight(rowCount) - 1); |
| const y:int = Math.min(maxY, scrollRect.bottom + 1); |
| const rowIndex:int = gridDimensionsView.getRowIndexAt(scrollRect.x, y); |
| return gridDimensionsView.getRowBounds(rowIndex); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function getElementBoundsLeftOfScrollRect(scrollRect:Rectangle):Rectangle |
| { |
| const x:int = Math.max(0, scrollRect.left - 1); |
| const columnIndex:int = gridDimensionsView.getColumnIndexAt(x, scrollRect.y); |
| return gridDimensionsView.getColumnBounds(columnIndex); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function getElementBoundsRightOfScrollRect(scrollRect:Rectangle):Rectangle |
| { |
| const columnCount:int = gridDimensionsView.columnCount; |
| const maxX:int = Math.max(0, gridDimensionsView.getContentWidth(columnCount) - 1); |
| const x:int = Math.min(maxX, scrollRect.right + 1); |
| const columnIndex:int = gridDimensionsView.getColumnIndexAt(x, scrollRect.y); |
| return gridDimensionsView.getColumnBounds(columnIndex); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function scrollPositionChanged():void |
| { |
| if (!grid) |
| return; |
| |
| grid.hoverRowIndex = -1; |
| grid.hoverColumnIndex = -1; |
| |
| super.scrollPositionChanged(); // sets GridView's scrollRect |
| |
| const hspChanged:Boolean = oldHorizontalScrollPosition != horizontalScrollPosition; |
| const vspChanged:Boolean = oldVerticalScrollPosition != verticalScrollPosition; |
| |
| oldHorizontalScrollPosition = horizontalScrollPosition; |
| oldVerticalScrollPosition = verticalScrollPosition; |
| |
| // Only invalidate if we're clipping and rows and/or columns covered |
| // by the scrollR changes. If so, the visible row/column indicies need |
| // to be updated. |
| |
| var invalidate:Boolean = false; |
| |
| if (visibleRowIndices.length == 0 || visibleColumnIndices.length == 0) |
| invalidate = true; |
| |
| if (!invalidate && vspChanged) |
| { |
| const oldFirstRowIndex:int = visibleRowIndices[0]; |
| const oldLastRowIndex:int = visibleRowIndices[visibleRowIndices.length - 1]; |
| |
| const newFirstRowIndex:int = |
| gridDimensionsView.getRowIndexAt(horizontalScrollPosition, verticalScrollPosition); |
| const newLastRowIndex:int = |
| gridDimensionsView.getRowIndexAt(horizontalScrollPosition, verticalScrollPosition + target.height); |
| |
| if (oldFirstRowIndex != newFirstRowIndex || oldLastRowIndex != newLastRowIndex) |
| invalidate = true; |
| } |
| |
| if (!invalidate && hspChanged) |
| { |
| const oldFirstColIndex:int = visibleColumnIndices[0]; |
| const oldLastColIndex:int = visibleColumnIndices[visibleColumnIndices.length - 1]; |
| |
| const newFirstColIndex:int = |
| gridDimensionsView.getColumnIndexAt(horizontalScrollPosition, verticalScrollPosition); |
| const newLastColIndex:int = |
| gridDimensionsView.getColumnIndexAt(horizontalScrollPosition + target.width, verticalScrollPosition); |
| |
| if (oldFirstColIndex != newFirstColIndex || oldLastColIndex != newLastColIndex) |
| invalidate = true; |
| } |
| |
| if (invalidate) |
| { |
| var reason:String = "none"; |
| if (vspChanged && hspChanged) |
| reason = "bothScrollPositions"; |
| else if (vspChanged) |
| reason = "verticalScrollPosition" |
| else if (hspChanged) |
| reason = "horizontalScrollPosition"; |
| |
| grid.invalidateDisplayListFor(reason); |
| } |
| } |
| |
| /** |
| * @private |
| * Computes new values for the grid's measuredWidth,Height and |
| * measuredMinWidth,Height properties. |
| * |
| * If grid.requestedRowCount is GTE 0, then measuredHeight is estimated |
| * content height for as many rows. Otherwise the measuredHeight is the estimated |
| * content height for all rows. The measuredWidth calculation is similar. The |
| * measuredMinWidth,Height properties are also similar however if the corresponding |
| * requestedMin property isn't specified, then the measuredMin size is the same |
| * as the measured size. |
| */ |
| override public function measure():void |
| { |
| const gridView:GridView = target as GridView; // TBD: requestedRowCount should be a local property... |
| const grid:Grid = this.grid; |
| |
| if (!gridView || !grid) |
| return; |
| |
| updateTypicalCellSizes(); |
| |
| var measuredRowCount:int = requestedRowCount; |
| if (measuredRowCount == -1) |
| { |
| measuredRowCount = gridDimensionsView.rowCount; |
| if (grid.requestedMaxRowCount != -1) |
| measuredRowCount = Math.min(grid.requestedMaxRowCount, measuredRowCount); |
| if (grid.requestedMinRowCount != -1) |
| measuredRowCount = Math.max(grid.requestedMinRowCount, measuredRowCount); |
| } |
| |
| var measuredColumnCount:int = requestedColumnCount; |
| if (measuredColumnCount == -1) |
| { |
| measuredColumnCount = getColumnsLength(); |
| if (grid.requestedMinColumnCount != -1) |
| measuredColumnCount = Math.max(grid.requestedMinColumnCount, measuredColumnCount); |
| } |
| |
| var measuredWidth:Number = gridDimensionsView.getTypicalContentWidth(measuredColumnCount); |
| var measuredHeight:Number = gridDimensionsView.getTypicalContentHeight(measuredRowCount); |
| var measuredMinWidth:Number = gridDimensionsView.getTypicalContentWidth(grid.requestedMinColumnCount); |
| var measuredMinHeight:Number = gridDimensionsView.getTypicalContentHeight(grid.requestedMinRowCount); |
| |
| // Use Math.ceil() to make sure that if the content partially occupies |
| // the last pixel, we'll count it as if the whole pixel is occupied. |
| |
| target.measuredWidth = Math.ceil(measuredWidth); |
| target.measuredHeight = Math.ceil(measuredHeight); |
| target.measuredMinWidth = Math.ceil(measuredMinWidth); |
| target.measuredMinHeight = Math.ceil(measuredMinHeight); |
| |
| //trace("measure", target.measuredWidth, target.measuredHeight); |
| } |
| |
| /** |
| * @private |
| */ |
| override public function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void |
| { |
| const grid:Grid = this.grid; |
| if (!grid) |
| return; |
| |
| //trace("updateDisplayList", unscaledWidth, unscaledHeight); |
| |
| // Find the index of the last GridColumn.visible==true column |
| |
| const columnsLength:int = gridDimensionsView.columnCount; |
| const lastVisibleColumnIndex:int = (columnsLength > 0) ? getPreviousVisibleColumnIndex(columnsLength) : -1; |
| if (lastVisibleColumnIndex < 0) |
| return; |
| |
| // Layers |
| |
| const backgroundLayer:GridLayer = getLayer("backgroundLayer"); |
| const selectionLayer:GridLayer = getLayer("selectionLayer"); |
| const editorIndicatorLayer:GridLayer = getLayer("editorIndicatorLayer"); |
| const rendererLayer:GridLayer = getLayer("rendererLayer"); |
| const overlayLayer:GridLayer = getLayer("overlayLayer"); |
| |
| // Relayout everything if the scroll position changed or if no |
| // "invalidateDisplayList reason" was specified. See |
| // Grid/invalidateDisplayListFor(reason) |
| |
| const completeLayoutNeeded:Boolean = |
| grid.isInvalidateDisplayListReason("verticalScrollPosition") || |
| grid.isInvalidateDisplayListReason("horizontalScrollPosition"); |
| |
| |
| // Layout the columns and item renderers; compute new values for visibleRowIndices et al. |
| |
| if (completeLayoutNeeded) |
| { |
| oldVisibleRowIndices = visibleRowIndices; |
| oldVisibleColumnIndices = visibleColumnIndices; |
| |
| // Determine the x/y position of the visible content. Note that the |
| // actual scroll positions may be negative. |
| |
| const scrollX:Number = Math.max(0, horizontalScrollPosition); |
| const scrollY:Number = Math.max(0, verticalScrollPosition); |
| |
| visibleGridBounds.x = scrollX; |
| visibleGridBounds.y = scrollY; |
| visibleGridBounds.width = unscaledWidth; |
| visibleGridBounds.height = unscaledHeight; |
| |
| layoutColumns(scrollX, scrollY, unscaledWidth); |
| layoutItemRenderers(rendererLayer, scrollX, scrollY, unscaledWidth, unscaledHeight); |
| |
| // Update the content size. Make sure that if the content spans partially |
| // over a pixel to the right/bottom, the content size includes the whole pixel. |
| |
| const columnCount:int = getColumnsLength(); |
| const rowCount:int = gridDimensionsView.rowCount; |
| |
| const contentWidth:Number = Math.ceil(gridDimensionsView.getContentWidth(columnCount)); |
| const contentHeight:Number = Math.ceil(gridDimensionsView.getContentHeight(rowCount)); |
| target.setContentSize(contentWidth, contentHeight); |
| |
| // If the grid's contentHeight is smaller than than the available height |
| // (unscaledHeight) then pad the visible rows |
| |
| var paddedRowCount:int = rowCount; |
| if ((scrollY == 0) && (contentHeight < unscaledHeight)) |
| { |
| const unusedHeight:Number = unscaledHeight - gridDimensionsView.getContentHeight(rowCount); |
| paddedRowCount += Math.ceil(unusedHeight / gridDimensionsView.defaultRowHeight); |
| } |
| |
| for (var rowIndex:int = rowCount; rowIndex < paddedRowCount; rowIndex++) |
| visibleRowIndices.push(rowIndex); |
| |
| // Layout the row backgrounds |
| |
| visibleRowBackgrounds = layoutLinearElements(grid.rowBackground, backgroundLayer, |
| visibleRowBackgrounds, oldVisibleRowIndices, visibleRowIndices, layoutRowBackground); |
| |
| // Layout the row and column separators. |
| |
| const lastRowIndex:int = paddedRowCount - 1; |
| |
| visibleRowSeparators = layoutLinearElements(grid.rowSeparator, overlayLayer, |
| visibleRowSeparators, oldVisibleRowIndices, visibleRowIndices, layoutRowSeparator, lastRowIndex); |
| |
| visibleColumnSeparators = layoutLinearElements(grid.columnSeparator, overlayLayer, |
| visibleColumnSeparators, oldVisibleColumnIndices, visibleColumnIndices, layoutColumnSeparator, lastVisibleColumnIndex); |
| |
| |
| // The old visible row,column indices are no longer needed |
| |
| oldVisibleRowIndices.length = 0; |
| oldVisibleColumnIndices.length = 0; |
| } |
| |
| // Layout the hoverIndicator, caretIndicator, and selectionIndicators |
| |
| if (completeLayoutNeeded || grid.isInvalidateDisplayListReason("hoverIndicator")) |
| layoutHoverIndicator(backgroundLayer); |
| |
| if (completeLayoutNeeded || grid.isInvalidateDisplayListReason("selectionIndicator")) |
| layoutSelectionIndicators(selectionLayer); |
| |
| if (completeLayoutNeeded || grid.isInvalidateDisplayListReason("caretIndicator")) |
| layoutCaretIndicator(overlayLayer); |
| |
| if (completeLayoutNeeded || grid.isInvalidateDisplayListReason("editorIndicator")) |
| layoutEditorIndicator(editorIndicatorLayer); |
| |
| if (!completeLayoutNeeded) |
| updateVisibleItemRenderers(); |
| |
| // To avoid flashing, force all of the layers to render now |
| |
| target.validateNow(); |
| } |
| |
| /** |
| * @private |
| * Reset the selected, showsCaret, and hovered properties for all visible item renderers. |
| * Run the prepare() method for renderers that have changed. |
| * |
| * This method is only called when the item renderers are not updated as part of a general |
| * redisplay, by layoutItemRenderers(). |
| */ |
| private function updateVisibleItemRenderers():void |
| { |
| const grid:Grid = grid; // avoid get method cost |
| const rowSelectionMode:Boolean = isRowSelectionMode(); |
| const cellSelectionMode:Boolean = isCellSelectionMode(); |
| |
| if (!rowSelectionMode && !cellSelectionMode) |
| return; |
| |
| for each (var renderer:IGridItemRenderer in visibleItemRenderers) |
| { |
| var rowIndex:int = renderer.rowIndex; // TBD: need grid-relative row,column indices here |
| var columnIndex:int = renderer.columnIndex; |
| |
| var oldSelected:Boolean = renderer.selected; |
| var oldShowsCaret:Boolean = renderer.showsCaret; |
| var oldHovered:Boolean = renderer.hovered; |
| |
| // The following initializations should match what's done in initializeItemRenderer() |
| if (rowSelectionMode) |
| { |
| renderer.selected = grid.selectionContainsIndex(rowIndex); |
| renderer.showsCaret = grid.caretRowIndex == rowIndex; |
| renderer.hovered = grid.hoverRowIndex == rowIndex; |
| } |
| else if (cellSelectionMode) |
| { |
| renderer.selected = grid.selectionContainsCell(rowIndex, columnIndex); |
| renderer.showsCaret = (grid.caretRowIndex == rowIndex) && (grid.caretColumnIndex == columnIndex); |
| renderer.hovered = (grid.hoverRowIndex == rowIndex) && (grid.hoverColumnIndex == columnIndex); |
| } |
| |
| if ((oldSelected != renderer.selected) || |
| (oldShowsCaret != renderer.showsCaret) || |
| (oldHovered != renderer.hovered)) |
| renderer.prepare(true); |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Target GridView Access |
| // |
| //-------------------------------------------------------------------------- |
| |
| private function gridRowIndexToViewIndex(gridRowIndex:int):int |
| { |
| return (gridRowIndex == -1) ? -1 : gridRowIndex - viewRowIndex; |
| } |
| |
| private function gridColumnIndexToViewIndex(gridColumnIndex:int):int |
| { |
| return (gridColumnIndex == -1) ? -1 : gridColumnIndex - viewColumnIndex; |
| } |
| |
| private function getLayer(name:String):GridLayer |
| { |
| return target.getChildByName(name) as GridLayer; |
| |
| } |
| |
| /** |
| * @private |
| */ |
| private function getGridColumn(columnIndex:int):GridColumn |
| { |
| const columnsView:IList = columnsView; |
| if ((columnsView == null) || (columnIndex >= columnsView.length) || (columnIndex < 0)) |
| return null; |
| |
| return columnsView.getItemAt(columnIndex) as GridColumn; |
| } |
| |
| /** |
| * @private |
| */ |
| private function getColumnsLength():int |
| { |
| return (columnsView) ? columnsView.length : 0; |
| } |
| |
| |
| /** |
| * @private |
| * Returns the index of the next Grid.visible==true column |
| * after index. Returns -1 if there are no more visible columns. |
| * |
| * To find the first GridColumn.visible==true column index, use |
| * getNextVisibleColumnIndex(-1). |
| */ |
| private function getNextVisibleColumnIndex(index:int=-1):int |
| { |
| if (index < -1) |
| return -1; |
| |
| const columns:IList = columnsView; |
| const columnsLength:int = (columns) ? columns.length : 0; |
| |
| for (var i:int = index + 1; i < columnsLength; i++) |
| { |
| var column:GridColumn = columns.getItemAt(i) as GridColumn; |
| if (column && column.visible) |
| return i; |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * @private |
| * Returns the index of the previous GridColumn.visible==true column |
| * before index. Returns -1 if there are no more visible columns. |
| * |
| * To find the last GridColumn.visible==true column index, use |
| * getPreviousVisibleColumnIndex(columns.length). |
| */ |
| private function getPreviousVisibleColumnIndex(index:int):int |
| { |
| const columns:IList = columnsView; |
| if (!columns || (index > columns.length)) |
| return -1; |
| |
| for (var i:int = index - 1; i >= 0; i--) |
| { |
| var column:GridColumn = columns.getItemAt(i) as GridColumn; |
| if (column && column.visible) |
| return i; |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * @private |
| */ |
| private function getDataProviderItem(rowIndex:int):Object |
| { |
| const dataProviderView:IList = this.dataProviderView; |
| |
| if ((dataProviderView == null) || (rowIndex >= dataProviderView.length) || (rowIndex < 0)) |
| return null; |
| |
| return dataProviderView.getItemAt(rowIndex); |
| } |
| |
| |
| /** |
| * @private |
| */ |
| private function getDataProviderLength():int |
| { |
| const dataProviderView:IList = this.dataProviderView; |
| |
| return (dataProviderView) ? dataProviderView.length : -1; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Updating the GridDimensions' typicalCell sizes and columnWidths |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Return width clamped to the column's minWidth and maxWidth properties. |
| */ |
| private static function clampColumnWidth(width:Number, column:GridColumn):Number |
| { |
| const minColumnWidth:Number = column.minWidth; |
| const maxColumnWidth:Number = column.maxWidth; |
| |
| if (!isNaN(minColumnWidth)) |
| width = Math.max(width, minColumnWidth); |
| if (!isNaN(maxColumnWidth)) |
| width = Math.min(width, maxColumnWidth); |
| |
| return width; |
| } |
| |
| /** |
| * @private |
| * Use the specified GridColumn's itemRenderer (IFactory) to create a temporary |
| * item renderer. The returned item renderer must be freed, with freeGridElement(), |
| * and removed from the rendererLayer after it's used. |
| */ |
| private function createTypicalItemRenderer(columnIndex:int):IGridItemRenderer |
| { |
| const rendererLayer:GridLayer = getLayer("rendererLayer"); |
| if (!rendererLayer) |
| return null; |
| |
| var typicalItem:Object = grid.typicalItem; |
| if (typicalItem == null) |
| typicalItem = getDataProviderItem(0); |
| |
| const column:GridColumn = getGridColumn(columnIndex); |
| const factory:IFactory = itemToRenderer(column, typicalItem); |
| const renderer:IGridItemRenderer = allocateGridElement(factory) as IGridItemRenderer; |
| |
| rendererLayer.addElement(renderer); |
| |
| initializeItemRenderer(renderer, 0 /* rowIndex */, columnIndex, grid.typicalItem, false); |
| |
| // If the column's width isn't specified, then use the renderer's explicit |
| // width, if any. If that isn't specified, then use 4096, to avoid wrapping. |
| |
| var columnWidth:Number = column.width; |
| |
| if (isNaN(columnWidth)) |
| { |
| // Sadly, IUIComponent, UITextField, and UIFTETextField all have an |
| // explicitWidth property but do not share a common type. |
| if ("explicitWidth" in renderer) |
| columnWidth = Object(renderer).explicitWidth; |
| } |
| |
| // The default width of a UI[FTE]TextField is 100. If autoWrap is true, and |
| // multiline is true, the measured text will wrap if it is wider than |
| // the TextField's width. This is not what we want when measuring the |
| // width of typicalItem columns that lack an explicit column width. |
| |
| if (isNaN(columnWidth)) |
| columnWidth = 4096; |
| |
| layoutItemRenderer(renderer, 0, 0, columnWidth, NaN); |
| |
| return renderer; |
| } |
| |
| /** |
| * @private |
| * Update the typicalCellWidth,Height for all of the columns starting |
| * with x coordinate startX and column startIndex that fit within the |
| * specified width. Typical sizes are only updated if the current |
| * typical cell size is NaN. |
| * |
| * The typicalCellWidth for GridColumns with an explicit width, is just |
| * the explicit width. Otherwise an item renderer is created for the column |
| * and the item renderer's preferred bounds become the typical cell size. |
| */ |
| private function updateVisibleTypicalCellSizes(width:Number, scrollX:Number, firstVisibleColumnIndex:int):void |
| { |
| const rendererLayer:GridLayer = getLayer("rendererLayer"); |
| if (!rendererLayer) |
| return; |
| |
| const columnCount:int = getColumnsLength(); |
| const startCellX:Number = gridDimensionsView.getCellX(0 /* rowIndex */, firstVisibleColumnIndex); |
| const columnGap:int = gridDimensionsView.columnGap; |
| |
| var columnIndex : int; |
| var column : GridColumn; |
| var totalWidthCoef : Number = 0; |
| var contentFreeSpace : Number = grid.contentWidth; |
| var gridFreeSpace : Number = grid.width; |
| for (columnIndex = 0; (columnIndex < columnCount); columnIndex++) |
| { |
| column = getGridColumn(columnIndex); |
| if (isNaN(column.width) && column.visible) |
| { |
| totalWidthCoef += column.percentWidth; |
| } |
| else |
| { |
| if (!isNaN(column.width) && column.visible) |
| { |
| contentFreeSpace -= column.width; |
| gridFreeSpace -= column.width; |
| } |
| } |
| } |
| var freeSpace : Number = gridFreeSpace < 0 ? contentFreeSpace : gridFreeSpace; |
| var unusedFreeSpace : Number = freeSpace; |
| |
| for (columnIndex = firstVisibleColumnIndex; |
| (width > 0) && (columnIndex >= 0) && (columnIndex < columnCount); |
| columnIndex = getNextVisibleColumnIndex(columnIndex)) |
| { |
| var cellHeight:Number = gridDimensionsView.getTypicalCellHeight(columnIndex); |
| var cellWidth:Number = gridDimensionsView.getTypicalCellWidth(columnIndex); |
| |
| column = getGridColumn(columnIndex); |
| if (!isNaN(column.width)) |
| { |
| cellWidth = column.width; |
| gridDimensionsView.setTypicalCellWidth(columnIndex, cellWidth); |
| } |
| else |
| { |
| if (totalWidthCoef > 0) |
| { |
| cellWidth = Math.round(column.percentWidth / totalWidthCoef * (freeSpace > 0 ? freeSpace : 0)); |
| if (cellWidth < column.minWidth) |
| { |
| cellWidth = column.minWidth; |
| unusedFreeSpace -= cellWidth; |
| } |
| else |
| { |
| if (cellWidth > unusedFreeSpace) |
| { |
| cellWidth = unusedFreeSpace; |
| unusedFreeSpace = 0; |
| } |
| else |
| { |
| unusedFreeSpace -= cellWidth; |
| } |
| } |
| gridDimensionsView.setTypicalCellWidth(columnIndex, cellWidth); |
| } |
| } |
| |
| if (isNaN(cellWidth) || isNaN(cellHeight)) |
| { |
| var renderer:IGridItemRenderer = createTypicalItemRenderer(columnIndex); |
| if (isNaN(cellWidth)) |
| { |
| cellWidth = clampColumnWidth(renderer.getPreferredBoundsWidth(), column); |
| gridDimensionsView.setTypicalCellWidth(columnIndex, cellWidth); |
| } |
| if (isNaN(cellHeight)) |
| { |
| cellHeight = renderer.getPreferredBoundsHeight(); |
| gridDimensionsView.setTypicalCellHeight(columnIndex, cellHeight); |
| } |
| |
| rendererLayer.removeElement(renderer); |
| freeGridElement(renderer); |
| } |
| |
| if (columnIndex == firstVisibleColumnIndex) |
| width -= startCellX + cellWidth - scrollX; |
| else |
| width -= cellWidth + columnGap; |
| } |
| } |
| |
| /** |
| * @private |
| * Used by the measure() method to initialize the GridDimensions typical width,height of |
| * requestedColumnCount columns, and the typical width of *all* columns with an explicit width. |
| */ |
| private function updateTypicalCellSizes():void |
| { |
| const rendererLayer:GridLayer = getLayer("rendererLayer"); |
| if (!rendererLayer) |
| return; |
| |
| const columnCount:int = getColumnsLength(); |
| const columnGap:int = gridDimensionsView.columnGap; |
| const requestedColumnCount:int = grid.requestedColumnCount; // TBD GridView... |
| var measuredColumnCount:int = 0; |
| |
| var columnIndex : int; |
| var column : GridColumn; |
| var totalWidthCoef : Number = 0; |
| var contentFreeSpace : Number = grid.contentWidth; |
| var gridFreeSpace : Number = grid.width; |
| for (columnIndex = 0; (columnIndex < columnCount); columnIndex++) |
| { |
| column = getGridColumn(columnIndex); |
| if (isNaN(column.width) && column.visible) |
| { |
| totalWidthCoef += column.percentWidth; |
| } |
| else |
| { |
| if (!isNaN(column.width) && column.visible) |
| { |
| contentFreeSpace -= column.width; |
| gridFreeSpace -= column.width; |
| } |
| } |
| } |
| var freeSpace : Number = gridFreeSpace < 0 ? contentFreeSpace : gridFreeSpace; |
| var unusedFreeSpace : Number = freeSpace; |
| |
| for (columnIndex = 0; (columnIndex < columnCount); columnIndex++) |
| { |
| var cellHeight:Number = gridDimensionsView.getTypicalCellHeight(columnIndex); |
| var cellWidth:Number = gridDimensionsView.getTypicalCellWidth(columnIndex); |
| |
| column = getGridColumn(columnIndex); |
| |
| // GridColumn.visible==false columns have a typical size of (0,0) |
| // to distinguish them from the GridColumn.visible==true columns |
| // that aren't in view yet. |
| |
| if (!column.visible) |
| { |
| gridDimensionsView.setTypicalCellWidth(columnIndex, 0); |
| gridDimensionsView.setTypicalCellHeight(columnIndex, 0); |
| continue; |
| } |
| |
| if (!isNaN(column.width)) |
| { |
| cellWidth = column.width; |
| gridDimensionsView.setTypicalCellWidth(columnIndex, cellWidth); |
| } |
| else |
| { |
| if (totalWidthCoef > 0) |
| { |
| cellWidth = Math.round(column.percentWidth / totalWidthCoef * (freeSpace > 0 ? freeSpace : 0)); |
| if (cellWidth < column.minWidth) |
| { |
| cellWidth = column.minWidth; |
| unusedFreeSpace -= cellWidth; |
| } |
| else |
| { |
| if (cellWidth > unusedFreeSpace) |
| { |
| cellWidth = unusedFreeSpace; |
| unusedFreeSpace = 0; |
| } |
| else |
| { |
| unusedFreeSpace -= cellWidth; |
| } |
| } |
| gridDimensionsView.setTypicalCellWidth(columnIndex, cellWidth); |
| } |
| } |
| |
| var needTypicalRenderer:Boolean = (requestedColumnCount == -1) || (measuredColumnCount < requestedColumnCount); |
| if (needTypicalRenderer && (isNaN(cellWidth) || isNaN(cellHeight))) |
| { |
| var renderer:IGridItemRenderer = createTypicalItemRenderer(columnIndex); |
| if (isNaN(cellWidth)) |
| { |
| cellWidth = clampColumnWidth(renderer.getPreferredBoundsWidth(), column); |
| gridDimensionsView.setTypicalCellWidth(columnIndex, cellWidth); |
| } |
| if (isNaN(cellHeight)) |
| { |
| cellHeight = renderer.getPreferredBoundsHeight(); |
| gridDimensionsView.setTypicalCellHeight(columnIndex, cellHeight); |
| } |
| |
| rendererLayer.removeElement(renderer); |
| freeGridElement(renderer); |
| } |
| measuredColumnCount++; |
| } |
| |
| } |
| |
| /** |
| * @private |
| * Update the column widths for the columns visible beginning at scrollX, that will fit |
| * within the specified width, or for all columns if width is NaN. The width of |
| * GridColumns that lack an explicit width is the preferred width of an item renderer |
| * for the grid's typicalItem. |
| * |
| * If width is specified and all columns are visible, then we'll increase the widths |
| * of GridDimensions columns for GridColumns without an explicit width so that all of |
| * the available space is consumed. |
| */ |
| private function layoutColumns(scrollX:Number, scrollY:Number, width:Number):void |
| { |
| var columnCount:int = gridDimensionsView.columnCount; |
| if (columnCount <= 0) |
| return; |
| |
| // Update the GridDimensions typicalCellWidth,Height values as needed. |
| |
| const firstVisibleColumnIndex:int = gridDimensionsView.getColumnIndexAt(scrollX, scrollY); |
| updateVisibleTypicalCellSizes(width, scrollX, firstVisibleColumnIndex); |
| |
| // Set the GridDimensions columnWidth for no more than columnCount columns. |
| |
| const columnGap:int = gridDimensionsView.columnGap; |
| const startCellX:Number = gridDimensionsView.getCellX(0 /* rowIndex */, firstVisibleColumnIndex); |
| var availableWidth:Number = width; |
| var flexibleColumnCount:uint = 0; |
| |
| for (var columnIndex:int = firstVisibleColumnIndex; |
| (availableWidth > 0) && (columnIndex >= 0) && (columnIndex < columnCount); |
| columnIndex = getNextVisibleColumnIndex(columnIndex)) |
| { |
| var columnWidth:Number = gridDimensionsView.getTypicalCellWidth(columnIndex); |
| var gridColumn:GridColumn = getGridColumn(columnIndex); |
| |
| if (isNaN(gridColumn.width)) // if this column's width wasn't explicitly specified |
| { |
| flexibleColumnCount += 1; |
| columnWidth = clampColumnWidth(columnWidth, gridColumn); |
| } |
| else |
| columnWidth = gridColumn.width; |
| |
| gridDimensionsView.setColumnWidth(columnIndex, columnWidth); // store the column width |
| |
| if (columnIndex == firstVisibleColumnIndex) |
| availableWidth -= startCellX + columnWidth - scrollX; |
| else |
| availableWidth -= columnWidth + columnGap; |
| } |
| |
| // If we haven't scrolled horizontally, and there's space left over, widen |
| // the columns whose GridColumn width isn't set explicitly, to fill the extra space. |
| |
| if ((scrollX != 0) || (availableWidth < 1.0) || (flexibleColumnCount == 0)) |
| return; |
| |
| const columnWidthDelta:Number = Math.ceil(availableWidth / flexibleColumnCount); |
| |
| for (columnIndex = firstVisibleColumnIndex; |
| (columnIndex >= 0) && (columnIndex < columnCount) && (availableWidth >= 1.0); |
| columnIndex = getNextVisibleColumnIndex(columnIndex)) |
| { |
| gridColumn = getGridColumn(columnIndex); |
| |
| if (isNaN(gridColumn.width)) // if this column's width wasn't explicitly specified |
| { |
| var oldColumnWidth:Number = gridDimensionsView.getColumnWidth(columnIndex); |
| columnWidth = oldColumnWidth + Math.min(availableWidth, columnWidthDelta); |
| columnWidth = clampColumnWidth(columnWidth, gridColumn); |
| gridDimensionsView.setColumnWidth(columnIndex, columnWidth); // store the column width |
| availableWidth -= (columnWidth - oldColumnWidth); |
| } |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Item Renderer Management and Layout |
| // |
| //-------------------------------------------------------------------------- |
| |
| private const gridItemRendererClassFactories:Dictionary = new Dictionary(true); |
| |
| /** |
| * @private |
| * Return the item renderer for the specified column and dataProvider item, |
| * essentially column.itemToRenderer(dataItem). |
| * |
| * If this app might have embedded fonts then item renderers must be created with the Grid's |
| * module factory. To enable that, we wrap the real item renderer ClassFactory with |
| * a GridItemRendererClassFactory. Wrapped factories are cached in |
| * the gridItemRendererClassFactories Dictionary. |
| */ |
| private function itemToRenderer(column:GridColumn, dataItem:Object):IFactory |
| { |
| var factory:IFactory = column.itemToRenderer(dataItem); |
| var rendererClassFactory:IFactory = null; |
| |
| if (embeddedFontRegistryExists && (factory is ClassFactory)) |
| { |
| rendererClassFactory = gridItemRendererClassFactories[factory]; |
| if (!rendererClassFactory) |
| { |
| rendererClassFactory = new GridItemRendererClassFactory(grid, ClassFactory(factory)); |
| gridItemRendererClassFactories[factory] = rendererClassFactory; |
| } |
| } |
| |
| return (rendererClassFactory) ? rendererClassFactory : factory; |
| } |
| |
| private function layoutItemRenderers(rendererLayer:GridLayer, scrollX:Number, scrollY:Number, width:Number, height:Number):void |
| { |
| if (!rendererLayer) |
| return; |
| |
| var rowIndex:int; |
| var colIndex:int; |
| |
| const rowCount:int = gridDimensionsView.rowCount; |
| const colCount:int = getColumnsLength(); |
| const rowGap:int = gridDimensionsView.rowGap; |
| const colGap:int = gridDimensionsView.columnGap; |
| |
| // Compute the row,column index and bounds of the upper left "start" cell |
| |
| const startColIndex:int = gridDimensionsView.getColumnIndexAt(scrollX, scrollY); |
| const startRowIndex:int = gridDimensionsView.getRowIndexAt(scrollX, scrollY); |
| const startCellX:Number = gridDimensionsView.getCellX(startRowIndex, startColIndex); |
| const startCellY:Number = gridDimensionsView.getCellY(startRowIndex, startColIndex); |
| |
| // Compute newVisibleColumns |
| |
| const newVisibleColumnIndices:Vector.<int> = new Vector.<int>(); |
| var availableWidth:Number = width; |
| var column:GridColumn; |
| |
| for (colIndex = startColIndex; |
| (availableWidth > 0) && (colIndex >= 0) && (colIndex < colCount); |
| colIndex = getNextVisibleColumnIndex(colIndex)) |
| { |
| newVisibleColumnIndices.push(colIndex); |
| var columnWidth:Number = gridDimensionsView.getColumnWidth(colIndex); |
| if (colIndex == startColIndex) |
| availableWidth -= startCellX + columnWidth - scrollX; |
| else |
| availableWidth -= columnWidth + colGap; |
| } |
| |
| // compute newVisibleRowIndices, newVisibleItemRenderers, layout item renderers |
| |
| const newVisibleRowIndices:Vector.<int> = new Vector.<int>(); |
| const newVisibleItemRenderers:Vector.<IGridItemRenderer> = new Vector.<IGridItemRenderer>(); |
| |
| var cellX:Number = startCellX; |
| var cellY:Number = startCellY; |
| var availableHeight:Number = height; |
| |
| for (rowIndex = startRowIndex; (availableHeight > 0) && (rowIndex >= 0) && (rowIndex < rowCount); rowIndex++) |
| { |
| newVisibleRowIndices.push(rowIndex); |
| |
| var rowHeight:Number = gridDimensionsView.getRowHeight(rowIndex); |
| for each (colIndex in newVisibleColumnIndices) |
| { |
| var renderer:IGridItemRenderer = takeVisibleItemRenderer(rowIndex, colIndex); |
| if (!renderer) |
| { |
| var dataItem:Object = getDataProviderItem(rowIndex); |
| column = getGridColumn(colIndex); |
| var factory:IFactory = itemToRenderer(column, dataItem); |
| renderer = allocateGridElement(factory) as IGridItemRenderer; |
| } |
| if (renderer.parent != rendererLayer) |
| rendererLayer.addElement(renderer); |
| newVisibleItemRenderers.push(renderer); |
| |
| initializeItemRenderer(renderer, rowIndex, colIndex); |
| |
| var colWidth:Number = gridDimensionsView.getColumnWidth(colIndex); |
| layoutItemRenderer(renderer, cellX, cellY, colWidth, rowHeight); |
| |
| var preferredRowHeight:Number = renderer.getPreferredBoundsHeight() |
| gridDimensionsView.setCellHeight(rowIndex, colIndex, preferredRowHeight); |
| cellX += colWidth + colGap; |
| } |
| |
| // If gridDimensions.rowHeight is now larger, we need to make another |
| // pass to fix up the item renderer heights. |
| |
| const finalRowHeight:Number = gridDimensionsView.getRowHeight(rowIndex); |
| if (rowHeight != finalRowHeight) |
| { |
| const visibleColumnsLength:int = newVisibleColumnIndices.length; |
| rowHeight = finalRowHeight; |
| for each (colIndex in newVisibleColumnIndices) |
| { |
| var rowOffset:int = newVisibleRowIndices.indexOf(rowIndex); |
| var colOffset:int = newVisibleColumnIndices.indexOf(colIndex); |
| var index:int = (rowOffset * visibleColumnsLength) + colOffset; |
| renderer = newVisibleItemRenderers[index]; |
| |
| // We're using layoutBoundsX,Y,Width instead of x,y.width because |
| // the IUITextField item renderers pad their x,y,width,height properties |
| var rendererX:Number = renderer.getLayoutBoundsX(); |
| var rendererY:Number = renderer.getLayoutBoundsY(); |
| var rendererWidth:Number = renderer.getLayoutBoundsWidth(); |
| |
| layoutItemRenderer(renderer, rendererX, rendererY, rendererWidth, rowHeight); |
| gridDimensionsView.setCellHeight(rowIndex, colIndex, renderer.getPreferredBoundsHeight()); |
| } |
| } |
| |
| cellX = startCellX; |
| cellY += rowHeight + rowGap; |
| |
| if (rowIndex == startRowIndex) |
| availableHeight -= startCellY + rowHeight - scrollY; |
| else |
| availableHeight -= rowHeight + rowGap; |
| } |
| |
| // Free renderers that aren't in use |
| |
| for each (var oldRenderer:IGridItemRenderer in visibleItemRenderers) |
| freeItemRenderer(oldRenderer); |
| |
| // Update visibleItemRenderersBounds |
| |
| if ((newVisibleRowIndices.length > 0) && (newVisibleColumnIndices.length > 0)) |
| { |
| const lastRowIndex:int = newVisibleRowIndices[newVisibleRowIndices.length - 1]; |
| const lastColIndex:int = newVisibleColumnIndices[newVisibleColumnIndices.length - 1]; |
| const lastCellR:Rectangle = gridDimensionsView.getCellBounds(lastRowIndex, lastColIndex); |
| |
| visibleItemRenderersBounds.x = startCellX; |
| visibleItemRenderersBounds.y = startCellY; |
| visibleItemRenderersBounds.width = lastCellR.x + lastCellR.width - startCellX; |
| visibleItemRenderersBounds.height = lastCellR.y + lastCellR.height - startCellY; |
| } |
| else |
| { |
| visibleItemRenderersBounds.setEmpty(); |
| } |
| |
| // Update visibleItemRenderers et al |
| |
| visibleItemRenderers = newVisibleItemRenderers; |
| visibleRowIndices = newVisibleRowIndices; |
| visibleColumnIndices = newVisibleColumnIndices; |
| } |
| |
| /** |
| * Reinitialize and layout the visible renderer at rowIndex, columnIndex. If the cell's preferred |
| * height changes and the Grid has been configured with variableRowHeight=true, the entire grid is |
| * invalidated. |
| * |
| * <p>If row,columnIndex do not correspond to a visible cell, nothing is done.</p> |
| * |
| * @param rowIndex The 0-based row index of the cell that changed. |
| * @param columnIndex The 0-based column index of the cell that changed. |
| */ |
| public function invalidateCell(rowIndex:int, columnIndex:int):void |
| { |
| const renderer:IGridItemRenderer = getVisibleItemRenderer(rowIndex, columnIndex); |
| if (!renderer) |
| return; |
| |
| // If the renderer at rowIndex,columnIndex is going to have to be replaced, because |
| // this columns itemRendererFunction now returns a different (IFactory) value, punt. |
| |
| if (itemRendererFunctionValueChanged(renderer)) |
| { |
| renderer.grid.invalidateDisplayList(); |
| return; |
| } |
| |
| initializeItemRenderer(renderer, rowIndex, columnIndex); |
| |
| // We're using layoutBoundsX,Y,Width,Height instead of x,y,width,height because |
| // the IUITextField item renderers pad their x,y,width,height properties |
| |
| const rendererX:Number = renderer.getLayoutBoundsX(); |
| const rendererY:Number = renderer.getLayoutBoundsY(); |
| const rendererWidth:Number = renderer.getLayoutBoundsWidth(); |
| const rendererHeight:Number = renderer.getLayoutBoundsHeight(); |
| |
| layoutItemRenderer(renderer, rendererX, rendererY, rendererWidth, rendererHeight); |
| |
| // If the renderer's preferredHeight has changed and variableRowHeight=true, then |
| // the row's height may have changed, which implies we need to layout -everything-. |
| // Warning: the unconditional getPreferredBoundsHeight() call also serves to |
| // force DefaultGridItemRenderer and UITextFieldGridItemRenderer to validate; |
| // similar to what happens in layoutItemRenderers() and updateTypicalCellSizes() |
| |
| const preferredRendererHeight:Number = renderer.getPreferredBoundsHeight(); |
| if (gridDimensionsView.variableRowHeight && (rendererHeight != preferredRendererHeight)) |
| grid.invalidateDisplayList(); |
| } |
| |
| /** |
| * @private |
| * Return true if the specified item renderer was defined by an itemRendererFunction whose |
| * value has changed. |
| */ |
| private function itemRendererFunctionValueChanged(renderer:IGridItemRenderer):Boolean |
| { |
| const column:GridColumn = renderer.column; |
| if (!column || (column.itemRendererFunction === null)) |
| return false; |
| |
| const factory:IFactory = itemToRenderer(column, renderer.data); |
| return factory !== elementToFactoryMap[renderer]; |
| } |
| |
| /** |
| * @private |
| */ |
| private function getVisibleItemRendererIndex(rowIndex:int, columnIndex:int):int |
| { |
| if ((visibleRowIndices == null) || (visibleColumnIndices == null)) |
| return -1; |
| |
| // TODO (hmuller) - binary search would be faster than indexOf() |
| |
| const rowOffset:int = visibleRowIndices.indexOf(rowIndex); |
| const colOffset:int = visibleColumnIndices.indexOf(columnIndex); |
| if ((rowOffset == -1) || (colOffset == -1)) |
| return -1; |
| |
| const index:int = (rowOffset * visibleColumnIndices.length) + colOffset; |
| return index; |
| } |
| |
| /** |
| * Return the visible item renderer at the specified GridView row,columnIndex. |
| */ |
| public function getVisibleItemRenderer(rowIndex:int, columnIndex:int):IGridItemRenderer |
| { |
| const index:int = getVisibleItemRendererIndex(rowIndex, columnIndex); |
| if (index == -1 || index >= visibleItemRenderers.length) |
| return null; |
| |
| const renderer:IGridItemRenderer = visibleItemRenderers[index]; |
| return renderer; |
| } |
| |
| /** |
| * @private |
| */ |
| private function takeVisibleItemRenderer(rowIndex:int, columnIndex:int):IGridItemRenderer |
| { |
| const index:int = getVisibleItemRendererIndex(rowIndex, columnIndex); |
| if (index == -1 || index >= visibleItemRenderers.length) |
| return null; |
| |
| const renderer:IGridItemRenderer = visibleItemRenderers[index]; |
| visibleItemRenderers[index] = null; |
| |
| // If the renderer at rowIndex,columnIndex is going to have to be replaced, because |
| // this column's itemRendererFunction now returns a different (IFactory) value, then |
| // get rid of the old one and return null. |
| |
| if (renderer && itemRendererFunctionValueChanged(renderer)) |
| { |
| freeItemRenderer(renderer); |
| return null; |
| } |
| |
| return renderer; |
| } |
| |
| /** |
| * @private |
| */ |
| private function initializeItemRenderer( |
| renderer:IGridItemRenderer, |
| rowIndex:int, columnIndex:int, |
| dataItem:Object=null, |
| visible:Boolean=true):void |
| { |
| renderer.visible = visible; |
| |
| const gridColumn:GridColumn = getGridColumn(columnIndex); |
| if (gridColumn) |
| { |
| renderer.rowIndex = rowIndex; |
| renderer.column = gridColumn; |
| if (dataItem == null) |
| dataItem = getDataProviderItem(rowIndex); |
| |
| renderer.label = gridColumn.itemToLabel(dataItem); |
| |
| // The following code must be kept in sync with updateVisibleItemRenderers() |
| if (isRowSelectionMode()) |
| { |
| renderer.selected = grid.selectionContainsIndex(rowIndex); |
| renderer.showsCaret = grid.caretRowIndex == rowIndex; |
| renderer.hovered = grid.hoverRowIndex == rowIndex; |
| } |
| else if (isCellSelectionMode()) |
| { |
| renderer.selected = grid.selectionContainsCell(rowIndex, columnIndex); |
| renderer.showsCaret = (grid.caretRowIndex == rowIndex) && (grid.caretColumnIndex == columnIndex); |
| renderer.hovered = (grid.hoverRowIndex == rowIndex) && (grid.hoverColumnIndex == columnIndex); |
| } |
| |
| renderer.data = dataItem; |
| |
| if (grid.dataGrid) |
| renderer.owner = grid.dataGrid; |
| |
| renderer.prepare(!createdGridElement); |
| } |
| } |
| |
| private function freeItemRenderer(renderer:IGridItemRenderer):void |
| { |
| if (!renderer) |
| return; |
| |
| freeGridElement(renderer); |
| renderer.discard(true); |
| } |
| |
| private function freeItemRenderers(renderers:Vector.<IGridItemRenderer>):void |
| { |
| for each (var renderer:IGridItemRenderer in renderers) |
| freeItemRenderer(renderer); |
| renderers.length = 0; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Linear elements: row,column separators, backgrounds |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Common code for laying out the rowBackround, rowSeparator, columnSeparator visual elements. |
| * |
| * For row,columnSeparators, lastIndex identifies the element in the new layout for which |
| * no separator is drawn. If the previous layout - oldVisibleIndices - included the lastIndex, |
| * it needs to be freed, even though it exists in the new layout (newVisibleIndices). See |
| * freeLinearElements(). |
| */ |
| private function layoutLinearElements( |
| factory:IFactory, |
| layer:GridLayer, |
| oldVisibleElements:Vector.<IVisualElement>, |
| oldVisibleIndices:Vector.<int>, |
| newVisibleIndices:Vector.<int>, |
| layoutFunction:Function, |
| lastIndex:int = -1):Vector.<IVisualElement> |
| { |
| if (!layer) |
| return new Vector.<IVisualElement>(0); |
| |
| // If a factory changed, free the old visual elements and set oldVisibleElements.length=0 |
| |
| discardGridElementsIfFactoryChanged(factory, layer, oldVisibleElements); |
| |
| if (factory == null) |
| return new Vector.<IVisualElement>(0); |
| |
| // Free and clear oldVisibleElements that are no long visible |
| |
| freeLinearElements(oldVisibleElements, oldVisibleIndices, newVisibleIndices, lastIndex); |
| |
| // Create, layout, and return newVisibleElements |
| |
| const newVisibleElementCount:uint = newVisibleIndices.length; |
| const newVisibleElements:Vector.<IVisualElement> = new Vector.<IVisualElement>(newVisibleElementCount); |
| |
| for (var index:int = 0; index < newVisibleElementCount; index++) |
| { |
| var newEltIndex:int = newVisibleIndices[index]; |
| if (newEltIndex == lastIndex) |
| { |
| newVisibleElements.length = index; |
| break; |
| } |
| |
| // If an element already exists for visibleIndex then use it, otherwise create one |
| |
| var eltOffset:int = oldVisibleIndices.indexOf(newEltIndex); |
| var elt:IVisualElement = (eltOffset != -1 && eltOffset < oldVisibleElements.length) ? oldVisibleElements[eltOffset] : null; |
| if (elt == null) |
| elt = allocateGridElement(factory); |
| |
| // Initialize the element, and then delegate to the layout function |
| |
| newVisibleElements[index] = elt; |
| |
| layer.addElement(elt); |
| |
| elt.visible = true; |
| |
| layoutFunction(elt, newEltIndex); |
| } |
| |
| return newVisibleElements; |
| } |
| |
| private function layoutCellElements( |
| factory:IFactory, |
| layer:GridLayer, |
| oldVisibleElements:Vector.<IVisualElement>, |
| oldVisibleRowIndices:Vector.<int>, oldVisibleColumnIndices:Vector.<int>, |
| newVisibleRowIndices:Vector.<int>, newVisibleColumnIndices:Vector.<int>, |
| layoutFunction:Function):Vector.<IVisualElement> |
| { |
| if (!layer) |
| return new Vector.<IVisualElement>(0); |
| |
| // If a factory changed, discard the old visual elements. |
| |
| if (discardGridElementsIfFactoryChanged(factory, layer, oldVisibleElements)) |
| { |
| oldVisibleRowIndices.length = 0; |
| oldVisibleColumnIndices.length = 0; |
| } |
| |
| if (factory == null) |
| return new Vector.<IVisualElement>(0); |
| |
| // Create, layout, and return newVisibleElements |
| |
| const newVisibleElementCount:uint = newVisibleRowIndices.length; |
| const newVisibleElements:Vector.<IVisualElement> = new Vector.<IVisualElement>(newVisibleElementCount); |
| |
| // Free and clear oldVisibleElements that are no long visible. |
| |
| freeCellElements(oldVisibleElements, newVisibleElements, |
| oldVisibleRowIndices, newVisibleRowIndices, |
| oldVisibleColumnIndices, newVisibleColumnIndices); |
| |
| for (var index:int = 0; index < newVisibleElementCount; index++) |
| { |
| var newEltRowIndex:int = newVisibleRowIndices[index]; |
| var newEltColumnIndex:int = newVisibleColumnIndices[index]; |
| |
| // If an element already exists for visibleIndex then use it, |
| // otherwise create one. |
| |
| var elt:IVisualElement = newVisibleElements[index]; |
| if (elt === null) |
| { |
| // Initialize the element, and then delegate to the layout |
| // function. |
| elt = allocateGridElement(factory); |
| newVisibleElements[index] = elt; |
| } |
| |
| layer.addElement(elt); |
| |
| elt.visible = true; |
| |
| layoutFunction(elt, newEltRowIndex, newEltColumnIndex); |
| } |
| |
| return newVisibleElements; |
| } |
| |
| /** |
| * @private |
| * If the factory has changed, or is now null, remove and free all the old |
| * visual elements, if there were any. |
| * |
| * @returns True if at least one visual element was removed. |
| */ |
| private function discardGridElementsIfFactoryChanged( |
| factory:IFactory, |
| layer:GridLayer, |
| oldVisibleElements:Vector.<IVisualElement>):Boolean |
| { |
| if ((oldVisibleElements.length) > 0 && (factory != elementToFactoryMap[oldVisibleElements[0]])) |
| { |
| for each (var oldElt:IVisualElement in oldVisibleElements) |
| { |
| layer.removeElement(oldElt); |
| freeGridElement(oldElt); |
| } |
| oldVisibleElements.length = 0; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * @private |
| * Free each member of elements if the corresponding member of oldIndices doesn't |
| * appear in newIndices. Both vectors of indices must have been sorted in increasing |
| * order. When an element is freed, the corresponding member of the vector parameter |
| * is set to null. |
| * |
| * This method is [supposed to be a] somewhat more efficient implementation of the following: |
| * |
| * for (var i:int = 0; i < elements.length; i++) |
| * { |
| * if ((oldIndices[i] == lastIndex) || (newIndices.indexOf(oldIndices[i]) == -1)) |
| * freeGridElement(elements[i]); |
| * elements[i] = null; |
| * } |
| * |
| * The lastIndex parameter is used to handle row and column separators, where the last |
| * element is left out since separators only appear in between elements. If the lastIndex |
| * appears in oldIndices, we're not going to need the old element. |
| */ |
| private function freeLinearElements ( |
| elements:Vector.<IVisualElement>, |
| oldIndices:Vector.<int>, |
| newIndices:Vector.<int>, |
| lastIndex:int):void |
| { |
| // TODO(hmuller): rewrite this, should be one pass (no indexOf) |
| for (var i:int = 0; i < elements.length; i++) |
| { |
| const offset:int = newIndices.indexOf(oldIndices[i]); |
| if ((oldIndices[i] == lastIndex) || (offset == -1)) |
| { |
| const elt:IVisualElement = elements[i]; |
| if (elt) |
| { |
| freeGridElement(elt); |
| elements[i] = null; |
| } |
| } |
| } |
| } |
| |
| private function freeCellElements ( |
| elements:Vector.<IVisualElement>, newElements:Vector.<IVisualElement>, |
| oldRowIndices:Vector.<int>, newRowIndices:Vector.<int>, |
| oldColumnIndices:Vector.<int>, newColumnIndices:Vector.<int>):void |
| { |
| var freeElement:Boolean = true; |
| |
| // assumes newRowIndices.length == newColumnIndices.length |
| const numNewCells:int = newRowIndices.length; |
| var newIndex:int = 0; |
| |
| for (var i:int = 0; i < elements.length; i++) |
| { |
| const elt:IVisualElement = elements[i]; |
| if (elt == null) |
| continue; |
| |
| // assumes oldIndices.length == elements.length |
| const oldRowIndex:int = oldRowIndices[i]; |
| const oldColumnIndex:int = oldColumnIndices[i]; |
| |
| for ( ; newIndex < numNewCells; newIndex++) |
| { |
| const newRowIndex:int = newRowIndices[newIndex]; |
| const newColumnIndex:int = newColumnIndices[newIndex]; |
| |
| if (newRowIndex == oldRowIndex) |
| { |
| if (newColumnIndex == oldColumnIndex) |
| { |
| // Same cell still selected so reuse the selection. |
| // Save it in the correct place in newElements. That |
| // way we know its location based on |
| // newRowIndices[newIndex], newColumnIndices[newIndex]. |
| newElements[newIndex] = elt; |
| freeElement = false; |
| break; |
| } |
| else if (newColumnIndex > oldColumnIndex) |
| { |
| // not found |
| break; |
| } |
| } |
| else if (newRowIndex > oldRowIndex) |
| { |
| // not found |
| break; |
| } |
| } |
| |
| if (freeElement) |
| freeGridElement(elt); |
| |
| freeElement = true; |
| } |
| |
| elements.length = 0; |
| } |
| |
| private function layoutRowBackground(rowBackground:IVisualElement, rowIndex:int):void |
| { |
| const rowCount:int = gridDimensionsView.rowCount; |
| const bounds:Rectangle = (rowIndex < rowCount) |
| ? gridDimensionsView.getRowBounds(rowIndex) |
| : gridDimensionsView.getPadRowBounds(rowIndex); |
| |
| if (!bounds) |
| return; |
| |
| if ((rowIndex < rowCount) && (bounds.width == 0)) // implies no columns |
| bounds.width = visibleGridBounds.width; |
| |
| // Initialize this visual element |
| intializeGridVisualElement(rowBackground, rowIndex); |
| |
| layoutGridElementR(rowBackground, bounds); |
| } |
| |
| private function layoutRowSeparator(separator:IVisualElement, rowIndex:int):void |
| { |
| // Initialize this visual element |
| intializeGridVisualElement(separator, rowIndex); |
| |
| const height:Number = separator.getPreferredBoundsHeight(); |
| const rowCount:int = gridDimensionsView.rowCount; |
| const bounds:Rectangle = (rowIndex < rowCount) |
| ? gridDimensionsView.getRowBounds(rowIndex) |
| : gridDimensionsView.getPadRowBounds(rowIndex); |
| |
| if (!bounds) |
| return; |
| |
| const x:Number = bounds.x; |
| const width:Number = Math.max(bounds.width, visibleGridBounds.right); |
| const y:Number = bounds.bottom; // TODO (klin): should center on gap here. |
| layoutGridElement(separator, x, y, width, height); |
| } |
| |
| private function layoutColumnSeparator(separator:IVisualElement, columnIndex:int):void |
| { |
| // Initialize this visual element |
| intializeGridVisualElement(separator, -1, columnIndex); |
| |
| const r:Rectangle = visibleItemRenderersBounds; |
| const width:Number = separator.getPreferredBoundsWidth(); |
| const height:Number = Math.max(r.height, visibleGridBounds.height); |
| const x:Number = gridDimensionsView.getCellX(0, columnIndex) + gridDimensionsView.getColumnWidth(columnIndex); // TODO (klin): should center on gap here. |
| const y:Number = r.y; |
| layoutGridElement(separator, x, y, width, height); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Selection Indicators |
| // |
| //-------------------------------------------------------------------------- |
| |
| private var visibleSelectionIndicators:Vector.<IVisualElement> = new Vector.<IVisualElement>(0); |
| private var visibleRowSelectionIndices:Vector.<int> = new Vector.<int>(0); |
| private var visibleColumnSelectionIndices:Vector.<int> = new Vector.<int>(0); |
| |
| private function isRowSelectionMode():Boolean |
| { |
| const mode:String = grid.selectionMode; |
| return (mode == GridSelectionMode.SINGLE_ROW) || (mode == GridSelectionMode.MULTIPLE_ROWS); |
| } |
| |
| private function isCellSelectionMode():Boolean |
| { |
| const mode:String = grid.selectionMode; |
| return mode == (GridSelectionMode.SINGLE_CELL) || (mode == GridSelectionMode.MULTIPLE_CELLS); |
| } |
| |
| private function layoutSelectionIndicators(layer:GridLayer):void |
| { |
| const selectionIndicatorFactory:IFactory = grid.selectionIndicator; |
| const viewRowIndex:int = viewRowIndex; |
| const viewColumnIndex:int = viewColumnIndex; |
| |
| // layout and update visibleSelectionIndicators,Indices |
| |
| if (isRowSelectionMode()) |
| { |
| // Selection is row-based so if there are existing cell selections, |
| // free them since they can't be reused. |
| if (visibleColumnSelectionIndices.length > 0) |
| clearSelectionIndicators(); |
| |
| var oldVisibleRowSelectionIndices:Vector.<int> = visibleRowSelectionIndices; |
| |
| // Load this up with the currently selected rows. |
| visibleRowSelectionIndices = new Vector.<int>(); |
| |
| for each (var rowIndex:int in visibleRowIndices) |
| { |
| if (grid.selectionContainsIndex(rowIndex + viewRowIndex)) |
| { |
| visibleRowSelectionIndices.push(rowIndex); |
| } |
| } |
| |
| // Display the row selections. |
| visibleSelectionIndicators = layoutLinearElements( |
| selectionIndicatorFactory, |
| layer, |
| visibleSelectionIndicators, |
| oldVisibleRowSelectionIndices, |
| visibleRowSelectionIndices, |
| layoutRowSelectionIndicator); |
| |
| return; |
| } |
| |
| // Selection is not row-based so if there are existing row selections, |
| // free them since they can't be reused. |
| if (visibleRowSelectionIndices.length > 0 && visibleColumnSelectionIndices.length == 0) |
| { |
| clearSelectionIndicators(); |
| } |
| |
| if (isCellSelectionMode()) |
| { |
| oldVisibleRowSelectionIndices = visibleRowSelectionIndices; |
| const oldVisibleColumnSelectionIndices:Vector.<int> = visibleColumnSelectionIndices; |
| |
| // Load up the vectors with the row/column of each selected cell. |
| visibleRowSelectionIndices = new Vector.<int>(); |
| visibleColumnSelectionIndices = new Vector.<int>(); |
| for each (rowIndex in visibleRowIndices) |
| { |
| for each (var columnIndex:int in visibleColumnIndices) |
| { |
| if (grid.selectionContainsCell(rowIndex + viewRowIndex, columnIndex + viewColumnIndex)) |
| { |
| visibleRowSelectionIndices.push(rowIndex); |
| visibleColumnSelectionIndices.push(columnIndex); |
| } |
| } |
| } |
| |
| // Display the cell selections. |
| visibleSelectionIndicators = layoutCellElements( |
| selectionIndicatorFactory, |
| layer, |
| visibleSelectionIndicators, |
| oldVisibleRowSelectionIndices, oldVisibleColumnSelectionIndices, |
| visibleRowSelectionIndices, visibleColumnSelectionIndices, |
| layoutCellSelectionIndicator); |
| |
| return; |
| } |
| |
| // No selection. |
| |
| // If there are existing cell selections, |
| // free them since there is no selection. |
| if (visibleColumnSelectionIndices.length > 0) |
| clearSelectionIndicators(); |
| } |
| |
| private function layoutRowSelectionIndicator(indicator:IVisualElement, rowIndex:int):void |
| { |
| // Initialize this visual element |
| intializeGridVisualElement(indicator, rowIndex); |
| layoutGridElementR(indicator, gridDimensionsView.getRowBounds(rowIndex)); |
| } |
| |
| private function layoutCellSelectionIndicator(indicator:IVisualElement, |
| rowIndex:int, |
| columnIndex:int):void |
| { |
| // Initialize this visual element |
| intializeGridVisualElement(indicator, rowIndex, columnIndex); |
| layoutGridElementR(indicator, gridDimensionsView.getCellBounds(rowIndex, columnIndex)); |
| } |
| |
| private function clearSelectionIndicators():void |
| { |
| freeGridElements(visibleSelectionIndicators); |
| visibleRowSelectionIndices.length = 0; |
| visibleColumnSelectionIndices.length = 0; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Indicators: hover, caret |
| // |
| //-------------------------------------------------------------------------- |
| |
| private function layoutIndicator( |
| layer:GridLayer, |
| indicatorFactory:IFactory, |
| indicator:IVisualElement, |
| rowIndex:int, |
| columnIndex:int):IVisualElement |
| { |
| if (!layer) |
| return null; |
| |
| // If the indicatorFactory has changed for the specified non-null indicator, |
| // then free the old indicator. |
| |
| if (indicator && (indicatorFactory != elementToFactoryMap[indicator])) |
| { |
| removeGridElement(indicator); |
| indicator = null; |
| if (indicatorFactory == null) |
| return null; |
| } |
| |
| if (rowIndex == -1 || grid.selectionMode == GridSelectionMode.NONE || |
| (isCellSelectionMode() && (getNextVisibleColumnIndex(columnIndex - 1) != columnIndex))) |
| { |
| if (indicator) |
| indicator.visible = false; |
| return indicator; |
| } |
| |
| if (!indicator && indicatorFactory) |
| indicator = createGridElement(indicatorFactory); |
| |
| if (indicator) |
| { |
| const bounds:Rectangle = isRowSelectionMode() ? |
| gridDimensionsView.getRowBounds(rowIndex) : |
| gridDimensionsView.getCellBounds(rowIndex, columnIndex); |
| |
| intializeGridVisualElement(indicator, rowIndex, columnIndex); |
| |
| // TODO (klin): Remove this special case for the caret overlapping separators |
| // when we implement column/row gaps. |
| if (indicatorFactory == grid.caretIndicator && bounds) |
| { |
| // increase width and height by 1 to cover separator. |
| const columnsLength:int = getColumnsLength(); |
| if (isCellSelectionMode() && (columnIndex < columnsLength - 1)) |
| bounds.width += 1; |
| |
| //if ((rowIndex < grid.dataProvider.length - 1) || (visibleRowIndices.length > grid.dataProvider.length)) |
| // bounds.height += 1; |
| const dataProviderLength:int = getDataProviderLength(); |
| if ((rowIndex < dataProviderLength) || (visibleRowIndices.length > dataProviderLength)) |
| bounds.height += 1; |
| } |
| |
| layoutGridElementR(indicator, bounds); |
| layer.addElement(indicator); |
| indicator.visible = true; |
| } |
| |
| return indicator; |
| } |
| |
| private var mouseXOffset:Number = 0; |
| private var mouseYOffset:Number = 0; |
| |
| private function layoutHoverIndicator(layer:GridLayer):void |
| { |
| const rowIndex:int = gridRowIndexToViewIndex(grid.hoverRowIndex); |
| const columnIndex:int = gridColumnIndexToViewIndex(grid.hoverColumnIndex); |
| const factory:IFactory = grid.hoverIndicator; |
| hoverIndicator = layoutIndicator(layer, factory, hoverIndicator, rowIndex, columnIndex); |
| } |
| |
| private function layoutCaretIndicator(layer:GridLayer):void |
| { |
| const rowIndex:int = gridRowIndexToViewIndex(grid.caretRowIndex); |
| const columnIndex:int = gridColumnIndexToViewIndex(grid.caretColumnIndex); |
| const factory:IFactory = grid.caretIndicator; |
| caretIndicator = layoutIndicator(layer, factory, caretIndicator, rowIndex, columnIndex); |
| |
| // Hide caret based on the showCaret property. Don't show caret if its |
| // already hidden by layoutIndicator() because it has an invalid position. |
| |
| if (caretIndicator && !grid.showCaret) |
| caretIndicator.visible = grid.showCaret; |
| } |
| |
| private function layoutEditorIndicator(layer:GridLayer):void |
| { |
| const dataGrid:DataGrid = grid.dataGrid; |
| if (!dataGrid) |
| return; |
| |
| const rowIndex:int = gridRowIndexToViewIndex(dataGrid.editorRowIndex); |
| const columnIndex:int = gridColumnIndexToViewIndex(dataGrid.editorColumnIndex); |
| var indicatorFactory:IFactory = dataGrid.editorIndicator; |
| |
| // If the indicatorFactory has changed for the specified non-null indicator, |
| // then free the old indicator. |
| |
| if (editorIndicator && (indicatorFactory != elementToFactoryMap[editorIndicator])) |
| { |
| removeGridElement(editorIndicator); |
| editorIndicator = null; |
| if (indicatorFactory == null) |
| return; |
| } |
| |
| if (rowIndex == -1 || columnIndex == -1) |
| { |
| if (editorIndicator) |
| editorIndicator.visible = false; |
| return; |
| } |
| |
| if (!editorIndicator && indicatorFactory) |
| editorIndicator = createGridElement(indicatorFactory); |
| |
| if (editorIndicator) |
| { |
| const bounds:Rectangle = gridDimensionsView.getCellBounds(rowIndex, columnIndex); |
| |
| // Initialize this visual element |
| intializeGridVisualElement(editorIndicator, rowIndex, columnIndex); |
| |
| layoutGridElementR(editorIndicator, bounds); |
| layer.addElement(editorIndicator); |
| editorIndicator.visible = true; |
| } |
| |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // CollectionEvent handling: dataProvider, columns |
| // |
| //-------------------------------------------------------------------------- |
| |
| public function dataProviderCollectionChanged(event:CollectionEvent):void |
| { |
| switch (event.kind) |
| { |
| case CollectionEventKind.ADD: |
| { |
| dataProviderCollectionAdd(event); |
| break; |
| } |
| |
| case CollectionEventKind.REMOVE: |
| { |
| dataProviderCollectionRemove(event); |
| break; |
| } |
| |
| case CollectionEventKind.MOVE: |
| { |
| // TBD(hmuller) |
| break; |
| } |
| |
| case CollectionEventKind.REFRESH: |
| case CollectionEventKind.RESET: |
| { |
| dataProviderCollectionReset(event); |
| break; |
| } |
| |
| case CollectionEventKind.UPDATE: |
| { |
| dataProviderCollectionUpdate(event); |
| break; |
| } |
| |
| case CollectionEventKind.REPLACE: |
| { |
| break; |
| } |
| } |
| } |
| |
| /** |
| * @private |
| * Called in response to one or more items having been inserted into the |
| * grid's dataProvider. Ensure that visibleRowIndices and visibleRowSelectionIndices |
| * correspond to the same, potentially shifted, dataProvider items. |
| */ |
| private function dataProviderCollectionAdd(event:CollectionEvent):void |
| { |
| const insertIndex:int = event.location - viewRowIndex; |
| const insertLength:int = event.items.length; |
| incrementIndicesGTE(visibleRowIndices, insertIndex, insertLength); |
| incrementIndicesGTE(visibleRowSelectionIndices, insertIndex, insertLength); |
| } |
| |
| /** |
| * @private |
| * Called in response to one or more items having been removed from the |
| * grid's dataProvider. |
| */ |
| private function dataProviderCollectionRemove(event:CollectionEvent):void |
| { |
| const eventItemsLength:uint = event.items.length; |
| const firstRemoveIndex:int = event.location - viewRowIndex; |
| const lastRemoveIndex:int = firstRemoveIndex + eventItemsLength - 1; |
| |
| // Compute the range of visibleRowIndices elements affected by the remove event. |
| // And while we're at it, decrement the visibleRowIndices "to the right of" the |
| // deleted items. |
| |
| var firstVisibleOffset:int = -1; // remove visibleRowIndices[firstVisibleOffset] |
| var lastVisibleOffset:int = -1; // ... through visibleRowIndices[lastVisibleOffset] |
| |
| for (var offset:int = 0; offset < visibleRowIndices.length; offset++) |
| { |
| var rowIndex:int = visibleRowIndices[offset]; |
| if ((rowIndex >= firstRemoveIndex) && (rowIndex <= lastRemoveIndex)) |
| { |
| if (firstVisibleOffset == -1) |
| firstVisibleOffset = lastVisibleOffset = offset; |
| else |
| lastVisibleOffset = offset; |
| } |
| else if (rowIndex > lastRemoveIndex) |
| { |
| visibleRowIndices[offset] = rowIndex - eventItemsLength; |
| } |
| } |
| |
| // Remove the elements of visibleRowBackgrounds, visibleRowSeparators, visibleRowIndices, |
| // and visibleItemRenderers in the range firstVisibleOffset, lastVisibleOffset. |
| |
| if ((firstVisibleOffset != -1) && (lastVisibleOffset != -1)) |
| { |
| const removeCount:int = (lastVisibleOffset - firstVisibleOffset) + 1; |
| visibleRowIndices.splice(firstVisibleOffset, removeCount); |
| |
| if (lastVisibleOffset < visibleRowBackgrounds.length) |
| freeGridElements(visibleRowBackgrounds.splice(firstVisibleOffset, removeCount)); |
| |
| if (lastVisibleOffset < visibleRowSeparators.length) |
| freeGridElements(visibleRowSeparators.splice(firstVisibleOffset, removeCount)); |
| |
| const visibleColCount:int = visibleColumnIndices.length; |
| const firstRendererOffset:int = firstVisibleOffset * visibleColCount; |
| freeItemRenderers(visibleItemRenderers.splice(firstRendererOffset, removeCount * visibleColCount)); |
| } |
| } |
| |
| /** |
| * @private |
| * Increment the elements of indices that are >= insertIndex by delta. |
| */ |
| private function incrementIndicesGTE(indices:Vector.<int>, insertIndex:int, delta:int):void |
| { |
| const indicesLength:int = indices.length; |
| for (var i:int = 0; i < indicesLength; i++) |
| { |
| var index:int = indices[i]; |
| if (index >= insertIndex) |
| { |
| indices[i] = index + delta; |
| } |
| } |
| } |
| |
| /** |
| * @private |
| * Called in response to a refresh/reset CollectionEvent. Clear everything. |
| */ |
| private function dataProviderCollectionReset(event:CollectionEvent):void |
| { |
| clearVirtualLayoutCache(); |
| } |
| |
| /** |
| * @private |
| * Called in response to an item being updated in the dataProvider. Checks |
| * to see if the item is visible and invalidates the grid if it is. Otherwise, |
| * do nothing. |
| */ |
| private function dataProviderCollectionUpdate(event:CollectionEvent):void |
| { |
| var data:Object; |
| const itemsLength:int = event.items.length; |
| const itemRenderersLength:int = visibleItemRenderers.length; |
| |
| for (var i:int = 0; i < itemsLength; i++) |
| { |
| data = PropertyChangeEvent(event.items[i]).source; |
| |
| for (var j:int = 0; j < itemRenderersLength; j++) |
| { |
| var renderer:IGridItemRenderer = visibleItemRenderers[j] as IGridItemRenderer; |
| if (renderer && renderer.data == data) |
| { |
| this.freeItemRenderer(renderer); |
| visibleItemRenderers[j] = null; |
| } |
| } |
| } |
| } |
| |
| /** |
| * @private |
| * This handler runs AFTER the GridDimension object has been updated. |
| */ |
| public function columnsCollectionChanged(event:CollectionEvent):void |
| { |
| switch (event.kind) |
| { |
| case CollectionEventKind.UPDATE: |
| { |
| clearVirtualLayoutCache(); |
| break; |
| } |
| |
| default: |
| { |
| clearVirtualLayoutCache(); |
| if (grid) |
| grid.setContentSize(0, 0); |
| break; |
| } |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Grid Elements |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Let the allocateGridElement() caller know if the returned element was |
| * created or recycled. |
| */ |
| private var createdGridElement:Boolean = false; |
| |
| private function createGridElement(factory:IFactory):IVisualElement |
| { |
| createdGridElement = true; |
| const element:IVisualElement = factory.newInstance() as IVisualElement; |
| elementToFactoryMap[element] = factory; |
| return element; |
| } |
| |
| /** |
| * @private |
| * Return an element the factory-specific free-list, or create a new element, |
| * with createGridElement, if a free element isn't available. |
| */ |
| private function allocateGridElement(factory:IFactory):IVisualElement |
| { |
| createdGridElement = false; |
| const elements:Vector.<IVisualElement> = freeElementMap[factory] as Vector.<IVisualElement>; |
| if (elements) |
| { |
| const element:IVisualElement = elements.pop(); |
| if (elements.length == 0) |
| delete freeElementMap[factory]; |
| if (element) |
| return element; |
| } |
| |
| return createGridElement(factory); |
| } |
| |
| /** |
| * @private |
| * Move the specified element to the free list after hiding it. Return true if the |
| * element was added to the free list (freeElements). Note that we do not remove |
| * the element from its parent. |
| */ |
| private function freeGridElement(element:IVisualElement):Boolean |
| { |
| if (!element) |
| return false; |
| |
| element.visible = false; |
| |
| const factory:IFactory = elementToFactoryMap[element]; |
| if (!factory) |
| return false; |
| |
| // Add the renderer to the freeElementMap |
| |
| var freeElements:Vector.<IVisualElement> = freeElementMap[factory]; |
| if (!freeElements) |
| { |
| freeElements = new Vector.<IVisualElement>(); |
| freeElementMap[factory] = freeElements; |
| } |
| freeElements.push(element); |
| |
| return true; |
| } |
| |
| private function freeGridElements(elements:Vector.<IVisualElement>):void |
| { |
| for each (var elt:IVisualElement in elements) |
| freeGridElement(elt); |
| elements.length = 0; |
| } |
| |
| /** |
| * @private |
| * Remove the element from the elementToFactory map and from the per-factory free list and, finally, |
| * from its container. On the off chance that someone is monitoring the visible property, |
| * we set that to false, just for good measure. |
| */ |
| private function removeGridElement(element:IVisualElement):void |
| { |
| const factory:IFactory = elementToFactoryMap[element]; |
| const freeElements:Vector.<IVisualElement> = (factory) ? freeElementMap[factory] : null; |
| if (freeElements) |
| { |
| const index:int = freeElements.indexOf(element); |
| if (index != -1) |
| freeElements.splice(index, 1); |
| if (freeElements.length == 0) |
| delete freeElementMap[factory]; |
| } |
| |
| delete elementToFactoryMap[element]; |
| |
| element.visible = false; |
| const parent:IVisualElementContainer = element.parent as IVisualElementContainer; |
| if (parent) |
| parent.removeElement(element); |
| } |
| |
| /** |
| * @private |
| */ |
| private function layoutItemRenderer(renderer:IGridItemRenderer, x:Number, y:Number, width:Number, height:Number):void |
| { |
| if (!isNaN(width) || !isNaN(height)) |
| { |
| if (renderer is ILayoutManagerClient) |
| { |
| const validateClientRenderer:ILayoutManagerClient = renderer as ILayoutManagerClient; |
| LayoutManager.getInstance().validateClient(validateClientRenderer, true); // true => skip validateDisplayList() |
| } |
| else if (renderer is IGraphicElement) |
| { |
| const graphicElementRenderer:IGraphicElement = renderer as IGraphicElement; |
| graphicElementRenderer.validateProperties(); |
| graphicElementRenderer.validateSize(); |
| } |
| |
| renderer.setLayoutBoundsSize(width, height); |
| } |
| |
| if ((renderer is IInvalidating) && !(renderer is IGraphicElement)) |
| { |
| const validateNowRenderer:IInvalidating = renderer as IInvalidating; |
| validateNowRenderer.validateNow(); |
| } |
| |
| renderer.setLayoutBoundsPosition(x, y); |
| } |
| |
| private function layoutGridElementR(elt:IVisualElement, bounds:Rectangle):void |
| { |
| if (bounds) |
| layoutGridElement(elt, bounds.x, bounds.y, bounds.width, bounds.height); |
| } |
| |
| private static const MAX_ELEMENT_SIZE:Number = 8192; |
| private static const ELEMENT_EDGE_PAD:Number = 512; |
| |
| |
| /** |
| * @private |
| * Set the visual element's layoutBounds size and position. |
| * |
| * Attempting to render graphics whose size is larger than MAX_ELEMENT_SIZE can cause the |
| * Flash Player to fail. We reduce the size of visual elements here and preserve |
| * the visibility of edges. For example if the element's left edge is not showing, |
| * then we ensure that it's no more than ELEMENT_EDGE_PAD to the left of the left edge of |
| * the scrollRect (that's the horizontalScrollPosition). The unfortunate assumption here |
| * is that shrinking the size of a visual element in this way will not affect the appearance |
| * of the part of the element that overlaps the scrollRect. |
| */ |
| private function layoutGridElement(elt:IVisualElement, x:Number, y:Number, width:Number, height:Number):void |
| { |
| if (width > MAX_ELEMENT_SIZE) |
| { |
| const scrollX:Number = Math.max(0, horizontalScrollPosition); |
| const gridWidth:Number = target.getLayoutBoundsWidth(); |
| |
| const newX:Number = Math.max(x, scrollX - ELEMENT_EDGE_PAD); |
| const newRight:Number = Math.min(x + width, scrollX + gridWidth + ELEMENT_EDGE_PAD); |
| |
| x = newX; |
| width = newRight - newX; |
| } |
| |
| if (height > MAX_ELEMENT_SIZE) |
| { |
| const scrollY:Number = Math.max(0, verticalScrollPosition); |
| const gridHeight:Number = target.getLayoutBoundsHeight(); |
| |
| const newY:Number = Math.max(y, scrollY - ELEMENT_EDGE_PAD); |
| const newBottom:Number = Math.min(y + height, scrollY + gridHeight + ELEMENT_EDGE_PAD); |
| |
| y = newY; |
| height = newBottom - newY; |
| } |
| |
| elt.setLayoutBoundsSize(width, height); |
| elt.setLayoutBoundsPosition(x, y); |
| } |
| |
| /** |
| * @private |
| * Calls <code>prepareGridVisualElement()</code> on the element if it is an |
| * IGridVisualElement. |
| */ |
| private function intializeGridVisualElement(elt:IVisualElement, rowIndex:int = -1, columnIndex:int = -1):void |
| { |
| const gridVisualElement:IGridVisualElement = elt as IGridVisualElement; |
| if (gridVisualElement) |
| { |
| gridVisualElement.prepareGridVisualElement(grid, rowIndex, columnIndex); |
| } |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Public API Exported for Grid Cover Methods |
| // |
| // The methods in this section return indices and coordinates relative to |
| // the Grid, not this layout's target GridView. Incoming parameters are |
| // similarly Grid-relative. |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Returns the Grid-relative bounds of the visible part of the target GridView. |
| */ |
| public function getVisibleBounds():Rectangle |
| { |
| const x:Number = gridDimensionsView.viewOriginX + target.horizontalScrollPosition; |
| const y:Number = gridDimensionsView.viewOriginY + target.verticalScrollPosition; |
| return new Rectangle(x, y, target.width, target.height); |
| } |
| |
| /** |
| * @copy spark.components.Grid#getVisibleRowIndices() |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.0 |
| * @productversion Flex 4.5 |
| */ |
| public function getVisibleRowIndices():Vector.<int> |
| { |
| const indexOffset:int = viewRowIndex; |
| const indices:Vector.<int> = visibleRowIndices.concat(); |
| for (var i:int = 0; i < indices.length; i++) |
| indices[i] += indexOffset; |
| return indices; |
| } |
| |
| /** |
| * @copy spark.components.Grid#getVisibleColumnIndices() |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.0 |
| * @productversion Flex 4.5 |
| */ |
| public function getVisibleColumnIndices():Vector.<int> |
| { |
| const indexOffset:int = viewColumnIndex; |
| const indices:Vector.<int> = visibleColumnIndices.concat(); |
| for (var i:int = 0; i < indices.length; i++) |
| indices[i] += indexOffset; |
| return indices; |
| } |
| |
| /** |
| * Return the Grid-relative index of the first fully visible row: if the first rowIndex |
| * element of visibleRowIndices hasn't been completely scrolled into view, then return |
| * the second element. |
| * |
| * If no rows are visible, return -1. If only one row is visible, return its index. |
| */ |
| public function getFirstFullyVisibleRowIndex():int |
| { |
| const visibleRowIndicesLength:int = visibleRowIndices.length; |
| if (visibleRowIndicesLength == 0) |
| return -1; |
| |
| // If the first visible row's Y origin is "above" ther current verticalScrollPosition, |
| // and there is a second visible row, then use the second one. |
| |
| const rowIndex:int = visibleRowIndices[0]; |
| const rowY:Number = gridDimensionsView.getCellY(rowIndex, -1); |
| const useSecondVisibleRow:Boolean = (rowY < verticalScrollPosition) && (visibleRowIndicesLength > 1); |
| return viewRowIndex + (useSecondVisibleRow ? visibleRowIndices[1] : rowIndex); |
| } |
| |
| /** |
| * Return the Grid-relative index of the last fully visible row: if the last rowIndex |
| * element of visibleRowIndices hasn't been completely scrolled into view, then return the |
| * second to last element. |
| * |
| * If no rows are visible, return -1. If only one row is visible, return its index. |
| */ |
| public function getLastFullyVisibleRowIndex():int |
| { |
| const visibleRowIndicesLength:int = visibleRowIndices.length; |
| if (visibleRowIndicesLength == 0) |
| return -1; |
| |
| const rowIndex:int = visibleRowIndices[visibleRowIndicesLength - 1]; |
| const rowY:Number = gridDimensionsView.getCellY(rowIndex, -1) + gridDimensionsView.getRowHeight(rowIndex); |
| const scrollY:Number = verticalScrollPosition + target.getLayoutBoundsHeight(); |
| const useSecondVisibleRow:Boolean = (rowY > scrollY) && (visibleRowIndicesLength > 1); |
| return viewRowIndex + (useSecondVisibleRow ? visibleRowIndices[visibleRowIndicesLength - 2] : rowIndex); |
| } |
| |
| /** |
| * @copy spark.components.Grid#getItemRendererAt() |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.0 |
| * @productversion Flex 4.5 |
| */ |
| public function getItemRendererAt(rowIndex:int, columnIndex:int):IGridItemRenderer |
| { |
| rowIndex = gridRowIndexToViewIndex(rowIndex); |
| columnIndex = gridColumnIndexToViewIndex(columnIndex); |
| |
| const visibleItemRenderer:IGridItemRenderer = getVisibleItemRenderer(rowIndex, columnIndex); |
| if (visibleItemRenderer) |
| return visibleItemRenderer; |
| |
| const rendererLayer:GridLayer = getLayer("rendererLayer"); |
| if (!rendererLayer) |
| return null; |
| |
| // Create an item renderer. |
| var dataItem:Object = getDataProviderItem(rowIndex); |
| var column:GridColumn = getGridColumn(columnIndex); |
| |
| // Invalid row or column. |
| if (dataItem == null || column == null) |
| return null; |
| |
| // column is GridColumn.visible==false |
| if (!column.visible) |
| return null; |
| |
| const factory:IFactory = itemToRenderer(column, dataItem); |
| const renderer:IGridItemRenderer = factory.newInstance() as IGridItemRenderer; |
| createdGridElement = true; // initializeItemRenderer() depends on this |
| |
| rendererLayer.addElement(renderer); |
| |
| initializeItemRenderer(renderer, rowIndex, columnIndex, dataItem, false); |
| |
| // The width/height may change later if the cell becomes visible. |
| var bounds:Rectangle = gridDimensionsView.getCellBounds(rowIndex, columnIndex); |
| if (bounds == null) |
| return null; |
| layoutItemRenderer(renderer, bounds.x, bounds.y, bounds.width, bounds.height); |
| |
| rendererLayer.removeElement(renderer); |
| renderer.visible = false; |
| |
| return renderer; |
| } |
| |
| /** |
| * @copy spark.components.Grid#isCellVisible() |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 10 |
| * @playerversion AIR 2.0 |
| * @productversion Flex 4.5 |
| */ |
| public function isCellVisible(rowIndex:int, columnIndex:int):Boolean |
| { |
| if (rowIndex == -1 && columnIndex == -1) |
| return false; |
| |
| rowIndex = gridRowIndexToViewIndex(rowIndex); |
| columnIndex = gridColumnIndexToViewIndex(columnIndex); |
| |
| return ((rowIndex == -1) || (visibleRowIndices.indexOf(rowIndex) != -1)) && |
| ((columnIndex == -1) || (visibleColumnIndices.indexOf(columnIndex) != -1)); |
| } |
| } |
| } |
| |
| import flash.utils.getQualifiedClassName; |
| |
| import mx.core.ClassFactory; |
| import mx.core.IFactory; |
| import mx.core.IFlexModuleFactory; |
| |
| import spark.components.Grid; |
| |
| /** |
| * @private |
| * A wrapper class for item renderers that creates the renderer instance with the grid's |
| * module factory. |
| * |
| * This is necessary for applications that use embedded fonts. The module factory creates |
| * the renderer instance in the correct "font context" in the same way as ContextualClassFactory |
| * does. More about this in the ContextualClassFactory ASDoc. |
| */ |
| class GridItemRendererClassFactory extends ClassFactory |
| { |
| public var grid:Grid; |
| public var factory:ClassFactory; |
| |
| public function GridItemRendererClassFactory(grid:Grid, factory:ClassFactory) |
| { |
| super(factory.generator); |
| this.grid = grid; |
| this.factory = factory; |
| } |
| |
| override public function newInstance():* |
| { |
| const factoryGenerator:Class = factory.generator; |
| const moduleFactory:IFlexModuleFactory = grid.moduleFactory; |
| const instance:Object = |
| (moduleFactory) ? moduleFactory.create(getQualifiedClassName(factoryGenerator)) : new factoryGenerator(); |
| |
| const factoryProperties:Object = factory.properties; |
| if (factoryProperties) |
| { |
| for (var p:String in factoryProperties) |
| instance[p] = factoryProperties[p]; |
| } |
| |
| return instance; |
| } |
| } |