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