| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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; |
| } |
| |
| } |
| |
| } |
| |
| } |