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