1////////////////////////////////////////////////////////////////////////////////
2//
3//  ADOBE SYSTEMS INCORPORATED
4//  Copyright 2005-2007 Adobe Systems Incorporated
5//  All Rights Reserved.
6//
7//  NOTICE: Adobe permits you to use, modify, and distribute this file
8//  in accordance with the terms of the license agreement accompanying it.
9//
10////////////////////////////////////////////////////////////////////////////////
11
12package mx.controls.menuClasses
13{
14
15import flash.display.DisplayObject;
16import flash.utils.getDefinitionByName;
17import mx.controls.Menu;
18import mx.controls.listClasses.BaseListData;
19import mx.controls.listClasses.IDropInListItemRenderer;
20import mx.controls.listClasses.IListItemRenderer;
21import mx.controls.listClasses.ListData;
22import mx.core.IDataRenderer;
23import mx.core.IFlexDisplayObject;
24import mx.core.IFlexModuleFactory;
25import mx.core.IFontContextComponent;
26import mx.core.IUITextField;
27import mx.core.UIComponent;
28import mx.core.UITextField;
29import mx.core.mx_internal;
30import mx.events.FlexEvent;
31
32use namespace mx_internal;
33
34//--------------------------------------
35//  Events
36//--------------------------------------
37
38/**
39 *  Dispatched when the <code>data</code> property changes.
40 *
41 *  <p>When you use a component as an item renderer,
42 *  the <code>data</code> property contains the data to display.
43 *  You can listen for this event and update the component
44 *  when the <code>data</code> property changes.</p>
45 *
46 *  @eventType mx.events.FlexEvent.DATA_CHANGE
47 *
48 *  @langversion 3.0
49 *  @playerversion Flash 9
50 *  @playerversion AIR 1.1
51 *  @productversion Flex 3
52 */
53[Event(name="dataChange", type="mx.events.FlexEvent")]
54
55//--------------------------------------
56//  Styles
57//--------------------------------------
58
59/**
60 *  Text color of the menu item label.
61 *
62 *  The default value for the Halo theme is <code>0x0B333C</code>.
63 *  The default value for the Spark theme is <code>0x000000</code>.
64 *
65 *  @langversion 3.0
66 *  @playerversion Flash 9
67 *  @playerversion AIR 1.1
68 *  @productversion Flex 3
69 */
70[Style(name="color", type="uint", format="Color", inherit="yes")]
71
72/**
73 *  Color of the menu item if it is disabled.
74 *
75 *  @default 0xAAB3B3
76 *
77 *  @langversion 3.0
78 *  @playerversion Flash 9
79 *  @playerversion AIR 1.1
80 *  @productversion Flex 3
81 */
82[Style(name="disabledColor", type="uint", format="Color", inherit="yes")]
83
84/**
85 *  The MenuItemRenderer class defines the default item renderer
86 *  for menu items in any menu control.
87 *
88 *  By default, the item renderer draws the text associated
89 *  with each menu item, the separator characters, and icons.
90 *
91 *  <p>You can override the default item renderer
92 *  by creating a custom item renderer.</p>
93 *
94 *  @see mx.controls.Menu
95 *  @see mx.controls.MenuBar
96 *  @see mx.core.IDataRenderer
97 *  @see mx.controls.listClasses.IDropInListItemRenderer
98 *
99 *  @langversion 3.0
100 *  @playerversion Flash 9
101 *  @playerversion AIR 1.1
102 *  @productversion Flex 3
103 */
104public class MenuItemRenderer extends UIComponent
105							  implements IDataRenderer, IListItemRenderer,
106							  IMenuItemRenderer, IDropInListItemRenderer,
107							  IFontContextComponent
108{
109	include "../../core/Version.as";
110
111	//--------------------------------------------------------------------------
112	//
113	//  Constructor
114	//
115	//--------------------------------------------------------------------------
116
117	/**
118	 *  Constructor.
119	 *
120	 *  @langversion 3.0
121	 *  @playerversion Flash 9
122	 *  @playerversion AIR 1.1
123	 *  @productversion Flex 3
124	 */
125	public function MenuItemRenderer()
126	{
127		super();
128	}
129
130    //--------------------------------------------------------------------------
131    //
132    //  Overridden properties: UIComponent
133    //
134    //--------------------------------------------------------------------------
135
136    //----------------------------------
137    //  baselinePosition
138    //----------------------------------
139
140    /**
141     *  @private
142     *  The baselinePosition of a MenuItemRenderer is calculated
143     *  for its label.
144     */
145    override public function get baselinePosition():Number
146    {
147		if (!validateBaselinePosition())
148			return NaN;
149
150		return label.y + label.baselinePosition;
151    }
152
153    //--------------------------------------------------------------------------
154    //
155    //  Properties
156    //
157    //--------------------------------------------------------------------------
158
159    //----------------------------------
160	//  branchIcon
161    //----------------------------------
162
163	/**
164	 *  The internal IFlexDisplayObject that displays the branch icon
165	 *  in this renderer.
166	 *
167	 *  @default null
168	 *
169	 *  @langversion 3.0
170	 *  @playerversion Flash 9
171	 *  @playerversion AIR 1.1
172	 *  @productversion Flex 3
173	 */
174	protected var branchIcon:IFlexDisplayObject;
175
176    //----------------------------------
177	//  data
178    //----------------------------------
179
180	/**
181	 *  @private
182	 *  Storage for the data property.
183	 */
184	private var _data:Object;
185
186	[Bindable("dataChange")]
187
188	/**
189	 *  The implementation of the <code>data</code> property
190	 *  as defined by the IDataRenderer interface.
191	 *
192	 *  @see mx.core.IDataRenderer
193	 *
194	 *  @langversion 3.0
195	 *  @playerversion Flash 9
196	 *  @playerversion AIR 1.1
197	 *  @productversion Flex 3
198	 */
199	public function get data():Object
200	{
201		return _data;
202	}
203
204	/**
205	 *  @private
206	 */
207	public function set data(value:Object):void
208	{
209		_data = value;
210
211		invalidateProperties();
212		invalidateSize();
213
214		dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
215	}
216
217    //----------------------------------
218    //  fontContext
219    //----------------------------------
220
221    /**
222     *  @private
223     */
224    public function get fontContext():IFlexModuleFactory
225    {
226        return moduleFactory;
227    }
228
229    /**
230     *  @private
231     */
232    public function set fontContext(moduleFactory:IFlexModuleFactory):void
233    {
234        this.moduleFactory = moduleFactory;
235    }
236
237    //----------------------------------
238	//  icon
239    //----------------------------------
240
241    // using getter/setter so we can have backwards-compatibility.  If someone
242    // subclassed and referred to icon, and they wanted to refer to the
243    // checkbox/radio button or a separator, it is now moved into typeIcon
244    // and separatorIcon
245
246	/**
247	 *  @private
248	 *  Storage for the icon property.
249	 */
250	private var _icon:IFlexDisplayObject;
251
252	/**
253	 *  The internal IFlexDisplayObject that displays the icon in this renderer.
254	 *
255	 *  @default null
256	 *
257	 *  @langversion 3.0
258	 *  @playerversion Flash 9
259	 *  @playerversion AIR 1.1
260	 *  @productversion Flex 3
261	 */
262	protected function get icon():IFlexDisplayObject
263	{
264		return _icon;
265	}
266
267	/**
268	 *  @private
269	 */
270	protected function set icon(value:IFlexDisplayObject):void
271	{
272		_icon = value;
273	}
274
275    //----------------------------------
276	//  label
277    //----------------------------------
278
279	/**
280	 *  The internal UITextField that displays the text in this renderer.
281	 *
282	 *  @default null
283	 *
284	 *  @langversion 3.0
285	 *  @playerversion Flash 9
286	 *  @playerversion AIR 1.1
287	 *  @productversion Flex 3
288	 */
289	protected var label:IUITextField;
290
291    //----------------------------------
292	//  listData
293    //----------------------------------
294
295	/**
296	 *  @private
297	 *  Storage for the listData property.
298	 */
299	private var _listData:ListData;
300
301	[Bindable("dataChange")]
302
303	/**
304	 *  The implementation of the <code>listData</code> property
305	 *  as defined by the IDropInListItemRenderer interface.
306	 *
307	 *  @see mx.controls.listClasses.IDropInListItemRenderer
308	 *
309	 *  @langversion 3.0
310	 *  @playerversion Flash 9
311	 *  @playerversion AIR 1.1
312	 *  @productversion Flex 3
313	 */
314	public function get listData():BaseListData
315	{
316		return _listData;
317	}
318
319	/**
320	 *  @private
321	 */
322	public function set listData(value:BaseListData):void
323	{
324		_listData = ListData(value);
325
326		invalidateProperties();
327	}
328
329    //----------------------------------
330	//  menu
331    //----------------------------------
332
333	/**
334	 *  @private
335	 *  Storage for the menu property.
336	 */
337	private var _menu:Menu;
338
339	/**
340	 *  Contains a reference to the associated Menu control.
341	 *
342	 *  @default null
343	 *
344	 *  @langversion 3.0
345	 *  @playerversion Flash 9
346	 *  @playerversion AIR 1.1
347	 *  @productversion Flex 3
348	 */
349	public function get menu():Menu
350	{
351		return _menu;
352	}
353
354	/**
355	 *  @private
356	 */
357	public function set menu(value:Menu):void
358	{
359		_menu = value;
360	}
361
362	//----------------------------------
363	//  separatorIcon
364    //----------------------------------
365
366	/**
367	 *  The internal IFlexDisplayObject that displays the separator icon in this renderer
368	 *
369	 *  @default null
370	 *
371	 *  @langversion 3.0
372	 *  @playerversion Flash 9
373	 *  @playerversion AIR 1.1
374	 *  @productversion Flex 3
375	 */
376	protected var separatorIcon:IFlexDisplayObject;
377
378	//----------------------------------
379	//  typeIcon
380    //----------------------------------
381
382	/**
383	 *  The internal IFlexDisplayObject that displays the type icon in this renderer for
384	 *  check and radio buttons.
385	 *
386	 *  @default null
387	 *
388	 *  @langversion 3.0
389	 *  @playerversion Flash 9
390	 *  @playerversion AIR 1.1
391	 *  @productversion Flex 3
392	 */
393	protected var typeIcon:IFlexDisplayObject;
394
395	//--------------------------------------------------------------------------
396	//
397	//  Overridden methods: UIComponent
398	//
399	//--------------------------------------------------------------------------
400
401	/**
402	 *  @private
403	 */
404	override protected function createChildren():void
405	{
406        super.createChildren();
407
408		createLabel(-1);
409	}
410
411	/**
412	 *  @private
413	 */
414	override protected function commitProperties():void
415	{
416		super.commitProperties();
417
418        // if the font changed and we already created the label, we will need to
419        // destory it so it can be re-created, possibly in a different swf context.
420        if (hasFontContextChanged() && label != null)
421        {
422        	var index:int = getChildIndex(DisplayObject(label));
423            removeLabel();
424 			createLabel(index);
425        }
426
427		var iconClass:Class;
428		var typeIconClass:Class;
429		var separatorIconClass:Class;
430		var branchIconClass:Class;
431
432		// Remove any existing icon/type/separator/branch icons.
433		// These will be recreated below if needed.
434		if (_icon)
435		{
436			removeChild(DisplayObject(_icon));
437			_icon = null;
438		}
439		if (typeIcon)
440		{
441			removeChild(DisplayObject(typeIcon));
442			typeIcon = null;
443		}
444		if (separatorIcon)
445		{
446			removeChild(DisplayObject(separatorIcon));
447			separatorIcon = null;
448		}
449		if (branchIcon)
450		{
451			removeChild(DisplayObject(branchIcon));
452			branchIcon = null;
453		}
454
455		if (_data)
456		{
457			var dataDescriptor:IMenuDataDescriptor =
458				Menu(_listData.owner).dataDescriptor;
459
460			var isEnabled:Boolean = dataDescriptor.isEnabled(_data);
461			var type:String = dataDescriptor.getType(_data);
462
463			// Separator
464			if (type.toLowerCase() == "separator")
465			{
466				label.text = "";
467				label.visible = false;
468				separatorIconClass = getStyle("separatorSkin");
469				separatorIcon = new separatorIconClass();
470				addChild(DisplayObject(separatorIcon));
471				return;
472			}
473			else
474			{
475				label.visible = true;
476			}
477
478			// Icon
479			if (_listData.icon)
480			{
481				var listDataIcon:Object = _listData.icon;
482				if (listDataIcon is Class)
483				{
484					iconClass = Class(listDataIcon);
485				}
486				else if (listDataIcon is String)
487				{
488					iconClass =
489						Class(getDefinitionByName(String(listDataIcon)));
490				}
491
492				_icon = new iconClass();
493
494				addChild(DisplayObject(_icon));
495			}
496
497			// Label
498			label.text = _listData.label;
499
500			label.enabled = isEnabled;
501
502			// Check/radio icon
503			if (dataDescriptor.isToggled(_data))
504			{
505				var typeVal:String = dataDescriptor.getType(_data);
506				if (typeVal)
507				{
508					typeVal = typeVal.toLowerCase();
509					if (typeVal == "radio")
510					{
511						typeIconClass = getStyle(isEnabled ?
512											 "radioIcon" :
513											 "radioDisabledIcon");
514					}
515					else if (typeVal == "check")
516					{
517						typeIconClass = getStyle(isEnabled ?
518											 "checkIcon" :
519											 "checkDisabledIcon");
520					}
521
522					if (typeIconClass)
523					{
524						typeIcon = new typeIconClass();
525						addChild(DisplayObject(typeIcon));
526					}
527				}
528			}
529
530			// Branch icon
531			if (dataDescriptor.isBranch(_data))
532			{
533				branchIconClass = getStyle(isEnabled ?
534										   "branchIcon" :
535										   "branchDisabledIcon");
536
537				if (branchIconClass)
538				{
539					branchIcon = new branchIconClass();
540					addChild(DisplayObject(branchIcon));
541				}
542			}
543		}
544		else
545		{
546			label.text = " ";
547		}
548
549		// Invalidate layout here to ensure icons are positioned correctly.
550		invalidateDisplayList();
551	}
552
553	/**
554	 *  @private
555	 */
556	override protected function measure():void
557	{
558		super.measure();
559
560		if (separatorIcon)
561		{
562			measuredWidth = separatorIcon.measuredWidth;
563			measuredHeight = separatorIcon.measuredHeight;
564			return;
565		}
566
567		if (_listData)
568		{
569			// need to determine the left/right margin needed
570			// depends on whether there's an icon, a typeIcon, and/or a branchIcon
571			var iconWidth:Number = MenuListData(_listData).maxMeasuredIconWidth;
572			var typeIconWidth:Number = MenuListData(_listData).maxMeasuredTypeIconWidth;
573			var branchIconWidth:Number = MenuListData(_listData).maxMeasuredBranchIconWidth;
574			var useTwoColumns:Boolean = MenuListData(_listData).useTwoColumns;
575
576			var leftMargin:Number = Math.max(getStyle("leftIconGap"),
577										useTwoColumns ? iconWidth + typeIconWidth :
578														Math.max(iconWidth, typeIconWidth));
579			var rightMargin:Number = Math.max(getStyle("rightIconGap"), branchIconWidth);
580
581			if (isNaN(explicitWidth))
582			{
583				measuredWidth = label.measuredWidth + leftMargin + rightMargin + 7;
584			}
585			else
586			{
587				label.width = explicitWidth - leftMargin - rightMargin;
588			}
589
590			measuredHeight = label.measuredHeight;
591
592			// need to determine the height needed
593			if (_icon && _icon.measuredHeight > measuredHeight)
594				measuredHeight = _icon.measuredHeight;
595
596			if (typeIcon && typeIcon.measuredHeight > measuredHeight)
597				measuredHeight = typeIcon.measuredHeight;
598
599			if (branchIcon && branchIcon.measuredHeight > measuredHeight)
600				measuredHeight = branchIcon.measuredHeight;
601		}
602	}
603
604	/**
605	 *  @private
606	 */
607	override protected function updateDisplayList(unscaledWidth:Number,
608												  unscaledHeight:Number):void
609	{
610		super.updateDisplayList(unscaledWidth, unscaledHeight);
611
612		if (_listData)
613		{
614			if (Menu(_listData.owner).dataDescriptor.
615				getType(_data).toLowerCase() == "separator")
616			{
617				if (separatorIcon)
618				{
619					separatorIcon.x = 2;
620					separatorIcon.y = (unscaledHeight - separatorIcon.measuredHeight) / 2;
621					separatorIcon.setActualSize(unscaledWidth - 4, separatorIcon.measuredHeight);
622				}
623				return;
624			}
625
626			var iconWidth:Number = MenuListData(_listData).maxMeasuredIconWidth;
627			var typeIconWidth:Number = MenuListData(_listData).maxMeasuredTypeIconWidth;
628			var branchIconWidth:Number = MenuListData(_listData).maxMeasuredBranchIconWidth;
629			var useTwoColumns:Boolean = MenuListData(_listData).useTwoColumns;
630
631			var leftMargin:Number = Math.max(getStyle("leftIconGap"),
632									useTwoColumns ? iconWidth + typeIconWidth :
633													Math.max(iconWidth, typeIconWidth));
634			var rightMargin:Number = Math.max(getStyle("rightIconGap"), branchIconWidth);
635
636			// check to see if laying out in two columns or not
637			if (useTwoColumns)
638			{
639				// if in two columns, center the two columns
640				// center the respective icons (if present) in their own column
641				var left:Number = (leftMargin - (iconWidth + typeIconWidth))/2
642
643				if (_icon)
644				{
645					_icon.x = left + (iconWidth - _icon.measuredWidth)/2;
646					_icon.setActualSize(_icon.measuredWidth, _icon.measuredHeight);
647				}
648
649				if (typeIcon)
650				{
651					typeIcon.x = left + iconWidth + (typeIconWidth - typeIcon.measuredWidth)/2;
652					typeIcon.setActualSize(typeIcon.measuredWidth, typeIcon.measuredHeight);
653				}
654			}
655			else
656			{
657				// if in one column mode, just center the one item in the left icon gap
658				if (_icon)
659				{
660					_icon.x = (leftMargin - _icon.measuredWidth)/2;
661					_icon.setActualSize(_icon.measuredWidth, _icon.measuredHeight);
662				}
663
664				if (typeIcon)
665				{
666					typeIcon.x = (leftMargin - typeIcon.measuredWidth)/2;
667					typeIcon.setActualSize(typeIcon.measuredWidth, typeIcon.measuredHeight);
668				}
669			}
670
671			if (branchIcon)
672			{
673				branchIcon.x = unscaledWidth - rightMargin +
674							   (rightMargin - branchIcon.measuredWidth)/2;
675				branchIcon.setActualSize(branchIcon.measuredWidth,
676										 branchIcon.measuredHeight);
677			}
678
679			label.x = leftMargin;
680			label.setActualSize(unscaledWidth - leftMargin - rightMargin,
681								label.getExplicitOrMeasuredHeight());
682
683			if (_listData && !Menu(_listData.owner).showDataTips)
684			{
685				label.text = _listData.label;
686				if (label.truncateToFit())
687					toolTip = _listData.label;
688				else
689					toolTip = null;
690			}
691
692			var verticalAlign:String = getStyle("verticalAlign");
693			if (verticalAlign == "top")
694			{
695				label.y = 0;
696				if (_icon)
697					_icon.y = 0;
698				if (typeIcon)
699					typeIcon.y = 0;
700				if (branchIcon)
701					branchIcon.y = 0;
702			}
703			else if (verticalAlign == "bottom")
704			{
705				label.y = unscaledHeight - label.height + 2; // 2 for gutter
706				if (_icon)
707					_icon.y = unscaledHeight - _icon.height;
708				if (typeIcon)
709					typeIcon.y = unscaledHeight - typeIcon.height;
710				if (branchIcon)
711					branchIcon.y = unscaledHeight - branchIcon.height;
712			}
713			else
714			{
715				label.y = (unscaledHeight - label.height) / 2;
716				if (_icon)
717					_icon.y = (unscaledHeight - _icon.height) / 2;
718				if (typeIcon)
719					typeIcon.y = (unscaledHeight - typeIcon.height) / 2;
720				if (branchIcon)
721					branchIcon.y = (unscaledHeight - branchIcon.height) / 2;
722			}
723
724			var labelColor:Number;
725
726			if (data && parent)
727			{
728				if (!enabled)
729				{
730					labelColor = getStyle("disabledColor");
731				}
732				else if (Menu(listData.owner).isItemHighlighted(listData.uid))
733				{
734					labelColor = getStyle("textRollOverColor");
735				}
736				else if (Menu(listData.owner).isItemSelected(listData.uid))
737				{
738					labelColor = getStyle("textSelectedColor");
739				}
740				else
741				{
742					labelColor = getStyle("color");
743				}
744
745				label.setColor(labelColor);
746			}
747		}
748	}
749
750	/**
751	 *  @private
752	 */
753	override public function styleChanged(styleProp:String):void
754	{
755		super.styleChanged(styleProp);
756
757		if (!styleProp ||
758			styleProp == "styleName" ||
759			(styleProp.toLowerCase().indexOf("icon") != -1))
760		{
761			// If any icons change, invalidate everything.
762			// We could be smarter about this if it causes
763			// performance problems.
764			invalidateSize();
765			invalidateDisplayList();
766		}
767	}
768
769    //--------------------------------------------------------------------------
770    //
771    //  Methods
772    //
773    //--------------------------------------------------------------------------
774
775    /**
776     *  @private
777     *  Creates the title text field and adds it as a child of this component.
778     *
779     *  @param childIndex The index of where to add the child.
780	 *  If -1, the text field is appended to the end of the list.
781     */
782    mx_internal function createLabel(childIndex:int):void
783    {
784        if (!label)
785        {
786            label = IUITextField(createInFontContext(UITextField));
787
788			label.styleName = this;
789
790            if (childIndex == -1)
791                addChild(DisplayObject(label));
792            else
793                addChildAt(DisplayObject(label), childIndex);
794        }
795    }
796
797    /**
798     *  @private
799     *  Removes the title text field from this component.
800     */
801    mx_internal function removeLabel():void
802    {
803        if (label)
804        {
805            removeChild(DisplayObject(label));
806            label = null;
807        }
808    }
809
810    /**
811     *  @private
812     */
813    mx_internal function getLabel():IUITextField
814    {
815        return label;
816    }
817
818    /**
819     *  The width of the icon
820     *
821     *  @langversion 3.0
822     *  @playerversion Flash 9
823     *  @playerversion AIR 1.1
824     *  @productversion Flex 3
825     */
826    public function get measuredIconWidth():Number
827    {
828    	var horizontalGap:Number = getStyle("horizontalGap");
829    	return _icon ? _icon.measuredWidth + horizontalGap : 0;
830    }
831
832    /**
833     *  The width of the type icon (radio/check)
834     *
835     *  @langversion 3.0
836     *  @playerversion Flash 9
837     *  @playerversion AIR 1.1
838     *  @productversion Flex 3
839     */
840    public function get measuredTypeIconWidth():Number
841    {
842    	var horizontalGap:Number = getStyle("horizontalGap");
843
844    	if (typeIcon)
845    		return typeIcon.measuredWidth + horizontalGap;
846
847    	// even if there's no type icon, get what it's width would be...
848    	if (_data)
849    	{
850    		var typeIconClass:Class;
851    		var dataDescriptor:IMenuDataDescriptor =
852				Menu(_listData.owner).dataDescriptor;
853			var isEnabled:Boolean = dataDescriptor.isEnabled(_data);
854    		var typeVal:String = dataDescriptor.getType(_data);
855			if (typeVal)
856			{
857				typeVal = typeVal.toLowerCase();
858				if (typeVal == "radio")
859				{
860					typeIconClass = getStyle(isEnabled ?
861										 "radioIcon" :
862										 "radioDisabledIcon");
863				}
864				else if (typeVal == "check")
865				{
866					typeIconClass = getStyle(isEnabled ?
867										 "checkIcon" :
868										 "checkDisabledIcon");
869				}
870
871				if (typeIconClass)
872				{
873					typeIcon = new typeIconClass();
874					var typeIconWidth:Number = typeIcon.measuredWidth;
875					typeIcon = null;
876					return typeIconWidth + horizontalGap;
877				}
878			}
879    	}
880
881    	return 0;
882    }
883
884    /**
885     *  The width of the branch icon
886     *
887     *  @langversion 3.0
888     *  @playerversion Flash 9
889     *  @playerversion AIR 1.1
890     *  @productversion Flex 3
891     */
892    public function get measuredBranchIconWidth():Number
893    {
894    	var horizontalGap:Number = getStyle("horizontalGap");
895        return branchIcon ? branchIcon.measuredWidth + horizontalGap : 0;
896    }
897}
898
899}
900