API 0.9.5
AppKit/CPMenu/CPMenu.j
Go to the documentation of this file.
00001 /*
00002  * CPMenu.j
00003  * AppKit
00004  *
00005  * Created by Francisco Tolmasky.
00006  * Copyright 2008, 280 North, Inc.
00007  *
00008  * This library is free software; you can redistribute it and/or
00009  * modify it under the terms of the GNU Lesser General Public
00010  * License as published by the Free Software Foundation; either
00011  * version 2.1 of the License, or (at your option) any later version.
00012  *
00013  * This library is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00016  * Lesser General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU Lesser General Public
00019  * License along with this library; if not, write to the Free Software
00020  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
00021  */
00022 
00023 
00024 
00025 
00026 CPMenuDidAddItemNotification        = @"CPMenuDidAddItemNotification";
00027 CPMenuDidChangeItemNotification     = @"CPMenuDidChangeItemNotification";
00028 CPMenuDidRemoveItemNotification     = @"CPMenuDidRemoveItemNotification";
00029 
00030 CPMenuDidEndTrackingNotification    = @"CPMenuDidEndTrackingNotification";
00031 
00032 var MENUBAR_HEIGHT = 28.0;
00033 
00034 var _CPMenuBarVisible               = NO,
00035     _CPMenuBarTitle                 = @"",
00036     _CPMenuBarIconImage             = nil,
00037     _CPMenuBarIconImageAlphaValue   = 1.0,
00038     _CPMenuBarAttributes            = nil,
00039     _CPMenuBarSharedWindow          = nil;
00040 
00048 @implementation CPMenu : CPObject
00049 {
00050     CPMenu          _supermenu;
00051 
00052     CPString        _title;
00053     CPString        _name;
00054 
00055     CPFont          _font;
00056 
00057     float           _minimumWidth;
00058 
00059     CPMutableArray  _items;
00060 
00061     BOOL            _autoenablesItems;
00062     BOOL            _showsStateColumn;
00063 
00064     id              _delegate;
00065 
00066     int             _highlightedIndex;
00067     _CPMenuWindow   _menuWindow;
00068 }
00069 
00070 // Managing the Menu Bar
00071 
00072 + (void)initialize
00073 {
00074     if (self !== CPMenu)
00075         return;
00076 
00077     [[self class] setMenuBarAttributes:[CPDictionary dictionary]];
00078 }
00079 
00080 + (BOOL)menuBarVisible
00081 {
00082     return _CPMenuBarVisible;
00083 }
00084 
00085 + (void)setMenuBarVisible:(BOOL)menuBarShouldBeVisible
00086 {
00087     if (_CPMenuBarVisible === menuBarShouldBeVisible)
00088         return;
00089 
00090     _CPMenuBarVisible = menuBarShouldBeVisible;
00091 
00092     if ([CPPlatform supportsNativeMainMenu])
00093         return;
00094 
00095     if (menuBarShouldBeVisible)
00096     {
00097         if (!_CPMenuBarSharedWindow)
00098             _CPMenuBarSharedWindow = [[_CPMenuBarWindow alloc] init];
00099 
00100         [_CPMenuBarSharedWindow setMenu:[CPApp mainMenu]];
00101 
00102         [_CPMenuBarSharedWindow setTitle:_CPMenuBarTitle];
00103         [_CPMenuBarSharedWindow setIconImage:_CPMenuBarIconImage];
00104         [_CPMenuBarSharedWindow setIconImageAlphaValue:_CPMenuBarIconImageAlphaValue];
00105 
00106         [_CPMenuBarSharedWindow setColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarBackgroundColor"]];
00107         [_CPMenuBarSharedWindow setTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextColor"]];
00108         [_CPMenuBarSharedWindow setTitleColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTitleColor"]];
00109         [_CPMenuBarSharedWindow setTextShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextShadowColor"]];
00110         [_CPMenuBarSharedWindow setTitleShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTitleShadowColor"]];
00111         [_CPMenuBarSharedWindow setHighlightColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightColor"]];
00112         [_CPMenuBarSharedWindow setHighlightTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightTextColor"]];
00113         [_CPMenuBarSharedWindow setHighlightTextShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightTextShadowColor"]];
00114 
00115         [_CPMenuBarSharedWindow orderFront:self];
00116     }
00117     else
00118         [_CPMenuBarSharedWindow orderOut:self];
00119 
00120 // FIXME: There must be a better way to do this.
00121 #if PLATFORM(DOM)
00122     [[CPPlatformWindow primaryPlatformWindow] resizeEvent:nil];
00123 #endif
00124 }
00125 
00126 + (void)setMenuBarTitle:(CPString)aTitle
00127 {
00128     _CPMenuBarTitle = aTitle;
00129     [_CPMenuBarSharedWindow setTitle:_CPMenuBarTitle];
00130 }
00131 
00132 + (CPString)menuBarTitle
00133 {
00134     return _CPMenuBarTitle;
00135 }
00136 
00137 + (void)setMenuBarIconImage:(CPImage)anImage
00138 {
00139     _CPMenuBarImage = anImage;
00140     [_CPMenuBarSharedWindow setIconImage:anImage];
00141 }
00142 
00143 + (CPImage)menuBarIconImage
00144 {
00145     return _CPMenuBarImage;
00146 }
00147 
00148 
00149 + (void)setMenuBarAttributes:(CPDictionary)attributes
00150 {
00151     if (_CPMenuBarAttributes == attributes)
00152         return;
00153 
00154     _CPMenuBarAttributes = [attributes copy];
00155 
00156     var textColor = [attributes objectForKey:@"CPMenuBarTextColor"],
00157         titleColor = [attributes objectForKey:@"CPMenuBarTitleColor"],
00158         textShadowColor = [attributes objectForKey:@"CPMenuBarTextShadowColor"],
00159         titleShadowColor = [attributes objectForKey:@"CPMenuBarTitleShadowColor"],
00160         highlightColor = [attributes objectForKey:@"CPMenuBarHighlightColor"],
00161         highlightTextColor = [attributes objectForKey:@"CPMenuBarHighlightTextColor"],
00162         highlightTextShadowColor = [attributes objectForKey:@"CPMenuBarHighlightTextShadowColor"];
00163 
00164     if (!textColor && titleColor)
00165         [_CPMenuBarAttributes setObject:titleColor forKey:@"CPMenuBarTextColor"];
00166 
00167     else if (textColor && !titleColor)
00168         [_CPMenuBarAttributes setObject:textColor forKey:@"CPMenuBarTitleColor"];
00169 
00170     else if (!textColor && !titleColor)
00171     {
00172         [_CPMenuBarAttributes setObject:[CPColor colorWithRed:0.051 green:0.2 blue:0.275 alpha:1.0] forKey:@"CPMenuBarTextColor"];
00173         [_CPMenuBarAttributes setObject:[CPColor colorWithRed:0.051 green:0.2 blue:0.275 alpha:1.0] forKey:@"CPMenuBarTitleColor"];
00174     }
00175 
00176     if (!textShadowColor && titleShadowColor)
00177         [_CPMenuBarAttributes setObject:titleShadowColor forKey:@"CPMenuBarTextShadowColor"];
00178 
00179     else if (textShadowColor && !titleShadowColor)
00180         [_CPMenuBarAttributes setObject:textShadowColor forKey:@"CPMenuBarTitleShadowColor"];
00181 
00182     else if (!textShadowColor && !titleShadowColor)
00183     {
00184         [_CPMenuBarAttributes setObject:[CPColor whiteColor] forKey:@"CPMenuBarTextShadowColor"];
00185         [_CPMenuBarAttributes setObject:[CPColor whiteColor] forKey:@"CPMenuBarTitleShadowColor"];
00186     }
00187 
00188     if (!highlightColor)
00189         [_CPMenuBarAttributes setObject:[CPColor colorWithCalibratedRed:94.0 / 255.0 green:130.0 / 255.0 blue:186.0 / 255.0 alpha:1.0] forKey:@"CPMenuBarHighlightColor"];
00190 
00191     if (!highlightTextColor)
00192         [_CPMenuBarAttributes setObject:[CPColor whiteColor] forKey:@"CPMenuBarHighlightTextColor"];
00193 
00194     if (!highlightTextShadowColor)
00195         [_CPMenuBarAttributes setObject:[CPColor blackColor] forKey:@"CPMenuBarHighlightTextShadowColor"];
00196 
00197     if (_CPMenuBarSharedWindow)
00198     {
00199         [_CPMenuBarSharedWindow setColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarBackgroundColor"]];
00200         [_CPMenuBarSharedWindow setTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextColor"]];
00201         [_CPMenuBarSharedWindow setTitleColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTitleColor"]];
00202         [_CPMenuBarSharedWindow setTextShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextShadowColor"]];
00203         [_CPMenuBarSharedWindow setTitleShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTitleShadowColor"]];
00204         [_CPMenuBarSharedWindow setHighlightColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightColor"]];
00205         [_CPMenuBarSharedWindow setHighlightTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightTextColor"]];
00206         [_CPMenuBarSharedWindow setHighlightTextShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightTextShadowColor"]];
00207     }
00208 }
00209 
00210 + (CPDictionary)menuBarAttributes
00211 {
00212     return _CPMenuBarAttributes;
00213 }
00214 
00215 + (void)_setMenuBarIconImageAlphaValue:(float)anAlphaValue
00216 {
00217     _CPMenuBarIconImageAlphaValue = anAlphaValue;
00218     [_CPMenuBarSharedWindow setIconImageAlphaValue:anAlphaValue];
00219 }
00220 
00221 - (float)menuBarHeight
00222 {
00223     if (self === [CPApp mainMenu])
00224         return MENUBAR_HEIGHT;
00225 
00226     return 0.0;
00227 }
00228 
00229 + (float)menuBarHeight
00230 {
00231     return MENUBAR_HEIGHT;
00232 }
00233 
00234 // Creating a CPMenu Object
00240 - (id)initWithTitle:(CPString)aTitle
00241 {
00242     self = [super init];
00243 
00244     if (self)
00245     {
00246         _title = aTitle;
00247         _items = [];
00248 
00249         _autoenablesItems = YES;
00250         _showsStateColumn = YES;
00251 
00252         [self setMinimumWidth:0];
00253     }
00254 
00255     return self;
00256 }
00257 
00258 - (id)init
00259 {
00260     return [self initWithTitle:@""];
00261 }
00262 
00263 // Setting Up Menu Commands
00269 - (void)insertItem:(CPMenuItem)aMenuItem atIndex:(unsigned)anIndex
00270 {
00271     [self insertObject:aMenuItem inItemsAtIndex:anIndex];
00272 }
00273 
00282 - (CPMenuItem)insertItemWithTitle:(CPString)aTitle action:(SEL)anAction keyEquivalent:(CPString)aKeyEquivalent atIndex:(unsigned)anIndex
00283 {
00284     var item = [[CPMenuItem alloc] initWithTitle:aTitle action:anAction keyEquivalent:aKeyEquivalent];
00285 
00286     [self insertItem:item atIndex:anIndex];
00287 
00288     return item;
00289 }
00290 
00295 - (void)addItem:(CPMenuItem)aMenuItem
00296 {
00297     [self insertItem:aMenuItem atIndex:[_items count]];
00298 }
00299 
00308 - (CPMenuItem)addItemWithTitle:(CPString)aTitle action:(SEL)anAction keyEquivalent:(CPString)aKeyEquivalent
00309 {
00310     return [self insertItemWithTitle:aTitle action:anAction keyEquivalent:aKeyEquivalent atIndex:[_items count]];
00311 }
00312 
00317 - (void)removeItem:(CPMenuItem)aMenuItem
00318 {
00319     [self removeItemAtIndex:[_items indexOfObjectIdenticalTo:aMenuItem]];
00320 }
00321 
00326 - (void)removeItemAtIndex:(unsigned)anIndex
00327 {
00328     [self removeObjectFromItemsAtIndex:anIndex];
00329 }
00330 
00337 - (void)removeAllItems
00338 {
00339     var count = [_items count];
00340 
00341     // Remove the connection to this menu in case
00342     // someone else has a reference to the menu item.
00343     while (count--)
00344         [_items[count] setMenu:nil];
00345 
00346     _highlightedIndex = CPNotFound;
00347 
00348     // Because we are changing _items directly, be sure to notify KVO
00349     [self willChangeValueForKey:@"items"];
00350     _items = [CPMutableArray array];
00351     [self didChangeValueForKey:@"items"];
00352 }
00353 
00358 - (void)itemChanged:(CPMenuItem)aMenuItem
00359 {
00360     if ([aMenuItem menu] !== self)
00361         return;
00362 
00363     [aMenuItem setValue:[aMenuItem valueForKey:@"changeCount"] + 1 forKey:@"changeCount"];
00364 
00365     [[CPNotificationCenter defaultCenter]
00366         postNotificationName:CPMenuDidChangeItemNotification
00367                       object:self
00368                     userInfo:[CPDictionary dictionaryWithObject:[_items indexOfObjectIdenticalTo:aMenuItem] forKey:@"CPMenuItemIndex"]];
00369 }
00370 
00371 // Finding Menu Items
00377 - (CPMenuItem)itemWithTag:(int)aTag
00378 {
00379     var index = [self indexOfItemWithTag:aTag];
00380 
00381     if (index == CPNotFound)
00382         return nil;
00383 
00384     return _items[index];
00385 }
00386 
00392 - (CPMenuItem)itemWithTitle:(CPString)aTitle
00393 {
00394     var index = [self indexOfItemWithTitle:aTitle];
00395 
00396     if (index == CPNotFound)
00397         return nil;
00398 
00399     return _items[index];
00400 }
00401 
00406 - (CPMenuItem)itemAtIndex:(int)anIndex
00407 {
00408     return [_items objectAtIndex:anIndex];
00409 }
00410 
00414 - (unsigned)numberOfItems
00415 {
00416     return [_items count];
00417 }
00418 
00422 - (CPArray)itemArray
00423 {
00424     return _items;
00425 }
00426 
00427 // Finding Indices of Menu Items
00433 - (int)indexOfItem:(CPMenuItem)aMenuItem
00434 {
00435     if ([aMenuItem menu] !== self)
00436         return CPNotFound;
00437 
00438     return [_items indexOfObjectIdenticalTo:aMenuItem];
00439 }
00440 
00446 - (int)indexOfItemWithTitle:(CPString)aTitle
00447 {
00448     var index = 0,
00449         count = _items.length;
00450 
00451     for (; index < count; ++index)
00452         if ([_items[index] title] === aTitle)
00453             return index;
00454 
00455     return CPNotFound;
00456 }
00457 
00463 - (int)indexOfItemWithTag:(int)aTag
00464 {
00465     var index = 0,
00466         count = _items.length;
00467 
00468     for (; index < count; ++index)
00469         if ([_items[index] tag] == aTag)
00470             return index;
00471 
00472     return CPNotFound;
00473 }
00474 
00481 - (int)indexOfItemWithTarget:(id)aTarget andAction:(SEL)anAction
00482 {
00483     var index = 0,
00484         count = _items.length;
00485 
00486     for (; index < count; ++index)
00487     {
00488         var item = _items[index];
00489 
00490         if ([item target] == aTarget && (!anAction || [item action] == anAction))
00491             return index;
00492     }
00493 
00494     return CPNotFound;
00495 }
00496 
00502 - (int)indexOfItemWithRepresentedObject:(id)anObject
00503 {
00504     var index = 0,
00505         count = _items.length;
00506 
00507     for (; index < count; ++index)
00508         if ([[_items[index] representedObject] isEqual:anObject])
00509             return index;
00510 
00511     return CPNotFound;
00512 }
00513 
00519 - (int)indexOfItemWithSubmenu:(CPMenu)aMenu
00520 {
00521     var index = 0,
00522         count = _items.length;
00523 
00524     for (; index < count; ++index)
00525         if ([_items[index] submenu] == aMenu)
00526             return index;
00527 
00528     return CPNotFound;
00529 }
00530 
00531 // Managing Submenus
00537 - (void)setSubmenu:(CPMenu)aMenu forItem:(CPMenuItem)aMenuItem
00538 {
00539     [aMenuItem setTarget:aMenuItem];
00540     [aMenuItem setAction:@selector(submenuAction:)];
00541 
00542     [aMenuItem setSubmenu:aMenu];
00543 }
00544 
00551 - (void)submenuAction:(id)aSender
00552 {
00553 }
00554 
00558 - (CPMenu)supermenu
00559 {
00560     return _supermenu;
00561 }
00562 
00567 - (void)setSupermenu:(CPMenu)aMenu
00568 {
00569     _supermenu = aMenu;
00570 }
00571 
00576 - (BOOL)isTornOff
00577 {
00578     return !_supermenu /* || offscreen(?) */ || self == [CPApp mainMenu];
00579 }
00580 
00581 // Enabling and Disabling Menu Items
00586 - (void)setAutoenablesItems:(BOOL)aFlag
00587 {
00588     _autoenablesItems = aFlag;
00589 }
00590 
00594 - (BOOL)autoenablesItems
00595 {
00596     return _autoenablesItems;
00597 }
00598 
00604 - (void)update
00605 {
00606     if (![self autoenablesItems])
00607         return;
00608 
00609     var items = [self itemArray];
00610     for (var i = 0; i < [items count]; i++)
00611     {
00612         var item = [items objectAtIndex:i];
00613 
00614         if ([item hasSubmenu])
00615             continue;
00616 
00617         var validator = [CPApp targetForAction:[item action] to:[item target] from:item];
00618 
00619         if (!validator || ![validator respondsToSelector:[item action]])
00620             [item setEnabled:NO];
00621         else if ([validator respondsToSelector:@selector(validateMenuItem:)])
00622             [item setEnabled:[validator validateMenuItem:item]];
00623         else if ([validator respondsToSelector:@selector(validateUserInterfaceItem:)])
00624             [item setEnabled:[validator validateUserInterfaceItem:item]];
00625     }
00626 
00627     [[_menuWindow _menuView] tile];
00628 }
00629 
00630 // Managing the Title
00635 - (void)setTitle:(CPString)aTitle
00636 {
00637     _title = aTitle;
00638 }
00639 
00643 - (CPString)title
00644 {
00645     return _title;
00646 }
00647 
00648 - (void)setMinimumWidth:(float)aMinimumWidth
00649 {
00650     _minimumWidth = aMinimumWidth;
00651 }
00652 
00653 - (float)minimumWidth
00654 {
00655     return _minimumWidth;
00656 }
00657 
00658 - (void)_performActionOfHighlightedItemChain
00659 {
00660     var highlightedItem = [self highlightedItem];
00661 
00662     while ([highlightedItem submenu] && [highlightedItem action] === @selector(submenuAction:))
00663         highlightedItem = [[highlightedItem submenu] highlightedItem];
00664 
00665     // FIXME: It is theoretically not necessarily to check isEnabled here since
00666     // highlightedItem is always enabled. Do there exist edge cases: disabling on closing a menu,
00667     // etc.? Requires further investigation and tests.
00668     if (highlightedItem && [highlightedItem isEnabled])
00669         [CPApp sendAction:[highlightedItem action] to:[highlightedItem target] from:highlightedItem];
00670 }
00671 
00672 //
00673 + (CGRect)_constraintRectForView:(CPView)aView
00674 {
00675     if ([CPPlatform isBrowser])
00676         return CGRectInset([[[aView window] platformWindow] contentBounds], 5.0, 5.0);
00677 
00678     return CGRectInset([[[aView window] screen] visibleFrame], 5.0, 5.0);
00679 }
00680 
00681 - (void)popUpMenuPositioningItem:(CPMenuItem)anItem atLocation:(CGPoint)aLocation inView:(CPView)aView callback:(Function)aCallback
00682 {
00683     [self _popUpMenuPositioningItem:anItem
00684                          atLocation:aLocation
00685                                topY:aLocation.y
00686                             bottomY:aLocation.y
00687                              inView:aView
00688                            callback:aCallback];
00689 }
00690 
00691 - (void)_popUpMenuPositioningItem:(CPMenuItem)anItem atLocation:(CGPoint)aLocation topY:(float)aTopY bottomY:(float)aBottomY inView:(CPView)aView callback:(Function)aCallback
00692 {
00693     var itemIndex = 0;
00694 
00695     if (anItem)
00696     {
00697         itemIndex = [self indexOfItem:anItem];
00698 
00699         if (itemIndex === CPNotFound)
00700             throw   "In call to popUpMenuPositioningItem:atLocation:inView:callback:, menu item " +
00701                     anItem  + " is not present in menu " + self;
00702     }
00703 
00704     var theWindow = [aView window];
00705 
00706     if (aView && !theWindow)
00707         throw "In call to popUpMenuPositioningItem:atLocation:inView:callback:, view is not in any window.";
00708 
00709     [self _menuWillOpen];
00710 
00711     // Convert location to global coordinates if not already in them.
00712     if (aView)
00713         aLocation = [theWindow convertBaseToGlobal:[aView convertPoint:aLocation toView:nil]];
00714 
00715     // Create the window for our menu.
00716     var menuWindow = [_CPMenuWindow menuWindowWithMenu:self font:[self font]];
00717 
00718     [menuWindow setBackgroundStyle:_CPMenuWindowPopUpBackgroundStyle];
00719 
00720     if (anItem)
00721         // Don't convert this value to global, we care about the distance (delta) from the
00722         // the edge of the window, which is equivalent to its origin.
00723         aLocation.y -= [menuWindow deltaYForItemAtIndex:itemIndex];
00724 
00725     // Grab the constraint rect for this view.
00726     var constraintRect = [CPMenu _constraintRectForView:aView];
00727 
00728     [menuWindow setFrameOrigin:aLocation];
00729     [menuWindow setConstraintRect:constraintRect];
00730 
00731     // If we aren't showing enough items, reposition the view in a better place.
00732     if (![menuWindow hasMinimumNumberOfVisibleItems])
00733     {
00734         var unconstrainedFrame = [menuWindow unconstrainedFrame],
00735             unconstrainedY = CGRectGetMinY(unconstrainedFrame);
00736 
00737         // If we scroll to early downwards, or are offscreen (!), move it up.
00738         if (unconstrainedY >= CGRectGetMaxY(constraintRect) || [menuWindow canScrollDown])
00739         {
00740             // Convert this to global if it isn't already.
00741             if (aView)
00742                 aTopY = [theWindow convertBaseToGlobal:[aView convertPoint:CGPointMake(0.0, aTopY) toView:nil]].y;
00743 
00744             unconstrainedFrame.origin.y = MIN(CGRectGetMaxY(constraintRect), aTopY) - CGRectGetHeight(unconstrainedFrame);
00745         }
00746 
00747         // If we scroll to early upwards, or are offscreen (!), move it down.
00748         else if (unconstrainedY < CGRectGetMinY(constraintRect) || [menuWindow canScrollUp])
00749         {
00750             // Convert this to global if it isn't already.
00751             if (aView)
00752                 aBottomY = [theWindow convertBaseToGlobal:[aView convertPoint:CGPointMake(0.0, aBottomY) toView:nil]].y;
00753 
00754             unconstrainedFrame.origin.y = MAX(CGRectGetMinY(constraintRect), aBottomY);
00755         }
00756 
00757         [menuWindow setFrameOrigin:CGRectIntersection(unconstrainedFrame, constraintRect).origin];
00758     }
00759 
00760     // Show it.
00761     if ([CPPlatform isBrowser])
00762         [menuWindow setPlatformWindow:[[aView window] platformWindow]];
00763 
00764     [menuWindow orderFront:self];
00765 
00766     // Track it.
00767     [[_CPMenuManager sharedMenuManager]
00768         beginTracking:[CPApp currentEvent]
00769         menuContainer:menuWindow
00770        constraintRect:constraintRect
00771              callback:[CPMenu trackingCallbackWithCallback:aCallback]];
00772 }
00773 
00774 + (Function)trackingCallbackWithCallback:(Function)aCallback
00775 {
00776     return function(aMenuWindow, aMenu)
00777     {
00778         [aMenuWindow setMenu:nil];
00779         [aMenuWindow orderOut:self];
00780 
00781         [_CPMenuWindow poolMenuWindow:aMenuWindow];
00782 
00783         if (aCallback)
00784             aCallback(aMenu);
00785 
00786         [aMenu _performActionOfHighlightedItemChain];
00787     }
00788 }
00789 
00790 + (void)popUpContextMenu:(CPMenu)aMenu withEvent:(CPEvent)anEvent forView:(CPView)aView
00791 {
00792     [self popUpContextMenu:aMenu withEvent:anEvent forView:aView withFont:nil];
00793 }
00794 
00795 + (void)popUpContextMenu:(CPMenu)aMenu withEvent:(CPEvent)anEvent forView:(CPView)aView withFont:(CPFont)aFont
00796 {
00797     [aMenu _menuWillOpen];
00798 
00799     if (!aFont)
00800         aFont = [CPFont systemFontOfSize:12.0];
00801 
00802     var theWindow = [aView window],
00803         menuWindow = [_CPMenuWindow menuWindowWithMenu:aMenu font:aFont];
00804 
00805     [menuWindow setBackgroundStyle:_CPMenuWindowPopUpBackgroundStyle];
00806 
00807     var constraintRect = [CPMenu _constraintRectForView:aView],
00808         aLocation = [[anEvent window] convertBaseToGlobal:[anEvent locationInWindow]];
00809 
00810     [menuWindow setConstraintRect:constraintRect];
00811     [menuWindow setFrameOrigin:aLocation];
00812 
00813     // If we aren't showing enough items, reposition the view in a better place.
00814     if (![menuWindow hasMinimumNumberOfVisibleItems])
00815     {
00816         var unconstrainedFrame = [menuWindow unconstrainedFrame],
00817             unconstrainedY = CGRectGetMinY(unconstrainedFrame);
00818 
00819         // If we scroll to early downwards, or are offscreen (!), move it up.
00820         if (unconstrainedY >= CGRectGetMaxY(constraintRect) || [menuWindow canScrollDown])
00821             unconstrainedFrame.origin.y = MIN(CGRectGetMaxY(constraintRect), aLocation.y) - CGRectGetHeight(unconstrainedFrame);
00822 
00823         // If we scroll to early upwards, or are offscreen (!), move it down.
00824         else if (unconstrainedY < CGRectGetMinY(constraintRect) || [menuWindow canScrollUp])
00825             unconstrainedFrame.origin.y = MAX(CGRectGetMinY(constraintRect), aLocation.y);
00826 
00827         [menuWindow setFrameOrigin:CGRectIntersection(unconstrainedFrame, constraintRect).origin];
00828     }
00829 
00830     if ([CPPlatform isBrowser])
00831         [menuWindow setPlatformWindow:[[aView window] platformWindow]];
00832 
00833     [menuWindow orderFront:self];
00834 
00835     [[_CPMenuManager sharedMenuManager]
00836         beginTracking:anEvent
00837         menuContainer:menuWindow
00838        constraintRect:[CPMenu _constraintRectForView:aView]
00839              callback:[CPMenu trackingCallbackWithCallback:nil]];
00840 }
00841 
00842 // Managing Display of State Column
00847 - (void)setShowsStateColumn:(BOOL)shouldShowStateColumn
00848 {
00849     _showsStateColumn = shouldShowStateColumn;
00850 }
00851 
00855 - (BOOL)showsStateColumn
00856 {
00857     return _showsStateColumn;
00858 }
00859 
00860 // Handling Highlighting
00865 - (CPMenuItem)highlightedItem
00866 {
00867     if (_highlightedIndex < 0)
00868         return nil;
00869 
00870     var highlightedItem = _items[_highlightedIndex];
00871 
00872     if ([highlightedItem isSeparatorItem])
00873         return nil;
00874 
00875     return highlightedItem;
00876 }
00877 
00878 // Managing the Delegate
00879 
00880 - (void)setDelegate:(id)aDelegate
00881 {
00882     _delegate = aDelegate;
00883 }
00884 
00885 - (id)delegate
00886 {
00887     return _delegate;
00888 }
00889 
00890 - (void)_menuWillOpen
00891 {
00892     var delegate = [self delegate];
00893 
00894     if ([delegate respondsToSelector:@selector(menuWillOpen:)])
00895         [delegate menuWillOpen:self];
00896 }
00897 
00898 - (void)_menuDidClose
00899 {
00900     var delegate = [self delegate];
00901 
00902     if ([delegate respondsToSelector:@selector(menuDidClose:)])
00903         [delegate menuDidClose:self];
00904 }
00905 
00906 // Handling Tracking
00910 - (void)cancelTracking
00911 {
00912     [[CPRunLoop currentRunLoop] performSelector:@selector(_fireCancelTrackingEvent) target:self argument:nil order:0 modes:[CPDefaultRunLoopMode]];
00913 }
00914 
00915 - (void)_fireCancelTrackingEvent
00916 {
00917     [CPApp sendEvent:[CPEvent
00918         otherEventWithType:CPAppKitDefined
00919                   location:_CGPointMakeZero()
00920              modifierFlags:0
00921                  timestamp:0
00922               windowNumber:0
00923                    context:0
00924                    subtype:0
00925                      data1:0
00926                      data2:0]];
00927 
00928     // FIXME: We need to do this because this happens in a limitDateForMode:, thus
00929     // the second limitDateForMode: won't take effect and the perform selector that
00930     // actually draws also won't go into effect. In Safari this works because it sends
00931     // an additional mouse move after all this, but not in other browsers.
00932     // This will be fixed correctly with the coming run loop changes.
00933     [_CPDisplayServer run];
00934 }
00935 
00936 /* @ignore */
00937 - (void)_setMenuWindow:(_CPMenuWindow)aMenuWindow
00938 {
00939     _menuWindow = aMenuWindow;
00940 }
00941 
00942 - (void)setFont:(CPFont)aFont
00943 {
00944     _font = aFont;
00945 }
00946 
00947 - (CPFont)font
00948 {
00949     return _font;
00950 }
00951 
00958 - (BOOL)performKeyEquivalent:(CPEvent)anEvent
00959 {
00960     if (_autoenablesItems)
00961         [self update];
00962 
00963     var index = 0,
00964         count = _items.length,
00965         characters = [anEvent charactersIgnoringModifiers],
00966         modifierFlags = [anEvent modifierFlags];
00967 
00968     for (; index < count; ++index)
00969     {
00970         var item = _items[index],
00971             modifierMask = [item keyEquivalentModifierMask];
00972 
00973         if ([anEvent _triggersKeyEquivalent:[item keyEquivalent] withModifierMask:[item keyEquivalentModifierMask]])
00974         {
00975             if ([item isEnabled])
00976                 [self performActionForItemAtIndex:index];
00977             else
00978             {
00979                 //beep?
00980             }
00981 
00982             return YES;
00983         }
00984 
00985         if ([[item submenu] performKeyEquivalent:anEvent])
00986             return YES;
00987    }
00988 
00989    return NO;
00990 }
00991 
00992 // Simulating Mouse Clicks
00997 - (void)performActionForItemAtIndex:(unsigned)anIndex
00998 {
00999     var item = _items[anIndex];
01000 
01001     [CPApp sendAction:[item action] to:[item target] from:item];
01002 }
01003 
01004 //
01005 /*
01006     @ignore
01007 */
01008 - (void)_highlightItemAtIndex:(int)anIndex
01009 {
01010     if (_highlightedIndex === anIndex)
01011         return;
01012 
01013     if (_highlightedIndex !== CPNotFound)
01014         [[_items[_highlightedIndex] _menuItemView] highlight:NO];
01015 
01016     _highlightedIndex = anIndex;
01017 
01018     if (_highlightedIndex !== CPNotFound)
01019         [[_items[_highlightedIndex] _menuItemView] highlight:YES];
01020 
01021     if (_highlightedIndex !== CPNotFound && _menuWindow)
01022         [_menuWindow._menuView scrollRectToVisible:[[_items[_highlightedIndex] _menuItemView] frame]];
01023 }
01024 
01025 - (void)_setMenuName:(CPString)aName
01026 {
01027     if (_name === aName)
01028         return;
01029 
01030     _name = aName;
01031 
01032     if (_name === @"CPMainMenu")
01033         [CPApp setMainMenu:self];
01034 }
01035 
01036 - (CPString)_menuName
01037 {
01038     return _name;
01039 }
01040 
01041 - (void)awakeFromCib
01042 {
01043     if (_name === @"_CPMainMenu")
01044     {
01045         [self _setMenuName:@"CPMainMenu"];
01046         [CPMenu setMenuBarVisible:YES];
01047     }
01048 }
01049 
01050 - (void)_menuWithName:(CPString)aName
01051 {
01052     if (aName === _name)
01053         return self;
01054 
01055     for (var i = 0, count = [_items count]; i < count; i++)
01056     {
01057         var menu = [[_items[i] submenu] _menuWithName:aName];
01058 
01059         if (menu)
01060             return menu;
01061     }
01062 
01063     return nil;
01064 }
01065 
01066 @end
01067 
01068 @implementation CPMenu (CPKeyValueCoding)
01069 
01070 - (CPUInteger)countOfItems
01071 {
01072     return [_items count];
01073 }
01074 
01075 - (CPMenuItem)objectInItemsAtIndex:(CPUInteger)anIndex
01076 {
01077     return [_items objectAtIndex:anIndex];
01078 }
01079 
01080 - (CPArray)itemsAtIndexes:(CPIndexSet)indexes
01081 {
01082     return [_items objectsAtIndexes:indexes];
01083 }
01084 
01085 @end
01086 
01087 @implementation CPMenu (CPKeyValueObserving)
01088 
01089 - (void)insertObject:(CPMenuItem)aMenuItem inItemsAtIndex:(CPUInteger)anIndex
01090 {
01091     var menu = [aMenuItem menu];
01092 
01093     if (menu)
01094         if (menu !== self)
01095             [CPException raise:CPInternalInconsistencyException reason:@"Attempted to insert item into menu that was already in another menu."];
01096         else
01097             return;
01098 
01099     [aMenuItem setMenu:self];
01100     [_items insertObject:aMenuItem atIndex:anIndex];
01101 
01102     [[CPNotificationCenter defaultCenter]
01103         postNotificationName:CPMenuDidAddItemNotification
01104                       object:self
01105                     userInfo:[CPDictionary dictionaryWithObject:anIndex forKey:@"CPMenuItemIndex"]];
01106 }
01107 
01108 - (void)removeObjectFromItemsAtIndex:(CPUInteger)anIndex
01109 {
01110     if (anIndex < 0 || anIndex >= [_items count])
01111         return;
01112 
01113     [[_items objectAtIndex:anIndex] setMenu:nil];
01114     [_items removeObjectAtIndex:anIndex];
01115 
01116     [[CPNotificationCenter defaultCenter]
01117         postNotificationName:CPMenuDidRemoveItemNotification
01118                       object:self
01119                     userInfo:[CPDictionary dictionaryWithObject:anIndex forKey:@"CPMenuItemIndex"]];
01120 }
01121 
01122 @end
01123 
01124 var CPMenuTitleKey              = @"CPMenuTitleKey",
01125     CPMenuNameKey               = @"CPMenuNameKey",
01126     CPMenuItemsKey              = @"CPMenuItemsKey",
01127     CPMenuShowsStateColumnKey   = @"CPMenuShowsStateColumnKey";
01128 
01129 @implementation CPMenu (CPCoding)
01130 
01136 - (id)initWithCoder:(CPCoder)aCoder
01137 {
01138     self = [super init];
01139 
01140     if (self)
01141     {
01142         _title = [aCoder decodeObjectForKey:CPMenuTitleKey];
01143         _items = [aCoder decodeObjectForKey:CPMenuItemsKey];
01144 
01145         [self _setMenuName:[aCoder decodeObjectForKey:CPMenuNameKey]];
01146 
01147         _showsStateColumn = ![aCoder containsValueForKey:CPMenuShowsStateColumnKey] || [aCoder decodeBoolForKey:CPMenuShowsStateColumnKey];
01148 
01149         _autoenablesItems = YES;
01150 
01151         [self setMinimumWidth:0];
01152     }
01153 
01154     return self;
01155 }
01156 
01161 - (void)encodeWithCoder:(CPCoder)aCoder
01162 {
01163     [aCoder encodeObject:_title forKey:CPMenuTitleKey];
01164 
01165     if (_name)
01166         [aCoder encodeObject:_name forKey:CPMenuNameKey];
01167 
01168     [aCoder encodeObject:_items forKey:CPMenuItemsKey];
01169 
01170     if (!_showsStateColumn)
01171         [aCoder encodeBool:_showsStateColumn forKey:CPMenuShowsStateColumnKey];
01172 }
01173 
01174 @end
01175 
01176 
 All Classes Files Functions Variables Defines