diff --git a/AppKit/CPMenu/CPMenu.j b/AppKit/CPMenu/CPMenu.j index 059c918b7..b5cf9a850 100644 --- a/AppKit/CPMenu/CPMenu.j +++ b/AppKit/CPMenu/CPMenu.j @@ -536,7 +536,6 @@ var _CPMenuBarVisible = NO, */ - (void)submenuAction:(id)aSender { - } /*! @@ -584,11 +583,30 @@ var _CPMenuBarVisible = NO, } /*! - Not implemented. + Enables or disables the receiver’s menu items. + If the target does not implement the menu item's action method the item is disabled. + If the target responsds to selector validateMenuItem: or validateUserInterfaceItem: (in that order) the return value is used. */ - (void)update { + if (![self autoenablesItems]) + return; + var items = [self itemArray]; + for (var i = 0; i < [items count]; i++) + { + var item = [items objectAtIndex:i], + validator = [CPApp targetForAction:[item action] to:[item target] from:item]; + + if (!validator || ![validator respondsToSelector:[item action]]) + [item _setEnabled:NO]; + else if ([validator respondsToSelector:@selector(validateMenuItem:)]) + [item _setEnabled:[validator validateMenuItem:item]]; + else if ([validator respondsToSelector:@selector(validateUserInterfaceItem:)]) + [item _setEnabled:[validator validateUserInterfaceItem:item]]; + } + + [[_menuWindow _menuView] tile]; } // Managing the Title @@ -1100,6 +1118,8 @@ var CPMenuTitleKey = @"CPMenuTitleKey", _showsStateColumn = ![aCoder containsValueForKey:CPMenuShowsStateColumnKey] || [aCoder decodeBoolForKey:CPMenuShowsStateColumnKey]; + _autoenablesItems = YES; + [self setMinimumWidth:0]; } diff --git a/AppKit/CPMenu/_CPMenuManager.j b/AppKit/CPMenu/_CPMenuManager.j index a95bc5fba..15af786fb 100644 --- a/AppKit/CPMenu/_CPMenuManager.j +++ b/AppKit/CPMenu/_CPMenuManager.j @@ -243,9 +243,15 @@ var STICKY_TIME_INTERVAL = 500, // Close the current menu item because we are going to select a new one after a short delay [self showMenu:nil fromMenu:activeMenu atPoint:CGPointMakeZero()]; - _showTimerID = setTimeout(function() { + + if (![activeMenuContainer isMenuBar]) + { + _showTimerID = setTimeout(function() { + [self showMenu:[activeItem submenu] fromMenu:[activeItem menu] atPoint:newMenuOrigin]; + }, 250); + } + else [self showMenu:[activeItem submenu] fromMenu:[activeItem menu] atPoint:newMenuOrigin]; - }, 250); } } diff --git a/AppKit/CPMenu/_CPMenuWindow.j b/AppKit/CPMenu/_CPMenuWindow.j index cb52e9822..053d493d8 100644 --- a/AppKit/CPMenu/_CPMenuWindow.j +++ b/AppKit/CPMenu/_CPMenuWindow.j @@ -208,8 +208,14 @@ var STICKY_TIME_INTERVAL = 500, return [_menuView menu]; } +- (_CPMenuView)_menuView +{ + return _menuView; +} + - (void)orderFront:(id)aSender { + [[self menu] update]; [self setFrame:_unconstrainedFrame]; [super orderFront:aSender]; diff --git a/AppKit/CPMenuItem/CPMenuItem.j b/AppKit/CPMenuItem/CPMenuItem.j index e0dcd305e..fd8f117db 100644 --- a/AppKit/CPMenuItem/CPMenuItem.j +++ b/AppKit/CPMenuItem/CPMenuItem.j @@ -133,10 +133,17 @@ if ([_menu autoenablesItems]) return; - _isEnabled = isEnabled; + [self _setEnabled:isEnabled]; +} + +- (void)_setEnabled:(BOOL)isEnabled +{ + if (_isEnabled === isEnabled) + return; + + _isEnabled = !!isEnabled; [_menuItemView setDirty]; - [_menu itemChanged:self]; } @@ -851,6 +858,11 @@ CPControlKeyMask return ![self submenu] && [self menu] === [CPApp mainMenu]; } +- (CPString)description +{ + return [super description] + @" target: " + [self target] + @" action: " + CPStringFromSelector([self action]); +} + @end var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey", diff --git a/AppKit/Cib/CPCibControlConnector.j b/AppKit/Cib/CPCibControlConnector.j index 68934e572..ddb64d8c3 100644 --- a/AppKit/Cib/CPCibControlConnector.j +++ b/AppKit/Cib/CPCibControlConnector.j @@ -47,12 +47,8 @@ // If the destination doesn't respond to this selector, warn but don't die. if (_destination && ![_destination respondsToSelector:selector]) - { CPLog.warn(@"Could not connect the action " + selector + @" to target of class " + [_destination className]); - return; - } - // Not being able to set the action is a fatal error. if ([_source respondsToSelector:@selector(setAction:)]) objj_msgSend(_source, @selector(setAction:), selector); diff --git a/Tests/AppKit/CPMenuValidatedUserInterfaceItemTest.j b/Tests/AppKit/CPMenuValidatedUserInterfaceItemTest.j new file mode 100644 index 000000000..e4fbb2115 --- /dev/null +++ b/Tests/AppKit/CPMenuValidatedUserInterfaceItemTest.j @@ -0,0 +1,52 @@ +@implementation CPMenuValidatedUserInterfaceItemTest : OJTestCase +{ + CPMenu _menu @accessors(property=menu); +} + +- (void)setUp +{ + [[CPApplication sharedApplication] setDelegate:[[MenuTarget alloc] init]]; + + _menu = [[CPMenu alloc] init]; + + [_menu addItem:[[CPMenuItem alloc] initWithTitle:@"implemented" action:@selector(implementedAction:) keyEquivalent:nil]]; + [_menu addItem:[[CPMenuItem alloc] initWithTitle:@"disabled" action:@selector(disabledAction:) keyEquivalent:nil]]; + [_menu addItem:[[CPMenuItem alloc] initWithTitle:@"unimplemented" action:@selector(unimplementedAction:) keyEquivalent:nil]]; +} + +- (void)testAutoenable +{ + // Update is what actually performs the autoenabling so we need to call it manually. + // It's normally called automatically just before a menu becomes visible. + [[self menu] update]; + + [self assertTrue:[[[self menu] itemWithTitle:@"implemented"] isEnabled] message:@"The implemented action should be enabled"]; + [self assertFalse:[[[self menu] itemWithTitle:@"disabled"] isEnabled] message:@"The disbabled action should be disabled"]; + [self assertFalse:[[[self menu] itemWithTitle:@"unimplemented"] isEnabled] message:@"The unimplemented action should be disabled"]; +} + +@end + +@implementation MenuTarget : CPObject +{ +} + +- (BOOL)validateMenuItem:(CPMenuItem)theMenuItem +{ + if ([theMenuItem action] === @selector(disabledAction:)) + return NO; + + return YES; +} + +- (@action)implementedAction:(id)theSender +{ + +} + +- (@action)disabledAction:(id)theSender +{ + +} + +@end \ No newline at end of file