Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / menu_button.mm
blob10520d5c144677298d7bd11e7e75e9817a0dafa5
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 "ui/base/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_;
22 // Overrides:
24 + (Class)cellClass {
25   return [ClickHoldButtonCell class];
28 - (id)init {
29   if ((self = [super init]))
30     [self configureCell];
31   return self;
34 - (id)initWithCoder:(NSCoder*)decoder {
35   if ((self = [super initWithCoder:decoder]))
36     [self configureCell];
37   return self;
40 - (id)initWithFrame:(NSRect)frameRect {
41   if ((self = [super initWithFrame:frameRect]))
42     [self configureCell];
43   return self;
46 - (void)dealloc {
47   self.attachedMenu = nil;
48   [super dealloc];
51 - (void)awakeFromNib {
52   [self configureCell];
55 - (void)setCell:(NSCell*)cell {
56   [super setCell:cell];
57   [self configureCell];
60 - (void)rightMouseDown:(NSEvent*)theEvent {
61   if (!openMenuOnRightClick_) {
62     [super rightMouseDown:theEvent];
63     return;
64   }
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 configureCell];
80 - (void)setOpenMenuOnClick:(BOOL)enabled {
81   openMenuOnClick_ = enabled;
82   [self configureCell];
85 - (void)setOpenMenuOnRightClick:(BOOL)enabled {
86   openMenuOnRightClick_ = enabled;
87   [self configureCell];
90 - (NSRect)menuRect {
91   return [self bounds];
94 @end  // @implementation MenuButton
96 @implementation MenuButton (Private)
98 // Synchronize the state of this class with its ClickHoldButtonCell.
99 - (void)configureCell {
100   ClickHoldButtonCell* cell = [self cell];
101   DCHECK([cell isKindOfClass:[ClickHoldButtonCell class]]);
103   if (![self attachedMenu]) {
104     [cell setEnableClickHold:NO];
105     [cell setEnableRightClick:NO];
106     [cell setClickHoldAction:nil];
107     [cell setClickHoldTarget:nil];
108     [cell setAccessibilityShowMenuAction:nil];
109     [cell setAccessibilityShowMenuTarget:nil];
110     return;
111   }
113   if (openMenuOnClick_) {
114     [cell setClickHoldTimeout:0.0];  // Make menu trigger immediately.
115     [cell setAction:@selector(clickShowMenu:)];
116     [cell setTarget:self];
117   } else {
118     [cell setClickHoldTimeout:0.25];  // Default value.
119   }
120   // Even when openMenuOnClick_ is true, click hold action and target still
121   // need to be set in order to allow classic Mac menu behavior.
122   [cell setEnableClickHold:YES];
123   [cell setClickHoldAction:@selector(dragShowMenu:)];
124   [cell setClickHoldTarget:self];
126   [cell setEnableRightClick:openMenuOnRightClick_];
127   if (!openMenuOnClick_ || openMenuOnRightClick_) {
128     [cell setAccessibilityShowMenuAction:@selector(clickShowMenu:)];
129     [cell setAccessibilityShowMenuTarget:self];
130   } else {
131     [cell setAccessibilityShowMenuAction:nil];
132     [cell setAccessibilityShowMenuTarget:nil];
133   }
136 // Actually show the menu (in the correct location). |isDragging| indicates
137 // whether the mouse button is still down or not.
138 - (void)showMenu:(BOOL)isDragging {
139   if (![self attachedMenu]) {
140     LOG(WARNING) << "No menu available.";
141     if (isDragging) {
142       // If we're dragging, wait for mouse up.
143       [NSApp nextEventMatchingMask:NSLeftMouseUpMask
144                          untilDate:[NSDate distantFuture]
145                             inMode:NSEventTrackingRunLoopMode
146                            dequeue:YES];
147     }
148     return;
149   }
151   // TODO(viettrungluu): We have some fudge factors below to make things line up
152   // (approximately). I wish I knew how to get rid of them. (Note that our view
153   // is flipped, and that frame should be in our coordinates.) The y/height is
154   // very odd, since it doesn't seem to respond to changes the way that it
155   // should. I don't understand it.
156   NSRect frame = [self menuRect];
157   frame.origin.x -= 2.0;
158   frame.size.height -= 19.0 - NSHeight(frame);
160   // Make our pop-up button cell and set things up. This is, as of 10.5, the
161   // official Apple-recommended hack. Later, perhaps |-[NSMenu
162   // popUpMenuPositioningItem:atLocation:inView:]| may be a better option.
163   // However, using a pulldown has the benefit that Cocoa automatically places
164   // the menu correctly even when we're at the edge of the screen (including
165   // "dragging upwards" when the button is close to the bottom of the screen).
166   // A |scoped_nsobject| local variable cannot be used here because
167   // Accessibility on 10.5 grabs the NSPopUpButtonCell without retaining it, and
168   // uses it later. (This is fixed in 10.6.)
169   if (!popUpCell_.get()) {
170     popUpCell_.reset([[NSPopUpButtonCell alloc] initTextCell:@""
171                                                    pullsDown:YES]);
172   }
173   DCHECK(popUpCell_.get());
174   [popUpCell_ setMenu:[self attachedMenu]];
175   [popUpCell_ selectItem:nil];
176   [popUpCell_ attachPopUpWithFrame:frame inView:self];
177   [popUpCell_ performClickWithFrame:frame inView:self];
179   // Once the menu is dismissed send a mouseExited event if necessary. If the
180   // menu action caused the super view to resize then we won't automatically
181   // get a mouseExited event so we need to do this manually.
182   // See http://crbug.com/82456
183   if (![self cr_isMouseInView]) {
184     if ([[self cell] respondsToSelector:@selector(mouseExited:)])
185       [[self cell] mouseExited:nil];
186   }
189 // Called when the button is clicked and released. (Shouldn't happen with
190 // timeout of 0, though there may be some strange pointing devices out there.)
191 - (void)clickShowMenu:(id)sender {
192   // We shouldn't get here unless the menu is enabled.
193   DCHECK([self attachedMenu]);
194   [self showMenu:NO];
197 // Called when the button is clicked and dragged/held.
198 - (void)dragShowMenu:(id)sender {
199   // We shouldn't get here unless the menu is enabled.
200   DCHECK([self attachedMenu]);
201   [self showMenu:YES];
204 @end  // @implementation MenuButton (Private)