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.
5 #import "chrome/browser/ui/cocoa/menu_button.h"
7 #include "base/logging.h"
8 #import "chrome/browser/ui/cocoa/clickhold_button_cell.h"
9 #import "chrome/browser/ui/cocoa/nsview_additions.h"
11 @interface MenuButton (Private)
12 - (void)showMenu:(BOOL)isDragging;
13 - (void)clickShowMenu:(id)sender;
14 - (void)dragShowMenu:(id)sender;
15 @end // @interface MenuButton (Private)
17 @implementation MenuButton
19 @synthesize openMenuOnClick = openMenuOnClick_;
20 @synthesize openMenuOnRightClick = openMenuOnRightClick_;
25 return [ClickHoldButtonCell class];
29 if ((self = [super init]))
34 - (id)initWithCoder:(NSCoder*)decoder {
35 if ((self = [super initWithCoder:decoder]))
40 - (id)initWithFrame:(NSRect)frameRect {
41 if ((self = [super initWithFrame:frameRect]))
47 self.attachedMenu = nil;
51 - (void)awakeFromNib {
55 - (void)setCell:(NSCell*)cell {
60 - (void)rightMouseDown:(NSEvent*)theEvent {
61 if (!openMenuOnRightClick_) {
62 [super rightMouseDown:theEvent];
66 [self clickShowMenu:self];
69 // Accessors and mutators:
71 - (NSMenu*)attachedMenu {
72 return attachedMenu_.get();
75 - (void)setAttachedMenu:(NSMenu*)menu {
76 attachedMenu_.reset([menu retain]);
77 [[self cell] setEnableClickHold:(menu != nil)];
80 - (void)setOpenMenuOnClick:(BOOL)enabled {
81 openMenuOnClick_ = enabled;
83 [[self cell] setClickHoldTimeout:0.0]; // Make menu trigger immediately.
84 [[self cell] setAction:@selector(clickShowMenu:)];
85 [[self cell] setTarget:self];
87 [[self cell] setClickHoldTimeout:0.25]; // Default value.
91 - (void)setOpenMenuOnRightClick:(BOOL)enabled {
92 openMenuOnRightClick_ = enabled;
99 @end // @implementation MenuButton
101 @implementation MenuButton (Private)
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)];
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.";
119 // If we're dragging, wait for mouse up.
120 [NSApp nextEventMatchingMask:NSLeftMouseUpMask
121 untilDate:[NSDate distantFuture]
122 inMode:NSEventTrackingRunLoopMode
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);
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:@""
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];
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];
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_);
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]);
182 @end // @implementation MenuButton (Private)