//////////////////////////////////////////////////////////////////////////////// // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package spark.components.gridClasses { import flash.geom.Rectangle; import mx.collections.ICollectionView; import mx.collections.IList; import mx.core.mx_internal; import mx.events.CollectionEvent; import mx.events.CollectionEventKind; import spark.components.Grid; import spark.components.gridClasses.CellPosition; use namespace mx_internal; [ExcludeClass] /** * Use the GridSelection class to track a Grid control's * selectionMode property and its set of selected rows, columns, or cells. * The selected elements are defined by integer indices. * * @see spark.components.Grid * @see spark.components.Grid#columns * @see spark.components.Grid#dataProvider * @see spark.components.gridClasses.GridSelectionMode * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public class GridSelection { include "../../core/Version.as"; //-------------------------------------------------------------------------- // // Class variables // //-------------------------------------------------------------------------- /** * @private * List of ordered selected/de-selected cell regions used to represent * either row or cell selections depending on the selection mode. * (For row selection, a row region will be in column 0 and column count * will be 1.) * * This is the list of cell regions that have * been added (isAdd==true) and removed (isAdd==false). * * Internally, regionsContainCell should be used to determine if a cell/row * is in the selection. */ private var cellRegions:Vector. = new Vector.(); /** * @private * If preserveSelection, and selectionMode is "singleRow" or "singleCell", * cache the dataItem that goes with the selection so we can find the * index of the selection after a collection refresh event. */ private var selectedItem:Object; /** * @private * True if in a collection handler. If this is the case, there is no * need to validate the index/indices. In the case of * CollectionEventKind.REMOVE, when the last item selected and it is * removed from the collection, the validate would fail since the index is * now out of range and the item would incorrectly remain in the selection. */ private var inCollectionHandler:Boolean; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function GridSelection() { super(); } //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // grid //---------------------------------- private var _grid:Grid; /** * The grid control associated with this object. * This property should only be set once. * *

For the Spark DataGrid, this value is initialized by * the DataGrid.partAdded() method.

* * @default null * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get grid():Grid { return _grid; } /** * @private */ public function set grid(value:Grid):void { _grid = value; } //---------------------------------- // preserveSelection //---------------------------------- private var _preserveSelection:Boolean = true; /** * If true, and selectionMode is * GridSelectionMode.SINGLE_ROW or * GridSelectionMode.SINGLE_CELL, then selection is * preserved when the dataProvider refreshes its collection, * and the selected item is contained in the collection after * the refresh. * * @default true * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get preserveSelection():Boolean { return _preserveSelection; } /** * @private */ public function set preserveSelection(value:Boolean):void { if (_preserveSelection == value) return; _preserveSelection = value; selectedItem = null; // If preserving the selection and there is currently a selection, // save the corresponding data item. if (_preserveSelection && (selectionMode == GridSelectionMode.SINGLE_ROW || selectionMode == GridSelectionMode.SINGLE_CELL) && selectionLength > 0) { if (selectionMode == GridSelectionMode.SINGLE_ROW) selectedItem = grid.dataProvider.getItemAt(allRows()[0]); else // SINGLE_CELL selectedItem = grid.dataProvider.getItemAt(allCells()[0].rowIndex); } } //---------------------------------- // requireSelection //---------------------------------- private var _requireSelection:Boolean = false; /** * If true, a data item must always be selected in the * control as long as there is at least one item in * dataProvider and one column in columns. * * @default false * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get requireSelection():Boolean { return _requireSelection; } /** * @private */ public function set requireSelection(value:Boolean):void { if (_requireSelection == value) return; _requireSelection = value; if (_requireSelection) ensureRequiredSelection(); } //---------------------------------- // selectionLength //---------------------------------- /** * @private * Cache the selectionLength. Only recalculate if selectionLength is -1. */ private var _selectionLength:int = 0; /** * The length of the selection. * *

If the selectionMode is either * GridSelectionMode.SINGLE_ROW or * GridSelectionMode.MULTIPLE_ROWS, contains the number of * selected rows. If a selected row has no columns whose * visible property is set to true it is still * included in the number of selected rows.

* *

If the selectionMode is either * GridSelectionMode.SINGLE_CELL or * GridSelectionMode.MULTIPLE_CELLS, contains the number of * selected cells. * If a selected cell is in a column which has its visible * property set to false after it is selected, the cell is included in * the number of selected cells.

* *

If the selectionMode is GridSelectionMode.NONE, * contains 0.

