![]() |
API 0.9.5
|
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