blob: 4663eb6188d7b337d1235001dfdbd8016d79e082 [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.listClasses
{
import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.geom.Point;
import flash.ui.Keyboard;
import flash.utils.Dictionary;
import flash.utils.setInterval;
import mx.collections.CursorBookmark;
import mx.collections.ItemResponder;
import mx.collections.ItemWrapper;
import mx.collections.ModifiedCollectionView;
import mx.collections.errors.ItemPendingError;
import mx.controls.scrollClasses.ScrollBar;
import mx.core.ClassFactory;
import mx.core.EdgeMetrics;
import mx.core.FlexShape;
import mx.core.FlexSprite;
import mx.core.IFactory;
import mx.core.IFlexDisplayObject;
import mx.core.UIComponentGlobals;
import mx.core.mx_internal;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.events.DragEvent;
import mx.events.FlexEvent;
import mx.events.ListEvent;
import mx.events.ScrollEvent;
import mx.events.ScrollEventDetail;
import mx.events.ScrollEventDirection;
import mx.skins.halo.ListDropIndicator;
use namespace mx_internal;
/**
* The TileBase class is the base class for controls
* that display data items in a sequence of rows and columns.
* TileBase-derived classes ignore the <code>variableRowHeight</code>
* and <code>wordWrap</code> properties inherited from their parent class.
* All items in a TileList are the same width and height.
*
* <p>This class is not used directly in applications.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class TileBase extends ListBase
{
include "../../core/Version.as";
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function TileBase()
{
super();
itemRenderer = new ClassFactory(TileListItemRenderer);
// Set default sizes.
setRowHeight(50);
setColumnWidth(50);
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
// 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 lastColumnCount:int = 0;
private var lastRowCount:int = 0;
/**
* Cache of measuring objects by factory.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var measuringObjects:Dictionary;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// direction
//----------------------------------
/**
* @private
* Storage for direction property.
*/
private var _direction:String = TileBaseDirection.HORIZONTAL;
[Bindable("directionChanged")]
[Inspectable(category="General", enumeration="vertical,horizontal", defaultValue="horizontal")]
/**
* The direction in which this control lays out its children.
* Possible values are <code>TileBaseDirection.HORIZONTAL</code>
* and <code>TileBaseDirection.VERTICAL</code>.
* The default value is <code>TileBaseDirection.HORIZONTAL</code>.
*
* <p>If the value is <code>TileBaseDirection.HORIZONTAL</code>, the tiles are
* laid out along the first row until the number of visible columns or maxColumns
* is reached and then a new row is started. If more rows are created
* than can be displayed at once, the control will display a vertical scrollbar.
* The opposite is true if the value is <code>TileBaseDirection.VERTICAL</code>.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get direction():String
{
return _direction;
}
/**
* @private
*/
public function set direction(value:String):void
{
_direction = value;
itemsSizeChanged = true;
offscreenExtraRowsOrColumnsChanged = true;
if (listContent)
{
if (direction == TileBaseDirection.HORIZONTAL)
{
listContent.leftOffset = listContent.rightOffset = 0;
offscreenExtraColumnsLeft = offscreenExtraColumnsRight = 0;
}
else
{
listContent.topOffset = listContent.bottomOffset = 0;
offscreenExtraRowsTop = offscreenExtraRowsBottom = 0;
}
}
invalidateProperties();
invalidateSize();
invalidateDisplayList();
dispatchEvent(new Event("directionChanged"));
}
//----------------------------------
// maxColumns
//----------------------------------
/**
* @private
* Storage for the maxColumns property.
*/
private var _maxColumns:int = 0;
[Inspectable(category="General", defaultValue="0")]
/**
* The maximum number of columns that the control can have.
* If 0, then there are no limits to the number of
* columns. This value is ignored
* if the direction is <code>TileBaseDirection.VERTICAL</code>
* because the control will have as many columns as it needs to
* to display all the data.
*
* <p>The default value is 0 (no limit).</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get maxColumns():int
{
return _maxColumns;
}
/**
* @private
*/
public function set maxColumns(value:int):void
{
if (_maxColumns != value)
{
_maxColumns = value;
invalidateSize();
invalidateDisplayList();
}
}
//----------------------------------
// maxRows
//----------------------------------
/**
* @private
* Storage for the maxRows property.
*/
private var _maxRows:int = 0;
[Inspectable(category="General", defaultValue="0")]
/**
* The maximum number of rows that the control can have.
* If 0, then there is no limit to the number of
* rows. This value is ignored
* if the direction is <code>TileBaseDirection.HORIZONTAL</code>
* because the control will have as many rows as it needs to
* to display all the data.
*
* <p>The default value is 0 (no limit).</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get maxRows():int
{
return _maxRows;
}
/**
* @private
*/
public function set maxRows(value:int):void
{
if (_maxRows != value)
{
_maxRows = value;
invalidateSize();
invalidateDisplayList();
}
}
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function createChildren():void
{
super.createChildren();
listContent.mask = maskShape;
}
/**
* @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);
var numRows:int;
var numCols:int;
var colNum:int;
var rowNum:int;
var xx:Number;
var yy:Number;
var wrappedData:Object;
var data:Object;
var uid:String
var oldItem:IListItemRenderer
var item:IListItemRenderer;
var more:Boolean;
var valid:Boolean;
var i:int;
var rh:Number;
var lastRowMade:int;
var lastColumnMade:int;
var bSelected:Boolean = false;
var bHighlight:Boolean = false;
var bCaret:Boolean = false;
// trace("TileBase.makeRowsAndColumns, horizontalScrollPosition = " + horizontalScrollPosition +
// ", iterator index = " + iterator.bookmark.getViewIndex() + ", iterator current = " +
// iterator.current);
if (columnWidth == 0 || rowHeight == 0)
return null;
invalidateSizeFlag = true;
allowItemSizeChangeNotification = false;
if (direction == TileBaseDirection.VERTICAL)
{
numRows = maxRows > 0 ? maxRows : Math.max(Math.floor(listContent.heightExcludingOffsets / rowHeight), 1);
numCols = Math.max(Math.ceil((listContent.widthExcludingOffsets)/ columnWidth), 1);
setRowCount(numRows);
setColumnCount(numCols);
colNum = firstCol;
xx = left;
yy = top;
lastColumnMade = colNum - 1;
more = (iterator != null && !iterator.afterLast && iteratorValid);
if ((!byCount && yy < bottom) || byCount)
{
while ((byCount && rowsNeeded--) || (!byCount && (colNum < numCols + firstCol)))
{
rowNum = firstRow;
yy = top;
while (rowNum < numRows)
{
valid = more;
wrappedData = more ? iterator.current : null;
data = (wrappedData is ItemWrapper) ? wrappedData.data : wrappedData;
more = moveNextSafely(more);
if (!listItems[rowNum])
listItems[rowNum] = [];
if (valid && yy < bottom)
{
uid = itemToUID(wrappedData);
rowInfo[rowNum] = new ListRowInfo(yy, rowHeight, uid);
item = getPreparedItemRenderer(rowNum, colNum, wrappedData, data, uid);
placeAndDrawItemRenderer(item,xx,yy,uid);
lastColumnMade = Math.max(colNum,lastColumnMade);
}
else
{
oldItem = listItems[rowNum][colNum];
if (oldItem)
{
addToFreeItemRenderers(oldItem);
// delete rowMap[oldItem.name];
listItems[rowNum][colNum] = null;
}
rowInfo[rowNum] = new ListRowInfo(yy, rowHeight, uid);
}
yy += rowHeight;
rowNum++;
}
colNum ++;
if (firstRow)
{
// we're doing a row along the bottom so we have to skip the beginning of the next column
for (i = 0; i < firstRow; i++)
more = moveNextSafely(more);
}
xx += columnWidth;
}
}
}
else // horizontal
{
numCols = maxColumns > 0 ? maxColumns : Math.max(Math.floor((listContent.widthExcludingOffsets)/ columnWidth), 1);
numRows = Math.max(Math.ceil(listContent.heightExcludingOffsets / rowHeight), 1);
setColumnCount(numCols);
setRowCount(numRows);
rowNum = firstRow;
yy = top;
xx = left;
more = (iterator != null && !iterator.afterLast && iteratorValid);
lastRowMade = rowNum-1;
if ((!byCount && xx < right) || byCount)
{
while ((byCount && rowsNeeded--) || (!byCount && rowNum < numRows + firstRow))
{
colNum = firstCol;
xx = left;
rowInfo[rowNum] = null;
while (colNum < numCols)
{
valid = more;
wrappedData = more ? iterator.current : null;
data = (wrappedData is ItemWrapper) ? wrappedData.data : wrappedData;
more = moveNextSafely(more);
if (!listItems[rowNum])
listItems[rowNum] = [];
if (valid && xx < right)
{
uid = itemToUID(wrappedData);
if (!rowInfo[rowNum])
rowInfo[rowNum] = new ListRowInfo(yy, rowHeight, uid);
item = getPreparedItemRenderer(rowNum, colNum, wrappedData, data, uid);
placeAndDrawItemRenderer(item,xx,yy,uid);
lastRowMade = rowNum;
}
else
{
if (!rowInfo[rowNum])
rowInfo[rowNum] = new ListRowInfo(yy, rowHeight, uid);
oldItem = listItems[rowNum][colNum];
if (oldItem)
{
addToFreeItemRenderers(oldItem);
listItems[rowNum][colNum] = null;
}
}
xx += columnWidth;
colNum++;
}
rowNum ++;
if (firstCol)
{
// we're doing a column along the side so we have to skip the beginning of the next column
for (i = 0; i < firstCol; i++)
more = moveNextSafely(more);
}
yy += rowHeight;
}
}
}
if (!byCount)
{
var a:Array;
// prune excess rows and columns
while (listItems.length > numRows + offscreenExtraRowsTop)
{
a = listItems.pop();
rowInfo.pop();
for (i = 0; i < a.length; i++)
{
oldItem = a[i];
if (oldItem)
{
addToFreeItemRenderers(oldItem);
}
}
}
if (listItems.length && listItems[0].length > numCols + offscreenExtraColumnsLeft)
{
for (i = 0; i < numRows + offscreenExtraRowsTop; i++)
{
a = listItems[i];
while (a.length > numCols + offscreenExtraColumnsLeft)
{
oldItem = a.pop();
if (oldItem)
{
addToFreeItemRenderers(oldItem);
}
}
}
}
}
allowItemSizeChangeNotification = true;
invalidateSizeFlag = false;
return new Point(lastColumnMade - firstCol + 1,lastRowMade - firstRow + 1);
}
private function moveNextSafely(more:Boolean):Boolean
{
if (iterator && more)
{
try
{
more = iterator.moveNext();
}
catch(e1:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(CursorBookmark.CURRENT, 0);
e1.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
more = false;
iteratorValid = false;
}
}
return more;
}
/**
* @private
*/
private function getPreparedItemRenderer(rowNum:int,colNum:int, wrappedData:Object,
data:Object, uid:String):IListItemRenderer
{
var oldItem:IListItemRenderer = listItems[rowNum][colNum];
var item:IListItemRenderer;
var rowData:ListData;
if (oldItem)
{
// If we're running a data effect, do a more expensive check when
// determining if we can reuse this item renderer
if (runningDataEffect ? (dataItemWrappersByRenderer[oldItem] != wrappedData) : (oldItem.data != data))
addToFreeItemRenderers(oldItem);
else
item = oldItem;
}
if (!item)
{
// if we're allowed to re-use existing renderers
if (allowRendererStealingDuringLayout)
{
// try to steal item renderer if it already exists,
// but don't steal item renderers that have already
// been used in the layout. (This will happen if there
// are duplicate UIDs in the collection, which shouldn't
// really happen, but nevertheless may happen).
item = visibleData[uid];
// if we can't steal an item based on it's UID,
// steal based on the UID of the underlying data
if (!item && (wrappedData != data))
item = visibleData[itemToUID(data)];
}
if (item) // if we've stolen a renderer from somewhere else...
{
// update data structures so we're not pointing to it twice
var ld:ListData = ListData(rowMap[item.name]);
if (ld)
{
if (((direction == TileBaseDirection.HORIZONTAL) &&
((ld.rowIndex > rowNum) || ((ld.rowIndex == rowNum) && (ld.columnIndex > colNum)))) ||
((direction == TileBaseDirection.VERTICAL) &&
((ld.columnIndex > colNum) || ((ld.columnIndex == colNum) && (ld.rowIndex > rowNum)))))
listItems[ld.rowIndex][ld.columnIndex] = null;
else
item = null;
}
}
if (!item)
{
item = getReservedOrFreeItemRenderer(wrappedData);
if (item && !isRendererUnconstrained(item))
{
item.x = 0;
item.y = 0;
}
}
// if all else fails...
if (!item)
item = createItemRenderer(data);
item.owner = this;
item.styleName = listContent;
}
if (!item.parent)
listContent.addChild(DisplayObject(item));
rowData = ListData(makeListData(data, uid, rowNum, colNum));
rowMap[item.name] = rowData;
if (item is IDropInListItemRenderer)
IDropInListItemRenderer(item).listData = data ? rowData : null;
item.data = data;
if (wrappedData != data)
dataItemWrappersByRenderer[item] = wrappedData;
item.visible = true;
if (uid)
visibleData[uid] = item;
listItems[rowNum][colNum] = item;
UIComponentGlobals.layoutManager.validateClient(item, true);
return item;
}
/**
* @private
*/
private function placeAndDrawItemRenderer(item:IListItemRenderer, xx:Number, yy:Number, uid:String):void
{
var bSelected:Boolean = false;
var bHighlight:Boolean = false;
var bCaret:Boolean = false;
var rh:Number;
rh = item.getExplicitOrMeasuredHeight();
if (item.width != columnWidth || rh != (rowHeight - cachedPaddingTop - cachedPaddingBottom))
item.setActualSize(columnWidth, rowHeight - cachedPaddingTop - cachedPaddingBottom);
// this is not really doing anything yet
if (!isRendererUnconstrained(item))
item.move(xx, yy + cachedPaddingTop);
bSelected = selectedData[uid] != null;
if (runningDataEffect)
{
bSelected = bSelected || (selectedData[itemToUID(item.data)] != null);
bSelected = bSelected && (!getRendererSemanticValue(item,ModifiedCollectionView.REPLACEMENT))
&& (!getRendererSemanticValue(item,ModifiedCollectionView.ADDED));
}
bHighlight = highlightUID == uid;
bCaret = caretUID == uid;
if (uid)
drawItem(item, bSelected, bHighlight, bCaret);
}
/**
* @private
*/
override protected function configureScrollBars():void
{
var rowCount:int = listItems.length;
if (rowCount == 0)
return;
var colCount:int = listItems[0].length;
if (colCount == 0)
return;
if (rowCount > 1 && (rowCount - offscreenExtraRowsTop - offscreenExtraRowsBottom) * rowHeight > listContent.heightExcludingOffsets)
rowCount--;
rowCount -= (offscreenExtraRowsTop + offscreenExtraRowsBottom);
if (colCount > 1 && (colCount - offscreenExtraColumnsLeft - offscreenExtraColumnsRight) * columnWidth > listContent.widthExcludingOffsets)
colCount--;
colCount -= (offscreenExtraColumnsLeft + offscreenExtraColumnsRight);
var oldHorizontalScrollBar:Object = horizontalScrollBar;
var oldVerticalScrollBar:Object = verticalScrollBar;
var numRows:int;
var numCols:int;
var index:int;
if (direction == TileBaseDirection.VERTICAL)
{
// handle extra blank items at end of list. This is the equivalent
// of adjustVerticalScrollPositionDownward in List.as
if (iteratorValid && horizontalScrollPosition > 0)
{
var fillerCols:int = 0;
// adjust colCount for null items
// note that we should never have offscreenExtraColumnsRight > 0 if there are columns filled with null items
while ((colCount > 0) && listItems[0][colCount + offscreenExtraColumnsLeft - 1] == null)
{
colCount--
fillerCols++;
}
// If we have pinned the scroll position while running a data effect, we can
// sometimes end up without null filler columns, but still needing to adjust the
// scroll position
var expectedFullColumns:int = Math.floor((listContent.widthExcludingOffsets)/ columnWidth);
var extraEmptyColumns:int = Math.max(0,expectedFullColumns - (colCount + fillerCols));
if (fillerCols || extraEmptyColumns)
{
for (var i:int = 0; i < listItems.length; i++)
while (listItems[i].length > colCount + offscreenExtraColumnsLeft)
(listItems[i] as Array).pop();
if (!runningDataEffect)
{
// need to keep >= 0...only a concern if if offscreenColumns > 0
horizontalScrollPosition = Math.max(0, horizontalScrollPosition - (fillerCols + extraEmptyColumns));
// back up enough to make the appropriate number of offscreen columns
index = scrollPositionToIndex(Math.max(0, horizontalScrollPosition - offscreenExtraColumnsLeft),
verticalScrollPosition);
seekPositionSafely(index);
updateList();
}
return;
}
}
if (!iteratorValid)
rowCount = Math.floor(listContent.heightExcludingOffsets / rowHeight);
numRows = maxRows > 0 ? maxRows : rowCount;
// we take out partialColumn if there's no collection because it'll get factored back in
// and we want the math to workout that there's no scrollbars
numCols = collection ? Math.ceil(collection.length / numRows) : colCount;
}
else
{
// handle extra blank items at end of list. This is the equivalent
// of adjustVerticalScrollPositionDownward in List.as
if (iteratorValid && verticalScrollPosition > 0)
{
var fillerRows:int = 0;
while ((rowCount > 0) && ((listItems[rowCount + offscreenExtraRowsTop - 1] == null)
|| (listItems[rowCount + offscreenExtraRowsTop - 1][0] == null)))
{
rowCount--;
fillerRows++;
}
if (fillerRows)
{
// literally prune out the extra rows
// not currently doing this for columns, but it's necessary here.
while (listItems.length > rowCount + offscreenExtraRowsTop)
{
listItems.pop();
rowInfo.pop();
}
if (!runningDataEffect)
{
verticalScrollPosition = Math.max(0, verticalScrollPosition - fillerRows);
index = scrollPositionToIndex(horizontalScrollPosition, Math.max(0,verticalScrollPosition-offscreenExtraRowsTop));
seekPositionSafely(index);
updateList();
}
return;
}
}
if (!iteratorValid)
colCount = Math.floor(listContent.widthExcludingOffsets / columnWidth);
numCols = maxColumns > 0 ? maxColumns : colCount;
// we take out partialRow if there's no collection because it'll get factored back in
// and we want the math to workout that there's no scrollbars
numRows = collection ? Math.ceil(collection.length / numCols) : rowCount;
}
// Depending on the direction of the Tile control the colCount and
// rowCount can be greater than numRows and numCols, resulting in
// negative values for H and V scroll positions.
// We ignore them when they are negative.
maxHorizontalScrollPosition = Math.max(0, numCols - colCount);
maxVerticalScrollPosition = Math.max(0, numRows - rowCount);
// offscreenColumns/Rows shouldn't change within this function (if index is changed,
// this code isn't reached)
// trace("setScrollBarProperties " + numRows + " " + rowCount);
setScrollBarProperties(numCols, colCount, numRows, rowCount);
}
/**
* @private
* Move any rows that don't need rerendering
* Move and rerender any rows left over.
*/
override protected function scrollVertically(pos:int, deltaPos:int,
scrollUp:Boolean):void
{
var numRows:int;
var numCols:int;
var curY:Number;
var uid:String;
var index:int;
// remove the clip mask that was applied to items in the last row of the list
removeClipMask();
var moveBlockDistance:Number;
var oldOffscreenExtraRowsBottom:int = offscreenExtraRowsBottom;
var oldOffscreenExtraRowsTop:int = offscreenExtraRowsTop;
var desiredOffscreenExtraRowsTop:int = offscreenExtraRows/2;
var desiredOffscreenExtraRowsBottom:int = offscreenExtraRows/2;
// by default, we are going to delete as many rows as we're scrolling
// but if we're not currently buffering as many offscreen rows as we
// want to, we might reduce that number
var modDeltaPos:int;
// rows whose renderers we have to discard, i.e. which are scrolling
// offscreen and not into the offscreen buffer
var rowsToClear:int;
var rowDelta:int; // rows that have to be spliced out of the arrays, or added
if (scrollUp)
{
offscreenExtraRowsTop = Math.min(desiredOffscreenExtraRowsTop, offscreenExtraRowsTop + deltaPos);
modDeltaPos = deltaPos - (offscreenExtraRowsTop - oldOffscreenExtraRowsTop);
rowsToClear = modDeltaPos;
}
else
{
// partialRowOffset accounts for an edge case where we are scrolled to the bottom
// but the last row will still be on screen (a partial row) if we scroll.
var partialRowOffset:int = ((offscreenExtraRowsBottom == 0) && listItems.length && listItems[listItems.length-1][0] &&
(listItems[listItems.length-1][0].y + rowHeight < listContent.heightExcludingOffsets - listContent.topOffset)) ? 1 : 0;
offscreenExtraRowsTop = Math.min(desiredOffscreenExtraRowsTop,pos);
offscreenExtraRowsBottom = Math.min(offscreenExtraRowsBottom + deltaPos - partialRowOffset,desiredOffscreenExtraRowsBottom);
modDeltaPos = deltaPos - (oldOffscreenExtraRowsTop - offscreenExtraRowsTop);
rowDelta = (offscreenExtraRowsTop - oldOffscreenExtraRowsTop) + partialRowOffset +
(offscreenExtraRowsBottom - oldOffscreenExtraRowsBottom);
rowsToClear = deltaPos - (offscreenExtraRowsBottom - oldOffscreenExtraRowsBottom) - partialRowOffset;
}
var rowCount:int = listItems.length;
// clear out the old rows that are being scrolled offscreen and not preserved
// in the offscreen buffer dictated by offscreenExtraRows
for (var i:int = 0; i < rowsToClear; i++)
{
numCols = scrollUp ? listItems[i].length : listItems[rowCount - i - 1].length;
for (var j:int = 0; j < columnCount && j < numCols; j++)
{
var r:IListItemRenderer = scrollUp ? listItems[i][j] : listItems[rowCount - i - 1][j];
if (r)
{
delete visibleData[rowMap[r.name].uid];
removeIndicators(rowMap[r.name].uid);
addToFreeItemRenderers(r);
delete rowMap[r.name];
if (scrollUp)
listItems[i][j] = null;
else
listItems[rowCount - i - 1][j] = null;
}
}
}
var actualRowCount:int = listItems.length;
if (scrollUp)
{
// move the rows that don't change
// note that we start from zero because we've taken some rows
// off and put them on the free list already
moveBlockDistance = modDeltaPos * rowHeight;
curY = 0;
for (i = modDeltaPos; i < actualRowCount; i++)
{
numCols = listItems[i].length;
for (j = 0; j < columnCount && j < numCols; j++)
{
r = listItems[i][j];
listItems[i - modDeltaPos][j] = r;
if (r)
{
r.y -= moveBlockDistance;
rowMap[r.name].rowIndex -= modDeltaPos;
moveIndicatorsVertically(rowMap[r.name].uid, -moveBlockDistance);
}
}
// when the row has less number of columns
// we need to clean up the row.
if (numCols < columnCount)
for (j = numCols; j < columnCount; ++j)
listItems[i-modDeltaPos][j] = null;
rowInfo[i - modDeltaPos] = rowInfo[i];
rowInfo[i - modDeltaPos].y -= moveBlockDistance;
curY = rowInfo[i - modDeltaPos].y + rowHeight;
}
listItems.splice(actualRowCount - modDeltaPos -1,modDeltaPos);
rowInfo.splice(actualRowCount - modDeltaPos -1,modDeltaPos);
// perhaps call setRowCount here()? or better to wait until done
index = indicesToIndex(verticalScrollPosition - offscreenExtraRowsTop + actualRowCount - modDeltaPos, horizontalScrollPosition);
seekPositionSafely(index);
// note that we may be making extra rows at the bottom here, if we are adding to the number of rows
// offscreen above.
var rowsNeeded:int = deltaPos + (desiredOffscreenExtraRowsBottom - oldOffscreenExtraRowsBottom);
var actual:Point =
makeRowsAndColumns(0, curY, listContent.width, curY+deltaPos*rowHeight, 0, actualRowCount - modDeltaPos, true, rowsNeeded);
var extraEmptyRows:int = rowsNeeded-actual.y;
while (extraEmptyRows--)
{
listItems.pop();
rowInfo.pop();
}
index = indicesToIndex(verticalScrollPosition - offscreenExtraRowsTop, horizontalScrollPosition);
seekPositionSafely(index);
// if we didn't make as many rows as we wanted to, we must not have entirely
// filled the buffer below the visible area
offscreenExtraRowsBottom = Math.max(0,desiredOffscreenExtraRowsBottom - (actual.y < deltaPos ? rowsNeeded - actual.y : 0));
}
else
{
if (rowDelta < 0)
{
listItems.splice(listItems.length + rowDelta, - rowDelta);
rowInfo.splice(rowInfo.length + rowDelta, -rowDelta);
}
else if (rowDelta > 0)
{
// we are going to increase listItems and rowInfo
for (i = 0; i < rowDelta; i++)
listItems[actualRowCount + i] = [];
}
moveBlockDistance = modDeltaPos * rowHeight;
curY = rowInfo[modDeltaPos].y;
for (i = listItems.length - 1 - modDeltaPos; i >= 0; i--)
{
numCols = listItems[i].length;
for (j = 0; j < columnCount && j < numCols; j++)
{
r = listItems[i][j];
if (r)
{
r.y += moveBlockDistance;
// using modDeltaPos twice here, but it's not necessarily the same value!!??
// or is it? (not if we're splicing...
rowMap[r.name].rowIndex += modDeltaPos;
uid = rowMap[r.name].uid;
listItems[i + modDeltaPos][j] = r;
moveIndicatorsVertically(uid, moveBlockDistance);
}
else
listItems[i + modDeltaPos][j] = null;
}
rowInfo[i + modDeltaPos] = rowInfo[i];
rowInfo[i + modDeltaPos].y += moveBlockDistance;
}
for (i = 0; i < modDeltaPos; i++)
for (j = 0; j < columnCount; j++)
listItems[i][j] = null;
index = indicesToIndex(verticalScrollPosition - offscreenExtraRowsTop, horizontalScrollPosition);
seekPositionSafely(index);
// can't allow makeRowsAndColumns to steal renderers during incremental scrolling down
// because any empty slots in listItems won't be refilled.
allowRendererStealingDuringLayout = false;
actual = makeRowsAndColumns(0, 0, listContent.width, curY, 0, 0, true, modDeltaPos);
allowRendererStealingDuringLayout = true;
seekPositionSafely(index);
}
var oldListContentHeight:Number = listContent.heightExcludingOffsets;
listContent.topOffset = - rowHeight * offscreenExtraRowsTop;
listContent.bottomOffset = offscreenExtraRowsBottom ?
rowInfo[rowInfo.length-1].y + rowHeight + listContent.topOffset - oldListContentHeight
: 0;
adjustListContent();
// if needed, add a clip mask to the items in the last row of the list
addClipMask(false);
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override protected function scrollHorizontally(pos:int, deltaPos:int,
scrollUp:Boolean):void
{
if (deltaPos == 0)
return;
var numRows:int;
var numCols:int;
var curX:Number;
var uid:String;
var index:int;
// remove the clip mask that was applied to items in the last row of the list
removeClipMask();
var moveBlockDistance:Number;
var oldOffscreenExtraColumnsRight:int = offscreenExtraColumnsRight;
var oldOffscreenExtraColumnsLeft:int = offscreenExtraColumnsLeft;
var desiredOffscreenExtraColumnsLeft:int = offscreenExtraColumns/2;
var desiredOffscreenExtraColumnsRight:int = offscreenExtraColumns/2;
// by default, we are going to delete as many rows as we're scrolling
// but if we're not currently buffering as many offscreen rows as we
// want to, we might reduce that number
var modDeltaPos:int;
// rows whose renderers we have to discard, i.e. which are scrolling
// offscreen and not into the offscreen buffer
var columnsToClear:int;
var columnDelta:int; // rows that have to be spliced out of the arrays, or added
if (scrollUp)
{
offscreenExtraColumnsLeft = Math.min(desiredOffscreenExtraColumnsLeft, offscreenExtraColumnsLeft + deltaPos);
modDeltaPos = deltaPos - (offscreenExtraColumnsLeft - oldOffscreenExtraColumnsLeft);
columnsToClear = modDeltaPos;
}
else
{
var partialColumnOffset:int = ((offscreenExtraColumnsRight == 0) && listItems[0] && (listItems[0].length > 0)
&& listItems[0][listItems[0].length - 1]
&& (listItems[0][listItems[0].length - 1].x + columnWidth < listContent.widthExcludingOffsets - listContent.leftOffset))? 1 : 0;
offscreenExtraColumnsLeft = Math.min(desiredOffscreenExtraColumnsLeft,pos);
offscreenExtraColumnsRight = Math.min(offscreenExtraColumnsRight + deltaPos - partialColumnOffset,desiredOffscreenExtraColumnsRight);
modDeltaPos = deltaPos - (oldOffscreenExtraColumnsLeft - offscreenExtraColumnsLeft);
columnDelta = (offscreenExtraColumnsLeft - oldOffscreenExtraColumnsLeft) +
partialColumnOffset +
(offscreenExtraColumnsRight - oldOffscreenExtraColumnsRight);
columnsToClear = deltaPos - (offscreenExtraColumnsRight - oldOffscreenExtraColumnsRight) - partialColumnOffset;
}
// a temporary measure, because columnCount isn't always being maintained accurately :-(
var columnCount:int = listItems[0].length;
// toss the old rows
for (var i:int = 0; i < columnsToClear; i++)
{
for (var j:int = 0; j < rowCount; j++)
{
var r:IListItemRenderer = scrollUp ? listItems[j][i] : listItems[j][columnCount - i - 1];
if (r)
{
delete visibleData[rowMap[r.name].uid];
removeIndicators(rowMap[r.name].uid);
addToFreeItemRenderers(r);
delete rowMap[r.name];
if (scrollUp)
listItems[j][i] = null
else
listItems[j][columnCount - i - 1] = null;
}
}
}
if (scrollUp)
{
moveBlockDistance = modDeltaPos * columnWidth;
curX = 0;
for (i = modDeltaPos; i < columnCount; i++)
{
for (j = 0; j < rowCount; j++)
{
var temp:IListItemRenderer = listItems[j][i];
if (temp)
{
r = temp;
r.x -= moveBlockDistance;
uid = rowMap[r.name].uid;
listItems[j][i - modDeltaPos] = r;
rowMap[r.name].columnIndex -= modDeltaPos;
moveIndicatorsHorizontally(uid, -moveBlockDistance);
}
else
listItems[j][i - modDeltaPos] = null;
}
curX += columnWidth;
}
for (i = 0; i < modDeltaPos; i++)
for (j = 0; j < rowCount; j++)
listItems[j][columnCount - i - 1] = null;
index = indicesToIndex(verticalScrollPosition, horizontalScrollPosition + columnCount - offscreenExtraColumnsLeft - modDeltaPos);
seekPositionSafely(index);
var columnsNeeded:int = deltaPos + (desiredOffscreenExtraColumnsRight - oldOffscreenExtraColumnsRight);
var currentColumns:int = listItems.length ? listItems[0].length - modDeltaPos: 0;
allowRendererStealingDuringLayout = false;
var actual:Point =
makeRowsAndColumns(curX, 0, listContent.width, listContent.height, columnCount - modDeltaPos, 0, true,columnsNeeded);
allowRendererStealingDuringLayout = true;
var extraEmptyColumns:int = listItems[0].length - (currentColumns + actual.x);
if (extraEmptyColumns)
{
for (i = 0; i < listItems.length; i++)
for (j = 0; j < extraEmptyColumns; j++)
listItems[i].pop();
}
index = indicesToIndex(verticalScrollPosition, horizontalScrollPosition - offscreenExtraColumnsLeft);
seekPositionSafely(index);
// if we didn't make as many columns as we wanted to, we must not have entirely
// filled the buffer to the right of the visible area
offscreenExtraColumnsRight = Math.max(0,desiredOffscreenExtraColumnsRight - (actual.x < deltaPos ? columnsNeeded - actual.x : 0));
}
else
{
if (columnDelta < 0)
{
// we are reducing the left offscreen columns and not shifting the array as
// much, we have to delete the columns we were planning on shifting into
var adjustedColumnCount:int = listItems[0].length + columnDelta;
for (j = 0; j < rowCount; j++)
while(listItems[j].length > adjustedColumnCount)
listItems[j].pop();
}
moveBlockDistance = modDeltaPos * columnWidth;
if (modDeltaPos)
curX = moveBlockDistance;
var newColumnCount:int = columnCount + columnDelta;
for (i = newColumnCount - modDeltaPos - 1; i >= 0; i--)
{
for (j = 0; j < rowCount; j++)
{
r = listItems[j][i];
if (r)
{
r.x += moveBlockDistance;
uid = rowMap[r.name].uid;
listItems[j][i + modDeltaPos] = r;
rowMap[r.name].columnIndex += modDeltaPos;
moveIndicatorsHorizontally(uid,moveBlockDistance);
}
else
listItems[j][i + modDeltaPos] = null;
}
}
for (i = 0; i < modDeltaPos; i++)
for (j = 0; j < rowCount; j++)
listItems[j][i] = null;
index = indicesToIndex(verticalScrollPosition, horizontalScrollPosition - offscreenExtraColumnsLeft);
seekPositionSafely(index);
allowRendererStealingDuringLayout = false;
makeRowsAndColumns(0, 0, curX, listContent.height, 0, 0, true, modDeltaPos);
allowRendererStealingDuringLayout = true;
seekPositionSafely(index);
}
var oldListContentWidth:Number = listContent.widthExcludingOffsets;
listContent.leftOffset = - columnWidth * offscreenExtraColumnsLeft;
listContent.rightOffset = offscreenExtraColumnsRight ?
(listItems[0][listItems[0].length-1].x + listItems[0][listItems[0].length-1].width
+ listContent.leftOffset - oldListContentWidth)
: 0;
adjustListContent();
// if needed, add a clip mask to the items in the last row of the list
addClipMask(false);
}
/**
* @private
*/
override protected function moveSelectionVertically(
code:uint, shiftKey:Boolean,
ctrlKey:Boolean):void
{
var newVerticalScrollPosition:Number;
var newHorizontalScrollPosition:Number;
var listItem:IListItemRenderer;
var uid:String;
var len:int;
var selected:Boolean;
var bSelChanged:Boolean = false;
var rowIndex:int;
var colIndex:int;
var rowCount:int = listItems.length - offscreenExtraRowsTop - offscreenExtraRowsBottom;
var numRows:int = ((maxRows > 0) && (direction != TileBaseDirection.HORIZONTAL)) ? maxRows : rowCount;
var partialRow:int = displayingPartialRow() ? 1 : 0;
var partialColumn:int = displayingPartialColumn() ? 1 : 0;
if (!collection)
return;
showCaret = true;
switch (code)
{
case Keyboard.UP:
{
if (caretIndex > 0)
{
if (direction == TileBaseDirection.VERTICAL)
--caretIndex;
else
{
rowIndex = indexToRow(caretIndex);
colIndex = indexToColumn(caretIndex);
if (rowIndex == 0)
{
colIndex--;
rowIndex = lastRowInColumn(colIndex);
}
else
rowIndex--;
caretIndex = Math.min(indicesToIndex(rowIndex, colIndex), collection.length - 1);
}
rowIndex = indexToRow(caretIndex);
colIndex = indexToColumn(caretIndex);
// scroll up if we need to
if (rowIndex < verticalScrollPosition)
newVerticalScrollPosition = verticalScrollPosition - 1;
// wrap down if we need to
if (rowIndex > verticalScrollPosition + rowCount - partialRow)
newVerticalScrollPosition = maxVerticalScrollPosition;
if (colIndex < horizontalScrollPosition)
newHorizontalScrollPosition = horizontalScrollPosition - 1;
}
break;
}
case Keyboard.DOWN:
{
if (caretIndex < collection.length - 1)
{
if (direction == TileBaseDirection.VERTICAL
|| caretIndex == -1)
{
++caretIndex;
}
else
{
rowIndex = indexToRow(caretIndex);
colIndex = indexToColumn(caretIndex);
if (rowIndex == lastRowInColumn(colIndex))
{
rowIndex = 0;
colIndex++;
}
else
{
rowIndex++;
}
caretIndex = Math.min(indicesToIndex(rowIndex, colIndex), collection.length - 1);
}
rowIndex = indexToRow(caretIndex);
colIndex = indexToColumn(caretIndex);
if (rowIndex >= verticalScrollPosition + rowCount - partialRow &&
verticalScrollPosition < maxVerticalScrollPosition)
{
newVerticalScrollPosition = verticalScrollPosition + 1;
}
if (rowIndex < verticalScrollPosition)
newVerticalScrollPosition = rowIndex;
if (colIndex > horizontalScrollPosition + columnCount - 1)
newHorizontalScrollPosition = horizontalScrollPosition + 1;
}
break;
}
case Keyboard.PAGE_UP:
{
if (caretIndex < 0)
caretIndex = scrollPositionToIndex(horizontalScrollPosition, verticalScrollPosition);
rowIndex = indexToRow(caretIndex);
colIndex = indexToColumn(caretIndex);
if (verticalScrollPosition > 0)
{
if (rowIndex == verticalScrollPosition)
newVerticalScrollPosition = rowIndex = Math.max(verticalScrollPosition - (rowCount - partialRow), 0);
else
rowIndex = verticalScrollPosition;
caretIndex = indicesToIndex(rowIndex, colIndex);
// this break is here so we fall throught to .HOME otherwise
break;
}
}
case Keyboard.HOME:
{
if (collection.length)
{
caretIndex = 0;
newVerticalScrollPosition = 0;
newHorizontalScrollPosition = 0;
}
break;
}
case Keyboard.PAGE_DOWN:
{
if (caretIndex < 0)
caretIndex = scrollPositionToIndex(horizontalScrollPosition, verticalScrollPosition);
rowIndex = indexToRow(caretIndex);
colIndex = indexToColumn(caretIndex);
if (rowIndex < maxVerticalScrollPosition)
{
if (rowIndex == verticalScrollPosition + (rowCount - partialRow))
{
newVerticalScrollPosition = Math.min(verticalScrollPosition + rowCount - partialRow, maxVerticalScrollPosition);
rowIndex = verticalScrollPosition + rowCount;
}
else
{
rowIndex = Math.min(verticalScrollPosition + rowCount - partialRow, indexToRow(collection.length - 1));
if (rowIndex == verticalScrollPosition + rowCount - partialRow)
newVerticalScrollPosition = Math.min(verticalScrollPosition + rowCount - partialRow, maxVerticalScrollPosition);
}
caretIndex = Math.min(indicesToIndex(rowIndex, colIndex), collection.length - 1);
// this break is here so we fall through to .END otherwise
break;
}
}
case Keyboard.END:
{
if (caretIndex < collection.length)
{
caretIndex = collection.length - 1;
newVerticalScrollPosition = maxVerticalScrollPosition;
newHorizontalScrollPosition = maxHorizontalScrollPosition;
}
break;
}
}
var scrollEvent:ScrollEvent;
if (!isNaN(newVerticalScrollPosition))
{
if (newVerticalScrollPosition != verticalScrollPosition)
{
scrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
scrollEvent.detail = ScrollEventDetail.THUMB_POSITION;
scrollEvent.direction = ScrollEventDirection.VERTICAL;
scrollEvent.delta = newVerticalScrollPosition - verticalScrollPosition;
scrollEvent.position = newVerticalScrollPosition;
verticalScrollPosition = newVerticalScrollPosition;
dispatchEvent(scrollEvent);
}
}
if (iteratorValid)
{
if (!isNaN(newHorizontalScrollPosition))
{
if (newHorizontalScrollPosition != horizontalScrollPosition)
{
scrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
scrollEvent.detail = ScrollEventDetail.THUMB_POSITION;
scrollEvent.direction = ScrollEventDirection.HORIZONTAL;
scrollEvent.delta = newHorizontalScrollPosition - horizontalScrollPosition;
scrollEvent.position = newHorizontalScrollPosition;
horizontalScrollPosition = newHorizontalScrollPosition;
dispatchEvent(scrollEvent);
}
}
}
if (!iteratorValid)
{
keySelectionPending = true;
return;
}
bShiftKey = shiftKey;
bCtrlKey = ctrlKey;
lastKey = code;
finishKeySelection();
}
/**
* @private
*/
override protected function moveSelectionHorizontally(
code:uint, shiftKey:Boolean,
ctrlKey:Boolean):void
{
var newVerticalScrollPosition:Number;
var newHorizontalScrollPosition:Number;
var listItem:IListItemRenderer;
var uid:String;
var len:int;
var selected:Boolean;
var rowIndex:int;
var colIndex:int;
var columnCount:int = listItems[0].length - offscreenExtraColumnsLeft - offscreenExtraColumnsRight;
var numCols:int = ((maxColumns > 0) && (direction != TileBaseDirection.VERTICAL)) ? maxColumns : columnCount;
var partialRow:int = displayingPartialRow() ? 1 : 0;
var partialColumn:int = displayingPartialColumn() ? 1 : 0;
if (!collection)
return;
showCaret = true;
switch (code)
{
case Keyboard.LEFT:
{
if (caretIndex > 0)
{
if (direction == TileBaseDirection.HORIZONTAL)
{
--caretIndex;
}
else
{
rowIndex = indexToRow(caretIndex);
colIndex = indexToColumn(caretIndex);
if (colIndex == 0)
{
rowIndex--;
colIndex = lastColumnInRow(rowIndex);
}
else
{
colIndex--;
}
caretIndex = Math.min(indicesToIndex(rowIndex, colIndex), collection.length - 1);
}
rowIndex = indexToRow(caretIndex);
colIndex = indexToColumn(caretIndex);
if (direction == TileBaseDirection.HORIZONTAL)
{
// scroll up if we need to
if (rowIndex < verticalScrollPosition)
{
newVerticalScrollPosition = verticalScrollPosition - 1;
}
// wrap down if we need to
else if (rowIndex > verticalScrollPosition + rowCount - partialRow)
{
newVerticalScrollPosition = maxVerticalScrollPosition;
}
}
else
{
// scroll left if we need to
if (colIndex < horizontalScrollPosition)
{
newHorizontalScrollPosition = horizontalScrollPosition - 1;
}
// wrap right if we need to
else if (colIndex > horizontalScrollPosition + columnCount - 1 - partialColumn)
{
newHorizontalScrollPosition = maxHorizontalScrollPosition;
}
}
}
break;
}
case Keyboard.RIGHT:
{
if (caretIndex < collection.length - 1)
{
if (direction == TileBaseDirection.HORIZONTAL
|| caretIndex == -1)
{
++caretIndex;
}
else
{
rowIndex = indexToRow(caretIndex);
colIndex = indexToColumn(caretIndex);
if (colIndex == lastColumnInRow(rowIndex))
{
colIndex = 0;
rowIndex++;
}
else
{
colIndex++;
}
caretIndex = Math.min(indicesToIndex(rowIndex, colIndex), collection.length - 1);
}
rowIndex = indexToRow(caretIndex);
colIndex = indexToColumn(caretIndex);
if (direction == TileBaseDirection.HORIZONTAL)
{
if (rowIndex >= verticalScrollPosition + rowCount - partialRow &&
verticalScrollPosition < maxVerticalScrollPosition)
{
newVerticalScrollPosition = verticalScrollPosition + 1;
}
if (rowIndex < verticalScrollPosition)
{
newVerticalScrollPosition = rowIndex;
}
}
else
{
if (colIndex >= horizontalScrollPosition + columnCount - partialColumn &&
horizontalScrollPosition < maxHorizontalScrollPosition)
{
newHorizontalScrollPosition = horizontalScrollPosition + 1;
}
if (colIndex < horizontalScrollPosition)
{
newHorizontalScrollPosition = colIndex;
}
}
}
break;
}
case Keyboard.PAGE_UP:
{
if (caretIndex < 0)
caretIndex = scrollPositionToIndex(horizontalScrollPosition, verticalScrollPosition);
rowIndex = indexToRow(caretIndex);
colIndex = indexToColumn(caretIndex);
if (colIndex > 0)
{
newHorizontalScrollPosition = colIndex = Math.max(horizontalScrollPosition - (columnCount - partialColumn), 0);
caretIndex = indicesToIndex(rowIndex, colIndex);
}
break;
}
case Keyboard.PAGE_DOWN:
{
if (caretIndex < 0)
caretIndex = scrollPositionToIndex(horizontalScrollPosition, verticalScrollPosition);
rowIndex = indexToRow(caretIndex);
colIndex = indexToColumn(caretIndex);
if (colIndex < maxHorizontalScrollPosition)
{
colIndex = Math.min(horizontalScrollPosition + columnCount - partialColumn, indexToColumn(collection.length - 1));
if (colIndex > horizontalScrollPosition)
newHorizontalScrollPosition = Math.min(colIndex,maxHorizontalScrollPosition);
caretIndex = indicesToIndex(rowIndex, colIndex);
}
break;
}
case Keyboard.HOME:
{
if (collection.length)
{
caretIndex = 0;
newHorizontalScrollPosition = 0;
newVerticalScrollPosition = 0;
}
break;
}
case Keyboard.END:
{
if (caretIndex < collection.length)
{
caretIndex = collection.length - 1;
newHorizontalScrollPosition = maxHorizontalScrollPosition;
newVerticalScrollPosition = maxVerticalScrollPosition;
}
break;
}
}
var scrollEvent:ScrollEvent;
if (!isNaN(newVerticalScrollPosition))
{
if (newVerticalScrollPosition != verticalScrollPosition)
{
scrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
scrollEvent.detail = ScrollEventDetail.THUMB_POSITION;
scrollEvent.direction = ScrollEventDirection.VERTICAL;
scrollEvent.delta = newVerticalScrollPosition - verticalScrollPosition;
scrollEvent.position = newVerticalScrollPosition;
verticalScrollPosition = newVerticalScrollPosition;
dispatchEvent(scrollEvent);
}
}
if (iteratorValid)
{
if (!isNaN(newHorizontalScrollPosition))
{
if (newHorizontalScrollPosition != horizontalScrollPosition)
{
scrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
scrollEvent.detail = ScrollEventDetail.THUMB_POSITION;
scrollEvent.direction = ScrollEventDirection.HORIZONTAL;
scrollEvent.delta = newHorizontalScrollPosition - horizontalScrollPosition;
scrollEvent.position = newHorizontalScrollPosition;
horizontalScrollPosition = newHorizontalScrollPosition;
dispatchEvent(scrollEvent);
}
}
}
if (!iteratorValid)
{
keySelectionPending = true;
return;
}
bShiftKey = shiftKey;
bCtrlKey = ctrlKey;
lastKey = code;
finishKeySelection();
}
private function displayingPartialRow():Boolean
{
var row:Array = listItems[listItems.length - 1 - offscreenExtraRowsBottom];
if (row && row.length > 0)
{
var r:IListItemRenderer = row[0];
// ignore partial row
if (!r || (r.y + r.height > (listContent.heightExcludingOffsets - listContent.topOffset)))
return true;
}
return false;
}
private function displayingPartialColumn():Boolean
{
if (listItems[0] && (listItems[0].length > 0))
{
var r:IListItemRenderer = listItems[0][listItems[0].length - 1 - offscreenExtraColumnsRight];
if (r && r.x + r.width > listContent.widthExcludingOffsets - listContent.leftOffset)
return true;
}
return false;
}
/**
* @private
*/
override protected function finishKeySelection():void
{
var uid:String;
var bSelChanged:Boolean = false;
var rowIndex:int;
var colIndex:int;
var listItem:IListItemRenderer;
if (caretIndex < 0)
return;
rowIndex = indexToRow(caretIndex);
colIndex = indexToColumn(caretIndex);
listItem = listItems[rowIndex - verticalScrollPosition + offscreenExtraRowsTop][colIndex - horizontalScrollPosition + offscreenExtraColumnsLeft];
if (!bCtrlKey)
{
selectItem(listItem, bShiftKey, bCtrlKey);
bSelChanged = true;
}
if (bCtrlKey)
{
//tkr - is this correct to assign this to our UID?
uid = itemToUID(listItem.data);
drawItem(visibleData[uid], selectedData[uid] != null, false, true);
}
if (bSelChanged)
{
var evt:ListEvent = new ListEvent(ListEvent.CHANGE);
evt.itemRenderer = listItem;
evt.rowIndex = rowIndex;
evt.columnIndex = colIndex;
dispatchEvent(evt);
}
}
/**
* @private
*/
override public function itemRendererToIndex(item:IListItemRenderer):int
{
var uid:String;
// perhaps wrap this in function in ListBase
if (runningDataEffect)
uid = itemToUID(dataItemWrappersByRenderer[item]);
else
uid = itemToUID(item.data);
var n:int = listItems.length;
for (var i:int = 0; i < listItems.length; i++)
{
var m:int = listItems[i].length;
for (var j:int = 0; j < m; j++)
{
if (listItems[i][j] && rowMap[listItems[i][j].name].uid == uid)
{
if (direction == TileBaseDirection.VERTICAL)
return (j + horizontalScrollPosition - offscreenExtraColumnsLeft) * Math.max(maxRows, rowCount) + i;
else
return (i + verticalScrollPosition - offscreenExtraRowsTop) * Math.max(maxColumns, columnCount) + j;
}
}
}
return -1;
}
/**
* @private
*/
override public function indexToItemRenderer(index:int):IListItemRenderer
{
// XXarielb: theoretically these lock variables mean something
// right now they're always 0, so we're not quite sure what to
// do with them. come back an revisit
var rowIndex:int = indexToRow(index);
if (rowIndex < verticalScrollPosition ||
rowIndex >= verticalScrollPosition + rowCount)
{
return null;
}
var colIndex:int = indexToColumn(index);
if (colIndex < horizontalScrollPosition ||
colIndex >= horizontalScrollPosition + columnCount)
{
return null;
}
return listItems[rowIndex - verticalScrollPosition]
[colIndex - horizontalScrollPosition];
}
/**
* @private
*/
override public function calculateDropIndex(event:DragEvent = null):int
{
if (event)
{
var item:IListItemRenderer;
var pt:Point = new Point(event.localX, event.localY);
pt = DisplayObject(event.target).localToGlobal(pt);
pt = listContent.globalToLocal(pt);
var rc:int = listItems.length;
for (var i:int = 0; i < rc; i++)
{
if (rowInfo[i].y <= pt.y && pt.y < rowInfo[i].y + rowInfo[i].height)
{
var cc:int = listItems[i].length;
for (var j:int = 0; j < cc; j++)
{
if (listItems[i][j] && listItems[i][j].x <= pt.x
&& pt.x < listItems[i][j].x + listItems[i][j].width)
{
item = listItems[i][j];
if (!DisplayObject(item).visible)
item = null;
break;
}
}
break;
}
}
if (item)
lastDropIndex = itemRendererToIndex(item);
else
lastDropIndex = collection ? collection.length : 0;
}
return lastDropIndex;
}
/**
* @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;
if (direction == TileBaseDirection.HORIZONTAL)
{
dropIndicator.setActualSize(rowHeight - 4, 4);
DisplayObject(dropIndicator).rotation = 90;
}
else
dropIndicator.setActualSize(columnWidth - 4, 4);
dropIndicator.visible = true;
listContent.addChild(DisplayObject(dropIndicator));
if (collection)
dragScrollingInterval = setInterval(dragScroll, 15);
}
var dropIndex:int = calculateDropIndex(event);
var rowNum:int = indexToRow(dropIndex);
var colNum:int = indexToColumn(dropIndex);
rowNum -= verticalScrollPosition - offscreenExtraRowsTop;
colNum -= horizontalScrollPosition - offscreenExtraColumnsLeft;
var rc:Number = listItems.length;
if (rowNum >= rc)
rowNum = rc - 1;
var cc:Number = rc ? listItems[0].length : 0;
if (colNum > cc)
colNum = cc;
dropIndicator.x = cc && listItems[rowNum].length && listItems[rowNum][colNum] ? listItems[rowNum][colNum].x : colNum * columnWidth;
dropIndicator.y = rc && listItems[rowNum].length && listItems[rowNum][0] ? listItems[rowNum][0].y : rowNum * rowHeight;
}
/**
* @private
*/
override public function measureWidthOfItems(index:int = -1,
count:int = 0):Number
{
var item:IListItemRenderer;
var w:Number;
var rowData:ListData;
var needSize:Boolean = false;
if (collection && collection.length)
{
var wrappedData:Object = iterator.current;
var data:Object = (wrappedData is ItemWrapper) ? wrappedData.data : wrappedData;
if (!measuringObjects)
measuringObjects = new Dictionary(true);
var factory:IFactory = getItemRendererFactory(data);
item = measuringObjects[factory];
if (!item)
{
item = getMeasuringRenderer(data);
needSize = true;
}
rowData = ListData(makeListData(data, uid, 0, 0));
if (item is IDropInListItemRenderer)
IDropInListItemRenderer(item).listData = data ? rowData : null;
item.data = data;
UIComponentGlobals.layoutManager.validateClient(item, true);
w = item.getExplicitOrMeasuredWidth();
if (needSize)
{
item.setActualSize(w, item.getExplicitOrMeasuredHeight());
needSize = false;
}
}
if (isNaN(w) || w == 0)
w = 50;
return w * count;
}
/**
* @private
*/
mx_internal function getMeasuringRenderer(data:Object):IListItemRenderer
{
var item:IListItemRenderer;
if (!measuringObjects)
measuringObjects = new Dictionary(true);
var factory:IFactory = getItemRendererFactory(data);
item = measuringObjects[factory];
if (!item)
{
item = createItemRenderer(data);
item.owner = this;
item.name = "hiddenItem";
item.visible = false;
item.styleName = listContent;
listContent.addChild(DisplayObject(item));
measuringObjects[factory] = item;
}
return item;
}
/**
* @private
*/
mx_internal function purgeMeasuringRenderers():void
{
var item:IListItemRenderer;
for each (item in measuringObjects)
if (item.parent)
item.parent.removeChild(DisplayObject(item));
if (!measuringObjects)
measuringObjects = new Dictionary(true);
}
/**
* @private
*/
override public function set itemRenderer(value:IFactory):void
{
super.itemRenderer = value;
purgeMeasuringRenderers();
}
/**
* Get the appropriate renderer, using the default renderer if none is specified.
*
* @param data The renderer's data structure.
*
* @return The item renderer.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override public function createItemRenderer(data:Object):IListItemRenderer
{
var factory:IFactory;
// get the factory for the data
factory = getItemRendererFactory(data);
if (!factory)
{
if (!data)
factory = nullItemRenderer;
if (!factory)
factory = itemRenderer;
}
var renderer:IListItemRenderer;
// if it is the default column factory, see if
// the freeItemRenderers table has a free one
if (factory == itemRenderer)
{
if (freeItemRenderers && freeItemRenderers.length && freeItemRenderersByFactory[factory])
{
renderer = freeItemRenderers.pop();
delete freeItemRenderersByFactory[factory][renderer];
}
}
else if (freeItemRenderersByFactory)
{
// other re-usable renderers are in the FactoryMap
var d:Dictionary = freeItemRenderersByFactory[factory];
if (d)
{
for (var p:* in d)
{
renderer = IListItemRenderer(p);
delete d[p];
break;
}
}
}
if (!renderer)
{
renderer = factory.newInstance();
renderer.styleName = this;
factoryMap[renderer] = factory;
}
renderer.owner = this;
return renderer;
}
/**
* @private
*/
mx_internal function setupRendererFromData(item:IListItemRenderer, data:Object):void
{
var rowData:ListData = ListData(makeListData(data, itemToUID(data), 0, 0));
if (item is IDropInListItemRenderer)
IDropInListItemRenderer(item).listData = data ? rowData : null;
item.data = data;
UIComponentGlobals.layoutManager.validateClient(item, true);
}
/**
* @private
*/
override public function measureHeightOfItems(index:int = -1,
count:int = 0):Number
{
var h:Number;
var needSize:Boolean = false;
if (collection && collection.length)
{
var wrappedData:Object = iterator.current;
var data:Object = (wrappedData is ItemWrapper) ? wrappedData.data : wrappedData;
var factory:IFactory = getItemRendererFactory(data);
var item:IListItemRenderer = measuringObjects[factory];
if (item == null)
{
item = getMeasuringRenderer(data);
needSize = true;
}
setupRendererFromData(item, data);
h = item.getExplicitOrMeasuredHeight();
if (needSize)
{
item.setActualSize(item.getExplicitOrMeasuredWidth(), h);
needSize = false;
}
}
if (isNaN(h) || h == 0)
h = 50;
var paddingTop:Number = getStyle("paddingTop");
var paddingBottom:Number = getStyle("paddingBottom");
h += paddingTop + paddingBottom;
return h * count;
}
/**
* @private
*/
override protected function scrollPositionToIndex(horizontalScrollPosition:int, verticalScrollPosition:int):int
{
if (iterator)
{
var startIndex:int;
if (direction == TileBaseDirection.HORIZONTAL)
startIndex = verticalScrollPosition * columnCount + horizontalScrollPosition;
else
startIndex = horizontalScrollPosition * rowCount + verticalScrollPosition;
return startIndex;
}
return -1;
}
/**
* @private
*/
override protected function get dragImageOffsets():Point
{
var pt:Point = new Point(8192, 8192);
var found:Boolean = false;
var rC:int = listItems.length;
for (var s:String in visibleData)
{
if (selectedData[s])
{
pt.x = Math.min(pt.x, visibleData[s].x);
pt.y = Math.min(pt.y, visibleData[s].y);
found = true;
}
}
if (found)
return pt;
return new Point(0, 0);
}
/**
* @private
*
* see ListBase.as
*/
override mx_internal function addClipMask(layoutChanged:Boolean):void
{
}
/**
* @private
*
* Undo the effects of the addClipMask function (above)
*/
override mx_internal function removeClipMask():void
{
}
/**
* @private
*/
override mx_internal function adjustOffscreenRowsAndColumns():void
{
if (direction == TileBaseDirection.VERTICAL)
{
offscreenExtraRows = 0;
offscreenExtraColumns = offscreenExtraRowsOrColumns;
}
else
{
offscreenExtraColumns = 0;
offscreenExtraRows = offscreenExtraRowsOrColumns;
}
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Creates a new ListData 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.
*
* @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):BaseListData
{
return new ListData(itemToLabel(data), itemToIcon(data), labelField, uid,
this, rowNum, columnNum);
}
/**
* @private
* Assumes horizontal.
*/
private function lastRowInColumn(index:int):int
{
var numCols:int = maxColumns > 0 ? maxColumns : columnCount;
var numRows:int = Math.floor((collection.length - 1) / numCols);
if (index * numRows > collection.length)
numRows--;
return numRows;
}
/**
* @private
* Assumes vertical.
*/
private function lastColumnInRow(index:int):int
{
var numRows:int = maxRows > 0 ? maxRows : rowCount;
var numCols:int = Math.floor((collection.length - 1) / numRows);
if (indicesToIndex(index, numCols) >= collection.length)
numCols--;
return numCols;
}
/**
* @private
*/
override protected function indexToRow(index:int):int
{
if (direction == TileBaseDirection.VERTICAL)
{
var numRows:int = maxRows > 0 ? maxRows : rowCount;
return index % numRows;
}
var numCols:int = maxColumns > 0 ? maxColumns : columnCount;
return Math.floor(index / numCols);
}
/**
* @private
*/
override protected function indexToColumn(index:int):int
{
if (direction == TileBaseDirection.VERTICAL)
{
var numRows:int = maxRows > 0 ? maxRows : rowCount;
return Math.floor(index / numRows);
}
var numCols:int = maxColumns > 0 ? maxColumns : columnCount;
return index % numCols;
}
/**
* @private
*/
override public function indicesToIndex(rowIndex:int, columnIndex:int):int
{
if (direction == TileBaseDirection.VERTICAL)
{
var numRows:int = maxRows > 0 ? maxRows : rowCount;
return columnIndex * numRows + rowIndex;
}
var numCols:int = maxColumns > 0 ? maxColumns : columnCount;
return rowIndex * numCols + columnIndex;
}
//--------------------------------------------------------------------------
//
// Overridden event handlers
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function collectionChangeHandler(event:Event):void
{
if (event is CollectionEvent)
{
var ce:CollectionEvent = CollectionEvent(event);
if (ce.location == 0 || ce.kind == CollectionEventKind.REFRESH)
{
itemsNeedMeasurement = true;
invalidateProperties();
}
// may not want to do this if we are running data effect
if (ce.kind == CollectionEventKind.REMOVE)
{
// oldIndex does not take into account offscreen rows & columns...scroll
// position has to be adjusted if items are removed before the first
// *visible* item (not taking into account offscreen rows/columns).
var oldIndex:int = indicesToIndex(verticalScrollPosition,horizontalScrollPosition);
// ListBase handles adjusting the iterator for listType = "vertical".
// It should probably handle this case as well, in a more general manner.
// There's more complexity for TileList, in that removing n items
// before the first visible item where n is not a multiple of the row/column
// size might actually result in a visual shift in scroll position (in order
// to keep the first onscreen item onscreen).
// Meanwhile...we expect full layout after this, but we need to make sure
// that the scroll positions and the iterator are at the right place.
if (ce.location < oldIndex)
{
oldIndex -= ce.items.length;
super.collectionChangeHandler(event);
var newOffscreenRowsTop:int = 0;
var newOffscreenColumnsLeft:int = 0;
if (direction == TileBaseDirection.HORIZONTAL)
{
super.verticalScrollPosition = indexToRow(oldIndex);
newOffscreenRowsTop = Math.min(offscreenExtraRows / 2, verticalScrollPosition);
}
else
{
super.horizontalScrollPosition = indexToColumn(oldIndex);
newOffscreenColumnsLeft = Math.min(offscreenExtraColumns / 2, horizontalScrollPosition);
}
var index:int = scrollPositionToIndex(horizontalScrollPosition - newOffscreenColumnsLeft,
verticalScrollPosition - newOffscreenRowsTop);
seekPositionSafely(index);
return;
}
}
}
super.collectionChangeHandler(event);
}
/**
* @private
*/
mx_internal override function reconstructDataFromListItems():Array
{
// super behavior is fine for horizontal layout
if (direction == TileBaseDirection.HORIZONTAL || !listItems)
return (super.reconstructDataFromListItems());
// might make sense to ignore offscreen columns here
var items:Array = [];
if (listItems.length > 0)
{
for (var j:int = 0; j < listItems[0].length; j++)
{
for (var i:int = 0; i < listItems.length; i++)
{
if (listItems[i] && listItems[i].length > j)
{
var renderer:IListItemRenderer = listItems[i][j] as IListItemRenderer;
var data:Object;
if (renderer)
{
data = renderer.data;
items.push(data);
}
}
}
}
}
return items;
}
/**
* @private
*/
override protected function commitProperties():void
{
super.commitProperties();
if (itemsNeedMeasurement)
{
itemsNeedMeasurement = false;
if (isNaN(explicitRowHeight))
setRowHeight(measureHeightOfItems(0, 1));
if (isNaN(explicitColumnWidth))
setColumnWidth(measureWidthOfItems(0, 1));
}
}
/**
* @private
*/
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
// setup the tile sizes before calling the base class
if (explicitColumnCount > 0 && isNaN(explicitColumnWidth))
{
// enforce that we can see the right number of columns
// even if we squeeze the columns
setColumnWidth(Math.floor((width - viewMetrics.left - viewMetrics.right) / explicitColumnCount));
}
if (explicitRowCount > 0 && isNaN(explicitRowHeight))
{
// enforce that we can see the right number of columns
// even if we squeeze the columns
setRowHeight(Math.floor((height - viewMetrics.top - viewMetrics.bottom) / explicitRowCount));
}
super.updateDisplayList(unscaledWidth, unscaledHeight);
drawTileBackgrounds();
}
/**
* Draws the backgrounds, if any, behind all of the tiles.
* This implementation makes a Sprite names "tileBGs" if
* it doesn't exist, adds it to the back
* of the z-order in the <code>listContent</code>, and
* calls <code>drawTileBackground()</code> for each visible
* tile.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function drawTileBackgrounds():void
{
var tileBGs:Sprite = Sprite(listContent.getChildByName("tileBGs"));
if (!tileBGs)
{
tileBGs = new FlexSprite();
tileBGs.mouseEnabled = false;
tileBGs.name = "tileBGs";
listContent.addChildAt(tileBGs, 0)
}
var colors:Array;
var colorsStyle:Object = getStyle("alternatingItemColors");
if (colorsStyle)
colors = (colorsStyle is Array) ? (colorsStyle as Array) : [colorsStyle];
if (!colors || colors.length == 0)
{
while (tileBGs.numChildren > n)
{
tileBGs.removeChildAt(tileBGs.numChildren - 1);
}
return;
}
styleManager.getColorNames(colors);
var curItem:int = 0;
for (var i:int = 0; i < rowCount; i++)
{
for (var j:int = 0; j < columnCount; j++)
{
// Height is usually as tall is the items in the row,
// but not if it would extend below the bottom of listContent.
var height:Number = (i < rowCount - 1) ? rowHeight :
Math.min(rowHeight,
listContent.height - ((rowCount - 1) * rowHeight));
var width:Number = (j < columnCount - 1) ? columnWidth :
Math.min(columnWidth,
listContent.width - ((columnCount - 1) * columnWidth));
var item:IListItemRenderer = (listItems[i] ? listItems[i][j] : null);
var actualIndex:int = (verticalScrollPosition + i) * columnCount + (horizontalScrollPosition + j);
var bg:DisplayObject = drawTileBackground(tileBGs, i, j, width, height, colors[actualIndex % colors.length], item);
bg.y = i * rowHeight;
bg.x = j * columnWidth;
}
}
var n:int = rowCount * columnCount;
while (tileBGs.numChildren > n)
{
tileBGs.removeChildAt(tileBGs.numChildren - 1);
}
}
/**
* Draws the background for an individual tile.
* Takes a Sprite object, applies the background dimensions
* and color, and returns the sprite with the values applied.
*
* @param s The Sprite that contains the individual tile backgrounds.
* @param rowIndex The index of the row that contains the tile.
* @param columnIndex The index of the column that contains the tile.
* @param width The width of the background.
* @param height The height of the background.
* @param color The fill color for the background.
* @param item The item renderer for the tile.
*
* @return The background Sprite.
*
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function drawTileBackground(s:Sprite, rowIndex:int, columnIndex:int, width:Number, height:Number,
color:uint, item:IListItemRenderer):DisplayObject
{
// trace("drawTileBackground " + rowIndex + " " + col);
var tileBGIndex:int = rowIndex * columnCount + columnIndex;
var bg:Shape;
if (tileBGIndex < s.numChildren)
{
bg = Shape(s.getChildAt(tileBGIndex));
}
else
{
bg = new FlexShape();
bg.name = "tileBackground";
s.addChild(bg);
}
var g:Graphics = bg.graphics;
g.clear();
g.beginFill(color, getStyle("backgroundAlpha"));
g.drawRect(0, 0, width, height);
g.endFill();
return bg;
}
/**
* @private
*/
override protected function keyDownHandler(event:KeyboardEvent):void
{
var selectedListItem:IListItemRenderer;
if (!iteratorValid) return;
if (!collection) return;
// If rtl layout, need to swap LEFT and RIGHT so correct action
// is done.
var keyCode:uint = mapKeycodeForLayoutDirection(event);
switch (keyCode)
{
case Keyboard.UP:
case Keyboard.DOWN:
{
moveSelectionVertically(keyCode,
event.shiftKey,
event.ctrlKey);
event.stopPropagation();
break;
}
case Keyboard.LEFT:
case Keyboard.RIGHT:
{
moveSelectionHorizontally(keyCode,
event.shiftKey,
event.ctrlKey);
event.stopPropagation();
break;
}
case Keyboard.END:
case Keyboard.HOME:
case Keyboard.PAGE_UP:
case Keyboard.PAGE_DOWN:
{
if (direction == TileBaseDirection.VERTICAL)
{
moveSelectionHorizontally(keyCode,
event.shiftKey,
event.ctrlKey);
}
else
{
moveSelectionVertically(keyCode,
event.shiftKey,
event.ctrlKey);
}
event.stopPropagation();
break;
}
case Keyboard.SPACE:
{
if (caretIndex < 0)
break;
var rowIndex:int = indexToRow(caretIndex);
var colIndex:int = indexToColumn(caretIndex);
selectedListItem = listItems
[rowIndex - verticalScrollPosition]
[colIndex - horizontalScrollPosition];
selectItem(selectedListItem, event.shiftKey, event.ctrlKey);
break;
}
default:
{
if (findKey(keyCode))
event.stopPropagation();
}
}
}
/**
* @private
*/
override protected function scrollHandler(event:Event):void
{
// TextField.scroll bubbles so you might see it here
if (event is ScrollEvent)
{
if (!liveScrolling &&
ScrollEvent(event).detail == ScrollEventDetail.THUMB_TRACK)
{
return;
}
var scrollBar:ScrollBar = ScrollBar(event.target);
var pos:Number = scrollBar.scrollPosition;
var delta:int;
var startIndex:int;
var o:EdgeMetrics;
var bookmark:CursorBookmark;
if (scrollBar == verticalScrollBar)
{
delta = pos - verticalScrollPosition;
super.scrollHandler(event);
if (Math.abs(delta) >= listItems.length || !iteratorValid)
{
startIndex = indicesToIndex(pos, horizontalScrollPosition);
try
{
iterator.seek(CursorBookmark.FIRST, startIndex);
if (!iteratorValid)
{
iteratorValid = true;
lastSeekPending = null;
}
}
catch(e:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, startIndex);
e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
// trace("IPE in UpdateDisplayList");
iteratorValid = false;
// don't do anything, we'll repaint when the data arrives
}
bookmark = iterator.bookmark;
//if we scrolled more than the number of scrollable rows
clearIndicators();
clearVisibleData();
makeRowsAndColumns(0, 0, listContent.width, listContent.height, 0, 0);
iterator.seek(bookmark, 0);
drawRowBackgrounds();
}
else if (delta != 0)
{
scrollVertically(pos, Math.abs(delta), delta > 0);
}
}
else
{
delta = pos - horizontalScrollPosition;
super.scrollHandler(event);
if (Math.abs(delta) >= listItems[0].length || !iteratorValid)
{
startIndex = indicesToIndex(verticalScrollPosition, pos);
try
{
iterator.seek(CursorBookmark.FIRST, startIndex);
if (!iteratorValid)
{
iteratorValid = true;
lastSeekPending = null;
}
}
catch(e:ItemPendingError)
{
lastSeekPending = new ListBaseSeekPending(CursorBookmark.FIRST, startIndex);
e.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler,
lastSeekPending));
// trace("IPE in UpdateDisplayList");
iteratorValid = false;
// don't do anything, we'll repaint when the data arrives
}
bookmark = iterator.bookmark;
//if we scrolled more than the number of scrollable rows
clearIndicators();
clearVisibleData();
makeRowsAndColumns(0, 0, listContent.width, listContent.height, 0, 0);
iterator.seek(bookmark, 0);
drawRowBackgrounds();
}
else if (delta != 0)
{
scrollHorizontally(pos, Math.abs(delta), delta > 0);
}
}
}
}
/**
* @private
*/
override public function scrollToIndex(index:int):Boolean
{
var newVPos:int;
var newHPos:int;
var firstIndex:int = scrollPositionToIndex(horizontalScrollPosition, verticalScrollPosition);
var numItemsVisible:int = ((listItems.length - offscreenExtraRowsTop - offscreenExtraRowsBottom) *
(listItems[0].length - offscreenExtraColumnsLeft - offscreenExtraColumnsRight));
if (index >= firstIndex + numItemsVisible || index < firstIndex)
{
newVPos = Math.min(indexToRow(index), maxVerticalScrollPosition);
newHPos = Math.min(indexToColumn(index), maxHorizontalScrollPosition);
try
{
iterator.seek(CursorBookmark.FIRST, scrollPositionToIndex(horizontalScrollPosition, verticalScrollPosition));
super.horizontalScrollPosition = newHPos;
super.verticalScrollPosition = newVPos;
}
catch(e:ItemPendingError)
{
}
return true;
}
return false;
}
/**
* Called from the <code>updateDisplayList()</code> method to adjust the size and position of
* listContent.
*
* @param unscaledWidth The width of the listContent before any external scaling is applied.
*
* @param unscaledHeight The height of the listContent before any external scaling is applied.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override protected function adjustListContent(unscaledWidth:Number = -1,
unscaledHeight:Number = -1):void
{
super.adjustListContent(unscaledWidth, unscaledHeight);
if (!collection)
return;
var partial:Boolean;
var index:int;
var numRows:int;
var numCols:int;
var numItems:int = collection.length;
if (direction == TileBaseDirection.VERTICAL)
{
numRows = maxRows > 0 ? maxRows : Math.max(Math.floor(listContent.heightExcludingOffsets / rowHeight), 1);
numCols = Math.max(Math.ceil(listContent.widthExcludingOffsets / columnWidth), 1);
if (numRows != lastRowCount)
{
partial = (listContent.widthExcludingOffsets / columnWidth) != Math.ceil(listContent.widthExcludingOffsets / columnWidth);
var maxHSP:int = Math.max(Math.ceil(numItems / numRows) - numCols, 0);
if (partial)
maxHSP++;
if (horizontalScrollPosition > maxHSP)
$horizontalScrollPosition = maxHSP;
setRowCount(numRows);
setColumnCount(numCols);
index = scrollPositionToIndex(Math.max(0, horizontalScrollPosition - offscreenExtraColumnsLeft),
verticalScrollPosition);
seekPositionSafely(index);
}
lastRowCount = numRows;
}
else // horizontal
{
numCols = maxColumns > 0 ? maxColumns : Math.max(Math.floor((listContent.widthExcludingOffsets)/ columnWidth), 1);
numRows = Math.max(Math.ceil(listContent.heightExcludingOffsets / rowHeight), 1);
if (numCols != lastColumnCount)
{
partial = (listContent.heightExcludingOffsets / rowHeight) != Math.ceil(listContent.heightExcludingOffsets / rowHeight);
var maxVSP:int = Math.max(Math.ceil(numItems / numCols) - numRows, 0);
if (partial)
maxVSP++;
if (verticalScrollPosition > maxVSP)
$verticalScrollPosition = maxVSP;
setRowCount(numRows);
setColumnCount(numCols);
index = scrollPositionToIndex(horizontalScrollPosition, Math.max(0,verticalScrollPosition-offscreenExtraRowsTop));
seekPositionSafely(index);
}
lastColumnCount = numCols;
}
}
}
}