blob: aa9336806c27ee4008a1be073b22cdc2c4a30783 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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 mx.controls.dataGridClasses
{
import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.ui.Keyboard;
import flash.utils.Dictionary;
import flash.utils.setInterval;
import mx.collections.CursorBookmark;
import mx.collections.IViewCursor;
import mx.collections.ItemResponder;
import mx.collections.errors.ItemPendingError;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.controls.listClasses.ListBase;
import mx.controls.listClasses.ListBaseContentHolder;
import mx.controls.listClasses.ListBaseSeekPending;
import mx.controls.listClasses.ListRowInfo;
import mx.core.EdgeMetrics;
import mx.core.IFactory;
import mx.core.IFlexDisplayObject;
import mx.core.IFlexModuleFactory;
import mx.core.IFontContextComponent;
import mx.core.IInvalidating;
import mx.core.ILayoutDirectionElement;
import mx.core.IUIComponent;
import mx.core.IUITextField;
import mx.core.SpriteAsset;
import mx.core.UIComponentGlobals;
import mx.core.mx_internal;
import mx.events.DragEvent;
import mx.events.ListEvent;
import mx.events.ScrollEvent;
import mx.events.ScrollEventDetail;
import mx.events.ScrollEventDirection;
import mx.events.TweenEvent;
import mx.skins.halo.ListDropIndicator;
use namespace mx_internal;
/**
* The DataGridBase class is the base class for controls
* that display lists of items in multiple columns.
* It is not used directly in applications.
*
* @mxml
*
* <p>The DataGridBase class inherits all the properties of its parent classes
* and adds the following properties:</p>
*
* <pre>
* &lt;mx:<i>tagname</i>
* headerHeight="depends on styles and header renderer"
* showHeaders="true|false"
* /&gt;
* </pre>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class DataGridBase extends ListBase implements IFontContextComponent
{
include "../../core/Version.as";
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function DataGridBase()
{
super();
listType = "vertical";
defaultRowCount = 7; // default number of rows is 7
columnMap = {};
freeItemRenderersTable = new Dictionary(false);
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* A map of item renderes to columns.
* Like <code>ListBase.rowMap</code>, this property contains
* a hash map of item renderers and the columns they belong to.
* Item renderers are indexed by their DisplayObject name.
*
* @see mx.controls.listClasses.ListBase#rowMap
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var columnMap:Object;
/**
* A per-column table of unused item renderers.
* Most list classes recycle item renderers that they have already created
* as they scroll off screen.
* The recycled renderers are stored here.
* The table is a Dictionary where the entries are Arrays indexed
* by the actual DataGridColumn (not the column's dataField or other
* properties), and each array is a stack of currently unused renderers.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var freeItemRenderersTable:Dictionary;
/**
* The set of visible columns.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
mx_internal var visibleColumns:Array;
/**
* The set of visible locked columns.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
mx_internal var visibleLockedColumns:Array;
/**
* The header sub-component.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var header:DataGridHeaderBase;
/**
* The class to use as the DGHeader.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
mx_internal var headerClass:Class = DataGridHeader;
/**
* The header mask.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var headerMask:Shape;
/**
* The header sub-component for locked columns.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var lockedColumnHeader:DataGridHeaderBase;
private var lockedColumnHeaderMask:Shape;
/**
* The sub-component that contains locked rows.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var lockedRowContent:DataGridLockedRowContentHolder;
private var lockedRowMask:Shape;
/**
* The sub-component that contains locked rows for locked columns.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var lockedColumnAndRowContent:DataGridLockedRowContentHolder;
private var lockedColumnAndRowMask:Shape;
/**
* The sub-component that contains locked columns.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var lockedColumnContent:ListBaseContentHolder;
private var lockedColumnMask:Shape;
/**
* @private
* automation
*/
mx_internal function get dataGridHeader():DataGridHeaderBase
{
return header;
}
/**
* @private
* automation
*/
mx_internal function get dataGridLockedColumnHeader():DataGridHeaderBase
{
return lockedColumnHeader;
}
/**
* @private
* automation
*/
mx_internal function get dataGridLockedColumnAndRows():ListBaseContentHolder
{
return lockedColumnAndRowContent;
}
/**
* @private
* automation
*/
mx_internal function get dataGridLockedRows():ListBaseContentHolder
{
return lockedRowContent;
}
/**
* @private
* automation
*/
mx_internal function get dataGridLockedColumns():ListBaseContentHolder
{
return lockedColumnContent;
}
/**
* Flag specifying that the set of visible columns and/or their sizes needs to
* be recomputed.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
mx_internal var columnsInvalid:Boolean = true;
/**
* Flag specifying that the font context of the renderers need to
* be recomputed.
*/
mx_internal var fontContextChanged:Boolean = true;
/**
* @private
* must be overridden by subclasses
*/
mx_internal function columnRendererChanged(c:DataGridColumn):void
{
}
/**
* @private
* must be overridden by subclasses
*/
mx_internal function resizeColumn(col:int, w:Number):void
{
}
// these three keep track of the key selection that caused
// the page fault
private var bShiftKey:Boolean = false;
private var bCtrlKey:Boolean = false;
private var lastKey:uint = 0;
private var bSelectItem:Boolean = false;
private var inSelectItem:Boolean = false;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// columns
//----------------------------------
/**
* @private
* must be overridden by subclasses
*/
public function get columns():Array
{
return null;
}
/**
* @private
*/
public function set columns(value:Array):void
{
}
//----------------------------------
// fontContext
//----------------------------------
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get fontContext():IFlexModuleFactory
{
return moduleFactory;
}
/**
* @private
*/
public function set fontContext(moduleFactory:IFlexModuleFactory):void
{
this.moduleFactory = moduleFactory;
}
//----------------------------------
// headerHeight
//----------------------------------
/**
* @private
* Storage for the headerHeight property.
*/
mx_internal var _headerHeight:Number = 22;
/**
* @private
*/
mx_internal var _explicitHeaderHeight:Boolean;
[Bindable("resize")]
[Inspectable(category="General", defaultValue="22")]
/**
* The height of the header cell of the column, in pixels.
* If set explicitly, that height will be used for all of
* the headers. If not set explicitly,
* the height will based on style settings and the header
* renderer.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get headerHeight():Number
{
return _headerHeight;
}
/**
* @private
*/
public function set headerHeight(value:Number):void
{
_headerHeight = value;
_explicitHeaderHeight = true;
itemsSizeChanged = true;
invalidateDisplayList();
}
//----------------------------------
// lockedColumnCount
//----------------------------------
private var lockedColumnCountChanged:Boolean = false;
/**
* @private
* Storage for the lockedColumnCount property.
*/
mx_internal var _lockedColumnCount:int = 0;
[Inspectable(defaultValue="0")]
/**
* The index of the first column in the control that scrolls.
* Columns with indexes that are lower than this value remain fixed
* in view. Not supported by all list classes.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get lockedColumnCount():int
{
return _lockedColumnCount;
}
/**
* @private
*/
public function set lockedColumnCount(value:int):void
{
_lockedColumnCount = value;
lockedColumnCountChanged = true;
itemsSizeChanged = true;
columnsInvalid = true;
invalidateDisplayList();
}
//----------------------------------
// lockedRowCount
//----------------------------------
private var lockedRowCountChanged:Boolean = false;
/**
* @private
* Storage for the lockedRowCount property.
*/
mx_internal var _lockedRowCount:int = 0;
[Inspectable(defaultValue="0")]
/**
* The index of the first row in the control that scrolls.
* Rows above this one remain fixed in view.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get lockedRowCount():int
{
return _lockedRowCount;
}
/**
* @private
*/
public function set lockedRowCount(value:int):void
{
_lockedRowCount = value;
lockedRowCountChanged = true;
itemsSizeChanged = true;
invalidateDisplayList();
}
//----------------------------------
// showHeaders
//----------------------------------
/**
* @private
* Storage for the showHeaders property.
*/
private var _showHeaders:Boolean = true;
[Bindable("showHeadersChanged")]
[Inspectable(category="General", defaultValue="true")]
/**
* A flag that indicates whether the control should show
* column headers.
* If <code>true</code>, the control shows column headers.
*
* @default true
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get showHeaders():Boolean
{
return _showHeaders;
}
/**
* @private
*/
public function set showHeaders(value:Boolean):void
{
if (value == _showHeaders)
return;
_showHeaders = value;
itemsSizeChanged = true;
invalidateDisplayList();
dispatchEvent(new Event("showHeadersChanged"));
}
/**
* @private
* headers are not renderered if showHeaders = false
* or headerheight = 0, so this test is whether row0 is
* a header or not.
*/
mx_internal function get headerVisible():Boolean
{
return showHeaders && (headerHeight > 0);
}
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* @private
* Sizes and positions the column headers, columns, and items based on the
* size of the DataGrid.
*/
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
if (header)
{
header.visibleColumns = visibleColumns;
header.headerItemsChanged = true;
header.invalidateSize();
header.validateNow();
}
if (lockedColumnCountChanged)
{
lockedColumnCountChanged = false;
if (lockedColumnCount > 0)
{
if (!lockedColumnContent)
{
lockedColumnHeader = new headerClass();
lockedColumnHeader.styleName = this;
addChild(lockedColumnHeader);
lockedColumnAndRowContent = new DataGridLockedRowContentHolder(this);
lockedColumnAndRowContent.styleName = this;
addChild(lockedColumnAndRowContent)
lockedColumnContent = new ListBaseContentHolder(this);
lockedColumnContent.styleName = this;
addChild(lockedColumnContent);
}
lockedColumnHeader.visible = true;
lockedColumnAndRowContent.visible = (lockedRowCount > 0);
lockedColumnContent.visible = true;
}
else
{
if (lockedColumnContent)
{
lockedColumnHeader.visible = false;
lockedColumnAndRowContent.visible = false;
lockedColumnContent.visible = false;
}
}
}
if (lockedRowCountChanged && iterator)
{
lockedRowCountChanged = false;
if (lockedRowCount > 0)
{
if (!lockedRowContent)
{
lockedRowContent = new DataGridLockedRowContentHolder(this);
lockedRowContent.styleName = this;
addChild(lockedRowContent);
}
lockedRowContent.visible = true;
}
else
{
if (lockedRowContent)
lockedRowContent.visible = false;
}
if (lockedColumnAndRowContent)
lockedColumnAndRowContent.visible = (lockedRowCount > 0 && lockedColumnCount > 0);
seekPositionSafely(lockedRowCount + verticalScrollPosition);
}
// ensure lockedColumnAndRowContent above other content
var ci:int;
var lcri:int;
if (lockedRowContent && lockedColumnAndRowContent)
{
lcri = getChildIndex(lockedColumnAndRowContent);
ci = getChildIndex(lockedRowContent);
if (lcri < ci)
setChildIndex(lockedRowContent, lcri);
}
if (lockedColumnContent && lockedColumnAndRowContent)
{
lcri = getChildIndex(lockedColumnAndRowContent);
ci = getChildIndex(lockedColumnContent);
if (lcri < ci)
setChildIndex(lockedColumnContent, lcri);
}
if (headerVisible && lockedColumnHeader)
{
lockedColumnHeader.visibleColumns = visibleLockedColumns;
lockedColumnHeader.headerItemsChanged = true;
lockedColumnHeader.invalidateSize();
lockedColumnHeader.validateNow();
}
super.updateDisplayList(unscaledWidth, unscaledHeight);
}
/**
* @private
*/
override protected function makeRowsAndColumns(left:Number, top:Number,
right:Number, bottom:Number,
firstCol:int, firstRow:int,
byCount:Boolean = false, rowsNeeded:uint = 0):Point
{
// trace(this, "makeRowsAndColumns " + left + " " + top + " " + right + " " + bottom + " " + firstCol + " " + firstRow);
return makeRows(listContent, left, top, right, bottom, firstCol, firstRow, byCount, rowsNeeded);
}
/**
* @private
*/
protected function makeRows(contentHolder:ListBaseContentHolder,
left:Number, top:Number,
right:Number, bottom:Number,
firstCol:int, firstRow:int,
byCount:Boolean = false, rowsNeeded:uint = 0,
alwaysCleanup:Boolean = false):Point
{
var xx:Number;
var yy:Number;
var hh:Number;
var bSelected:Boolean = false;
var bHighlight:Boolean = false;
var bCaret:Boolean = false;
var i:int;
var j:int;
var n:int;
var colNum:int = 0;
var rowNum:int = 0;
var rowsMade:int = 0;
var item:IListItemRenderer;
var listItems:Array = contentHolder.listItems;
var iterator:IViewCursor = contentHolder.iterator;
var visibleData:Object = contentHolder.visibleData;
var rowInfo:Object = contentHolder.rowInfo;
// bail if we have no columns
if ((!visibleColumns || visibleColumns.length == 0) && lockedColumnCount == 0)
{
while (listItems.length)
{
rowNum = listItems.length - 1;
while (listItems[rowNum].length)
{
// remove extra columns
item = listItems[rowNum].pop();
addToFreeItemRenderers(item);
}
listItems.pop();
}
contentHolder.visibleData = {};
return new Point(0,0);
}
invalidateSizeFlag = true;
var data:Object;
var uid:String;
var more:Boolean = true;
var valid:Boolean = true;
yy = top;
rowNum = firstRow;
more = (iterator != null && !iterator.afterLast && iteratorValid);
while ((!byCount && yy < bottom) || (byCount && rowsNeeded > 0))
{
if (byCount)
rowsNeeded--;
valid = more;
data = more ? iterator.current : null;
if (iterator && more)
{
try
{
more = iterator.moveNext();
}
catch(e:ItemPendingError)
{
//AJH commented out to fix one bug. We'll see if it causes others
//lastSeekPending = new ListBaseSeekPending(CursorBookmark.CURRENT, 0)
//e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
// lastSeekPending));
more = false;
iteratorValid = false;
}
}
hh = 0;
uid = null;
prepareRowArray(contentHolder, rowNum);
if (valid)
{
uid = itemToUID(data);
hh = makeRow(contentHolder, rowNum, left, right, yy, data, uid);
}
else
{
hh = rowNum > 1 ? rowInfo[rowNum - 1].height : rowHeight;
}
if (valid && variableRowHeight)
{
hh = Math.ceil(calculateRowHeight(data, hh, true));
}
if (valid)
adjustRow(contentHolder, rowNum, yy, hh);
else
clearRow(contentHolder, rowNum);
bSelected = isItemSelectable(data) && isItemSelected(uid);
bHighlight = highlightUID == uid;
bCaret = caretUID == uid;
setRowInfo(contentHolder, rowNum, yy, hh, uid);
// trace("rowNum = " + rowNum);
if (valid)
drawVisibleItem(uid, bSelected, bHighlight, bCaret);
if (hh == 0) // hh can be zero if we had zero width
hh = rowHeight;
yy += hh;
rowNum++;
rowsMade++;
}
// byCount means we're making rows and wont get all the way to the bottom
// so we skip this cleanup pass
if (!byCount || alwaysCleanup)
{
// trace("MakeRowsAndColumns rowNum = " + rowNum);
// delete extra rows
while (rowNum < listItems.length)
{
removeExtraRow(contentHolder);
}
}
// trace("MakeRowsAndColumns listItems.length = " + listItems.length);
invalidateSizeFlag = false;
return new Point(colNum, rowsMade);
}
/**
* Ensures that there is a slot in the row arrays for the given row number.
*
* @param contentHolder The set of rows (locked rows, regular rows).
*
* @param rowNum The row number.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function prepareRowArray(contentHolder:ListBaseContentHolder, rowNum:int):void
{
var listItems:Array = contentHolder.listItems;
var columnContent:ListBaseContentHolder;
if (lockedColumnCount > 0)
{
if (contentHolder == lockedRowContent)
columnContent = lockedColumnAndRowContent;
else
columnContent = lockedColumnContent;
}
else
columnContent = null;
if (!listItems[rowNum])
listItems[rowNum] = [];
if (columnContent)
if (!columnContent.listItems[rowNum])
columnContent.listItems[rowNum] = [];
}
/**
* Creates the renderers for the given rowNum, dataObject and uid.
*
* @param contentHolder The set of rows (locked rows, regular rows).
* @param rowNum The row number.
* @param left The offset from the left side for the first column.
* @param right The offset from the right side for the last column.
* @param yy The y position of the row.
* @param data The data for the row.
* @param uid The uid for the data.
*
* @return Height of the row.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function makeRow(contentHolder:ListBaseContentHolder, rowNum:int, left:Number, right:Number, yy:Number, data:Object, uid:String):Number
{
var listItems:Array = contentHolder.listItems;
var columnContent:ListBaseContentHolder;
var item:IListItemRenderer;
var extraItem:IListItemRenderer;
var c:DataGridColumn;
var itemSize:Point;
if (lockedColumnCount > 0)
{
if (contentHolder == lockedRowContent)
columnContent = lockedColumnAndRowContent;
else
columnContent = lockedColumnContent;
}
else
columnContent = null;
var colNum:int = 0;
var xx:Number = left;
var hh:Number = 0;
while (colNum < visibleLockedColumns.length)
{
c = visibleLockedColumns[colNum];
item = setupColumnItemRenderer(c, columnContent, rowNum, colNum, data, uid);
itemSize = layoutColumnItemRenderer(c, item, xx, yy);
xx += itemSize.x;
colNum++;
hh = Math.ceil(Math.max(hh, variableRowHeight ? itemSize.y + cachedPaddingTop + cachedPaddingBottom : rowHeight));
}
if (visibleLockedColumns.length)
{
while (columnContent.listItems[rowNum].length > colNum)
{
// remove extra columns
extraItem = columnContent.listItems[rowNum].pop();
addToFreeItemRenderers(extraItem);
}
}
colNum = 0;
xx = left;
while (xx < right && colNum < visibleColumns.length)
{
c = visibleColumns[colNum];
item = setupColumnItemRenderer(c, contentHolder, rowNum, colNum, data, uid);
itemSize = layoutColumnItemRenderer(c, item, xx, yy);
xx += itemSize.x;
colNum++;
hh = Math.ceil(Math.max(hh, variableRowHeight ? itemSize.y + cachedPaddingTop + cachedPaddingBottom : rowHeight));
}
while (listItems[rowNum].length > colNum)
{
// remove extra columns
extraItem = listItems[rowNum].pop();
addToFreeItemRenderers(extraItem);
}
return hh;
}
/**
* Removes renderers from a row that should be empty for the given rowNum.
*
* @param contentHolder The set of rows (locked rows, regular rows).
* @param rowNum The row number.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function clearRow(contentHolder:ListBaseContentHolder, rowNum:int):void
{
var listItems:Array = contentHolder.listItems;
var columnContent:ListBaseContentHolder;
var extraItem:IListItemRenderer;
if (lockedColumnCount > 0)
{
if (contentHolder == lockedRowContent)
columnContent = lockedColumnAndRowContent;
else
columnContent = lockedColumnContent;
}
else
columnContent = null;
while (listItems[rowNum].length)
{
// remove extra columns
extraItem = listItems[rowNum].pop();
addToFreeItemRenderers(extraItem);
}
if (columnContent)
{
while (columnContent.listItems[rowNum].length)
{
// remove extra columns
extraItem = columnContent.listItems[rowNum].pop();
addToFreeItemRenderers(extraItem);
}
}
}
/**
* Adjusts the size and positions of the renderers for the given rowNum, row position and height.
*
* @param contentHolder The set of rows (locked rows, regular rows).
* @param rowNum The row number.
* @param yy The y position of the row.
* @param hh The height of the row.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function adjustRow(contentHolder:ListBaseContentHolder, rowNum:int, yy:Number, hh:Number):void
{
var listItems:Array = contentHolder.listItems;
var columnContent:ListBaseContentHolder;
var i:int;
var j:int;
var n:int;
var colNum:int;
var item:IListItemRenderer;
if (lockedColumnCount > 0)
{
if (contentHolder == lockedRowContent)
columnContent = lockedColumnAndRowContent;
else
columnContent = lockedColumnContent;
}
else
columnContent = null;
if (listItems[rowNum])
{
if (columnContent)
{
n = columnContent.listItems[rowNum].length;
for (j = 0; j < n; j++)
columnContent.listItems[rowNum][j].setActualSize(
columnContent.listItems[rowNum][j].width,
hh - cachedPaddingTop - cachedPaddingBottom);
}
n = listItems[rowNum].length;
for (j = 0; j < n; j++)
listItems[rowNum][j].setActualSize(
listItems[rowNum][j].width,
hh - cachedPaddingTop - cachedPaddingBottom);
}
if (cachedVerticalAlign != "top")
{
if (cachedVerticalAlign == "bottom")
{
colNum = listItems[rowNum].length;
for (j = 0; j < colNum; j++)
{
item = listItems[rowNum][j];
item.move(item.x, yy + hh - cachedPaddingBottom - item.getExplicitOrMeasuredHeight());
}
if (columnContent)
{
n = columnContent.listItems[rowNum].length
for (j = 0; j < n; j++)
{
item = columnContent.listItems[rowNum][j];
item.move(item.x, yy + hh - cachedPaddingBottom - item.getExplicitOrMeasuredHeight());
}
}
}
else
{
colNum = listItems[rowNum].length;
for (j = 0; j < colNum; j++)
{
item = listItems[rowNum][j];
item.move(item.x, yy + cachedPaddingTop + (hh - cachedPaddingBottom - cachedPaddingTop - item.getExplicitOrMeasuredHeight()) / 2);
}
if (columnContent)
{
n = columnContent.listItems[rowNum].length
for (j = 0; j < n; j++)
{
item = columnContent.listItems[rowNum][j];
item.move(item.x, yy + cachedPaddingTop + (hh - cachedPaddingBottom - cachedPaddingTop - item.getExplicitOrMeasuredHeight()) / 2);
}
}
}
}
}
/**
* Sets the rowInfo for the given rowNum, row position and height.
*
* @param contentHolder The set of rows (locked rows, regular rows).
* @param rowNum The row number.
* @param yy The y position of the row.
* @param hh The height of the row.
* @param uid The UID for the data.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function setRowInfo(contentHolder:ListBaseContentHolder, rowNum:int, yy:Number, hh:Number, uid:String):void
{
var listItems:Array = contentHolder.listItems;
var rowInfo:Object = contentHolder.rowInfo;
var columnContent:ListBaseContentHolder;
if (lockedColumnCount > 0)
{
if (contentHolder == lockedRowContent)
columnContent = lockedColumnAndRowContent;
else
columnContent = lockedColumnContent;
}
else
columnContent = null;
rowInfo[rowNum] = new ListRowInfo(yy, hh, uid);
if (columnContent)
columnContent.rowInfo[rowNum] = rowInfo[rowNum];
}
/**
* Removes extra row from the end of the contentHolder.
*
* @param contentHolder The set of rows (locked rows, regular rows).
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function removeExtraRow(contentHolder:ListBaseContentHolder):void
{
var item:IListItemRenderer;
var listItems:Array = contentHolder.listItems;
var rowInfo:Object = contentHolder.rowInfo;
var columnContent:ListBaseContentHolder;
if (contentHolder == lockedRowContent)
columnContent = lockedColumnAndRowContent;
else
columnContent = lockedColumnContent;
var rr:Array = listItems.pop();
rowInfo.pop();
while (rr.length)
{
item = rr.pop();
addToFreeItemRenderers(item);
}
if (columnContent)
{
columnContent.rowInfo.pop();
rr = columnContent.listItems.pop();
while (rr && rr.length)
{
item = rr.pop();
addToFreeItemRenderers(item);
}
}
}
/**
* Sets up an item renderer for a column and put it in the listItems array
* at the requested position.
*
* @param c The DataGridColumn for the renderer.
* @param contentHolder The set of rows (locked rows, regular rows).
* @param rowNum The row number.
* @param colNum The column number.
* @param data The data for the row.
* @param uid The uid for the data.
*
* @return The renderer for this column and row.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function setupColumnItemRenderer(c:DataGridColumn, contentHolder:ListBaseContentHolder,
rowNum:int, colNum:int, data:Object, uid:String):IListItemRenderer
{
var listItems:Array = contentHolder.listItems;
var item:IListItemRenderer;
var rowData:DataGridListData;
item = listItems[rowNum][colNum];
if (!item || itemToUID(item.data) != uid
|| columnMap[item.name] != c)
{
item = createColumnItemRenderer(c, false, data);
if (item == null)
return null;
if (item.parent != contentHolder)
contentHolder.addChild(DisplayObject(item));
// a space is used if no data so text widgets get some default size
columnMap[item.name] = c;
if (listItems[rowNum][colNum])
addToFreeItemRenderers(listItems[rowNum][colNum]);
listItems[rowNum][colNum] = item;
}
//[Matt] moved from inside the block above to outside because
//the item definitely exists at this point, and always needs
//its data refreshed
rowData = DataGridListData(makeListData(data, uid, rowNum, c.colNum, c));
rowMap[item.name] = rowData;
if (item is IDropInListItemRenderer)
IDropInListItemRenderer(item).listData = data ? rowData : null;
item.data = data;
item.visible = true;
if (uid && colNum == 0)
contentHolder.visibleData[uid] = item;
return item;
}
/**
* Sizes and temporarily positions an itemRenderer for a column, returning its size as a Point.
* The final position might be adjusted later due to alignment settings.
*
* @param c The DataGridColumn for the renderer.
* @param item The renderer.
* @param xx The x position.
* @param yy The y position.
*
* @return Size of the renderer as a Point.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function layoutColumnItemRenderer(c:DataGridColumn, item:IListItemRenderer, xx:Number, yy:Number):Point
{
var ww:Number = 0;
var rh:Number = 0;
if (item)
{
item.explicitWidth = ww = getWidthOfItem(item, c);
UIComponentGlobals.layoutManager.validateClient(item, true);
rh = item.getExplicitOrMeasuredHeight();
item.setActualSize(ww, variableRowHeight
? item.getExplicitOrMeasuredHeight()
: rowHeight - cachedPaddingTop - cachedPaddingBottom);
item.move(xx, yy + cachedPaddingTop);
}
return new Point(ww, rh);
}
/**
* Draws an item if it is visible.
*
* @param uid The uid used to find the renderer.
* @param selected <code>true</code> if the renderer should be drawn in
* its selected state.
* @param highlighted <code>true</code> if the renderer should be drawn in
* its highlighted state.
* @param caret <code>true</code> if the renderer should be drawn as if
* it is the selection caret.
* @param transition <code>true</code> if the selection state should fade in
* via an effect.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function drawVisibleItem(uid:String,
selected:Boolean = false,
highlighted:Boolean = false,
caret:Boolean = false,
transition:Boolean = false):void
{
if (visibleData[uid])
drawItem(visibleData[uid], selected, highlighted, caret, transition);
if (lockedRowCount && lockedRowContent && lockedRowContent.visibleData[uid])
drawItem(lockedRowContent.visibleData[uid], selected, highlighted, caret, transition);
}
/**
* @private
*/
override protected function drawItem(item:IListItemRenderer,
selected:Boolean = false,
highlighted:Boolean = false,
caret:Boolean = false,
transition:Boolean = false):void
{
if (!item)
return;
super.drawItem(item, selected, highlighted, caret, transition);
var rowIndex:int = rowMap[item.name].rowIndex;
var i:int;
var r:IListItemRenderer;
for (i = 0; i < visibleColumns.length; i++)
{
r = (item.parent as ListBaseContentHolder).listItems[rowIndex][i];
updateRendererDisplayList(r);
}
if (lockedColumnCount)
{
var columnContents:ListBaseContentHolder;
if ((item.parent == listContent) || (item.parent == lockedColumnContent))
columnContents = lockedColumnContent;
else
columnContents = lockedColumnAndRowContent;
for (i = 0; i < visibleLockedColumns.length; i++)
{
r = columnContents.listItems[rowIndex][i];
updateRendererDisplayList(r);
}
}
}
/**
* Redraws the renderer synchronously.
*
* @param r The renderer;
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function updateRendererDisplayList(r:IListItemRenderer):void
{
if (r is IInvalidating)
{
var ui:IInvalidating = IInvalidating(r);
ui.invalidateDisplayList();
ui.validateNow();
}
}
/**
* @private
*/
override protected function addToFreeItemRenderers(item:IListItemRenderer):void
{
// trace("putting " + item + " on free list");
DisplayObject(item).visible = false;
delete rowMap[item.name];
var factory:IFactory = factoryMap[item];
// Only delete from visibleData if we are freeing the renderer for
// the first visible column
var UID:String = itemToUID(item.data);
var visibleData:Object = ListBaseContentHolder(item.parent).visibleData;
if (visibleData[UID] == item)
delete visibleData[UID];
if (columnMap[item.name])
{
var c:DataGridColumn = columnMap[item.name];
if (factory == c.itemRenderer)
{
if (freeItemRenderersTable[c] == undefined)
freeItemRenderersTable[c] = [];
freeItemRenderersTable[c].push(item);
}
if (!c.freeItemRenderersByFactory)
c.freeItemRenderersByFactory = new Dictionary(true);
if (c.freeItemRenderersByFactory[factory] == undefined)
c.freeItemRenderersByFactory[factory] = new Dictionary(true);
c.freeItemRenderersByFactory[factory][item] = 1;
delete columnMap[item.name];
}
}
/**
* @private
*/
override protected function purgeItemRenderers():void
{
var listItems:Array;
var row:Array;
var item:IListItemRenderer;
if (lockedRowContent)
{
listItems = lockedRowContent.listItems;
while (listItems.length)
{
row = listItems.pop();
while (row.length)
{
item = IListItemRenderer(row.pop());
if (item)
{
lockedRowContent.removeChild(DisplayObject(item));
delete lockedRowContent.visibleData[itemToUID(item.data)];
}
}
}
}
if (lockedColumnContent)
{
listItems = lockedColumnContent.listItems;
while (listItems.length)
{
row = listItems.pop();
while (row.length)
{
item = IListItemRenderer(row.pop());
if (item)
{
lockedColumnContent.removeChild(DisplayObject(item));
delete lockedColumnContent.visibleData[itemToUID(item.data)];
}
}
}
}
if (lockedColumnAndRowContent)
{
listItems = lockedColumnAndRowContent.listItems;
while (listItems.length)
{
row = listItems.pop();
while (row.length)
{
item = IListItemRenderer(row.pop());
if (item)
{
lockedColumnAndRowContent.removeChild(DisplayObject(item));
delete lockedColumnAndRowContent.visibleData[itemToUID(item.data)];
}
}
}
}
super.purgeItemRenderers();
}
/**
* @private
*/
override public function indicesToIndex(rowIndex:int, colIndex:int):int
{
if (inSelectItem)
return 0;
return rowIndex;
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Creates a new DataGridListData instance and populates the fields based on
* the input data provider item.
*
* @param data The data provider item used to populate the ListData.
* @param uid The UID for the item.
* @param rowNum The index of the item in the data provider.
* @param columnNum The columnIndex associated with this item.
* @param column The column associated with this item.
*
* @return A newly constructed ListData object.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function makeListData(data:Object, uid:String,
rowNum:int, columnNum:int, column:DataGridColumn):BaseListData
{
if (data is DataGridColumn)
{
return new DataGridListData((column.headerText != null) ? column.headerText : column.dataField,
column.dataField, columnNum, uid, this, rowNum);
}
else
{
return new DataGridListData(column.itemToLabel(data), column.dataField,
columnNum, uid, this, rowNum);
}
}
/**
* @private
* This grid just returns the column size,
* but could handle column spanning.
*/
mx_internal function getWidthOfItem(item:IListItemRenderer,
col:DataGridColumn):Number
{
return col.width;
}
/**
* Calculates the row height of columns in a row.
* If <code>skipVisible</code> is <code>true></code>,
* the DataGridBase already knows the height of
* the renderers for the column that do fit in the display area
* so this method only needs to calculate for the item renderers
* that would exist if other columns in that row were in the
* display area. This is needed so that if the user scrolls
* horizontally, the height of the row does not adjust as different
* columns appear and disappear.
*
* @param data The data provider item for the row.
*
* @param hh The current height of the row.
*
* @param skipVisible If <code>true</code>, there is no need to measure item
* renderers in visible columns.
*
* @return The row height, in pixels.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function calculateRowHeight(data:Object, hh:Number, skipVisible:Boolean = false):Number
{
return NaN;
}
/**
* Returns the item renderer for a column cell or for a column header.
* This method returns the default item renderer if no custom render is assigned
* to the column.
*
* <p>This method is public so that is can be accessed by the DataGridHeader class,
* and is primarily used in subclasses of the DataGrid control.</p>
*
* @param c The DataGridColumn instance of the item renderer.
*
* @param forHeader <code>true</code> to return the header item renderer,
* and <code>false</code> to return the item render for the column cells.
*
* @param data If <code>forHeader</code> is <code>false</code>,
* the <code>data</code> Object for the item renderer.
* If <code>forHeader</code> is <code>true</code>,
* the DataGridColumn instance.
*
* @return The item renderer.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function createColumnItemRenderer(c:DataGridColumn, forHeader:Boolean, data:Object):IListItemRenderer
{
var factory:IFactory;
// get the factory for the data
factory = c.getItemRendererFactory(forHeader, data);
if (!factory)
{
if (data == null)
factory = nullItemRenderer;
if (!factory)
factory = itemRenderer;
}
var renderer:IListItemRenderer;
// if it is the default column factory, see if
// the freeItemRenderersTable has a free one
if (factory == c.itemRenderer)
{
if (freeItemRenderersTable[c] && freeItemRenderersTable[c].length)
{
renderer = freeItemRenderersTable[c].pop();
delete c.freeItemRenderersByFactory[factory][renderer];
}
}
else if (c.freeItemRenderersByFactory)
{
// other re-usable renderers are in the FactoryMap
var d:Dictionary = c.freeItemRenderersByFactory[factory];
if (d)
{
for (var p:* in d)
{
renderer = IListItemRenderer(p);
delete d[p];
break;
}
}
}
if (!renderer )
{
renderer = factory.newInstance();
if (renderer)
{
renderer.styleName = c;
factoryMap[renderer] = factory;
}
}
if (renderer)
renderer.owner = this;
return renderer;
}
/**
* Gets the headerWordWrap for a column, using the default wordWrap if none is specified.
*
* @param c The column to get the headerWordWrap for.
*
* @return <code>true</code> if the value of the column's <code>headerWordWrap</code> property is <code>true</code>, or
* <code>false</code> if the value is <code>false</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
mx_internal function columnHeaderWordWrap(c:DataGridColumn):Boolean
{
if (c.headerWordWrap == true)
return true;
if (c.headerWordWrap == false)
return false;
return wordWrap;
}
/**
* Gets the wordWrap for a column, using the default wordWrap if none is specified.
*
* @param c The column to get the wordWrap for.
*
* @return <code>true</code> if the value of the column's <code>wordWrap</code> property is <code>true</code>, or
* <code>false</code> if the value is <code>false</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
mx_internal function columnWordWrap(c:DataGridColumn):Boolean
{
if (c.wordWrap == true)
return true;
if (c.wordWrap == false)
return false;
return wordWrap;
}
/**
* @private
*/
override protected function clearIndicators():void
{
super.clearIndicators();
if (lockedColumnCount && lockedColumnContent)
{
while (lockedColumnContent.selectionLayer.numChildren > 0)
{
lockedColumnContent.selectionLayer.removeChildAt(0);
}
}
if (lockedRowCount && lockedRowContent)
{
while (lockedRowContent.selectionLayer.numChildren > 0)
{
lockedRowContent.selectionLayer.removeChildAt(0);
}
if (lockedColumnCount && lockedColumnAndRowContent)
{
while (lockedColumnAndRowContent.selectionLayer.numChildren > 0)
{
lockedColumnAndRowContent.selectionLayer.removeChildAt(0);
}
}
}
if (header)
header.clearSelectionLayer();
if (lockedColumnHeader)
lockedColumnHeader.clearSelectionLayer();
}
/**
* The DisplayObject that contains the graphics that indicates
* which renderer is highlighted for lockedColumns.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var columnHighlightIndicator:Sprite;
/**
* The DisplayObject that contains the graphics that indicates
* which renderer is the caret for lockedColumns.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var columnCaretIndicator:Sprite;
/**
* @private
*/
override protected function drawHighlightIndicator(indicator:Sprite, x:Number, y:Number, width:Number, height:Number, color:uint, itemRenderer:IListItemRenderer):void
{
super.drawHighlightIndicator(indicator, x, y, unscaledWidth - viewMetrics.left - viewMetrics.right, height, color, itemRenderer);
if (lockedColumnCount)
{
var columnContents:ListBaseContentHolder;
if (itemRenderer.parent == listContent)
columnContents = lockedColumnContent;
else
columnContents = lockedColumnAndRowContent;
var selectionLayer:Sprite = columnContents.selectionLayer;
if (!columnHighlightIndicator)
{
columnHighlightIndicator = new SpriteAsset();
columnContents.selectionLayer.addChild(DisplayObject(columnHighlightIndicator));
}
else
{
if (columnHighlightIndicator.parent != selectionLayer)
selectionLayer.addChild(columnHighlightIndicator);
else
selectionLayer.setChildIndex(DisplayObject(columnHighlightIndicator),
selectionLayer.numChildren - 1);
}
// Let the columnHighlightIndicator inherit the layoutDirection
if (columnHighlightIndicator is ILayoutDirectionElement)
ILayoutDirectionElement(columnHighlightIndicator).layoutDirection = null;
super.drawHighlightIndicator(columnHighlightIndicator, x, y, columnContents.width, height, color, itemRenderer);
}
}
/**
* @private
*/
override protected function clearHighlightIndicator(indicator:Sprite, itemRenderer:IListItemRenderer):void
{
super.clearHighlightIndicator(indicator, itemRenderer);
if (lockedColumnCount)
{
if (columnHighlightIndicator)
Sprite(columnHighlightIndicator).graphics.clear();
}
}
/**
* @private
*/
override protected function drawCaretIndicator(indicator:Sprite, x:Number, y:Number, width:Number, height:Number, color:uint, itemRenderer:IListItemRenderer):void
{
super.drawCaretIndicator(indicator, x, y, unscaledWidth - viewMetrics.left - viewMetrics.right, height, color, itemRenderer);
if (lockedColumnCount)
{
var columnContents:ListBaseContentHolder;
if (itemRenderer.parent == listContent)
columnContents = lockedColumnContent;
else
columnContents = lockedColumnAndRowContent;
var selectionLayer:Sprite = columnContents.selectionLayer;
if (!columnCaretIndicator)
{
columnCaretIndicator = new SpriteAsset();
columnContents.selectionLayer.addChild(DisplayObject(columnCaretIndicator));
}
else
{
if (columnCaretIndicator.parent != selectionLayer)
selectionLayer.addChild(columnCaretIndicator);
else
selectionLayer.setChildIndex(DisplayObject(columnCaretIndicator),
selectionLayer.numChildren - 1);
}
// Let the columnCaretIndicator inherit the layoutDirection
if (columnCaretIndicator is ILayoutDirectionElement)
ILayoutDirectionElement(columnCaretIndicator).layoutDirection = null;
super.drawCaretIndicator(columnCaretIndicator, x, y, columnContents.width, height, color, itemRenderer);
}
}
/**
* @private
*/
override protected function clearCaretIndicator(indicator:Sprite, itemRenderer:IListItemRenderer):void
{
super.clearCaretIndicator(indicator, itemRenderer);
if (lockedColumnCount)
{
if (columnCaretIndicator)
Sprite(columnCaretIndicator).graphics.clear();
}
}
private var indicatorDictionary:Dictionary = new Dictionary(true);
/**
* @private
*/
override protected function drawSelectionIndicator(indicator:Sprite, x:Number, y:Number, width:Number, height:Number, color:uint, itemRenderer:IListItemRenderer):void
{
super.drawSelectionIndicator(indicator, x, y, unscaledWidth - viewMetrics.left - viewMetrics.right, height, color, itemRenderer);
if (lockedColumnCount)
{
var columnContents:ListBaseContentHolder;
if (itemRenderer.parent == listContent)
columnContents = lockedColumnContent;
else
columnContents = lockedColumnAndRowContent;
var selectionLayer:Sprite = columnContents.selectionLayer;
var columnIndicator:Sprite = indicatorDictionary[indicator] as Sprite;
if (!columnIndicator)
{
columnIndicator = new SpriteAsset();
columnIndicator.mouseEnabled = false;
selectionLayer.addChild(DisplayObject(columnIndicator));
indicator.parent.addEventListener(Event.REMOVED, selectionRemovedListener);
indicatorDictionary[indicator] = columnIndicator;
}
// Let the columnIndicator inherit the layoutDirection
if (columnIndicator is ILayoutDirectionElement)
ILayoutDirectionElement(columnIndicator).layoutDirection = null;
super.drawSelectionIndicator(columnIndicator, x, y, columnContents.width, height, color, itemRenderer);
}
}
private function selectionRemovedListener(event:Event):void
{
if (!lockedColumnCount)
return;
var columnIndicator:Sprite = indicatorDictionary[event.target] as Sprite;
if (columnIndicator)
{
columnIndicator.parent.removeChild(columnIndicator);
}
}
/**
* @private
*/
override mx_internal function mouseEventToItemRendererOrEditor(event:MouseEvent):IListItemRenderer
{
var isHighlight:Boolean = false;
var target:DisplayObject = DisplayObject(event.target);
var currentContent:ListBaseContentHolder;
var visibleColumns:Array = this.visibleColumns;
if (event.target == listContent)
currentContent = listContent;
else if (event.target == lockedColumnContent)
{
currentContent = lockedColumnContent;
visibleColumns = visibleLockedColumns;
}
else if (event.target == lockedRowContent)
currentContent = lockedRowContent;
else if (event.target == lockedColumnAndRowContent)
{
currentContent = lockedColumnAndRowContent;
visibleColumns = visibleLockedColumns;
}
else if (event.target == highlightIndicator)
{
currentContent = highlightIndicator.parent.parent as ListBaseContentHolder;
visibleColumns = this.visibleColumns;
if (currentContent == lockedColumnContent || currentContent == lockedColumnAndRowContent)
visibleColumns = visibleLockedColumns;
isHighlight = true;
}
else if (event.target == columnHighlightIndicator)
{
currentContent = columnHighlightIndicator.parent.parent as ListBaseContentHolder;
visibleColumns = this.visibleColumns;
if (currentContent == lockedColumnContent || currentContent == lockedColumnAndRowContent)
visibleColumns = visibleLockedColumns;
isHighlight = true;
}
if (isHighlight || target == currentContent)
{
var listItems:Array = currentContent.listItems;
var rowInfo:Array = currentContent.rowInfo;
var pt:Point = new Point(event.stageX, event.stageY);
pt = currentContent.globalToLocal(pt);
var yy:Number = 0;
var n:int = listItems.length;
for (var i:int = 0; i < n; i++)
{
if (listItems[i].length)
{
if (pt.y < yy + rowInfo[i].height)
{
var xx:Number = 0;
var m:int = visibleColumns.length;
for (var j:int = 0; j < m; j++)
{
if (pt.x < xx + visibleColumns[j].width)
return listItems[i][j];
xx += visibleColumns[j].width;
}
}
}
yy += rowInfo[i].height;
}
}
while (target && target != this)
{
if (target is IListItemRenderer && target.parent &&
target.parent.parent == this && target.parent is ListBaseContentHolder)
{
if (target.visible)
return IListItemRenderer(target);
break;
}
if (target is IUIComponent)
target = IUIComponent(target).owner;
else
target = target.parent;
}
return null;
}
/**
* Determines the column under the mouse for dropping a column, if any.
* This method only checks horizontal position, and assumes the y value is within headers.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
mx_internal function getAllVisibleColumns():Array
{
var temp:Array = [];
if (lockedColumnCount)
temp = temp.concat(visibleLockedColumns);
temp = temp.concat(visibleColumns);
return temp;
}
/**
* @private
*/
override protected function UIDToItemRenderer(uid:String):IListItemRenderer
{
var r:IListItemRenderer = visibleData[uid];
if (!r)
{
if (lockedRowContent)
r = lockedRowContent.visibleData[uid];
}
if (!r)
{
if (lockedColumnContent)
r = lockedColumnContent.visibleData[uid];
}
if (!r)
{
if (lockedColumnAndRowContent)
r = lockedColumnAndRowContent.visibleData[uid];
}
return r;
}
/**
* @private
* By default, there's a single large clip mask applied to the entire
* listContent area of the List. When the List contains a mixture of
* device text and vector graphics (e.g.: there are custom item renderers),
* that clip mask imposes a rendering overhead.
*
* When graphical (non-text) item renderers are used, we optimize by only
* applying a clip mask to the list items in the last row ... and then
* only when it's needed.
*
* This optimization breaks down when there's a horizontal scrollbar.
* Rather than attempting to apply clip masks to every item along the left
* and right edges, we give up and use the default clip mask that covers
* the entire List.
*
* For Lists and DataGrids containing custom item renderers, this
* optimization yields a 25% improvement in scrolling speed.
*/
override mx_internal function addClipMask(layoutChanged:Boolean):void
{
var g:Graphics;
// If something about the List has changed, check to see if we need
// to clip items in the last row.
if (layoutChanged)
{
if ((horizontalScrollBar && horizontalScrollBar.visible) || hasOnlyTextRenderers() ||
listContent.bottomOffset != 0 ||
listContent.topOffset != 0 ||
listContent.leftOffset != 0 ||
listContent.rightOffset != 0)
{
// As described above, we just use the default clip mask
// when there's a horizontal scrollbar or the item renders
// are all UITextFields.
listContent.mask = maskShape;
selectionLayer.mask = null;
if (!headerMask)
{
headerMask = new Shape();
addChild(headerMask);
g = headerMask.graphics;
g.beginFill(0xFFFFFF);
g.drawRect(0, 0, 10, 10);
g.endFill();
headerMask.visible = false;
}
header.mask = headerMask;
header.selectionLayer.mask = null;
headerMask.width = maskShape.width;
headerMask.height = maskShape.height;
headerMask.x = maskShape.x;
headerMask.y = maskShape.y;
// If we have a vScroll only, we want the scrollbar to be below
// the header.
if (verticalScrollBar != null && verticalScrollBar.visible &&
(horizontalScrollBar == null || !horizontalScrollBar.visible) &&
headerVisible)
{
headerMask.width += verticalScrollBar.getExplicitOrMeasuredWidth();
}
if (lockedRowContent)
{
if (!lockedRowMask)
{
lockedRowMask = new Shape();
addChild(lockedRowMask);
g = lockedRowMask.graphics;
g.beginFill(0xFFFFFF);
g.drawRect(0, 0, 10, 10);
g.endFill();
lockedRowMask.visible = false;
}
lockedRowContent.mask = lockedRowMask;
lockedRowContent.selectionLayer.mask = null;
lockedRowMask.width = maskShape.width;
lockedRowMask.height = maskShape.height;
lockedRowMask.x = maskShape.x;
lockedRowMask.y = maskShape.y;
}
if (lockedColumnContent)
{
if (!lockedColumnMask)
{
lockedColumnMask = new Shape();
addChild(lockedColumnMask);
g = lockedColumnMask.graphics;
g.beginFill(0xFFFFFF);
g.drawRect(0, 0, 10, 10);
g.endFill();
lockedColumnMask.visible = false;
}
lockedColumnContent.mask = lockedColumnMask;
lockedColumnContent.selectionLayer.mask = null;
lockedColumnMask.width = maskShape.width;
lockedColumnMask.height = maskShape.height;
lockedColumnMask.x = maskShape.x;
lockedColumnMask.y = maskShape.y;
}
if (lockedColumnAndRowContent)
{
if (!lockedColumnAndRowMask)
{
lockedColumnAndRowMask = new Shape();
addChild(lockedColumnAndRowMask);
g = lockedColumnAndRowMask.graphics;
g.beginFill(0xFFFFFF);
g.drawRect(0, 0, 10, 10);
g.endFill();
lockedColumnAndRowMask.visible = false;
}
lockedColumnAndRowContent.mask = lockedColumnAndRowMask;
lockedColumnAndRowContent.selectionLayer.mask = null;
lockedColumnAndRowMask.width = maskShape.width;
lockedColumnAndRowMask.height = maskShape.height;
lockedColumnAndRowMask.x = maskShape.x;
lockedColumnAndRowMask.y = maskShape.y;
}
if (lockedColumnHeader)
{
if (!lockedColumnHeaderMask)
{
lockedColumnHeaderMask = new Shape();
addChild(lockedColumnHeaderMask);
g = lockedColumnHeaderMask.graphics;
g.beginFill(0xFFFFFF);
g.drawRect(0, 0, 10, 10);
g.endFill();
lockedColumnHeaderMask.visible = false;
}
lockedColumnHeader.mask = lockedColumnHeaderMask;
lockedColumnHeader.selectionLayer.mask = null;
lockedColumnHeaderMask.width = maskShape.width;
lockedColumnHeaderMask.height = maskShape.height;
lockedColumnHeaderMask.x = maskShape.x;
lockedColumnHeaderMask.y = maskShape.y;
}
}
else
{
// When we're not applying the default clip mask to the whole
// listContent, we still want to apply it to the selectionLayer
// (so that the selection rectangle and the mouseOver rectangle
// are properly clipped)
listContent.mask = null;
selectionLayer.mask = maskShape;
if (!headerMask)
{
headerMask = new Shape();
addChild(headerMask);
g = headerMask.graphics;
g.beginFill(0xFFFFFF);
g.drawRect(0, 0, 10, 10);
g.endFill();
headerMask.visible = false;
}
header.mask = null;
header.selectionLayer.mask = headerMask;
headerMask.width = maskShape.width;
headerMask.height = maskShape.height;
headerMask.x = maskShape.x;
headerMask.y = maskShape.y;
// If we have a vScroll only, we want the scrollbar to be below
// the header.
if (verticalScrollBar != null && verticalScrollBar.visible &&
(horizontalScrollBar == null || !horizontalScrollBar.visible) &&
headerVisible)
{
headerMask.width += verticalScrollBar.getExplicitOrMeasuredWidth();
}
if (lockedRowContent)
{
if (!lockedRowMask)
{
lockedRowMask = new Shape();
addChild(lockedRowMask);
g = lockedRowMask.graphics;
g.beginFill(0xFFFFFF);
g.drawRect(0, 0, 10, 10);
g.endFill();
lockedRowMask.visible = false;
}
lockedRowContent.mask = null;
lockedRowContent.selectionLayer.mask = lockedRowMask;
lockedRowMask.width = maskShape.width;
lockedRowMask.height = maskShape.height;
lockedRowMask.x = maskShape.x;
lockedRowMask.y = maskShape.y;
}
if (lockedColumnContent)
{
if (!lockedColumnMask)
{
lockedColumnMask = new Shape();
addChild(lockedColumnMask);
g = lockedColumnMask.graphics;
g.beginFill(0xFFFFFF);
g.drawRect(0, 0, 10, 10);
g.endFill();
lockedColumnMask.visible = false;
}
lockedColumnContent.mask = null;
lockedColumnContent.selectionLayer.mask = lockedColumnMask;
lockedColumnMask.width = maskShape.width;
lockedColumnMask.height = maskShape.height;
lockedColumnMask.x = maskShape.x;
lockedColumnMask.y = maskShape.y;
}
if (lockedColumnAndRowContent)
{
if (!lockedColumnAndRowMask)
{
lockedColumnAndRowMask = new Shape();
addChild(lockedColumnAndRowMask);
g = lockedColumnAndRowMask.graphics;
g.beginFill(0xFFFFFF);
g.drawRect(0, 0, 10, 10);
g.endFill();
lockedColumnAndRowMask.visible = false;
}
lockedColumnAndRowContent.mask = null;
lockedColumnAndRowContent.selectionLayer.mask = lockedColumnAndRowMask;
lockedColumnAndRowMask.width = maskShape.width;
lockedColumnAndRowMask.height = maskShape.height;
lockedColumnAndRowMask.x = maskShape.x;
lockedColumnAndRowMask.y = maskShape.y;
}
if (lockedColumnHeader)
{
if (!lockedColumnHeaderMask)
{
lockedColumnHeaderMask = new Shape();
addChild(lockedColumnHeaderMask);
g = lockedColumnHeaderMask.graphics;
g.beginFill(0xFFFFFF);
g.drawRect(0, 0, 10, 10);
g.endFill();
lockedColumnHeaderMask.visible = false;
}
lockedColumnHeader.mask = null;
lockedColumnHeader.selectionLayer.mask = lockedColumnHeaderMask;
lockedColumnHeaderMask.width = maskShape.width;
lockedColumnHeaderMask.height = maskShape.height;
lockedColumnHeaderMask.x = maskShape.x;
lockedColumnHeaderMask.y = maskShape.y;
}
}
}
// If we've decided to clip the entire listContent, then stop here.
// There's no need to clip individual items
if (listContent.mask)
return;
// If the last row fits inside listContent, then stop here. There's
// no need to do any clipping.
var lastRowIndex:int = listItems.length - 1;
var lastRowInfo:ListRowInfo = rowInfo[lastRowIndex];
var lastRowItems:Array = listItems[lastRowIndex];
if (lastRowInfo.y + lastRowInfo.height <= listContent.height)
return;
// For each list item in the last row, either apply a clip mask or
// set the row's height to not exceed the height of listContent
var numColumns:int = lastRowItems.length;
var rowY:Number = lastRowInfo.y;
var rowWidth:Number = listContent.width;
var rowHeight:Number = listContent.height - lastRowInfo.y;
for (var i:int = 0; i < numColumns; i++)
{
var item:DisplayObject = lastRowItems[i];
var yOffset:Number = item.y - rowY;
if (item is IUITextField && !IUITextField(item).embedFonts)
item.height = Math.max(rowHeight - yOffset, 0);
else
item.mask = createItemMask(0, rowY + yOffset, rowWidth, Math.max(rowHeight - yOffset, 0));
}
if (lockedColumnContent)
{
lastRowIndex = lockedColumnContent.listItems.length - 1;
lastRowItems = lockedColumnContent.listItems[lastRowIndex];
numColumns = lastRowItems.length;
rowWidth = lockedColumnContent.width;
for (i = 0; i < numColumns; i++)
{
item = lastRowItems[i];
yOffset = item.y - rowY;
if (item is IUITextField && !IUITextField(item).embedFonts)
item.height = Math.max(rowHeight - yOffset, 0);
else
item.mask = createItemMask(0, rowY + yOffset, rowWidth, Math.max(rowHeight - yOffset, 0), lockedColumnContent);
}
}
}
/**
* @private
*/
override mx_internal function removeClipMask():void
{
super.removeClipMask();
if (!lockedColumnContent)
return;
// If there are no rows, do nothing.
var lastRowIndex:int = listItems.length - 1;
if (lastRowIndex < 0)
return;
// Undo the effects of the last "for" loop in addClipMask
var rowHeight:Number = rowInfo[lastRowIndex].height;
var lastRowInfo:ListRowInfo = rowInfo[lastRowIndex];
var lastRowItems:Array = lockedColumnContent.listItems[lastRowIndex];
if (lastRowItems)
{
var numColumns:int = lastRowItems.length;
for (var i:int = 0; i < numColumns; i++)
{
var item:DisplayObject = lastRowItems[i];
if (item is IUITextField && !IUITextField(item).embedFonts)
{
if (item.height != rowHeight - (item.y - lastRowInfo.y))
item.height = rowHeight - (item.y - lastRowInfo.y);
}
else if (item && item.mask)
{
itemMaskFreeList.push(item.mask);
item.mask = null;
}
}
}
}
/**
* @private
*/
mx_internal function get gridColumnMap():Object
{
return columnMap;
}
/**
* @private
*/
override public function itemRendererToIndex(itemRenderer:IListItemRenderer):int
{
if (itemRenderer.name in rowMap)
{
var index:int = rowMap[itemRenderer.name].rowIndex;
if (itemRenderer.parent is DataGridLockedRowContentHolder)
return index;
// not clear why the commented out logic isn't correct...
// maybe rowIndex isn't being set correctly?
// return index + verticalScrollPosition + offscreenExtraRowsTop;
return index + lockedRowCount + verticalScrollPosition - offscreenExtraRowsTop;
}
return int.MIN_VALUE;
}
/**
* @private
*/
override mx_internal function selectionTween_updateHandler(event:TweenEvent):void
{
super.selectionTween_updateHandler(event);
if (lockedColumnCount)
{
var s:Sprite = Sprite(event.target.listener);
s = indicatorDictionary[s] as Sprite;
s.alpha = Number(event.value);
}
}
/**
* @private
*/
override protected function destroyRow(i:int, numCols:int):void
{
super.destroyRow(i, numCols);
if (lockedColumnCount)
{
var listItems:Array = lockedColumnContent.listItems;
numCols = listItems[i].length;
var rowInfo:Array = lockedColumnContent.rowInfo;
var visibleData:Object = lockedColumnContent.visibleData;
var r:IListItemRenderer;
var uid:String = rowInfo[i].uid;
removeIndicators(uid);
for (var j:int = 0; j < numCols; j++)
{
r = listItems[i][j];
if (r.data)
delete visibleData[uid];
addToFreeItemRenderers(r);
// we don't seem to be doing this consistently throughout the code?
// listContent.removeChild(DisplayObject(r));
}
}
}
/**
* @private
*/
override protected function moveRowVertically(i:int, numCols:int, moveBlockDistance:Number):void
{
super.moveRowVertically(i, numCols, moveBlockDistance);
if (lockedColumnCount)
{
var listItems:Array = lockedColumnContent.listItems;
numCols = listItems[i].length;
var rowInfo:Array = lockedColumnContent.rowInfo;
var r:IListItemRenderer;
for (var j:int = 0; j < numCols; j++)
{
r = listItems[i][j];
r.move(r.x, r.y + moveBlockDistance);
}
}
}
/**
* @private
*/
override protected function shiftRow(oldIndex:int, newIndex:int, numCols:int, shiftItems:Boolean):void
{
super.shiftRow(oldIndex, newIndex, numCols, shiftItems);
if (lockedColumnCount)
{
var listItems:Array = lockedColumnContent.listItems;
numCols = listItems[oldIndex].length;
var r:IListItemRenderer;
for (var j:int = 0; j < numCols; j++)
{
r = listItems[oldIndex][j];
if (shiftItems)
{
listItems[newIndex][j] = r;
rowMap[r.name].rowIndex = newIndex;
}
// this is sort of a hack to accomodate the fact that
// scrolling down does a splice which throws off these values.
// probably better to call shiftRow with different parameters?
else
rowMap[r.name].rowIndex = oldIndex;
}
if (shiftItems)
lockedColumnContent.rowInfo[newIndex] = lockedColumnContent.rowInfo[oldIndex];
}
}
/**
* @private
*/
override protected function moveIndicatorsVertically(uid:String, moveBlockDistance:Number):void
{
super.moveIndicatorsVertically(uid, moveBlockDistance);
if (lockedColumnCount)
{
if (uid)
{
if (selectionIndicators[uid])
Sprite(indicatorDictionary[selectionIndicators[uid]]).y += moveBlockDistance;
if (highlightUID == uid)
columnHighlightIndicator.y += moveBlockDistance;
if (caretUID == uid)
columnCaretIndicator.y += moveBlockDistance;
}
}
}
/**
* @private
*/
override protected function truncateRowArrays(numRows:int):void
{
super.truncateRowArrays(numRows);
if (lockedColumnCount)
{
lockedColumnContent.listItems.splice(numRows);
lockedColumnContent.rowInfo.splice(numRows);
}
}
/**
* @private
*/
override protected function addToRowArrays():void
{
super.addToRowArrays();
if (lockedColumnCount)
{
lockedColumnContent.listItems.splice(0, 0, null);
lockedColumnContent.rowInfo.splice(0, 0, null);
}
}
/**
* @private
*/
override protected function restoreRowArrays(modDeltaPos:int):void
{
super.restoreRowArrays(modDeltaPos);
if (lockedColumnCount)
{
lockedColumnContent.listItems.splice(0, modDeltaPos);
lockedColumnContent.rowInfo.splice(0, modDeltaPos);
}
}
/**
* @private
*/
override protected function removeFromRowArrays(i:int):void
{
super.removeFromRowArrays(i);
if (lockedColumnCount)
{
lockedColumnContent.listItems.splice(i, 1);
lockedColumnContent.rowInfo.splice(i, 1);
}
}
/**
* @private
*/
override protected function clearVisibleData():void
{
if (lockedColumnContent)
lockedColumnContent.visibleData = {};
if (lockedRowContent)
lockedRowContent.visibleData = {};
if (lockedColumnAndRowContent)
lockedColumnAndRowContent.visibleData = {};
super.clearVisibleData();
}
/**
* @private
*/
override protected function set allowItemSizeChangeNotification(value:Boolean):void
{
if (lockedColumnContent)
lockedColumnContent.allowItemSizeChangeNotification = value;
if (lockedRowContent)
lockedRowContent.allowItemSizeChangeNotification = value;
if (lockedColumnAndRowContent)
lockedColumnAndRowContent.allowItemSizeChangeNotification = value;
super.allowItemSizeChangeNotification = value;
}
/**
* @private
*/
override protected function indexToRow(index:int):int
{
if (index < lockedRowCount)
return -1;
return index - lockedRowCount;
}
/**
* Moves the selection in a vertical direction in response
* to the user selecting items with the up arrow or down arrow
* keys and modifiers such as the Shift and Ctrl keys. This method
* might change the <code>horizontalScrollPosition</code>,
* <code>verticalScrollPosition</code>, and <code>caretIndex</code>
* properties, and call the <code>finishKeySelection()</code>method
* to update the selection.
*
* @param code The key that was pressed (for example, <code>Keyboard.DOWN</code>).
*
* @param shiftKey <code>true</code> if the Shift key was held down when
* the keyboard key was pressed.
*
* @param ctrlKey <code>true</code> if the Ctrl key was held down when
* the keyboard key was pressed
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override protected function moveSelectionVertically(code:uint, shiftKey:Boolean,
ctrlKey:Boolean):void
{
var newVerticalScrollPosition:Number;
var listItem:IListItemRenderer;
var uid:String;
var len:int;
var bSelChanged:Boolean = false;
showCaret = true;
var rowCount:int = listItems.length;
var onscreenRowCount:int = listItems.length - offscreenExtraRowsTop - offscreenExtraRowsBottom;
var partialRow:int = (rowInfo[rowCount - offscreenExtraRowsBottom - 1].y +
rowInfo[rowCount - offscreenExtraRowsBottom - 1].height >
listContent.heightExcludingOffsets - listContent.topOffset) ? 1 : 0;
var bUpdateVerticalScrollPosition:Boolean = false;
bSelectItem = false;
switch (code)
{
case Keyboard.UP:
{
if (caretIndex > 0)
{
caretIndex--;
bSelectItem = true;
if (caretIndex >= lockedRowCount)
bUpdateVerticalScrollPosition = true;
}
break;
}
case Keyboard.DOWN:
{
if (caretIndex >= lockedRowCount - 1)
{
if (caretIndex < collection.length - 1)
{
caretIndex++;
bUpdateVerticalScrollPosition = true;
bSelectItem = true;
}
else if ((caretIndex == collection.length - 1) && partialRow)
{
if (verticalScrollPosition < maxVerticalScrollPosition)
newVerticalScrollPosition = verticalScrollPosition + 1;
}
}
else if (caretIndex < collection.length - 1)
{
caretIndex++;
bSelectItem = true;
}
break;
}
case Keyboard.PAGE_UP:
{
if (caretIndex > lockedRowCount)
{
// if the caret is on-screen, but not at the top row
// just move the caret to the top row
if (caretIndex > verticalScrollPosition + lockedRowCount &&
caretIndex < verticalScrollPosition + lockedRowCount + onscreenRowCount)
{
caretIndex = verticalScrollPosition + lockedRowCount;
}
else
{
// paging up is really hard because we don't know how many
// rows to move because of variable row height. We would have
// to double-buffer a previous screen in order to get this exact
// so we just guess for now based on current rowCount
caretIndex = Math.max(caretIndex - Math.max(onscreenRowCount - partialRow, 1), lockedRowCount);
newVerticalScrollPosition = Math.max(caretIndex, lockedRowCount) - lockedRowCount;
}
bSelectItem = true;
}
else
{
caretIndex = 0;
bSelectItem = true;
}
break;
}
case Keyboard.PAGE_DOWN:
{
// if the caret is on-screen, but not at the bottom row
// just move the caret to the bottom row (not partial row)
if (caretIndex >= verticalScrollPosition + lockedRowCount &&
caretIndex < verticalScrollPosition + lockedRowCount + onscreenRowCount - partialRow - 1)
{
}
else
{
// With edge case involving very large rows
// make sure we move forward.
if ((caretIndex - lockedRowCount == verticalScrollPosition) &&
(onscreenRowCount - partialRow <= 1))
caretIndex++;
newVerticalScrollPosition = Math.min(Math.max(caretIndex - lockedRowCount, 0), maxVerticalScrollPosition);
}
bSelectItem = true;
break;
}
case Keyboard.HOME:
{
if (caretIndex > 0)
{
caretIndex = 0;
bSelectItem = true;
newVerticalScrollPosition = 0;
}
break;
}
case Keyboard.END:
{
if (lockedRowCount >= collection.length)
{
caretIndex = collection.length - 1;
bSelectItem = true;
}
else
{
if (caretIndex < collection.length - 1)
{
caretIndex = collection.length - 1;
bSelectItem = true;
newVerticalScrollPosition = maxVerticalScrollPosition;
}
}
break;
}
case Keyboard.SPACE:
{
bUpdateVerticalScrollPosition = true;
bSelectItem = true;
break;
}
}
if (bUpdateVerticalScrollPosition)
{
if (caretIndex >= verticalScrollPosition + lockedRowCount + onscreenRowCount - partialRow)
{
if (onscreenRowCount - partialRow == 0)
newVerticalScrollPosition = Math.min(maxVerticalScrollPosition, Math.max(caretIndex - lockedRowCount, 0));
else
newVerticalScrollPosition = Math.min(maxVerticalScrollPosition, caretIndex - lockedRowCount - onscreenRowCount + partialRow + 1);
}
else if (caretIndex < verticalScrollPosition + lockedRowCount)
newVerticalScrollPosition = Math.max(caretIndex - lockedRowCount, 0);
}
if (!isNaN(newVerticalScrollPosition))
{
if (verticalScrollPosition != newVerticalScrollPosition)
{
var se:ScrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
se.detail = ScrollEventDetail.THUMB_POSITION;
se.direction = ScrollEventDirection.VERTICAL;
se.delta = newVerticalScrollPosition - verticalScrollPosition;
se.position = newVerticalScrollPosition;
verticalScrollPosition = newVerticalScrollPosition;
dispatchEvent(se);
}
// bail if we page faulted
if (!iteratorValid)
{
keySelectionPending = true;
return;
}
}
bShiftKey = shiftKey;
bCtrlKey = ctrlKey;
lastKey = code;
finishKeySelection();
}
/**
* Sets selected items based on the <code>caretIndex</code> and
* <code>anchorIndex</code> properties.
* This method is called by the keyboard selection handlers
* and by the <code>updateDisplayList()</code> method in case the
* keyboard selection handler received a page fault while scrolling to get more items.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override protected function finishKeySelection():void
{
var uid:String;
var rowCount:int = listItems.length;
var onscreenRowCount:int = listItems.length - offscreenExtraRowsTop - offscreenExtraRowsBottom;
var partialRow:int = (rowInfo[rowCount - offscreenExtraRowsBottom - 1].y +
rowInfo[rowCount - offscreenExtraRowsBottom - 1].height >
listContent.heightExcludingOffsets - listContent.topOffset) ? 1 : 0;
if (lastKey == Keyboard.PAGE_DOWN)
{
// set caret to last full row of new screen
// partial rows take what you can get
if (onscreenRowCount - partialRow == 0)
{
caretIndex = Math.min(verticalScrollPosition + lockedRowCount + onscreenRowCount - partialRow,
collection.length - 1);
}
else
{
caretIndex = Math.min(verticalScrollPosition + lockedRowCount + onscreenRowCount - partialRow - 1,
collection.length - 1);
}
}
var listItem:IListItemRenderer;
var bSelChanged:Boolean = false;
if (bSelectItem && ((caretIndex - verticalScrollPosition >= 0) || (caretIndex < lockedRowCount)))
{
if (caretIndex - lockedRowCount - verticalScrollPosition > Math.max(onscreenRowCount - partialRow - 1,0))
{
// If we've tried to jump to the end of the list but find that
// maxVerticalScrollPosition was off...try again.
if ((lastKey == Keyboard.END) && (maxVerticalScrollPosition > verticalScrollPosition))
{
caretIndex = caretIndex - 1;
moveSelectionVertically(lastKey,bShiftKey,bCtrlKey);
return;
}
caretIndex = lockedRowCount + onscreenRowCount - partialRow - 1 + verticalScrollPosition;
}
if (caretIndex < lockedRowCount)
listItem = lockedRowContent.listItems[caretIndex][0];
else
listItem = listItems[caretIndex - lockedRowCount - verticalScrollPosition + offscreenExtraRowsTop][0];
if (listItem)
{
uid = itemToUID(listItem.data);
listItem = UIDToItemRenderer(uid);
if (!bCtrlKey || lastKey == Keyboard.SPACE)
{
selectItem(listItem, bShiftKey, bCtrlKey);
bSelChanged = true;
}
if (bCtrlKey)
{
drawItem(listItem, selectedData[uid] != null, uid == highlightUID, true);
}
}
}
if (bSelChanged)
{
var pt:Point = itemRendererToIndices(listItem);
var evt:ListEvent = new ListEvent(ListEvent.CHANGE);
if (pt)
{
evt.columnIndex = pt.x;
evt.rowIndex = pt.y;
}
evt.itemRenderer = listItem;
dispatchEvent(evt);
}
}
/**
* Returns a Point object that defines the <code>columnIndex</code> and <code>rowIndex</code> properties of an
* item renderer. Because item renderers are only created for items
* within the set of viewable rows, you cannot use this method to get the indices for items
* that are not visible. Also, item renderers
* are recycled so the indices that you get for an item might change
* if that item renderer is reused to display a different item.
* Usually, this method is called during mouse and keyboard handling
* when the set of data displayed by the item renderers has not yet changed.
*
* @param item An item renderer.
*
* @return A Point object. The <code>x</code> property is the <code>columnIndex</code>
* and the <code>y</code> property is the <code>rowIndex</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override protected function itemRendererToIndices(item:IListItemRenderer):Point
{
if (!item || !(item.name in rowMap))
return null;
var content:ListBaseContentHolder = item.parent as ListBaseContentHolder;
var found:Boolean = false;
var index:int = rowMap[item.name].rowIndex;
var len:int = content.listItems[index].length;
for (var i:int = 0; i < len; i++)
{
if (content.listItems[index][i] == item)
{
found = true;
break;
}
}
if (!found)
return null;
if (lockedRowContent == content)
return new Point(i + horizontalScrollPosition + lockedColumnCount,
index + offscreenExtraRowsTop);
if (lockedColumnAndRowContent == content)
return new Point(i, index + offscreenExtraRowsTop);
if (lockedColumnContent == content)
return new Point(i,
index + verticalScrollPosition + lockedRowCount + offscreenExtraRowsTop);
return new Point(i + horizontalScrollPosition + lockedColumnCount,
index + verticalScrollPosition + lockedRowCount + offscreenExtraRowsTop);
}
/**
* @private
* the inselectItem is a flag that converts relative seeks to absolute
*/
override protected function selectItem(item:IListItemRenderer,
shiftKey:Boolean, ctrlKey:Boolean,
transition:Boolean = true):Boolean
{
var bookmark:CursorBookmark = iterator.bookmark;
if (lockedRowCount)
{
inSelectItem = true;
iterator.seek(CursorBookmark.FIRST, 0);
}
var retval:Boolean = super.selectItem(item, shiftKey, ctrlKey, transition);
if (lockedRowCount)
{
iterator.seek(bookmark, 0);
inSelectItem = false;
}
return retval;
}
/**
* @private
*/
override public function showDropFeedback(event:DragEvent):void
{
if (!dropIndicator)
{
var dropIndicatorClass:Class = getStyle("dropIndicatorSkin");
if (!dropIndicatorClass)
dropIndicatorClass = ListDropIndicator;
dropIndicator = IFlexDisplayObject(new dropIndicatorClass());
var vm:EdgeMetrics = viewMetrics;
drawFocus(true);
dropIndicator.x = 2;
dropIndicator.setActualSize(listContent.width - 4, 4);
dropIndicator.visible = true;
listContent.addChild(DisplayObject(dropIndicator));
if (collection)
dragScrollingInterval = setInterval(dragScroll, 15);
}
var rowNum:Number = calculateDropIndex(event);
if (lockedRowCount && rowNum < lockedRowCount)
{
if (dropIndicator.parent != lockedRowContent)
lockedRowContent.addChild(DisplayObject(dropIndicator));
dropIndicator.y = lockedRowContent.listItems[rowNum][0].y - 1
}
else
{
var rowCount:int = listItems.length;
var partialRow:int = (rowCount > 0 && rowInfo[rowCount - offscreenExtraRowsBottom - 1].y +
rowInfo[rowCount - offscreenExtraRowsBottom - 1].height >
listContent.heightExcludingOffsets - listContent.topOffset) ? 1 : 0;
rowNum -= verticalScrollPosition + lockedRowCount;
if (rowNum >= rowCount)
{
if (partialRow)
rowNum = rowCount - 1;
else
rowNum = rowCount;
}
if (rowNum < 0)
rowNum = 0;
if (dropIndicator.parent != listContent)
listContent.addChild(DisplayObject(dropIndicator));
dropIndicator.y = calculateDropIndicatorY(rowCount, rowNum + offscreenExtraRowsTop);
}
}
/**
* @private
* If there are locked rows but there weren't enough collection items, the iterator
* is not in the right place and needs fixing.
*/
override protected function adjustAfterAdd(items:Array, location:int):Boolean
{
var retval:Boolean = super.adjustAfterAdd(items, location);
if (lockedRowCount)
{
if (verticalScrollPosition > 0 && verticalScrollPosition >= location && location <= lockedRowCount)
{
if (verticalScrollPosition + lockedRowCount >= collection.length)
{
// don't let adjustment take us beyond end of iterator
super.verticalScrollPosition = verticalScrollPosition - items.length;
}
}
if (collection.length - items.length <= lockedRowCount && collection.length >= lockedRowCount
|| location <= lockedRowCount
|| location == lockedRowCount + verticalScrollPosition)
{
try
{
iterator.seek(CursorBookmark.FIRST, lockedRowCount + verticalScrollPosition);
if (!iteratorValid)
{
iteratorValid = true;
lastSeekPending = null;
}
}
catch(e:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, lockedRowCount + verticalScrollPosition)
e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
// trace("IPE in UpdateDisplayList");
iteratorValid = false;
// don't do anything, we'll repaint when the data arrives
}
}
}
return retval;
}
/**
* @private
* If there are locked rows but there weren't enough collection items, the iterator
* is not in the right place and needs fixing.
*/
override protected function adjustAfterRemove(items:Array, location:int, requiresValueCommit:Boolean):Boolean
{
var adjustIterator:Boolean = false;
if (lockedRowCount && listItems.length && listItems[0].length)
{
if (location <= lockedRowCount + verticalScrollPosition)
adjustIterator = true;
else
{
// special case when we have less than a screen full of stuff
var firstUID:String = rowMap[listItems[0][0].name].uid;
for (var i:int = 0; i < items.length; i++)
{
var uid:String = itemToUID(items[i]);
if (uid == firstUID && verticalScrollPosition == 0)
{
adjustIterator = true;
break;
}
}
}
}
var retval:Boolean = super.adjustAfterRemove(items, location, requiresValueCommit);
if (lockedRowCount)
{
if (verticalScrollPosition > 0 && location > lockedRowCount
&& verticalScrollPosition <= lockedRowCount && verticalScrollPosition == maxVerticalScrollPosition )
{
super.verticalScrollPosition = verticalScrollPosition - items.length;
adjustIterator = true;
}
if (adjustIterator)
{
try
{
iterator.seek(CursorBookmark.FIRST, lockedRowCount + verticalScrollPosition);
if (!iteratorValid)
{
iteratorValid = true;
lastSeekPending = null;
}
}
catch(e:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, lockedRowCount + verticalScrollPosition)
e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
// trace("IPE in UpdateDisplayList");
iteratorValid = false;
// don't do anything, we'll repaint when the data arrives
}
}
}
return retval;
}
}
}