diff --git a/AppKit/CPMenu.j b/AppKit/CPMenu.j deleted file mode 100644 index ef7579484..000000000 --- a/AppKit/CPMenu.j +++ /dev/null @@ -1,2014 +0,0 @@ -/* - * CPMenu.j - * AppKit - * - * Created by Francisco Tolmasky. - * Copyright 2008, 280 North, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -@import -@import -@import -@import - -@import "CPApplication.j" -@import "CPClipView.j" -@import "CPMenuItem.j" -@import "CPPanel.j" - -#include "CoreGraphics/CGGeometry.h" -#include "Platform/Platform.h" - - -CPMenuDidAddItemNotification = @"CPMenuDidAddItemNotification"; -CPMenuDidChangeItemNotification = @"CPMenuDidChangeItemNotification"; -CPMenuDidRemoveItemNotification = @"CPMenuDidRemoveItemNotification"; - -CPMenuDidEndTrackingNotification = @"CPMenuDidEndTrackingNotification"; - -var MENUBAR_HEIGHT = 19.0; - -var _CPMenuBarVisible = NO, - _CPMenuBarTitle = @"", - _CPMenuBarIconImage = nil, - _CPMenuBarIconImageAlphaValue = 1.0, - _CPMenuBarAttributes = nil, - _CPMenuBarSharedWindow = nil; - -/*! - @ingroup appkit - @class CPMenu - - Menus provide the user with a list of actions and/or submenus. Submenus themselves are full fledged menus - and so a heirarchical structure appears. -*/ -@implementation CPMenu : CPObject -{ - CPMenu _supermenu; - - CPString _title; - CPString _name; - - CPMutableArray _items; - CPMenu _attachedMenu; - - BOOL _autoenablesItems; - BOOL _showsStateColumn; - - id _delegate; - - CPMenuItem _highlightedIndex; - _CPMenuWindow _menuWindow; -} - -// Managing the Menu Bar - -+ (void)initialize -{ - [[self class] setMenuBarAttributes:[CPDictionary dictionary]]; -} - -+ (BOOL)menuBarVisible -{ - return _CPMenuBarVisible; -} - -+ (void)setMenuBarVisible:(BOOL)menuBarShouldBeVisible -{ - if (_CPMenuBarVisible == menuBarShouldBeVisible) - return; - - _CPMenuBarVisible = menuBarShouldBeVisible; - - if ([CPPlatform supportsNativeMainMenu]) - return; - - if (menuBarShouldBeVisible) - { - if (!_CPMenuBarSharedWindow) - _CPMenuBarSharedWindow = [[_CPMenuBarWindow alloc] init]; - - [_CPMenuBarSharedWindow setMenu:[CPApp mainMenu]]; - - [_CPMenuBarSharedWindow setTitle:_CPMenuBarTitle]; - [_CPMenuBarSharedWindow setIconImage:_CPMenuBarIconImage]; - [_CPMenuBarSharedWindow setIconImageAlphaValue:_CPMenuBarIconImageAlphaValue]; - - [_CPMenuBarSharedWindow setColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarBackgroundColor"]]; - [_CPMenuBarSharedWindow setTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextColor"]]; - [_CPMenuBarSharedWindow setTitleColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTitleColor"]]; - [_CPMenuBarSharedWindow setTextShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextShadowColor"]]; - [_CPMenuBarSharedWindow setTitleShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTitleShadowColor"]]; - [_CPMenuBarSharedWindow setHighlightColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightColor"]]; - [_CPMenuBarSharedWindow setHighlightTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightTextColor"]]; - [_CPMenuBarSharedWindow setHighlightTextShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightTextShadowColor"]]; - - [_CPMenuBarSharedWindow orderFront:self]; - } - else - [_CPMenuBarSharedWindow orderOut:self]; - -// FIXME: There must be a better way to do this. -#if PLATFORM(DOM) - [[CPPlatformWindow primaryPlatformWindow] resizeEvent:nil]; -#endif -} - -+ (void)setMenuBarTitle:(CPString)aTitle -{ - _CPMenuBarTitle = aTitle; - [_CPMenuBarSharedWindow setTitle:_CPMenuBarTitle]; -} - -+ (CPString)menuBarTitle -{ - return _CPMenuBarTitle; -} - -+ (void)setMenuBarIconImage:(CPImage)anImage -{ - _CPMenuBarImage = anImage; - [_CPMenuBarSharedWindow setIconImage:anImage]; -} - -+ (CPImage)menuBarIconImage -{ - return _CPMenuBarImage; -} - - -+ (void)setMenuBarAttributes:(CPDictionary)attributes -{ - if (_CPMenuBarAttributes == attributes) - return; - - _CPMenuBarAttributes = [attributes copy]; - - var textColor = [attributes objectForKey:@"CPMenuBarTextColor"], - titleColor = [attributes objectForKey:@"CPMenuBarTitleColor"], - textShadowColor = [attributes objectForKey:@"CPMenuBarTextShadowColor"], - titleShadowColor = [attributes objectForKey:@"CPMenuBarTitleShadowColor"], - highlightColor = [attributes objectForKey:@"CPMenuBarHighlightColor"], - highlightTextColor = [attributes objectForKey:@"CPMenuBarHighlightTextColor"], - highlightTextShadowColor = [attributes objectForKey:@"CPMenuBarHighlightTextShadowColor"]; - - if (!textColor && titleColor) - [_CPMenuBarAttributes setObject:titleColor forKey:@"CPMenuBarTextColor"]; - - else if (textColor && !titleColor) - [_CPMenuBarAttributes setObject:textColor forKey:@"CPMenuBarTitleColor"]; - - else if (!textColor && !titleColor) - { - [_CPMenuBarAttributes setObject:[CPColor colorWithRed:0.051 green:0.2 blue:0.275 alpha:1.0] forKey:@"CPMenuBarTextColor"]; - [_CPMenuBarAttributes setObject:[CPColor colorWithRed:0.051 green:0.2 blue:0.275 alpha:1.0] forKey:@"CPMenuBarTitleColor"]; - } - - if (!textShadowColor && titleShadowColor) - [_CPMenuBarAttributes setObject:titleShadowColor forKey:@"CPMenuBarTextShadowColor"]; - - else if (textShadowColor && !titleShadowColor) - [_CPMenuBarAttributes setObject:textShadowColor forKey:@"CPMenuBarTitleShadowColor"]; - - else if (!textShadowColor && !titleShadowColor) - { - [_CPMenuBarAttributes setObject:[CPColor whiteColor] forKey:@"CPMenuBarTextShadowColor"]; - [_CPMenuBarAttributes setObject:[CPColor whiteColor] forKey:@"CPMenuBarTitleShadowColor"]; - } - - if (!highlightColor) - [_CPMenuBarAttributes setObject:[CPColor colorWithCalibratedRed:94.0/255.0 green:130.0/255.0 blue:186.0/255.0 alpha:1.0] forKey:@"CPMenuBarHighlightColor"]; - - if (!highlightTextColor) - [_CPMenuBarAttributes setObject:[CPColor whiteColor] forKey:@"CPMenuBarHighlightTextColor"]; - - if (!highlightTextShadowColor) - [_CPMenuBarAttributes setObject:[CPColor blackColor] forKey:@"CPMenuBarHighlightTextShadowColor"]; - - if (_CPMenuBarSharedWindow) - { - [_CPMenuBarSharedWindow setColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarBackgroundColor"]]; - [_CPMenuBarSharedWindow setTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextColor"]]; - [_CPMenuBarSharedWindow setTitleColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTitleColor"]]; - [_CPMenuBarSharedWindow setTextShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextShadowColor"]]; - [_CPMenuBarSharedWindow setTitleShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTitleShadowColor"]]; - [_CPMenuBarSharedWindow setHighlightColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightColor"]]; - [_CPMenuBarSharedWindow setHighlightTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightTextColor"]]; - [_CPMenuBarSharedWindow setHighlightTextShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightTextShadowColor"]]; - } -} - -+ (CPDictionary)menuBarAttributes -{ - return _CPMenuBarAttributes; -} - -+ (void)_setMenuBarIconImageAlphaValue:(float)anAlphaValue -{ - _CPMenuBarIconImageAlphaValue = anAlphaValue; - [_CPMenuBarSharedWindow setIconImageAlphaValue:anAlphaValue]; -} - -- (float)menuBarHeight -{ - if (self == [CPApp mainMenu]) - return MENUBAR_HEIGHT; - - return 0.0; -} - -+ (float)menuBarHeight -{ - return MENUBAR_HEIGHT; -} - -// Creating a CPMenu Object -/*! - Initializes the menu with a specified title. - @param aTile the menu title - @return the initialized menu -*/ -- (id)initWithTitle:(CPString)aTitle -{ - self = [super init]; - - if (self) - { - _title = aTitle; - _items = []; - - _autoenablesItems = YES; - _showsStateColumn = YES; - } - - return self; -} - -- (id)init -{ - return [self initWithTitle:@""]; -} - -// Setting Up Menu Commands -/*! - Inserts a menu item at the specified index. - @param aMenuItem the item to insert - @param anIndex the index in the menu to insert the item. -*/ -- (void)insertItem:(CPMenuItem)aMenuItem atIndex:(unsigned)anIndex -{ - var menu = [aMenuItem menu]; - - if (menu) - if (menu !== self) - [CPException raise:CPInternalInconsistencyException reason:@"Attempted to insert item into menu that was already in another menu."]; - else - return; - - [aMenuItem setMenu:self]; - [_items insertObject:aMenuItem atIndex:anIndex]; - - [[CPNotificationCenter defaultCenter] - postNotificationName:CPMenuDidAddItemNotification - object:self - userInfo:[CPDictionary dictionaryWithObject:anIndex forKey:@"CPMenuItemIndex"]]; - -} - -/*! - Creates and inserts a new menu item with the specified attributes. - @param aTitle the title of the menu item - @param anAction the action initiated when the user selects the item - @param aKeyEquivalent the keyboard shortcut for the item - @param anIndex the index location in the menu for the new item - @return the new menu item -*/ -- (CPMenuItem)insertItemWithTitle:(CPString)aTitle action:(SEL)anAction keyEquivalent:(CPString)aKeyEquivalent atIndex:(unsigned)anIndex -{ - var item = [[CPMenuItem alloc] initWithTitle:aTitle action:anAction keyEquivalent:aKeyEquivalent]; - - [self insertItem:item atIndex:anIndex]; - - return item; -} - -/*! - Adds a menu item at the end of the menu. - @param aMenuItem the menu item to add -*/ -- (void)addItem:(CPMenuItem)aMenuItem -{ - [self insertItem:aMenuItem atIndex:[_items count]]; -} - -/*! - Creates and adds a menu item with the specified attributes - at the end of the menu. - @param aTitle the title of the new menu item - @param anAction the action initiated when the user selects the item - @param aKeyEquivalent the keyboard shortcut for the menu item - @return the new menu item -*/ -- (CPMenuItem)addItemWithTitle:(CPString)aTitle action:(SEL)anAction keyEquivalent:(CPString)aKeyEquivalent -{ - return [self insertItemWithTitle:aTitle action:anAction keyEquivalent:aKeyEquivalent atIndex:[_items count]]; -} - -/*! - Removes the specified item from the menu - @param aMenuItem the item to remove -*/ -- (void)removeItem:(CPMenuItem)aMenuItem -{ - [self removeItemAtIndex:[_items indexOfObjectIdenticalTo:aMenuItem]]; -} - -/*! - Removes the item at the specified index from the menu - @param anIndex the index of the item to remove -*/ -- (void)removeItemAtIndex:(unsigned)anIndex -{ - if (anIndex < 0 || anIndex >= _items.length) - return; - - [_items[anIndex] setMenu:nil]; - [_items removeObjectAtIndex:anIndex]; - - [[CPNotificationCenter defaultCenter] - postNotificationName:CPMenuDidRemoveItemNotification - object:self - userInfo:[CPDictionary dictionaryWithObject:anIndex forKey:@"CPMenuItemIndex"]]; -} - -/*! - Called when a menu item has visually changed. - @param aMenuItem the item that changed -*/ -- (void)itemChanged:(CPMenuItem)aMenuItem -{ - if ([aMenuItem menu] != self) - return; - - [[CPNotificationCenter defaultCenter] - postNotificationName:CPMenuDidChangeItemNotification - object:self - userInfo:[CPDictionary dictionaryWithObject:[_items indexOfObjectIdenticalTo:aMenuItem] forKey:@"CPMenuItemIndex"]]; -} - -// Finding Menu Items -/*! - Returns the menu item with the specified tag - @param the tag of the desired menu item - @return the menu item or \c nil if a match was not found -*/ -- (CPMenuItem)itemWithTag:(int)aTag -{ - var index = [self indexOfItemWithTag:aTag]; - - if (index == CPNotFound) - return nil; - - return _items[index]; -} - -/*! - Returns the menu item with the specified title. - @param aTitle the title of the menu item - @return the menu item or \c nil if a match was not found -*/ -- (CPMenuItem)itemWithTitle:(CPString)aTitle -{ - var index = [self indexOfItemWithTitle:aTitle]; - - if (index == CPNotFound) - return nil; - - return _items[index]; -} - -/*! - Returns the menu item at the specified index - @param anIndex the index of the requested item -*/ -- (CPMenuItem)itemAtIndex:(int)anIndex -{ - return [_items objectAtIndex:anIndex]; -} - -/*! - Returns the number of menu items in the menu -*/ -- (unsigned)numberOfItems -{ - return [_items count]; -} - -/*! - Returns the array of menu items backing this menu -*/ -- (CPArray)itemArray -{ - return _items; -} - -// Finding Indices of Menu Items -/*! - Returns the index of the specified menu item - @param aMenuItem the item to find the index for - @return the item index or CPNotFound -*/ -- (int)indexOfItem:(CPMenuItem)aMenuItem -{ - if ([aMenuItem menu] != self) - return CPNotFound; - - return [_items indexOfObjectIdenticalTo:aMenuItem]; -} - -/*! - Returns the index of the item with the specified title. - @param aTitle the desired title to match - @return the index of the item or CPNotFound -*/ -- (int)indexOfItemWithTitle:(CPString)aTitle -{ - var index = 0, - count = _items.length; - - for (; index < count; ++index) - if ([_items[index] title] === aTitle) - return index; - - return CPNotFound; -} - -/*! - Returns the index of the item with the specified tag - @param aTag the desired tag to match - @return the index of the item or CPNotFound -*/ -- (int)indexOfItemWithTag:(int)aTag -{ - var index = 0, - count = _items.length; - - for (; index < count; ++index) - if ([_items[index] tag] == aTag) - return index; - - return CPNotFound; -} - -/*! - Returns the index of the item with the specified target and action. - @param aTarget the target of the desired menu item - @param anAction the action of the desired menu item - @return the index of the item or CPNotFound -*/ -- (int)indexOfItemWithTarget:(id)aTarget andAction:(SEL)anAction -{ - var index = 0, - count = _items.length; - - for (; index < count; ++index) - { - var item = _items[index]; - - if ([item target] == aTarget && (!anAction || [item action] == anAction)) - return index; - } - - return CPNotFound; -} - -/*! - Returns the index of the menu item with the specified represented object. - @param anObject the represented object of the desired item - @return the index of the item or CPNotFound -*/ -- (int)indexOfItemWithRepresentedObject:(id)anObject -{ - var index = 0, - count = _items.length; - - for (; index < count; ++index) - if ([[_items[index] representedObject] isEqual:anObject]) - return index; - - return CPNotFound; -} - -/*! - Returns the index of the item with the specified submenu. - @param the submenu of the desired menu item - @return the index of the item or CPNotFound -*/ -- (int)indexOfItemWithSubmenu:(CPMenu)aMenu -{ - var index = 0, - count = _items.length; - - for (; index < count; ++index) - if ([_items[index] submenu] == aMenu) - return index; - - return CPNotFound; -} - -// Managing Submenus -/*! - Sets a submenu for a menu item - @param aMenu the submenu - @param aMenuItem the menu item to set the submenu on -*/ -- (void)setSubmenu:(CPMenu)aMenu forItem:(CPMenuItem)aMenuItem -{ - [aMenuItem setTarget:aMenuItem]; - [aMenuItem setAction:@selector(submenuAction:)]; - - [aMenuItem setSubmenu:aMenu]; -} - -/*! - The action method of menu items that open submenus. - The default implementation does nothing, but it may - be subclassed to provide different behavior. - @param aSender the object that sent the message -*/ -- (void)submenuAction:(id)aSender -{ - -} - -/*! - Returns the attaced menu, or \c nil if there isn't one. -*/ -- (CPMenu)attachedMenu -{ - return _attachedMenu; -} - -/*! - Returns \c YES if the menu is attached to another menu. -*/ -- (BOOL)isAttached -{ - return _isAttached; -} - -/*! - Not yet implemented -*/ -- (CGPoint)locationOfSubmenu:(CPMenu)aMenu -{ - // FIXME: IMPLEMENT. -} - -/*! - Returns the super menu or \c nil if there is none. -*/ -- (CPMenu)supermenu -{ - return _supermenu; -} - -/*! - Sets the super menu. - @param aMenu the new super menu -*/ -- (void)setSupermenu:(CPMenu)aMenu -{ - _supermenu = aMenu; -} - -/*! - If there are two instances of this menu visible, return \c NO. - Otherwise, return \c YES if we are a detached menu and visible. -*/ -- (BOOL)isTornOff -{ - return !_supermenu /* || offscreen(?) */ || self == [CPApp mainMenu]; -} - -// Enabling and Disabling Menu Items -/*! - Sets whether the menu automatically enables menu items. - @param aFlag \c YES sets the menu to automatically enable items. -*/ -- (void)setAutoenablesItems:(BOOL)aFlag -{ - _autoenablesItems = aFlag; -} - -/*! - Returns \c YES if the menu auto enables items. -*/ -- (BOOL)autoenablesItems -{ - return _autoenablesItems; -} - -/*! - Not implemented. -*/ -- (void)update -{ - -} - -// Managing the Title -/*! - Sets the menu title. - @param the new title -*/ -- (void)setTitle:(CPString)aTitle -{ - _title = aTitle; -} - -/*! - Returns the menu title -*/ -- (CPString)title -{ - return _title; -} - -// - -+ (void)popUpContextMenu:(CPMenu)aMenu withEvent:(CPEvent)anEvent forView:(CPView)aView -{ - [self popUpContextMenu:aMenu withEvent:anEvent forView:aView withFont:nil]; -} - -+ (void)popUpContextMenu:(CPMenu)aMenu withEvent:(CPEvent)anEvent forView:(CPView)aView withFont:(CPFont)aFont -{ - [self _popUpContextMenu:aMenu withEvent:anEvent forView:aView withFont:aFont forMenuBar:NO]; -} - -+ (void)_popUpContextMenu:(CPMenu)aMenu withEvent:(CPEvent)anEvent forView:(CPView)aView withFont:(CPFont)aFont forMenuBar:(BOOL)isForMenuBar -{ - var delegate = [aMenu delegate]; - - if ([delegate respondsToSelector:@selector(menuWillOpen:)]) - [delegate menuWillOpen:aMenu]; - - if (!aFont) - aFont = [CPFont systemFontOfSize:12.0]; - - var theWindow = [aView window], - menuWindow = [_CPMenuWindow menuWindowWithMenu:aMenu font:aFont]; - - [menuWindow setDelegate:self]; - [menuWindow setBackgroundStyle:isForMenuBar ? _CPMenuWindowMenuBarBackgroundStyle : _CPMenuWindowPopUpBackgroundStyle]; - - [menuWindow setFrameOrigin:[[anEvent window] convertBaseToGlobal:[anEvent locationInWindow]]]; - - [menuWindow orderFront:self]; - [menuWindow beginTrackingWithEvent:anEvent sessionDelegate:self didEndSelector:@selector(_menuWindowDidFinishTracking:highlightedItem:)]; -} - -+ (void)_menuWindowDidFinishTracking:(_CPMenuWindow)aMenuWindow highlightedItem:(CPMenuItem)aMenuItem -{ - var menu = [aMenuWindow menu]; - - [_CPMenuWindow poolMenuWindow:aMenuWindow]; - - if([aMenuItem isEnabled]) - [CPApp sendAction:[aMenuItem action] to:[aMenuItem target] from:aMenuItem]; -} - -// Managing Display of State Column -/*! - Sets whether to show the state column - @param shouldShowStateColumn \c YES shows the state column -*/ -- (void)setShowsStateColumn:(BOOL)shouldShowStateColumn -{ - _showsStateColumn = shouldShowStateColumn; -} - -/*! - Returns \c YES if the menu shows the state column -*/ -- (BOOL)showsStateColumn -{ - return _showsStateColumn; -} - -// Handling Highlighting -/*! - Returns the currently highlighted menu item. - @return the highlighted menu item or \c nil if no item is currently highlighted -*/ -- (CPMenuItem)highlightedItem -{ - return _highlightedIndex >= 0 ? _items[_highlightedIndex] : nil; -} - -// Managing the Delegate - -- (void)setDelegate:(id)aDelegate -{ - _delegate = aDelegate; -} - -- (id)delegate -{ - return _delegate; -} - -// Handling Tracking -/*! - Cancels tracking. -*/ -- (void)cancelTracking -{ - [[CPRunLoop currentRunLoop] performSelector:@selector(_fireCancelTrackingEvent) target:self argument:nil order:0 modes:[CPDefaultRunLoopMode]]; -} - -- (void)_fireCancelTrackingEvent -{ - [CPApp sendEvent:[CPEvent - otherEventWithType:CPAppKitDefined - location:_CGPointMakeZero() - modifierFlags:0 - timestamp:0 - windowNumber:0 - context:0 - subtype:0 - data1:0 - data2:0]]; -} - -/* @ignore */ -- (void)_setMenuWindow:(_CPMenuWindow)aMenuWindow -{ - _menuWindow = aMenuWindow; -} - -/*! - Initiates the action of the menu item that - has a keyboard shortcut equivalent to \c anEvent - @param anEvent the keyboard event - @return \c YES if it was handled. -*/ -- (BOOL)performKeyEquivalent:(CPEvent)anEvent -{ - if (_autoenablesItems) - [self update]; - - var index = 0, - count = _items.length, - characters = [anEvent charactersIgnoringModifiers], - modifierFlags = [anEvent modifierFlags]; - - for(; index < count; ++index) - { - var item = _items[index], - modifierMask = [item keyEquivalentModifierMask]; - - if ((modifierFlags & (CPShiftKeyMask | CPAlternateKeyMask | CPCommandKeyMask | CPControlKeyMask)) == modifierMask && - [characters caseInsensitiveCompare:[item keyEquivalent]] == CPOrderedSame) - { - if ([item isEnabled]) - [self performActionForItemAtIndex:index]; - else - { - //beep? - } - - return YES; - } - - if ([[item submenu] performKeyEquivalent:anEvent]) - return YES; - } - - return NO; -} - -// Simulating Mouse Clicks -/*! - Sends the action of the menu item at the specified index. - @param anIndex the index of the item -*/ -- (void)performActionForItemAtIndex:(unsigned)anIndex -{ - var item = _items[anIndex]; - - [CPApp sendAction:[item action] to:[item target] from:item]; -} - -// -/* - @ignore -*/ -- (BOOL)_itemIsHighlighted:(CPMenuItem)aMenuItem -{ - return _items[_highlightedIndex] == aMenuItem; -} - -/* - @ignore -*/ -- (void)_highlightItemAtIndex:(int)anIndex -{ - var previousHighlightedIndex = _highlightedIndex; - - _highlightedIndex = anIndex; - - if (previousHighlightedIndex != CPNotFound) - [[_items[previousHighlightedIndex] _menuItemView] highlight:NO]; - - if (_highlightedIndex != CPNotFound) - [[_items[_highlightedIndex] _menuItemView] highlight:YES]; -} - -- (void)_setMenuName:(CPString)aName -{ - if (_name === aName) - return; - - _name = aName; - - if (_name === @"CPMainMenu") - [CPApp setMainMenu:self]; -} - -- (CPString)_menuName -{ - return _name; -} - -- (void)awakeFromCib -{ - if (_name === @"_CPMainMenu") - { - [self _setMenuName:@"CPMainMenu"]; - [CPMenu setMenuBarVisible:YES]; - } -} - -- (void)_menuWithName:(CPString)aName -{ - if (aName === _name) - return self; - - for (var i = 0, count = [_items count]; i < count; i++) - { - var menu = [[_items[i] submenu] _menuWithName:aName]; - - if (menu) - return menu; - } - - return nil; -} - -@end - - -var CPMenuTitleKey = @"CPMenuTitleKey", - CPMenuNameKey = @"CPMenuNameKey", - CPMenuItemsKey = @"CPMenuItemsKey", - CPMenuShowsStateColumnKey = @"CPMenuShowsStateColumnKey"; - -@implementation CPMenu (CPCoding) - -/*! - Initializes the menu with data from the specified coder. - @param aCoder the coder from which to read the data - @return the initialized menu -*/ -- (id)initWithCoder:(CPCoder)aCoder -{ - self = [super init]; - - if (self) - { - _title = [aCoder decodeObjectForKey:CPMenuTitleKey]; - _items = [aCoder decodeObjectForKey:CPMenuItemsKey]; - - [self _setMenuName:[aCoder decodeObjectForKey:CPMenuNameKey]]; - - _showsStateColumn = ![aCoder containsValueForKey:CPMenuShowsStateColumnKey] || [aCoder decodeBoolForKey:CPMenuShowsStateColumnKey]; - } - - return self; -} - -/*! - Encodes the data of the menu into a coder - @param aCoder the coder to which the data will be written -*/ -- (void)encodeWithCoder:(CPCoder)aCoder -{ - [aCoder encodeObject:_title forKey:CPMenuTitleKey]; - - if (_name) - [aCoder encodeObject:_name forKey:CPMenuNameKey]; - - [aCoder encodeObject:_items forKey:CPMenuItemsKey]; - - if (!_showsStateColumn) - [aCoder encodeBool:_showsStateColumn forKey:CPMenuShowsStateColumnKey]; -} - -@end - -var _CPMenuWindowPool = [], - _CPMenuWindowPoolCapacity = 5, - - _CPMenuWindowBackgroundColors = [], - - _CPMenuWindowScrollingStateUp = -1, - _CPMenuWindowScrollingStateDown = 1, - _CPMenuWindowScrollingStateNone = 0; - -_CPMenuWindowMenuBarBackgroundStyle = 0; -_CPMenuWindowPopUpBackgroundStyle = 1; -_CPMenuWindowAttachedMenuBackgroundStyle = 2; - -var STICKY_TIME_INTERVAL = 500, - - TOP_MARGIN = 5.0, - LEFT_MARGIN = 1.0, - RIGHT_MARGIN = 1.0, - BOTTOM_MARGIN = 5.0, - - SCROLL_INDICATOR_HEIGHT = 16.0; - -/* - @ignore -*/ -@implementation _CPMenuWindow : CPWindow -{ - _CPMenuView _menuView; - CPClipView _menuClipView; - CPView _lastMouseOverMenuView; - - CPImageView _moreAboveView; - CPImageView _moreBelowView; - - id _sessionDelegate; - SEL _didEndSelector; - - CPTimeInterval _startTime; - int _scrollingState; - CGPoint _lastGlobalLocation; - - BOOL _isShowingTopScrollIndicator; - BOOL _isShowingBottomScrollIndicator; - BOOL _trackingCanceled; - - CGRect _unconstrainedFrame; -} - -+ (id)menuWindowWithMenu:(CPMenu)aMenu font:(CPFont)aFont -{ - var menuWindow = nil; - - if (_CPMenuWindowPool.length) - menuWindow = _CPMenuWindowPool.pop(); - else - menuWindow = [[_CPMenuWindow alloc] init]; - - [menuWindow setFont:aFont]; - [menuWindow setMenu:aMenu]; - - return menuWindow; -} - -+ (void)poolMenuWindow:(_CPMenuWindow)aMenuWindow -{ - if (!aMenuWindow || _CPMenuWindowPool.length >= _CPMenuWindowPoolCapacity) - return; - - _CPMenuWindowPool.push(aMenuWindow); -} - -+ (void)initialize -{ - if (self != [_CPMenuWindow class]) - return; - - var bundle = [CPBundle bundleForClass:self]; - - _CPMenuWindowMoreAboveImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowMoreAbove.png"] size:CGSizeMake(38.0, 18.0)]; - _CPMenuWindowMoreBelowImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowMoreBelow.png"] size:CGSizeMake(38.0, 18.0)]; -} - -- (id)init -{ - self = [super initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessWindowMask]; - - if (self) - { - [self setLevel:CPPopUpMenuWindowLevel]; - [self setHasShadow:YES]; - [self setShadowStyle:CPMenuWindowShadowStyle]; - [self setAcceptsMouseMovedEvents:YES]; - - _unconstrainedFrame = CGRectMakeZero(); - - var contentView = [self contentView]; - - _menuView = [[_CPMenuView alloc] initWithFrame:CGRectMakeZero()]; - - _menuClipView = [[CPClipView alloc] initWithFrame:CGRectMake(LEFT_MARGIN, TOP_MARGIN, 0.0, 0.0)]; - [_menuClipView setDocumentView:_menuView]; - - [contentView addSubview:_menuClipView]; - - _moreAboveView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()]; - - [_moreAboveView setImage:_CPMenuWindowMoreAboveImage]; - [_moreAboveView setFrameSize:[_CPMenuWindowMoreAboveImage size]]; - - [contentView addSubview:_moreAboveView]; - - _moreBelowView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()]; - - [_moreBelowView setImage:_CPMenuWindowMoreBelowImage]; - [_moreBelowView setFrameSize:[_CPMenuWindowMoreBelowImage size]]; - - [contentView addSubview:_moreBelowView]; - - [self setShadowStyle:CPWindowShadowStyleMenu]; - } - - return self; -} - -- (CGFloat)overlapOffsetWidth -{ - return LEFT_MARGIN; -} - -- (void)setFont:(CPFont)aFont -{ - [_menuView setFont:aFont]; -} - -+ (CPColor)backgroundColorForBackgroundStyle:(_CPMenuWindowBackgroundStyle)aBackgroundStyle -{ - var color = _CPMenuWindowBackgroundColors[aBackgroundStyle]; - - if (!color) - { - var bundle = [CPBundle bundleForClass:[self class]]; - - if (aBackgroundStyle == _CPMenuWindowPopUpBackgroundStyle) - color = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices: - [ - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded0.png"] size:CGSizeMake(4.0, 4.0)], - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow1.png"] size:CGSizeMake(1.0, 4.0)], - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded2.png"] size:CGSizeMake(4.0, 4.0)], - - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow3.png"] size:CGSizeMake(4.0, 1.0)], - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow4.png"] size:CGSizeMake(1.0, 1.0)], - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow5.png"] size:CGSizeMake(4.0, 1.0)], - - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded6.png"] size:CGSizeMake(4.0, 4.0)], - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow7.png"] size:CGSizeMake(1.0, 4.0)], - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded8.png"] size:CGSizeMake(4.0, 4.0)] - ]]]; - - else if (aBackgroundStyle == _CPMenuWindowMenuBarBackgroundStyle) - color = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices: - [ - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow3.png"] size:CGSizeMake(4.0, 0.0)], - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow4.png"] size:CGSizeMake(1.0, 0.0)], - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow5.png"] size:CGSizeMake(4.0, 0.0)], - - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow3.png"] size:CGSizeMake(4.0, 1.0)], - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow4.png"] size:CGSizeMake(1.0, 1.0)], - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow5.png"] size:CGSizeMake(4.0, 1.0)], - - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded6.png"] size:CGSizeMake(4.0, 4.0)], - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow7.png"] size:CGSizeMake(1.0, 4.0)], - [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded8.png"] size:CGSizeMake(4.0, 4.0)] - ]]]; - - _CPMenuWindowBackgroundColors[aBackgroundStyle] = color; - } - - return color; -} - -- (void)setBackgroundStyle:(_CPMenuWindowBackgroundStyle)aBackgroundStyle -{ - [self setBackgroundColor:[[self class] backgroundColorForBackgroundStyle:aBackgroundStyle]]; -} - -- (void)setMenu:(CPMenu)aMenu -{ - [aMenu _setMenuWindow:self]; - [_menuView setMenu:aMenu]; - - var menuViewSize = [_menuView frame].size; - - [self setFrameSize:CGSizeMake(LEFT_MARGIN + menuViewSize.width + RIGHT_MARGIN, TOP_MARGIN + menuViewSize.height + BOTTOM_MARGIN)]; - - [_menuView scrollPoint:CGPointMake(0.0, 0.0)]; - [_menuClipView setFrame:CGRectMake(LEFT_MARGIN, TOP_MARGIN, menuViewSize.width, menuViewSize.height)]; -} - -- (void)setMinWidth:(float)aWidth -{ - var size = [self frame].size; - - [self setFrameSize:CGSizeMake(MAX(size.width, aWidth), size.height)]; -} - -- (CGPoint)rectForItemAtIndex:(int)anIndex -{ - return [_menuView convertRect:[_menuView rectForItemAtIndex:anIndex] toView:nil]; -} - -- (void)orderFront:(id)aSender -{ - [self constrainToScreen]; - - [super orderFront:aSender]; -} - -- (void)constrainToScreen -{ - // FIXME: There are integral window issues with platform windows. - // FIXME: This gets called far too often. - _unconstrainedFrame = CGRectMakeCopy([self frame]); - - var isBrowser = [CPPlatform isBrowser], - visibleFrame = CGRectInset(isBrowser ? [[self platformWindow] contentBounds] : [[self screen] visibleFrame], 5.0, 5.0), - constrainedFrame = CGRectIntersection(_unconstrainedFrame, visibleFrame); - - // We don't want to simply intersect the visible frame and the unconstrained frame. - // We should be allowing as much of the width to fit as possible (pushing back and forward). - constrainedFrame.origin.x = CGRectGetMinX(_unconstrainedFrame); - constrainedFrame.size.width = CGRectGetWidth(_unconstrainedFrame); - - if (CGRectGetWidth(constrainedFrame) > CGRectGetWidth(visibleFrame)) - constrainedFrame.size.width = CGRectGetWidth(visibleFrame); - - if (CGRectGetMaxX(constrainedFrame) > CGRectGetMaxX(visibleFrame)) - constrainedFrame.origin.x -= CGRectGetMaxX(constrainedFrame) - CGRectGetMaxX(visibleFrame); - - if (CGRectGetMinX(constrainedFrame) < CGRectGetMinX(visibleFrame)) - constrainedFrame.origin.x = CGRectGetMinX(visibleFrame); - - // This needs to happen before changing the frame. - var menuViewOrigin = [self convertBaseToGlobal:CGPointMake(LEFT_MARGIN, TOP_MARGIN)]; - - [super setFrame:constrainedFrame]; - - var moreAbove = menuViewOrigin.y < CGRectGetMinY(constrainedFrame) + TOP_MARGIN, - moreBelow = menuViewOrigin.y + CGRectGetHeight([_menuView frame]) > CGRectGetMaxY(constrainedFrame) - BOTTOM_MARGIN, - - topMargin = TOP_MARGIN, - bottomMargin = BOTTOM_MARGIN, - - contentView = [self contentView], - bounds = [contentView bounds]; - - if (moreAbove) - { - topMargin += SCROLL_INDICATOR_HEIGHT; - - var frame = [_moreAboveView frame]; - - [_moreAboveView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth(frame)) / 2.0, (TOP_MARGIN + SCROLL_INDICATOR_HEIGHT - CGRectGetHeight(frame)) / 2.0)]; - } - - [_moreAboveView setHidden:!moreAbove]; - - if (moreBelow) - { - bottomMargin += SCROLL_INDICATOR_HEIGHT; - - [_moreBelowView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth([_moreBelowView frame])) / 2.0, CGRectGetHeight(bounds) - SCROLL_INDICATOR_HEIGHT - BOTTOM_MARGIN)]; - } - - [_moreBelowView setHidden:!moreBelow]; - - var clipFrame = CGRectMake(LEFT_MARGIN, topMargin, CGRectGetWidth(constrainedFrame) - LEFT_MARGIN - RIGHT_MARGIN, CGRectGetHeight(constrainedFrame) - topMargin - bottomMargin) - - [_menuClipView setFrame:clipFrame]; - [_menuView setFrameSize:CGSizeMake(CGRectGetWidth(clipFrame), CGRectGetHeight([_menuView frame]))]; - - [_menuView scrollPoint:CGPointMake(0.0, [self convertBaseToGlobal:clipFrame.origin].y - menuViewOrigin.y)]; -} - -- (void)beginTrackingWithEvent:(CPEvent)anEvent sessionDelegate:(id)aSessionDelegate didEndSelector:(SEL)aDidEndSelector -{ - CPApp._activeMenu = [_menuView menu]; - - _startTime = [anEvent timestamp];//new Date(); - _scrollingState = _CPMenuWindowScrollingStateNone; - _trackingCanceled = NO; - - _sessionDelegate = aSessionDelegate; - _didEndSelector = aDidEndSelector; - - [self trackEvent:anEvent]; -} - -- (void)trackEvent:(CPEvent)anEvent -{ - var type = [anEvent type], - menu = [_menuView menu]; - - // Close Menu Event. - if (type === CPAppKitDefined) - { - // Stop all periodic events at this point. - [CPEvent stopPeriodicEvents]; - - var highlightedItem = [[_menuView menu] highlightedItem]; - - [menu _highlightItemAtIndex:CPNotFound]; - - [self orderOut:self]; - - var delegate = [menu delegate]; - - if ([delegate respondsToSelector:@selector(menuDidClose:)]) - [delegate menuDidClose:menu]; - - if (_sessionDelegate && _didEndSelector) - objj_msgSend(_sessionDelegate, _didEndSelector, self, highlightedItem); - - [[CPNotificationCenter defaultCenter] - postNotificationName:CPMenuDidEndTrackingNotification - object:menu]; - - // Clear these now so its faster next time around. - [_menuView setMenu:nil]; - - CPApp._activeMenu = nil; - - return; - } - - var theWindow = [anEvent window], - globalLocation = theWindow ? [theWindow convertBaseToGlobal:[anEvent locationInWindow]] : [anEvent locationInWindow]; - - if (type === CPPeriodic) - { - var constrainedBounds = CGRectInset([CPPlatform isBrowser] ? [[self platformWindow] contentBounds] : [[self screen] visibleFrame], 5.0, 5.0); - - if (_scrollingState == _CPMenuWindowScrollingStateUp) - { - if (CGRectGetMinY(_unconstrainedFrame) < CGRectGetMinY(constrainedBounds)) - _unconstrainedFrame.origin.y += 10; - } - else if (_scrollingState == _CPMenuWindowScrollingStateDown) - if (CGRectGetMaxY(_unconstrainedFrame) > CGRectGetHeight(constrainedBounds)) - _unconstrainedFrame.origin.y -= 10; - - [self setFrame:_unconstrainedFrame]; - [self constrainToScreen]; - - globalLocation = _lastGlobalLocation; - } - - _lastGlobalLocation = globalLocation; - - var menuLocation = [self convertGlobalToBase:globalLocation], - activeItemIndex = [_menuView itemIndexAtPoint:[_menuView convertPoint:menuLocation fromView:nil]], - mouseOverMenuView = [[menu itemAtIndex:activeItemIndex] view]; - - // If we're over a custom menu view... - if (mouseOverMenuView) - { - if (!_lastMouseOverMenuView) - [menu _highlightItemAtIndex:CPNotFound]; - - if (_lastMouseOverMenuView != mouseOverMenuView) - { - [mouseOverMenuView mouseExited:anEvent]; - // FIXME: Possibly multiple of these? - [_lastMouseOverMenuView mouseEntered:anEvent]; - - _lastMouseOverMenuView = mouseOverMenuView; - } - - [self sendEvent:[CPEvent mouseEventWithType:type location:menuLocation modifierFlags:[anEvent modifierFlags] - timestamp:[anEvent timestamp] windowNumber:[self windowNumber] context:nil - eventNumber:0 clickCount:[anEvent clickCount] pressure:[anEvent pressure]]]; - } - else - { - if (_lastMouseOverMenuView) - { - [_lastMouseOverMenuView mouseExited:anEvent]; - _lastMouseOverMenuView = nil; - } - - [menu _highlightItemAtIndex:[_menuView itemIndexAtPoint:[_menuView convertPoint:[self convertGlobalToBase:globalLocation] fromView:nil]]]; - - if (type == CPMouseMoved || type == CPLeftMouseDragged || type == CPLeftMouseDown) - { - var frame = [self frame], - oldScrollingState = _scrollingState; - - _scrollingState = _CPMenuWindowScrollingStateNone; - - // If we're at or above of the top scroll indicator... - if (globalLocation.y < CGRectGetMinY(frame) + TOP_MARGIN + SCROLL_INDICATOR_HEIGHT) - _scrollingState = _CPMenuWindowScrollingStateUp; - - // If we're at or below the bottom scroll indicator... - else if (globalLocation.y > CGRectGetMaxY(frame) - BOTTOM_MARGIN - SCROLL_INDICATOR_HEIGHT) - _scrollingState = _CPMenuWindowScrollingStateDown; - - if (_scrollingState != oldScrollingState) - - if (_scrollingState == _CPMenuWindowScrollingStateNone) - [CPEvent stopPeriodicEvents]; - - else if (oldScrollingState == _CPMenuWindowScrollingStateNone) - [CPEvent startPeriodicEventsAfterDelay:0.0 withPeriod:0.04]; - } - else if (type == CPLeftMouseUp && ([anEvent timestamp] - _startTime > STICKY_TIME_INTERVAL)) - [menu cancelTracking]; - } - - [CPApp setTarget:self selector:@selector(trackEvent:) forNextEventMatchingMask:CPPeriodicMask | CPMouseMovedMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask | CPAppKitDefinedMask untilDate:nil inMode:nil dequeue:YES]; -} - -@end - -/* - @ignore -*/ -@implementation _CPMenuView : CPView -{ - CPArray _menuItemViews; - CPArray _visibleMenuItemInfos; - - CPFont _font; -} - -/*- (id)initWithFrame:(CGRect)aFrame -{ - self = [super initWithFrame:aFrame]; - - if (self) - [self setAutoresizingMask:CPViewWidthSizable]; - - return self; -}*/ - -- (void)setFont:(CPFont)aFont -{ - _font = aFont; -} - -- (CGRect)rectForItemAtIndex:(int)anIndex -{ - return [_menuItemViews[anIndex == CPNotFound ? 0 : anIndex] frame]; -} - -- (int)itemIndexAtPoint:(CGPoint)aPoint -{ - var x = aPoint.x, - bounds = [self bounds]; - - if (x < CGRectGetMinX(bounds) || x > CGRectGetMaxX(bounds)) - return CPNotFound; - - var y = aPoint.y, - low = 0, - high = _visibleMenuItemInfos.length - 1; - - while (low <= high) - { - var middle = FLOOR(low + (high - low) / 2), - info = _visibleMenuItemInfos[middle] - frame = [info.view frame]; - - if (y < CGRectGetMinY(frame)) - high = middle - 1; - - else if (y > CGRectGetMaxY(frame)) - low = middle + 1; - - else - return info.index; - } - - return CPNotFound; -} - -- (void)setMenu:(CPMenu)aMenu -{ - [super setMenu:aMenu]; - - [_menuItemViews makeObjectsPerformSelector:@selector(removeFromSuperview)]; - - _menuItemViews = []; - _visibleMenuItemInfos = []; - - var menu = [self menu]; - - if (!menu) - return; - - var items = [menu itemArray], - index = 0, - count = [items count], - maxWidth = 0, - y = 0, - showsStateColumn = [menu showsStateColumn]; - - for (; index < count; ++index) - { - var item = items[index], - view = [item _menuItemView]; - - _menuItemViews.push(view); - - if ([item isHidden]) - continue; - - _visibleMenuItemInfos.push({ view:view, index:index }); - - [view setFont:_font]; - [view setShowsStateColumn:showsStateColumn]; - [view synchronizeWithMenuItem]; - - [view setFrameOrigin:CGPointMake(0.0, y)]; - - [self addSubview:view]; - - var size = [view minSize], - width = size.width; - - if (maxWidth < width) - maxWidth = width; - - y += size.height; - } - - for (index = 0; index < count; ++index) - { - var view = _menuItemViews[index]; - - [view setFrameSize:CGSizeMake(maxWidth, CGRectGetHeight([view frame]))]; - } - - [self setAutoresizesSubviews:NO]; - [self setFrameSize:CGSizeMake(maxWidth, y)]; - [self setAutoresizesSubviews:YES]; -} - -@end - -var MENUBAR_HEIGHT = 29.0, - MENUBAR_MARGIN = 10.0, - MENUBAR_LEFT_MARGIN = 10.0, - MENUBAR_RIGHT_MARGIN = 10.0; - -var _CPMenuBarWindowBackgroundColor = nil, - _CPMenuBarWindowFont = nil; - -@implementation _CPMenuBarWindow : CPPanel -{ - CPMenu _menu; - CPView _highlightView; - CPArray _menuItemViews; - - CPMenuItem _trackingMenuItem; - - CPImageView _iconImageView; - CPTextField _titleField; - - CPColor _textColor; - CPColor _titleColor; - - CPColor _textShadowColor; - CPColor _titleShadowColor; - - CPColor _highlightColor; - CPColor _highlightTextColor; - CPColor _highlightTextShadowColor; -} - -+ (void)initialize -{ - if (self != [_CPMenuBarWindow class]) - return; - - var bundle = [CPBundle bundleForClass:self]; - - _CPMenuBarWindowFont = [CPFont systemFontOfSize:11.0]; -} - -- (id)init -{ - // This only shows up in browser land, so don't bother calculating metrics in desktop. - var contentRect = [[CPPlatformWindow primaryPlatformWindow] contentBounds]; - - contentRect.size.height = MENUBAR_HEIGHT; - - self = [super initWithContentRect:contentRect styleMask:CPBorderlessWindowMask]; - - if (self) - { - // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/39-dont-allow-windows-to-go-above-menubar - [self setLevel:-1];//CPTornOffMenuWindowLevel]; - [self setAutoresizingMask:CPWindowWidthSizable]; - - var contentView = [self contentView]; - - [contentView setAutoresizesSubviews:NO]; - - [self setBecomesKeyOnlyIfNeeded:YES]; - - // - _iconImageView = [[CPImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 16.0, 16.0)]; - - [contentView addSubview:_iconImageView]; - - _titleField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()]; - - [_titleField setFont:[CPFont boldSystemFontOfSize:12.0]]; - [_titleField setAlignment:CPCenterTextAlignment]; - [_titleField setTextShadowOffset:CGSizeMake(0, 1)]; - - [contentView addSubview:_titleField]; - } - - return self; -} - -- (void)setTitle:(CPString)aTitle -{ -#if PLATFORM(DOM) - var bundleName = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPBundleName"]; - - if (![bundleName length]) - document.title = aTitle; - else if ([aTitle length]) - document.title = aTitle + @" - " + bundleName; - else - document.title = bundleName; -#endif - - [_titleField setStringValue:aTitle]; - [_titleField sizeToFit]; - - [self tile]; -} - -- (void)setIconImage:(CPImage)anImage -{ - [_iconImageView setImage:anImage]; - [_iconImageView setHidden:anImage == nil]; - - [self tile]; -} - -- (void)setIconImageAlphaValue:(float)anAlphaValue -{ - [_iconImageView setAlphaValue:anAlphaValue]; -} - -- (void)setColor:(CPColor)aColor -{ - if (!aColor) - { - if (!_CPMenuBarWindowBackgroundColor) - _CPMenuBarWindowBackgroundColor = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[_CPMenuBarWindow class]] pathForResource:@"_CPMenuBarWindow/_CPMenuBarWindowBackground.png"] size:CGSizeMake(1.0, 18.0)]]; - - [[self contentView] setBackgroundColor:_CPMenuBarWindowBackgroundColor]; - } - else - [[self contentView] setBackgroundColor:aColor]; -} - -- (void)setTextColor:(CPColor)aColor -{ - if (_textColor == aColor) - return; - - _textColor = aColor; - - [_menuItemViews makeObjectsPerformSelector:@selector(setTextColor:) withObject:_textColor]; -} - -- (void)setTitleColor:(CPColor)aColor -{ - if (_titleColor == aColor) - return; - - _titleColor = aColor; - - [_titleField setTextColor:aColor ? aColor : [CPColor blackColor]]; -} - -- (void)setTextShadowColor:(CPColor)aColor -{ - if (_textShadowColor == aColor) - return; - - _textShadowColor = aColor; - - [_menuItemViews makeObjectsPerformSelector:@selector(setTextShadowColor:) withObject:_textShadowColor]; -} - -- (void)setTitleShadowColor:(CPColor)aColor -{ - if (_titleShadowColor == aColor) - return; - - _titleShadowColor = aColor; - - [_titleField setTextShadowColor:aColor ? aColor : [CPColor whiteColor]]; -} - -- (void)setHighlightColor:(CPColor)aColor -{ - if (_highlightColor == aColor) - return; - - _highlightColor = aColor; -} - -- (void)setHighlightTextColor:(CPColor)aColor -{ - if (_highlightTextColor == aColor) - return; - - _highlightTextColor = aColor; - - [_menuItemViews makeObjectsPerformSelector:@selector(setActivateColor:) withObject:_highlightTextColor]; -} - -- (void)setHighlightTextShadowColor:(CPColor)aColor -{ - if (_highlightTextShadowColor == aColor) - return; - - _highlightTextShadowColor = aColor; - - [_menuItemViews makeObjectsPerformSelector:@selector(setActivateShadowColor:) withObject:_highlightTextShadowColor]; -} - -- (void)setMenu:(CPMenu)aMenu -{ - if (_menu == aMenu) - return; - - var defaultCenter = [CPNotificationCenter defaultCenter]; - - if (_menu) - { - [defaultCenter - removeObserver:self - name:CPMenuDidAddItemNotification - object:_menu]; - - [defaultCenter - removeObserver:self - name:CPMenuDidChangeItemNotification - object:_menu]; - - [defaultCenter - removeObserver:self - name:CPMenuDidRemoveItemNotification - object:_menu]; - - var items = [_menu itemArray], - count = items.length; - - while (count--) - [[items[count] _menuItemView] removeFromSuperview]; - } - - _menu = aMenu; - - if (_menu) - { - [defaultCenter - addObserver:self - selector:@selector(menuDidAddItem:) - name:CPMenuDidAddItemNotification - object:_menu]; - - [defaultCenter - addObserver:self - selector:@selector(menuDidChangeItem:) - name:CPMenuDidChangeItemNotification - object:_menu]; - - [defaultCenter - addObserver:self - selector:@selector(menuDidRemoveItem:) - name:CPMenuDidRemoveItemNotification - object:_menu]; - } - - _menuItemViews = []; - - var contentView = [self contentView], - items = [_menu itemArray], - count = items.length; - - for (index = 0; index < count; ++index) - { - var item = items[index], - menuItemView = [item _menuItemView]; - - _menuItemViews.push(menuItemView); - - [menuItemView setShowsStateColumn:NO]; - [menuItemView setBelongsToMenuBar:YES]; - [menuItemView setFont:_CPMenuBarWindowFont]; - [menuItemView setTextColor:_textColor]; - [menuItemView setHidden:[item isHidden]]; - - [menuItemView synchronizeWithMenuItem]; - - [contentView addSubview:menuItemView]; - } - - [self tile]; -} - -- (void)menuDidChangeItem:(CPNotification)aNotification -{ - var menuItem = [_menu itemAtIndex:[[aNotification userInfo] objectForKey:@"CPMenuItemIndex"]], - menuItemView = [menuItem _menuItemView]; - - [menuItemView setHidden:[menuItem isHidden]]; - [menuItemView synchronizeWithMenuItem]; - - [self tile]; -} - -- (void)menuDidAddItem:(CPNotification)aNotification -{ - var index = [[aNotification userInfo] objectForKey:@"CPMenuItemIndex"], - menuItem = [_menu itemAtIndex:index], - menuItemView = [menuItem _menuItemView]; - - [_menuItemViews insertObject:menuItemView atIndex:index]; - - [menuItemView setShowsStateColumn:NO]; - [menuItemView setBelongsToMenuBar:YES]; - [menuItemView setFont:_CPMenuBarWindowFont]; - [menuItemView setTextColor:_textColor]; - [menuItemView setHidden:[menuItem isHidden]]; - - [menuItemView synchronizeWithMenuItem]; - - [[self contentView] addSubview:menuItemView]; - - [self tile]; -} - -- (void)menuDidRemoveItem:(CPNotification)aNotification -{ - var index = [[aNotification userInfo] objectForKey:@"CPMenuItemIndex"], - menuItemView = [_menuItemViews objectAtIndex:index]; - - [_menuItemViews removeObjectAtIndex:index]; - - [menuItemView removeFromSuperview]; - - [self tile]; -} - -- (CGRect)frameForMenuItem:(CPMenuItem)aMenuItem -{ - var frame = [[aMenuItem _menuItemView] frame]; - - frame.origin.x -= 5.0; - frame.origin.y = 0; - frame.size.width += 10.0; - frame.size.height = MENUBAR_HEIGHT; - - return frame; -} - -- (CPMenuItem)menuItemAtPoint:(CGPoint)aPoint -{ - var items = [_menu itemArray], - count = items.length; - - while (count--) - { - var item = items[count]; - - if ([item isHidden] || [item isSeparatorItem]) - continue; - - if (CGRectContainsPoint([self frameForMenuItem:item], aPoint)) - return item; - } - - return nil; -} - -- (void)mouseDown:(CPEvent)anEvent -{ - _trackingMenuItem = [self menuItemAtPoint:[anEvent locationInWindow]]; - - if (![_trackingMenuItem isEnabled]) - return; - - if ([[_trackingMenuItem _menuItemView] eventOnSubmenu:anEvent]) - return [self showMenu:anEvent]; - - if ([_trackingMenuItem isEnabled]) - [self trackEvent:anEvent]; -} - -- (void)trackEvent:(CPEvent)anEvent -{ - var type = [anEvent type]; - - if (type === CPPeriodic) - return [self showMenu:anEvent]; - - var frame = [self frameForMenuItem:_trackingMenuItem], - menuItemView = [_trackingMenuItem _menuItemView], - onMenuItemView = CGRectContainsPoint(frame, [anEvent locationInWindow]); - - if (type == CPLeftMouseDown) - { - if ([_trackingMenuItem submenu] != nil) - { - var action = [_trackingMenuItem action]; - - // If the item has a submenu, but not direct action, a.k.a. a "pure" menu, simply show the menu. - // FIXME: (?) should we use submenuAction: or not? - if (!action || action === @selector(submenuAction:)) - return [self showMenu:anEvent]; - - // If this is a hybrid button/menu, show it in a bit... - [CPEvent startPeriodicEventsAfterDelay:0.0 withPeriod:0.5]; - } - - [menuItemView highlight:onMenuItemView]; - } - - else if (type == CPLeftMouseDragged) - { - if (!onMenuItemView && [_trackingMenuItem submenu]) - return [self showMenu:anEvent]; - - [menuItemView highlight:onMenuItemView]; - } - - else /*if (type == CPLeftMouseUp)*/ - { - [CPEvent stopPeriodicEvents]; - - [menuItemView highlight:NO]; - - if (onMenuItemView) - [CPApp sendAction:[_trackingMenuItem action] to:[_trackingMenuItem target] from:_trackingMenuItem]; - - return; - } - - [CPApp setTarget:self selector:@selector(trackEvent:) forNextEventMatchingMask:CPPeriodicMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES]; -} - -- (void)showMenu:(CPEvent)anEvent -{ - [CPEvent stopPeriodicEvents]; - - var frame = [self frameForMenuItem:_trackingMenuItem], - menuItemView = [_trackingMenuItem _menuItemView]; - - if (!_highlightView) - { - _highlightView = [[CPView alloc] initWithFrame:frame]; - - [_highlightView setBackgroundColor:_highlightColor ? _highlightColor : [CPColor colorWithRed:95.0/255.0 green:131.0/255.0 blue:185.0/255.0 alpha:1.0]]; - } - else - [_highlightView setFrame:frame]; - - [[self contentView] addSubview:_highlightView positioned:CPWindowBelow relativeTo:menuItemView]; - - [menuItemView activate:YES]; - - var submenu = [_trackingMenuItem submenu]; - - [[CPNotificationCenter defaultCenter] - addObserver:self - selector:@selector(menuDidEndTracking:) - name:CPMenuDidEndTrackingNotification - object:submenu]; - - [CPMenu _popUpContextMenu:submenu - withEvent:[CPEvent mouseEventWithType:CPLeftMouseDown location:CGPointMake(CGRectGetMinX(frame), CGRectGetMaxY(frame)) - modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] windowNumber:[self windowNumber] - context:nil eventNumber:0 clickCount:[anEvent clickCount] pressure:[anEvent pressure]] - forView:[self contentView] - withFont:nil - forMenuBar:YES]; -} - -- (void)menuDidEndTracking:(CPNotification)aNotification -{ - [_highlightView removeFromSuperview]; - - [[_trackingMenuItem _menuItemView] activate:NO]; - - [[CPNotificationCenter defaultCenter] - removeObserver:self - name:CPMenuDidEndTrackingNotification - object:[aNotification object]]; -} - -- (void)tile -{ - var items = [_menu itemArray], - index = 0, - count = items.length, - - x = MENUBAR_LEFT_MARGIN, - y = 0.0, - isLeftAligned = YES; - - for (; index < count; ++index) - { - var item = items[index]; - - if ([item isSeparatorItem]) - { - x = CGRectGetWidth([self frame]) - MENUBAR_RIGHT_MARGIN; - isLeftAligned = NO; - - continue; - } - - if ([item isHidden]) - continue; - - var menuItemView = [item _menuItemView], - frame = [menuItemView frame]; - - if (isLeftAligned) - { - [menuItemView setFrameOrigin:CGPointMake(x, (MENUBAR_HEIGHT - 1.0 - CGRectGetHeight(frame)) / 2.0)]; - - x += CGRectGetWidth([menuItemView frame]) + MENUBAR_MARGIN; - } - else - { - [menuItemView setFrameOrigin:CGPointMake(x - CGRectGetWidth(frame), (MENUBAR_HEIGHT - 1.0 - CGRectGetHeight(frame)) / 2.0)]; - - x = CGRectGetMinX([menuItemView frame]) - MENUBAR_MARGIN; - } - } - - var bounds = [[self contentView] bounds], - titleFrame = [_titleField frame]; - - if ([_iconImageView isHidden]) - [_titleField setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth(titleFrame)) / 2.0, (CGRectGetHeight(bounds) - CGRectGetHeight(titleFrame)) / 2.0)]; - else - { - var iconFrame = [_iconImageView frame], - iconWidth = CGRectGetWidth(iconFrame), - totalWidth = iconWidth + CGRectGetWidth(titleFrame); - - [_iconImageView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - totalWidth) / 2.0, (CGRectGetHeight(bounds) - CGRectGetHeight(iconFrame)) / 2.0)]; - [_titleField setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - totalWidth) / 2.0 + iconWidth, (CGRectGetHeight(bounds) - CGRectGetHeight(titleFrame)) / 2.0)]; - } -} - -- (void)setFrame:(CGRect)aRect display:(BOOL)shouldDisplay animate:(BOOL)shouldAnimate -{ - var size = [self frame].size; - - [super setFrame:aRect display:shouldDisplay animate:shouldAnimate]; - - if (!_CGSizeEqualToSize(size, aRect.size)) - [self tile]; -} - -@end diff --git a/AppKit/CPMenu/CPMenu.j b/AppKit/CPMenu/CPMenu.j new file mode 100644 index 000000000..5a7e8bed2 --- /dev/null +++ b/AppKit/CPMenu/CPMenu.j @@ -0,0 +1,937 @@ +/* + * CPMenu.j + * AppKit + * + * Created by Francisco Tolmasky. + * Copyright 2008, 280 North, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import +@import +@import +@import + +@import "CPApplication.j" +@import "CPClipView.j" +@import "CPMenuItem.j" +@import "CPPanel.j" + +#include "../CoreGraphics/CGGeometry.h" +#include "../Platform/Platform.h" + + +CPMenuDidAddItemNotification = @"CPMenuDidAddItemNotification"; +CPMenuDidChangeItemNotification = @"CPMenuDidChangeItemNotification"; +CPMenuDidRemoveItemNotification = @"CPMenuDidRemoveItemNotification"; + +CPMenuDidEndTrackingNotification = @"CPMenuDidEndTrackingNotification"; + +var MENUBAR_HEIGHT = 19.0; + +var _CPMenuBarVisible = NO, + _CPMenuBarTitle = @"", + _CPMenuBarIconImage = nil, + _CPMenuBarIconImageAlphaValue = 1.0, + _CPMenuBarAttributes = nil, + _CPMenuBarSharedWindow = nil; + +/*! + @ingroup appkit + @class CPMenu + + Menus provide the user with a list of actions and/or submenus. Submenus themselves are full fledged menus + and so a heirarchical structure appears. +*/ +@implementation CPMenu : CPObject +{ + CPMenu _supermenu; + + CPString _title; + CPString _name; + + CPMutableArray _items; + CPMenu _attachedMenu; + + BOOL _autoenablesItems; + BOOL _showsStateColumn; + + id _delegate; + + CPMenuItem _highlightedIndex; + _CPMenuWindow _menuWindow; +} + +// Managing the Menu Bar + ++ (void)initialize +{ + [[self class] setMenuBarAttributes:[CPDictionary dictionary]]; +} + ++ (BOOL)menuBarVisible +{ + return _CPMenuBarVisible; +} + ++ (void)setMenuBarVisible:(BOOL)menuBarShouldBeVisible +{ + if (_CPMenuBarVisible == menuBarShouldBeVisible) + return; + + _CPMenuBarVisible = menuBarShouldBeVisible; + + if ([CPPlatform supportsNativeMainMenu]) + return; + + if (menuBarShouldBeVisible) + { + if (!_CPMenuBarSharedWindow) + _CPMenuBarSharedWindow = [[_CPMenuBarWindow alloc] init]; + + [_CPMenuBarSharedWindow setMenu:[CPApp mainMenu]]; + + [_CPMenuBarSharedWindow setTitle:_CPMenuBarTitle]; + [_CPMenuBarSharedWindow setIconImage:_CPMenuBarIconImage]; + [_CPMenuBarSharedWindow setIconImageAlphaValue:_CPMenuBarIconImageAlphaValue]; + + [_CPMenuBarSharedWindow setColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarBackgroundColor"]]; + [_CPMenuBarSharedWindow setTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextColor"]]; + [_CPMenuBarSharedWindow setTitleColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTitleColor"]]; + [_CPMenuBarSharedWindow setTextShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextShadowColor"]]; + [_CPMenuBarSharedWindow setTitleShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTitleShadowColor"]]; + [_CPMenuBarSharedWindow setHighlightColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightColor"]]; + [_CPMenuBarSharedWindow setHighlightTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightTextColor"]]; + [_CPMenuBarSharedWindow setHighlightTextShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightTextShadowColor"]]; + + [_CPMenuBarSharedWindow orderFront:self]; + } + else + [_CPMenuBarSharedWindow orderOut:self]; + +// FIXME: There must be a better way to do this. +#if PLATFORM(DOM) + [[CPPlatformWindow primaryPlatformWindow] resizeEvent:nil]; +#endif +} + ++ (void)setMenuBarTitle:(CPString)aTitle +{ + _CPMenuBarTitle = aTitle; + [_CPMenuBarSharedWindow setTitle:_CPMenuBarTitle]; +} + ++ (CPString)menuBarTitle +{ + return _CPMenuBarTitle; +} + ++ (void)setMenuBarIconImage:(CPImage)anImage +{ + _CPMenuBarImage = anImage; + [_CPMenuBarSharedWindow setIconImage:anImage]; +} + ++ (CPImage)menuBarIconImage +{ + return _CPMenuBarImage; +} + + ++ (void)setMenuBarAttributes:(CPDictionary)attributes +{ + if (_CPMenuBarAttributes == attributes) + return; + + _CPMenuBarAttributes = [attributes copy]; + + var textColor = [attributes objectForKey:@"CPMenuBarTextColor"], + titleColor = [attributes objectForKey:@"CPMenuBarTitleColor"], + textShadowColor = [attributes objectForKey:@"CPMenuBarTextShadowColor"], + titleShadowColor = [attributes objectForKey:@"CPMenuBarTitleShadowColor"], + highlightColor = [attributes objectForKey:@"CPMenuBarHighlightColor"], + highlightTextColor = [attributes objectForKey:@"CPMenuBarHighlightTextColor"], + highlightTextShadowColor = [attributes objectForKey:@"CPMenuBarHighlightTextShadowColor"]; + + if (!textColor && titleColor) + [_CPMenuBarAttributes setObject:titleColor forKey:@"CPMenuBarTextColor"]; + + else if (textColor && !titleColor) + [_CPMenuBarAttributes setObject:textColor forKey:@"CPMenuBarTitleColor"]; + + else if (!textColor && !titleColor) + { + [_CPMenuBarAttributes setObject:[CPColor colorWithRed:0.051 green:0.2 blue:0.275 alpha:1.0] forKey:@"CPMenuBarTextColor"]; + [_CPMenuBarAttributes setObject:[CPColor colorWithRed:0.051 green:0.2 blue:0.275 alpha:1.0] forKey:@"CPMenuBarTitleColor"]; + } + + if (!textShadowColor && titleShadowColor) + [_CPMenuBarAttributes setObject:titleShadowColor forKey:@"CPMenuBarTextShadowColor"]; + + else if (textShadowColor && !titleShadowColor) + [_CPMenuBarAttributes setObject:textShadowColor forKey:@"CPMenuBarTitleShadowColor"]; + + else if (!textShadowColor && !titleShadowColor) + { + [_CPMenuBarAttributes setObject:[CPColor whiteColor] forKey:@"CPMenuBarTextShadowColor"]; + [_CPMenuBarAttributes setObject:[CPColor whiteColor] forKey:@"CPMenuBarTitleShadowColor"]; + } + + if (!highlightColor) + [_CPMenuBarAttributes setObject:[CPColor colorWithCalibratedRed:94.0/255.0 green:130.0/255.0 blue:186.0/255.0 alpha:1.0] forKey:@"CPMenuBarHighlightColor"]; + + if (!highlightTextColor) + [_CPMenuBarAttributes setObject:[CPColor whiteColor] forKey:@"CPMenuBarHighlightTextColor"]; + + if (!highlightTextShadowColor) + [_CPMenuBarAttributes setObject:[CPColor blackColor] forKey:@"CPMenuBarHighlightTextShadowColor"]; + + if (_CPMenuBarSharedWindow) + { + [_CPMenuBarSharedWindow setColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarBackgroundColor"]]; + [_CPMenuBarSharedWindow setTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextColor"]]; + [_CPMenuBarSharedWindow setTitleColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTitleColor"]]; + [_CPMenuBarSharedWindow setTextShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextShadowColor"]]; + [_CPMenuBarSharedWindow setTitleShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTitleShadowColor"]]; + [_CPMenuBarSharedWindow setHighlightColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightColor"]]; + [_CPMenuBarSharedWindow setHighlightTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightTextColor"]]; + [_CPMenuBarSharedWindow setHighlightTextShadowColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarHighlightTextShadowColor"]]; + } +} + ++ (CPDictionary)menuBarAttributes +{ + return _CPMenuBarAttributes; +} + ++ (void)_setMenuBarIconImageAlphaValue:(float)anAlphaValue +{ + _CPMenuBarIconImageAlphaValue = anAlphaValue; + [_CPMenuBarSharedWindow setIconImageAlphaValue:anAlphaValue]; +} + +- (float)menuBarHeight +{ + if (self == [CPApp mainMenu]) + return MENUBAR_HEIGHT; + + return 0.0; +} + ++ (float)menuBarHeight +{ + return MENUBAR_HEIGHT; +} + +// Creating a CPMenu Object +/*! + Initializes the menu with a specified title. + @param aTile the menu title + @return the initialized menu +*/ +- (id)initWithTitle:(CPString)aTitle +{ + self = [super init]; + + if (self) + { + _title = aTitle; + _items = []; + + _autoenablesItems = YES; + _showsStateColumn = YES; + } + + return self; +} + +- (id)init +{ + return [self initWithTitle:@""]; +} + +// Setting Up Menu Commands +/*! + Inserts a menu item at the specified index. + @param aMenuItem the item to insert + @param anIndex the index in the menu to insert the item. +*/ +- (void)insertItem:(CPMenuItem)aMenuItem atIndex:(unsigned)anIndex +{ + var menu = [aMenuItem menu]; + + if (menu) + if (menu !== self) + [CPException raise:CPInternalInconsistencyException reason:@"Attempted to insert item into menu that was already in another menu."]; + else + return; + + [aMenuItem setMenu:self]; + [_items insertObject:aMenuItem atIndex:anIndex]; + + [[CPNotificationCenter defaultCenter] + postNotificationName:CPMenuDidAddItemNotification + object:self + userInfo:[CPDictionary dictionaryWithObject:anIndex forKey:@"CPMenuItemIndex"]]; + +} + +/*! + Creates and inserts a new menu item with the specified attributes. + @param aTitle the title of the menu item + @param anAction the action initiated when the user selects the item + @param aKeyEquivalent the keyboard shortcut for the item + @param anIndex the index location in the menu for the new item + @return the new menu item +*/ +- (CPMenuItem)insertItemWithTitle:(CPString)aTitle action:(SEL)anAction keyEquivalent:(CPString)aKeyEquivalent atIndex:(unsigned)anIndex +{ + var item = [[CPMenuItem alloc] initWithTitle:aTitle action:anAction keyEquivalent:aKeyEquivalent]; + + [self insertItem:item atIndex:anIndex]; + + return item; +} + +/*! + Adds a menu item at the end of the menu. + @param aMenuItem the menu item to add +*/ +- (void)addItem:(CPMenuItem)aMenuItem +{ + [self insertItem:aMenuItem atIndex:[_items count]]; +} + +/*! + Creates and adds a menu item with the specified attributes + at the end of the menu. + @param aTitle the title of the new menu item + @param anAction the action initiated when the user selects the item + @param aKeyEquivalent the keyboard shortcut for the menu item + @return the new menu item +*/ +- (CPMenuItem)addItemWithTitle:(CPString)aTitle action:(SEL)anAction keyEquivalent:(CPString)aKeyEquivalent +{ + return [self insertItemWithTitle:aTitle action:anAction keyEquivalent:aKeyEquivalent atIndex:[_items count]]; +} + +/*! + Removes the specified item from the menu + @param aMenuItem the item to remove +*/ +- (void)removeItem:(CPMenuItem)aMenuItem +{ + [self removeItemAtIndex:[_items indexOfObjectIdenticalTo:aMenuItem]]; +} + +/*! + Removes the item at the specified index from the menu + @param anIndex the index of the item to remove +*/ +- (void)removeItemAtIndex:(unsigned)anIndex +{ + if (anIndex < 0 || anIndex >= _items.length) + return; + + [_items[anIndex] setMenu:nil]; + [_items removeObjectAtIndex:anIndex]; + + [[CPNotificationCenter defaultCenter] + postNotificationName:CPMenuDidRemoveItemNotification + object:self + userInfo:[CPDictionary dictionaryWithObject:anIndex forKey:@"CPMenuItemIndex"]]; +} + +/*! + Called when a menu item has visually changed. + @param aMenuItem the item that changed +*/ +- (void)itemChanged:(CPMenuItem)aMenuItem +{ + if ([aMenuItem menu] != self) + return; + + [[CPNotificationCenter defaultCenter] + postNotificationName:CPMenuDidChangeItemNotification + object:self + userInfo:[CPDictionary dictionaryWithObject:[_items indexOfObjectIdenticalTo:aMenuItem] forKey:@"CPMenuItemIndex"]]; +} + +// Finding Menu Items +/*! + Returns the menu item with the specified tag + @param the tag of the desired menu item + @return the menu item or \c nil if a match was not found +*/ +- (CPMenuItem)itemWithTag:(int)aTag +{ + var index = [self indexOfItemWithTag:aTag]; + + if (index == CPNotFound) + return nil; + + return _items[index]; +} + +/*! + Returns the menu item with the specified title. + @param aTitle the title of the menu item + @return the menu item or \c nil if a match was not found +*/ +- (CPMenuItem)itemWithTitle:(CPString)aTitle +{ + var index = [self indexOfItemWithTitle:aTitle]; + + if (index == CPNotFound) + return nil; + + return _items[index]; +} + +/*! + Returns the menu item at the specified index + @param anIndex the index of the requested item +*/ +- (CPMenuItem)itemAtIndex:(int)anIndex +{ + return [_items objectAtIndex:anIndex]; +} + +/*! + Returns the number of menu items in the menu +*/ +- (unsigned)numberOfItems +{ + return [_items count]; +} + +/*! + Returns the array of menu items backing this menu +*/ +- (CPArray)itemArray +{ + return _items; +} + +// Finding Indices of Menu Items +/*! + Returns the index of the specified menu item + @param aMenuItem the item to find the index for + @return the item index or CPNotFound +*/ +- (int)indexOfItem:(CPMenuItem)aMenuItem +{ + if ([aMenuItem menu] != self) + return CPNotFound; + + return [_items indexOfObjectIdenticalTo:aMenuItem]; +} + +/*! + Returns the index of the item with the specified title. + @param aTitle the desired title to match + @return the index of the item or CPNotFound +*/ +- (int)indexOfItemWithTitle:(CPString)aTitle +{ + var index = 0, + count = _items.length; + + for (; index < count; ++index) + if ([_items[index] title] === aTitle) + return index; + + return CPNotFound; +} + +/*! + Returns the index of the item with the specified tag + @param aTag the desired tag to match + @return the index of the item or CPNotFound +*/ +- (int)indexOfItemWithTag:(int)aTag +{ + var index = 0, + count = _items.length; + + for (; index < count; ++index) + if ([_items[index] tag] == aTag) + return index; + + return CPNotFound; +} + +/*! + Returns the index of the item with the specified target and action. + @param aTarget the target of the desired menu item + @param anAction the action of the desired menu item + @return the index of the item or CPNotFound +*/ +- (int)indexOfItemWithTarget:(id)aTarget andAction:(SEL)anAction +{ + var index = 0, + count = _items.length; + + for (; index < count; ++index) + { + var item = _items[index]; + + if ([item target] == aTarget && (!anAction || [item action] == anAction)) + return index; + } + + return CPNotFound; +} + +/*! + Returns the index of the menu item with the specified represented object. + @param anObject the represented object of the desired item + @return the index of the item or CPNotFound +*/ +- (int)indexOfItemWithRepresentedObject:(id)anObject +{ + var index = 0, + count = _items.length; + + for (; index < count; ++index) + if ([[_items[index] representedObject] isEqual:anObject]) + return index; + + return CPNotFound; +} + +/*! + Returns the index of the item with the specified submenu. + @param the submenu of the desired menu item + @return the index of the item or CPNotFound +*/ +- (int)indexOfItemWithSubmenu:(CPMenu)aMenu +{ + var index = 0, + count = _items.length; + + for (; index < count; ++index) + if ([_items[index] submenu] == aMenu) + return index; + + return CPNotFound; +} + +// Managing Submenus +/*! + Sets a submenu for a menu item + @param aMenu the submenu + @param aMenuItem the menu item to set the submenu on +*/ +- (void)setSubmenu:(CPMenu)aMenu forItem:(CPMenuItem)aMenuItem +{ + [aMenuItem setTarget:aMenuItem]; + [aMenuItem setAction:@selector(submenuAction:)]; + + [aMenuItem setSubmenu:aMenu]; +} + +/*! + The action method of menu items that open submenus. + The default implementation does nothing, but it may + be subclassed to provide different behavior. + @param aSender the object that sent the message +*/ +- (void)submenuAction:(id)aSender +{ + +} + +/*! + Returns the attaced menu, or \c nil if there isn't one. +*/ +- (CPMenu)attachedMenu +{ + return _attachedMenu; +} + +/*! + Returns \c YES if the menu is attached to another menu. +*/ +- (BOOL)isAttached +{ + return _isAttached; +} + +/*! + Not yet implemented +*/ +- (CGPoint)locationOfSubmenu:(CPMenu)aMenu +{ + // FIXME: IMPLEMENT. +} + +/*! + Returns the super menu or \c nil if there is none. +*/ +- (CPMenu)supermenu +{ + return _supermenu; +} + +/*! + Sets the super menu. + @param aMenu the new super menu +*/ +- (void)setSupermenu:(CPMenu)aMenu +{ + _supermenu = aMenu; +} + +/*! + If there are two instances of this menu visible, return \c NO. + Otherwise, return \c YES if we are a detached menu and visible. +*/ +- (BOOL)isTornOff +{ + return !_supermenu /* || offscreen(?) */ || self == [CPApp mainMenu]; +} + +// Enabling and Disabling Menu Items +/*! + Sets whether the menu automatically enables menu items. + @param aFlag \c YES sets the menu to automatically enable items. +*/ +- (void)setAutoenablesItems:(BOOL)aFlag +{ + _autoenablesItems = aFlag; +} + +/*! + Returns \c YES if the menu auto enables items. +*/ +- (BOOL)autoenablesItems +{ + return _autoenablesItems; +} + +/*! + Not implemented. +*/ +- (void)update +{ + +} + +// Managing the Title +/*! + Sets the menu title. + @param the new title +*/ +- (void)setTitle:(CPString)aTitle +{ + _title = aTitle; +} + +/*! + Returns the menu title +*/ +- (CPString)title +{ + return _title; +} + +// + ++ (void)popUpContextMenu:(CPMenu)aMenu withEvent:(CPEvent)anEvent forView:(CPView)aView +{ + [self popUpContextMenu:aMenu withEvent:anEvent forView:aView withFont:nil]; +} + ++ (void)popUpContextMenu:(CPMenu)aMenu withEvent:(CPEvent)anEvent forView:(CPView)aView withFont:(CPFont)aFont +{ + [self _popUpContextMenu:aMenu withEvent:anEvent forView:aView withFont:aFont forMenuBar:NO]; +} + ++ (void)_popUpContextMenu:(CPMenu)aMenu withEvent:(CPEvent)anEvent forView:(CPView)aView withFont:(CPFont)aFont forMenuBar:(BOOL)isForMenuBar +{ + var delegate = [aMenu delegate]; + + if ([delegate respondsToSelector:@selector(menuWillOpen:)]) + [delegate menuWillOpen:aMenu]; + + if (!aFont) + aFont = [CPFont systemFontOfSize:12.0]; + + var theWindow = [aView window], + menuWindow = [_CPMenuWindow menuWindowWithMenu:aMenu font:aFont]; + + [menuWindow setDelegate:self]; + [menuWindow setBackgroundStyle:isForMenuBar ? _CPMenuWindowMenuBarBackgroundStyle : _CPMenuWindowPopUpBackgroundStyle]; + + [menuWindow setFrameOrigin:[[anEvent window] convertBaseToGlobal:[anEvent locationInWindow]]]; + + [menuWindow orderFront:self]; + [menuWindow beginTrackingWithEvent:anEvent sessionDelegate:self didEndSelector:@selector(_menuWindowDidFinishTracking:highlightedItem:)]; +} + ++ (void)_menuWindowDidFinishTracking:(_CPMenuWindow)aMenuWindow highlightedItem:(CPMenuItem)aMenuItem +{ + var menu = [aMenuWindow menu]; + + [_CPMenuWindow poolMenuWindow:aMenuWindow]; + + if([aMenuItem isEnabled]) + [CPApp sendAction:[aMenuItem action] to:[aMenuItem target] from:aMenuItem]; +} + +// Managing Display of State Column +/*! + Sets whether to show the state column + @param shouldShowStateColumn \c YES shows the state column +*/ +- (void)setShowsStateColumn:(BOOL)shouldShowStateColumn +{ + _showsStateColumn = shouldShowStateColumn; +} + +/*! + Returns \c YES if the menu shows the state column +*/ +- (BOOL)showsStateColumn +{ + return _showsStateColumn; +} + +// Handling Highlighting +/*! + Returns the currently highlighted menu item. + @return the highlighted menu item or \c nil if no item is currently highlighted +*/ +- (CPMenuItem)highlightedItem +{ + return _highlightedIndex >= 0 ? _items[_highlightedIndex] : nil; +} + +// Managing the Delegate + +- (void)setDelegate:(id)aDelegate +{ + _delegate = aDelegate; +} + +- (id)delegate +{ + return _delegate; +} + +// Handling Tracking +/*! + Cancels tracking. +*/ +- (void)cancelTracking +{ + [[CPRunLoop currentRunLoop] performSelector:@selector(_fireCancelTrackingEvent) target:self argument:nil order:0 modes:[CPDefaultRunLoopMode]]; +} + +- (void)_fireCancelTrackingEvent +{ + [CPApp sendEvent:[CPEvent + otherEventWithType:CPAppKitDefined + location:_CGPointMakeZero() + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:0 + subtype:0 + data1:0 + data2:0]]; +} + +/* @ignore */ +- (void)_setMenuWindow:(_CPMenuWindow)aMenuWindow +{ + _menuWindow = aMenuWindow; +} + +/*! + Initiates the action of the menu item that + has a keyboard shortcut equivalent to \c anEvent + @param anEvent the keyboard event + @return \c YES if it was handled. +*/ +- (BOOL)performKeyEquivalent:(CPEvent)anEvent +{ + if (_autoenablesItems) + [self update]; + + var index = 0, + count = _items.length, + characters = [anEvent charactersIgnoringModifiers], + modifierFlags = [anEvent modifierFlags]; + + for(; index < count; ++index) + { + var item = _items[index], + modifierMask = [item keyEquivalentModifierMask]; + + if ((modifierFlags & (CPShiftKeyMask | CPAlternateKeyMask | CPCommandKeyMask | CPControlKeyMask)) == modifierMask && + [characters caseInsensitiveCompare:[item keyEquivalent]] == CPOrderedSame) + { + if ([item isEnabled]) + [self performActionForItemAtIndex:index]; + else + { + //beep? + } + + return YES; + } + + if ([[item submenu] performKeyEquivalent:anEvent]) + return YES; + } + + return NO; +} + +// Simulating Mouse Clicks +/*! + Sends the action of the menu item at the specified index. + @param anIndex the index of the item +*/ +- (void)performActionForItemAtIndex:(unsigned)anIndex +{ + var item = _items[anIndex]; + + [CPApp sendAction:[item action] to:[item target] from:item]; +} + +// +/* + @ignore +*/ +- (BOOL)_itemIsHighlighted:(CPMenuItem)aMenuItem +{ + return _items[_highlightedIndex] == aMenuItem; +} + +/* + @ignore +*/ +- (void)_highlightItemAtIndex:(int)anIndex +{ + var previousHighlightedIndex = _highlightedIndex; + + _highlightedIndex = anIndex; + + if (previousHighlightedIndex != CPNotFound) + [[_items[previousHighlightedIndex] _menuItemView] highlight:NO]; + + if (_highlightedIndex != CPNotFound) + [[_items[_highlightedIndex] _menuItemView] highlight:YES]; +} + +- (void)_setMenuName:(CPString)aName +{ + if (_name === aName) + return; + + _name = aName; + + if (_name === @"CPMainMenu") + [CPApp setMainMenu:self]; +} + +- (CPString)_menuName +{ + return _name; +} + +- (void)awakeFromCib +{ + if (_name === @"_CPMainMenu") + { + [self _setMenuName:@"CPMainMenu"]; + [CPMenu setMenuBarVisible:YES]; + } +} + +- (void)_menuWithName:(CPString)aName +{ + if (aName === _name) + return self; + + for (var i = 0, count = [_items count]; i < count; i++) + { + var menu = [[_items[i] submenu] _menuWithName:aName]; + + if (menu) + return menu; + } + + return nil; +} + +@end + + +var CPMenuTitleKey = @"CPMenuTitleKey", + CPMenuNameKey = @"CPMenuNameKey", + CPMenuItemsKey = @"CPMenuItemsKey", + CPMenuShowsStateColumnKey = @"CPMenuShowsStateColumnKey"; + +@implementation CPMenu (CPCoding) + +/*! + Initializes the menu with data from the specified coder. + @param aCoder the coder from which to read the data + @return the initialized menu +*/ +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [super init]; + + if (self) + { + _title = [aCoder decodeObjectForKey:CPMenuTitleKey]; + _items = [aCoder decodeObjectForKey:CPMenuItemsKey]; + + [self _setMenuName:[aCoder decodeObjectForKey:CPMenuNameKey]]; + + _showsStateColumn = ![aCoder containsValueForKey:CPMenuShowsStateColumnKey] || [aCoder decodeBoolForKey:CPMenuShowsStateColumnKey]; + } + + return self; +} + +/*! + Encodes the data of the menu into a coder + @param aCoder the coder to which the data will be written +*/ +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [aCoder encodeObject:_title forKey:CPMenuTitleKey]; + + if (_name) + [aCoder encodeObject:_name forKey:CPMenuNameKey]; + + [aCoder encodeObject:_items forKey:CPMenuItemsKey]; + + if (!_showsStateColumn) + [aCoder encodeBool:_showsStateColumn forKey:CPMenuShowsStateColumnKey]; +} + +@end + +@import "_CPMenuBarWindow.j" +@import "_CPMenuWindow.j" + diff --git a/AppKit/CPMenu/_CPMenuBarWindow.j b/AppKit/CPMenu/_CPMenuBarWindow.j new file mode 100644 index 000000000..84372fe50 --- /dev/null +++ b/AppKit/CPMenu/_CPMenuBarWindow.j @@ -0,0 +1,642 @@ + +#include "../CoreGraphics/CGGeometry.h" + +@import "_CPMenuWindow.j" + + +var MENUBAR_HEIGHT = 29.0, + MENUBAR_MARGIN = 10.0, + MENUBAR_LEFT_MARGIN = 10.0, + MENUBAR_RIGHT_MARGIN = 10.0; + +var _CPMenuBarWindowBackgroundColor = nil, + _CPMenuBarWindowFont = nil; + +@implementation _CPMenuBarWindow : CPPanel +{ + CPMenu _menu; + CPView _highlightView; + CPArray _menuItemViews; + + CPMenuItem _trackingMenuItem; + + CPImageView _iconImageView; + CPTextField _titleField; + + CPColor _textColor; + CPColor _titleColor; + + CPColor _textShadowColor; + CPColor _titleShadowColor; + + CPColor _highlightColor; + CPColor _highlightTextColor; + CPColor _highlightTextShadowColor; +} + ++ (void)initialize +{ + if (self != [_CPMenuBarWindow class]) + return; + + var bundle = [CPBundle bundleForClass:self]; + + _CPMenuBarWindowFont = [CPFont systemFontOfSize:11.0]; +} + +- (id)init +{ + // This only shows up in browser land, so don't bother calculating metrics in desktop. + var contentRect = [[CPPlatformWindow primaryPlatformWindow] contentBounds]; + + contentRect.size.height = MENUBAR_HEIGHT; + + self = [super initWithContentRect:contentRect styleMask:CPBorderlessWindowMask]; + + if (self) + { + // FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/39-dont-allow-windows-to-go-above-menubar + [self setLevel:-1];//CPTornOffMenuWindowLevel]; + [self setAutoresizingMask:CPWindowWidthSizable]; + + var contentView = [self contentView]; + + [contentView setAutoresizesSubviews:NO]; + + [self setBecomesKeyOnlyIfNeeded:YES]; + + // + _iconImageView = [[CPImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 16.0, 16.0)]; + + [contentView addSubview:_iconImageView]; + + _titleField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()]; + + [_titleField setFont:[CPFont boldSystemFontOfSize:12.0]]; + [_titleField setAlignment:CPCenterTextAlignment]; + [_titleField setTextShadowOffset:CGSizeMake(0, 1)]; + + [contentView addSubview:_titleField]; + } + + return self; +} + +- (void)setTitle:(CPString)aTitle +{ +#if PLATFORM(DOM) + var bundleName = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPBundleName"]; + + if (![bundleName length]) + document.title = aTitle; + else if ([aTitle length]) + document.title = aTitle + @" - " + bundleName; + else + document.title = bundleName; +#endif + + [_titleField setStringValue:aTitle]; + [_titleField sizeToFit]; + + [self tile]; +} + +- (void)setIconImage:(CPImage)anImage +{ + [_iconImageView setImage:anImage]; + [_iconImageView setHidden:anImage == nil]; + + [self tile]; +} + +- (void)setIconImageAlphaValue:(float)anAlphaValue +{ + [_iconImageView setAlphaValue:anAlphaValue]; +} + +- (void)setColor:(CPColor)aColor +{ + if (!aColor) + { + if (!_CPMenuBarWindowBackgroundColor) + _CPMenuBarWindowBackgroundColor = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[_CPMenuBarWindow class]] pathForResource:@"_CPMenuBarWindow/_CPMenuBarWindowBackground.png"] size:CGSizeMake(1.0, 18.0)]]; + + [[self contentView] setBackgroundColor:_CPMenuBarWindowBackgroundColor]; + } + else + [[self contentView] setBackgroundColor:aColor]; +} + +- (void)setTextColor:(CPColor)aColor +{ + if (_textColor == aColor) + return; + + _textColor = aColor; + + [_menuItemViews makeObjectsPerformSelector:@selector(setTextColor:) withObject:_textColor]; +} + +- (void)setTitleColor:(CPColor)aColor +{ + if (_titleColor == aColor) + return; + + _titleColor = aColor; + + [_titleField setTextColor:aColor ? aColor : [CPColor blackColor]]; +} + +- (void)setTextShadowColor:(CPColor)aColor +{ + if (_textShadowColor == aColor) + return; + + _textShadowColor = aColor; + + [_menuItemViews makeObjectsPerformSelector:@selector(setTextShadowColor:) withObject:_textShadowColor]; +} + +- (void)setTitleShadowColor:(CPColor)aColor +{ + if (_titleShadowColor == aColor) + return; + + _titleShadowColor = aColor; + + [_titleField setTextShadowColor:aColor ? aColor : [CPColor whiteColor]]; +} + +- (void)setHighlightColor:(CPColor)aColor +{ + if (_highlightColor == aColor) + return; + + _highlightColor = aColor; +} + +- (void)setHighlightTextColor:(CPColor)aColor +{ + if (_highlightTextColor == aColor) + return; + + _highlightTextColor = aColor; + +// [_menuItemViews makeObjectsPerformSelector:@selector(setActivateColor:) withObject:_highlightTextColor]; +} + +- (void)setHighlightTextShadowColor:(CPColor)aColor +{ + if (_highlightTextShadowColor == aColor) + return; + + _highlightTextShadowColor = aColor; + +// [_menuItemViews makeObjectsPerformSelector:@selector(setActivateShadowColor:) withObject:_highlightTextShadowColor]; +} + +- (void)setMenu:(CPMenu)aMenu +{ + if (_menu == aMenu) + return; + + var defaultCenter = [CPNotificationCenter defaultCenter]; + + if (_menu) + { + [defaultCenter + removeObserver:self + name:CPMenuDidAddItemNotification + object:_menu]; + + [defaultCenter + removeObserver:self + name:CPMenuDidChangeItemNotification + object:_menu]; + + [defaultCenter + removeObserver:self + name:CPMenuDidRemoveItemNotification + object:_menu]; + + var items = [_menu itemArray], + count = items.length; + + while (count--) + [[items[count] _menuItemView] removeFromSuperview]; + } + + _menu = aMenu; + + if (_menu) + { + [defaultCenter + addObserver:self + selector:@selector(menuDidAddItem:) + name:CPMenuDidAddItemNotification + object:_menu]; + + [defaultCenter + addObserver:self + selector:@selector(menuDidChangeItem:) + name:CPMenuDidChangeItemNotification + object:_menu]; + + [defaultCenter + addObserver:self + selector:@selector(menuDidRemoveItem:) + name:CPMenuDidRemoveItemNotification + object:_menu]; + } + + _menuItemViews = []; + + var contentView = [self contentView], + items = [_menu itemArray], + count = items.length; + + for (index = 0; index < count; ++index) + { + var item = items[index], + menuItemView = [item _menuItemView]; + + _menuItemViews.push(menuItemView); + + [menuItemView setShowsStateColumn:NO]; + [menuItemView setBelongsToMenuBar:YES]; + [menuItemView setFont:_CPMenuBarWindowFont]; + [menuItemView setTextColor:_textColor]; + [menuItemView setHidden:[item isHidden]]; + + [menuItemView synchronizeWithMenuItem]; + + [contentView addSubview:menuItemView]; + } + + [self tile]; +} + +- (void)menuDidChangeItem:(CPNotification)aNotification +{ + var menuItem = [_menu itemAtIndex:[[aNotification userInfo] objectForKey:@"CPMenuItemIndex"]], + menuItemView = [menuItem _menuItemView]; + + [menuItemView setHidden:[menuItem isHidden]]; + [menuItemView synchronizeWithMenuItem]; + + [self tile]; +} + +- (void)menuDidAddItem:(CPNotification)aNotification +{ + var index = [[aNotification userInfo] objectForKey:@"CPMenuItemIndex"], + menuItem = [_menu itemAtIndex:index], + menuItemView = [menuItem _menuItemView]; + + [_menuItemViews insertObject:menuItemView atIndex:index]; + + [menuItemView setShowsStateColumn:NO]; + [menuItemView setBelongsToMenuBar:YES]; + [menuItemView setFont:_CPMenuBarWindowFont]; + [menuItemView setTextColor:_textColor]; + [menuItemView setHidden:[menuItem isHidden]]; + + [menuItemView synchronizeWithMenuItem]; + + [[self contentView] addSubview:menuItemView]; + + [self tile]; +} + +- (void)menuDidRemoveItem:(CPNotification)aNotification +{ + var index = [[aNotification userInfo] objectForKey:@"CPMenuItemIndex"], + menuItemView = [_menuItemViews objectAtIndex:index]; + + [_menuItemViews removeObjectAtIndex:index]; + + [menuItemView removeFromSuperview]; + + [self tile]; +} + +- (CGRect)frameForMenuItem:(CPMenuItem)aMenuItem +{ + var frame = [[aMenuItem _menuItemView] frame]; + + frame.origin.x -= 5.0; + frame.origin.y = 0; + frame.size.width += 10.0; + frame.size.height = MENUBAR_HEIGHT; + + return frame; +} + +- (CPMenuItem)menuItemAtPoint:(CGPoint)aPoint +{ + var items = [_menu itemArray], + count = items.length; + + while (count--) + { + var item = items[count]; + + if ([item isHidden] || [item isSeparatorItem]) + continue; + + if (CGRectContainsPoint([self frameForMenuItem:item], aPoint)) + return item; + } + + return nil; +} + +- (void)mouseDown:(CPEvent)anEvent +{ + _trackingMenuItem = [self menuItemAtPoint:[anEvent locationInWindow]]; + + if (![_trackingMenuItem isEnabled]) + return; + + if ([[_trackingMenuItem _menuItemView] eventOnSubmenu:anEvent]) + return [self showMenu:anEvent]; + + if ([_trackingMenuItem isEnabled]) + [self trackEvent:anEvent]; +} + +- (void)trackEvent:(CPEvent)anEvent +{ + var type = [anEvent type]; + + if (type === CPPeriodic) + return [self showMenu:anEvent]; + + var frame = [self frameForMenuItem:_trackingMenuItem], + menuItemView = [_trackingMenuItem _menuItemView], + onMenuItemView = CGRectContainsPoint(frame, [anEvent locationInWindow]); + + if (type == CPLeftMouseDown) + { + if ([_trackingMenuItem submenu] != nil) + { + var action = [_trackingMenuItem action]; + + // If the item has a submenu, but not direct action, a.k.a. a "pure" menu, simply show the menu. + // FIXME: (?) should we use submenuAction: or not? + if (!action || action === @selector(submenuAction:)) + return [self showMenu:anEvent]; + + // If this is a hybrid button/menu, show it in a bit... + [CPEvent startPeriodicEventsAfterDelay:0.0 withPeriod:0.5]; + } + + [menuItemView highlight:onMenuItemView]; + } + + else if (type == CPLeftMouseDragged) + { + if (!onMenuItemView && [_trackingMenuItem submenu]) + return [self showMenu:anEvent]; + + [menuItemView highlight:onMenuItemView]; + } + + else /*if (type == CPLeftMouseUp)*/ + { + [CPEvent stopPeriodicEvents]; + + [menuItemView highlight:NO]; + + if (onMenuItemView) + [CPApp sendAction:[_trackingMenuItem action] to:[_trackingMenuItem target] from:_trackingMenuItem]; + + return; + } + + [CPApp setTarget:self selector:@selector(trackEvent:) forNextEventMatchingMask:CPPeriodicMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES]; +} + +- (void)showMenu:(CPEvent)anEvent +{ + [CPEvent stopPeriodicEvents]; + + var frame = [self frameForMenuItem:_trackingMenuItem], + menuItemView = [_trackingMenuItem _menuItemView]; + + if (!_highlightView) + { + _highlightView = [[CPView alloc] initWithFrame:frame]; + + [_highlightView setBackgroundColor:_highlightColor ? _highlightColor : [CPColor colorWithRed:95.0/255.0 green:131.0/255.0 blue:185.0/255.0 alpha:1.0]]; + } + else + [_highlightView setFrame:frame]; + + [[self contentView] addSubview:_highlightView positioned:CPWindowBelow relativeTo:menuItemView]; + + [menuItemView activate:YES]; + + var submenu = [_trackingMenuItem submenu]; + + [[CPNotificationCenter defaultCenter] + addObserver:self + selector:@selector(menuDidEndTracking:) + name:CPMenuDidEndTrackingNotification + object:submenu]; + + [CPMenu _popUpContextMenu:submenu + withEvent:[CPEvent mouseEventWithType:CPLeftMouseDown location:CGPointMake(CGRectGetMinX(frame), CGRectGetMaxY(frame)) + modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] windowNumber:[self windowNumber] + context:nil eventNumber:0 clickCount:[anEvent clickCount] pressure:[anEvent pressure]] + forView:[self contentView] + withFont:nil + forMenuBar:YES]; +} + +- (void)menuDidEndTracking:(CPNotification)aNotification +{ + [_highlightView removeFromSuperview]; + + [[_trackingMenuItem _menuItemView] activate:NO]; + + [[CPNotificationCenter defaultCenter] + removeObserver:self + name:CPMenuDidEndTrackingNotification + object:[aNotification object]]; +} + +- (void)tile +{ + var items = [_menu itemArray], + index = 0, + count = items.length, + + x = MENUBAR_LEFT_MARGIN, + y = 0.0, + isLeftAligned = YES; + + for (; index < count; ++index) + { + var item = items[index]; + + if ([item isSeparatorItem]) + { + x = CGRectGetWidth([self frame]) - MENUBAR_RIGHT_MARGIN; + isLeftAligned = NO; + + continue; + } + + if ([item isHidden]) + continue; + + var menuItemView = [item _menuItemView], + frame = [menuItemView frame]; + + if (isLeftAligned) + { + [menuItemView setFrameOrigin:CGPointMake(x, (MENUBAR_HEIGHT - 1.0 - CGRectGetHeight(frame)) / 2.0)]; + + x += CGRectGetWidth([menuItemView frame]) + MENUBAR_MARGIN; + } + else + { + [menuItemView setFrameOrigin:CGPointMake(x - CGRectGetWidth(frame), (MENUBAR_HEIGHT - 1.0 - CGRectGetHeight(frame)) / 2.0)]; + + x = CGRectGetMinX([menuItemView frame]) - MENUBAR_MARGIN; + } + } + + var bounds = [[self contentView] bounds], + titleFrame = [_titleField frame]; + + if ([_iconImageView isHidden]) + [_titleField setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth(titleFrame)) / 2.0, (CGRectGetHeight(bounds) - CGRectGetHeight(titleFrame)) / 2.0)]; + else + { + var iconFrame = [_iconImageView frame], + iconWidth = CGRectGetWidth(iconFrame), + totalWidth = iconWidth + CGRectGetWidth(titleFrame); + + [_iconImageView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - totalWidth) / 2.0, (CGRectGetHeight(bounds) - CGRectGetHeight(iconFrame)) / 2.0)]; + [_titleField setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - totalWidth) / 2.0 + iconWidth, (CGRectGetHeight(bounds) - CGRectGetHeight(titleFrame)) / 2.0)]; + } +} + +- (void)setFrame:(CGRect)aRect display:(BOOL)shouldDisplay animate:(BOOL)shouldAnimate +{ + var size = [self frame].size; + + [super setFrame:aRect display:shouldDisplay animate:shouldAnimate]; + + if (!_CGSizeEqualToSize(size, aRect.size)) + [self tile]; +} + +@end + +@implementation __CPMenuBarWindow : _CPMenuWindow +{ +} + +@end + +@implementation _CPMenuBarView : _CPMenuView +{ +} + +- (CGRect)rectForItemAtIndex:(int)anIndex +{ + return [_menuItemViews[anIndex === CPNotFound ? 0 : anIndex] frame]; +} + +- (int)itemIndexAtPoint:(CGPoint)aPoint +{ + var bounds = [self bounds]; + + if (!CGRectContainsPoint(bounds, aPoint)) + return CPNotFound; + + var x = aPoint.x, + low = 0, + high = _visibleMenuItemInfos.length - 1; + + while (low <= high) + { + var middle = FLOOR(low + (high - low) / 2), + info = _visibleMenuItemInfos[middle], + frame = [info.view frame]; + + if (x < CGRectGetMinX(frame)) + high = middle - 1; + + else if (x > CGRectGetMaxX(frame)) + low = middle + 1; + + else + return info.index; + } + + return CPNotFound; +} + +- (void)tile +{ + var items = [_menu itemArray], + index = 0, + count = items.length, + + x = MENUBAR_LEFT_MARGIN, + y = 0.0, + isLeftAligned = YES; + + for (; index < count; ++index) + { + var item = items[index]; + + if ([item isSeparatorItem]) + { + x = CGRectGetWidth([self frame]) - MENUBAR_RIGHT_MARGIN; + isLeftAligned = NO; + + continue; + } + + if ([item isHidden]) + continue; + + var menuItemView = [item _menuItemView], + frame = [menuItemView frame]; + + if (isLeftAligned) + { + [menuItemView setFrameOrigin:CGPointMake(x, (MENUBAR_HEIGHT - 1.0 - CGRectGetHeight(frame)) / 2.0)]; + + x += CGRectGetWidth([menuItemView frame]) + MENUBAR_MARGIN; + } + else + { + [menuItemView setFrameOrigin:CGPointMake(x - CGRectGetWidth(frame), (MENUBAR_HEIGHT - 1.0 - CGRectGetHeight(frame)) / 2.0)]; + + x = CGRectGetMinX([menuItemView frame]) - MENUBAR_MARGIN; + } + } + + var bounds = [[self contentView] bounds], + titleFrame = [_titleField frame]; + + if ([_iconImageView isHidden]) + [_titleField setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth(titleFrame)) / 2.0, (CGRectGetHeight(bounds) - CGRectGetHeight(titleFrame)) / 2.0)]; + else + { + var iconFrame = [_iconImageView frame], + iconWidth = CGRectGetWidth(iconFrame), + totalWidth = iconWidth + CGRectGetWidth(titleFrame); + + [_iconImageView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - totalWidth) / 2.0, (CGRectGetHeight(bounds) - CGRectGetHeight(iconFrame)) / 2.0)]; + [_titleField setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - totalWidth) / 2.0 + iconWidth, (CGRectGetHeight(bounds) - CGRectGetHeight(titleFrame)) / 2.0)]; + } +} + +@end diff --git a/AppKit/CPMenu/_CPMenuWindow.j b/AppKit/CPMenu/_CPMenuWindow.j new file mode 100644 index 000000000..aa5224cdd --- /dev/null +++ b/AppKit/CPMenu/_CPMenuWindow.j @@ -0,0 +1,646 @@ + +#include "../CoreGraphics/CGGeometry.h" + + +var _CPMenuWindowPool = [], + _CPMenuWindowPoolCapacity = 5, + + _CPMenuWindowBackgroundColors = [], + + _CPMenuWindowScrollingStateUp = -1, + _CPMenuWindowScrollingStateDown = 1, + _CPMenuWindowScrollingStateNone = 0; + +_CPMenuWindowMenuBarBackgroundStyle = 0; +_CPMenuWindowPopUpBackgroundStyle = 1; +_CPMenuWindowAttachedMenuBackgroundStyle = 2; + +var STICKY_TIME_INTERVAL = 500, + + TOP_MARGIN = 5.0, + LEFT_MARGIN = 1.0, + RIGHT_MARGIN = 1.0, + BOTTOM_MARGIN = 5.0, + + SCROLL_INDICATOR_HEIGHT = 16.0; + +/* + @ignore +*/ +@implementation _CPMenuWindow : CPWindow +{ + _CPMenuView _menuView; + CPClipView _menuClipView; + CPView _lastMouseOverMenuView; + + CPImageView _moreAboveView; + CPImageView _moreBelowView; + + id _sessionDelegate; + SEL _didEndSelector; + + CPTimeInterval _startTime; + int _scrollingState; + CGPoint _lastGlobalLocation; + CPMenu _lastActiveMenu; + + BOOL _isShowingTopScrollIndicator; + BOOL _isShowingBottomScrollIndicator; + BOOL _trackingCanceled; + + CGRect _unconstrainedFrame; + + CPArray _menuWindowStack; +} + ++ (id)menuWindowWithMenu:(CPMenu)aMenu font:(CPFont)aFont +{ + var menuWindow = nil; + + if (_CPMenuWindowPool.length) + menuWindow = _CPMenuWindowPool.pop(); + else + menuWindow = [[_CPMenuWindow alloc] init]; + + [menuWindow setFont:aFont]; + [menuWindow setMenu:aMenu]; + + return menuWindow; +} + ++ (void)poolMenuWindow:(_CPMenuWindow)aMenuWindow +{ + if (!aMenuWindow || _CPMenuWindowPool.length >= _CPMenuWindowPoolCapacity) + return; + + _CPMenuWindowPool.push(aMenuWindow); +} + ++ (void)initialize +{ + if (self != [_CPMenuWindow class]) + return; + + var bundle = [CPBundle bundleForClass:self]; + + _CPMenuWindowMoreAboveImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowMoreAbove.png"] size:CGSizeMake(38.0, 18.0)]; + _CPMenuWindowMoreBelowImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowMoreBelow.png"] size:CGSizeMake(38.0, 18.0)]; +} + +- (id)init +{ + self = [super initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessWindowMask]; + + if (self) + { + [self setLevel:CPPopUpMenuWindowLevel]; + [self setHasShadow:YES]; + [self setShadowStyle:CPMenuWindowShadowStyle]; + [self setAcceptsMouseMovedEvents:YES]; + + _unconstrainedFrame = CGRectMakeZero(); + + var contentView = [self contentView]; + + _menuView = [[_CPMenuView alloc] initWithFrame:CGRectMakeZero()]; + + _menuClipView = [[CPClipView alloc] initWithFrame:CGRectMake(LEFT_MARGIN, TOP_MARGIN, 0.0, 0.0)]; + [_menuClipView setDocumentView:_menuView]; + + [contentView addSubview:_menuClipView]; + + _moreAboveView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()]; + + [_moreAboveView setImage:_CPMenuWindowMoreAboveImage]; + [_moreAboveView setFrameSize:[_CPMenuWindowMoreAboveImage size]]; + + [contentView addSubview:_moreAboveView]; + + _moreBelowView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()]; + + [_moreBelowView setImage:_CPMenuWindowMoreBelowImage]; + [_moreBelowView setFrameSize:[_CPMenuWindowMoreBelowImage size]]; + + [contentView addSubview:_moreBelowView]; + + [self setShadowStyle:CPWindowShadowStyleMenu]; + } + + return self; +} + +- (CGFloat)overlapOffsetWidth +{ + return LEFT_MARGIN; +} + +- (void)setFont:(CPFont)aFont +{ + [_menuView setFont:aFont]; +} + +- (CPFont)font +{ + return [_menuView font]; +} + ++ (CPColor)backgroundColorForBackgroundStyle:(_CPMenuWindowBackgroundStyle)aBackgroundStyle +{ + var color = _CPMenuWindowBackgroundColors[aBackgroundStyle]; + + if (!color) + { + var bundle = [CPBundle bundleForClass:[self class]]; + + if (aBackgroundStyle == _CPMenuWindowPopUpBackgroundStyle) + color = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices: + [ + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded0.png"] size:CGSizeMake(4.0, 4.0)], + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow1.png"] size:CGSizeMake(1.0, 4.0)], + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded2.png"] size:CGSizeMake(4.0, 4.0)], + + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow3.png"] size:CGSizeMake(4.0, 1.0)], + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow4.png"] size:CGSizeMake(1.0, 1.0)], + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow5.png"] size:CGSizeMake(4.0, 1.0)], + + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded6.png"] size:CGSizeMake(4.0, 4.0)], + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow7.png"] size:CGSizeMake(1.0, 4.0)], + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded8.png"] size:CGSizeMake(4.0, 4.0)] + ]]]; + + else if (aBackgroundStyle == _CPMenuWindowMenuBarBackgroundStyle) + color = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices: + [ + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow3.png"] size:CGSizeMake(4.0, 0.0)], + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow4.png"] size:CGSizeMake(1.0, 0.0)], + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow5.png"] size:CGSizeMake(4.0, 0.0)], + + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow3.png"] size:CGSizeMake(4.0, 1.0)], + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow4.png"] size:CGSizeMake(1.0, 1.0)], + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow5.png"] size:CGSizeMake(4.0, 1.0)], + + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded6.png"] size:CGSizeMake(4.0, 4.0)], + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow7.png"] size:CGSizeMake(1.0, 4.0)], + [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded8.png"] size:CGSizeMake(4.0, 4.0)] + ]]]; + + _CPMenuWindowBackgroundColors[aBackgroundStyle] = color; + } + + return color; +} + +- (void)setBackgroundStyle:(_CPMenuWindowBackgroundStyle)aBackgroundStyle +{ + [self setBackgroundColor:[[self class] backgroundColorForBackgroundStyle:aBackgroundStyle]]; +} + +- (void)setMenu:(CPMenu)aMenu +{ + [aMenu _setMenuWindow:self]; + [_menuView setMenu:aMenu]; + + var menuViewSize = [_menuView frame].size; + + [self setFrameSize:CGSizeMake(LEFT_MARGIN + menuViewSize.width + RIGHT_MARGIN, TOP_MARGIN + menuViewSize.height + BOTTOM_MARGIN)]; + + [_menuView scrollPoint:CGPointMake(0.0, 0.0)]; + [_menuClipView setFrame:CGRectMake(LEFT_MARGIN, TOP_MARGIN, menuViewSize.width, menuViewSize.height)]; +} + +- (void)setMinWidth:(float)aWidth +{ + var size = [self frame].size; + + [self setFrameSize:CGSizeMake(MAX(size.width, aWidth), size.height)]; +} + +- (CGPoint)rectForItemAtIndex:(int)anIndex +{ + return [_menuView convertRect:[_menuView rectForItemAtIndex:anIndex] toView:nil]; +} + +- (int)itemIndexAtPoint:(CGPoint)aPoint +{ + return [_menuView itemIndexAtPoint:[_menuView convertPoint:aPoint fromView:nil]]; +} + +- (CPMenu)menu +{ + return [_menuView menu]; +} + +- (void)orderFront:(id)aSender +{ + [self constrainToScreen]; + + [super orderFront:aSender]; +} + +- (void)constrainToScreen +{ + // FIXME: There are integral window issues with platform windows. + // FIXME: This gets called far too often. + _unconstrainedFrame = CGRectMakeCopy([self frame]); + + var isBrowser = [CPPlatform isBrowser], + visibleFrame = CGRectInset(isBrowser ? [[self platformWindow] contentBounds] : [[self screen] visibleFrame], 5.0, 5.0), + constrainedFrame = CGRectIntersection(_unconstrainedFrame, visibleFrame); + + // We don't want to simply intersect the visible frame and the unconstrained frame. + // We should be allowing as much of the width to fit as possible (pushing back and forward). + constrainedFrame.origin.x = CGRectGetMinX(_unconstrainedFrame); + constrainedFrame.size.width = CGRectGetWidth(_unconstrainedFrame); + + if (CGRectGetWidth(constrainedFrame) > CGRectGetWidth(visibleFrame)) + constrainedFrame.size.width = CGRectGetWidth(visibleFrame); + + if (CGRectGetMaxX(constrainedFrame) > CGRectGetMaxX(visibleFrame)) + constrainedFrame.origin.x -= CGRectGetMaxX(constrainedFrame) - CGRectGetMaxX(visibleFrame); + + if (CGRectGetMinX(constrainedFrame) < CGRectGetMinX(visibleFrame)) + constrainedFrame.origin.x = CGRectGetMinX(visibleFrame); + + // This needs to happen before changing the frame. + var menuViewOrigin = [self convertBaseToGlobal:CGPointMake(LEFT_MARGIN, TOP_MARGIN)]; + + [super setFrame:constrainedFrame]; + + var moreAbove = menuViewOrigin.y < CGRectGetMinY(constrainedFrame) + TOP_MARGIN, + moreBelow = menuViewOrigin.y + CGRectGetHeight([_menuView frame]) > CGRectGetMaxY(constrainedFrame) - BOTTOM_MARGIN, + + topMargin = TOP_MARGIN, + bottomMargin = BOTTOM_MARGIN, + + contentView = [self contentView], + bounds = [contentView bounds]; + + if (moreAbove) + { + topMargin += SCROLL_INDICATOR_HEIGHT; + + var frame = [_moreAboveView frame]; + + [_moreAboveView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth(frame)) / 2.0, (TOP_MARGIN + SCROLL_INDICATOR_HEIGHT - CGRectGetHeight(frame)) / 2.0)]; + } + + [_moreAboveView setHidden:!moreAbove]; + + if (moreBelow) + { + bottomMargin += SCROLL_INDICATOR_HEIGHT; + + [_moreBelowView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth([_moreBelowView frame])) / 2.0, CGRectGetHeight(bounds) - SCROLL_INDICATOR_HEIGHT - BOTTOM_MARGIN)]; + } + + [_moreBelowView setHidden:!moreBelow]; + + var clipFrame = CGRectMake(LEFT_MARGIN, topMargin, CGRectGetWidth(constrainedFrame) - LEFT_MARGIN - RIGHT_MARGIN, CGRectGetHeight(constrainedFrame) - topMargin - bottomMargin) + + [_menuClipView setFrame:clipFrame]; + [_menuView setFrameSize:CGSizeMake(CGRectGetWidth(clipFrame), CGRectGetHeight([_menuView frame]))]; + + [_menuView scrollPoint:CGPointMake(0.0, [self convertBaseToGlobal:clipFrame.origin].y - menuViewOrigin.y)]; +} + +- (void)beginTrackingWithEvent:(CPEvent)anEvent sessionDelegate:(id)aSessionDelegate didEndSelector:(SEL)aDidEndSelector +{ + CPApp._activeMenu = [_menuView menu]; + + _startTime = [anEvent timestamp];//new Date(); + _scrollingState = _CPMenuWindowScrollingStateNone; + _trackingCanceled = NO; + _menuWindowStack = [self]; + + _sessionDelegate = aSessionDelegate; + _didEndSelector = aDidEndSelector; + + [self trackEvent:anEvent]; +} + +- (_CPMenuWindow)menuWindowAtPoint:(CGPoint)aGlobalLocation +{ + var count = _menuWindowStack.length; + + while (count--) + { + var menuWindow = _menuWindowStack[count]; + + if (CGRectContainsPoint([menuWindow frame], aGlobalLocation)) + return menuWindow; + } + + return nil; +} + +- (void)showMenu:(CPMenu)newMenu fromMenu:(CPMenu)baseMenu atPoint:(CGPoint)aGlobalLocation +{ + var count = _menuWindowStack.length, + index = count; + + while (index--) + { + var menuWindow = _menuWindowStack[index]; + + if ([menuWindow menu] !== baseMenu) + continue; + + // If we're already showing this menu, then hide all submenus... + while (count-- > index + 1) + { + var menuWindow = _menuWindowStack[count]; + + [menuWindow orderOut:self]; + [menuWindow setMenu:nil]; + + [_CPMenuWindow poolMenuWindow:menuWindow]; + [_menuWindowStack removeObjectAtIndex:count]; + } + } + + if (!newMenu) + return; + + var menuWindow = [_CPMenuWindow menuWindowWithMenu:newMenu font:[self font]]; + + _menuWindowStack.push(menuWindow); + + [menuWindow setBackgroundStyle:_CPMenuWindowPopUpBackgroundStyle]; + [menuWindow setFrameOrigin:aGlobalLocation]; + [menuWindow orderFront:self]; +} + +- (void)trackEvent:(CPEvent)anEvent +{ + var type = [anEvent type], + menu = [_menuView menu]; + + // Close Menu Event. + if (type === CPAppKitDefined) + { + // Stop all periodic events at this point. + [CPEvent stopPeriodicEvents]; + + var highlightedItem = [[_menuView menu] highlightedItem]; + + [menu _highlightItemAtIndex:CPNotFound]; + + [_menuWindowStack makeObjectsPerformSelector:@selector(orderOut:) withObject:self]; + + var delegate = [menu delegate]; + + if ([delegate respondsToSelector:@selector(menuDidClose:)]) + [delegate menuDidClose:menu]; + + if (_sessionDelegate && _didEndSelector) + objj_msgSend(_sessionDelegate, _didEndSelector, self, highlightedItem); + + [[CPNotificationCenter defaultCenter] + postNotificationName:CPMenuDidEndTrackingNotification + object:menu]; + + // Clear these now so its faster next time around. + [_menuView setMenu:nil]; + + CPApp._activeMenu = nil; + + return; + } + + [CPApp setTarget:self selector:@selector(trackEvent:) forNextEventMatchingMask:CPPeriodicMask | CPMouseMovedMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask | CPAppKitDefinedMask untilDate:nil inMode:nil dequeue:YES]; + + var theWindow = [anEvent window], + globalLocation = [anEvent locationInWindow]; + + if (theWindow) + globalLocation = [theWindow convertBaseToGlobal:globalLocation]; + + if (type === CPPeriodic) + { + var constrainedBounds = CGRectInset([CPPlatform isBrowser] ? [[self platformWindow] contentBounds] : [[self screen] visibleFrame], 5.0, 5.0); + + if (_scrollingState == _CPMenuWindowScrollingStateUp) + { + if (CGRectGetMinY(_unconstrainedFrame) < CGRectGetMinY(constrainedBounds)) + _unconstrainedFrame.origin.y += 10; + } + else if (_scrollingState == _CPMenuWindowScrollingStateDown) + if (CGRectGetMaxY(_unconstrainedFrame) > CGRectGetHeight(constrainedBounds)) + _unconstrainedFrame.origin.y -= 10; + + [self setFrame:_unconstrainedFrame]; + [self constrainToScreen]; + + globalLocation = _lastGlobalLocation; + } + + _lastGlobalLocation = globalLocation; + + // Find which menu window the mouse is currently on top of + var activeMenuWindow = [self menuWindowAtPoint:globalLocation]; + + if (activeMenuWindow) + { + var menuLocation = [activeMenuWindow convertGlobalToBase:globalLocation], + activeItemIndex = [activeMenuWindow itemIndexAtPoint:menuLocation], + activeMenu = [activeMenuWindow menu], + activeItem = [activeMenu itemAtIndex:activeItemIndex], + mouseOverMenuView = [activeItem view]; + } + else + { + var activeMenuItemIndex = CPNotFound, + activeMenu = nil, + activeItem = nil, + mouseOverMenuView = nil; + } + + _lastActiveMenu = activeMenu || _lastActiveMenu; + + // If we're over a custom menu view... + if (mouseOverMenuView) + { + if (!_lastMouseOverMenuView) + [menu _highlightItemAtIndex:CPNotFound]; + + if (_lastMouseOverMenuView != mouseOverMenuView) + { + [mouseOverMenuView mouseExited:anEvent]; + // FIXME: Possibly multiple of these? + [_lastMouseOverMenuView mouseEntered:anEvent]; + + _lastMouseOverMenuView = mouseOverMenuView; + } + + [self sendEvent:[CPEvent mouseEventWithType:type location:menuLocation modifierFlags:[anEvent modifierFlags] + timestamp:[anEvent timestamp] windowNumber:[self windowNumber] context:nil + eventNumber:0 clickCount:[anEvent clickCount] pressure:[anEvent pressure]]]; + } + else + { + if (_lastMouseOverMenuView) + { + [_lastMouseOverMenuView mouseExited:anEvent]; + _lastMouseOverMenuView = nil; + } + + [_lastActiveMenu _highlightItemAtIndex:activeItemIndex]; + + if (type === CPMouseMoved || type === CPLeftMouseDragged || type === CPLeftMouseDown) + { + var frame = [self frame], + oldScrollingState = _scrollingState; + + _scrollingState = _CPMenuWindowScrollingStateNone; + + // If we're at or above of the top scroll indicator... + if (globalLocation.y < CGRectGetMinY(frame) + TOP_MARGIN + SCROLL_INDICATOR_HEIGHT) + _scrollingState = _CPMenuWindowScrollingStateUp; + + // If we're at or below the bottom scroll indicator... + else if (globalLocation.y > CGRectGetMaxY(frame) - BOTTOM_MARGIN - SCROLL_INDICATOR_HEIGHT) + _scrollingState = _CPMenuWindowScrollingStateDown; + + if (_scrollingState != oldScrollingState) + + if (_scrollingState == _CPMenuWindowScrollingStateNone) + [CPEvent stopPeriodicEvents]; + + else if (oldScrollingState == _CPMenuWindowScrollingStateNone) + [CPEvent startPeriodicEventsAfterDelay:0.0 withPeriod:0.04]; + } + else if (type === CPLeftMouseUp && ([anEvent timestamp] - _startTime > STICKY_TIME_INTERVAL)) + [menu cancelTracking]; + } + + if (activeItem) + if ([activeItem hasSubmenu])// && [activeItem action] === @selector(submenuAction:)) + { + var activeItemRect = [activeMenuWindow rectForItemAtIndex:activeItemIndex], + newMenuOrigin = CGPointMake(CGRectGetMaxX(activeItemRect), CGRectGetMinY(activeItemRect)); + + newMenuOrigin = [activeMenuWindow convertBaseToGlobal:newMenuOrigin]; + + [self showMenu:[activeItem submenu] fromMenu:[activeItem menu] atPoint:newMenuOrigin]; + } + else + [self showMenu:nil fromMenu:[activeItem menu] atPoint:CGPointMakeZero()]; +} + +@end + +/* + @ignore +*/ + +@implementation _CPMenuView : CPView +{ + CPArray _menuItemViews; + CPArray _visibleMenuItemInfos; + + CPFont _font @accessors(property=font); +} + +- (CGRect)rectForItemAtIndex:(int)anIndex +{ + return [_menuItemViews[anIndex === CPNotFound ? 0 : anIndex] frame]; +} + +- (int)itemIndexAtPoint:(CGPoint)aPoint +{ + var x = aPoint.x, + bounds = [self bounds]; + + if (x < CGRectGetMinX(bounds) || x > CGRectGetMaxX(bounds)) + return CPNotFound; + + var y = aPoint.y, + low = 0, + high = _visibleMenuItemInfos.length - 1; + + while (low <= high) + { + var middle = FLOOR(low + (high - low) / 2), + info = _visibleMenuItemInfos[middle] + frame = [info.view frame]; + + if (y < CGRectGetMinY(frame)) + high = middle - 1; + + else if (y > CGRectGetMaxY(frame)) + low = middle + 1; + + else + return info.index; + } + + return CPNotFound; +} + +- (void)tile +{ + [_menuItemViews makeObjectsPerformSelector:@selector(removeFromSuperview)]; + + _menuItemViews = []; + _visibleMenuItemInfos = []; + + var menu = [self menu]; + + if (!menu) + return; + + var items = [menu itemArray], + index = 0, + count = [items count], + maxWidth = 0, + y = 0, + showsStateColumn = [menu showsStateColumn]; + + for (; index < count; ++index) + { + var item = items[index], + view = [item _menuItemView]; + + _menuItemViews.push(view); + + if ([item isHidden]) + continue; + + _visibleMenuItemInfos.push({ view:view, index:index }); + + [view setFont:_font]; + [view setShowsStateColumn:showsStateColumn]; + [view synchronizeWithMenuItem]; + + [view setFrameOrigin:CGPointMake(0.0, y)]; + + [self addSubview:view]; + + var size = [view minSize], + width = size.width; + + if (maxWidth < width) + maxWidth = width; + + y += size.height; + } + + for (index = 0; index < count; ++index) + { + var view = _menuItemViews[index]; + + [view setFrameSize:CGSizeMake(maxWidth, CGRectGetHeight([view frame]))]; + } + + [self setAutoresizesSubviews:NO]; + [self setFrameSize:CGSizeMake(maxWidth, y)]; + [self setAutoresizesSubviews:YES]; +} + +- (void)setMenu:(CPMenu)aMenu +{ + [super setMenu:aMenu]; + [self tile]; +} + +@end diff --git a/AppKit/CPMenuItem.j b/AppKit/CPMenuItem/CPMenuItem.j similarity index 62% rename from AppKit/CPMenuItem.j rename to AppKit/CPMenuItem/CPMenuItem.j index 7306b8f49..0aa3b7ad2 100644 --- a/AppKit/CPMenuItem.j +++ b/AppKit/CPMenuItem/CPMenuItem.j @@ -24,9 +24,10 @@ @import @import -@import -@import -@import +@import "CPImage.j" +@import "CPMenu.j" +@import "CPView.j" +@import "_CPMenuItemView.j" /*! @ingroup appkit @@ -570,6 +571,25 @@ CPControlKeyMask return _keyEquivalentModifierMask; } +- (CPString)keyEquivalentStringRepresentation +{ + if (![_keyEquivalent length]) + return @""; + + var string = _keyEquivalent.toUpperCase(); + + if (_keyEquivalentModifierMask & CPCommandKeyMask) + string = "⌘" + string; + + if (_keyEquivalentModifierMask & CPShiftKeyMask) + string = "⇧" + string; + + if (_keyEquivalentModifierMask & CPControlKeyMask) + string = "^" + string; + + return string; +} + // Managing Mnemonics /*! Sets the index of the mnemonic character in the title. The character @@ -861,418 +881,3 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey", } @end - -var LEFT_MARGIN = 3.0, - RIGHT_MARGIN = 16.0, - STATE_COLUMN_WIDTH = 14.0, - INDENTATION_WIDTH = 17.0, - VERTICAL_MARGIN = 4.0; - -var _CPMenuItemSelectionColor = nil, - _CPMenuItemTextShadowColor = nil, - - _CPMenuItemDefaultStateImages = [], - _CPMenuItemDefaultStateHighlightedImages = []; - -/* - @ignore -*/ -@implementation _CPMenuItemView : CPView -{ - CPMenuItem _menuItem; - - CPFont _font; - CPColor _textColor; - CPColor _textShadowColor; - CPColor _activateColor; - CPColor _activateShadowColor; - - CGSize _minSize; - BOOL _isDirty; - BOOL _showsStateColumn; - BOOL _belongsToMenuBar; - - CPImageView _stateView; - _CPImageAndTextView _imageAndTextView; - CPView _submenuView; -} - -+ (void)initialize -{ - if (self != [_CPMenuItemView class]) - return; - - _CPMenuItemSelectionColor = [CPColor colorWithCalibratedRed:95.0 / 255.0 green:131.0 / 255.0 blue:185.0 / 255.0 alpha:1.0]; - _CPMenuItemTextShadowColor = [CPColor colorWithCalibratedRed:26.0 / 255.0 green: 73.0 / 255.0 blue:109.0 / 255.0 alpha:1.0] - - var bundle = [CPBundle bundleForClass:self]; - - _CPMenuItemDefaultStateImages[CPOffState] = nil; - _CPMenuItemDefaultStateHighlightedImages[CPOffState] = nil; - - _CPMenuItemDefaultStateImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnState.png"] size:CGSizeMake(14.0, 14.0)]; - _CPMenuItemDefaultStateHighlightedImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnStateHighlighted.png"] size:CGSizeMake(14.0, 14.0)]; - - _CPMenuItemDefaultStateImages[CPMixedState] = nil; - _CPMenuItemDefaultStateHighlightedImages[CPMixedState] = nil; -} - -+ (float)leftMargin -{ - return LEFT_MARGIN + STATE_COLUMN_WIDTH; -} - -- (id)initWithFrame:(CGRect)aFrame forMenuItem:(CPMenuItem)aMenuItem -{ - self = [super initWithFrame:aFrame]; - - if (self) - { - _menuItem = aMenuItem; - _showsStateColumn = YES; - _isDirty = YES; - - [self setAutoresizingMask:CPViewWidthSizable]; - - [self synchronizeWithMenuItem]; - } - - return self; -} - -- (CGSize)minSize -{ - return _minSize; -} - -- (void)setDirty -{ - _isDirty = YES; -} - -- (void)synchronizeWithMenuItem -{ - if (!_isDirty) - return; - - _isDirty = NO; - - var view = [_menuItem view]; - - if ([_menuItem isSeparatorItem]) - { - var line = [[CPView alloc] initWithFrame:CGRectMake(0.0, 5.0, 10.0, 1.0)]; - - view = [[CPView alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 10.0)]; - - [view setAutoresizingMask:CPViewWidthSizable]; - [line setAutoresizingMask:CPViewWidthSizable]; - - [line setBackgroundColor:[CPColor lightGrayColor]]; - - [view addSubview:line]; - } - - if (view) - { - [_imageAndTextView removeFromSuperview]; - _imageAndTextView = nil; - - [_stateView removeFromSuperview]; - _stateView = nil; - - [_submenuView removeFromSuperview]; - _submenuView = nil; - - _minSize = [view frame].size; - - [self setFrameSize:_minSize]; - - [self addSubview:view]; - - return; - } - - // State Column - var x = _belongsToMenuBar ? 0.0 : (LEFT_MARGIN + [_menuItem indentationLevel] * INDENTATION_WIDTH); - - if (_showsStateColumn) - { - if (!_stateView) - { - _stateView = [[CPImageView alloc] initWithFrame:CGRectMake(x, (CGRectGetHeight([self frame]) - STATE_COLUMN_WIDTH) / 2.0, STATE_COLUMN_WIDTH, STATE_COLUMN_WIDTH)]; - - [_stateView setAutoresizingMask:CPViewMinYMargin | CPViewMaxYMargin]; - - [self addSubview:_stateView]; - } - - var state = [_menuItem state]; - - switch (state) - { - case CPOffState: - case CPOnState: - case CPMixedState: [_stateView setImage:_CPMenuItemDefaultStateImages[state]]; - break; - - default: [_stateView setImage:nil]; - } - - x += STATE_COLUMN_WIDTH; - } - else - { - [_stateView removeFromSuperview]; - - _stateView = nil; - } - - // Image and Title - - if (!_imageAndTextView) - { - _imageAndTextView = [[_CPImageAndTextView alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 0.0)]; - - [_imageAndTextView setImagePosition:CPImageLeft]; - [_imageAndTextView setTextShadowOffset:CGSizeMake(0.0, 1.0)]; - - [self addSubview:_imageAndTextView]; - } - - var font = [_menuItem font]; - - if (!font) - font = _font; - - [_imageAndTextView setFont:font]; - [_imageAndTextView setVerticalAlignment:CPCenterVerticalTextAlignment]; - [_imageAndTextView setImage:[_menuItem image]]; - [_imageAndTextView setText:[_menuItem title]]; - [_imageAndTextView setTextColor:[self textColor]]; - [_imageAndTextView setTextShadowColor:[self textShadowColor]]; - [_imageAndTextView setTextShadowOffset:CGSizeMake(0, 1)]; - [_imageAndTextView setFrameOrigin:CGPointMake(x, VERTICAL_MARGIN)]; - [_imageAndTextView sizeToFit]; - - var frame = [_imageAndTextView frame]; - -// frame.size.height += 1.0; -// [_imageAndTextView setFrame:frame]; - - frame.size.height += 2 * VERTICAL_MARGIN; - - x += CGRectGetWidth(frame); - - // Submenu Arrow - if ([_menuItem hasSubmenu]) - { - x += 3.0; - - if (!_submenuView) - { - _submenuView = [[_CPMenuItemArrowView alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 10.0)]; - - [self addSubview:_submenuView]; - } - - [_submenuView setHidden:NO]; - [_submenuView setColor:_belongsToMenuBar ? [self textColor] : nil]; - [_submenuView setFrameOrigin:CGPointMake(x, (CGRectGetHeight(frame) - 10.0) / 2.0)]; - - x += 10.0; - } - else - [_submenuView setHidden:YES]; - - _minSize = CGSizeMake(x + (_belongsToMenuBar ? 0.0 : RIGHT_MARGIN) + 3.0, CGRectGetHeight(frame)); - - [self setFrameSize:_minSize]; -} - -- (CGFloat)overlapOffsetWidth -{ - return LEFT_MARGIN + ([[_menuItem menu] showsStateColumn] ? STATE_COLUMN_WIDTH : 0.0); -} - -- (void)setShowsStateColumn:(BOOL)shouldShowStateColumn -{ - _showsStateColumn = shouldShowStateColumn; -} - -- (void)setBelongsToMenuBar:(BOOL)shouldBelongToMenuBar -{ - _belongsToMenuBar = shouldBelongToMenuBar; -} - -- (void)highlight:(BOOL)shouldHighlight -{ - // ASSERT(![_menuItem view]); - - if (_belongsToMenuBar) - [_imageAndTextView setImage:shouldHighlight ? [_menuItem alternateImage] : [_menuItem image]]; - - else if ([_menuItem isEnabled]) - { - if (shouldHighlight) - { - [self setBackgroundColor:_CPMenuItemSelectionColor]; - - [_imageAndTextView setTextColor:[CPColor whiteColor]]; - [_imageAndTextView setTextShadowColor:_CPMenuItemTextShadowColor]; - } - else - { - [self setBackgroundColor:nil]; - - [_imageAndTextView setTextColor:[self textColor]]; - [_imageAndTextView setTextShadowColor:[self textShadowColor]]; - } - - var state = [_menuItem state]; - - switch (state) - { - case CPOffState: - case CPOnState: - case CPMixedState: [_stateView setImage:shouldHighlight ? _CPMenuItemDefaultStateHighlightedImages[state] : _CPMenuItemDefaultStateImages[state]]; - break; - - default: [_stateView setImage:nil]; - } - } -} - -- (void)activate:(BOOL)shouldActivate -{ - [_imageAndTextView setImage:[_menuItem image]]; - - if (shouldActivate) - { - [_imageAndTextView setTextColor:[self activateColor] || [CPColor whiteColor]]; - [_imageAndTextView setTextShadowColor:[self activateShadowColor] || [CPColor blackColor]]; - [_submenuView setColor:[self activateColor] || [CPColor whiteColor]]; - } - else - { - [_imageAndTextView setTextColor:[self textColor]]; - [_imageAndTextView setTextShadowColor:[self textShadowColor]]; - [_submenuView setColor:[self textColor]]; - } -} - -- (BOOL)eventOnSubmenu:(CPEvent)anEvent -{ - if (![_menuItem hasSubmenu]) - return NO; - - return CGRectContainsPoint([_submenuView frame], [self convertPoint:[anEvent locationInWindow] fromView:nil]); -} - -- (BOOL)isHidden -{ - return [_menuItem isHidden]; -} - -- (CPMenuItem)menuItem -{ - return _menuItem; -} - -- (void)setFont:(CPFont)aFont -{ - if (_font == aFont) - return; - - _font = aFont; - - [self setDirty]; -} - -- (void)setTextColor:(CPColor)aColor -{ - if (_textColor == aColor) - return; - - _textColor = aColor; - - [_imageAndTextView setTextColor:[self textColor]]; - [_submenuView setColor:[self textColor]]; -} - -- (CPColor)textColor -{ - return [_menuItem isEnabled] ? (_textColor ? _textColor : [CPColor colorWithCalibratedRed:70.0 / 255.0 green:69.0 / 255.0 blue:69.0 / 255.0 alpha:1.0]) : [CPColor lightGrayColor]; -} - -- (void)setTextShadowColor:(CPColor)aColor -{ - if (_textShadowColor == aColor) - return; - - _textShadowColor = aColor; - - [_imageAndTextView setTextShadowColor:[self textShadowColor]]; - //[_submenuView setColor:[self textColor]]; -} - -- (CPColor)textShadowColor -{ - return [_menuItem isEnabled] ? (_textShadowColor ? _textShadowColor : [CPColor colorWithWhite:1.0 alpha:0.8]) : [CPColor colorWithWhite:0.8 alpha:0.8]; -} - -- (void)setActivateColor:(CPColor)aColor -{ - _activateColor = aColor; -} - -- (CPColor)activateColor -{ - return _activateColor; -} - -- (void)setActivateShadowColor:(CPColor)aColor -{ - _activateShadowColor = aColor; -} - -- (CPColor)activateShadowColor -{ - return _activateShadowColor; -} - -@end - -@implementation _CPMenuItemArrowView : CPView -{ - CPColor _color; -} - -- (void)setColor:(CPColor)aColor -{ - if (_color == aColor) - return; - - _color = aColor; - - [self setNeedsDisplay:YES]; -} - -- (void)drawRect:(CGRect)aRect -{ - var context = [[CPGraphicsContext currentContext] graphicsPort]; - - CGContextBeginPath(context); - - CGContextMoveToPoint(context, 1.0, 4.0); - CGContextAddLineToPoint(context, 9.0, 4.0); - CGContextAddLineToPoint(context, 5.0, 8.0); - CGContextAddLineToPoint(context, 1.0, 4.0); - - CGContextClosePath(context); - - CGContextSetFillColor(context, _color); - CGContextFillPath(context); -} - -@end diff --git a/AppKit/CPMenuItem/_CPMenuItemSeparatorView.j b/AppKit/CPMenuItem/_CPMenuItemSeparatorView.j new file mode 100644 index 000000000..1c1b6a441 --- /dev/null +++ b/AppKit/CPMenuItem/_CPMenuItemSeparatorView.j @@ -0,0 +1,34 @@ +@implementation _CPMenuItemSeparatorView : CPView +{ +} + ++ (id)view +{ + return [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 10.0)]; +} + +- (id)initWithFrame:(CGRect)aFrame +{ + self = [super initWithFrame:aFrame]; + + if (self) + [self setAutoresizingMask:CPViewWidthSizable]; + + return self; +} + +- (void)drawRect:(CGRect)aRect +{ + var context = [[CPGraphicsContext currentContext] graphicsPort], + bounds = [self bounds]; + + CGContextBeginPath(context); + + CGContextMoveToPoint(context, CGRectGetMinX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5); + CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5); + + CGContextSetStrokeColor(context, [CPColor lightGrayColor]); + CGContextStrokePath(context); +} + +@end \ No newline at end of file diff --git a/AppKit/CPMenuItem/_CPMenuItemStandardView.j b/AppKit/CPMenuItem/_CPMenuItemStandardView.j new file mode 100644 index 000000000..e1d13787c --- /dev/null +++ b/AppKit/CPMenuItem/_CPMenuItemStandardView.j @@ -0,0 +1,282 @@ +var LEFT_MARGIN = 3.0, + RIGHT_MARGIN = 16.0, + STATE_COLUMN_WIDTH = 14.0, + INDENTATION_WIDTH = 17.0, + VERTICAL_MARGIN = 4.0, + + RIGHT_COLUMNS_MARGIN = 30.0, + KEY_EQUIVALENT_MARGIN = 10.0; + +var SUBMENU_INDICATOR_COLOR = nil, + _CPMenuItemSelectionColor = nil, + _CPMenuItemTextShadowColor = nil, + + _CPMenuItemDefaultStateImages = [], + _CPMenuItemDefaultStateHighlightedImages = []; + +@implementation _CPMenuItemStandardView : CPView +{ + CPMenuItem _menuItem @accessors(property = menuItem); + + CPFont _font; + CPColor _textColor; + CPColor _textShadowColor; + + CGSize _minSize @accessors(readonly, property=minSize); + BOOL _isDirty; + BOOL _showsStateColumn; + + CPImageView _stateView; + _CPImageAndTextView _imageAndTextView; + _CPImageAndTextView _keyEquivalentView; + CPView _submenuIndicatorView; +} + ++ (void)initialize +{ + if (self !== [_CPMenuItemStandardView class]) + return; + + SUBMENU_INDICATOR_COLOR = [CPColor grayColor]; + + _CPMenuItemSelectionColor = [CPColor colorWithCalibratedRed:95.0 / 255.0 green:131.0 / 255.0 blue:185.0 / 255.0 alpha:1.0]; + _CPMenuItemTextShadowColor = [CPColor colorWithCalibratedRed:26.0 / 255.0 green: 73.0 / 255.0 blue:109.0 / 255.0 alpha:1.0] + + var bundle = [CPBundle bundleForClass:self]; + + _CPMenuItemDefaultStateImages[CPOffState] = nil; + _CPMenuItemDefaultStateHighlightedImages[CPOffState] = nil; + + _CPMenuItemDefaultStateImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnState.png"] size:CGSizeMake(14.0, 14.0)]; + _CPMenuItemDefaultStateHighlightedImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnStateHighlighted.png"] size:CGSizeMake(14.0, 14.0)]; + + _CPMenuItemDefaultStateImages[CPMixedState] = nil; + _CPMenuItemDefaultStateHighlightedImages[CPMixedState] = nil; +} + ++ (id)view +{ + return [[self alloc] init]; +} + +- (id)initWithFrame:(CGRect)aFrame +{ + self = [super initWithFrame:aFrame]; + + if (self) + { + _stateView = [[CPImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 0.0)]; + + [self addSubview:_stateView]; + + _imageAndTextView = [[_CPImageAndTextView alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 0.0)]; + + [_imageAndTextView setImagePosition:CPImageLeft]; + [_imageAndTextView setTextShadowOffset:CGSizeMake(0.0, 1.0)]; + + [self addSubview:_imageAndTextView]; + + _keyEquivalentView = [[_CPImageAndTextView alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 0.0)]; + + [_keyEquivalentView setImagePosition:CPNoImage]; + [_keyEquivalentView setTextShadowOffset:CGSizeMake(0.0, 1.0)]; + [_keyEquivalentView setAutoresizingMask:CPViewMinXMargin]; + + [self addSubview:_keyEquivalentView]; + + _submenuIndicatorView = [[_CPMenuItemSubmenuIndicatorView alloc] initWithFrame:CGRectMake(0.0, 0.0, 8.0, 10.0)]; + + [_submenuIndicatorView setColor:SUBMENU_INDICATOR_COLOR]; + [_submenuIndicatorView setAutoresizingMask:CPViewMinXMargin]; + + [self addSubview:_submenuIndicatorView]; + + [self setAutoresizingMask:CPViewWidthSizable]; + } + + return self; +} + +- (CPColor)textColor +{ + if (![_menuItem isEnabled]) + return [CPColor lightGrayColor]; + + return _textColor || [CPColor colorWithCalibratedRed:70.0 / 255.0 green:69.0 / 255.0 blue:69.0 / 255.0 alpha:1.0]; +} + +- (CPColor)textShadowColor +{ + if (![_menuItem isEnabled]) + return [CPColor colorWithWhite:0.8 alpha:0.8]; + + return _textShadowColor || [CPColor colorWithWhite:1.0 alpha:0.8]; +} + +- (void)update +{ + var x = LEFT_MARGIN + [_menuItem indentationLevel] * INDENTATION_WIDTH, + height = 0.0; + + if (_showsStateColumn) + { + [_stateView setHidden:NO]; + [_stateView setFrameSize:CGSizeMake(STATE_COLUMN_WIDTH, STATE_COLUMN_WIDTH)]; + [_stateView setImage:_CPMenuItemDefaultStateImages[[_menuItem state]] || nil]; + + x += STATE_COLUMN_WIDTH; + } + else + [_stateView setHidden:YES]; + + [_imageAndTextView setFont:[_menuItem font] || _font]; + [_imageAndTextView setVerticalAlignment:CPCenterVerticalTextAlignment]; + [_imageAndTextView setImage:[_menuItem image]]; + [_imageAndTextView setText:[_menuItem title]]; + [_imageAndTextView setTextColor:[self textColor]]; + [_imageAndTextView setTextShadowColor:[self textShadowColor]]; + [_imageAndTextView setTextShadowOffset:CGSizeMake(0, 1)]; + [_imageAndTextView sizeToFit]; + + var imageAndTextViewFrame = [_imageAndTextView frame]; + + imageAndTextViewFrame.origin.x = x; + x += CGRectGetWidth(imageAndTextViewFrame); + height = MAX(height, CGRectGetHeight(imageAndTextViewFrame)); + + var hasKeyEquivalent = !![_menuItem keyEquivalent], + hasSubmenu = [_menuItem hasSubmenu]; + + if (hasKeyEquivalent || hasSubmenu) + x += RIGHT_COLUMNS_MARGIN; + + if (hasKeyEquivalent) + { + [_keyEquivalentView setFont:[_menuItem font] || _font]; + [_keyEquivalentView setVerticalAlignment:CPCenterVerticalTextAlignment]; + [_keyEquivalentView setImage:[_menuItem image]]; + [_keyEquivalentView setText:[_menuItem keyEquivalentStringRepresentation]]; + [_keyEquivalentView setTextColor:[self textColor]]; + [_keyEquivalentView setTextShadowColor:[self textShadowColor]]; + [_keyEquivalentView setTextShadowOffset:CGSizeMake(0, 1)]; + [_keyEquivalentView setFrameOrigin:CGPointMake(x, VERTICAL_MARGIN)]; + [_keyEquivalentView sizeToFit]; + + var keyEquivalentViewFrame = [_keyEquivalentView frame]; + + keyEquivalentViewFrame.origin.x = x; + x += CGRectGetWidth(keyEquivalentViewFrame); + height = MAX(height, CGRectGetHeight(keyEquivalentViewFrame)); + + if (hasSubmenu) + x += RIGHT_COLUMNS_MARGIN; + } + else + [_keyEquivalentView setHidden:YES]; + + if (hasSubmenu) + { + [_submenuIndicatorView setHidden:NO]; + + var submenuViewFrame = [_submenuIndicatorView frame]; + + height = MAX(height, CGRectGetHeight(submenuViewFrame)); + submenuViewFrame.origin.x = x; + } + else + [_submenuIndicatorView setHidden:YES]; + + height += 2.0 * VERTICAL_MARGIN; + + imageAndTextViewFrame.origin.y = FLOOR((height - CGRectGetHeight(imageAndTextViewFrame)) / 2.0); + [_imageAndTextView setFrame:imageAndTextViewFrame]; + + if (hasKeyEquivalent) + { + keyEquivalentViewFrame.origin.y = FLOOR((height - CGRectGetHeight(keyEquivalentViewFrame)) / 2.0); + [_keyEquivalentView setFrame:keyEquivalentViewFrame]; + } + + if (hasSubmenu) + { + submenuViewFrame.origin.y = FLOOR((height - CGRectGetHeight(submenuViewFrame)) / 2.0); + [_submenuIndicatorView setFrame:submenuViewFrame]; + } + + _minSize = CGSizeMake(x + RIGHT_MARGIN + 3.0, height); + + [self setAutoresizesSubviews:NO]; + [self setFrameSize:_minSize]; + [self setAutoresizesSubviews:YES]; +} + +- (void)highlight:(BOOL)shouldHighlight +{ + // FIXME: This should probably be even throw. + if (![_menuItem isEnabled]) + return; + + if (shouldHighlight) + { + [self setBackgroundColor:_CPMenuItemSelectionColor]; + + [_imageAndTextView setTextColor:[CPColor whiteColor]]; + [_keyEquivalentView setTextColor:[CPColor whiteColor]]; + [_submenuIndicatorView setColor:[CPColor whiteColor]]; + + [_imageAndTextView setTextShadowColor:_CPMenuItemTextShadowColor]; + [_keyEquivalentView setTextShadowColor:_CPMenuItemTextShadowColor]; + } + else + { + [self setBackgroundColor:nil]; + + [_imageAndTextView setTextColor:[self textColor]]; + [_keyEquivalentView setTextColor:[self textColor]]; + [_submenuIndicatorView setColor:SUBMENU_INDICATOR_COLOR]; + + [_imageAndTextView setTextShadowColor:[self textShadowColor]]; + [_keyEquivalentView setTextShadowColor:[self textShadowColor]]; + } + + if (shouldHighlight) + [_stateView setImage:_CPMenuItemDefaultStateHighlightedImages[[_menuItem state]] || nil]; + else + [_stateView setImage:_CPMenuItemDefaultStateImages[[_menuItem state]] || nil]; +} + +@end + +@implementation _CPMenuItemSubmenuIndicatorView : CPView +{ + CPColor _color; +} + +- (void)setColor:(CPColor)aColor +{ + if (_color === aColor) + return; + + _color = aColor; + + [self setNeedsDisplay:YES]; +} + +- (void)drawRect:(CGRect)aRect +{ + var context = [[CPGraphicsContext currentContext] graphicsPort], + bounds = [self bounds]; + + CGContextBeginPath(context); + + CGContextMoveToPoint(context, CGRectGetMinX(bounds), CGRectGetMinY(bounds)); + CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), CGRectGetMidY(bounds)); + CGContextAddLineToPoint(context, CGRectGetMinX(bounds), CGRectGetMaxY(bounds)); + + CGContextClosePath(context); + + CGContextSetFillColor(context, _color); + CGContextFillPath(context); +} + +@end diff --git a/AppKit/CPMenuItem/_CPMenuItemView.j b/AppKit/CPMenuItem/_CPMenuItemView.j new file mode 100644 index 000000000..62a77cc9e --- /dev/null +++ b/AppKit/CPMenuItem/_CPMenuItemView.j @@ -0,0 +1,266 @@ + +@import "_CPMenuItemSeparatorView.j" +@import "_CPMenuItemStandardView.j" + + +var LEFT_MARGIN = 3.0, + RIGHT_MARGIN = 16.0, + STATE_COLUMN_WIDTH = 14.0, + INDENTATION_WIDTH = 17.0, + VERTICAL_MARGIN = 4.0; + +var _CPMenuItemSelectionColor = nil, + _CPMenuItemTextShadowColor = nil, + + _CPMenuItemDefaultStateImages = [], + _CPMenuItemDefaultStateHighlightedImages = []; + +/* + @ignore +*/ +@implementation _CPMenuItemView : CPView +{ + CPMenuItem _menuItem; + CPView _view; + + CPFont _font; + CPColor _textColor; + CPColor _textShadowColor; + + CGSize _minSize; + BOOL _isDirty; + BOOL _showsStateColumn; + BOOL _belongsToMenuBar; + + CPImageView _stateView; + _CPImageAndTextView _imageAndTextView; + CPView _submenuView; +} + ++ (void)initialize +{ + if (self != [_CPMenuItemView class]) + return; + + _CPMenuItemSelectionColor = [CPColor colorWithCalibratedRed:95.0 / 255.0 green:131.0 / 255.0 blue:185.0 / 255.0 alpha:1.0]; + _CPMenuItemTextShadowColor = [CPColor colorWithCalibratedRed:26.0 / 255.0 green: 73.0 / 255.0 blue:109.0 / 255.0 alpha:1.0] + + var bundle = [CPBundle bundleForClass:self]; + + _CPMenuItemDefaultStateImages[CPOffState] = nil; + _CPMenuItemDefaultStateHighlightedImages[CPOffState] = nil; + + _CPMenuItemDefaultStateImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnState.png"] size:CGSizeMake(14.0, 14.0)]; + _CPMenuItemDefaultStateHighlightedImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnStateHighlighted.png"] size:CGSizeMake(14.0, 14.0)]; + + _CPMenuItemDefaultStateImages[CPMixedState] = nil; + _CPMenuItemDefaultStateHighlightedImages[CPMixedState] = nil; +} + ++ (float)leftMargin +{ + return LEFT_MARGIN + STATE_COLUMN_WIDTH; +} + +- (id)initWithFrame:(CGRect)aFrame forMenuItem:(CPMenuItem)aMenuItem +{ + self = [super initWithFrame:aFrame]; + + if (self) + { + _menuItem = aMenuItem; + _showsStateColumn = YES; + _isDirty = YES; + + [self setAutoresizingMask:CPViewWidthSizable]; + + [self synchronizeWithMenuItem]; + } + + return self; +} + +- (CGSize)minSize +{ + return _minSize; +} + +- (void)setDirty +{ + _isDirty = YES; +} + +- (void)synchronizeWithMenuItem +{ + var oldView = nil, + menuItemView = [_menuItem view]; + + if ([_menuItem isSeparatorItem]) + { + if (![_view isKindOfClass:[_CPMenuItemSeparatorView class]]) + { + [_view removeFromSuperview]; + _view = [_CPMenuItemSeparatorView view]; + } + } + else if (menuItemView && _view !== menuItemView) + { + [_view removeFromSuperview]; + _view = view; + } + + else + { + if (![_view isKindOfClass:[_CPMenuItemStandardView class]]) + { + [_view removeFromSuperview]; + _view = [_CPMenuItemStandardView view]; + } + + [_view setMenuItem:_menuItem]; + } + + if ([_view superview] !== self) + [self addSubview:_view]; + + if ([_view respondsToSelector:@selector(update)]) + [_view update]; + + _minSize = [_view frame].size; + [self setFrameSize:_minSize]; +} + +- (CGFloat)overlapOffsetWidth +{ + return LEFT_MARGIN + ([[_menuItem menu] showsStateColumn] ? STATE_COLUMN_WIDTH : 0.0); +} + +- (void)setShowsStateColumn:(BOOL)shouldShowStateColumn +{ + _showsStateColumn = shouldShowStateColumn; +} + +- (void)setBelongsToMenuBar:(BOOL)shouldBelongToMenuBar +{ + _belongsToMenuBar = shouldBelongToMenuBar; +} + +- (void)highlight:(BOOL)shouldHighlight +{ + if ([_view respondsToSelector:@selector(highlight:)]) + [_view highlight:shouldHighlight]; +} + +- (void)activate:(BOOL)shouldActivate +{ + [_imageAndTextView setImage:[_menuItem image]]; + + if (shouldActivate) + { + [_imageAndTextView setTextColor:[self activateColor] || [CPColor whiteColor]]; + [_imageAndTextView setTextShadowColor:[self activateShadowColor] || [CPColor blackColor]]; + [_submenuView setColor:[self activateColor] || [CPColor whiteColor]]; + } + else + { + [_imageAndTextView setTextColor:[self textColor]]; + [_imageAndTextView setTextShadowColor:[self textShadowColor]]; + [_submenuView setColor:[self textColor]]; + } +} + +- (BOOL)eventOnSubmenu:(CPEvent)anEvent +{ + if (![_menuItem hasSubmenu]) + return NO; + + return CGRectContainsPoint([_submenuView frame], [self convertPoint:[anEvent locationInWindow] fromView:nil]); +} + +- (BOOL)isHidden +{ + return [_menuItem isHidden]; +} + +- (CPMenuItem)menuItem +{ + return _menuItem; +} + +- (void)setFont:(CPFont)aFont +{ + if (_font == aFont) + return; + + _font = aFont; + + [self setDirty]; +} + +- (void)setTextColor:(CPColor)aColor +{ + if (_textColor == aColor) + return; + + _textColor = aColor; + + [_imageAndTextView setTextColor:[self textColor]]; + [_submenuView setColor:[self textColor]]; +} + +- (CPColor)textColor +{ + return nil; +} + +- (void)setTextShadowColor:(CPColor)aColor +{ + if (_textShadowColor == aColor) + return; + + _textShadowColor = aColor; + + [_imageAndTextView setTextShadowColor:[self textShadowColor]]; + //[_submenuView setColor:[self textColor]]; +} + +- (CPColor)textShadowColor +{ + return [_menuItem isEnabled] ? (_textShadowColor ? _textShadowColor : [CPColor colorWithWhite:1.0 alpha:0.8]) : [CPColor colorWithWhite:0.8 alpha:0.8]; +} + +@end + +@implementation _CPMenuItemArrowView : CPView +{ + CPColor _color; +} + +- (void)setColor:(CPColor)aColor +{ + if (_color == aColor) + return; + + _color = aColor; + + [self setNeedsDisplay:YES]; +} + +- (void)drawRect:(CGRect)aRect +{ + var context = [[CPGraphicsContext currentContext] graphicsPort]; + + CGContextBeginPath(context); + + CGContextMoveToPoint(context, 1.0, 4.0); + CGContextAddLineToPoint(context, 9.0, 4.0); + CGContextAddLineToPoint(context, 5.0, 8.0); + CGContextAddLineToPoint(context, 1.0, 4.0); + + CGContextClosePath(context); + + CGContextSetFillColor(context, _color); + CGContextFillPath(context); +} + +@end \ No newline at end of file