API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
CPMenu.j
Go to the documentation of this file.
1 /*
2  * CPMenu.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 
26 CPMenuDidAddItemNotification = @"CPMenuDidAddItemNotification";
27 CPMenuDidChangeItemNotification = @"CPMenuDidChangeItemNotification";
28 CPMenuDidRemoveItemNotification = @"CPMenuDidRemoveItemNotification";
29 
30 CPMenuDidEndTrackingNotification = @"CPMenuDidEndTrackingNotification";
31 
32 var MENUBAR_HEIGHT = 28.0;
33 
34 var _CPMenuBarVisible = NO,
35  _CPMenuBarTitle = @"",
36  _CPMenuBarIconImage = nil,
37  _CPMenuBarIconImageAlphaValue = 1.0,
38  _CPMenuBarAttributes = nil,
39  _CPMenuBarSharedWindow = nil;
40 
48 @implementation CPMenu : CPObject
49 {
50  CPMenu _supermenu;
51 
52  CPString _title;
53  CPString _name;
54 
55  CPFont _font;
56 
57  float _minimumWidth;
58 
59  CPMutableArray _items;
60 
61  BOOL _autoenablesItems;
62  BOOL _showsStateColumn;
63 
64  id _delegate;
65 
66  int _highlightedIndex;
67  _CPMenuWindow _menuWindow;
68 }
69 
70 // Managing the Menu Bar
71 
72 + (void)initialize
73 {
74  if (self !== [CPMenu class])
75  return;
76 
77  [[self class] setMenuBarAttributes:[CPDictionary dictionary]];
78 }
79 
80 + (BOOL)menuBarVisible
81 {
82  return _CPMenuBarVisible;
83 }
84 
85 + (void)setMenuBarVisible:(BOOL)menuBarShouldBeVisible
86 {
87  if (_CPMenuBarVisible === menuBarShouldBeVisible)
88  return;
89 
90  _CPMenuBarVisible = menuBarShouldBeVisible;
91 
92  if ([CPPlatform supportsNativeMainMenu])
93  return;
94 
95  if (menuBarShouldBeVisible)
96  {
97  if (!_CPMenuBarSharedWindow)
98  _CPMenuBarSharedWindow = [[_CPMenuBarWindow alloc] init];
99 
100  [_CPMenuBarSharedWindow setMenu:[CPApp mainMenu]];
101 
102  [_CPMenuBarSharedWindow setTitle:_CPMenuBarTitle];
103  [_CPMenuBarSharedWindow setIconImage:_CPMenuBarIconImage];
104  [_CPMenuBarSharedWindow setIconImageAlphaValue:_CPMenuBarIconImageAlphaValue];
105 
106  [_CPMenuBarSharedWindow setColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarBackgroundColor"]];
107  [_CPMenuBarSharedWindow setTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextColor"]];
108  [_CPMenuBarSharedWindow setTitleColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTitleColor"]];
109  [_CPMenuBarSharedWindow setTextShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextShadowColor"]];
110  [_CPMenuBarSharedWindow setTitleShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTitleShadowColor"]];
111  [_CPMenuBarSharedWindow setHighlightColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightColor"]];
112  [_CPMenuBarSharedWindow setHighlightTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightTextColor"]];
113  [_CPMenuBarSharedWindow setHighlightTextShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightTextShadowColor"]];
114 
115  [_CPMenuBarSharedWindow orderFront:self];
116  }
117  else
118  [_CPMenuBarSharedWindow orderOut:self];
119 
120 // FIXME: There must be a better way to do this.
121 #if PLATFORM(DOM)
122  [[CPPlatformWindow primaryPlatformWindow] resizeEvent:nil];
123 #endif
124 }
125 
126 + (void)setMenuBarTitle:(CPString)aTitle
127 {
128  _CPMenuBarTitle = aTitle;
129  [_CPMenuBarSharedWindow setTitle:_CPMenuBarTitle];
130 }
131 
132 + (CPString)menuBarTitle
133 {
134  return _CPMenuBarTitle;
135 }
136 
137 + (void)setMenuBarIconImage:(CPImage)anImage
138 {
139  _CPMenuBarImage = anImage;
140  [_CPMenuBarSharedWindow setIconImage:anImage];
141 }
142 
143 + (CPImage)menuBarIconImage
144 {
145  return _CPMenuBarImage;
146 }
147 
148 
149 + (void)setMenuBarAttributes:(CPDictionary)attributes
150 {
151  if (_CPMenuBarAttributes == attributes)
152  return;
153 
154  _CPMenuBarAttributes = [attributes copy];
155 
156  var textColor = [attributes objectForKey:@"CPMenuBarTextColor"],
157  titleColor = [attributes objectForKey:@"CPMenuBarTitleColor"],
158  textShadowColor = [attributes objectForKey:@"CPMenuBarTextShadowColor"],
159  titleShadowColor = [attributes objectForKey:@"CPMenuBarTitleShadowColor"],
160  highlightColor = [attributes objectForKey:@"CPMenuBarHighlightColor"],
161  highlightTextColor = [attributes objectForKey:@"CPMenuBarHighlightTextColor"],
162  highlightTextShadowColor = [attributes objectForKey:@"CPMenuBarHighlightTextShadowColor"];
163 
164  if (!textColor && titleColor)
165  [_CPMenuBarAttributes setObject:titleColor forKey:@"CPMenuBarTextColor"];
166 
167  else if (textColor && !titleColor)
168  [_CPMenuBarAttributes setObject:textColor forKey:@"CPMenuBarTitleColor"];
169 
170  else if (!textColor && !titleColor)
171  {
172  [_CPMenuBarAttributes setObject:[CPColor colorWithRed:0.051 green:0.2 blue:0.275 alpha:1.0] forKey:@"CPMenuBarTextColor"];
173  [_CPMenuBarAttributes setObject:[CPColor colorWithRed:0.051 green:0.2 blue:0.275 alpha:1.0] forKey:@"CPMenuBarTitleColor"];
174  }
175 
176  if (!textShadowColor && titleShadowColor)
177  [_CPMenuBarAttributes setObject:titleShadowColor forKey:@"CPMenuBarTextShadowColor"];
178 
179  else if (textShadowColor && !titleShadowColor)
180  [_CPMenuBarAttributes setObject:textShadowColor forKey:@"CPMenuBarTitleShadowColor"];
181 
182  else if (!textShadowColor && !titleShadowColor)
183  {
184  [_CPMenuBarAttributes setObject:[CPColor whiteColor] forKey:@"CPMenuBarTextShadowColor"];
185  [_CPMenuBarAttributes setObject:[CPColor whiteColor] forKey:@"CPMenuBarTitleShadowColor"];
186  }
187 
188  if (!highlightColor)
189  [_CPMenuBarAttributes setObject:[CPColor colorWithCalibratedRed:94.0 / 255.0 green:130.0 / 255.0 blue:186.0 / 255.0 alpha:1.0] forKey:@"CPMenuBarHighlightColor"];
190 
191  if (!highlightTextColor)
192  [_CPMenuBarAttributes setObject:[CPColor whiteColor] forKey:@"CPMenuBarHighlightTextColor"];
193 
194  if (!highlightTextShadowColor)
195  [_CPMenuBarAttributes setObject:[CPColor blackColor] forKey:@"CPMenuBarHighlightTextShadowColor"];
196 
197  if (_CPMenuBarSharedWindow)
198  {
199  [_CPMenuBarSharedWindow setColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarBackgroundColor"]];
200  [_CPMenuBarSharedWindow setTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextColor"]];
201  [_CPMenuBarSharedWindow setTitleColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTitleColor"]];
202  [_CPMenuBarSharedWindow setTextShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextShadowColor"]];
203  [_CPMenuBarSharedWindow setTitleShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTitleShadowColor"]];
204  [_CPMenuBarSharedWindow setHighlightColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightColor"]];
205  [_CPMenuBarSharedWindow setHighlightTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightTextColor"]];
206  [_CPMenuBarSharedWindow setHighlightTextShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightTextShadowColor"]];
207  }
208 }
209 
210 + (CPDictionary)menuBarAttributes
211 {
212  return _CPMenuBarAttributes;
213 }
214 
215 + (void)_setMenuBarIconImageAlphaValue:(float)anAlphaValue
216 {
217  _CPMenuBarIconImageAlphaValue = anAlphaValue;
218  [_CPMenuBarSharedWindow setIconImageAlphaValue:anAlphaValue];
219 }
220 
221 - (float)menuBarHeight
222 {
223  if (self === [CPApp mainMenu])
224  return MENUBAR_HEIGHT;
225 
226  return 0.0;
227 }
228 
229 + (float)menuBarHeight
230 {
231  return MENUBAR_HEIGHT;
232 }
233 
234 // Creating a CPMenu Object
240 - (id)initWithTitle:(CPString)aTitle
241 {
242  self = [super init];
243 
244  if (self)
245  {
246  _title = aTitle;
247  _items = [];
248 
249  _autoenablesItems = YES;
250  _showsStateColumn = YES;
251 
252  [self setMinimumWidth:0];
253  }
254 
255  return self;
256 }
257 
258 - (id)init
259 {
260  return [self initWithTitle:@""];
261 }
262 
263 // Setting Up Menu Commands
269 - (void)insertItem:(CPMenuItem)aMenuItem atIndex:(unsigned)anIndex
270 {
271  [self insertObject:aMenuItem inItemsAtIndex:anIndex];
272 }
273 
282 - (CPMenuItem)insertItemWithTitle:(CPString)aTitle action:(SEL)anAction keyEquivalent:(CPString)aKeyEquivalent atIndex:(unsigned)anIndex
283 {
284  var item = [[CPMenuItem alloc] initWithTitle:aTitle action:anAction keyEquivalent:aKeyEquivalent];
285 
286  [self insertItem:item atIndex:anIndex];
287 
288  return item;
289 }
290 
295 - (void)addItem:(CPMenuItem)aMenuItem
296 {
297  [self insertItem:aMenuItem atIndex:[_items count]];
298 }
299 
308 - (CPMenuItem)addItemWithTitle:(CPString)aTitle action:(SEL)anAction keyEquivalent:(CPString)aKeyEquivalent
309 {
310  return [self insertItemWithTitle:aTitle action:anAction keyEquivalent:aKeyEquivalent atIndex:[_items count]];
311 }
312 
317 - (void)removeItem:(CPMenuItem)aMenuItem
318 {
319  [self removeItemAtIndex:[_items indexOfObjectIdenticalTo:aMenuItem]];
320 }
321 
326 - (void)removeItemAtIndex:(unsigned)anIndex
327 {
328  [self removeObjectFromItemsAtIndex:anIndex];
329 }
330 
337 - (void)removeAllItems
338 {
339  var count = [_items count];
340 
341  // Remove the connection to this menu in case
342  // someone else has a reference to the menu item.
343  while (count--)
344  [_items[count] setMenu:nil];
345 
346  _highlightedIndex = CPNotFound;
347 
348  // Because we are changing _items directly, be sure to notify KVO
349  [self willChangeValueForKey:@"items"];
350  _items = [CPMutableArray array];
351  [self didChangeValueForKey:@"items"];
352 }
353 
358 - (void)itemChanged:(CPMenuItem)aMenuItem
359 {
360  if ([aMenuItem menu] !== self)
361  return;
362 
363  [aMenuItem setValue:[aMenuItem valueForKey:@"changeCount"] + 1 forKey:@"changeCount"];
364 
366  postNotificationName:CPMenuDidChangeItemNotification
367  object:self
368  userInfo:[CPDictionary dictionaryWithObject:[_items indexOfObjectIdenticalTo:aMenuItem] forKey:@"CPMenuItemIndex"]];
369 }
370 
371 // Finding Menu Items
377 - (CPMenuItem)itemWithTag:(int)aTag
378 {
379  var index = [self indexOfItemWithTag:aTag];
380 
381  if (index == CPNotFound)
382  return nil;
383 
384  return _items[index];
385 }
386 
392 - (CPMenuItem)itemWithTitle:(CPString)aTitle
393 {
394  var index = [self indexOfItemWithTitle:aTitle];
395 
396  if (index == CPNotFound)
397  return nil;
398 
399  return _items[index];
400 }
401 
406 - (CPMenuItem)itemAtIndex:(int)anIndex
407 {
408  return [_items objectAtIndex:anIndex];
409 }
410 
414 - (unsigned)numberOfItems
415 {
416  return [_items count];
417 }
418 
422 - (CPArray)itemArray
423 {
424  return _items;
425 }
426 
427 // Finding Indices of Menu Items
433 - (int)indexOfItem:(CPMenuItem)aMenuItem
434 {
435  if ([aMenuItem menu] !== self)
436  return CPNotFound;
437 
438  return [_items indexOfObjectIdenticalTo:aMenuItem];
439 }
440 
446 - (int)indexOfItemWithTitle:(CPString)aTitle
447 {
448  var index = 0,
449  count = _items.length;
450 
451  for (; index < count; ++index)
452  if ([_items[index] title] === aTitle)
453  return index;
454 
455  return CPNotFound;
456 }
457 
463 - (int)indexOfItemWithTag:(int)aTag
464 {
465  var index = 0,
466  count = _items.length;
467 
468  for (; index < count; ++index)
469  if ([_items[index] tag] == aTag)
470  return index;
471 
472  return CPNotFound;
473 }
474 
481 - (int)indexOfItemWithTarget:(id)aTarget andAction:(SEL)anAction
482 {
483  var index = 0,
484  count = _items.length;
485 
486  for (; index < count; ++index)
487  {
488  var item = _items[index];
489 
490  if ([item target] == aTarget && (!anAction || [item action] == anAction))
491  return index;
492  }
493 
494  return CPNotFound;
495 }
496 
502 - (int)indexOfItemWithRepresentedObject:(id)anObject
503 {
504  var index = 0,
505  count = _items.length;
506 
507  for (; index < count; ++index)
508  if ([[_items[index] representedObject] isEqual:anObject])
509  return index;
510 
511  return CPNotFound;
512 }
513 
519 - (int)indexOfItemWithSubmenu:(CPMenu)aMenu
520 {
521  var index = 0,
522  count = _items.length;
523 
524  for (; index < count; ++index)
525  if ([_items[index] submenu] == aMenu)
526  return index;
527 
528  return CPNotFound;
529 }
530 
531 // Managing Submenus
537 - (void)setSubmenu:(CPMenu)aMenu forItem:(CPMenuItem)aMenuItem
538 {
539  [aMenuItem setTarget:aMenuItem];
540  [aMenuItem setAction:@selector(submenuAction:)];
541 
542  [aMenuItem setSubmenu:aMenu];
543 }
544 
551 - (void)submenuAction:(id)aSender
552 {
553 }
554 
558 - (CPMenu)supermenu
559 {
560  return _supermenu;
561 }
562 
567 - (void)setSupermenu:(CPMenu)aMenu
568 {
569  _supermenu = aMenu;
570 }
571 
576 - (BOOL)isTornOff
577 {
578  return !_supermenu /* || offscreen(?) */ || self == [CPApp mainMenu];
579 }
580 
581 // Enabling and Disabling Menu Items
586 - (void)setAutoenablesItems:(BOOL)aFlag
587 {
588  _autoenablesItems = aFlag;
589 }
590 
594 - (BOOL)autoenablesItems
595 {
596  return _autoenablesItems;
597 }
598 
604 - (void)update
605 {
606  if (![self autoenablesItems])
607  return;
608 
609  var items = [self itemArray];
610  for (var i = 0; i < [items count]; i++)
611  {
612  var item = [items objectAtIndex:i];
613 
614  if ([item hasSubmenu])
615  continue;
616 
617  var validator = [CPApp targetForAction:[item action] to:[item target] from:item];
618 
619  if (!validator || ![validator respondsToSelector:[item action]])
620  [item setEnabled:NO];
621  else if ([validator respondsToSelector:@selector(validateMenuItem:)])
622  [item setEnabled:[validator validateMenuItem:item]];
623  else if ([validator respondsToSelector:@selector(validateUserInterfaceItem:)])
624  [item setEnabled:[validator validateUserInterfaceItem:item]];
625  }
626 
627  [[_menuWindow _menuView] tile];
628 }
629 
630 // Managing the Title
635 - (void)setTitle:(CPString)aTitle
636 {
637  _title = aTitle;
638 }
639 
643 - (CPString)title
644 {
645  return _title;
646 }
647 
648 - (void)setMinimumWidth:(float)aMinimumWidth
649 {
650  _minimumWidth = aMinimumWidth;
651 }
652 
653 - (float)minimumWidth
654 {
655  return _minimumWidth;
656 }
657 
658 - (void)_performActionOfHighlightedItemChain
659 {
660  var highlightedItem = [self highlightedItem];
661 
662  while ([highlightedItem submenu] && [highlightedItem action] === @selector(submenuAction:))
663  highlightedItem = [[highlightedItem submenu] highlightedItem];
664 
665  // FIXME: It is theoretically not necessarily to check isEnabled here since
666  // highlightedItem is always enabled. Do there exist edge cases: disabling on closing a menu,
667  // etc.? Requires further investigation and tests.
668  if (highlightedItem && [highlightedItem isEnabled])
669  [CPApp sendAction:[highlightedItem action] to:[highlightedItem target] from:highlightedItem];
670 }
671 
672 //
673 + (CGRect)_constraintRectForView:(CPView)aView
674 {
675  if ([CPPlatform isBrowser])
676  return CGRectInset([[[aView window] platformWindow] contentBounds], 5.0, 5.0);
677 
678  return CGRectInset([[[aView window] screen] visibleFrame], 5.0, 5.0);
679 }
680 
681 - (void)popUpMenuPositioningItem:(CPMenuItem)anItem atLocation:(CGPoint)aLocation inView:(CPView)aView callback:(Function)aCallback
682 {
683  [self _popUpMenuPositioningItem:anItem
684  atLocation:aLocation
685  topY:aLocation.y
686  bottomY:aLocation.y
687  inView:aView
688  callback:aCallback];
689 }
690 
691 - (void)_popUpMenuPositioningItem:(CPMenuItem)anItem atLocation:(CGPoint)aLocation topY:(float)aTopY bottomY:(float)aBottomY inView:(CPView)aView callback:(Function)aCallback
692 {
693  var itemIndex = 0;
694 
695  if (anItem)
696  {
697  itemIndex = [self indexOfItem:anItem];
698 
699  if (itemIndex === CPNotFound)
700  throw "In call to popUpMenuPositioningItem:atLocation:inView:callback:, menu item " +
701  anItem + " is not present in menu " + self;
702  }
703 
704  var theWindow = [aView window];
705 
706  if (aView && !theWindow)
707  throw "In call to popUpMenuPositioningItem:atLocation:inView:callback:, view is not in any window.";
708 
709  [self _menuWillOpen];
710 
711  // Convert location to global coordinates if not already in them.
712  if (aView)
713  aLocation = [theWindow convertBaseToGlobal:[aView convertPoint:aLocation toView:nil]];
714 
715  // Create the window for our menu.
716  var menuWindow = [_CPMenuWindow menuWindowWithMenu:self font:[self font]];
717 
718  [menuWindow setBackgroundStyle:_CPMenuWindowPopUpBackgroundStyle];
719 
720  if (anItem)
721  // Don't convert this value to global, we care about the distance (delta) from the
722  // the edge of the window, which is equivalent to its origin.
723  aLocation.y -= [menuWindow deltaYForItemAtIndex:itemIndex];
724 
725  // Grab the constraint rect for this view.
726  var constraintRect = [CPMenu _constraintRectForView:aView];
727 
728  [menuWindow setFrameOrigin:aLocation];
729  [menuWindow setConstraintRect:constraintRect];
730 
731  // If we aren't showing enough items, reposition the view in a better place.
732  if (![menuWindow hasMinimumNumberOfVisibleItems])
733  {
734  var unconstrainedFrame = [menuWindow unconstrainedFrame],
735  unconstrainedY = CGRectGetMinY(unconstrainedFrame);
736 
737  // If we scroll to early downwards, or are offscreen (!), move it up.
738  if (unconstrainedY >= CGRectGetMaxY(constraintRect) || [menuWindow canScrollDown])
739  {
740  // Convert this to global if it isn't already.
741  if (aView)
742  aTopY = [theWindow convertBaseToGlobal:[aView convertPoint:CGPointMake(0.0, aTopY) toView:nil]].y;
743 
744  unconstrainedFrame.origin.y = MIN(CGRectGetMaxY(constraintRect), aTopY) - CGRectGetHeight(unconstrainedFrame);
745  }
746 
747  // If we scroll to early upwards, or are offscreen (!), move it down.
748  else if (unconstrainedY < CGRectGetMinY(constraintRect) || [menuWindow canScrollUp])
749  {
750  // Convert this to global if it isn't already.
751  if (aView)
752  aBottomY = [theWindow convertBaseToGlobal:[aView convertPoint:CGPointMake(0.0, aBottomY) toView:nil]].y;
753 
754  unconstrainedFrame.origin.y = MAX(CGRectGetMinY(constraintRect), aBottomY);
755  }
756 
757  [menuWindow setFrameOrigin:CGRectIntersection(unconstrainedFrame, constraintRect).origin];
758  }
759 
760  // Show it.
761  if ([CPPlatform isBrowser])
762  [menuWindow setPlatformWindow:[[aView window] platformWindow]];
763 
764  [menuWindow orderFront:self];
765 
766  // Track it.
767  [[_CPMenuManager sharedMenuManager]
768  beginTracking:[CPApp currentEvent]
769  menuContainer:menuWindow
770  constraintRect:constraintRect
771  callback:[CPMenu trackingCallbackWithCallback:aCallback]];
772 }
773 
774 + (Function)trackingCallbackWithCallback:(Function)aCallback
775 {
776  return function(aMenuWindow, aMenu)
777  {
778  [aMenuWindow setMenu:nil];
779  [aMenuWindow orderOut:self];
780 
781  [_CPMenuWindow poolMenuWindow:aMenuWindow];
782 
783  if (aCallback)
784  aCallback(aMenu);
785 
786  [aMenu _performActionOfHighlightedItemChain];
787  }
788 }
789 
790 + (void)popUpContextMenu:(CPMenu)aMenu withEvent:(CPEvent)anEvent forView:(CPView)aView
791 {
792  [self popUpContextMenu:aMenu withEvent:anEvent forView:aView withFont:nil];
793 }
794 
795 + (void)popUpContextMenu:(CPMenu)aMenu withEvent:(CPEvent)anEvent forView:(CPView)aView withFont:(CPFont)aFont
796 {
797  [aMenu _menuWillOpen];
798 
799  if (!aFont)
801 
802  var theWindow = [aView window],
803  menuWindow = [_CPMenuWindow menuWindowWithMenu:aMenu font:aFont];
804 
805  [menuWindow setBackgroundStyle:_CPMenuWindowPopUpBackgroundStyle];
806 
807  var constraintRect = [CPMenu _constraintRectForView:aView],
808  aLocation = [[anEvent window] convertBaseToGlobal:[anEvent locationInWindow]];
809 
810  [menuWindow setConstraintRect:constraintRect];
811  [menuWindow setFrameOrigin:aLocation];
812 
813  // If we aren't showing enough items, reposition the view in a better place.
814  if (![menuWindow hasMinimumNumberOfVisibleItems])
815  {
816  var unconstrainedFrame = [menuWindow unconstrainedFrame],
817  unconstrainedY = CGRectGetMinY(unconstrainedFrame);
818 
819  // If we scroll to early downwards, or are offscreen (!), move it up.
820  if (unconstrainedY >= CGRectGetMaxY(constraintRect) || [menuWindow canScrollDown])
821  unconstrainedFrame.origin.y = MIN(CGRectGetMaxY(constraintRect), aLocation.y) - CGRectGetHeight(unconstrainedFrame);
822 
823  // If we scroll to early upwards, or are offscreen (!), move it down.
824  else if (unconstrainedY < CGRectGetMinY(constraintRect) || [menuWindow canScrollUp])
825  unconstrainedFrame.origin.y = MAX(CGRectGetMinY(constraintRect), aLocation.y);
826 
827  [menuWindow setFrameOrigin:CGRectIntersection(unconstrainedFrame, constraintRect).origin];
828  }
829 
830  if ([CPPlatform isBrowser])
831  [menuWindow setPlatformWindow:[[aView window] platformWindow]];
832 
833  [menuWindow orderFront:self];
834 
835  [[_CPMenuManager sharedMenuManager]
836  beginTracking:anEvent
837  menuContainer:menuWindow
838  constraintRect:[CPMenu _constraintRectForView:aView]
839  callback:[CPMenu trackingCallbackWithCallback:nil]];
840 }
841 
842 // Managing Display of State Column
847 - (void)setShowsStateColumn:(BOOL)shouldShowStateColumn
848 {
849  _showsStateColumn = shouldShowStateColumn;
850 }
851 
855 - (BOOL)showsStateColumn
856 {
857  return _showsStateColumn;
858 }
859 
860 // Handling Highlighting
865 - (CPMenuItem)highlightedItem
866 {
867  if (_highlightedIndex < 0)
868  return nil;
869 
870  var highlightedItem = _items[_highlightedIndex];
871 
872  if ([highlightedItem isSeparatorItem])
873  return nil;
874 
875  return highlightedItem;
876 }
877 
878 // Managing the Delegate
879 
880 - (void)setDelegate:(id)aDelegate
881 {
882  _delegate = aDelegate;
883 }
884 
885 - (id)delegate
886 {
887  return _delegate;
888 }
889 
890 - (void)_menuWillOpen
891 {
892  var delegate = [self delegate];
893 
894  if ([delegate respondsToSelector:@selector(menuWillOpen:)])
895  [delegate menuWillOpen:self];
896 }
897 
898 - (void)_menuDidClose
899 {
900  var delegate = [self delegate];
901 
902  if ([delegate respondsToSelector:@selector(menuDidClose:)])
903  [delegate menuDidClose:self];
904 }
905 
906 // Handling Tracking
910 - (void)cancelTracking
911 {
912  [[CPRunLoop currentRunLoop] performSelector:@selector(_fireCancelTrackingEvent) target:self argument:nil order:0 modes:[CPDefaultRunLoopMode]];
913 }
914 
915 - (void)_fireCancelTrackingEvent
916 {
917  [CPApp sendEvent:[CPEvent
918  otherEventWithType:CPAppKitDefined
919  location:_CGPointMakeZero()
920  modifierFlags:0
921  timestamp:0
922  windowNumber:0
923  context:0
924  subtype:0
925  data1:0
926  data2:0]];
927 
928  // FIXME: We need to do this because this happens in a limitDateForMode:, thus
929  // the second limitDateForMode: won't take effect and the perform selector that
930  // actually draws also won't go into effect. In Safari this works because it sends
931  // an additional mouse move after all this, but not in other browsers.
932  // This will be fixed correctly with the coming run loop changes.
933  [_CPDisplayServer run];
934 }
935 
936 /* @ignore */
937 - (void)_setMenuWindow:(_CPMenuWindow)aMenuWindow
938 {
939  _menuWindow = aMenuWindow;
940 }
941 
942 - (void)setFont:(CPFont)aFont
943 {
944  _font = aFont;
945 }
946 
947 - (CPFont)font
948 {
949  return _font;
950 }
951 
958 - (BOOL)performKeyEquivalent:(CPEvent)anEvent
959 {
960  if (_autoenablesItems)
961  [self update];
962 
963  var index = 0,
964  count = _items.length,
965  characters = [anEvent charactersIgnoringModifiers],
966  modifierFlags = [anEvent modifierFlags];
967 
968  for (; index < count; ++index)
969  {
970  var item = _items[index];
971 
972  if ([anEvent _triggersKeyEquivalent:[item keyEquivalent] withModifierMask:[item keyEquivalentModifierMask]])
973  {
974  if ([item isEnabled])
975  [self performActionForItemAtIndex:index];
976  else
977  {
978  //beep?
979  }
980 
981  return YES;
982  }
983 
984  if ([[item submenu] performKeyEquivalent:anEvent])
985  return YES;
986  }
987 
988  return NO;
989 }
990 
991 // Simulating Mouse Clicks
996 - (void)performActionForItemAtIndex:(unsigned)anIndex
997 {
998  var item = _items[anIndex];
999 
1000  [CPApp sendAction:[item action] to:[item target] from:item];
1001 }
1002 
1003 //
1004 /*
1005  @ignore
1006 */
1007 - (void)_highlightItemAtIndex:(int)anIndex
1008 {
1009  if (_highlightedIndex === anIndex)
1010  return;
1011 
1012  if (_highlightedIndex !== CPNotFound)
1013  [[_items[_highlightedIndex] _menuItemView] highlight:NO];
1014 
1015  _highlightedIndex = anIndex;
1016 
1017  if (_highlightedIndex !== CPNotFound)
1018  [[_items[_highlightedIndex] _menuItemView] highlight:YES];
1019 
1020  if (_highlightedIndex !== CPNotFound && _menuWindow)
1021  [_menuWindow._menuView scrollRectToVisible:[[_items[_highlightedIndex] _menuItemView] frame]];
1022 }
1023 
1024 - (void)_setMenuName:(CPString)aName
1025 {
1026  if (_name === aName)
1027  return;
1028 
1029  _name = aName;
1030 
1031  if (_name === @"CPMainMenu")
1032  [CPApp setMainMenu:self];
1033 }
1034 
1035 - (CPString)_menuName
1036 {
1037  return _name;
1038 }
1039 
1040 - (void)awakeFromCib
1041 {
1042  if (_name === @"_CPMainMenu")
1043  {
1044  [self _setMenuName:@"CPMainMenu"];
1045  [CPMenu setMenuBarVisible:YES];
1046  }
1047 }
1048 
1049 - (void)_menuWithName:(CPString)aName
1050 {
1051  if (aName === _name)
1052  return self;
1053 
1054  for (var i = 0, count = [_items count]; i < count; i++)
1055  {
1056  var menu = [[_items[i] submenu] _menuWithName:aName];
1057 
1058  if (menu)
1059  return menu;
1060  }
1061 
1062  return nil;
1063 }
1064 
1065 @end
1066 
1068 
1069 - (CPUInteger)countOfItems
1070 {
1071  return [_items count];
1072 }
1073 
1074 - (CPMenuItem)objectInItemsAtIndex:(CPUInteger)anIndex
1075 {
1076  return [_items objectAtIndex:anIndex];
1077 }
1078 
1079 - (CPArray)itemsAtIndexes:(CPIndexSet)indexes
1080 {
1081  return [_items objectsAtIndexes:indexes];
1082 }
1083 
1084 @end
1085 
1087 
1088 - (void)insertObject:(CPMenuItem)aMenuItem inItemsAtIndex:(CPUInteger)anIndex
1089 {
1090  var menu = [aMenuItem menu];
1091 
1092  if (menu)
1093  if (menu !== self)
1094  [CPException raise:CPInternalInconsistencyException reason:@"Attempted to insert item into menu that was already in another menu."];
1095  else
1096  return;
1097 
1098  [aMenuItem setMenu:self];
1099  [_items insertObject:aMenuItem atIndex:anIndex];
1100 
1102  postNotificationName:CPMenuDidAddItemNotification
1103  object:self
1104  userInfo:[CPDictionary dictionaryWithObject:anIndex forKey:@"CPMenuItemIndex"]];
1105 }
1106 
1107 - (void)removeObjectFromItemsAtIndex:(CPUInteger)anIndex
1108 {
1109  if (anIndex < 0 || anIndex >= [_items count])
1110  return;
1111 
1112  [[_items objectAtIndex:anIndex] setMenu:nil];
1113  [_items removeObjectAtIndex:anIndex];
1114 
1116  postNotificationName:CPMenuDidRemoveItemNotification
1117  object:self
1118  userInfo:[CPDictionary dictionaryWithObject:anIndex forKey:@"CPMenuItemIndex"]];
1119 }
1120 
1121 @end
1122 
1123 var CPMenuTitleKey = @"CPMenuTitleKey",
1124  CPMenuNameKey = @"CPMenuNameKey",
1125  CPMenuItemsKey = @"CPMenuItemsKey",
1126  CPMenuShowsStateColumnKey = @"CPMenuShowsStateColumnKey";
1127 
1128 @implementation CPMenu (CPCoding)
1129 
1135 - (id)initWithCoder:(CPCoder)aCoder
1136 {
1137  self = [super init];
1138 
1139  if (self)
1140  {
1141  _title = [aCoder decodeObjectForKey:CPMenuTitleKey];
1142  _items = [aCoder decodeObjectForKey:CPMenuItemsKey];
1143 
1144  [self _setMenuName:[aCoder decodeObjectForKey:CPMenuNameKey]];
1145 
1146  _showsStateColumn = ![aCoder containsValueForKey:CPMenuShowsStateColumnKey] || [aCoder decodeBoolForKey:CPMenuShowsStateColumnKey];
1147 
1148  _autoenablesItems = YES;
1149 
1150  [self setMinimumWidth:0];
1151  }
1152 
1153  return self;
1154 }
1155 
1160 - (void)encodeWithCoder:(CPCoder)aCoder
1161 {
1162  [aCoder encodeObject:_title forKey:CPMenuTitleKey];
1163 
1164  if (_name)
1165  [aCoder encodeObject:_name forKey:CPMenuNameKey];
1166 
1167  [aCoder encodeObject:_items forKey:CPMenuItemsKey];
1168 
1169  if (!_showsStateColumn)
1170  [aCoder encodeBool:_showsStateColumn forKey:CPMenuShowsStateColumnKey];
1171 }
1172 
1173 @end
1174 
1175