API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPMenuItem.j
Go to the documentation of this 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 
24 
25 @class CPMenu
26 
27 @global CPApp
28 
29 var CPMenuItemStringRepresentationDictionary = @{
30  CPEscapeFunctionKey: "\u238B",
31  CPTabCharacter: "\u21E5",
32  CPBackTabCharacter: "\u21E4",
33  CPSpaceFunctionKey: "\u2423",
34  CPCarriageReturnCharacter: "\u23CE",
35  CPBackspaceCharacter: "\u232B",
36  CPDeleteFunctionKey: "\u232B",
37  CPDeleteCharacter: "\u2326",
38  CPHomeFunctionKey: "\u21F1",
39  CPEndFunctionKey: "\u21F2",
40  CPPageUpFunctionKey: "\u21DE",
41  CPPageDownFunctionKey: "\u21DF",
42  CPUpArrowFunctionKey: "\u2191",
43  CPDownArrowFunctionKey: "\u2193",
44  CPLeftArrowFunctionKey: "\u2190",
45  CPRightArrowFunctionKey: "\u2192",
46  CPClearDisplayFunctionKey: "\u2327",
47  };
48 
57 @implementation CPMenuItem : CPObject
58 {
59  BOOL _isSeparator;
60 
61  CPString _title;
62  //CPAttributedString _attributedTitle;
63 
64  CPFont _font;
65 
66  id _target;
67  SEL _action;
68 
69  BOOL _isEnabled;
70  BOOL _isHidden;
71 
72  int _tag;
73  int _state;
74 
75  CPImage _image;
76  CPImage _alternateImage;
77  CPImage _onStateImage;
78  CPImage _offStateImage;
79  CPImage _mixedStateImage;
80 
81  CPMenu _submenu;
82  CPMenu _menu;
83 
84  CPString _keyEquivalent;
85  unsigned _keyEquivalentModifierMask;
86 
87  int _mnemonicLocation;
88 
89  BOOL _isAlternate;
90  int _indentationLevel;
91 
92  CPString _toolTip;
93  id _representedObject;
94  CPView _view;
95 
96  int _changeCount;
97 
98  _CPMenuItemView _menuItemView;
99 }
100 
101 + (Class)_binderClassForBinding:(CPString)aBinding
102 {
103  if ([aBinding hasPrefix:CPEnabledBinding])
104  return [CPMultipleValueAndBinding class];
105  else if (aBinding === CPTargetBinding || [aBinding hasPrefix:CPArgumentBinding])
106  return [CPActionBinding class];
107 
108  return [super _binderClassForBinding:aBinding];
109 }
110 
111 - (id)init
112 {
113  return [self initWithTitle:@"" action:nil keyEquivalent:nil];
114 }
115 
123 - (id)initWithTitle:(CPString)aTitle action:(SEL)anAction keyEquivalent:(CPString)aKeyEquivalent
124 {
125  self = [super init];
126 
127  if (self)
128  {
129  _changeCount = 0;
130  _isSeparator = NO;
131 
132  _title = aTitle;
133  _action = anAction;
134 
135  _isEnabled = YES;
136  _isHidden = NO;
137 
138  _tag = 0;
139  _state = CPOffState;
140 
141  _keyEquivalent = aKeyEquivalent || @"";
142  _keyEquivalentModifierMask = CPPlatformActionKeyMask;
143 
144  _indentationLevel = 0;
145 
146  _mnemonicLocation = CPNotFound;
147  }
148 
149  return self;
150 }
151 
152 // Enabling a Menu Item
157 - (void)setEnabled:(BOOL)isEnabled
158 {
159  if (_isEnabled === isEnabled)
160  return;
161 
162  if (!isEnabled && [self isHighlighted])
163  [_menu _highlightItemAtIndex:CPNotFound];
164 
165  _isEnabled = !!isEnabled;
166 
167  [_menuItemView setDirty];
168  [_menu itemChanged:self];
169 }
170 
174 - (BOOL)isEnabled
175 {
176  return _isEnabled;
177 }
178 
179 // Managing Hidden Status
184 - (void)setHidden:(BOOL)isHidden
185 {
186  if (_isHidden == isHidden)
187  return;
188 
189  _isHidden = isHidden;
190 
191  [_menu itemChanged:self];
192 }
193 
197 - (BOOL)isHidden
198 {
199  return _isHidden;
200 }
201 
205 - (BOOL)isHiddenOrHasHiddenAncestor
206 {
207  if (_isHidden)
208  return YES;
209 
210  var supermenu = [_menu supermenu];
211 
212  if ([[supermenu itemAtIndex:[supermenu indexOfItemWithSubmenu:_menu]] isHiddenOrHasHiddenAncestor])
213  return YES;
214 
215  return NO;
216 }
217 
218 // Managing Target and Action
223 - (void)setTarget:(id)aTarget
224 {
225  _target = aTarget;
226 }
227 
231 - (id)target
232 {
233  return _target;
234 }
235 
240 - (void)setAction:(SEL)anAction
241 {
242  _action = anAction;
243 }
244 
248 - (SEL)action
249 {
250  return _action;
251 }
252 
253 // Managing the Title
258 - (void)setTitle:(CPString)aTitle
259 {
260  _mnemonicLocation = CPNotFound;
261 
262  if (_title == aTitle)
263  return;
264 
265  _title = aTitle;
266 
267  [_menuItemView setDirty];
268 
269  [_menu itemChanged:self];
270 }
271 
275 - (CPString)title
276 {
277  return _title;
278 }
279 
283 - (void)setTextColor:(CPString)aColor
284 {
285  //FIXME IMPLEMENT
286 }
287 
292 - (void)setFont:(CPFont)aFont
293 {
294  if ([_font isEqual:aFont])
295  return;
296 
297  _font = aFont;
298 
299  [_menu itemChanged:self];
300 
301  [_menuItemView setDirty];
302 }
303 
307 - (CPFont)font
308 {
309  return _font;
310 }
311 
312 /*
313 - (void)setAttributedTitle:(CPAttributedString)aTitle
314 {
315 }
316 
317 - (CPAttributedString)attributedTitle
318 {
319 }
320 */
321 
322 // Managing the Tag
327 - (void)setTag:(int)aTag
328 {
329  _tag = aTag;
330 }
331 
335 - (int)tag
336 {
337  return _tag;
338 }
339 
348 - (void)setState:(int)aState
349 {
350  if (_state == aState)
351  return;
352 
353  _state = aState;
354 
355  [_menu itemChanged:self];
356 
357  [_menuItemView setDirty];
358 }
359 
368 - (int)state
369 {
370  return _state;
371 }
372 
373 // Managing the Image
378 - (void)setImage:(CPImage)anImage
379 {
380  if (_image == anImage)
381  return;
382 
383  _image = anImage;
384 
385  [_menuItemView setDirty];
386 
387  [_menu itemChanged:self];
388 }
389 
393 - (CPImage)image
394 {
395  return _image;
396 }
397 
402 - (void)setAlternateImage:(CPImage)anImage
403 {
404  _alternateImage = anImage;
405 }
406 
410 - (CPImage)alternateImage
411 {
412  return _alternateImage;
413 }
414 
420 - (void)setOnStateImage:(CPImage)anImage
421 {
422  if (_onStateImage == anImage)
423  return;
424 
425  _onStateImage = anImage;
426  [_menu itemChanged:self];
427 }
428 
432 - (CPImage)onStateImage
433 {
434  return _onStateImage;
435 }
436 
441 - (void)setOffStateImage:(CPImage)anImage
442 {
443  if (_offStateImage == anImage)
444  return;
445 
446  _offStateImage = anImage;
447  [_menu itemChanged:self];
448 }
449 
453 - (CPImage)offStateImage
454 {
455  return _offStateImage;
456 }
457 
462 - (void)setMixedStateImage:(CPImage)anImage
463 {
464  if (_mixedStateImage == anImage)
465  return;
466 
467  _mixedStateImage = anImage;
468  [_menu itemChanged:self];
469 }
470 
475 - (CPImage)mixedStateImage
476 {
477  return _mixedStateImage;
478 }
479 
480 // Managing Submenus
485 - (void)setSubmenu:(CPMenu)aMenu
486 {
487  if (_submenu === aMenu)
488  return;
489 
490  var supermenu = [_submenu supermenu];
491 
492  if (supermenu)
493  [CPException raise:CPInvalidArgumentException
494  reason: @"Can't add submenu \"" + [aMenu title] + "\" to item \"" + [self title] + "\", because it is already submenu of \"" + [[aMenu supermenu] title] + "\""];
495 
496  _submenu = aMenu;
497 
498  if (_submenu)
499  {
500  [_submenu setSupermenu:_menu];
501  [_submenu setTitle:[self title]]
502 
503  [self setTarget:_menu];
504  [self setAction:@selector(submenuAction:)];
505  }
506  else
507  {
508  [self setTarget:nil];
509  [self setAction:NULL];
510  }
511 
512  [_menuItemView setDirty];
513 
514  [_menu itemChanged:self];
515 }
516 
520 - (CPMenu)submenu
521 {
522  return _submenu;
523 }
524 
528 - (BOOL)hasSubmenu
529 {
530  return _submenu ? YES : NO;
531 }
532 
533 // Getting a Separator Item
534 
538 + (CPMenuItem)separatorItem
539 {
540  var separatorItem = [[self alloc] initWithTitle:@"" action:nil keyEquivalent:nil];
541 
542  separatorItem._isSeparator = YES;
543 
544  return separatorItem;
545 }
546 
550 - (BOOL)isSeparatorItem
551 {
552  return _isSeparator;
553 }
554 
555 // Managing the Owning Menu
560 - (void)setMenu:(CPMenu)aMenu
561 {
562  _menu = aMenu;
563 }
564 
568 - (CPMenu)menu
569 {
570  return _menu;
571 }
572 
573 //
574 
579 - (void)setKeyEquivalent:(CPString)aString
580 {
581  _keyEquivalent = aString || @"";
582 }
583 
587 - (CPString)keyEquivalent
588 {
589  return _keyEquivalent;
590 }
591 
602 - (void)setKeyEquivalentModifierMask:(unsigned)aMask
603 {
604  _keyEquivalentModifierMask = aMask;
605 }
606 
617 - (unsigned)keyEquivalentModifierMask
618 {
619  return _keyEquivalentModifierMask;
620 }
621 
622 - (CPString)keyEquivalentStringRepresentation
623 {
624  if (![_keyEquivalent length])
625  return @"";
626 
627  var string = _keyEquivalent.toUpperCase(),
628  needsShift = _keyEquivalentModifierMask & CPShiftKeyMask ||
629  (string === _keyEquivalent && _keyEquivalent.toLowerCase() !== _keyEquivalent.toUpperCase());
630 
631  if ([CPMenuItemStringRepresentationDictionary objectForKey:string])
632  string = [CPMenuItemStringRepresentationDictionary objectForKey:string];
633 
635  {
636  if (_keyEquivalentModifierMask & CPCommandKeyMask)
637  string = "\u2318" + string;
638 
639  if (needsShift)
640  string = "\u21E7" + string;
641 
642  if (_keyEquivalentModifierMask & CPAlternateKeyMask)
643  string = "\u2325" + string;
644 
645  if (_keyEquivalentModifierMask & CPControlKeyMask)
646  string = "\u2303" + string;
647  }
648  else
649  {
650  if (needsShift)
651  string = "Shift-" + string;
652 
653  if (_keyEquivalentModifierMask & CPAlternateKeyMask)
654  string = "Alt-" + string;
655 
656  if (_keyEquivalentModifierMask & CPControlKeyMask || _keyEquivalentModifierMask & CPCommandKeyMask)
657  string = "Ctrl-" + string;
658  }
659 
660  return string;
661 }
662 
663 // Managing Mnemonics
669 - (void)setMnemonicLocation:(unsigned)aLocation
670 {
671  _mnemonicLocation = aLocation;
672 }
673 
677 - (unsigned)mnemonicLocation
678 {
679  return _mnemonicLocation;
680 }
681 
686 - (void)setTitleWithMnemonicLocation:(CPString)aTitle
687 {
688  var location = [aTitle rangeOfString:@"&"].location;
689 
690  if (location == CPNotFound)
691  [self setTitle:aTitle];
692  else
693  {
694  [self setTitle:[aTitle substringToIndex:location] + [aTitle substringFromIndex:location + 1]];
695  [self setMnemonicLocation:location];
696  }
697 }
698 
702 - (CPString)mnemonic
703 {
704  return _mnemonicLocation == CPNotFound ? @"" : [_title characterAtIndex:_mnemonicLocation];
705 }
706 
707 // Managing Alternates
708 
713 - (void)setAlternate:(BOOL)isAlternate
714 {
715  _isAlternate = isAlternate;
716 }
717 
721 - (BOOL)isAlternate
722 {
723  return _isAlternate;
724 }
725 
726 // Managing Indentation Levels
727 
733 - (void)setIndentationLevel:(unsigned)aLevel
734 {
735  if (aLevel < 0)
736  [CPException raise:CPInvalidArgumentException reason:"setIndentationLevel: argument must be greater than or equal to 0."];
737 
738  _indentationLevel = MIN(15, aLevel);
739 }
740 
744 - (unsigned)indentationLevel
745 {
746  return _indentationLevel;
747 }
748 
749 // Managing Tool Tips
754 - (void)setToolTip:(CPString)aToolTip
755 {
756  _toolTip = aToolTip;
757 }
758 
762 - (CPString)toolTip
763 {
764  return _toolTip;
765 }
766 
767 // Representing an Object
768 
773 - (void)setRepresentedObject:(id)anObject
774 {
775  _representedObject = anObject;
776 }
777 
781 - (id)representedObject
782 {
783  return _representedObject;
784 }
785 
786 // Managing the View
787 
792 - (void)setView:(CPView)aView
793 {
794  if (_view === aView)
795  return;
796 
797  _view = aView;
798 
799  [_menuItemView setDirty];
800 
801  [_menu itemChanged:self];
802 }
803 
807 - (CPView)view
808 {
809  return _view;
810 }
811 
812 // Getting Highlighted Status
813 
817 - (BOOL)isHighlighted
818 {
819  return [[self menu] highlightedItem] == self;
820 }
821 
822 #pragma mark CPObject Overrides
823 
827 - (id)copy
828 {
829  var item = [[CPMenuItem alloc] init];
830 
831  // No point in going through accessors and doing lots of unnecessary state checking/updating
832  item._isSeparator = _isSeparator;
833 
834  [item setTitle:_title];
835  [item setFont:_font];
836  [item setTarget:_target];
837  [item setAction:_action];
838  [item setEnabled:_isEnabled];
839  [item setHidden:_isHidden]
840  [item setTag:_tag];
841  [item setState:_state];
842  [item setImage:_image];
843  [item setAlternateImage:_alternateImage];
844  [item setOnStateImage:_onStateImage];
845  [item setOffStateImage:_offStateImage];
846  [item setMixedStateImage:_mixedStateImage];
847  [item setKeyEquivalent:_keyEquivalent];
848  [item setKeyEquivalentModifierMask:_keyEquivalentModifierMask];
849  [item setMnemonicLocation:_mnemonicLocation];
850  [item setAlternate:_isAlternate];
851  [item setIndentationLevel:_indentationLevel];
852  [item setToolTip:_toolTip];
853  [item setRepresentedObject:_representedObject];
854 
855  return item;
856 }
857 
858 - (id)mutableCopy
859 {
860  return [self copy];
861 }
862 
863 #pragma mark Internal
864 
865 /*
866  @ignore
867 */
868 - (id)_menuItemView
869 {
870  if (!_menuItemView)
871  _menuItemView = [[_CPMenuItemView alloc] initWithFrame:CGRectMakeZero() forMenuItem:self];
872 
873  return _menuItemView;
874 }
875 
876 - (BOOL)_isSelectable
877 {
878  return ![self submenu] || [self action] !== @selector(submenuAction:) || [self target] !== [self menu];
879 }
880 
881 - (BOOL)_isMenuBarButton
882 {
883  return ![self submenu] && [self menu] === [CPApp mainMenu];
884 }
885 
886 - (CPString)description
887 {
888  return [super description] + @" target: " + [self target] + @" action: " + CPStringFromSelector([self action]);
889 }
890 
891 @end
892 
893 var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
894 
895  CPMenuItemTitleKey = @"CPMenuItemTitleKey",
896  CPMenuItemTargetKey = @"CPMenuItemTargetKey",
897  CPMenuItemActionKey = @"CPMenuItemActionKey",
898 
899  CPMenuItemIsEnabledKey = @"CPMenuItemIsEnabledKey",
900  CPMenuItemIsHiddenKey = @"CPMenuItemIsHiddenKey",
901 
902  CPMenuItemTagKey = @"CPMenuItemTagKey",
903  CPMenuItemStateKey = @"CPMenuItemStateKey",
904 
905  CPMenuItemImageKey = @"CPMenuItemImageKey",
906  CPMenuItemAlternateImageKey = @"CPMenuItemAlternateImageKey",
907 
908  CPMenuItemSubmenuKey = @"CPMenuItemSubmenuKey",
909  CPMenuItemMenuKey = @"CPMenuItemMenuKey",
910 
911  CPMenuItemKeyEquivalentKey = @"CPMenuItemKeyEquivalentKey",
912  CPMenuItemKeyEquivalentModifierMaskKey = @"CPMenuItemKeyEquivalentModifierMaskKey",
913 
914  CPMenuItemIndentationLevelKey = @"CPMenuItemIndentationLevelKey",
915 
916  CPMenuItemRepresentedObjectKey = @"CPMenuItemRepresentedObjectKey",
917  CPMenuItemViewKey = @"CPMenuItemViewKey";
918 
919 #define DEFAULT_VALUE(aKey, aDefaultValue) [aCoder containsValueForKey:(aKey)] ? [aCoder decodeObjectForKey:(aKey)] : (aDefaultValue)
920 #define ENCODE_IFNOT(aKey, aValue, aDefaultValue) if ((aValue) !== (aDefaultValue)) [aCoder encodeObject:(aValue) forKey:(aKey)];
921 
922 @implementation CPMenuItem (CPCoding)
928 - (id)initWithCoder:(CPCoder)aCoder
929 {
930  self = [super init];
931 
932  if (self)
933  {
934  _changeCount = 0;
935  _isSeparator = [aCoder containsValueForKey:CPMenuItemIsSeparatorKey] && [aCoder decodeBoolForKey:CPMenuItemIsSeparatorKey];
936 
937  _title = [aCoder decodeObjectForKey:CPMenuItemTitleKey];
938 
939 // _font;
940 
941  _target = [aCoder decodeObjectForKey:CPMenuItemTargetKey];
942  _action = [aCoder decodeObjectForKey:CPMenuItemActionKey];
943 
944  _isEnabled = DEFAULT_VALUE(CPMenuItemIsEnabledKey, YES);
945  _isHidden = [aCoder decodeBoolForKey:CPMenuItemIsHiddenKey];
946  _tag = [aCoder decodeIntForKey:CPMenuItemTagKey];
947  _state = [aCoder decodeIntForKey:CPMenuItemStateKey];
948 
949  _image = [aCoder decodeObjectForKey:CPMenuItemImageKey];
950  _alternateImage = [aCoder decodeObjectForKey:CPMenuItemAlternateImageKey];
951 // CPImage _onStateImage;
952 // CPImage _offStateImage;
953 // CPImage _mixedStateImage;
954 
955  // This order matters because setSubmenu: needs _menu to be around.
956  _menu = [aCoder decodeObjectForKey:CPMenuItemMenuKey];
957  [self setSubmenu:[aCoder decodeObjectForKey:CPMenuItemSubmenuKey]];
958 
959  _keyEquivalent = [aCoder decodeObjectForKey:CPMenuItemKeyEquivalentKey] || @"";
960  _keyEquivalentModifierMask = [aCoder decodeIntForKey:CPMenuItemKeyEquivalentModifierMaskKey];
961 
962 // int _mnemonicLocation;
963 
964 // BOOL _isAlternate;
965 
966  [self setIndentationLevel:[aCoder decodeIntForKey:CPMenuItemIndentationLevelKey]];
967 
968 // CPString _toolTip;
969 
970  _representedObject = [aCoder decodeObjectForKey:CPMenuItemRepresentedObjectKey];
971  _view = [aCoder decodeObjectForKey:CPMenuItemViewKey];
972  }
973 
974  return self;
975 }
976 
981 - (void)encodeWithCoder:(CPCoder)aCoder
982 {
983  if (_isSeparator)
984  [aCoder encodeBool:_isSeparator forKey:CPMenuItemIsSeparatorKey];
985 
986  [aCoder encodeObject:_title forKey:CPMenuItemTitleKey];
987 
988  [aCoder encodeObject:_target forKey:CPMenuItemTargetKey];
989  [aCoder encodeObject:_action forKey:CPMenuItemActionKey];
990 
991  ENCODE_IFNOT(CPMenuItemIsEnabledKey, _isEnabled, YES);
992  ENCODE_IFNOT(CPMenuItemIsHiddenKey, _isHidden, NO);
993 
994  ENCODE_IFNOT(CPMenuItemTagKey, _tag, 0);
995  ENCODE_IFNOT(CPMenuItemStateKey, _state, CPOffState);
996 
997  ENCODE_IFNOT(CPMenuItemImageKey, _image, nil);
998  ENCODE_IFNOT(CPMenuItemAlternateImageKey, _alternateImage, nil);
999 
1000  ENCODE_IFNOT(CPMenuItemSubmenuKey, _submenu, nil);
1001  ENCODE_IFNOT(CPMenuItemMenuKey, _menu, nil);
1002 
1003  if (_keyEquivalent && _keyEquivalent.length)
1004  [aCoder encodeObject:_keyEquivalent forKey:CPMenuItemKeyEquivalentKey];
1005 
1006  if (_keyEquivalentModifierMask)
1007  [aCoder encodeObject:_keyEquivalentModifierMask forKey:CPMenuItemKeyEquivalentModifierMaskKey];
1008 
1009  if (_indentationLevel > 0)
1010  [aCoder encodeInt:_indentationLevel forKey:CPMenuItemIndentationLevelKey];
1011 
1012  ENCODE_IFNOT(CPMenuItemRepresentedObjectKey, _representedObject, nil);
1013  ENCODE_IFNOT(CPMenuItemViewKey, _view, nil);
1014 }
1015 
1016 @end