blob: 8d274013e1300c0b7bd23e347e4ad221b7e5056f [file] [log] [blame]
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/menu_button.h"
#include "base/logging.h"
#import "chrome/browser/ui/cocoa/clickhold_button_cell.h"
#import "chrome/browser/ui/cocoa/nsview_additions.h"
@interface MenuButton (Private)
- (void)showMenu:(BOOL)isDragging;
- (void)clickShowMenu:(id)sender;
- (void)dragShowMenu:(id)sender;
@end // @interface MenuButton (Private)
@implementation MenuButton
@synthesize openMenuOnClick = openMenuOnClick_;
@synthesize openMenuOnRightClick = openMenuOnRightClick_;
// Overrides:
+ (Class)cellClass {
return [ClickHoldButtonCell class];
}
- (id)init {
if ((self = [super init]))
[self configureCell];
return self;
}
- (id)initWithCoder:(NSCoder*)decoder {
if ((self = [super initWithCoder:decoder]))
[self configureCell];
return self;
}
- (id)initWithFrame:(NSRect)frameRect {
if ((self = [super initWithFrame:frameRect]))
[self configureCell];
return self;
}
- (void)dealloc {
self.attachedMenu = nil;
[super dealloc];
}
- (void)awakeFromNib {
[self configureCell];
}
- (void)setCell:(NSCell*)cell {
[super setCell:cell];
[self configureCell];
}
- (void)rightMouseDown:(NSEvent*)theEvent {
if (!openMenuOnRightClick_) {
[super rightMouseDown:theEvent];
return;
}
[self clickShowMenu:self];
}
// Accessors and mutators:
- (NSMenu*)attachedMenu {
return attachedMenu_.get();
}
- (void)setAttachedMenu:(NSMenu*)menu {
attachedMenu_.reset([menu retain]);
[[self cell] setEnableClickHold:(menu != nil)];
}
- (void)setOpenMenuOnClick:(BOOL)enabled {
openMenuOnClick_ = enabled;
if (enabled) {
[[self cell] setClickHoldTimeout:0.0]; // Make menu trigger immediately.
[[self cell] setAction:@selector(clickShowMenu:)];
[[self cell] setTarget:self];
} else {
[[self cell] setClickHoldTimeout:0.25]; // Default value.
}
}
- (void)setOpenMenuOnRightClick:(BOOL)enabled {
openMenuOnRightClick_ = enabled;
}
- (NSRect)menuRect {
return [self bounds];
}
@end // @implementation MenuButton
@implementation MenuButton (Private)
// Reset various settings of the button and its associated |ClickHoldButtonCell|
// to the standard state which provides reasonable defaults.
- (void)configureCell {
ClickHoldButtonCell* cell = [self cell];
DCHECK([cell isKindOfClass:[ClickHoldButtonCell class]]);
[cell setClickHoldAction:@selector(dragShowMenu:)];
[cell setClickHoldTarget:self];
[cell setEnableClickHold:([self attachedMenu] != nil)];
}
// Actually show the menu (in the correct location). |isDragging| indicates
// whether the mouse button is still down or not.
- (void)showMenu:(BOOL)isDragging {
if (![self attachedMenu]) {
LOG(WARNING) << "No menu available.";
if (isDragging) {
// If we're dragging, wait for mouse up.
[NSApp nextEventMatchingMask:NSLeftMouseUpMask
untilDate:[NSDate distantFuture]
inMode:NSEventTrackingRunLoopMode
dequeue:YES];
}
return;
}
// TODO(viettrungluu): We have some fudge factors below to make things line up
// (approximately). I wish I knew how to get rid of them. (Note that our view
// is flipped, and that frame should be in our coordinates.) The y/height is
// very odd, since it doesn't seem to respond to changes the way that it
// should. I don't understand it.
NSRect frame = [self menuRect];
frame.origin.x -= 2.0;
frame.size.height -= 19.0 - NSHeight(frame);
// Make our pop-up button cell and set things up. This is, as of 10.5, the
// official Apple-recommended hack. Later, perhaps |-[NSMenu
// popUpMenuPositioningItem:atLocation:inView:]| may be a better option.
// However, using a pulldown has the benefit that Cocoa automatically places
// the menu correctly even when we're at the edge of the screen (including
// "dragging upwards" when the button is close to the bottom of the screen).
// A |scoped_nsobject| local variable cannot be used here because
// Accessibility on 10.5 grabs the NSPopUpButtonCell without retaining it, and
// uses it later. (This is fixed in 10.6.)
if (!popUpCell_.get()) {
popUpCell_.reset([[NSPopUpButtonCell alloc] initTextCell:@""
pullsDown:YES]);
}
DCHECK(popUpCell_.get());
[popUpCell_ setMenu:[self attachedMenu]];
[popUpCell_ selectItem:nil];
[popUpCell_ attachPopUpWithFrame:frame inView:self];
[popUpCell_ performClickWithFrame:frame inView:self];
// Once the menu is dismissed send a mouseExited event if necessary. If the
// menu action caused the super view to resize then we won't automatically
// get a mouseExited event so we need to do this manually.
// See http://crbug.com/82456
if (![self cr_isMouseInView]) {
if ([[self cell] respondsToSelector:@selector(mouseExited:)])
[[self cell] mouseExited:nil];
}
}
// Called when the button is clicked and released. (Shouldn't happen with
// timeout of 0, though there may be some strange pointing devices out there.)
- (void)clickShowMenu:(id)sender {
// This should only be called if openMenuOnClick has been set (which hooks
// up this target-action).
DCHECK(openMenuOnClick_ || openMenuOnRightClick_);
[self showMenu:NO];
}
// Called when the button is clicked and dragged/held.
- (void)dragShowMenu:(id)sender {
// We shouldn't get here unless the menu is enabled.
DCHECK([self attachedMenu]);
[self showMenu:YES];
}
@end // @implementation MenuButton (Private)