* * @default 0 * * @return Number of selected rows or cells. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get selectionLength():int { // Note: this assumes there are no duplicate cells in cellRegions - ie // 2 adds of the same cell without an intermediate delete. const isRows:Boolean = isRowSelectionMode(); if (_selectionLength < 0) { _selectionLength = 0; const cellRegionsLength:int = cellRegions.length; for (var i:int = 0; i < cellRegionsLength; i++) { var cr:CellRect = cellRegions[i]; const numSelected:int = isRows ? cr.height : cr.height * cr.width; if (cr.isAdd) _selectionLength += numSelected; else _selectionLength -= numSelected; } } return _selectionLength; } //---------------------------------- // selectionMode //---------------------------------- private var _selectionMode:String = GridSelectionMode.SINGLE_ROW; /** * The selection mode of the control. Possible values are: * GridSelectionMode.MULTIPLE_CELLS, * GridSelectionMode.MULTIPLE_ROWS, * GridSelectionMode.NONE, * GridSelectionMode.SINGLE_CELL, and * GridSelectionMode.SINGLE_ROW. * *

Changing the selectionMode causes the * current selection to be cleared.

* * @default GridSelectionMode.SINGLE_ROW * * @see spark.components.gridClasses.GridSelectionMode * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function get selectionMode():String { return _selectionMode; } /** * @private */ public function set selectionMode(value:String):void { if (value == _selectionMode) return; switch (value) { case GridSelectionMode.SINGLE_ROW: case GridSelectionMode.MULTIPLE_ROWS: case GridSelectionMode.SINGLE_CELL: case GridSelectionMode.MULTIPLE_CELLS: case GridSelectionMode.NONE: _selectionMode = value; removeAll(); break; } } /** * If the selectionMode is either GridSelectionMode.SINGLE_CELL or * GridSelectionMode.MULTIPLE_CELLS, returns a list of all the * selected cells. * * @return Vector of selected cell locations as row and column indices or, * if none are selected, a Vector of length 0. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function allCells():Vector. { var cells:Vector. = new Vector.; if (!isCellSelectionMode()) return cells; // Iterate over the selected cells region. const bounds:Rectangle = getCellRegionsBounds(); const left:int = bounds.left; const right:int = bounds.right; const bottom:int = bounds.bottom; for (var rowIndex:int = bounds.top; rowIndex < bottom; rowIndex++) { for (var columnIndex:int = left; columnIndex < right; columnIndex++) { if (regionsContainCell(rowIndex, columnIndex)) cells.push(new CellPosition(rowIndex, columnIndex)); } } return cells; } /** * If the selectionMode is either GridSelectionMode.SINGLE_ROW or * GridSelectionMode.MULTIPLE_ROWS, returns a list of all the * selected rows. * * @return Vector of selected rows as row indices or, if none, a Vector * of length 0. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function allRows():Vector. { if (!isRowSelectionMode()) return new Vector.(0, true); var rows:Vector. = new Vector.(); const bounds:Rectangle = getCellRegionsBounds(); const bottom:int = bounds.bottom; for (var rowIndex:int = bounds.top; rowIndex < bottom; rowIndex++) { // row is represented as cell in column 0 of the row if (regionsContainCell(rowIndex, 0)) rows.push(rowIndex); } return rows; } /** * If the selectionMode is GridSelectionMode.MULTIPLE_ROWS * selects all the rows in the grid and if selectionMode is * GridSelectionMode.MULTIPLE_CELLS, selects all the cells in * columns with visible set to true. * * @return true if the selection changed. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function selectAll():Boolean { const maxRows:int = getGridDataProviderLength(); if (selectionMode == GridSelectionMode.MULTIPLE_ROWS) { return setRows(0, maxRows); } else if (selectionMode == GridSelectionMode.MULTIPLE_CELLS) { const maxColumns:int = getGridColumnsLength(); return setCellRegion(0, 0, maxRows, maxColumns); } return false; } /** * Remove the current selection. * If requireSelection is true, * and the selectionMode is row-based, then row 0 * is selected. * If the selectionMode is cell-based, * then cell 0,0 is selected. * * @return true if the list of selected items has changed from the last call. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function removeAll():Boolean { var selectionChanged:Boolean = removeSelection(); return ensureRequiredSelection() || selectionChanged; } //---------------------------------- // Rows //---------------------------------- /** * If the selectionMode is either GridSelectionMode.SINGLE_ROW or * GridSelectionMode.MULTIPLE_ROWS, determines if the row is in * the current selection. * * @param rowIndex The 0-based row index. * * @return true if the row is in the selection * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function containsRow(rowIndex:int):Boolean { if (!validateIndex(rowIndex)) return false; return regionsContainCell(rowIndex, 0); } /** * If the selectionMode is GridSelectionMode.MULTIPLE_ROWS, * determines if the rows are in the current selection. * * @param rowIndex The 0-based row index. * * @return true if the rows are in the selection * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function containsRows(rowsIndices:Vector.):Boolean { if (!validateIndices(rowsIndices)) return false; for each (var rowIndex:int in rowsIndices) { if (!regionsContainCell(rowIndex, 0)) return false; } return true; } /** * If the selectionMode is either GridSelectionMode.SINGLE_ROW * or GridSelectionMode.MULTIPLE_ROWS, replaces the current * selection with the specified row. * * @param rowIndex The 0-based row index. * * @return true if no errors, or false if the * rowIndex is not a valid index in the control's dataProvider, * or if the selectionMode is not valid. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function setRow(rowIndex:int):Boolean { if (!validateIndex(rowIndex)) return false; internalSetCellRegion(rowIndex); return true; } /** * If the selectionMode is GridSelectionMode.MULTIPLE_ROWS, * adds the row to the selection. * * @param rowIndex The 0-based row index. * * @return true if no errors, or false if the * rowIndex is not a valid index in the * control's dataProvider, or the selectionMode is not valid. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function addRow(rowIndex:int):Boolean { if (!validateIndex(rowIndex)) return false; if (selectionMode != GridSelectionMode.MULTIPLE_ROWS) return false; internalAddCell(rowIndex); return true; } /** * If the selectionMode is either GridSelectionMode.SINGLE_ROW * or GridSelectionMode.MULTIPLE_ROWS, removes the row from * the selection. * * @param rowIndex The 0-based row index. * * @return true if no errors. * Returns false if the * rowIndex is not a valid index in the * control's dataProvider, * or if the selectionMode is not valid. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function removeRow(rowIndex:int):Boolean { if (!validateIndex(rowIndex) ) return false; if (requireSelection && containsRow(rowIndex) && selectionLength == 1) return false; internalRemoveCell(rowIndex); return true; } /** * If the selectionMode is GridSelectionMode.MULTIPLE_ROWS, * replaces the current selection with the rowCount rows starting at * rowIndex. * * @param rowIndex 0-based row index of the first row in the selection. * @param rowCount Number of rows in the selection. * * @return true if no errors. * Returns false if any of the indices are invalid, * if startRowIndex is not less than or equal to endRowIndex, * or if the selectionMode is not valid. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function setRows(rowIndex:int, rowCount:int):Boolean { if (!validateRowRegion(rowIndex, rowCount)) return false; internalSetCellRegion(rowIndex, 0, rowCount, 1); return true; } //---------------------------------- // Cells //---------------------------------- /** * If the selectionMode is either * GridSelectionMode.SINGLE_CELLS * or GridSelectionMode.MULTIPLE_CELLS, determines if the cell * is in the current selection. * * @param rowIndex The 0-based row index. * * @param columnsIndex The 0-based column index. * * @return true if the cell is in the selection. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function containsCell(rowIndex:int, columnIndex:int):Boolean { if (!validateCell(rowIndex, columnIndex)) return false; return regionsContainCell(rowIndex, columnIndex); } /** * If the selectionMode is * GridSelectionMode.MULTIPLE_CELLS, determines if all the * cells in the cell region are in the current selection. * * @param rowIndex The 0-based row index. * * @param columnsIndex The 0-based column index. * * @param rowCount The row height of the region. * * @param columnsCount The width of the cell region, in columns. * * @return true if the cells are in the selection. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function containsCellRegion(rowIndex:int, columnIndex:int, rowCount:int, columnCount:int):Boolean { if (!validateCellRegion(rowIndex, columnIndex, rowCount, columnCount)) return false; if (rowCount * columnCount > selectionLength) return false; const cellRegionsLength:int = cellRegions.length; if (cellRegionsLength == 0) return false; // Simple selection. if (cellRegionsLength == 1) { const cr:CellRect = cellRegions[0]; return (rowIndex >= cr.top && columnIndex >= cr.left && rowIndex + rowCount <= cr.bottom && columnIndex + columnCount <= cr.right); } // Not a simple selection so we're going to have to check each cell. const bottom:int = rowIndex + rowCount; const right:int = columnIndex + columnCount; for (var r:int = rowIndex; r < bottom; r++) { for (var c:int = columnIndex; c < right; c++) { if (!containsCell(r, c)) return false; } } return true; } /** * If the selectionMode is either GridSelectionMode.SINGLE_CELLS * or GridSelectionMode.MULTIPLE_CELLS, replaces the current * selection with the cell at the given location. The cell must be in * a visible column. * * @param rowIndex The 0-based row index. * * @param columnsIndex The 0-based column index. * * @return true no errors. * Returns false if * rowIndex is not a valid index in dataProvider, * if columnIndex is not a valid index in columns, * if the selectionMode is invalid or the column is not visible. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function setCell(rowIndex:int, columnIndex:int):Boolean { if (!validateCell(rowIndex, columnIndex)) return false; // columns and indexes validated above. const columnVisible:Boolean = isColumnVisible(columnIndex); if (columnVisible) internalSetCellRegion(rowIndex, columnIndex, 1, 1); return columnVisible; } /** * If the selectionMode is GridSelectionMode.MULTIPLE_CELLS, * adds the cell at the given location to the cell selection. The cell * must be in a visible column. * * @param rowIndex The 0-based row index. * * @param columnsIndex The 0-based column index. * * @return true no errors. * Returns false if * rowIndex is not a valid index in dataProvider, * if columnIndex is not a valid index in columns, * if the selectionMode is invalid or the column is not visible. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function addCell(rowIndex:int, columnIndex:int):Boolean { if (!validateCellRegion(rowIndex, columnIndex, 1, 1)) return false; // columns and indexes validated above. const columnVisible:Boolean = isColumnVisible(columnIndex); if (columnVisible) internalAddCell(rowIndex, columnIndex); return columnVisible; } /** * If the selectionMode is either GridSelectionMode.SINGLE_CELL * or GridSelectionMode.MULTIPLE_CELLS, removes the cell at the * given position from the cell selection. * * @param rowIndex The 0-based row index. * * @param columnsIndex The 0-based column index. * * @return true if no errors. * Returns false if rowIndex * is not a valid index in dataProvider, or if * columnIndex is not a valid index in columns, * or if the selectionMode is invalid. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function removeCell(rowIndex:int, columnIndex:int):Boolean { if (!validateCell(rowIndex, columnIndex)) return false; if (requireSelection && containsCell(rowIndex, columnIndex) && selectionLength == 1) return false; internalRemoveCell(rowIndex, columnIndex); return true; } /** * If the selectionMode is GridSelectionMode.MULTIPLE_CELLS, * replaces the current selection with the cells in the given cell region. * The origin of the cell region is the cell location specified by * rowIndex and columnIndex, the width is * columnCount and the height is rowCound. * *

Only cells in columns which are visible at the time of selection are * included in the selection.

* * @param rowIndex The 0-based row index of the origin. * * @param columnsIndex The 0-based column index of the origin. * * @param rowCount The row height of the cell region. * * @param columnsCount The column width of the cell region. * * @return true if no errors. * Returns false if * rowIndex is not a valid index in dataProvider, * if columnIndex is not a valid index in columns, * or if the selectionMode is invalid. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function setCellRegion(rowIndex:int, columnIndex:int, rowCount:uint, columnCount:uint):Boolean { if (!validateCellRegion(rowIndex, columnIndex, rowCount, columnCount)) return false; removeSelection(); var startColumnIndex:int = columnIndex; var curColumnCount:int = 0; const endColumnIndex:int = columnIndex + columnCount - 1; for (var i:int = columnIndex; i <= endColumnIndex; i++) { // columns and indexes validated above. const columnVisible:Boolean = isColumnVisible(i); if (columnVisible) { curColumnCount++; continue; } // This column isn't visible so commit the cell region. internalAddCellRegion(rowIndex, startColumnIndex, rowCount, curColumnCount); curColumnCount = 0; startColumnIndex = i + 1; } // If the last column(s) are not visible, need to commit the last // cell region. if (curColumnCount > 0) internalAddCellRegion(rowIndex, startColumnIndex, rowCount, curColumnCount); return true; } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * @private */ private function isRowSelectionMode():Boolean { const mode:String = selectionMode; return mode == GridSelectionMode.SINGLE_ROW || mode == GridSelectionMode.MULTIPLE_ROWS; } /** * @private */ private function isCellSelectionMode():Boolean { const mode:String = selectionMode; return mode == GridSelectionMode.SINGLE_CELL || mode == GridSelectionMode.MULTIPLE_CELLS; } /** * @private * Assumes columns is set and index is valid. */ private function isColumnVisible(columnIndex:int):Boolean { return GridColumn(grid.columns.getItemAt(columnIndex)).visible; } /** * @private */ private function getGridColumnsLength():uint { if (grid == null) return 0; const columns:IList = grid.columns; return (columns) ? columns.length : 0; } /** * @private */ private function getGridDataProviderLength():uint { if (grid == null) return 0; const dataProvider:IList = grid.dataProvider; return (dataProvider) ? dataProvider.length : 0; } /** * @private * True if the given cell is contained in the list of cell regions. */ private function regionsContainCell(rowIndex:int, columnIndex:int):Boolean { // Find the index of the last isAdd=true cell region that contains // row,columnIndex. const cellRegionsLength:int = cellRegions.length; var index:int = -1; for (var i:int = 0; i < cellRegionsLength; i++) { var cr:CellRect = cellRegions[i]; if (cr.isAdd && cr.containsCell(rowIndex, columnIndex)) index = i; } // Is there an isAdd=true CellRegion that contains the cell? if (index == -1) return false; // Starting with index, if any subsequent isAdd=false cell region // contains row,columnIndex return false. for (i = index + 1; i < cellRegionsLength; i++) { cr = cellRegions[i]; if (!cr.isAdd && cr.containsCell(rowIndex, columnIndex)) return false; } return true; } /** * @private * If requiredSelection, then there must always be at least one row/cell * selected. If the selection is changed, the caret is changed to match. * * @return true if the selection has changed. */ private function ensureRequiredSelection():Boolean { var selectionChanged:Boolean; if (!requireSelection) return false; if (getGridDataProviderLength() == 0 || getGridColumnsLength() == 0) return false; // If there isn't a selection, set one, using the grid method rather // than the internal one, so that the caretPosition will be updated too. if (isRowSelectionMode()) { if (selectionLength == 0) selectionChanged = grid.setSelectedIndex(0); } else if (isCellSelectionMode()) { if (selectionLength == 0) selectionChanged = grid.setSelectedCell(0, 0); } return selectionChanged; } /** * @private * Remove any currently selected rows, cells and cached items. This * disregards the requireSelection flag. */ private function removeSelection():Boolean { const selectionChanged:Boolean = (selectionLength > 0); cellRegions.length = 0; _selectionLength = 0; selectedItem = null; return selectionChanged; } /** * @private * True if the selection mode is row-based and the 0-based index is * valid index in the dataProvider. */ protected function validateIndex(index:int):Boolean { // Don't validate. if (inCollectionHandler) return true; return isRowSelectionMode() && index >= 0 && index < getGridDataProviderLength(); } /** * @private * True if the selection mode is GridSelectionMode.MULTIPLE_ROWS * and each index in indices is a valid index into the * dataProvider. */ protected function validateIndices(indices:Vector.):Boolean { if (selectionMode == GridSelectionMode.MULTIPLE_ROWS) { // Don't validate. if (inCollectionHandler) return true; for each (var index:int in indices) { if (index < 0 || index >= getGridDataProviderLength()) return false; } return true; } return false; } /** * @private * True if the selection mode is GridSelectionMode.SINGLE_CELL * or code>GridSelectionMode.MULTIPLE_CELLS * and the 0-based index is valid index in columns. */ protected function validateCell(rowIndex:int, columnIndex:int):Boolean { // Don't validate. if (inCollectionHandler) return true; return isCellSelectionMode() && rowIndex >= 0 && rowIndex < getGridDataProviderLength() && columnIndex >= 0 && columnIndex < getGridColumnsLength(); } /** * @private * True if the selection mode is * GridSelectionMode.MULTIPLE_CELLS and the entire cell region * is contained within the grid. */ protected function validateCellRegion(rowIndex:int, columnIndex:int, rowCount:int, columnCount:int):Boolean { if (selectionMode == GridSelectionMode.MULTIPLE_CELLS) { // Don't validate. if (inCollectionHandler) return true; const maxRows:int = getGridDataProviderLength(); const maxColumns:int = getGridColumnsLength(); return (rowIndex >= 0 && rowCount >= 0 && rowIndex + rowCount <= maxRows && columnIndex >= 0 && columnCount >= 0 && columnIndex + columnCount <= maxColumns); } return false; } /** * @private * True if the selection mode is * GridSelectionMode.MULTIPLE_ROW and the entire row region * is contained within the grid. */ protected function validateRowRegion(rowIndex:int, rowCount:int):Boolean { if (selectionMode == GridSelectionMode.MULTIPLE_ROWS) { // Don't validate. if (inCollectionHandler) return true; const maxRows:int = getGridDataProviderLength(); return (rowIndex >= 0 && rowCount >= 0 && rowIndex + rowCount <= maxRows); } return false; } /** * @private * Initalize the list of cellRegions with this one. */ private function internalSetCellRegion(rowIndex:int, columnIndex:int=0, rowCount:uint=1, columnCount:uint=1):void { const cr:CellRect = new CellRect(rowIndex, columnIndex, rowCount, columnCount, true); removeSelection(); cellRegions.push(cr); _selectionLength = rowCount * columnCount; if (preserveSelection && (selectionMode == GridSelectionMode.SINGLE_ROW || selectionMode == GridSelectionMode.SINGLE_CELL)) { selectedItem = grid.dataProvider.getItemAt(rowIndex); } } /** * @private * This should only be called by setCellRegion after the selection has been * removed. * This will add a cellRegion to the list of cellRegions. * This allows the special-handling needed for setting a cell region * which has one of more columns which are not visible since only visible columns should * be included in the selection. * * The code is not equipped to handle the general case of overlapping * cell regions. */ private function internalAddCellRegion(rowIndex:int, columnIndex:int=0, rowCount:uint=1, columnCount:uint=1):void { const cr:CellRect = new CellRect(rowIndex, columnIndex, rowCount, columnCount, true); cellRegions.push(cr); _selectionLength += rowCount * columnCount; } /** * @private * Add the given row/cell to the list of cellRegions. */ private function internalAddCell(rowIndex:int, columnIndex:int=0):void { if (!regionsContainCell(rowIndex, columnIndex)) { const cr:CellRect = new CellRect(rowIndex, columnIndex, 1, 1, true); cellRegions.push(cr); // If the length is current before this add, just increment the // length. if (_selectionLength >= 0) _selectionLength++; } } /** * @private * Remove the given row/cell from the list of cellRegions. */ private function internalRemoveCell(rowIndex:int, columnIndex:int=0):void { if (regionsContainCell(rowIndex, columnIndex)) { const cr:CellRect = new CellRect(rowIndex, columnIndex, 1, 1, false); cellRegions.push(cr); // If the length is current before this remove, just decrement the // length. if (_selectionLength >= 0) _selectionLength--; selectedItem = null; } } /** * @private * Find the bounding box for all the added cell regions. It could be * larger than the current selection region if cell regions have been * removed. */ private function getCellRegionsBounds():Rectangle { var bounds:Rectangle = new Rectangle(); const cellRegionsLength:int = cellRegions.length; for (var i:int = 0; i < cellRegionsLength; i++) { var cr:CellRect = cellRegions[i]; if (!cr.isAdd) continue; bounds = bounds.union(cr); } return bounds; } //-------------------------------------------------------------------------- // // Data Provider Collection methods // //-------------------------------------------------------------------------- /** * @private * Called when the grid's dataProvider dispatches a * CollectionEvent.COLLECTION_CHANGE event. It handles * each of the events defined in CollectionEventKind. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function dataProviderCollectionChanged(event:CollectionEvent):Boolean { var selectionChanged:Boolean = false; inCollectionHandler = true; switch (event.kind) { case CollectionEventKind.ADD: { selectionChanged = dataProviderCollectionAdd(event); break; } case CollectionEventKind.MOVE: { selectionChanged = dataProviderCollectionMove(event); break; } case CollectionEventKind.REFRESH: { selectionChanged = dataProviderCollectionRefresh(event); break; } case CollectionEventKind.REMOVE: { selectionChanged = dataProviderCollectionRemove(event); break; } case CollectionEventKind.REPLACE: { selectionChanged = dataProviderCollectionReplace(event); break; } case CollectionEventKind.RESET: { selectionChanged = dataProviderCollectionReset(event); break; } case CollectionEventKind.UPDATE: { selectionChanged = dataProviderCollectionUpdate(event); break; } } inCollectionHandler = false; return selectionChanged; } /** * @private * Add an item to the collection. */ private function dataProviderCollectionAdd(event:CollectionEvent):Boolean { var selectionChanged:Boolean = handleRowAdd(event.location, event.items.length); return ensureRequiredSelection() || selectionChanged; } /** * @private */ private function handleRowAdd(insertIndex:int, insertCount:int=1):Boolean { var selectionChanged:Boolean = false; for (var cnt:int = 0; cnt < insertCount; cnt++) { for (var crIndex:int = 0; crIndex < cellRegions.length; crIndex++) { var cr:CellRect = cellRegions[crIndex]; // If the insert is before the region or at the first row of // the region, move the region down a row. If the insert is // in the region (but not the first row), split the region // into two and insert the new region. if (insertIndex <= cr.y) { cr.y++; selectionChanged = true; } else if (insertIndex < cr.bottom) { var newCR:CellRect = new CellRect(insertIndex + 1, cr.x, cr.bottom - insertIndex, cr.width, cr.isAdd); cr.height = insertIndex - cr.y; // insert newCR just after cr cellRegions.splice(++crIndex, 0, newCR); _selectionLength = -1; // recalculate selectionChanged = true; } } } return selectionChanged; } /** * @private * The item has been moved from the oldLocation to location. */ private function dataProviderCollectionMove(event:CollectionEvent):Boolean { var selectionChanged:Boolean = false; const oldRowIndex:int = event.oldLocation; const newRowIndex:int = event.location; selectionChanged = handleRowRemove(oldRowIndex); // If the row is removed before the newly added item // then change index to account for this. if (newRowIndex > oldRowIndex) newRowIndex--; return handleRowAdd(newRowIndex) || selectionChanged; } /** * @private * The sort or filter on the collection changed. */ private function dataProviderCollectionRefresh(event:CollectionEvent):Boolean { return handleRefreshAndReset(event); } /** * @private * If preserving the selection and the selected item is in the new view, * keep the item selected. Otherwise, clear the selection (or maintain one * if requireSelection is true). */ private function handleRefreshAndReset(event:CollectionEvent):Boolean { // Is the selectedItem still in the collection? if (selectedItem) { const view:ICollectionView = event.currentTarget as ICollectionView; if (view && view.contains(selectedItem)) { // Selection is in view so move it to the new row location. const newRowIndex:int = grid.dataProvider.getItemIndex(selectedItem); if (selectionMode == GridSelectionMode.SINGLE_ROW) { internalSetCellRegion(newRowIndex); } else { var oldSelectedCell:CellPosition = allCells()[0]; internalSetCellRegion(newRowIndex, oldSelectedCell.columnIndex); } return true; } } // Not preserving selection or selection not in current view so remove // selection. var selectionChanged:Boolean = removeSelection(); return ensureRequiredSelection() || selectionChanged; } /** * @private * An item has been removed from the collection. */ private function dataProviderCollectionRemove(event:CollectionEvent):Boolean { if (getGridDataProviderLength() == 0) return removeSelection(); var selectionChanged:Boolean = handleRowRemove(event.location, event.items.length); return ensureRequiredSelection() || selectionChanged; } /** * @private */ private function handleRowRemove(removeIndex:int, removeCount:int=1):Boolean { var selectionChanged:Boolean = false; for (var cnt:int = 0; cnt < removeCount; cnt++) { var crIndex:int = 0 while (crIndex < cellRegions.length) { var cr:CellRect = cellRegions[crIndex]; // Handle the cases where the remove is before the cell region // or in the cell region. if (removeIndex < cr.y) { cr.y--; selectionChanged = true; } else if (removeIndex >= cr.y && removeIndex < cr.bottom) { _selectionLength = -1; // recalculate selectionChanged = true; cr.height--; if (cr.height == 0) { cellRegions.splice(crIndex, 1); continue; } } crIndex++; } } return selectionChanged; } /** * @private * The item has been replaced. */ private function dataProviderCollectionReplace(event:CollectionEvent):Boolean { // Nothing to do here unless we're saving the data items to preserve // the selection. return false; } /** * @private * The data source changed or all the items were removed from the data * source. If there is a preserved selected item and it is in the new * data source the selection will be maintained. */ private function dataProviderCollectionReset(event:CollectionEvent):Boolean { return handleRefreshAndReset(event); } /** * @private * One or more items in the collection have been updated. */ private function dataProviderCollectionUpdate(event:CollectionEvent):Boolean { // Nothing to do. return false; } //-------------------------------------------------------------------------- // // Columns Collection methods // //-------------------------------------------------------------------------- /** * @private * Called when the grid's columns dispatches a * CollectionEvent.COLLECTION_CHANGE event. It handles * each of the events defined in CollectionEventKind. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 2.5 * @productversion Flex 4.5 */ public function columnsCollectionChanged(event:CollectionEvent):Boolean { var selectionChanged:Boolean = false; inCollectionHandler = true; switch (event.kind) { case CollectionEventKind.ADD: { selectionChanged = columnsCollectionAdd(event); break; } case CollectionEventKind.MOVE: { selectionChanged = columnsCollectionMove(event); break; } case CollectionEventKind.REMOVE: { selectionChanged = columnsCollectionRemove(event); break; } case CollectionEventKind.REPLACE: case CollectionEventKind.UPDATE: { break; } case CollectionEventKind.REFRESH: { selectionChanged = columnsCollectionRefresh(event); break; } case CollectionEventKind.RESET: { selectionChanged = columnsCollectionReset(event); break; } } inCollectionHandler = false; return selectionChanged; } /** * @private * Add a column to the columns collection. */ private function columnsCollectionAdd(event:CollectionEvent):Boolean { // If no selectionMode or a row-based selectionMode, nothing to do. if (!isCellSelectionMode()) return false; var selectionChanged:Boolean = handleColumnAdd(event.location, event.items.length); return ensureRequiredSelection() || selectionChanged; } /** * @private */ private function handleColumnAdd(insertIndex:int, insertCount:int=1):Boolean { var selectionChanged:Boolean = false; for (var cnt:int = 0; cnt < insertCount; cnt++) { for (var crIndex:int = 0; crIndex < cellRegions.length; crIndex++) { var cr:CellRect = cellRegions[crIndex]; // If the insert is to the left of the region or at the // first column of the region, move the region to the right a // column. If the insert is in the region (but not the first // column), split the region into two and insert the new region. if (insertIndex <= cr.x) { cr.x++; selectionChanged = true; } else if (insertIndex < cr.x) { var newCR:CellRect = new CellRect(cr.y, insertIndex + 1, cr.height, cr.right - insertIndex, cr.isAdd); cr.width = insertIndex - cr.x; // insert newCR just after cr cellRegions.splice(++crIndex, 0, newCR); _selectionLength = -1; // recalculate selectionChanged = true; } } } return selectionChanged; } /** * @private * The column has been moved from the oldLocation to location in the * columns collection. */ private function columnsCollectionMove(event:CollectionEvent):Boolean { // If no selectionMode or a row-based selectionMode, nothing to do. if (!isCellSelectionMode()) return false; const oldColumnIndex:int = event.oldLocation; const newColumnIndex:int = event.location; var selectionChanged:Boolean = handleColumnRemove(oldColumnIndex); // If the column is removed before the newly added column // then change index to account for this. if (newColumnIndex > oldColumnIndex) newColumnIndex--; return handleColumnAdd(newColumnIndex) || selectionChanged; } /** * @private * A column has been removed from the columns collection. */ private function columnsCollectionRemove(event:CollectionEvent):Boolean { // If no selectionMode or a row-based selectionMode, nothing to do. if (!isCellSelectionMode()) return false; if (getGridColumnsLength() == 0) return removeSelection(); var selectionChanged:Boolean = handleColumnRemove(event.location, event.items.length); return ensureRequiredSelection() || selectionChanged; } /** * @private */ private function handleColumnRemove(removeIndex:int, removeCount:int=1):Boolean { var selectionChanged:Boolean = false; for (var cnt:int = 0; cnt < removeCount; cnt++) { var crIndex:int = 0 while (crIndex < cellRegions.length) { var cr:CellRect = cellRegions[crIndex]; // Handle the cases where the remove is before the cell region // or in the cell region. if (removeIndex < cr.x) { cr.x--; selectionChanged = true; } else if (removeIndex >= cr.x && removeIndex < cr.right) { _selectionLength = -1; // recalculate selectionChanged = true; cr.width--; if (cr.width == 0) { cellRegions.splice(crIndex, 1); continue; } } crIndex++; } } return selectionChanged; } /** * @private * The sort or filter on the collection changed. For columns, this is * the same as a "reset" event. */ private function columnsCollectionRefresh(event:CollectionEvent):Boolean { return columnsCollectionReset(event); } /** * @private * The columns changed. If the selectionMode is cell-based, don't preserve * the selection. */ private function columnsCollectionReset(event:CollectionEvent):Boolean { // If no selectionMode or a row-based selectionMode, nothing to do. if (!isCellSelectionMode()) return false; var selectionChanged:Boolean = removeSelection(); return ensureRequiredSelection() || selectionChanged; } } } import flash.geom.Rectangle; /** * @private * A CellRect is a rectangle with one additional, isAdd property. * A CellRect for a row is represented with columnIndex=0 and columnCount=1. * * Mappings between Rectangle and selection cell regions: * y = rowIndex * x = columnIndex * height = rowCount * width = columnCount */ internal class CellRect extends Rectangle { public var isAdd:Boolean = false; // For a row, columnIndex=0 and columnCount=1. public function CellRect(rowIndex:int, columnIndex:int, rowCount:uint, columnCount:uint, isAdd:Boolean) { super(columnIndex, rowIndex, columnCount, rowCount); this.isAdd = isAdd; } public function containsCell(cellRowIndex:int, cellColumnIndex:int):Boolean { return contains(cellColumnIndex, cellRowIndex); } }