mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
1010 lines
27 KiB
Plaintext
1010 lines
27 KiB
Plaintext
/*
|
|
* 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 <Foundation/CPArray.j>
|
|
@import <Foundation/CPDictionary.j>
|
|
@import <Foundation/CPNotificationCenter.j>
|
|
@import <Foundation/CPString.j>
|
|
|
|
@import "_CPMenuManager.j"
|
|
@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 = 28.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;
|
|
|
|
float _minimumWidth;
|
|
|
|
CPMutableArray _items;
|
|
|
|
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;
|
|
|
|
[self setMinimumWidth:0];
|
|
}
|
|
|
|
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 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)setMinimumWidth:(float)aMinimumWidth
|
|
{
|
|
_minimumWidth = aMinimumWidth;
|
|
}
|
|
|
|
- (float)minimumWidth
|
|
{
|
|
return _minimumWidth;
|
|
}
|
|
|
|
//
|
|
+ (CGRect)_constraintRectForView:(CPView)aView
|
|
{
|
|
if ([CPPlatform isBrowser])
|
|
return CGRectInset([[[aView window] platformWindow] contentBounds], 5.0, 5.0);
|
|
|
|
return CGRectInset([[[aView window] screen] visibleFrame], 5.0, 5.0);
|
|
}
|
|
|
|
- (void)popUpMenuPositioningItem:(CPMenuItem)anItem atLocation:(CGPoint)aLocation inView:(CPView)aView callback:(Function)aCallback
|
|
{
|
|
var itemIndex = [self indexOfItem:anItem];
|
|
|
|
if (anItem && itemIndex === CPNotFound)
|
|
throw "uh oh";
|
|
|
|
if (aView && ![aView window])
|
|
throw "uh oh 2";
|
|
var aFont = nil;
|
|
if (!aFont)
|
|
aFont = [CPFont systemFontOfSize:12.0];
|
|
|
|
var theWindow = [aView window],
|
|
menuWindow = [_CPMenuWindow menuWindowWithMenu:self font:aFont];
|
|
|
|
[menuWindow setDelegate:self];
|
|
[menuWindow setBackgroundStyle:_CPMenuWindowPopUpBackgroundStyle];
|
|
|
|
var globalLocation = aLocation;
|
|
|
|
if (aView)
|
|
globalLocation = [[aView window] convertBaseToGlobal:[aView convertPoint:aLocation toView:nil]];
|
|
|
|
if (anItem)
|
|
{
|
|
// Don't convert this value to global, we care about the distance (delta) from the
|
|
// the edge of the window, which is equivalent to its origin.
|
|
var itemOrigin = [menuWindow rectForItemAtIndex:itemIndex].origin;
|
|
|
|
globalLocation.y -= itemOrigin.y;
|
|
}
|
|
|
|
[menuWindow setFrameOrigin:globalLocation];
|
|
|
|
var delegate = [self delegate];
|
|
|
|
if ([delegate respondsToSelector:@selector(menuWillOpen:)])
|
|
[delegate menuWillOpen:aMenu];
|
|
|
|
var constraintRect = [CPMenu _constraintRectForView:aView];
|
|
|
|
[menuWindow setConstraintRect:constraintRect];
|
|
[menuWindow orderFront:self];
|
|
|
|
[[_CPMenuManager sharedMenuManager]
|
|
beginTracking:[CPApp currentEvent]
|
|
menuContainer:menuWindow
|
|
constraintRect:constraintRect
|
|
callback:[CPMenu trackingCallbackWithCallback:aCallback]];
|
|
}
|
|
|
|
+ (Function)trackingCallbackWithCallback:(Function)aCallback
|
|
{
|
|
return function(aMenuWindow, aMenu)
|
|
{
|
|
[aMenuWindow setMenu:nil];
|
|
[aMenuWindow orderOut:self];
|
|
|
|
[_CPMenuWindow poolMenuWindow:aMenuWindow];
|
|
|
|
if (aCallback)
|
|
aCallback(aMenu);
|
|
|
|
var highlightedItem = [aMenu highlightedItem];
|
|
|
|
while ([highlightedItem submenu] && [highlightedItem action] === @selector(submenuAction:))
|
|
highlightedItem = [[highlightedItem submenu] highlightedItem];
|
|
|
|
if (highlightedItem)
|
|
[CPApp sendAction:[highlightedItem action] to:[highlightedItem target] from:highlightedItem];
|
|
}
|
|
}
|
|
|
|
+ (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];
|
|
|
|
// FIXME: Why do we need this?
|
|
[menuWindow setDelegate:self];
|
|
[menuWindow setBackgroundStyle:isForMenuBar ? _CPMenuWindowMenuBarBackgroundStyle : _CPMenuWindowPopUpBackgroundStyle];
|
|
|
|
|
|
var constraintRect = [CPMenu _constraintRectForView:aView];
|
|
|
|
[menuWindow setConstraintRect:constraintRect];
|
|
[menuWindow setFrameOrigin:[[anEvent window] convertBaseToGlobal:[anEvent locationInWindow]]];
|
|
[menuWindow orderFront:self];
|
|
|
|
[[_CPMenuManager sharedMenuManager]
|
|
beginTracking:anEvent
|
|
menuContainer:menuWindow
|
|
constraintRect:[CPMenu _constraintRectForView:aView]
|
|
callback:[CPMenu trackingCallbackWithCallback:nil]];
|
|
}
|
|
|
|
// 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] | ([item keyEquivalent] === [[item keyEquivalent] uppercaseString] ? CPShiftKeyMask : 0);
|
|
|
|
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];
|
|
|
|
[self setMinimumWidth:0];
|
|
}
|
|
|
|
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"
|
|
|