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 #import "chrome/browser/ui/cocoa/clickhold_button_cell.h" 9 #import "chrome/browser/ui/cocoa/nsview_additions.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 @synthesize openMenuOnRightClick = openMenuOnRightClick_; 21 22 // Overrides: 23 24 + (Class)cellClass { 25 return [ClickHoldButtonCell class]; 26 } 27 28 - (id)init { 29 if ((self = [super init])) 30 [self configureCell]; 31 return self; 32 } 33 34 - (id)initWithCoder:(NSCoder*)decoder { 35 if ((self = [super initWithCoder:decoder])) 36 [self configureCell]; 37 return self; 38 } 39 40 - (id)initWithFrame:(NSRect)frameRect { 41 if ((self = [super initWithFrame:frameRect])) 42 [self configureCell]; 43 return self; 44 } 45 46 - (void)dealloc { 47 self.attachedMenu = nil; 48 [super dealloc]; 49 } 50 51 - (void)awakeFromNib { 52 [self configureCell]; 53 } 54 55 - (void)setCell:(NSCell*)cell { 56 [super setCell:cell]; 57 [self configureCell]; 58 } 59 60 - (void)rightMouseDown:(NSEvent*)theEvent { 61 if (!openMenuOnRightClick_) { 62 [super rightMouseDown:theEvent]; 63 return; 64 } 65 66 [self clickShowMenu:self]; 67 } 68 69 // Accessors and mutators: 70 71 - (NSMenu*)attachedMenu { 72 return attachedMenu_.get(); 73 } 74 75 - (void)setAttachedMenu:(NSMenu*)menu { 76 attachedMenu_.reset([menu retain]); 77 [[self cell] setEnableClickHold:(menu != nil)]; 78 } 79 80 - (void)setOpenMenuOnClick:(BOOL)enabled { 81 openMenuOnClick_ = enabled; 82 if (enabled) { 83 [[self cell] setClickHoldTimeout:0.0]; // Make menu trigger immediately. 84 [[self cell] setAction:@selector(clickShowMenu:)]; 85 [[self cell] setTarget:self]; 86 } else { 87 [[self cell] setClickHoldTimeout:0.25]; // Default value. 88 } 89 } 90 91 - (void)setOpenMenuOnRightClick:(BOOL)enabled { 92 openMenuOnRightClick_ = enabled; 93 } 94 95 - (NSRect)menuRect { 96 return [self bounds]; 97 } 98 99 @end // @implementation MenuButton 100 101 @implementation MenuButton (Private) 102 103 // Reset various settings of the button and its associated |ClickHoldButtonCell| 104 // to the standard state which provides reasonable defaults. 105 - (void)configureCell { 106 ClickHoldButtonCell* cell = [self cell]; 107 DCHECK([cell isKindOfClass:[ClickHoldButtonCell class]]); 108 [cell setClickHoldAction:@selector(dragShowMenu:)]; 109 [cell setClickHoldTarget:self]; 110 [cell setEnableClickHold:([self attachedMenu] != nil)]; 111 } 112 113 // Actually show the menu (in the correct location). |isDragging| indicates 114 // whether the mouse button is still down or not. 115 - (void)showMenu:(BOOL)isDragging { 116 if (![self attachedMenu]) { 117 LOG(WARNING) << "No menu available."; 118 if (isDragging) { 119 // If we're dragging, wait for mouse up. 120 [NSApp nextEventMatchingMask:NSLeftMouseUpMask 121 untilDate:[NSDate distantFuture] 122 inMode:NSEventTrackingRunLoopMode 123 dequeue:YES]; 124 } 125 return; 126 } 127 128 // TODO(viettrungluu): We have some fudge factors below to make things line up 129 // (approximately). I wish I knew how to get rid of them. (Note that our view 130 // is flipped, and that frame should be in our coordinates.) The y/height is 131 // very odd, since it doesn't seem to respond to changes the way that it 132 // should. I don't understand it. 133 NSRect frame = [self menuRect]; 134 frame.origin.x -= 2.0; 135 frame.size.height -= 19.0 - NSHeight(frame); 136 137 // Make our pop-up button cell and set things up. This is, as of 10.5, the 138 // official Apple-recommended hack. Later, perhaps |-[NSMenu 139 // popUpMenuPositioningItem:atLocation:inView:]| may be a better option. 140 // However, using a pulldown has the benefit that Cocoa automatically places 141 // the menu correctly even when we're at the edge of the screen (including 142 // "dragging upwards" when the button is close to the bottom of the screen). 143 // A |scoped_nsobject| local variable cannot be used here because 144 // Accessibility on 10.5 grabs the NSPopUpButtonCell without retaining it, and 145 // uses it later. (This is fixed in 10.6.) 146 if (!popUpCell_.get()) { 147 popUpCell_.reset([[NSPopUpButtonCell alloc] initTextCell:@"" 148 pullsDown:YES]); 149 } 150 DCHECK(popUpCell_.get()); 151 [popUpCell_ setMenu:[self attachedMenu]]; 152 [popUpCell_ selectItem:nil]; 153 [popUpCell_ attachPopUpWithFrame:frame inView:self]; 154 [popUpCell_ performClickWithFrame:frame inView:self]; 155 156 // Once the menu is dismissed send a mouseExited event if necessary. If the 157 // menu action caused the super view to resize then we won't automatically 158 // get a mouseExited event so we need to do this manually. 159 // See http://crbug.com/82456 160 if (![self cr_isMouseInView]) { 161 if ([[self cell] respondsToSelector:@selector(mouseExited:)]) 162 [[self cell] mouseExited:nil]; 163 } 164 } 165 166 // Called when the button is clicked and released. (Shouldn't happen with 167 // timeout of 0, though there may be some strange pointing devices out there.) 168 - (void)clickShowMenu:(id)sender { 169 // This should only be called if openMenuOnClick has been set (which hooks 170 // up this target-action). 171 DCHECK(openMenuOnClick_ || openMenuOnRightClick_); 172 [self showMenu:NO]; 173 } 174 175 // Called when the button is clicked and dragged/held. 176 - (void)dragShowMenu:(id)sender { 177 // We shouldn't get here unless the menu is enabled. 178 DCHECK([self attachedMenu]); 179 [self showMenu:YES]; 180 } 181 182 @end // @implementation MenuButton (Private) 183