1// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "chrome/browser/ui/cocoa/menu_button.h" 6 7#include "base/logging.h" 8#include "base/memory/scoped_nsobject.h" 9#import "chrome/browser/ui/cocoa/clickhold_button_cell.h" 10 11@interface MenuButton (Private) 12- (void)showMenu:(BOOL)isDragging; 13- (void)clickShowMenu:(id)sender; 14- (void)dragShowMenu:(id)sender; 15@end // @interface MenuButton (Private) 16 17@implementation MenuButton 18 19@synthesize openMenuOnClick = openMenuOnClick_; 20 21// Overrides: 22 23+ (Class)cellClass { 24 return [ClickHoldButtonCell class]; 25} 26 27- (id)init { 28 if ((self = [super init])) 29 [self configureCell]; 30 return self; 31} 32 33- (id)initWithCoder:(NSCoder*)decoder { 34 if ((self = [super initWithCoder:decoder])) 35 [self configureCell]; 36 return self; 37} 38 39- (id)initWithFrame:(NSRect)frameRect { 40 if ((self = [super initWithFrame:frameRect])) 41 [self configureCell]; 42 return self; 43} 44 45- (void)dealloc { 46 self.attachedMenu = nil; 47 [super dealloc]; 48} 49 50- (void)awakeFromNib { 51 [self configureCell]; 52} 53 54- (void)setCell:(NSCell*)cell { 55 [super setCell:cell]; 56 [self configureCell]; 57} 58 59// Accessors and mutators: 60 61- (NSMenu*)attachedMenu { 62 return attachedMenu_.get(); 63} 64 65- (void)setAttachedMenu:(NSMenu*)menu { 66 attachedMenu_.reset([menu retain]); 67 [[self cell] setEnableClickHold:(menu != nil)]; 68} 69 70- (void)setOpenMenuOnClick:(BOOL)enabled { 71 openMenuOnClick_ = enabled; 72 if (enabled) { 73 [[self cell] setClickHoldTimeout:0.0]; // Make menu trigger immediately. 74 [[self cell] setAction:@selector(clickShowMenu:)]; 75 [[self cell] setTarget:self]; 76 } else { 77 [[self cell] setClickHoldTimeout:0.25]; // Default value. 78 } 79} 80 81@end // @implementation MenuButton 82 83@implementation MenuButton (Private) 84 85// Reset various settings of the button and its associated |ClickHoldButtonCell| 86// to the standard state which provides reasonable defaults. 87- (void)configureCell { 88 ClickHoldButtonCell* cell = [self cell]; 89 DCHECK([cell isKindOfClass:[ClickHoldButtonCell class]]); 90 [cell setClickHoldAction:@selector(dragShowMenu:)]; 91 [cell setClickHoldTarget:self]; 92 [cell setEnableClickHold:([self attachedMenu] != nil)]; 93} 94 95// Actually show the menu (in the correct location). |isDragging| indicates 96// whether the mouse button is still down or not. 97- (void)showMenu:(BOOL)isDragging { 98 if (![self attachedMenu]) { 99 LOG(WARNING) << "No menu available."; 100 if (isDragging) { 101 // If we're dragging, wait for mouse up. 102 [NSApp nextEventMatchingMask:NSLeftMouseUpMask 103 untilDate:[NSDate distantFuture] 104 inMode:NSEventTrackingRunLoopMode 105 dequeue:YES]; 106 } 107 return; 108 } 109 110 // TODO(viettrungluu): We have some fudge factors below to make things line up 111 // (approximately). I wish I knew how to get rid of them. (Note that our view 112 // is flipped, and that frame should be in our coordinates.) The y/height is 113 // very odd, since it doesn't seem to respond to changes the way that it 114 // should. I don't understand it. 115 NSRect frame = [self convertRect:[self frame] 116 fromView:[self superview]]; 117 frame.origin.x -= 2.0; 118 frame.size.height += 10.0; 119 120 // Make our pop-up button cell and set things up. This is, as of 10.5, the 121 // official Apple-recommended hack. Later, perhaps |-[NSMenu 122 // popUpMenuPositioningItem:atLocation:inView:]| may be a better option. 123 // However, using a pulldown has the benefit that Cocoa automatically places 124 // the menu correctly even when we're at the edge of the screen (including 125 // "dragging upwards" when the button is close to the bottom of the screen). 126 // A |scoped_nsobject| local variable cannot be used here because 127 // Accessibility on 10.5 grabs the NSPopUpButtonCell without retaining it, and 128 // uses it later. (This is fixed in 10.6.) 129 if (!popUpCell_.get()) { 130 popUpCell_.reset([[NSPopUpButtonCell alloc] initTextCell:@"" 131 pullsDown:YES]); 132 } 133 DCHECK(popUpCell_.get()); 134 [popUpCell_ setMenu:[self attachedMenu]]; 135 [popUpCell_ selectItem:nil]; 136 [popUpCell_ attachPopUpWithFrame:frame 137 inView:self]; 138 [popUpCell_ performClickWithFrame:frame 139 inView:self]; 140} 141 142// Called when the button is clicked and released. (Shouldn't happen with 143// timeout of 0, though there may be some strange pointing devices out there.) 144- (void)clickShowMenu:(id)sender { 145 // This should only be called if openMenuOnClick has been set (which hooks 146 // up this target-action). 147 DCHECK(openMenuOnClick_); 148 [self showMenu:NO]; 149} 150 151// Called when the button is clicked and dragged/held. 152- (void)dragShowMenu:(id)sender { 153 // We shouldn't get here unless the menu is enabled. 154 DCHECK([self attachedMenu]); 155 [self showMenu:YES]; 156} 157 158@end // @implementation MenuButton (Private) 159