////////////////////////////////////////////////////////////////////////////////
//
// ADOBE SYSTEMS INCORPORATED
// Copyright 2003-2007 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file
// in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////
package mx.controls
{
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.GradientType;
import flash.display.Graphics;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.ui.Keyboard;
import flash.utils.describeType;
import flash.utils.Dictionary;
import mx.collections.CursorBookmark;
import mx.collections.ICollectionView;
import mx.collections.ItemResponder;
import mx.collections.Sort;
import mx.collections.SortField;
import mx.collections.errors.ItemPendingError;
import mx.controls.dataGridClasses.DataGridBase;
import mx.controls.dataGridClasses.DataGridColumn;
import mx.controls.dataGridClasses.DataGridDragProxy;
import mx.controls.dataGridClasses.DataGridHeader;
import mx.controls.dataGridClasses.DataGridItemRenderer;
import mx.controls.dataGridClasses.DataGridListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.controls.listClasses.ListBaseContentHolder;
import mx.controls.listClasses.ListBaseSeekPending;
import mx.controls.listClasses.ListRowInfo;
import mx.controls.scrollClasses.ScrollBar;
import mx.core.ContextualClassFactory;
import mx.core.EdgeMetrics;
import mx.core.EventPriority;
import mx.core.FlexShape;
import mx.core.FlexSprite;
import mx.core.FlexVersion;
import mx.core.IChildList;
import mx.core.IFactory;
import mx.core.IFlexDisplayObject;
import mx.core.IIMESupport;
import mx.core.IInvalidating;
import mx.core.IPropertyChangeNotifier;
import mx.core.IRawChildrenContainer;
import mx.core.IRectangularBorder;
import mx.core.IUIComponent;
import mx.core.ScrollPolicy;
import mx.core.UIComponent;
import mx.core.UIComponentGlobals;
import mx.core.mx_internal;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
import mx.events.ListEvent;
import mx.events.DataGridEvent;
import mx.events.DataGridEventReason;
import mx.events.DragEvent;
import mx.events.FlexEvent;
import mx.events.IndexChangedEvent;
import mx.events.ScrollEvent;
import mx.events.ScrollEventDetail;
import mx.managers.CursorManager;
import mx.managers.CursorManagerPriority;
import mx.managers.IFocusManager;
import mx.managers.IFocusManagerComponent;
import mx.skins.halo.ListDropIndicator;
import mx.styles.ISimpleStyleClient;
import mx.styles.StyleManager;
import mx.utils.ObjectUtil;
import mx.managers.ISystemManager;
import mx.core.IFlexModuleFactory;
import mx.utils.StringUtil;
use namespace mx_internal;
//--------------------------------------
// Events
//--------------------------------------
/**
* Dispatched when the user releases the mouse button while over an item
* renderer, tabs to the DataGrid control or within the DataGrid control,
* or in any other way attempts to edit an item.
*
* @eventType mx.events.DataGridEvent.ITEM_EDIT_BEGINNING
*/
[Event(name="itemEditBeginning", type="mx.events.DataGridEvent")]
/**
* Dispatched when the editedItemPosition property has been set
* and the item can be edited.
*
* @eventType mx.events.DataGridEvent.ITEM_EDIT_BEGIN
*/
[Event(name="itemEditBegin", type="mx.events.DataGridEvent")]
/**
* Dispatched when an item editing session ends for any reason.
*
* @eventType mx.events.DataGridEvent.ITEM_EDIT_END
*/
[Event(name="itemEditEnd", type="mx.events.DataGridEvent")]
/**
* Dispatched when an item renderer gets focus, which can occur if the user
* clicks on an item in the DataGrid control or navigates to the item using
* a keyboard. Only dispatched if the item is editable.
*
* @eventType mx.events.DataGridEvent.ITEM_FOCUS_IN
*/
[Event(name="itemFocusIn", type="mx.events.DataGridEvent")]
/**
* Dispatched when an item renderer loses focus, which can occur if the user
* clicks another item in the DataGrid control or clicks outside the control,
* or uses the keyboard to navigate to another item in the DataGrid control
* or outside the control.
* Only dispatched if the item is editable.
*
* @eventType mx.events.DataGridEvent.ITEM_FOCUS_OUT
*/
[Event(name="itemFocusOut", type="mx.events.DataGridEvent")]
/**
* Dispatched when a user changes the width of a column, indicating that the
* amount of data displayed in that column may have changed.
* If horizontalScrollPolicy is "off", other
* columns shrink or expand to compensate for the columns' resizing,
* and they also dispatch this event.
*
* @eventType mx.events.DataGridEvent.COLUMN_STRETCH
*/
[Event(name="columnStretch", type="mx.events.DataGridEvent")]
/**
* Dispatched when the user releases the mouse button on a column header
* to request the control to sort
* the grid contents based on the contents of the column.
* Only dispatched if the column is sortable and the data provider supports
* sorting. The DataGrid control has a default handler for this event that implements
* a single-column sort. Multiple-column sort can be implemented by calling the
* preventDefault() method to prevent the single column sort and setting
* the sort property of the data provider.
*
* Note: The sort arrows are defined by the default event handler for
* the headerRelease event. If you call the preventDefault() method
* in your event handler, the arrows are not drawn.
*
true, shows vertical grid lines.
* If false, hides vertical grid lines.
* @default true
*/
[Style(name="verticalGridLines", type="Boolean", inherit="no")]
/**
* A flag that indicates whether to show horizontal grid lines between
* the rows.
* If true, shows horizontal grid lines.
* If false, hides horizontal grid lines.
* @default false
*/
[Style(name="horizontalGridLines", type="Boolean", inherit="no")]
/**
* The color of the vertical grid lines.
* @default 0x666666
*/
[Style(name="verticalGridLineColor", type="uint", format="Color", inherit="yes")]
/**
* The color of the horizontal grid lines.
*/
[Style(name="horizontalGridLineColor", type="uint", format="Color", inherit="yes")]
/**
* An array of two colors used to draw the header background gradient.
* The first color is the top color.
* The second color is the bottom color.
* @default [0xFFFFFF, 0xE6E6E6]
*/
[Style(name="headerColors", type="Array", arrayType="uint", format="Color", inherit="yes")]
/**
* The color of the row background when the user rolls over the row.
* @default 0xE3FFD6
*/
[Style(name="rollOverColor", type="uint", format="Color", inherit="yes")]
/**
* The color of the background for the row when the user selects
* an item renderer in the row.
* @default 0xCDFFC1
*/
[Style(name="selectionColor", type="uint", format="Color", inherit="yes")]
/**
* The name of a CSS style declaration for controlling other aspects of
* the appearance of the column headers.
* @default "dataGridStyles"
*/
[Style(name="headerStyleName", type="String", inherit="no")]
/**
* The class to use as the skin for a column that is being resized.
* @default mx.skins.halo.DataGridColumnResizeSkin
*/
[Style(name="columnResizeSkin", type="Class", inherit="no")]
/**
* The class to use as the skin that defines the appearance of the
* background of the column headers in a DataGrid control.
* @default mx.skins.halo.DataGridHeaderSeparator
*/
[Style(name="headerBackgroundSkin", type="Class", inherit="no")]
/**
* The class to use as the skin that defines the appearance of the
* separator between column headers in a DataGrid control.
* @default mx.skins.halo.DataGridHeaderSeparator
*/
[Style(name="headerSeparatorSkin", type="Class", inherit="no")]
/**
* The class to use as the skin that defines the appearance of the
* separator between rows in a DataGrid control.
* By default, the DataGrid control uses the
* drawHorizontalLine() and drawVerticalLine() methods
* to draw the separators.
*
* @default undefined
*/
[Style(name="horizontalSeparatorSkin", type="Class", inherit="no")]
/**
* The class to use as the skin that defines the appearance of the
* separator between the locked and unlocked rows in a DataGrid control.
* By default, the DataGrid control uses the
* drawHorizontalLine() and drawVerticalLine() methods
* to draw the separators.
*
* @default undefined
*/
[Style(name="horizontalLockedSeparatorSkin", type="Class", inherit="no")]
/**
* The class to use as the skin that defines the appearance of the
* separators between columns in a DataGrid control.
* By default, the DataGrid control uses the
* drawHorizontalLine() and drawVerticalLine() methods
* to draw the separators.
*
* @default undefined
*/
[Style(name="verticalSeparatorSkin", type="Class", inherit="no")]
/**
* The class to use as the skin that defines the appearance of the
* separator between the locked and unlocked columns in a DataGrid control.
* By default, the DataGrid control uses the
* drawHorizontalLine() and drawVerticalLine() methods
* to draw the separators.
*
* @default undefined
*/
[Style(name="verticalLockedSeparatorSkin", type="Class", inherit="no")]
/**
* The class to use as the skin for the arrow that indicates the column sort
* direction.
* @default mx.skins.halo.DataGridSortArrow
*/
[Style(name="sortArrowSkin", type="Class", inherit="no")]
/**
* The class to use as the skin for the cursor that indicates that a column
* can be resized.
* @default mx.skins.halo.DataGridStretchCursor
*/
[Style(name="stretchCursor", type="Class", inherit="no")]
/**
* The class to use as the skin that indicates that
* a column can be dropped in the current location.
*
* @default mx.skins.halo.DataGridColumnDropIndicator
*/
[Style(name="columnDropIndicatorSkin", type="Class", inherit="no")]
/**
* The name of a CSS style declaration for controlling aspects of the
* appearance of column when the user is dragging it to another location.
*
* @default "headerDragProxyStyle"
*/
[Style(name="headerDragProxyStyleName", type="String", inherit="no")]
//--------------------------------------
// Excluded APIs
//--------------------------------------
[Exclude(name="columnCount", kind="property")]
[Exclude(name="iconField", kind="property")]
[Exclude(name="iconFunction", kind="property")]
[Exclude(name="labelField", kind="property")]
[Exclude(name="offscreenExtraRowsOrColumns", kind="property")]
[Exclude(name="offscreenExtraRows", kind="property")]
[Exclude(name="offscreenExtraRowsTop", kind="property")]
[Exclude(name="offscreenExtraRowsBottom", kind="property")]
[Exclude(name="offscreenExtraColumns", kind="property")]
[Exclude(name="offscreenExtraColumnsLeft", kind="property")]
[Exclude(name="offscreenExtraColumnsRight", kind="property")]
[Exclude(name="offscreenExtraRowsOrColumnsChanged", kind="property")]
[Exclude(name="showDataTips", kind="property")]
[Exclude(name="cornerRadius", kind="style")]
//--------------------------------------
// Other metadata
//--------------------------------------
[AccessibilityClass(implementation="mx.accessibility.DataGridAccImpl")]
[DataBindingInfo("acceptedTypes", "{ dataProvider: "String" }")]
[DefaultBindingProperty(source="selectedItem", destination="dataProvider")]
[DefaultProperty("dataProvider")]
[DefaultTriggerEvent("change")]
[IconFile("DataGrid.png")]
[RequiresDataBinding(true)]
/**
* The DataGrid control is like a List except that it can
* show more than one column of data making it suited for showing
* objects with multiple properties.
* * The DataGrid control provides the following features: *
The DataGrid control has the following default sizing * characteristics:
*| Characteristic | *Description | *
|---|---|
| Default size | *If the columns are empty, the default width is 300 * pixels. If the columns contain information but define * no explicit widths, the default width is 100 pixels * per column. The DataGrid width is sized to fit the * width of all columns, if possible. * The default number of displayed rows, including the * header is 7, and each row, by default, is 20 pixels * high. * | *
| Minimum size | *0 pixels. | *
| Maximum size | *5000 by 5000. | *
* The <mx:DataGrid> tag inherits all of the tag
* attributes of its superclass, except for labelField,
* iconField, and iconFunction, and adds the
* following tag attributes:
*
* <mx:DataGrid * Properties * columns="From dataProvider" * draggableColumns="true|false" * editable="false|true" * editedItemPosition="* * * @see mx.controls.dataGridClasses.DataGridItemRenderer * @see mx.controls.dataGridClasses.DataGridColumn * @see mx.controls.dataGridClasses.DataGridDragProxy * @see mx.events.DataGridEvent * * @includeExample examples/SimpleDataGrid.mxml */ public class DataGrid extends DataGridBase implements IIMESupport { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Class mixins // //-------------------------------------------------------------------------- /** * @private * Placeholder for mixin by DataGridAccImpl. */ mx_internal static var createAccessibilityImplementation:Function; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. */ public function DataGrid() { super(); _columns = []; // pick a default row height setRowHeight(20); // Register default handlers for item editing and sorting events. addEventListener(DataGridEvent.ITEM_EDIT_BEGINNING, itemEditorItemEditBeginningHandler, false, EventPriority.DEFAULT_HANDLER); addEventListener(DataGridEvent.ITEM_EDIT_BEGIN, itemEditorItemEditBeginHandler, false, EventPriority.DEFAULT_HANDLER); addEventListener(DataGridEvent.ITEM_EDIT_END, itemEditorItemEditEndHandler, false, EventPriority.DEFAULT_HANDLER); addEventListener(DataGridEvent.HEADER_RELEASE, headerReleaseHandler, false, EventPriority.DEFAULT_HANDLER); addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler); } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- [Inspectable(environment="none")] /** * A reference to the currently active instance of the item editor, * if it exists. * *null" * horizontalScrollPosition="null" * imeMode="null" * itemEditorInstance="null" * minColumnWidth="NaN" * resizableColumns="true|false" * sortableColumns="true|false" * * Styles * backgroundDisabledColor="0xEFEEEF" * columnDropIndicatorSkin="DataGridColumnDropIndicator" * columnResizeSkin="DataGridColumnResizeSkin" * disabledIconColor="0x999999" * headerColors="[#FFFFFF, #E6E6E6]" * headerDragProxyStyleName="headerDragProxyStyle" * headerSeparatorSkin="DataGridHeaderSeparator" * headerStyleName="dataGridStyles" * horizontalGridLineColor="No default" * horizontalGridLines="false|true" * horizontalLockedSeparatorSkin="undefined" * horizontalSeparatorSkin="undefined" * iconColor="0x111111" * rollOverColor="#E3FFD6" * selectionColor="#CDFFC1" * sortArrowSkin="DataGridSortArrow" * stretchCursor="DataGridStretchCursor" * verticalGridLineColor="#666666" * verticalGridLines="false|true" * verticalLockedSeparatorSkin="undefined" * verticalSeparatorSkin="undefined" * * Events * columnStretch="No default" * headerRelease="No default" * headerShift="No default" * itemEditBegin="No default" * itemEditBeginning="No default" * itemEditEnd="No default" * itemFocusIn="No default" * itemFocusOut="No default" * /> * * The following DataGrid code sample specifies the column order: * <mx:DataGrid> * <mx:dataProvider> * <mx:Object Artist="Pavement" Price="11.99" * Album="Slanted and Enchanted"/> * <mx:Object Artist="Pavement" * Album="Brighten the Corners" Price="11.99"/> * </mx:dataProvider> * <mx:columns> * <mx:DataGridColumn dataField="Album"/> * <mx:DataGridColumn dataField="Price"/> * </mx:columns> * </mx:DataGrid> *
To access the item editor instance and the new item value when an
* item is being edited, you use the itemEditorInstance
* property. The itemEditorInstance property
* is not valid until after the event listener for
* the itemEditBegin event executes. Therefore, you typically
* only access the itemEditorInstance property from within
* the event listener for the itemEditEnd event.
The DataGridColumn.itemEditor property defines the
* class of the item editor
* and, therefore, the data type of the item editor instance.
You do not set this property in MXML.
*/ public var itemEditorInstance:IListItemRenderer; /** * A reference to the item renderer * in the DataGrid control whose item is currently being edited. * *From within an event listener for the itemEditBegin
* and itemEditEnd events,
* you can access the current value of the item being edited
* using the editedItemRenderer.data property.
horizontalScrollPosition property is always
* in the range of 0 to the index of the columns
* that will make the last column visible. This is different from the
* List control that scrolls by pixels. The DataGrid control always aligns the left edge
* of a column with the left edge of the DataGrid control.
*/
override public function set horizontalScrollPosition(value:Number):void
{
// if not init or no data;
if (!initialized || listItems.length == 0)
{
super.horizontalScrollPosition = value;
return;
}
var oldValue:int = super.horizontalScrollPosition;
super.horizontalScrollPosition = value;
// columns have variable width so we need to recalc scroll parms
scrollAreaChanged = true;
columnsInvalid = true;
calculateColumnSizes();
// we are going to get a full repaint so don't repaint now
if (itemsSizeChanged)
return;
if (oldValue != value)
{
removeClipMask();
var bookmark:CursorBookmark;
if (iterator)
bookmark = iterator.bookmark;
clearIndicators();
clearVisibleData();
//if we scrolled more than the number of scrollable columns
makeRowsAndColumns(0, 0, listContent.width, listContent.height, 0, 0);
if (lockedRowCount)
{
var cursorPos:CursorBookmark;
cursorPos = lockedRowContent.iterator.bookmark;
makeRows(lockedRowContent, 0, 0, unscaledWidth, unscaledHeight, 0, 0, true, lockedRowCount);
if (iteratorValid)
lockedRowContent.iterator.seek(cursorPos, 0);
}
if (headerVisible && header)
{
header.visibleColumns = visibleColumns;
header.headerItemsChanged = true;
header.invalidateSize();
header.validateNow();
}
if (iterator && bookmark)
iterator.seek(bookmark, 0);
invalidateDisplayList();
addClipMask(false);
}
}
//----------------------------------
// horizontalScrollPolicy
//----------------------------------
/**
* @private
* Accomodates ScrollPolicy.AUTO.
* Makes sure column widths stay in synch.
*
* @param policy on, off, or auto
*/
override public function set horizontalScrollPolicy(value:String):void
{
super.horizontalScrollPolicy = value;
columnsInvalid = true;
itemsSizeChanged = true;
invalidateDisplayList();
}
/**
* @private
*/
override public function set verticalScrollPosition(value:Number):void
{
skipHeaderUpdate = true;
var oldValue:Number = super.verticalScrollPosition;
super.verticalScrollPosition = value;
if (oldValue != value)
{
if (lockedColumnContent)
drawRowGraphics(lockedColumnContent)
}
skipHeaderUpdate = false;
}
/**
* @private
*
*/
override protected function createChildren():void
{
super.createChildren();
if (!header)
{
header = new headerClass();
header.styleName = this;
addChild(header);
}
}
//----------------------------------
// imeMode
//----------------------------------
/**
* @private
*/
private var _imeMode:String = null;
[Inspectable(environment="none")]
/**
* Specifies the IME (input method editor) mode.
* The IME enables users to enter text in Chinese, Japanese, and Korean.
* Flex sets the specified IME mode when the control gets the focus,
* and sets it back to the previous value when the control loses the focus.
*
* The flash.system.IMEConversionMode class defines constants for the
* valid values for this property.
* You can also specify null to specify no IME.
If you want to change the set of columns, you must get this array, * make modifications to the columns and order of columns in the array, * and then assign the new array to the columns property. This is because * the DataGrid control returned a new copy of the array of columns and therefore * did not notice the changes.
*/ override public function get columns():Array { return _columns.slice(0); } /** * @private */ override public function set columns(value:Array):void { var n:int; var i:int; n = _columns.length; for (i = 0; i < n; i++) { columnRendererChanged(_columns[i]); } freeItemRenderersTable = new Dictionary(false); columnMap = {}; _columns = value.slice(0); columnsInvalid = true; generatedColumns = false; n = value.length; for (i = 0; i < n; i++) { var column:DataGridColumn = _columns[i]; column.owner = this; column.colNum = i; } updateSortIndexAndDirection(); itemsSizeChanged = true; invalidateDisplayList(); dispatchEvent(new Event("columnsChanged")); } //---------------------------------- // draggableColumns //---------------------------------- /** * @private * Storage for the draggableColumns property. */ private var _draggableColumns:Boolean = true; [Inspectable(defaultValue="true")] /** * A flag that indicates whether the user is allowed to reorder columns. * Iftrue, the user can reorder the columns
* of the DataGrid control by dragging the header cells.
*
* @default true
*/
public function get draggableColumns():Boolean
{
return _draggableColumns;
}
/**
* @private
*/
public function set draggableColumns(value:Boolean):void
{
_draggableColumns = value;
}
//----------------------------------
// editable
//----------------------------------
[Inspectable(category="General")]
/**
* A flag that indicates whether or not the user can edit
* items in the data provider.
* If true, the item renderers in the control are editable.
* The user can click on an item renderer to open an editor.
*
* You can turn off editing for individual columns of the
* DataGrid control using the DataGridColumn.editable property,
* or by handling the itemEditBeginning and
* itemEditBegin events
This Object has two fields, columnIndex and
* rowIndex,
* the zero-based column and row indexes of the item.
* For example: {columnIndex:2, rowIndex:3}
Setting this property scrolls the item into view and
* dispatches the itemEditBegin event to
* open an item editor on the specified item renderer.
true, the user can stretch or shrink the columns of
* the DataGrid control by dragging the grid lines between the header cells.
* If true, individual columns must also have their
* resizable properties set to false to
* prevent the user from resizing a particular column.
*
* @default true
*/
public var resizableColumns:Boolean = true;
//----------------------------------
// sortableColumns
//----------------------------------
[Inspectable(category="General")]
/**
* A flag that indicates whether the user can sort the data provider items
* by clicking on a column header cell.
* If true, the user can sort the data provider items by
* clicking on a column header cell.
* The DataGridColumn.dataField property of the column
* or the DataGridColumn.sortCompareFunction property
* of the column is used as the sort field.
* If a column is clicked more than once
* the sort alternates between ascending and descending order.
* If true, individual columns can be made to not respond
* to a click on a header by setting the column's sortable
* property to false.
*
* When a user releases the mouse button over a header cell, the DataGrid
* control dispatches a headerRelease event if both
* this property and the column's sortable property are true.
* If no handler calls the preventDefault() method on the event, the
* DataGrid sorts using that column's DataGridColumn.dataField or
* DataGridColumn.sortCompareFunction properties.
backgroundAlpha style property
* setting to determine the transparency of the background color.
*
* @param s A Sprite that will contain a display object
* that contains the graphics for that row.
*
* @param rowIndex The row's index in the set of displayed rows. The
* header does not count, the top most visible row has a row index of 0.
* This is used to keep track of the objects used for drawing
* backgrounds so a particular row can re-use the same display object
* even though the index of the item that row is rendering has changed.
*
* @param y The suggested y position for the background
*
* @param height The suggested height for the indicator
*
* @param color The suggested color for the indicator
*
* @param dataIndex The index of the item for that row in the
* data provider. This can be used to color the 10th item differently
* for example.
*/
protected function drawRowBackground(s:Sprite, rowIndex:int,
y:Number, height:Number, color:uint, dataIndex:int):void
{
var contentHolder:ListBaseContentHolder = ListBaseContentHolder(s.parent);
var background:Shape;
if (rowIndex < s.numChildren)
{
background = Shape(s.getChildAt(rowIndex));
}
else
{
background = new FlexShape();
background.name = "background";
s.addChild(background);
}
background.y = y;
// 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 = Math.min(height,
contentHolder.height -
y);
var g:Graphics = background.graphics;
g.clear();
g.beginFill(color, getStyle("backgroundAlpha"));
g.drawRect(0, 0, contentHolder.width, height);
g.endFill();
}
/**
* Draws a column background for a column with the suggested color.
* This implementation creates a Shape as a
* child of the input Sprite and fills it with the appropriate color.
*
* @param s A Sprite that will contain a display object
* that contains the graphics for that column.
*
* @param columnIndex The column's index in the set of displayed columns.
* The left most visible column has a column index of 0.
* This is used to keep track of the objects used for drawing
* backgrounds so a particular column can re-use the same display object
* even though the index of the DataGridColumn for that column has changed.
*
* @param color The suggested color for the indicator
*
* @param column The column of the DataGrid control that you are drawing the background for.
*/
protected function drawColumnBackground(s:Sprite, columnIndex:int,
color:uint, column:DataGridColumn):void
{
var background:Shape;
background = Shape(s.getChildByName(columnIndex.toString()));
if (!background)
{
background = new FlexShape();
s.addChild(background);
background.name = columnIndex.toString();
}
var g:Graphics = background.graphics;
g.clear();
g.beginFill(color);
var lastRow:Object = rowInfo[listItems.length - 1];
var columnHeader:DataGridHeader = (s.parent == lockedColumnContent) ?
DataGridHeader(lockedColumnHeader) :
DataGridHeader(header);
var xx:Number = columnHeader.rendererArray[columnIndex].x
var yy:Number = rowInfo[0].y
// 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 = Math.min(lastRow.y + lastRow.height,
listContent.height - yy);
g.drawRect(xx, yy, columnHeader.visibleColumns[columnIndex].width,
listContent.height - yy);
g.endFill();
}
/**
* Creates and sizes the horizontalSeparator skins. If none have been specified, then draws the lines using
* drawHorizontalLine().
*/
private function drawHorizontalSeparator(s:Sprite, rowIndex:int, color:uint, y:Number, useLockedSeparator:Boolean = false):void
{
var hSepSkinName:String = "hSeparator" + rowIndex;
var hLockedSepSkinName:String = "hLockedSeparator" + rowIndex;
var createThisSkinName:String = useLockedSeparator ? hLockedSepSkinName : hSepSkinName;
var createThisStyleName:String = useLockedSeparator ? "horizontalLockedSeparatorSkin" : "horizontalSeparatorSkin";
var sepSkin:IFlexDisplayObject;
var lockedSepSkin:IFlexDisplayObject;
var deleteThisSkin:IFlexDisplayObject;
var createThisSkin:IFlexDisplayObject;
// Look for separator by name
sepSkin = IFlexDisplayObject(s.getChildByName(hSepSkinName));
lockedSepSkin = IFlexDisplayObject(s.getChildByName(hLockedSepSkinName));
createThisSkin = useLockedSeparator ? lockedSepSkin : sepSkin;
deleteThisSkin = useLockedSeparator ? sepSkin : lockedSepSkin;
if (deleteThisSkin)
{
s.removeChild(DisplayObject(deleteThisSkin));
//delete deleteThisSkin;
}
if (!createThisSkin)
{
var sepSkinClass:Class = Class(getStyle(createThisStyleName));
if (sepSkinClass)
{
createThisSkin = IFlexDisplayObject(new sepSkinClass());
createThisSkin.name = createThisSkinName;
var styleableSkin:ISimpleStyleClient = createThisSkin as ISimpleStyleClient;
if (styleableSkin)
styleableSkin.styleName = this;
s.addChild(DisplayObject(createThisSkin));
}
}
if (createThisSkin)
{
var mHeight:Number = !isNaN(createThisSkin.measuredHeight) ? createThisSkin.measuredHeight : 1;
createThisSkin.setActualSize(displayWidth - lockedColumnWidth, mHeight);
createThisSkin.move(0, y);
}
else // If we still don't have a sepSkin, then we have no skin style defined. Use the default function instead
{
drawHorizontalLine(s, rowIndex, color, y);
}
}
/**
* Draws a line between rows. This implementation draws a line
* directly into the given Sprite. The Sprite has been cleared
* before lines are drawn into it.
*
* @param s A Sprite that will contain a display object
* that contains the graphics for that row.
*
* @param rowIndex The row's index in the set of displayed rows. The
* header does not count, the top most visible row has a row index of 0.
* This is used to keep track of the objects used for drawing
* backgrounds so a particular row can re-use the same display object
* even though the index of the item that row is rendering has changed.
*
* @param color The suggested color for the indicator
*
* @param y The suggested y position for the background
*/
protected function drawHorizontalLine(s:Sprite, rowIndex:int, color:uint, y:Number):void
{
var contentHolder:ListBaseContentHolder = s.parent.parent as ListBaseContentHolder;
var g:Graphics = s.graphics;
g.lineStyle(1, color);
g.moveTo(0, y);
g.lineTo(contentHolder.width, y);
}
/**
* Creates and sizes the verticalSeparator skins. If none have been specified, then draws the lines using
* drawVerticalLine().
*/
private function drawVerticalSeparator(s:Sprite, colIndex:int, color:uint, x:Number, useLockedSeparator:Boolean = false):void
{
var vSepSkinName:String = "vSeparator" + colIndex;
var vLockedSepSkinName:String = "vLockedSeparator" + colIndex;
var createThisSkinName:String = useLockedSeparator ? vLockedSepSkinName : vSepSkinName;
var createThisStyleName:String = useLockedSeparator ? "verticalLockedSeparatorSkin" : "verticalSeparatorSkin";
var sepSkin:IFlexDisplayObject;
var lockedSepSkin:IFlexDisplayObject;
var deleteThisSkin:IFlexDisplayObject;
var createThisSkin:IFlexDisplayObject;
// Look for separator by name
sepSkin = IFlexDisplayObject(s.getChildByName(vSepSkinName));
lockedSepSkin = IFlexDisplayObject(s.getChildByName(vLockedSepSkinName));
createThisSkin = useLockedSeparator ? lockedSepSkin : sepSkin;
deleteThisSkin = useLockedSeparator ? sepSkin : lockedSepSkin;
if (deleteThisSkin)
{
s.removeChild(DisplayObject(deleteThisSkin));
//delete deleteThisSkin;
}
if (!createThisSkin)
{
var sepSkinClass:Class = Class(getStyle(createThisStyleName));
if (sepSkinClass)
{
createThisSkin = IFlexDisplayObject(new sepSkinClass());
createThisSkin.name = createThisSkinName;
var styleableSkin:ISimpleStyleClient = createThisSkin as ISimpleStyleClient;
if (styleableSkin)
styleableSkin.styleName = this;
s.addChild(DisplayObject(createThisSkin));
}
}
if (createThisSkin)
{
var mWidth:Number = !isNaN(createThisSkin.measuredWidth) ? createThisSkin.measuredWidth : 1;
createThisSkin.setActualSize(mWidth, s.parent.parent.height);
createThisSkin.move(x - Math.round(mWidth / 2), 0);
}
else // If we still don't have a sepSkin, then we have no skin style defined. Use the default function instead
{
drawVerticalLine(s, colIndex, color, x);
}
}
/**
* Draw lines between columns. This implementation draws a line
* directly into the given Sprite. The Sprite has been cleared
* before lines are drawn into it.
*
* @param s A Sprite that will contain a display object
* that contains the graphics for that row.
*
* @param columnIndex The column's index in the set of displayed columns.
* The left most visible column has a column index of 0.
*
* @param color The suggested color for the indicator
*
* @param x The suggested x position for the background
*/
protected function drawVerticalLine(s:Sprite, colIndex:int, color:uint, x:Number):void
{
var contentHolder:ListBaseContentHolder = s.parent.parent as ListBaseContentHolder;
//draw our vertical lines
var g:Graphics = s.graphics;
g.lineStyle(1, color, 100);
g.moveTo(x, headerVisible ? 0 : 1);
g.lineTo(x, contentHolder.height);
}
/**
* Draw lines between columns, and column backgrounds.
* This implementation calls the drawHorizontalLine(),
* drawVerticalLine(),
* and drawColumnBackground() methods as needed.
* It creates a
* Sprite that contains all of these graphics and adds it as a
* child of the listContent at the front of the z-order.
*/
protected function drawLinesAndColumnBackgrounds():void
{
drawLinesAndColumnGraphics(listContent, visibleColumns, {});
}
/**
* Draw lines between columns, and column backgrounds.
* This implementation calls the drawHorizontalLine(),
* drawVerticalLine(),
* and drawColumnBackground() methods as needed.
* It creates a
* Sprite that contains all of these graphics and adds it as a
* child of the listContent at the front of the z-order.
*/
protected function drawLinesAndColumnGraphics(contentHolder:ListBaseContentHolder, visibleColumns:Array, separators:Object):void
{
var lines:Sprite = Sprite(contentHolder.getChildByName("lines"));
if (!lines)
{
lines = new UIComponent();
lines.name = "lines";
lines.cacheAsBitmap = true;
lines.mouseEnabled = false;
contentHolder.addChild(lines);
}
contentHolder.setChildIndex(lines, contentHolder.numChildren - 1);
var rowInfo:Array = contentHolder.rowInfo;
lines.graphics.clear();
var linesBody:Sprite = Sprite(lines.getChildByName("body"));
if (!linesBody)
{
linesBody = new UIComponent();
linesBody.name = "body";
linesBody.mouseEnabled = false;
lines.addChild(linesBody);
}
linesBody.graphics.clear();
while (linesBody.numChildren)
{
linesBody.removeChildAt(0);
}
var tmpHeight:Number = unscaledHeight - 1; // FIXME: can remove?
var lineCol:uint;
var i:int;
var len:uint = visibleColumns ? visibleColumns.length : 0;
var rowlen:uint = contentHolder.listItems.length
// draw horizontalGridlines if needed.
lineCol = getStyle("horizontalGridLineColor");
if (getStyle("horizontalGridLines"))
{
for (i = 0; i < rowlen; i++)
{
var yy:Number = rowInfo[i].y + rowInfo[i].height;
if (yy < contentHolder.height)
drawHorizontalSeparator(linesBody, i, lineCol, yy);
}
}
if (separators.top)
drawHorizontalSeparator(linesBody, i++, 0, rowInfo[0].y, true);
if (separators.bottom && rowlen > 0)
drawHorizontalSeparator(linesBody, i++, 0, rowInfo[rowlen - 1].y + rowInfo[rowlen - 1].height, true);
var vLines:Boolean = getStyle("verticalGridLines");
lineCol = getStyle("verticalGridLineColor");
if (len)
{
var colBGs:Sprite = Sprite(contentHolder.getChildByName("colBGs"));
// traverse the columns, set the sizes, draw the column backgrounds
var lastChild:int = -1;
var xx:Number = 0;
for (i = 0; i < len; i++)
{
// only draw the vertical separator for the ones in the middle (not beginning and not end)
if (vLines && i < (len - 1))
drawVerticalSeparator(linesBody, i, lineCol, xx + visibleColumns[i].width);
var col:DataGridColumn = visibleColumns[i];
var bgCol:Object;
if (enabled)
bgCol = col.getStyle("backgroundColor");
else
bgCol = col.getStyle("backgroundDisabledColor");
if (bgCol !== null && !isNaN(Number(bgCol)))
{
if (!colBGs)
{
colBGs = new FlexSprite();
colBGs.mouseEnabled = false;
colBGs.name = "colBGs";
contentHolder.addChildAt(colBGs, contentHolder.getChildIndex(contentHolder.getChildByName("rowBGs")) + 1);
}
drawColumnBackground(colBGs, i, Number(bgCol), col);
lastChild = i;
}
else if (colBGs)
{
var background:Shape = Shape(colBGs.getChildByName(i.toString()));
if (background)
{
var g:Graphics = background.graphics;
g.clear();
colBGs.removeChild(background);
}
}
xx += visibleColumns[i].width;
}
if (colBGs && colBGs.numChildren)
{
while (colBGs.numChildren)
{
var bg:DisplayObject = colBGs.getChildAt(colBGs.numChildren - 1);
if (parseInt(bg.name) > lastChild)
colBGs.removeChild(bg);
else
break;
}
}
}
if (separators.right && visibleColumns && visibleColumns.length)
{
if (contentHolder.listItems.length && contentHolder.listItems[0].length)
drawVerticalSeparator(linesBody, i++, 0, contentHolder.listItems[0][len - 1].x + visibleColumns[len - 1].width, true);
else
{
xx = 0;
for (i = 0; i < len; i++)
{
xx += visibleColumns[i].width;
}
drawVerticalSeparator(linesBody, i++, 0, xx, true);
}
}
if (separators.left)
drawVerticalSeparator(linesBody, i++, 0, 0, true);
}
mx_internal function _drawHeaderBackground(headerBG:UIComponent):void
{
drawHeaderBackground(headerBG);
}
/**
* Draws the background of the headers into the given
* UIComponent. The graphics drawn may be scaled horizontally
* if the component's width changes or this method will be
* called again to redraw at a different width and/or height
*
* @param headerBG A UIComponent that will contain the header
* background graphics.
*/
protected function drawHeaderBackground(headerBG:UIComponent):void
{
DataGridHeader(headerBG.parent)._drawHeaderBackground(headerBG);
}
mx_internal function _clearSeparators():void
{
clearSeparators();
}
/**
* Removes column header separators that the user normally uses
* to resize columns.
*/
protected function clearSeparators():void
{
DataGridHeader(header)._clearSeparators();
if (lockedColumnHeader)
DataGridHeader(lockedColumnHeader)._clearSeparators();
}
mx_internal function _drawSeparators():void
{
drawSeparators();
}
/**
* Creates and displays the column header separators that the user
* normally uses to resize columns. This implementation uses
* the same Sprite as the lines and column backgrounds and adds
* instances of the headerSeparatorSkin and attaches mouse
* listeners to them in order to know when the user wants
* to resize a column.
*/
protected function drawSeparators():void
{
DataGridHeader(header)._drawSeparators();
if (lockedColumnHeader)
DataGridHeader(lockedColumnHeader)._drawSeparators();
}
/**
* @private
* Update sortIndex and sortDirection based on sort info availabled in
* underlying data provider.
*/
private function updateSortIndexAndDirection():void
{
// Don't show sort indicator if sortableColumns is false or if the
// column sorted on has sortable="false"
if (!sortableColumns)
{
lastSortIndex = sortIndex;
sortIndex = -1;
if (lastSortIndex != sortIndex)
invalidateDisplayList();
return;
}
if (!dataProvider)
return;
var view:ICollectionView = ICollectionView(dataProvider);
var sort:Sort = view.sort;
if (!sort)
{
sortIndex = lastSortIndex = -1;
return;
}
var fields:Array = sort.fields;
if (!fields)
return;
if (fields.length != 1)
{
lastSortIndex = sortIndex;
sortIndex = -1;
if (lastSortIndex != sortIndex)
invalidateDisplayList();
return;
}
// fields.length == 1, so the collection is sorted on a single field.
var sortField:SortField = fields[0];
var n:int = _columns.length;
for (var i:int = 0; i < n; i++)
{
if (_columns[i].dataField == sortField.name)
{
sortIndex = _columns[i].sortable ? i : -1;
sortDirection = sortField.descending ? "DESC" : "ASC";
return;
}
}
}
mx_internal function _placeSortArrow():void
{
placeSortArrow();
}
/**
* Draws the sort arrow graphic on the column that is the current sort key.
* This implementation creates or reuses an instance of the skin specified
* by sortArrowSkin style property and places
* it in the appropriate column header. It
* also shrinks the size of the column header if the text in the header
* would be obscured by the sort arrow.
*/
protected function placeSortArrow():void
{
DataGridHeader(header)._placeSortArrow();
if (lockedColumnHeader)
DataGridHeader(lockedColumnHeader)._placeSortArrow();
}
/**
* @private
*/
private function sortByColumn(index:int):void
{
var c:DataGridColumn = columns[index];
var desc:Boolean = c.sortDescending;
// do the sort if we're allowed to
if (c.sortable)
{
var s:Sort = collection.sort;
var f:SortField;
if (s)
{
s.compareFunction = null;
// analyze the current sort to see what we've been given
var sf:Array = s.fields;
if (sf)
{
for (var i:int = 0; i < sf.length; i++)
{
if (sf[i].name == c.dataField)
{
// we're part of the current sort
f = sf[i]
// flip the logic so desc is new desired order
desc = !f.descending;
break;
}
}
}
}
else
s = new Sort;
if (!f)
f = new SortField(c.dataField);
c.sortDescending = desc;
var dir:String = (desc) ? "DESC" : "ASC";
sortDirection = dir;
// set the grid's sortIndex
lastSortIndex = sortIndex;
sortIndex = index;
sortColumn = c;
// if you have a labelFunction you must supply a sortCompareFunction
f.name = c.dataField;
if (c.sortCompareFunction != null)
{
f.compareFunction = c.sortCompareFunction;
}
else
{
f.compareFunction = null;
}
f.descending = desc;
s.fields = [f];
}
collection.sort = s;
collection.refresh();
}
/**
* @private
*/
private function setEditedItemPosition(coord:Object):void
{
bEditedItemPositionChanged = true;
_proposedEditedItemPosition = coord;
invalidateDisplayList();
}
/**
* @private
* focus an item renderer in the grid - harder than it looks
*/
private function commitEditedItemPosition(coord:Object):void
{
if (!enabled || !editable)
return;
if (!collection || collection.length == 0)
return;
// just give focus back to the itemEditorInstance
if (itemEditorInstance && coord &&
itemEditorInstance is IFocusManagerComponent &&
_editedItemPosition.rowIndex == coord.rowIndex &&
_editedItemPosition.columnIndex == coord.columnIndex)
{
IFocusManagerComponent(itemEditorInstance).setFocus();
return;
}
// dispose of any existing editor, saving away its data first
if (itemEditorInstance)
{
var reason:String;
if (!coord)
{
reason = DataGridEventReason.OTHER;
}
else
{
reason = (!editedItemPosition || coord.rowIndex == editedItemPosition.rowIndex) ?
DataGridEventReason.NEW_COLUMN :
DataGridEventReason.NEW_ROW;
}
if (!endEdit(reason) && reason != DataGridEventReason.OTHER)
return;
}
// store the value
_editedItemPosition = coord;
// allow setting of undefined to dispose item editor instance
if (!coord)
return;
if (dontEdit)
{
return;
}
var rowIndex:int = coord.rowIndex;
var colIndex:int = coord.columnIndex;
if (displayableColumns.length != _columns.length)
{
for (var i:int = 0; i < displayableColumns.length; i++)
{
if (displayableColumns[i].colNum >= colIndex)
{
colIndex = i;
break;
}
}
if (i == displayableColumns.length)
colIndex = 0;
}
// trace("commitEditedItemPosition ", coord.rowIndex, selectedIndex);
var needChangeEvent:Boolean = false;
if (selectedIndex != coord.rowIndex)
{
commitSelectedIndex(coord.rowIndex);
needChangeEvent = true;
}
scrollToEditedItem(rowIndex, colIndex);
// get the actual references for the column, row, and item
var item:IListItemRenderer = actualContentHolder.listItems[actualRowIndex][actualColIndex];
if (!item)
{
// assume that editing was cancelled
commitEditedItemPosition(null);
return;
}
if (!isItemEditable(item.data))
{
// assume that editing was cancelled
commitEditedItemPosition(null);
return;
}
if (needChangeEvent)
{
var evt:ListEvent = new ListEvent(ListEvent.CHANGE);
evt.columnIndex = coord.columnIndex;
evt.rowIndex = coord.rowIndex;;
evt.itemRenderer = item;
dispatchEvent(evt);
}
var event:DataGridEvent =
new DataGridEvent(DataGridEvent.ITEM_EDIT_BEGIN, false, true);
// ITEM_EDIT events are cancelable
event.columnIndex = displayableColumns[colIndex].colNum;
event.rowIndex = _editedItemPosition.rowIndex;
event.itemRenderer = item;
dispatchEvent(event);
lastEditedItemPosition = _editedItemPosition;
// user may be trying to change the focused item renderer
if (bEditedItemPositionChanged)
{
bEditedItemPositionChanged = false;
commitEditedItemPosition(_proposedEditedItemPosition);
_proposedEditedItemPosition = undefined;
}
if (!itemEditorInstance)
{
// assume that editing was cancelled
commitEditedItemPosition(null);
}
}
// computes actualRowIndex, actualColIndex and actualContentHolder by
// taking inputs for rowIndex, colIndex and scrolling to the right
// place
private function scrollToEditedItem(rowIndex:int, colIndex:int):void
{
actualContentHolder = listContent;
var listItems:Array = actualContentHolder.listItems;
var lastRowIndex:int = verticalScrollPosition + listItems.length - 1 + lockedRowCount;
var partialRow:int = (rowInfo[listItems.length - 1].y + rowInfo[listItems.length - 1].height > listContent.height) ? 1 : 0;
// actual row/column is the offset into one of the containers
if (rowIndex > lockedRowCount)
{
// not a locked editable row make sure it is on screen
if (rowIndex < verticalScrollPosition + lockedRowCount)
verticalScrollPosition = rowIndex - lockedRowCount;
else
{
// variable row heights means that we can't know how far to scroll sometimes so we loop
// until we get it right
while (rowIndex > lastRowIndex ||
// we're the last row, and we're partially visible, but we're not
// the top scrollable row already
(rowIndex == lastRowIndex && rowIndex > verticalScrollPosition + lockedRowCount &&
partialRow))
{
if (verticalScrollPosition == maxVerticalScrollPosition)
break;
verticalScrollPosition = Math.min(verticalScrollPosition + (rowIndex > lastRowIndex ? rowIndex - lastRowIndex : partialRow), maxVerticalScrollPosition);
lastRowIndex = verticalScrollPosition + listItems.length - 1 + lockedRowCount;
partialRow = (rowInfo[listItems.length - 1].y + rowInfo[listItems.length - 1].height > listContent.height) ? 1 : 0;
}
}
actualRowIndex = rowIndex - verticalScrollPosition - lockedRowCount;
}
else
{
if (rowIndex == lockedRowCount)
{
verticalScrollPosition = 0;
actualRowIndex = rowIndex - lockedRowCount;
}
else
{
if (lockedRowCount)
actualContentHolder = lockedRowContent;
actualRowIndex = rowIndex;
}
}
// reset since actualContentHolder could have changed
listItems = actualCon