// 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" #include "base/memory/scoped_nsobject.h" #import "chrome/browser/ui/cocoa/clickhold_button_cell.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_; // 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]; } // 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. } } @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 convertRect:[self frame] fromView:[self superview]]; frame.origin.x -= 2.0; frame.size.height += 10.0; // 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]; } // 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_); [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)