summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/cocoa/menu_button.mm
blob: 2bf73789f8bd14d77f7dd978dc9708908382499d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "chrome/browser/ui/cocoa/menu_button.h"

#include "base/logging.h"
#import "chrome/browser/ui/cocoa/clickhold_button_cell.h"
#import "ui/base/cocoa/nsview_additions.h"

@interface MenuButton (Private)
- (void)showMenu:(BOOL)isDragging;
- (void)clickShowMenu:(id)sender;
- (void)dragShowMenu:(id)sender;
@end  // @interface MenuButton (Private)

@implementation MenuButton

@synthesize openMenuOnClick = openMenuOnClick_;
@synthesize openMenuOnRightClick = openMenuOnRightClick_;

// Overrides:

+ (Class)cellClass {
  return [ClickHoldButtonCell class];
}

- (id)init {
  if ((self = [super init]))
    [self configureCell];
  return self;
}

- (id)initWithCoder:(NSCoder*)decoder {
  if ((self = [super initWithCoder:decoder]))
    [self configureCell];
  return self;
}

- (id)initWithFrame:(NSRect)frameRect {
  if ((self = [super initWithFrame:frameRect]))
    [self configureCell];
  return self;
}

- (void)dealloc {
  self.attachedMenu = nil;
  [super dealloc];
}

- (void)awakeFromNib {
  [self configureCell];
}

- (void)setCell:(NSCell*)cell {
  [super setCell:cell];
  [self configureCell];
}

- (void)rightMouseDown:(NSEvent*)theEvent {
  if (!openMenuOnRightClick_) {
    [super rightMouseDown:theEvent];
    return;
  }

  [self clickShowMenu:self];
}

// Accessors and mutators:

- (NSMenu*)attachedMenu {
  return attachedMenu_.get();
}

- (void)setAttachedMenu:(NSMenu*)menu {
  attachedMenu_.reset([menu retain]);
  [[self cell] setEnableClickHold:(menu != nil)];
}

- (void)setOpenMenuOnClick:(BOOL)enabled {
  openMenuOnClick_ = enabled;
  if (enabled) {
    [[self cell] setClickHoldTimeout:0.0];  // Make menu trigger immediately.
    [[self cell] setAction:@selector(clickShowMenu:)];
    [[self cell] setTarget:self];
  } else {
    [[self cell] setClickHoldTimeout:0.25];  // Default value.
  }
}

- (void)setOpenMenuOnRightClick:(BOOL)enabled {
  openMenuOnRightClick_ = enabled;
}

- (NSRect)menuRect {
  return [self bounds];
}

@end  // @implementation MenuButton

@implementation MenuButton (Private)

// Reset various settings of the button and its associated |ClickHoldButtonCell|
// to the standard state which provides reasonable defaults.
- (void)configureCell {
  ClickHoldButtonCell* cell = [self cell];
  DCHECK([cell isKindOfClass:[ClickHoldButtonCell class]]);
  [cell setClickHoldAction:@selector(dragShowMenu:)];
  [cell setClickHoldTarget:self];
  [cell setEnableClickHold:([self attachedMenu] != nil)];
}

// Actually show the menu (in the correct location). |isDragging| indicates
// whether the mouse button is still down or not.
- (void)showMenu:(BOOL)isDragging {
  if (![self attachedMenu]) {
    LOG(WARNING) << "No menu available.";
    if (isDragging) {
      // If we're dragging, wait for mouse up.
      [NSApp nextEventMatchingMask:NSLeftMouseUpMask
                         untilDate:[NSDate distantFuture]
                            inMode:NSEventTrackingRunLoopMode
                           dequeue:YES];
    }
    return;
  }

  // TODO(viettrungluu): We have some fudge factors below to make things line up
  // (approximately). I wish I knew how to get rid of them. (Note that our view
  // is flipped, and that frame should be in our coordinates.) The y/height is
  // very odd, since it doesn't seem to respond to changes the way that it
  // should. I don't understand it.
  NSRect frame = [self menuRect];
  frame.origin.x -= 2.0;
  frame.size.height -= 19.0 - NSHeight(frame);

  // Make our pop-up button cell and set things up. This is, as of 10.5, the
  // official Apple-recommended hack. Later, perhaps |-[NSMenu
  // popUpMenuPositioningItem:atLocation:inView:]| may be a better option.
  // However, using a pulldown has the benefit that Cocoa automatically places
  // the menu correctly even when we're at the edge of the screen (including
  // "dragging upwards" when the button is close to the bottom of the screen).
  // A |scoped_nsobject| local variable cannot be used here because
  // Accessibility on 10.5 grabs the NSPopUpButtonCell without retaining it, and
  // uses it later. (This is fixed in 10.6.)
  if (!popUpCell_.get()) {
    popUpCell_.reset([[NSPopUpButtonCell alloc] initTextCell:@""
                                                   pullsDown:YES]);
  }
  DCHECK(popUpCell_.get());
  [popUpCell_ setMenu:[self attachedMenu]];
  [popUpCell_ selectItem:nil];
  [popUpCell_ attachPopUpWithFrame:frame inView:self];
  [popUpCell_ performClickWithFrame:frame inView:self];

  // Once the menu is dismissed send a mouseExited event if necessary. If the
  // menu action caused the super view to resize then we won't automatically
  // get a mouseExited event so we need to do this manually.
  // See http://crbug.com/82456
  if (![self cr_isMouseInView]) {
    if ([[self cell] respondsToSelector:@selector(mouseExited:)])
      [[self cell] mouseExited:nil];
  }
}

// Called when the button is clicked and released. (Shouldn't happen with
// timeout of 0, though there may be some strange pointing devices out there.)
- (void)clickShowMenu:(id)sender {
  // This should only be called if openMenuOnClick has been set (which hooks
  // up this target-action).
  DCHECK(openMenuOnClick_ || openMenuOnRightClick_);
  [self showMenu:NO];
}

// Called when the button is clicked and dragged/held.
- (void)dragShowMenu:(id)sender {
  // We shouldn't get here unless the menu is enabled.
  DCHECK([self attachedMenu]);
  [self showMenu:YES];
}

@end  // @implementation MenuButton (Private)