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