PageRenderTime 50ms CodeModel.GetById 18ms app.highlight 15ms RepoModel.GetById 2ms app.codeStats 0ms

/AppKit/CPMenuItem/CPMenuItem.j

http://github.com/cacaodev/cappuccino
Unknown | 1023 lines | 830 code | 193 blank | 0 comment | 0 complexity | 0c80f83c773097cf0c8bea8579828899 MD5 | raw file
   1/*
   2 * CPMenuItem.j
   3 * AppKit
   4 *
   5 * Created by Francisco Tolmasky.
   6 * Copyright 2008, 280 North, Inc.
   7 *
   8 * This library is free software; you can redistribute it and/or
   9 * modify it under the terms of the GNU Lesser General Public
  10 * License as published by the Free Software Foundation; either
  11 * version 2.1 of the License, or (at your option) any later version.
  12 *
  13 * This library is distributed in the hope that it will be useful,
  14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16 * Lesser General Public License for more details.
  17 *
  18 * You should have received a copy of the GNU Lesser General Public
  19 * License along with this library; if not, write to the Free Software
  20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  21 */
  22
  23@import <Foundation/CPCoder.j>
  24@import <Foundation/CPObject.j>
  25@import <Foundation/CPString.j>
  26
  27@import "CPImage.j"
  28@import "CPText.j"
  29@import "CPView.j"
  30@import "_CPMenuItemView.j"
  31
  32@class CPMenu
  33
  34@global CPApp
  35
  36var CPMenuItemStringRepresentationDictionary = @{
  37        CPEscapeFunctionKey:        "\u238B",
  38        CPTabCharacter:             "\u21E5",
  39        CPBackTabCharacter:         "\u21E4",
  40        CPSpaceFunctionKey:         "\u2423",
  41        CPCarriageReturnCharacter:  "\u23CE",
  42        CPBackspaceCharacter:       "\u232B",
  43        CPDeleteFunctionKey:        "\u232B",
  44        CPDeleteCharacter:          "\u2326",
  45        CPHomeFunctionKey:          "\u21F1",
  46        CPEndFunctionKey:           "\u21F2",
  47        CPPageUpFunctionKey:        "\u21DE",
  48        CPPageDownFunctionKey:      "\u21DF",
  49        CPUpArrowFunctionKey:       "\u2191",
  50        CPDownArrowFunctionKey:     "\u2193",
  51        CPLeftArrowFunctionKey:     "\u2190",
  52        CPRightArrowFunctionKey:    "\u2192",
  53        CPClearDisplayFunctionKey:  "\u2327",
  54    };
  55
  56/*!
  57    @ingroup appkit
  58    @class CPMenuItem
  59
  60    A CPMenuItem is added to a CPMenu.
  61    It has an action and a target for that action to be sent to
  62    whenever the item is 'activated'.
  63*/
  64@implementation CPMenuItem : CPObject
  65{
  66    BOOL            _isSeparator;
  67
  68    CPString        _title;
  69    //CPAttributedString  _attributedTitle;
  70
  71    CPFont          _font;
  72
  73    id              _target;
  74    SEL             _action;
  75
  76    BOOL            _isEnabled;
  77    BOOL            _isHidden;
  78
  79    int             _tag;
  80    int             _state;
  81
  82    CPImage         _image;
  83    CPImage         _alternateImage;
  84    CPImage         _onStateImage;
  85    CPImage         _offStateImage;
  86    CPImage         _mixedStateImage;
  87
  88    CPMenu          _submenu;
  89    CPMenu          _menu;
  90
  91    CPString        _keyEquivalent;
  92    unsigned        _keyEquivalentModifierMask;
  93
  94    int             _mnemonicLocation;
  95
  96    BOOL            _isAlternate;
  97    int             _indentationLevel;
  98
  99    CPString        _toolTip;
 100    id              _representedObject;
 101    CPView          _view;
 102
 103    int             _changeCount;
 104
 105    _CPMenuItemView _menuItemView;
 106}
 107
 108+ (Class)_binderClassForBinding:(CPString)aBinding
 109{
 110    if ([aBinding hasPrefix:CPEnabledBinding])
 111        return [CPMultipleValueAndBinding class];
 112    else if (aBinding === CPTargetBinding || [aBinding hasPrefix:CPArgumentBinding])
 113        return [CPActionBinding class];
 114
 115    return [super _binderClassForBinding:aBinding];
 116}
 117
 118- (id)init
 119{
 120    return [self initWithTitle:@"" action:nil keyEquivalent:nil];
 121}
 122
 123/*!
 124    Initializes the menu item with a title, action, and keyboard equivalent.
 125    @param aTitle the menu item's title
 126    @param anAction the action that gets triggered when the item is selected
 127    @param aKeyEquivalent the keyboard shortcut for the item
 128    @return the initialized menu item
 129*/
 130- (id)initWithTitle:(CPString)aTitle action:(SEL)anAction keyEquivalent:(CPString)aKeyEquivalent
 131{
 132    self = [super init];
 133
 134    if (self)
 135    {
 136        _changeCount = 0;
 137        _isSeparator = NO;
 138
 139        _title = aTitle;
 140        _action = anAction;
 141
 142        _isEnabled = YES;
 143        _isHidden = NO;
 144
 145        _tag = 0;
 146        _state = CPOffState;
 147
 148        _keyEquivalent = aKeyEquivalent || @"";
 149        _keyEquivalentModifierMask = CPPlatformActionKeyMask;
 150
 151        _indentationLevel = 0;
 152
 153        _mnemonicLocation = CPNotFound;
 154    }
 155
 156    return self;
 157}
 158
 159// Enabling a Menu Item
 160/*!
 161    Sets whether the menu item is enabled or not
 162    @param isEnabled \c YES enables the item. \c NO disables it.
 163*/
 164- (void)setEnabled:(BOOL)isEnabled
 165{
 166    if (_isEnabled === isEnabled)
 167        return;
 168
 169    if (!isEnabled && [self isHighlighted])
 170        [_menu _highlightItemAtIndex:CPNotFound];
 171
 172    _isEnabled = !!isEnabled;
 173
 174    [_menuItemView setDirty];
 175    [_menu itemChanged:self];
 176}
 177
 178/*!
 179    Returns \c YES if the item is enabled.
 180*/
 181- (BOOL)isEnabled
 182{
 183    return _isEnabled;
 184}
 185
 186// Managing Hidden Status
 187/*!
 188    Sets whether the item should be hidden. A hidden item can not be triggered by keyboard shortcuts.
 189    @param isHidden \c YES hides the item. \c NO reveals it.
 190*/
 191- (void)setHidden:(BOOL)isHidden
 192{
 193    if (_isHidden == isHidden)
 194        return;
 195
 196    _isHidden = isHidden;
 197
 198    [_menu itemChanged:self];
 199}
 200
 201/*!
 202    Returns \c YES if the item is hidden.
 203*/
 204- (BOOL)isHidden
 205{
 206    return _isHidden;
 207}
 208
 209/*!
 210    Returns \c YES if the item is hidden or if one of it's supermenus is hidden.
 211*/
 212- (BOOL)isHiddenOrHasHiddenAncestor
 213{
 214    if (_isHidden)
 215        return YES;
 216
 217    var supermenu = [_menu supermenu];
 218
 219    if ([[supermenu itemAtIndex:[supermenu indexOfItemWithSubmenu:_menu]] isHiddenOrHasHiddenAncestor])
 220        return YES;
 221
 222    return NO;
 223}
 224
 225// Managing Target and Action
 226/*!
 227    Sets the menu item's action target.
 228    @param aTarget the target for the action
 229*/
 230- (void)setTarget:(id)aTarget
 231{
 232    _target = aTarget;
 233}
 234
 235/*!
 236    Returns the item's action target
 237*/
 238- (id)target
 239{
 240    return _target;
 241}
 242
 243/*!
 244    Sets the action that gets sent to the item's target when triggered.
 245    @param anAction the action to send
 246*/
 247- (void)setAction:(SEL)anAction
 248{
 249    _action = anAction;
 250}
 251
 252/*!
 253    Returns the item's action.
 254*/
 255- (SEL)action
 256{
 257    return _action;
 258}
 259
 260// Managing the Title
 261/*!
 262    Sets the item's title.
 263    @param aTitle the item's new title
 264*/
 265- (void)setTitle:(CPString)aTitle
 266{
 267    _mnemonicLocation = CPNotFound;
 268
 269    if (_title == aTitle)
 270        return;
 271
 272    _title = aTitle;
 273
 274    [_menuItemView setDirty];
 275
 276    [_menu itemChanged:self];
 277}
 278
 279/*!
 280    Returns the menu item's title.
 281*/
 282- (CPString)title
 283{
 284    return _title;
 285}
 286
 287/*!
 288    Set's the item's text color
 289*/
 290- (void)setTextColor:(CPString)aColor
 291{
 292    //FIXME IMPLEMENT
 293}
 294
 295/*!
 296    Sets the font for the text of this menu item
 297    @param aFont the font for the menu item
 298*/
 299- (void)setFont:(CPFont)aFont
 300{
 301    if ([_font isEqual:aFont])
 302        return;
 303
 304    _font = aFont;
 305
 306    [_menu itemChanged:self];
 307
 308    [_menuItemView setDirty];
 309}
 310
 311/*!
 312    Returns the menu item's font
 313*/
 314- (CPFont)font
 315{
 316    return _font;
 317}
 318
 319/*
 320- (void)setAttributedTitle:(CPAttributedString)aTitle
 321{
 322}
 323
 324- (CPAttributedString)attributedTitle
 325{
 326}
 327*/
 328
 329// Managing the Tag
 330/*!
 331    Sets the menu item's tag
 332    @param aTag the tag for the item
 333*/
 334- (void)setTag:(int)aTag
 335{
 336    _tag = aTag;
 337}
 338
 339/*!
 340    Returns the item's tag
 341*/
 342- (int)tag
 343{
 344    return _tag;
 345}
 346
 347/*!
 348    Sets the state of the menu item. Possible states are:
 349<pre>
 350CPMixedState
 351CPOnState
 352CPOffState
 353</pre>
 354*/
 355- (void)setState:(int)aState
 356{
 357    if (_state == aState)
 358        return;
 359
 360    _state = aState;
 361
 362    [_menu itemChanged:self];
 363
 364    [_menuItemView setDirty];
 365}
 366
 367/*!
 368    Returns the menu item's current state. Possible states are:
 369<pre>
 370CPMixedState
 371CPOnState
 372CPOffState
 373</pre>
 374*/
 375- (int)state
 376{
 377    return _state;
 378}
 379
 380// Managing the Image
 381/*!
 382    Sets the menu item's image
 383    @param anImage the menu item's image
 384*/
 385- (void)setImage:(CPImage)anImage
 386{
 387    if (_image == anImage)
 388        return;
 389
 390    _image = anImage;
 391
 392    [_menuItemView setDirty];
 393
 394    [_menu itemChanged:self];
 395}
 396
 397/*!
 398    Returns the menu item's image
 399*/
 400- (CPImage)image
 401{
 402    return _image;
 403}
 404
 405/*!
 406    Sets the menu item's alternate image
 407    @param anImage the menu item's alternate image
 408*/
 409- (void)setAlternateImage:(CPImage)anImage
 410{
 411    _alternateImage = anImage;
 412}
 413
 414/*!
 415    Returns the menu item's alternate image
 416*/
 417- (CPImage)alternateImage
 418{
 419    return _alternateImage;
 420}
 421
 422/*!
 423    Sets the image that is shown when the
 424    menu item is in the 'on' state.
 425    @param anImage the image to show
 426*/
 427- (void)setOnStateImage:(CPImage)anImage
 428{
 429    if (_onStateImage == anImage)
 430        return;
 431
 432    _onStateImage = anImage;
 433    [_menu itemChanged:self];
 434}
 435
 436/*!
 437    Returns the image shown when the menu item is in the 'on' state.
 438*/
 439- (CPImage)onStateImage
 440{
 441    return _onStateImage;
 442}
 443
 444/*!
 445    Sets the image that is shown when the menu item is in the 'off' state.
 446    @param anImage the image to show
 447*/
 448- (void)setOffStateImage:(CPImage)anImage
 449{
 450    if (_offStateImage == anImage)
 451        return;
 452
 453    _offStateImage = anImage;
 454    [_menu itemChanged:self];
 455}
 456
 457/*!
 458    Returns the image shown when the menu item is in the 'off' state.
 459*/
 460- (CPImage)offStateImage
 461{
 462    return _offStateImage;
 463}
 464
 465/*!
 466    Sets the image that is shown when the menu item is in the 'mixed' state.
 467    @param anImage the image to show
 468*/
 469- (void)setMixedStateImage:(CPImage)anImage
 470{
 471    if (_mixedStateImage == anImage)
 472        return;
 473
 474    _mixedStateImage = anImage;
 475    [_menu itemChanged:self];
 476}
 477
 478/*!
 479    Returns the image shown when the menu item is
 480    in the 'mixed' state.
 481*/
 482- (CPImage)mixedStateImage
 483{
 484    return _mixedStateImage;
 485}
 486
 487// Managing Submenus
 488/*!
 489    Sets the submenu for this item
 490    @param aMenu the submenu
 491*/
 492- (void)setSubmenu:(CPMenu)aMenu
 493{
 494    if (_submenu === aMenu)
 495        return;
 496
 497    var supermenu = [_submenu supermenu];
 498
 499    if (supermenu)
 500        [CPException raise:CPInvalidArgumentException
 501           reason: @"Can't add submenu \"" + [aMenu title] + "\" to item \"" + [self title] + "\", because it is already submenu of \"" + [[aMenu supermenu] title] + "\""];
 502
 503    _submenu = aMenu;
 504
 505    if (_submenu)
 506    {
 507        [_submenu setSupermenu:_menu];
 508        [_submenu setTitle:[self title]]
 509
 510        [self setTarget:_menu];
 511        [self setAction:@selector(submenuAction:)];
 512    }
 513    else
 514    {
 515        [self setTarget:nil];
 516        [self setAction:NULL];
 517    }
 518
 519    [_menuItemView setDirty];
 520
 521    [_menu itemChanged:self];
 522}
 523
 524/*!
 525    Returns the submenu of the item. \c nil if there is no submenu.
 526*/
 527- (CPMenu)submenu
 528{
 529    return _submenu;
 530}
 531
 532/*!
 533    Returns \c YES if the menu item has a submenu.
 534*/
 535- (BOOL)hasSubmenu
 536{
 537    return _submenu ? YES : NO;
 538}
 539
 540// Getting a Separator Item
 541
 542/*!
 543    Returns a new menu item separator.
 544*/
 545+ (CPMenuItem)separatorItem
 546{
 547    var separatorItem = [[self alloc] initWithTitle:@"" action:nil keyEquivalent:nil];
 548
 549    separatorItem._isSeparator = YES;
 550
 551    return separatorItem;
 552}
 553
 554/*!
 555    Returns \c YES if the menu item is a separator.
 556*/
 557- (BOOL)isSeparatorItem
 558{
 559    return _isSeparator;
 560}
 561
 562// Managing the Owning Menu
 563/*!
 564    Set the container menu of this item.
 565    @param aMenu the item's container menu
 566*/
 567- (void)setMenu:(CPMenu)aMenu
 568{
 569    _menu = aMenu;
 570}
 571
 572/*!
 573    Returns the container menu of this item
 574*/
 575- (CPMenu)menu
 576{
 577    return _menu;
 578}
 579
 580//
 581
 582/*!
 583    Sets the keyboard shortcut for this menu item
 584    @param aString the keyboard shortcut
 585*/
 586- (void)setKeyEquivalent:(CPString)aString
 587{
 588    _keyEquivalent = aString || @"";
 589}
 590
 591/*!
 592    Returns the keyboard shortcut for this menu item
 593*/
 594- (CPString)keyEquivalent
 595{
 596    return _keyEquivalent;
 597}
 598
 599/*!
 600    Sets the modifier mask used for the item's keyboard shortcut.
 601    Can be a combination of:
 602<pre>
 603CPShiftKeyMask
 604CPAlternateKeyMask
 605CPCommandKeyMask
 606CPControlKeyMask
 607</pre>
 608*/
 609- (void)setKeyEquivalentModifierMask:(unsigned)aMask
 610{
 611    _keyEquivalentModifierMask = aMask;
 612}
 613
 614/*!
 615    Returns the item's keyboard shortcut modifier mask.
 616    Can be a combination of:
 617<pre>
 618CPShiftKeyMask
 619CPAlternateKeyMask
 620CPCommandKeyMask
 621CPControlKeyMask
 622</pre>
 623*/
 624- (unsigned)keyEquivalentModifierMask
 625{
 626    return _keyEquivalentModifierMask;
 627}
 628
 629- (CPString)keyEquivalentStringRepresentation
 630{
 631    if (![_keyEquivalent length])
 632        return @"";
 633
 634    var string = _keyEquivalent.toUpperCase(),
 635        needsShift = _keyEquivalentModifierMask & CPShiftKeyMask ||
 636                    (string === _keyEquivalent && _keyEquivalent.toLowerCase() !== _keyEquivalent.toUpperCase());
 637
 638    if ([CPMenuItemStringRepresentationDictionary objectForKey:string])
 639        string = [CPMenuItemStringRepresentationDictionary objectForKey:string];
 640
 641    if (CPBrowserIsOperatingSystem(CPMacOperatingSystem))
 642    {
 643        if (_keyEquivalentModifierMask & CPCommandKeyMask)
 644            string = "\u2318" + string;
 645
 646        if (needsShift)
 647            string = "\u21E7" + string;
 648
 649        if (_keyEquivalentModifierMask & CPAlternateKeyMask)
 650            string = "\u2325" + string;
 651
 652        if (_keyEquivalentModifierMask & CPControlKeyMask)
 653            string = "\u2303" + string;
 654    }
 655    else
 656    {
 657        if (needsShift)
 658            string = "Shift-" + string;
 659
 660        if (_keyEquivalentModifierMask & CPAlternateKeyMask)
 661            string = "Alt-" + string;
 662
 663        if (_keyEquivalentModifierMask & CPControlKeyMask || _keyEquivalentModifierMask & CPCommandKeyMask)
 664            string = "Ctrl-" + string;
 665    }
 666
 667    return string;
 668}
 669
 670// Managing Mnemonics
 671/*!
 672    Sets the index of the mnemonic character in the title. The character
 673    will be underlined and is used as a shortcut for navigation.
 674    @param aLocation the index of the character in the title
 675*/
 676- (void)setMnemonicLocation:(unsigned)aLocation
 677{
 678    _mnemonicLocation = aLocation;
 679}
 680
 681/*!
 682    Returns the index of the mnemonic character in the title.
 683*/
 684- (unsigned)mnemonicLocation
 685{
 686    return _mnemonicLocation;
 687}
 688
 689/*!
 690    Sets the title of the menu item and the mnemonic character. The mnemonic character should be preceded by an '&'.
 691    @param aTitle the title string with a denoted mnemonic
 692*/
 693- (void)setTitleWithMnemonicLocation:(CPString)aTitle
 694{
 695    var location = [aTitle rangeOfString:@"&"].location;
 696
 697    if (location == CPNotFound)
 698        [self setTitle:aTitle];
 699    else
 700    {
 701        [self setTitle:[aTitle substringToIndex:location] + [aTitle substringFromIndex:location + 1]];
 702        [self setMnemonicLocation:location];
 703    }
 704}
 705
 706/*!
 707    Returns the menu items mnemonic character
 708*/
 709- (CPString)mnemonic
 710{
 711    return _mnemonicLocation == CPNotFound ? @"" : [_title characterAtIndex:_mnemonicLocation];
 712}
 713
 714// Managing Alternates
 715
 716/*!
 717    Sets whether this item is an alternate for the previous menu item.
 718    @param isAlternate \c YES denotes that this menu item is an alternate
 719*/
 720- (void)setAlternate:(BOOL)isAlternate
 721{
 722    _isAlternate = isAlternate;
 723}
 724
 725/*!
 726    Returns \c YES if the menu item is an alternate for the previous item.
 727*/
 728- (BOOL)isAlternate
 729{
 730    return _isAlternate;
 731}
 732
 733// Managing Indentation Levels
 734
 735/*!
 736    Sets the indentation level of the menu item. Must be a value between 0 and 15 (inclusive).
 737    @param aLevel the item's new indentation level
 738    @throws CPInvalidArgumentException if aLevel is less than 0
 739*/
 740- (void)setIndentationLevel:(unsigned)aLevel
 741{
 742    if (aLevel < 0)
 743        [CPException raise:CPInvalidArgumentException reason:"setIndentationLevel: argument must be greater than or equal to 0."];
 744
 745    _indentationLevel = MIN(15, aLevel);
 746}
 747
 748/*!
 749    Returns the menu item's indentation level. This is a value between 0 and 15 (inclusive).
 750*/
 751- (unsigned)indentationLevel
 752{
 753    return _indentationLevel;
 754}
 755
 756// Managing Tool Tips
 757/*!
 758    Sets the tooltip for the menu item.
 759    @param aToolTip the tool tip for the item
 760*/
 761- (void)setToolTip:(CPString)aToolTip
 762{
 763    _toolTip = aToolTip;
 764}
 765
 766/*!
 767    Returns the item's tooltip
 768*/
 769- (CPString)toolTip
 770{
 771    return _toolTip;
 772}
 773
 774// Representing an Object
 775
 776/*!
 777    Sets the menu item's represented object. This is a kind of tag for the developer. Not a UI feature.
 778    @param anObject the represented object
 779*/
 780- (void)setRepresentedObject:(id)anObject
 781{
 782    _representedObject = anObject;
 783}
 784
 785/*!
 786    Returns the item's represented object.
 787*/
 788- (id)representedObject
 789{
 790    return _representedObject;
 791}
 792
 793// Managing the View
 794
 795/*!
 796    Sets the view for the menu item
 797    @param aView the menu's item's view
 798*/
 799- (void)setView:(CPView)aView
 800{
 801    if (_view === aView)
 802        return;
 803
 804    _view = aView;
 805
 806    [_menuItemView setDirty];
 807
 808    [_menu itemChanged:self];
 809}
 810
 811/*!
 812    Returns the menu item's view
 813*/
 814- (CPView)view
 815{
 816    return _view;
 817}
 818
 819// Getting Highlighted Status
 820
 821/*!
 822    Returns \c YES if the menu item is highlighted.
 823*/
 824- (BOOL)isHighlighted
 825{
 826    return [[self menu] highlightedItem] == self;
 827}
 828
 829#pragma mark CPObject Overrides
 830
 831/*!
 832    Returns a copy of the item. The copy does not belong If the item has a submenu, it is NOT copied.
 833*/
 834- (id)copy
 835{
 836    var item = [[CPMenuItem alloc] init];
 837
 838    // No point in going through accessors and doing lots of unnecessary state checking/updating
 839    item._isSeparator = _isSeparator;
 840
 841    [item setTitle:_title];
 842    [item setFont:_font];
 843    [item setTarget:_target];
 844    [item setAction:_action];
 845    [item setEnabled:_isEnabled];
 846    [item setHidden:_isHidden]
 847    [item setTag:_tag];
 848    [item setState:_state];
 849    [item setImage:_image];
 850    [item setAlternateImage:_alternateImage];
 851    [item setOnStateImage:_onStateImage];
 852    [item setOffStateImage:_offStateImage];
 853    [item setMixedStateImage:_mixedStateImage];
 854    [item setKeyEquivalent:_keyEquivalent];
 855    [item setKeyEquivalentModifierMask:_keyEquivalentModifierMask];
 856    [item setMnemonicLocation:_mnemonicLocation];
 857    [item setAlternate:_isAlternate];
 858    [item setIndentationLevel:_indentationLevel];
 859    [item setToolTip:_toolTip];
 860    [item setRepresentedObject:_representedObject];
 861
 862    return item;
 863}
 864
 865- (id)mutableCopy
 866{
 867    return [self copy];
 868}
 869
 870#pragma mark Internal
 871
 872/*
 873    @ignore
 874*/
 875- (id)_menuItemView
 876{
 877    if (!_menuItemView)
 878        _menuItemView = [[_CPMenuItemView alloc] initWithFrame:CGRectMakeZero() forMenuItem:self];
 879
 880    return _menuItemView;
 881}
 882
 883- (BOOL)_isSelectable
 884{
 885    return ![self submenu] || [self action] !== @selector(submenuAction:) || [self target] !== [self menu];
 886}
 887
 888- (BOOL)_isMenuBarButton
 889{
 890    return ![self submenu] && [self menu] === [CPApp mainMenu];
 891}
 892
 893- (CPString)description
 894{
 895    return [super description] + @" target: " + [self target] + @" action: " + CPStringFromSelector([self action]);
 896}
 897
 898@end
 899
 900var CPMenuItemIsSeparatorKey                = @"CPMenuItemIsSeparatorKey",
 901
 902    CPMenuItemTitleKey                      = @"CPMenuItemTitleKey",
 903    CPMenuItemTargetKey                     = @"CPMenuItemTargetKey",
 904    CPMenuItemActionKey                     = @"CPMenuItemActionKey",
 905
 906    CPMenuItemIsEnabledKey                  = @"CPMenuItemIsEnabledKey",
 907    CPMenuItemIsHiddenKey                   = @"CPMenuItemIsHiddenKey",
 908
 909    CPMenuItemTagKey                        = @"CPMenuItemTagKey",
 910    CPMenuItemStateKey                      = @"CPMenuItemStateKey",
 911
 912    CPMenuItemImageKey                      = @"CPMenuItemImageKey",
 913    CPMenuItemAlternateImageKey             = @"CPMenuItemAlternateImageKey",
 914
 915    CPMenuItemSubmenuKey                    = @"CPMenuItemSubmenuKey",
 916    CPMenuItemMenuKey                       = @"CPMenuItemMenuKey",
 917
 918    CPMenuItemKeyEquivalentKey              = @"CPMenuItemKeyEquivalentKey",
 919    CPMenuItemKeyEquivalentModifierMaskKey  = @"CPMenuItemKeyEquivalentModifierMaskKey",
 920
 921    CPMenuItemIndentationLevelKey           = @"CPMenuItemIndentationLevelKey",
 922
 923    CPMenuItemRepresentedObjectKey          = @"CPMenuItemRepresentedObjectKey",
 924    CPMenuItemViewKey                       = @"CPMenuItemViewKey";
 925
 926#define DEFAULT_VALUE(aKey, aDefaultValue) [aCoder containsValueForKey:(aKey)] ? [aCoder decodeObjectForKey:(aKey)] : (aDefaultValue)
 927#define ENCODE_IFNOT(aKey, aValue, aDefaultValue) if ((aValue) !== (aDefaultValue)) [aCoder encodeObject:(aValue) forKey:(aKey)];
 928
 929@implementation CPMenuItem (CPCoding)
 930/*!
 931    Initializes the menu item from a coder.
 932    @param aCoder the coder from which to initialize
 933    @return the initialized menu item
 934*/
 935- (id)initWithCoder:(CPCoder)aCoder
 936{
 937    self = [super init];
 938
 939    if (self)
 940    {
 941        _changeCount = 0;
 942        _isSeparator = [aCoder containsValueForKey:CPMenuItemIsSeparatorKey] && [aCoder decodeBoolForKey:CPMenuItemIsSeparatorKey];
 943
 944        _title = [aCoder decodeObjectForKey:CPMenuItemTitleKey];
 945
 946//        _font;
 947
 948        _target = [aCoder decodeObjectForKey:CPMenuItemTargetKey];
 949        _action = [aCoder decodeObjectForKey:CPMenuItemActionKey];
 950
 951        _isEnabled = DEFAULT_VALUE(CPMenuItemIsEnabledKey, YES);
 952        _isHidden = [aCoder decodeBoolForKey:CPMenuItemIsHiddenKey];
 953        _tag = [aCoder decodeIntForKey:CPMenuItemTagKey];
 954        _state = [aCoder decodeIntForKey:CPMenuItemStateKey];
 955
 956        _image = [aCoder decodeObjectForKey:CPMenuItemImageKey];
 957        _alternateImage = [aCoder decodeObjectForKey:CPMenuItemAlternateImageKey];
 958//    CPImage         _onStateImage;
 959//    CPImage         _offStateImage;
 960//    CPImage         _mixedStateImage;
 961
 962        // This order matters because setSubmenu: needs _menu to be around.
 963        _menu = [aCoder decodeObjectForKey:CPMenuItemMenuKey];
 964        [self setSubmenu:[aCoder decodeObjectForKey:CPMenuItemSubmenuKey]];
 965
 966        _keyEquivalent = [aCoder decodeObjectForKey:CPMenuItemKeyEquivalentKey] || @"";
 967        _keyEquivalentModifierMask = [aCoder decodeIntForKey:CPMenuItemKeyEquivalentModifierMaskKey];
 968
 969//    int             _mnemonicLocation;
 970
 971//    BOOL            _isAlternate;
 972
 973        [self setIndentationLevel:[aCoder decodeIntForKey:CPMenuItemIndentationLevelKey]];
 974
 975//    CPString        _toolTip;
 976
 977        _representedObject = [aCoder decodeObjectForKey:CPMenuItemRepresentedObjectKey];
 978        _view = [aCoder decodeObjectForKey:CPMenuItemViewKey];
 979    }
 980
 981    return self;
 982}
 983
 984/*!
 985    Writes the menu item out to a coder.
 986    @param aCoder the coder to write the menu item out to
 987*/
 988- (void)encodeWithCoder:(CPCoder)aCoder
 989{
 990    if (_isSeparator)
 991        [aCoder encodeBool:_isSeparator forKey:CPMenuItemIsSeparatorKey];
 992
 993    [aCoder encodeObject:_title forKey:CPMenuItemTitleKey];
 994
 995    [aCoder encodeObject:_target forKey:CPMenuItemTargetKey];
 996    [aCoder encodeObject:_action forKey:CPMenuItemActionKey];
 997
 998    ENCODE_IFNOT(CPMenuItemIsEnabledKey, _isEnabled, YES);
 999    ENCODE_IFNOT(CPMenuItemIsHiddenKey, _isHidden, NO);
1000
1001    ENCODE_IFNOT(CPMenuItemTagKey, _tag, 0);
1002    ENCODE_IFNOT(CPMenuItemStateKey, _state, CPOffState);
1003
1004    ENCODE_IFNOT(CPMenuItemImageKey, _image, nil);
1005    ENCODE_IFNOT(CPMenuItemAlternateImageKey, _alternateImage, nil);
1006
1007    ENCODE_IFNOT(CPMenuItemSubmenuKey, _submenu, nil);
1008    ENCODE_IFNOT(CPMenuItemMenuKey, _menu, nil);
1009
1010    if (_keyEquivalent && _keyEquivalent.length)
1011        [aCoder encodeObject:_keyEquivalent forKey:CPMenuItemKeyEquivalentKey];
1012
1013    if (_keyEquivalentModifierMask)
1014        [aCoder encodeObject:_keyEquivalentModifierMask forKey:CPMenuItemKeyEquivalentModifierMaskKey];
1015
1016    if (_indentationLevel > 0)
1017        [aCoder encodeInt:_indentationLevel forKey:CPMenuItemIndentationLevelKey];
1018
1019    ENCODE_IFNOT(CPMenuItemRepresentedObjectKey, _representedObject, nil);
1020    ENCODE_IFNOT(CPMenuItemViewKey, _view, nil);
1021}
1022
1023@end