blob: 8a32d8f8a9334398370f1f83c3030b188de2045a [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.menuClasses
{
import flash.display.DisplayObject;
import flash.utils.getDefinitionByName;
import mx.controls.Menu;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.controls.listClasses.ListData;
import mx.core.IDataRenderer;
import mx.core.IFlexDisplayObject;
import mx.core.IFlexModuleFactory;
import mx.core.IFontContextComponent;
import mx.core.IUITextField;
import mx.core.UIComponent;
import mx.core.UITextField;
import mx.core.mx_internal;
import mx.events.FlexEvent;
use namespace mx_internal;
//--------------------------------------
// Events
//--------------------------------------
/**
* Dispatched when the <code>data</code> property changes.
*
* <p>When you use a component as an item renderer,
* the <code>data</code> property contains the data to display.
* You can listen for this event and update the component
* when the <code>data</code> property changes.</p>
*
* @eventType mx.events.FlexEvent.DATA_CHANGE
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Event(name="dataChange", type="mx.events.FlexEvent")]
//--------------------------------------
// Styles
//--------------------------------------
/**
* Text color of the menu item label.
*
* The default value for the Halo theme is <code>0x0B333C</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="color", type="uint", format="Color", inherit="yes")]
/**
* Color of the menu item if it is disabled.
*
* @default 0xAAB3B3
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
[Style(name="disabledColor", type="uint", format="Color", inherit="yes")]
/**
* The MenuItemRenderer class defines the default item renderer
* for menu items in any menu control.
*
* By default, the item renderer draws the text associated
* with each menu item, the separator characters, and icons.
*
* <p>You can override the default item renderer
* by creating a custom item renderer.</p>
*
* @see mx.controls.Menu
* @see mx.controls.MenuBar
* @see mx.core.IDataRenderer
* @see mx.controls.listClasses.IDropInListItemRenderer
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class MenuItemRenderer extends UIComponent
implements IDataRenderer, IListItemRenderer,
IMenuItemRenderer, IDropInListItemRenderer,
IFontContextComponent
{
include "../../core/Version.as";
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function MenuItemRenderer()
{
super();
}
//--------------------------------------------------------------------------
//
// Overridden properties: UIComponent
//
//--------------------------------------------------------------------------
//----------------------------------
// baselinePosition
//----------------------------------
/**
* @private
* The baselinePosition of a MenuItemRenderer is calculated
* for its label.
*/
override public function get baselinePosition():Number
{
if (!validateBaselinePosition())
return NaN;
return label.y + label.baselinePosition;
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// branchIcon
//----------------------------------
/**
* The internal IFlexDisplayObject that displays the branch icon
* in this renderer.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var branchIcon:IFlexDisplayObject;
//----------------------------------
// data
//----------------------------------
/**
* @private
* Storage for the data property.
*/
private var _data:Object;
[Bindable("dataChange")]
/**
* The implementation of the <code>data</code> property
* as defined by the IDataRenderer interface.
*
* @see mx.core.IDataRenderer
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get data():Object
{
return _data;
}
/**
* @private
*/
public function set data(value:Object):void
{
_data = value;
invalidateProperties();
invalidateSize();
dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
}
//----------------------------------
// fontContext
//----------------------------------
/**
* @private
*/
public function get fontContext():IFlexModuleFactory
{
return moduleFactory;
}
/**
* @private
*/
public function set fontContext(moduleFactory:IFlexModuleFactory):void
{
this.moduleFactory = moduleFactory;
}
//----------------------------------
// icon
//----------------------------------
// using getter/setter so we can have backwards-compatibility. If someone
// subclassed and referred to icon, and they wanted to refer to the
// checkbox/radio button or a separator, it is now moved into typeIcon
// and separatorIcon
/**
* @private
* Storage for the icon property.
*/
private var _icon:IFlexDisplayObject;
/**
* The internal IFlexDisplayObject that displays the icon in this renderer.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected function get icon():IFlexDisplayObject
{
return _icon;
}
/**
* @private
*/
protected function set icon(value:IFlexDisplayObject):void
{
_icon = value;
}
//----------------------------------
// label
//----------------------------------
/**
* The internal UITextField that displays the text in this renderer.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var label:IUITextField;
//----------------------------------
// listData
//----------------------------------
/**
* @private
* Storage for the listData property.
*/
private var _listData:ListData;
[Bindable("dataChange")]
/**
* The implementation of the <code>listData</code> property
* as defined by the IDropInListItemRenderer interface.
*
* @see mx.controls.listClasses.IDropInListItemRenderer
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get listData():BaseListData
{
return _listData;
}
/**
* @private
*/
public function set listData(value:BaseListData):void
{
_listData = ListData(value);
invalidateProperties();
}
//----------------------------------
// menu
//----------------------------------
/**
* @private
* Storage for the menu property.
*/
private var _menu:Menu;
/**
* Contains a reference to the associated Menu control.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get menu():Menu
{
return _menu;
}
/**
* @private
*/
public function set menu(value:Menu):void
{
_menu = value;
}
//----------------------------------
// separatorIcon
//----------------------------------
/**
* The internal IFlexDisplayObject that displays the separator icon in this renderer
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var separatorIcon:IFlexDisplayObject;
//----------------------------------
// typeIcon
//----------------------------------
/**
* The internal IFlexDisplayObject that displays the type icon in this renderer for
* check and radio buttons.
*
* @default null
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
protected var typeIcon:IFlexDisplayObject;
//--------------------------------------------------------------------------
//
// Overridden methods: UIComponent
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function createChildren():void
{
super.createChildren();
createLabel(-1);
}
/**
* @private
*/
override protected function commitProperties():void
{
super.commitProperties();
// if the font changed and we already created the label, we will need to
// destory it so it can be re-created, possibly in a different swf context.
if (hasFontContextChanged() && label != null)
{
var index:int = getChildIndex(DisplayObject(label));
removeLabel();
createLabel(index);
}
var iconClass:Class;
var typeIconClass:Class;
var separatorIconClass:Class;
var branchIconClass:Class;
// Remove any existing icon/type/separator/branch icons.
// These will be recreated below if needed.
if (_icon)
{
removeChild(DisplayObject(_icon));
_icon = null;
}
if (typeIcon)
{
removeChild(DisplayObject(typeIcon));
typeIcon = null;
}
if (separatorIcon)
{
removeChild(DisplayObject(separatorIcon));
separatorIcon = null;
}
if (branchIcon)
{
removeChild(DisplayObject(branchIcon));
branchIcon = null;
}
if (_data)
{
var dataDescriptor:IMenuDataDescriptor =
Menu(_listData.owner).dataDescriptor;
var isEnabled:Boolean = dataDescriptor.isEnabled(_data);
var type:String = dataDescriptor.getType(_data);
// Separator
if (type.toLowerCase() == "separator")
{
label.text = "";
label.visible = false;
separatorIconClass = getStyle("separatorSkin");
separatorIcon = new separatorIconClass();
addChild(DisplayObject(separatorIcon));
return;
}
else
{
label.visible = true;
}
// Icon
if (_listData.icon)
{
var listDataIcon:Object = _listData.icon;
if (listDataIcon is Class)
{
iconClass = Class(listDataIcon);
}
else if (listDataIcon is String)
{
iconClass =
Class(getDefinitionByName(String(listDataIcon)));
}
_icon = new iconClass();
addChild(DisplayObject(_icon));
}
// Label
label.text = _listData.label;
label.enabled = isEnabled;
// Check/radio icon
if (dataDescriptor.isToggled(_data))
{
var typeVal:String = dataDescriptor.getType(_data);
if (typeVal)
{
typeVal = typeVal.toLowerCase();
if (typeVal == "radio")
{
typeIconClass = getStyle(isEnabled ?
"radioIcon" :
"radioDisabledIcon");
}
else if (typeVal == "check")
{
typeIconClass = getStyle(isEnabled ?
"checkIcon" :
"checkDisabledIcon");
}
if (typeIconClass)
{
typeIcon = new typeIconClass();
addChild(DisplayObject(typeIcon));
}
}
}
// Branch icon
if (dataDescriptor.isBranch(_data))
{
branchIconClass = getStyle(isEnabled ?
"branchIcon" :
"branchDisabledIcon");
if (branchIconClass)
{
branchIcon = new branchIconClass();
addChild(DisplayObject(branchIcon));
}
}
}
else
{
label.text = " ";
}
// Invalidate layout here to ensure icons are positioned correctly.
invalidateDisplayList();
}
/**
* @private
*/
override protected function measure():void
{
super.measure();
if (separatorIcon)
{
measuredWidth = separatorIcon.measuredWidth;
measuredHeight = separatorIcon.measuredHeight;
return;
}
if (_listData)
{
// need to determine the left/right margin needed
// depends on whether there's an icon, a typeIcon, and/or a branchIcon
var iconWidth:Number = MenuListData(_listData).maxMeasuredIconWidth;
var typeIconWidth:Number = MenuListData(_listData).maxMeasuredTypeIconWidth;
var branchIconWidth:Number = MenuListData(_listData).maxMeasuredBranchIconWidth;
var useTwoColumns:Boolean = MenuListData(_listData).useTwoColumns;
var leftMargin:Number = Math.max(getStyle("leftIconGap"),
useTwoColumns ? iconWidth + typeIconWidth :
Math.max(iconWidth, typeIconWidth));
var rightMargin:Number = Math.max(getStyle("rightIconGap"), branchIconWidth);
if (isNaN(explicitWidth))
{
measuredWidth = label.measuredWidth + leftMargin + rightMargin + 7;
}
else
{
label.width = explicitWidth - leftMargin - rightMargin;
}
measuredHeight = label.measuredHeight;
// need to determine the height needed
if (_icon && _icon.measuredHeight > measuredHeight)
measuredHeight = _icon.measuredHeight;
if (typeIcon && typeIcon.measuredHeight > measuredHeight)
measuredHeight = typeIcon.measuredHeight;
if (branchIcon && branchIcon.measuredHeight > measuredHeight)
measuredHeight = branchIcon.measuredHeight;
}
}
/**
* @private
*/
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if (_listData)
{
if (Menu(_listData.owner).dataDescriptor.
getType(_data).toLowerCase() == "separator")
{
if (separatorIcon)
{
separatorIcon.x = 2;
separatorIcon.y = (unscaledHeight - separatorIcon.measuredHeight) / 2;
separatorIcon.setActualSize(unscaledWidth - 4, separatorIcon.measuredHeight);
}
return;
}
var iconWidth:Number = MenuListData(_listData).maxMeasuredIconWidth;
var typeIconWidth:Number = MenuListData(_listData).maxMeasuredTypeIconWidth;
var branchIconWidth:Number = MenuListData(_listData).maxMeasuredBranchIconWidth;
var useTwoColumns:Boolean = MenuListData(_listData).useTwoColumns;
var leftMargin:Number = Math.max(getStyle("leftIconGap"),
useTwoColumns ? iconWidth + typeIconWidth :
Math.max(iconWidth, typeIconWidth));
var rightMargin:Number = Math.max(getStyle("rightIconGap"), branchIconWidth);
// check to see if laying out in two columns or not
if (useTwoColumns)
{
// if in two columns, center the two columns
// center the respective icons (if present) in their own column
var left:Number = (leftMargin - (iconWidth + typeIconWidth))/2
if (_icon)
{
_icon.x = left + (iconWidth - _icon.measuredWidth)/2;
_icon.setActualSize(_icon.measuredWidth, _icon.measuredHeight);
}
if (typeIcon)
{
typeIcon.x = left + iconWidth + (typeIconWidth - typeIcon.measuredWidth)/2;
typeIcon.setActualSize(typeIcon.measuredWidth, typeIcon.measuredHeight);
}
}
else
{
// if in one column mode, just center the one item in the left icon gap
if (_icon)
{
_icon.x = (leftMargin - _icon.measuredWidth)/2;
_icon.setActualSize(_icon.measuredWidth, _icon.measuredHeight);
}
if (typeIcon)
{
typeIcon.x = (leftMargin - typeIcon.measuredWidth)/2;
typeIcon.setActualSize(typeIcon.measuredWidth, typeIcon.measuredHeight);
}
}
if (branchIcon)
{
branchIcon.x = unscaledWidth - rightMargin +
(rightMargin - branchIcon.measuredWidth)/2;
branchIcon.setActualSize(branchIcon.measuredWidth,
branchIcon.measuredHeight);
}
label.x = leftMargin;
label.setActualSize(unscaledWidth - leftMargin - rightMargin,
label.getExplicitOrMeasuredHeight());
if (_listData && !Menu(_listData.owner).showDataTips)
{
label.text = _listData.label;
if (label.truncateToFit())
toolTip = _listData.label;
else
toolTip = null;
}
var verticalAlign:String = getStyle("verticalAlign");
if (verticalAlign == "top")
{
label.y = 0;
if (_icon)
_icon.y = 0;
if (typeIcon)
typeIcon.y = 0;
if (branchIcon)
branchIcon.y = 0;
}
else if (verticalAlign == "bottom")
{
label.y = unscaledHeight - label.height + 2; // 2 for gutter
if (_icon)
_icon.y = unscaledHeight - _icon.height;
if (typeIcon)
typeIcon.y = unscaledHeight - typeIcon.height;
if (branchIcon)
branchIcon.y = unscaledHeight - branchIcon.height;
}
else
{
label.y = (unscaledHeight - label.height) / 2;
if (_icon)
_icon.y = (unscaledHeight - _icon.height) / 2;
if (typeIcon)
typeIcon.y = (unscaledHeight - typeIcon.height) / 2;
if (branchIcon)
branchIcon.y = (unscaledHeight - branchIcon.height) / 2;
}
var labelColor:Number;
if (data && parent)
{
if (!enabled)
{
labelColor = getStyle("disabledColor");
}
else if (Menu(listData.owner).isItemHighlighted(listData.uid))
{
labelColor = getStyle("textRollOverColor");
}
else if (Menu(listData.owner).isItemSelected(listData.uid))
{
labelColor = getStyle("textSelectedColor");
}
else
{
labelColor = getStyle("color");
}
label.setColor(labelColor);
}
}
}
/**
* @private
*/
override public function styleChanged(styleProp:String):void
{
super.styleChanged(styleProp);
if (!styleProp ||
styleProp == "styleName" ||
(styleProp.toLowerCase().indexOf("icon") != -1))
{
// If any icons change, invalidate everything.
// We could be smarter about this if it causes
// performance problems.
invalidateSize();
invalidateDisplayList();
}
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* @private
* Creates the title text field and adds it as a child of this component.
*
* @param childIndex The index of where to add the child.
* If -1, the text field is appended to the end of the list.
*/
mx_internal function createLabel(childIndex:int):void
{
if (!label)
{
label = IUITextField(createInFontContext(UITextField));
label.styleName = this;
if (childIndex == -1)
addChild(DisplayObject(label));
else
addChildAt(DisplayObject(label), childIndex);
}
}
/**
* @private
* Removes the title text field from this component.
*/
mx_internal function removeLabel():void
{
if (label)
{
removeChild(DisplayObject(label));
label = null;
}
}
/**
* @private
*/
mx_internal function getLabel():IUITextField
{
return label;
}
/**
* The width of the icon
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get measuredIconWidth():Number
{
var horizontalGap:Number = getStyle("horizontalGap");
return _icon ? _icon.measuredWidth + horizontalGap : 0;
}
/**
* The width of the type icon (radio/check)
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get measuredTypeIconWidth():Number
{
var horizontalGap:Number = getStyle("horizontalGap");
if (typeIcon)
return typeIcon.measuredWidth + horizontalGap;
// even if there's no type icon, get what it's width would be...
if (_data)
{
var typeIconClass:Class;
var dataDescriptor:IMenuDataDescriptor =
Menu(_listData.owner).dataDescriptor;
var isEnabled:Boolean = dataDescriptor.isEnabled(_data);
var typeVal:String = dataDescriptor.getType(_data);
if (typeVal)
{
typeVal = typeVal.toLowerCase();
if (typeVal == "radio")
{
typeIconClass = getStyle(isEnabled ?
"radioIcon" :
"radioDisabledIcon");
}
else if (typeVal == "check")
{
typeIconClass = getStyle(isEnabled ?
"checkIcon" :
"checkDisabledIcon");
}
if (typeIconClass)
{
typeIcon = new typeIconClass();
var typeIconWidth:Number = typeIcon.measuredWidth;
typeIcon = null;
return typeIconWidth + horizontalGap;
}
}
}
return 0;
}
/**
* The width of the branch icon
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get measuredBranchIconWidth():Number
{
var horizontalGap:Number = getStyle("horizontalGap");
return branchIcon ? branchIcon.measuredWidth + horizontalGap : 0;
}
}
}