API  0.9.6
 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 
44 
53 @implementation CPMenuItem : CPObject
54 {
55  BOOL _isSeparator;
56 
57  CPString _title;
58  //CPAttributedString _attributedTitle;
59 
60  CPFont _font;
61 
62  id _target;
63  SEL _action;
64 
65  BOOL _isEnabled;
66  BOOL _isHidden;
67 
68  int _tag;
69  int _state;
70 
71  CPImage _image;
72  CPImage _alternateImage;
73  CPImage _onStateImage;
74  CPImage _offStateImage;
75  CPImage _mixedStateImage;
76 
77  CPMenu _submenu;
78  CPMenu _menu;
79 
80  CPString _keyEquivalent;
81  unsigned _keyEquivalentModifierMask;
82 
83  int _mnemonicLocation;
84 
85  BOOL _isAlternate;
86  int _indentationLevel;
87 
88  CPString _toolTip;
89  id _representedObject;
90  CPView _view;
91 
92  int _changeCount;
93 
94  _CPMenuItemView _menuItemView;
95 }
96 
97 - (id)init
98 {
99  return [self initWithTitle:@"" action:nil keyEquivalent:nil];
100 }
101 
109 - (id)initWithTitle:(CPString)aTitle action:(SEL)anAction keyEquivalent:(CPString)aKeyEquivalent
110 {
111  self = [super init];
112 
113  if (self)
114  {
115  _changeCount = 0;
116  _isSeparator = NO;
117 
118  _title = aTitle;
119  _action = anAction;
120 
121  _isEnabled = YES;
122  _isHidden = NO;
123 
124  _tag = 0;
125  _state = CPOffState;
126 
127  _keyEquivalent = aKeyEquivalent || @"";
128  _keyEquivalentModifierMask = CPPlatformActionKeyMask;
129 
130  _indentationLevel = 0;
131 
132  _mnemonicLocation = CPNotFound;
133  }
134 
135  return self;
136 }
137 
138 // Enabling a Menu Item
143 - (void)setEnabled:(BOOL)isEnabled
144 {
145  if (_isEnabled === isEnabled)
146  return;
147 
148  _isEnabled = !!isEnabled;
149 
150  [_menuItemView setDirty];
151  [_menu itemChanged:self];
152 }
153 
157 - (BOOL)isEnabled
158 {
159  return _isEnabled;
160 }
161 
162 // Managing Hidden Status
167 - (void)setHidden:(BOOL)isHidden
168 {
169  if (_isHidden == isHidden)
170  return;
171 
172  _isHidden = isHidden;
173 
174  [_menu itemChanged:self];
175 }
176 
180 - (BOOL)isHidden
181 {
182  return _isHidden;
183 }
184 
188 - (BOOL)isHiddenOrHasHiddenAncestor
189 {
190  if (_isHidden)
191  return YES;
192 
193  var supermenu = [_menu supermenu];
194 
195  if ([[supermenu itemAtIndex:[supermenu indexOfItemWithSubmenu:_menu]] isHiddenOrHasHiddenAncestor])
196  return YES;
197 
198  return NO;
199 }
200 
201 // Managing Target and Action
206 - (void)setTarget:(id)aTarget
207 {
208  _target = aTarget;
209 }
210 
214 - (id)target
215 {
216  return _target;
217 }
218 
223 - (void)setAction:(SEL)anAction
224 {
225  _action = anAction;
226 }
227 
231 - (SEL)action
232 {
233  return _action;
234 }
235 
236 // Managing the Title
241 - (void)setTitle:(CPString)aTitle
242 {
243  _mnemonicLocation = CPNotFound;
244 
245  if (_title == aTitle)
246  return;
247 
248  _title = aTitle;
249 
250  [_menuItemView setDirty];
251 
252  [_menu itemChanged:self];
253 }
254 
258 - (CPString)title
259 {
260  return _title;
261 }
262 
266 - (void)setTextColor:(CPString)aColor
267 {
268  //FIXME IMPLEMENT
269 }
270 
275 - (void)setFont:(CPFont)aFont
276 {
277  if ([_font isEqual:aFont])
278  return;
279 
280  _font = aFont;
281 
282  [_menu itemChanged:self];
283 
284  [_menuItemView setDirty];
285 }
286 
290 - (CPFont)font
291 {
292  return _font;
293 }
294 
295 /*
296 - (void)setAttributedTitle:(CPAttributedString)aTitle
297 {
298 }
299 
300 - (CPAttributedString)attributedTitle
301 {
302 }
303 */
304 
305 // Managing the Tag
310 - (void)setTag:(int)aTag
311 {
312  _tag = aTag;
313 }
314 
318 - (int)tag
319 {
320  return _tag;
321 }
322 
331 - (void)setState:(int)aState
332 {
333  if (_state == aState)
334  return;
335 
336  _state = aState;
337 
338  [_menu itemChanged:self];
339 
340  [_menuItemView setDirty];
341 }
342 
351 - (int)state
352 {
353  return _state;
354 }
355 
356 // Managing the Image
361 - (void)setImage:(CPImage)anImage
362 {
363  if (_image == anImage)
364  return;
365 
366  _image = anImage;
367 
368  [_menuItemView setDirty];
369 
370  [_menu itemChanged:self];
371 }
372 
376 - (CPImage)image
377 {
378  return _image;
379 }
380 
385 - (void)setAlternateImage:(CPImage)anImage
386 {
387  _alternateImage = anImage;
388 }
389 
393 - (CPImage)alternateImage
394 {
395  return _alternateImage;
396 }
397 
403 - (void)setOnStateImage:(CPImage)anImage
404 {
405  if (_onStateImage == anImage)
406  return;
407 
408  _onStateImage = anImage;
409  [_menu itemChanged:self];
410 }
411 
415 - (CPImage)onStateImage
416 {
417  return _onStateImage;
418 }
419 
424 - (void)setOffStateImage:(CPImage)anImage
425 {
426  if (_offStateImage == anImage)
427  return;
428 
429  _offStateImage = anImage;
430  [_menu itemChanged:self];
431 }
432 
436 - (CPImage)offStateImage
437 {
438  return _offStateImage;
439 }
440 
445 - (void)setMixedStateImage:(CPImage)anImage
446 {
447  if (_mixedStateImage == anImage)
448  return;
449 
450  _mixedStateImage = anImage;
451  [_menu itemChanged:self];
452 }
453 
458 - (CPImage)mixedStateImage
459 {
460  return _mixedStateImage;
461 }
462 
463 // Managing Submenus
468 - (void)setSubmenu:(CPMenu)aMenu
469 {
470  if (_submenu === aMenu)
471  return;
472 
473  var supermenu = [_submenu supermenu];
474 
475  if (supermenu)
476  [CPException raise:CPInvalidArgumentException
477  reason: @"Can't add submenu \"" + [aMenu title] + "\" to item \"" + [self title] + "\", because it is already submenu of \"" + [[aMenu supermenu] title] + "\""];
478 
479  _submenu = aMenu;
480 
481  if (_submenu)
482  {
483  [_submenu setSupermenu:_menu];
484  [_submenu setTitle:[self title]]
485 
486  [self setTarget:_menu];
487  [self setAction:@selector(submenuAction:)];
488  }
489  else
490  {
491  [self setTarget:nil];
492  [self setAction:NULL];
493  }
494 
495  [_menuItemView setDirty];
496 
497  [_menu itemChanged:self];
498 }
499 
503 - (CPMenu)submenu
504 {
505  return _submenu;
506 }
507 
511 - (BOOL)hasSubmenu
512 {
513  return _submenu ? YES : NO;
514 }
515 
516 // Getting a Separator Item
517 
521 + (CPMenuItem)separatorItem
522 {
523  var separatorItem = [[self alloc] initWithTitle:@"" action:nil keyEquivalent:nil];
524 
525  separatorItem._isSeparator = YES;
526 
527  return separatorItem;
528 }
529 
533 - (BOOL)isSeparatorItem
534 {
535  return _isSeparator;
536 }
537 
538 // Managing the Owning Menu
543 - (void)setMenu:(CPMenu)aMenu
544 {
545  _menu = aMenu;
546 }
547 
552 {
553  return _menu;
554 }
555 
556 //
557 
562 - (void)setKeyEquivalent:(CPString)aString
563 {
564  _keyEquivalent = aString || @"";
565 }
566 
570 - (CPString)keyEquivalent
571 {
572  return _keyEquivalent;
573 }
574 
585 - (void)setKeyEquivalentModifierMask:(unsigned)aMask
586 {
587  _keyEquivalentModifierMask = aMask;
588 }
589 
600 - (unsigned)keyEquivalentModifierMask
601 {
602  return _keyEquivalentModifierMask;
603 }
604 
605 - (CPString)keyEquivalentStringRepresentation
606 {
607  if (![_keyEquivalent length])
608  return @"";
609 
610  var string = _keyEquivalent.toUpperCase(),
611  needsShift = _keyEquivalentModifierMask & CPShiftKeyMask ||
612  (string === _keyEquivalent && _keyEquivalent.toLowerCase() !== _keyEquivalent.toUpperCase());
613 
614  if ([CPMenuItemStringRepresentationDictionary objectForKey:string])
615  string = [CPMenuItemStringRepresentationDictionary objectForKey:string];
616 
618  {
619  if (_keyEquivalentModifierMask & CPCommandKeyMask)
620  string = "\u2318" + string;
621 
622  if (needsShift)
623  string = "\u21E7" + string;
624 
625  if (_keyEquivalentModifierMask & CPAlternateKeyMask)
626  string = "\u2325" + string;
627 
628  if (_keyEquivalentModifierMask & CPControlKeyMask)
629  string = "\u2303" + string;
630  }
631  else
632  {
633  if (needsShift)
634  string = "Shift-" + string;
635 
636  if (_keyEquivalentModifierMask & CPAlternateKeyMask)
637  string = "Alt-" + string;
638 
639  if (_keyEquivalentModifierMask & CPControlKeyMask || _keyEquivalentModifierMask & CPCommandKeyMask)
640  string = "Ctrl-" + string;
641  }
642 
643  return string;
644 }
645 
646 // Managing Mnemonics
652 - (void)setMnemonicLocation:(unsigned)aLocation
653 {
654  _mnemonicLocation = aLocation;
655 }
656 
660 - (unsigned)mnemonicLocation
661 {
662  return _mnemonicLocation;
663 }
664 
669 - (void)setTitleWithMnemonicLocation:(CPString)aTitle
670 {
671  var location = [aTitle rangeOfString:@"&"].location;
672 
673  if (location == CPNotFound)
674  [self setTitle:aTitle];
675  else
676  {
677  [self setTitle:[aTitle substringToIndex:location] + [aTitle substringFromIndex:location + 1]];
678  [self setMnemonicLocation:location];
679  }
680 }
681 
685 - (CPString)mnemonic
686 {
687  return _mnemonicLocation == CPNotFound ? @"" : [_title characterAtIndex:_mnemonicLocation];
688 }
689 
690 // Managing Alternates
691 
696 - (void)setAlternate:(BOOL)isAlternate
697 {
698  _isAlternate = isAlternate;
699 }
700 
704 - (BOOL)isAlternate
705 {
706  return _isAlternate;
707 }
708 
709 // Managing Indentation Levels
710 
716 - (void)setIndentationLevel:(unsigned)aLevel
717 {
718  if (aLevel < 0)
719  [CPException raise:CPInvalidArgumentException reason:"setIndentationLevel: argument must be greater than or equal to 0."];
720 
721  _indentationLevel = MIN(15, aLevel);
722 }
723 
727 - (unsigned)indentationLevel
728 {
729  return _indentationLevel;
730 }
731 
732 // Managing Tool Tips
737 - (void)setToolTip:(CPString)aToolTip
738 {
739  _toolTip = aToolTip;
740 }
741 
745 - (CPString)toolTip
746 {
747  return _toolTip;
748 }
749 
750 // Representing an Object
751 
756 - (void)setRepresentedObject:(id)anObject
757 {
758  _representedObject = anObject;
759 }
760 
764 - (id)representedObject
765 {
766  return _representedObject;
767 }
768 
769 // Managing the View
770 
775 - (void)setView:(CPView)aView
776 {
777  if (_view === aView)
778  return;
779 
780  _view = aView;
781 
782  [_menuItemView setDirty];
783 
784  [_menu itemChanged:self];
785 }
786 
790 - (CPView)view
791 {
792  return _view;
793 }
794 
795 // Getting Highlighted Status
796 
800 - (BOOL)isHighlighted
801 {
802  return [[self menu] highlightedItem] == self;
803 }
804 
805 #pragma mark CPObject Overrides
806 
810 - (id)copy
811 {
812  var item = [[CPMenuItem alloc] init];
813 
814  // No point in going through accessors and doing lots of unnecessary state checking/updating
815  item._isSeparator = _isSeparator;
816 
817  [item setTitle:_title];
818  [item setFont:_font];
819  [item setTarget:_target];
820  [item setAction:_action];
821  [item setEnabled:_isEnabled];
822  [item setHidden:_isHidden]
823  [item setTag:_tag];
824  [item setState:_state];
825  [item setImage:_image];
826  [item setAlternateImage:_alternateImage];
827  [item setOnStateImage:_onStateImage];
828  [item setOffStateImage:_offStateImage];
829  [item setMixedStateImage:_mixedStateImage];
830  [item setKeyEquivalent:_keyEquivalent];
831  [item setKeyEquivalentModifierMask:_keyEquivalentModifierMask];
832  [item setMnemonicLocation:_mnemonicLocation];
833  [item setAlternate:_isAlternate];
834  [item setIndentationLevel:_indentationLevel];
835  [item setToolTip:_toolTip];
836  [item setRepresentedObject:_representedObject];
837 
838  return item;
839 }
840 
841 - (id)mutableCopy
842 {
843  return [self copy];
844 }
845 
846 #pragma mark Internal
847 
848 /*
849  @ignore
850 */
851 - (id)_menuItemView
852 {
853  if (!_menuItemView)
854  _menuItemView = [[_CPMenuItemView alloc] initWithFrame:CGRectMakeZero() forMenuItem:self];
855 
856  return _menuItemView;
857 }
858 
859 - (BOOL)_isSelectable
860 {
861  return ![self submenu] || [self action] !== @selector(submenuAction:) || [self target] !== [self menu];
862 }
863 
864 - (BOOL)_isMenuBarButton
865 {
866  return ![self submenu] && [self menu] === [CPApp mainMenu];
867 }
868 
869 - (CPString)description
870 {
871  return [super description] + @" target: " + [self target] + @" action: " + CPStringFromSelector([self action]);
872 }
873 
874 @end
875 
876 var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
877 
878  CPMenuItemTitleKey = @"CPMenuItemTitleKey",
879  CPMenuItemTargetKey = @"CPMenuItemTargetKey",
880  CPMenuItemActionKey = @"CPMenuItemActionKey",
881 
882  CPMenuItemIsEnabledKey = @"CPMenuItemIsEnabledKey",
883  CPMenuItemIsHiddenKey = @"CPMenuItemIsHiddenKey",
884 
885  CPMenuItemTagKey = @"CPMenuItemTagKey",
886  CPMenuItemStateKey = @"CPMenuItemStateKey",
887 
888  CPMenuItemImageKey = @"CPMenuItemImageKey",
889  CPMenuItemAlternateImageKey = @"CPMenuItemAlternateImageKey",
890 
891  CPMenuItemSubmenuKey = @"CPMenuItemSubmenuKey",
892  CPMenuItemMenuKey = @"CPMenuItemMenuKey",
893 
894  CPMenuItemKeyEquivalentKey = @"CPMenuItemKeyEquivalentKey",
895  CPMenuItemKeyEquivalentModifierMaskKey = @"CPMenuItemKeyEquivalentModifierMaskKey",
896 
897  CPMenuItemIndentationLevelKey = @"CPMenuItemIndentationLevelKey",
898 
899  CPMenuItemRepresentedObjectKey = @"CPMenuItemRepresentedObjectKey",
900  CPMenuItemViewKey = @"CPMenuItemViewKey";
901 
902 #define DEFAULT_VALUE(aKey, aDefaultValue) [aCoder containsValueForKey:(aKey)] ? [aCoder decodeObjectForKey:(aKey)] : (aDefaultValue)
903 #define ENCODE_IFNOT(aKey, aValue, aDefaultValue) if ((aValue) !== (aDefaultValue)) [aCoder encodeObject:(aValue) forKey:(aKey)];
904 
905 @implementation CPMenuItem (CPCoding)
911 - (id)initWithCoder:(CPCoder)aCoder
912 {
913  self = [super init];
914 
915  if (self)
916  {
917  _changeCount = 0;
918  _isSeparator = [aCoder containsValueForKey:CPMenuItemIsSeparatorKey] && [aCoder decodeBoolForKey:CPMenuItemIsSeparatorKey];
919 
920  _title = [aCoder decodeObjectForKey:CPMenuItemTitleKey];
921 
922 // _font;
923 
924  _target = [aCoder decodeObjectForKey:CPMenuItemTargetKey];
925  _action = [aCoder decodeObjectForKey:CPMenuItemActionKey];
926 
927  _isEnabled = DEFAULT_VALUE(CPMenuItemIsEnabledKey, YES);
928  _isHidden = [aCoder decodeBoolForKey:CPMenuItemIsHiddenKey];
929  _tag = [aCoder decodeIntForKey:CPMenuItemTagKey];
930  _state = [aCoder decodeIntForKey:CPMenuItemStateKey];
931 
932  _image = [aCoder decodeObjectForKey:CPMenuItemImageKey];
933  _alternateImage = [aCoder decodeObjectForKey:CPMenuItemAlternateImageKey];
934 // CPImage _onStateImage;
935 // CPImage _offStateImage;
936 // CPImage _mixedStateImage;
937 
938  // This order matters because setSubmenu: needs _menu to be around.
939  _menu = [aCoder decodeObjectForKey:CPMenuItemMenuKey];
940  [self setSubmenu:[aCoder decodeObjectForKey:CPMenuItemSubmenuKey]];
941 
942  _keyEquivalent = [aCoder decodeObjectForKey:CPMenuItemKeyEquivalentKey] || @"";
943  _keyEquivalentModifierMask = [aCoder decodeIntForKey:CPMenuItemKeyEquivalentModifierMaskKey];
944 
945 // int _mnemonicLocation;
946 
947 // BOOL _isAlternate;
948 
949  [self setIndentationLevel:[aCoder decodeIntForKey:CPMenuItemIndentationLevelKey]];
950 
951 // CPString _toolTip;
952 
953  _representedObject = [aCoder decodeObjectForKey:CPMenuItemRepresentedObjectKey];
954  _view = [aCoder decodeObjectForKey:CPMenuItemViewKey];
955  }
956 
957  return self;
958 }
959 
964 - (void)encodeWithCoder:(CPCoder)aCoder
965 {
966  if (_isSeparator)
967  [aCoder encodeBool:_isSeparator forKey:CPMenuItemIsSeparatorKey];
968 
969  [aCoder encodeObject:_title forKey:CPMenuItemTitleKey];
970 
971  [aCoder encodeObject:_target forKey:CPMenuItemTargetKey];
972  [aCoder encodeObject:_action forKey:CPMenuItemActionKey];
973 
974  ENCODE_IFNOT(CPMenuItemIsEnabledKey, _isEnabled, YES);
975  ENCODE_IFNOT(CPMenuItemIsHiddenKey, _isHidden, NO);
976 
977  ENCODE_IFNOT(CPMenuItemTagKey, _tag, 0);
979 
980  ENCODE_IFNOT(CPMenuItemImageKey, _image, nil);
981  ENCODE_IFNOT(CPMenuItemAlternateImageKey, _alternateImage, nil);
982 
983  ENCODE_IFNOT(CPMenuItemSubmenuKey, _submenu, nil);
984  ENCODE_IFNOT(CPMenuItemMenuKey, _menu, nil);
985 
986  if (_keyEquivalent && _keyEquivalent.length)
987  [aCoder encodeObject:_keyEquivalent forKey:CPMenuItemKeyEquivalentKey];
988 
989  if (_keyEquivalentModifierMask)
990  [aCoder encodeObject:_keyEquivalentModifierMask forKey:CPMenuItemKeyEquivalentModifierMaskKey];
991 
992  if (_indentationLevel > 0)
993  [aCoder encodeInt:_indentationLevel forKey:CPMenuItemIndentationLevelKey];
994 
995  ENCODE_IFNOT(CPMenuItemRepresentedObjectKey, _representedObject, nil);
996  ENCODE_IFNOT(CPMenuItemViewKey, _view, nil);
997 }
998 
999 @end