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