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