blob: ef104e339be321589dc7a6fba9b7972f47c319bf [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package mx.controls
{
import flash.display.DisplayObject;
import flash.events.MouseEvent;
import mx.core.ClassFactory;
import mx.core.EdgeMetrics;
import mx.core.IFlexDisplayObject;
import mx.core.mx_internal;
import mx.events.ChildExistenceChangedEvent;
import mx.events.FlexEvent;
import mx.events.ItemClickEvent;
import mx.styles.ISimpleStyleClient;
import mx.styles.StyleProxy;
use namespace mx_internal;
//--------------------------------------
// Styles
//--------------------------------------
/**
* Number of pixels between the LinkButton controls in the horizontal direction.
*
* @default 8
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="horizontalGap", type="Number", format="Length", inherit="no")]
/**
* Name of CSS style declaration that specifies the styles to use for the link
* button navigation items.
*
* @default ""
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="linkButtonStyleName", type="String", inherit="no")]
/**
* Number of pixels between the bottom border and the LinkButton controls.
*
* @default 2
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="paddingBottom", type="Number", format="Length", inherit="no")]
/**
* Number of pixels between the top border and the LinkButton controls.
*
* @default 2
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="paddingTop", type="Number", format="Length", inherit="no")]
/**
* Color of links as you roll the mouse pointer over them.
* The default value is based on the current <code>themeColor</code>.
*
* The default value for the Halo theme is <code>0xB2E1FF</code>.
* The default value for the Spark theme is <code>0xCEDBEF</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="rollOverColor", type="uint", format="Color", inherit="yes")]
/**
* Background color of the LinkButton control as you press it.
*
* The default value for the Halo theme is <code>0x7FCEFF</code>.
* The default value for the Spark theme is <code>0xA8C6EE</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="selectionColor", type="uint", format="Color", inherit="yes")]
/**
* Separator color used by the default separator skin.
*
* @default 0xC4CCCC
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="separatorColor", type="uint", format="Color", inherit="yes")]
/**
* Seperator symbol between LinkButton controls in the LinkBar.
*
* @default mx.skins.halo.LinkSeparator (for both Halo and Spark themes)
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="separatorSkin", type="Class", inherit="no")]
/**
* Separator pixel width, in pixels.
*
* @default 1
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="separatorWidth", type="Number", format="Length", inherit="yes")]
/**
* Text color of the link as you move the mouse pointer over it.
*
* The default value for the Halo theme is <code>0x2B333C</code>.
* The default value for the Spark theme is <code>0x000000</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="textRollOverColor", type="uint", format="Color", inherit="yes")]
/**
* Text color of the link as you press it.
*
* The default value for the Halo theme is <code>0x2B333C</code>.
* The default for the Spark theme is <code>0x000000</code>.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="textSelectedColor", type="uint", format="Color", inherit="yes")]
/**
* Number of pixels between children in the vertical direction.
*
* @default 8
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="verticalGap", type="Number", format="Length", inherit="no")]
//--------------------------------------
// Excluded APIs
//--------------------------------------
[Exclude(name="horizontalLineScrollSize", kind="property")]
[Exclude(name="horizontalPageScrollSize", kind="property")]
[Exclude(name="horizontalScrollBar", kind="property")]
[Exclude(name="horizontalScrollPolicy", kind="property")]
[Exclude(name="horizontalScrollPosition", kind="property")]
[Exclude(name="maxHorizontalScrollPosition", kind="property")]
[Exclude(name="maxVerticalScrollPosition", kind="property")]
[Exclude(name="verticalLineScrollSize", kind="property")]
[Exclude(name="verticalPageScrollSize", kind="property")]
[Exclude(name="verticalScrollBar", kind="property")]
[Exclude(name="verticalScrollPolicy", kind="property")]
[Exclude(name="verticalScrollPosition", kind="property")]
[Exclude(name="scroll", kind="event")]
[Exclude(name="click", kind="event")]
[Exclude(name="horizontalScrollBarStyleName", kind="style")]
[Exclude(name="verticalScrollBarStyleName", kind="style")]
//--------------------------------------
// Other metadata
//--------------------------------------
[DefaultProperty("dataProvider")]
[IconFile("LinkBar.png")]
[MaxChildren(0)]
/**
* A LinkBar control defines a horizontal or vertical row of LinkButton controls
* that designate a series of link destinations.
* You typically use a LinkBar control to control
* the active child container of a ViewStack container,
* or to create a stand-alone set of links.
*
* <p>The LinkBar control creates LinkButton controls based on the value of
* its <code>dataProvider</code> property.
* Even though LinkBar is a subclass of Container, do not use methods such as
* <code>Container.addChild()</code> and <code>Container.removeChild()</code>
* to add or remove LinkButton controls.
* Instead, use methods such as <code>addItem()</code> and <code>removeItem()</code>
* to manipulate the <code>dataProvider</code> property.
* The LinkBar control automatically adds or removes the necessary children based on
* changes to the <code>dataProvider</code> property.</p>
*
* <p>A LinkBar control has the following default characteristics:</p>
* <table class="innertable">
* <tr>
* <th>Characteristic</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>Preferred size</td>
* <td>A width wide enough to contain all label text, plus any padding and separators, and the height of the tallest child.</td>
* </tr>
* <tr>
* <td>Control resizing rules</td>
* <td>LinkBar controls do not resize by default. Specify percentage sizes if you want your LinkBar to resize based on the size of its parent container.</td>
* </tr>
* <tr>
* <td>Padding</td>
* <td>2 pixels for the top, bottom, left, and right properties.</td>
* </tr>
* </table>
*
* @mxml
* <p>The <code>&lt;mx:LinkBar&gt;</code> tag inherits all of the tag
* attributes of its superclass, and adds the following tag attributes:</p>
*
* <pre>
* &lt;mx:LinkBar
* <b>Properties</b>
* selectedIndex="-1"
*
* <b>Styles</b>
* linkButtonStyleName=""
* horizontalGap="8"
* paddingBottom="2"
* paddingTop="2"
* rollOverColor="0xEEFEE6"
* selectionColor="0xCDFFC1"
* separatorColor="<i>No default</i>"
* separatorSkin="0x000000"
* separatorWidth="1"
* textRollOverColor="0x2B333C"
* textSelectedColor="0x000000"
* verticalGap="8"
* &gt;
* ...
* <i>child tags</i>
* ...
* &lt;/mx:LinkBar&gt;
* </pre>
*
* @includeExample examples/LinkBarExample.mxml
*
* @see mx.controls.NavBar
* @see mx.containers.ViewStack
* @see mx.controls.LinkButton
* @see mx.controls.ToggleButtonBar
* @see mx.controls.ButtonBar
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class LinkBar extends NavBar
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Class constants
//
//--------------------------------------------------------------------------
/**
* @private
*/
private static const SEPARATOR_NAME:String = "_separator";
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function LinkBar()
{
super();
navItemFactory = new ClassFactory(LinkButton);
addEventListener(MouseEvent.CLICK, defaultClickHandler);
addEventListener(ChildExistenceChangedEvent.CHILD_REMOVE,
childRemoveHandler);
}
//--------------------------------------------------------------------------
//
// Overridden properties
//
//--------------------------------------------------------------------------
//----------------------------------
// selectedIndex
//----------------------------------
/**
* @private
* Storage for the selectedIndex property.
*/
private var _selectedIndex:int = -1;
/**
* @private
*/
private var _selectedIndexChanged:Boolean = false;
[Bindable("valueCommit")]
[Inspectable(category="General")]
/**
* The index of the last selected LinkButton control if the LinkBar
* control uses a ViewStack container as its data provider.
*
* @default -1
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override public function get selectedIndex():int
{
return super.selectedIndex;
}
/**
* @private
*/
override public function set selectedIndex(value:int):void
{
if (value == selectedIndex)
return;
_selectedIndex = value;
_selectedIndexChanged = true;
invalidateProperties();
dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
}
//--------------------------------------------------------------------------
//
// Overridden methods: UIComponent
//
//--------------------------------------------------------------------------
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override public function styleChanged(styleProp:String):void
{
super.styleChanged(styleProp);
var navItemStyleName:Object;
if (styleProp == "linkButtonStyleName")
navItemStyleName = getStyle("linkButtonStyleName");
if (navItemStyleName)
{
var n:int = numChildren;
for (var i:int = 0; i < n; i++)
{
LinkButton(getChildAt(i)).styleName = navItemStyleName;
}
}
}
/**
* @private
*/
override protected function commitProperties():void
{
super.commitProperties();
if (_selectedIndexChanged)
{
hiliteSelectedNavItem(_selectedIndex);
// Update parent index.
super.selectedIndex = _selectedIndex;
_selectedIndexChanged = false;
}
}
/**
* Responds to size changes by setting the positions and sizes
* of this LinkBar control's children.
* For more information about the <code>updateDisplayList()</code> method,
* see the <code>UIComponent.updateDisplayList()</code> method.
*
* <p>The <code>LinkBar.updateDisplayList()</code> method first calls
* the <code>Box.updateDisplayList()</code> method to position the LinkButton controls.
* For more details, see the <code>Box.updateDisplayList()</code> method.
* After laying out the LinkButton controls, the separators are positioned
* between them.</p>
*
* @param unscaledWidth Specifies the width of the component, in pixels,
* of the component's coordinates, regardless of the value of the
* <code>scaleX</code> property of the component.
*
* @param unscaledHeight Specifies the height of the component, in pixels,
* in the component's coordinates, regardless of the value of the
* <code>scaleY</code> property of the component.
*
* @see mx.core.UIComponent#updateDisplayList()
* @see mx.containers.Box#updateDisplayList()
*
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
// The super method will lay out the Links.
super.updateDisplayList(unscaledWidth, unscaledHeight);
var vm:EdgeMetrics = viewMetricsAndPadding;
var horizontalGap:Number = getStyle("horizontalGap");
var verticalGap:Number = getStyle("verticalGap");
var separatorHeight:Number = unscaledHeight - (vm.top + vm.bottom);
var separatorWidth:Number = unscaledWidth - (vm.left + vm.right);
// Lay out the separators.
var n:int = numChildren;
for (var i:int = 0; i < n; i++)
{
var child:IFlexDisplayObject = IFlexDisplayObject(getChildAt(i));
var separator:IFlexDisplayObject = IFlexDisplayObject(
rawChildren.getChildByName(SEPARATOR_NAME + i));
if (separator)
{
separator.visible = false;
// The 0th separator is to the left of the first link.
// It should always be invisible, and doesn't need
// to be laid out.
if (i == 0)
continue;
if (isVertical())
{
separator.move(vm.left, child.y - verticalGap);
separator.setActualSize(separatorWidth, verticalGap);
// The separators don't get clipped.
// (In general, chrome elements
// don't get automatically clipped.)
// So show a separator only if it is completely visible.
if (separator.y + separator.height <
unscaledHeight - vm.bottom)
{
separator.visible = true;
}
}
else
{
separator.move(child.x - horizontalGap, vm.top);
separator.setActualSize(horizontalGap, separatorHeight);
if (separator.x + separator.width <
unscaledWidth - vm.right)
{
separator.visible = true;
}
}
}
}
}
//--------------------------------------------------------------------------
//
// Overridden methods: NavBar
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function createNavItem(
label:String,
icon:Class = null):IFlexDisplayObject
{
// Create the new LinkButton.
var newLink:Button = Button(navItemFactory.newInstance());
var linkButtonStyleName:String = getStyle("linkButtonStyleName");
if (linkButtonStyleName)
newLink.styleName = linkButtonStyleName;
if (label && label.length > 0)
newLink.label = label;
else
newLink.label = " ";
if (icon)
newLink.setStyle("icon", icon);
addChild(newLink);
newLink.addEventListener(MouseEvent.CLICK, clickHandler);
// Create the new separator to the left of the LinkButton.
var separatorClass:Class = Class(getStyle("separatorSkin"));
var separator:DisplayObject = DisplayObject(new separatorClass());
separator.name = SEPARATOR_NAME + (numChildren - 1);
if (separator is ISimpleStyleClient)
ISimpleStyleClient(separator).styleName = this;
rawChildren.addChild(separator);
return newLink;
}
/**
* @private
*/
override protected function hiliteSelectedNavItem(index:int):void
{
var child:Button;
// Un-hilite the current selection.
if (selectedIndex != -1 && selectedIndex < numChildren)
{
child = Button(getChildAt(selectedIndex));
child.enabled = true;
}
// Set new index.
super.selectedIndex = index;
if (index != -1)
{
// Hilite the new selection.
child = Button(getChildAt(selectedIndex));
child.enabled = false;
}
}
/**
* @private
*/
override protected function resetNavItems():void
{
// Reset the index values and selection state.
var n:int = numChildren;
for (var i:int = 0; i < n; i++)
{
var child:Button = Button(getChildAt(i));
child.enabled = !(i == selectedIndex);
}
invalidateDisplayList();
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
/**
* @private
*/
private function childRemoveHandler(event:ChildExistenceChangedEvent):void
{
var child:DisplayObject = event.relatedObject;
var index:int = getChildIndex(child);
var separator:DisplayObject =
rawChildren.getChildByName(SEPARATOR_NAME + index);
rawChildren.removeChild(separator);
// Shuffle the separators down.
var n:int = numChildren - 1;
for (var i:int = index; i < n; i++)
{
rawChildren.getChildByName(SEPARATOR_NAME + (i + 1)).name =
SEPARATOR_NAME + i;
}
}
/**
* @private
*/
private function defaultClickHandler(event:MouseEvent):void
{
// We do not want to propagate a MouseEvent.CLICK event up.
if (!(event is ItemClickEvent))
event.stopImmediatePropagation();
}
//--------------------------------------------------------------------------
//
// Overridden event handlers: NavBar
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function clickHandler(event:MouseEvent):void
{
var index:int = getChildIndex(Button(event.currentTarget));
if (targetStack)
{
if (index == selectedIndex)
hiliteSelectedNavItem(-1);
else
hiliteSelectedNavItem(index);
}
super.clickHandler(event);
}
}
}