blob: 63a0eb3884446124a7e87961fe2660120545773a [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package spark.components
{
import flash.display.InteractiveObject;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import mx.collections.IList;
import mx.core.IFactory;
import mx.core.mx_internal;
import mx.events.PropertyChangeEvent;
import mx.managers.ILayoutManagerClient;
import spark.components.gridClasses.CellPosition;
import spark.components.gridClasses.GridColumn;
import spark.components.gridClasses.GridColumnHeaderView;
import spark.components.gridClasses.GridDimensionsView;
import spark.components.gridClasses.GridHeaderLayout;
import spark.components.gridClasses.GridHeaderViewLayout;
import spark.components.gridClasses.GridLayout;
import spark.components.gridClasses.GridView;
import spark.components.gridClasses.IDataGridElement;
import spark.components.gridClasses.IGridItemRenderer;
import spark.events.GridEvent;
import spark.utils.MouseEventUtil;
use namespace mx_internal;
//--------------------------------------
// Events
//--------------------------------------
/**
* Dispatched when the mouse button is pressed over a column header.
*
* @eventType spark.events.GridEvent.GRID_MOUSE_DOWN
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Event(name="gridMouseDown", type="spark.events.GridEvent")]
/**
* Dispatched after a <code>gridMouseDown</code> event
* if the mouse moves before the button is released.
*
* @eventType spark.events.GridEvent.GRID_MOUSE_DRAG
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Event(name="gridMouseDrag", type="spark.events.GridEvent")]
/**
* Dispatched after a <code>gridMouseDown</code> event
* when the mouse button is released, even
* if the mouse is no longer within the GridColumnHeaderGroup.
*
* @eventType spark.events.GridEvent.GRID_MOUSE_UP
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Event(name="gridMouseUp", type="spark.events.GridEvent")]
/**
* Dispatched when the mouse enters a column header.
*
* @eventType spark.events.GridEvent.GRID_ROLL_OVER
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Event(name="gridRollOver", type="spark.events.GridEvent")]
/**
* Dispatched when the mouse leaves a column header.
*
* @eventType spark.events.GridEvent.GRID_ROLL_OUT
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Event(name="gridRollOut", type="spark.events.GridEvent")]
/**
* Dispatched when the mouse is clicked over a column header.
*
* @eventType spark.events.GridEvent.GRID_CLICK
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Event(name="gridClick", type="spark.events.GridEvent")]
/**
* Dispatched when the mouse is double-clicked over a column header.
*
* @eventType spark.events.GridEvent.GRID_DOUBLE_CLICK
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Event(name="gridDoubleClick", type="spark.events.GridEvent")]
/**
* Dispatched after a <code>separatorMouseDown</code> event
* if the mouse moves before the button is released.
*
* @eventType spark.events.GridEvent.SEPARATOR_MOUSE_DRAG
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Event(name="separatorMouseDrag", type="spark.events.GridEvent")]
/**
* Dispatched after a <code>separatorMouseDown</code> event
* when the mouse button is released, even if the mouse is
* no longer within the separator affordance.
*
* @eventType spark.events.GridEvent.SEPARATOR_MOUSE_UP
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Event(name="separatorMouseUp", type="spark.events.GridEvent")]
/**
* Dispatched when the mouse enters the area defined by a column
* separator and <code>separatorMouseWidth</code>.
*
* @eventType spark.events.GridEvent.SEPARATOR_ROLL_OVER
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Event(name="separatorRollOver", type="spark.events.GridEvent")]
/**
* Dispatched when the mouse leaves the area defined by a column
* separator and <code>separatorMouseWidth</code>.
*
* @eventType spark.events.GridEvent.SEPARATOR_ROLL_OUT
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Event(name="separatorRollOut", type="spark.events.GridEvent")]
/**
* Dispatched when the mouse is clicked over a column header separator.
*
* @eventType spark.events.GridEvent.SEPARATOR_CLICK
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Event(name="separatorClick", type="spark.events.GridEvent")]
/**
* Dispatched when the mouse is double-clicked over a column
* header separator.
*
* @eventType spark.events.GridEvent.SEPARATOR_DOUBLE_CLICK
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Event(name="separatorDoubleClick", type="spark.events.GridEvent")]
//--------------------------------------
// Styles
//--------------------------------------
/**
* Horizontal space on either side of a column separator that's considered to be
* part of the separator for the sake of mouse event dispatching.
*
* <p>Separators are often just one pixel wide which makes interacting with them difficult.
* This value is used by <code>getSeparatorIndexAt()</code> to give separators a wider
* area, so that separator events are dispatched when the mouse is closer than
* <code>separatorMouseWidth</code> to the horizontal midpoint of a separator.</p>
*
* @default 5
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Style(name="separatorAffordance", type="Number", format="Length", inherit="no")]
/**
* Bottom inset, in pixels, for all header renderers.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Style(name="paddingBottom", type="Number", format="Length", inherit="no")]
/**
* Left inset, in pixels, for the first header renderer.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Style(name="paddingLeft", type="Number", format="Length", inherit="no")]
/**
* Right inset, in pixels, for the last header renderer.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Style(name="paddingRight", type="Number", format="Length", inherit="no")]
/**
* Top inset, in pixels, for all header renderers.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
[Style(name="paddingTop", type="Number", format="Length", inherit="no")]
//--------------------------------------
// Other metadata
//--------------------------------------
[IconFile("GridColumnHeaderGroup.png")]
/**
* The GridColumnHeaderGroup class displays a row of column headers
* and separators aligned with the grid's layout.
*
* <p>Headers are rendered by the class specified by the <code>headerRenderer</code> property.
* Separators are rendered by the class specified by the <code>columnSeparator</code> property.
* The layout, which cannot be changed, is virtual; that means renderers and separators that have been
* scrolled out of view are reused.</p>
*
* @mxml <p>The <code>&lt;s:GridColumnHeaderGroup&gt;</code> tag inherits all of the tag
* attributes of its superclass and adds the following tag attributes:</p>
*
* <pre>
* &lt;s:GridColumnHeaderGroup
* <strong>Properties</strong>
* columnSeperator="null"
* dataGrid="null"
* downColumnIndex="-1"
* headerRenderer="null"
* hoverColumnIndex="-1"
* selectedColumnIndex="-1"
* highlightSelectedColumn="false"
* visibleSortIndicatorIndices="<i>empty Vector.&lt;int&gt<i>"
*
* <strong>Styles</strong>
* paddingBottom="0"
* paddingLeft="0"
* paddingRight="0"
* paddingTop="0"
* separatorAffordance="5"
*
* <strong>Events</strong>
* gridClick="<i>No default</i>"
* gridDoubleClick="<i>No default</i>"
* gridMouseDown="<i>No default</i>"
* gridMouseDrag="<i>No default</i>"
* gridMouseUp="<i>No default</i>"
* gridMouseRollOut="<i>No default</i>"
* gridMouseRollOver="<i>No default</i>"
* separatorClick="<i>No default</i>"
* separatorDoubleClick="<i>No default</i>"
* separatorMouseDrag="<i>No default</i>"
* separatorMouseUp="<i>No default</i>"
* separatorMouseRollOut="<i>No default</i>"
* separatorMouseRollOver="<i>No default</i>"
* /&gt;
* </pre>
*
* @see Grid
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
public class GridColumnHeaderGroup extends Group implements IDataGridElement
{
include "../core/Version.as";
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
public function GridColumnHeaderGroup()
{
super();
layout = new GridHeaderLayout();
// Event handlers that dispatch GridEvents
MouseEventUtil.addDownDragUpListeners(this,
gchg_mouseDownDragUpHandler,
gchg_mouseDownDragUpHandler,
gchg_mouseDownDragUpHandler);
addEventListener(MouseEvent.MOUSE_MOVE, gchg_mouseMoveHandler);
addEventListener(MouseEvent.ROLL_OUT, gchg_mouseRollOutHandler);
addEventListener(MouseEvent.CLICK, gchg_clickHandler);
addEventListener(MouseEvent.DOUBLE_CLICK, gchg_doubleClickHandler);
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
/**
* @private
*/
private function dispatchChangeEvent(type:String):void
{
if (hasEventListener(type))
dispatchEvent(new Event(type));
}
//----------------------------------
// columnSeparator
//----------------------------------
private var _columnSeparator:IFactory = null;
[Bindable("columnSeparatorChanged")]
/**
* A visual element that's displayed between each column.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
public function get columnSeparator():IFactory
{
return _columnSeparator;
}
/**
* @private
*/
public function set columnSeparator(value:IFactory):void
{
if (_columnSeparator == value)
return;
_columnSeparator = value;
invalidateDisplayList();
dispatchChangeEvent("columnSeparatorChanged");
}
//----------------------------------
// dataGrid
//----------------------------------
private var _dataGrid:DataGrid = null;
private var lockedColumnCountChanged:Boolean = false;
[Bindable("dataGridChanged")]
/**
* The DataGrid control that defines the column layout and
* horizontal scroll position for this component.
* This property is set by the DataGrid control after
* its <code>grid</code> skin part has been added.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
public function get dataGrid():DataGrid
{
return _dataGrid;
}
/**
* @private
*/
public function set dataGrid(value:DataGrid):void
{
if (_dataGrid == value)
return;
if (_dataGrid && _dataGrid.grid)
{
_dataGrid.grid.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, grid_propertyChangeHandler);
_dataGrid.grid.removeEventListener("columnsChanged", grid_propertyChangeHandler);
_dataGrid.grid.removeEventListener("lockedColumnCountChanged", grid_propertyChangeHandler);
}
_dataGrid = value;
if (_dataGrid && _dataGrid.grid)
{
_dataGrid.grid.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, grid_propertyChangeHandler);
_dataGrid.grid.addEventListener("columnsChanged", grid_propertyChangeHandler);
_dataGrid.grid.addEventListener("lockedColumnCountChanged", grid_propertyChangeHandler);
}
layout.clearVirtualLayoutCache();
invalidateSize();
invalidateDisplayList();
dispatchChangeEvent("dataGridChanged");
}
/**
* @private
*/
private function grid_propertyChangeHandler(event:Event):void
{
const ghl:GridHeaderLayout = layout as GridHeaderLayout;
if (!ghl)
return;
const pce:PropertyChangeEvent = event as PropertyChangeEvent;
if (pce && (pce.property == "horizontalScrollPosition"))
ghl.centerGridColumnHeaderView.horizontalScrollPosition = Number(pce.newValue);
else if ((event.type == "columnsChanged") || (event.type == "lockedColumnCountChanged"))
invalidateProperties();
}
//----------------------------------
// downColumnIndex
//----------------------------------
private var _downColumnIndex:int = -1;
[Bindable("downColumnIndexChanged")]
/**
* Specifies the column index of the header renderer currently
* being pressed down by the user.
*
* <p>Setting <code>downColumnIndex</code> to -1 (the default) means
* that the column index is undefined, and the header renderer has
* its <code>down</code> property set to <code>false</code>.</p>
*
* @default -1
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
public function get downColumnIndex():int
{
return _downColumnIndex;
}
/**
* @private
*/
public function set downColumnIndex(value:int):void
{
if (_downColumnIndex == value)
return;
_downColumnIndex = value;
invalidateDisplayList();
dispatchChangeEvent("downColumnIndexChanged");
}
//----------------------------------
// headerRenderer
//----------------------------------
private var _headerRenderer:IFactory = null;
[Bindable("headerRendererChanged")]
/**
* The IGridItemRenderer class used to renderer each column header.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
public function get headerRenderer():IFactory
{
return _headerRenderer; // TODO: find a way to make it possible to specify this with DataGrid...
}
/**
* @private
*/
public function set headerRenderer(value:IFactory):void
{
if (value == _headerRenderer)
return;
_headerRenderer = value;
layout.clearVirtualLayoutCache();
invalidateSize();
invalidateDisplayList();
dispatchChangeEvent("headerRendererChanged");
}
//----------------------------------
// hoverColumnIndex
//----------------------------------
private var _hoverColumnIndex:int = -1;
[Bindable("hoverColumnIndexChanged")]
/**
* Specifies the column index of the header renderer currently
* being hovered over by the user.
*
* <p>Setting <code>hoverColumnIndex</code> to -1, the default, means that
* the column index is undefined, and the header renderer has its
* <code>hovered</code> property set to <code>false</code>.</p>
*
* @default -1
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
public function get hoverColumnIndex():int
{
return _hoverColumnIndex;
}
/**
* @private
*/
public function set hoverColumnIndex(value:int):void
{
if (_hoverColumnIndex == value)
return;
_hoverColumnIndex = value;
invalidateDisplayList();
dispatchChangeEvent("hoverColumnIndexChanged");
}
//----------------------------------
// selectedColumnIndex
//----------------------------------
private var _selectedColumnIndex:int = -1;
[Bindable("selectedColumnIndexChanged")]
/**
* Specifies the column index of the header renderer currently selected by the user.
* The selected property of the header renderer for selectedColumnIndex will be true
* and false for all other header renderers.
*
* <p>Setting selectedColumnIndex to -1, the default, means that no column is selected and
* selected property for all header renderers will be false.</p>
*
* @default -1
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
public function get selectedColumnIndex():int
{
return _selectedColumnIndex;
}
/**
* @private
*/
public function set selectedColumnIndex(value:int):void
{
if (_selectedColumnIndex == value)
return;
_selectedColumnIndex = value;
invalidateDisplayList();
dispatchChangeEvent("selectedColumnIndexChanged");
}
//----------------------------------
// highlightSelectedColumn
//----------------------------------
private var _highlightSelectedColumn:Boolean = false;
[Bindable("highlightSelectedColumnChanged")]
/**
* The DefaultGridHeaderRenderer only highlights the selected column when selectedColumnIndex is
* valid and highlightSelectedColumn is true. If this property is set to true and
* the selectedColumnIndex is -1, then the selectedColumnIndex is set to the hoverColumnIndex,
* and if that's -1, then the first visible column.
*
* @default false
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
public function get highlightSelectedColumn():Boolean
{
return _highlightSelectedColumn;
}
/**
* @private
*/
public function set highlightSelectedColumn(value:Boolean):void
{
if (_highlightSelectedColumn == value)
return;
// If this property's value is being changed to true, make sure that the
// initial value of selectedColumnIndex corresponds to a visible column.
// Tabbing to the DataGrid header shouldn't cause the DataGrid to scroll.
if (value)
{
if ((selectedColumnIndex == -1) && (hoverColumnIndex != 1))
selectedColumnIndex = hoverColumnIndex;
if (dataGrid.grid && !dataGrid.grid.isCellVisible(-1, selectedColumnIndex))
{
const visibleColumnIndices:Vector.<int> = dataGrid.grid.getVisibleColumnIndices();
selectedColumnIndex = (visibleColumnIndices.length > 0) ? visibleColumnIndices[0] : -1;
}
}
_highlightSelectedColumn = value;
invalidateDisplayList();
dispatchChangeEvent("highlightSelectedColumnChanged");
}
//----------------------------------
// visibleSortIndicatorIndices
//----------------------------------
private var _visibleSortIndicatorIndices:Vector.<int> = new Vector.<int>();
[Bindable("visibleSortIndicatorIndicesChanged")]
/**
* A vector of column indices corresponding to the header renderers
* which currently have their sort indicators visible.
*
* @default an empty Vector.&lt;int&gt;
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
public function get visibleSortIndicatorIndices():Vector.<int>
{
return _visibleSortIndicatorIndices.concat();
}
/**
* @private
*/
public function set visibleSortIndicatorIndices(value:Vector.<int>):void
{
// Defensively copy vector and tolerate null
const valueCopy:Vector.<int> = (value) ? value.concat() : new Vector.<int>();
_visibleSortIndicatorIndices = valueCopy;
invalidateDisplayList();
dispatchChangeEvent("visibleSortIndicatorIndicesChanged");
}
/**
* Returns <code>true</code> if the sort indicator for the specified column
* is visible.
* This is just a more efficient version of:
* <pre>
* visibleSortIndicatorIndices.indexOf(columnIndex) != -1</pre>
*
* @param columnIndex The 0-based column index of the header renderer's column.
*
* @return <code>true</code> if the sort indicator for the specified column
* is visible.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
public function isSortIndicatorVisible(columnIndex:int):Boolean
{
return (_visibleSortIndicatorIndices.indexOf(columnIndex) != -1);
}
/**
* True if either of this GridColumnHeaderGroup's views contains the global
* coordinates in the event, or if they fall over this component's padding.
*/
public function containsMouseEvent(event:MouseEvent):Boolean
{
return containsGlobalCoordinates(new Point(event.stageX, event.stageY));
}
public function containsGlobalCoordinates(coordinates:Point):Boolean
{
var globalPosition:Point = localToGlobal(new Point(getLayoutBoundsX(), getLayoutBoundsY()));
var bounds:Rectangle = new Rectangle(globalPosition.x, globalPosition.y, getLayoutBoundsWidth(), getLayoutBoundsHeight());
return bounds.containsPoint(coordinates);
}
public function areCoordinatesOverAHeaderView(coordinates:Point):Boolean
{
return getHeaderViewUnderGlobalCoordinates(coordinates) != null;
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
private function createGridColumnHeaderView():Group
{
const view:GridColumnHeaderView = new GridColumnHeaderView();
view.gridColumnHeaderGroup = this;
addElement(view);
return view;
}
/**
* Create and/or configure the GridColumnHeaderViews. We're assuming that the
* DataGrid's GridViews have already been created.
*/
public function configureGridColumnHeaderViews():void
{
const headerLayout:GridHeaderLayout = layout as GridHeaderLayout;
if (!headerLayout)
return;
if (headerLayout.centerGridColumnHeaderView == null)
headerLayout.centerGridColumnHeaderView = createGridColumnHeaderView();
if (dataGrid.lockedColumnCount > 0)
{
headerLayout.leftGridColumnHeaderView = createGridColumnHeaderView();
}
else if (headerLayout.leftGridColumnHeaderView)
{
removeElement(headerLayout.leftGridColumnHeaderView);
headerLayout.leftGridColumnHeaderView = null;
}
const gridLayout:GridLayout = dataGrid.grid.layout as GridLayout;
GridHeaderViewLayout(headerLayout.centerGridColumnHeaderView.layout).gridView = gridLayout.centerGridView;
if (headerLayout.leftGridColumnHeaderView)
GridHeaderViewLayout(headerLayout.leftGridColumnHeaderView.layout).gridView = gridLayout.leftGridView;
}
/**
* Returns the column index corresponding to the specified coordinates,
* or -1 if the coordinates are out of bounds. The coordinates are
* resolved with respect to the GridColumnHeaderGroup layout target.
*
* <p>If all of the columns or rows for the grid have not yet been scrolled
* into view, the returned index may only be an approximation,
* based on the <code>typicalItem</code> property of all columns.</p>
*
* @param x The pixel's x coordinate relative to the <code>columnHeaderGroup</code>.
*
* @param y The pixel's y coordinate relative to the <code>columnHeaderGroup</code>.
*
* @return the index of the column or -1 if the coordinates are out of bounds.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
public function getHeaderIndexAt(x:Number, y:Number):int
{
const view:Group = getColumnHeaderViewAtX(x);
return GridHeaderViewLayout(view.layout).getHeaderIndexAt(x, y);
}
/**
* Returns the column separator index corresponding to the specified
* coordinates, or -1 if the coordinates don't overlap a separator. The
* coordinates are resolved with respect to the GridColumnHeaderGroup layout target.
*
* <p>A separator is considered to "overlap" the specified location if the
* x coordinate is within <code>separatorMouseWidth</code> of separator's
* horizontal midpoint.</p>
*
* <p>The separator index is the same as the index of the column on the left,
* assuming that this component's <code>layoutDirection</code> is <code>"ltr"</code>.
* That means all column headers are flanked by two separators, except for the first
* visible column, which just has a separator on the right, and the last visible
* column, which just has a separator on the left.</p>
*
* <p>If all of the columns or rows for the grid have not yet been scrolled
* into view, the returned index may only be an approximation,
* based on the <code>typicalItem</code> property of all columns.</p>
*
* @param x The pixel's x coordinate relative to the <code>columnHeaderGroup</code>.
*
* @param y The pixel's y coordinate relative to the <code>columnHeaderGroup</code>.
*
* @return the index of the column or -1 if the coordinates don't overlap a separator.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
public function getSeparatorIndexAt(x:Number, y:Number):int
{
// TODO: fix this: x coordinate has to be adjusted
const view:Group = getColumnHeaderViewAtX(x);
return GridHeaderViewLayout(view.layout).getSeparatorIndexAt(x, y);
}
/**
* If the requested header renderer is visible, returns a reference to
* the header renderer currently displayed for the specified column.
* Note that once the returned header renderer is no longer visible it
* may be recycled and its properties reset.
*
* <p>If the requested header renderer is not visible then,
* each time this method is called, a new header renderer is created.
* The new item renderer is not visible</p>
*
* <p>The width of the returned renderer is the same as for item renderers
* returned by DataGrid/getItemRendererAt().</p>
*
* @param columnIndex The 0-based column index of the header renderer's column.
*
* @return The item renderer or null if the column index is invalid.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
public function getHeaderRendererAt(columnIndex:int):IGridItemRenderer
{
// TODO: fix this: do the work here, rather than the layout
const view:Group = getColumnHeaderViewAtIndex(columnIndex);
return GridHeaderViewLayout(view.layout).getHeaderRendererAt(columnIndex);
}
/**
* Returns the current pixel bounds of the specified header (renderer), or null if
* no such column exists. Header bounds are reported in GridColumnHeaderGroup coordinates.
*
* <p>If all of the visible columns preceding the specified column have not
* yet been scrolled into view, the returned bounds may only be an approximation,
* based on all of the Grid's <code>typicalItem</code>s.</p>
*
* @param columnIndex The 0-based index of the column.
* @return A <code>Rectangle</code> that represents the column header's pixel bounds, or null.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
public function getHeaderBounds(columnIndex:int):Rectangle
{
// TODO: fix this: do the work here, rather than the layout
const view:Group = getColumnHeaderViewAtIndex(columnIndex);
return GridHeaderViewLayout(view.layout).getHeaderBounds(columnIndex);
}
/**
* @private
*/
override public function invalidateSize():void
{
super.invalidateSize();
const ghl:GridHeaderLayout = layout as GridHeaderLayout;
if (!ghl)
return;
if (ghl.leftGridColumnHeaderView)
ghl.leftGridColumnHeaderView.invalidateSize();
if (ghl.centerGridColumnHeaderView)
ghl.centerGridColumnHeaderView.invalidateSize();
}
/**
* @private
*/
override public function invalidateDisplayList():void
{
super.invalidateDisplayList();
const ghl:GridHeaderLayout = layout as GridHeaderLayout;
if (!ghl)
return;
if (ghl.leftGridColumnHeaderView)
ghl.leftGridColumnHeaderView.invalidateDisplayList();
if (ghl.centerGridColumnHeaderView)
ghl.centerGridColumnHeaderView.invalidateDisplayList();
}
//--------------------------------------------------------------------------
//
// GridEvent dispatching
//
//--------------------------------------------------------------------------
// The down,roll pairs of variables below define column indices. Only one
// member of each pair will be not equal to -1 at a time.
private var rollColumnIndex:int = -1; // column mouse has rolled into
private var rollSeparatorIndex:int = -1; // separator mouse has rolled into
private var pressColumnIndex:int = -1; // column button press occurred on
private var pressSeparatorIndex:int = -1; // separator button press occurred on
/**
* @private
* Return the GridView whose bounds contain the MouseEvent, or null. Note that the
* comparison is based strictly on the event's location and the GridViews' bounds.
* The event's target can be anything.
*/
private function getHeaderViewUnderGlobalCoordinates(globalCoordinates:Point):GridColumnHeaderView
{
const ghl:GridHeaderLayout = layout as GridHeaderLayout;
const centerGridColumnHeaderView:GridColumnHeaderView = GridColumnHeaderView(ghl.centerGridColumnHeaderView);
if (centerGridColumnHeaderView && centerGridColumnHeaderView.containsGlobalPoint(globalCoordinates))
return centerGridColumnHeaderView;
const leftGridColumnHeaderView:GridColumnHeaderView = GridColumnHeaderView(ghl.leftGridColumnHeaderView);
if (leftGridColumnHeaderView && leftGridColumnHeaderView.containsGlobalPoint(globalCoordinates))
return leftGridColumnHeaderView;
return null;
}
// TODO: apologize for stashing the separatorIndex in headerCP.rowIndex
private function eventToHeaderLocations(event:MouseEvent, headerCP:CellPosition, headerXY:Point):Boolean
{
const stageXY:Point = new Point(event.stageX, event.stageY);
const view:Group = getHeaderViewUnderGlobalCoordinates(stageXY);
if (!view)
return false;
const viewXY:Point = view.globalToLocal(stageXY);
const viewLayout:GridHeaderViewLayout = view.layout as GridHeaderViewLayout;
const gdv:GridDimensionsView = viewLayout.gridView.gridViewLayout.gridDimensionsView;
const separatorIndex:int = viewLayout.getSeparatorIndexAt(viewXY.x, 0);
headerCP.rowIndex = (separatorIndex != -1) ? separatorIndex + gdv.viewColumnIndex : -1;
headerCP.columnIndex = (separatorIndex == -1) ? viewLayout.getHeaderIndexAt(viewXY.x, viewXY.y) + gdv.viewColumnIndex : -1;
headerXY.x = viewXY.x + gdv.viewOriginX;
headerXY.y = viewXY.y;
return true;
}
/**
* @private
*
* This method is called when a MOUSE_DOWN event occurs within the column header group and
* for all subsequent MOUSE_MOVE events until the button is released (even if the
* mouse leaves the column header group). The last event in such a "down drag up" gesture is
* always a MOUSE_UP. By default this method dispatches GRID_MOUSE_DOWN,
* GRID_MOUSE_DRAG, or a GRID_MOUSE_UP event in response to the the corresponding
* mouse event on a column header or SEPARATOR_MOUSE_DOWN, SEPARATOR_MOUSE_DRAG,
* or a SEPARATOR_MOUSE_UP event in response to the the corresponding
* mouse event on a column header separator.
*
* The GridEvent's columnIndex, column itemRenderer properties correspond to the
* column header or separator under the mouse.
*
* @param event A MOUSE_DOWN, MOUSE_MOVE, or MOUSE_UP MouseEvent from a
* down/move/up gesture initiated within the column header group.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
protected function gchg_mouseDownDragUpHandler(event:MouseEvent):void
{
const eventHeaderCP:CellPosition = new CellPosition();
const eventHeaderXY:Point = new Point();
// mouse can be released outside of header , so don't check cf. https://issues.apache.org/jira/browse/FLEX-33818
if (event.type != MouseEvent.MOUSE_UP && !eventToHeaderLocations(event, eventHeaderCP, eventHeaderXY))
return;
const eventSeparatorIndex:int = eventHeaderCP.rowIndex;
const eventColumnIndex:int = (eventSeparatorIndex == -1) ? eventHeaderCP.columnIndex : -1;
var gridEventType:String;
switch(event.type)
{
case MouseEvent.MOUSE_MOVE:
{
gridEventType = (pressSeparatorIndex != -1) ? GridEvent.SEPARATOR_MOUSE_DRAG : GridEvent.GRID_MOUSE_DRAG;
break;
}
case MouseEvent.MOUSE_UP:
{
gridEventType = (pressSeparatorIndex != -1) ? GridEvent.SEPARATOR_MOUSE_UP : GridEvent.GRID_MOUSE_UP;
downColumnIndex = -1; // update renderer property
break;
}
case MouseEvent.MOUSE_DOWN:
{
if (eventSeparatorIndex != -1)
{
gridEventType = GridEvent.SEPARATOR_MOUSE_DOWN;
pressSeparatorIndex = eventSeparatorIndex;
pressColumnIndex = -1;
downColumnIndex = -1; // update renderer property
}
else
{
gridEventType = GridEvent.GRID_MOUSE_DOWN;
pressSeparatorIndex = -1;
pressColumnIndex = eventColumnIndex;
downColumnIndex = eventColumnIndex; // update renderer property
}
break;
}
}
const columnIndex:int = (pressSeparatorIndex != -1) ? pressSeparatorIndex : eventColumnIndex;
if (columnIndex != -1) {
dispatchGridEvent(event, gridEventType, eventHeaderXY, columnIndex);
}
}
/**
* @private
*
* This method is called whenever a MOUSE_MOVE event occurs and the
* button is not pressed. Despite the fact that the area considered to be
* occupied by the separators overlaps the headers (see <code>mouseSeparatorWidth</code>)
* the mouse is considered to be in either a header or a separator, but not both.
*
* This method dispatches a GRID_ROLL_OVER event when the mouse enters a header,
* a SEPARATOR_ROLL_OVER when the mouse enters a separator, and GRID_ROLL_OUT
* and SEPARATOR_ROLL_OUT when the mouse leaves a header or separator respectively.
*
* Listeners are guaranteed to receive a GRID_ROLL_OUT event for every
* GRID_ROLL_OVER event and to receive a SEPARATOR_ROLL_OUT event for
* every SEPARATOR_ROLL_OVER event.
*
* @param event A MOUSE_MOVE MouseEvent within the column header group
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
protected function gchg_mouseMoveHandler(event:MouseEvent):void
{
const eventHeaderCP:CellPosition = new CellPosition();
const eventHeaderXY:Point = new Point();
if (!eventToHeaderLocations(event, eventHeaderCP, eventHeaderXY))
return;
const eventSeparatorIndex:int = eventHeaderCP.rowIndex;
const eventColumnIndex:int = (eventSeparatorIndex == -1) ? eventHeaderCP.columnIndex : -1;
if (eventSeparatorIndex != rollSeparatorIndex)
{
if (rollSeparatorIndex != -1)
dispatchGridEvent(event, GridEvent.SEPARATOR_ROLL_OUT, eventHeaderXY, rollSeparatorIndex);
if (eventSeparatorIndex != -1)
dispatchGridEvent(event, GridEvent.SEPARATOR_ROLL_OVER, eventHeaderXY, eventSeparatorIndex);
}
if (eventColumnIndex != rollColumnIndex)
{
if (rollColumnIndex != -1)
dispatchGridEvent(event, GridEvent.GRID_ROLL_OUT, eventHeaderXY, rollColumnIndex);
if (eventColumnIndex != -1)
dispatchGridEvent(event, GridEvent.GRID_ROLL_OVER, eventHeaderXY, eventColumnIndex);
}
rollColumnIndex = eventColumnIndex;
rollSeparatorIndex = eventSeparatorIndex;
}
/**
* @private
*
* Called when the mouse moves out of the GridColumnHeaderGroup.
* By default it dispatches either a GRID_ROLL_OUT or a
* SEPARATOR_ROLL_OUT event.
*
* @param event A ROLL_OUT MouseEvent from the column header group.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
protected function gchg_mouseRollOutHandler(event:MouseEvent):void
{
const eventHeaderXY:Point = globalToLocal(new Point(event.stageX, event.stageY));
if (rollSeparatorIndex != -1)
dispatchGridEvent(event, GridEvent.SEPARATOR_ROLL_OUT, eventHeaderXY, rollSeparatorIndex);
else if (rollColumnIndex != -1)
dispatchGridEvent(event, GridEvent.GRID_ROLL_OUT, eventHeaderXY, rollColumnIndex);
rollColumnIndex = -1;
rollSeparatorIndex = -1;
}
/**
* @private
*
* This method is called whenever a CLICK MouseEvent occurs on the
* column header group if both the corresponding down and up events occur
* within the same column header cell. By default it dispatches a
* GRID_CLICK event.
*
* @param event A CLICK MouseEvent from the column header group.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
protected function gchg_clickHandler(event:MouseEvent):void
{
const eventHeaderCP:CellPosition = new CellPosition();
const eventHeaderXY:Point = new Point();
if (!eventToHeaderLocations(event, eventHeaderCP, eventHeaderXY))
return;
const eventSeparatorIndex:int = eventHeaderCP.rowIndex;
const eventColumnIndex:int = (eventSeparatorIndex == -1) ? eventHeaderCP.columnIndex : -1;
if ((eventSeparatorIndex != -1) && (pressSeparatorIndex == eventSeparatorIndex))
dispatchGridEvent(event, GridEvent.SEPARATOR_CLICK, eventHeaderXY, eventSeparatorIndex);
else if ((eventColumnIndex != -1) && (pressColumnIndex == eventColumnIndex))
dispatchGridEvent(event, GridEvent.GRID_CLICK, eventHeaderXY, eventColumnIndex);
}
/**
* @private
*
* This method is called whenever a DOUBLE_CLICK MouseEvent occurs
* if the corresponding sequence of down and up events occur within
* the same column header cell. It dispatches a GRID_DOUBLE_CLICK event.
*
* @param event A DOUBLE_CLICK MouseEvent from the column header group.
*
* @see flash.display.InteractiveObject#doubleClickEnabled
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 2.0
* @productversion Flex 4.5
*/
protected function gchg_doubleClickHandler(event:MouseEvent):void
{
const eventHeaderCP:CellPosition = new CellPosition();
const eventHeaderXY:Point = new Point();
if (!eventToHeaderLocations(event, eventHeaderCP, eventHeaderXY))
return;
const eventSeparatorIndex:int = eventHeaderCP.rowIndex;
const eventColumnIndex:int = (eventSeparatorIndex == -1) ? eventHeaderCP.columnIndex : -1;
if ((eventSeparatorIndex != -1) && (pressSeparatorIndex == eventSeparatorIndex))
dispatchGridEvent(event, GridEvent.SEPARATOR_DOUBLE_CLICK, eventHeaderXY, eventSeparatorIndex);
//Commented out because it completely bypasses the grids normal doubleclick handler, yet it calls a doubleclick.
// else if ((eventColumnIndex != -1) && (pressColumnIndex == eventColumnIndex))
// dispatchGridEvent(event, GridEvent.GRID_DOUBLE_CLICK, eventHeaderXY, eventColumnIndex);
}
/**
* @private
*/
private function dispatchGridEvent(mouseEvent:MouseEvent, type:String, headerGroupXY:Point, columnIndex:int):void
{
const column:GridColumn = getColumnAt(columnIndex);
const item:Object = null;
const itemRenderer:IGridItemRenderer = getHeaderRendererAt(columnIndex);
const bubbles:Boolean = mouseEvent.bubbles;
const cancelable:Boolean = mouseEvent.cancelable;
const relatedObject:InteractiveObject = mouseEvent.relatedObject;
const ctrlKey:Boolean = mouseEvent.ctrlKey;
const altKey:Boolean = mouseEvent.altKey;
const shiftKey:Boolean = mouseEvent.shiftKey;
const buttonDown:Boolean = mouseEvent.buttonDown;
const delta:int = mouseEvent.delta;
const event:GridEvent = new GridEvent(
type, bubbles, cancelable,
headerGroupXY.x, headerGroupXY.y,
relatedObject, ctrlKey, altKey, shiftKey, buttonDown, delta,
-1 /* rowIndex */, columnIndex, column, item, itemRenderer);
dispatchEvent(event);
}
//--------------------------------------------------------------------------
//
// Private methods, properties
//
//--------------------------------------------------------------------------
/**
* @private
*/
private function getColumnAt(columnIndex:int):GridColumn
{
const grid:Grid = (dataGrid) ? dataGrid.grid : null;
if (!grid || !grid.columns)
return null;
const columns:IList = grid.columns;
return ((columnIndex >= 0) && (columnIndex < columns.length)) ? columns.getItemAt(columnIndex) as GridColumn : null;
}
/**
* @private
*/
private function getColumnHeaderViewAtX(x:Number):Group
{
const ghl:GridHeaderLayout = layout as GridHeaderLayout;
if (ghl.leftGridColumnHeaderView && (x < ghl.centerGridColumnHeaderView.getLayoutBoundsX()))
return ghl.leftGridColumnHeaderView;
return ghl.centerGridColumnHeaderView;
}
/**
* @private
*/
private function getColumnHeaderViewAtIndex(columnIndex:int):Group
{
const ghl:GridHeaderLayout = layout as GridHeaderLayout;
if (ghl.leftGridColumnHeaderView && (columnIndex < dataGrid.lockedColumnCount))
return ghl.leftGridColumnHeaderView;
return ghl.centerGridColumnHeaderView;
}
}
}