You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flex.apache.org by cf...@apache.org on 2012/12/21 19:05:22 UTC
svn commit: r1425063 [8/13] - in /incubator/flex/sdk/branches/develop:
frameworks/ frameworks/projects/spark/ frameworks/projects/spark/src/
frameworks/projects/spark/src/spark/collections/
frameworks/projects/spark/src/spark/components/ frameworks/pro...
Added: incubator/flex/sdk/branches/develop/frameworks/projects/spark/src/spark/components/gridClasses/GridViewLayout.as
URL: http://svn.apache.org/viewvc/incubator/flex/sdk/branches/develop/frameworks/projects/spark/src/spark/components/gridClasses/GridViewLayout.as?rev=1425063&view=auto
==============================================================================
--- incubator/flex/sdk/branches/develop/frameworks/projects/spark/src/spark/components/gridClasses/GridViewLayout.as (added)
+++ incubator/flex/sdk/branches/develop/frameworks/projects/spark/src/spark/components/gridClasses/GridViewLayout.as Fri Dec 21 18:05:20 2012
@@ -0,0 +1,3026 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+// 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;
+
+ for (var columnIndex:int = firstVisibleColumnIndex;
+ (width > 0) && (columnIndex >= 0) && (columnIndex < columnCount);
+ columnIndex = getNextVisibleColumnIndex(columnIndex))
+ {
+ var cellHeight:Number = gridDimensionsView.getTypicalCellHeight(columnIndex);
+ var cellWidth:Number = gridDimensionsView.getTypicalCellWidth(columnIndex);
+
+ var column:GridColumn = getGridColumn(columnIndex);
+ if (!isNaN(column.width))
+ {
+ cellWidth = column.width;
+ 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;
+
+ for (var columnIndex:int = 0; (columnIndex < columnCount); columnIndex++)
+ {
+ var cellHeight:Number = gridDimensionsView.getTypicalCellHeight(columnIndex);
+ var cellWidth:Number = gridDimensionsView.getTypicalCellWidth(columnIndex);
+
+ var column:GridColumn = 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);
+ }
+
+ 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();
[... 697 lines stripped ...]