summaryrefslogtreecommitdiffstats
path: root/webkit/glue/webmenurunner_mac.mm
blob: e4c8a4b5e20fa20e7a7ed16114510c3171089e3d (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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// Copyright (c) 2009 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.

#include "webkit/glue/webmenurunner_mac.h"

#include "base/sys_string_conversions.h"

namespace {

const CGFloat kPopupXOffset = -10.0f;
BOOL gNewNSMenuAPI;

}  // namespace

#if !defined(MAC_OS_X_VERSION_10_6) || \
    MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
@interface NSMenu (SnowLeopardSDKDeclarations)
- (BOOL)popUpMenuPositioningItem:(NSMenuItem *)item
                      atLocation:(NSPoint)location
                          inView:(NSView *)view;
- (void)setFont:(NSFont *)font;
@end
#endif

@interface WebMenuRunner (PrivateAPI)

// Worker function used during initialization.
- (void)addItem:(const WebMenuItem&)item
 withAttributes:(NSDictionary*)attrs;

// A callback for the menu controller object to call when an item is selected
// from the menu. This is not called if the menu is dismissed without a
// selection.
- (void)menuItemSelected:(id)sender;

@end  // WebMenuRunner (PrivateAPI)

@implementation WebMenuRunner

- (id)initWithItems:(const std::vector<WebMenuItem>&)items
           fontSize:(CGFloat)fontSize
       rightAligned:(BOOL)rightAligned {
  static BOOL newNSMenuAPIInitialized = NO;
  if (!newNSMenuAPIInitialized) {
    newNSMenuAPIInitialized = YES;
    gNewNSMenuAPI = [NSMenu instancesRespondToSelector:
        @selector(popUpMenuPositioningItem:atLocation:inView:)] &&
        [NSMenu instancesRespondToSelector:@selector(setFont:)];
  }

  if ((self = [super init])) {
    menu_.reset([[NSMenu alloc] initWithTitle:@""]);
    if (gNewNSMenuAPI)
      [menu_ setFont:[NSFont menuFontOfSize:fontSize]];
    [menu_ setAutoenablesItems:NO];
    index_ = -1;
    fontSize_ = fontSize;
    scoped_nsobject<NSDictionary> attrs;
    if (rightAligned) {
      // NB: Right-aligning menu items in this manner is known to not work in
      // Mac OS X 10.5.
      scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
          [[NSMutableParagraphStyle alloc] init]);
      [paragraphStyle setAlignment:NSRightTextAlignment];
      attrs.reset([[NSDictionary alloc] initWithObjectsAndKeys:
          paragraphStyle, NSParagraphStyleAttributeName, nil]);
    }
    for (size_t i = 0; i < items.size(); ++i)
      [self addItem:items[i] withAttributes:attrs];
  }
  return self;
}

- (void)addItem:(const WebMenuItem&)item
 withAttributes:(NSDictionary*)attrs {
  if (item.type == WebMenuItem::SEPARATOR) {
    [menu_ addItem:[NSMenuItem separatorItem]];
    return;
  }

  NSString* title = base::SysUTF16ToNSString(item.label);
  NSMenuItem* menuItem = [menu_ addItemWithTitle:title
                                          action:@selector(menuItemSelected:)
                                   keyEquivalent:@""];
  [menuItem setEnabled:(item.enabled && item.type != WebMenuItem::GROUP)];
  [menuItem setTarget:self];
  if (attrs) {
    scoped_nsobject<NSAttributedString> attrTitle(
        [[NSAttributedString alloc] initWithString:title
                                        attributes:attrs]);
    [menuItem setAttributedTitle:attrTitle];
  }
  if (gNewNSMenuAPI)
    [menuItem setTag:[menu_ numberOfItems] - 1];
}

// Reflects the result of the user's interaction with the popup menu. If NO, the
// menu was dismissed without the user choosing an item, which can happen if the
// user clicked outside the menu region or hit the escape key. If YES, the user
// selected an item from the menu.
- (BOOL)menuItemWasChosen {
  return menuItemWasChosen_;
}

- (void)menuItemSelected:(id)sender {
  menuItemWasChosen_ = YES;
  if (gNewNSMenuAPI)
    index_ = [sender tag];
}

- (void)runMenuInView:(NSView*)view
           withBounds:(NSRect)bounds
         initialIndex:(int)index {
  if (gNewNSMenuAPI) {
    NSMenuItem* selectedItem = [menu_ itemAtIndex:index];
    [selectedItem setState:NSOnState];
    NSPoint anchor = NSMakePoint(NSMinX(bounds) + kPopupXOffset,
                                 NSMaxY(bounds));
    [menu_ popUpMenuPositioningItem:selectedItem
                         atLocation:anchor
                             inView:view];
  } else {
    // Set up the button cell, converting to NSView coordinates. The menu is
    // positioned such that the currently selected menu item appears over the
    // popup button, which is the expected Mac popup menu behavior.
    NSPopUpButtonCell* button = [[NSPopUpButtonCell alloc] initTextCell:@""
                                                              pullsDown:NO];
    [button autorelease];
    [button setMenu:menu_];
    [button selectItemAtIndex:index];
    [button setFont:[NSFont menuFontOfSize:fontSize_]];

    // Display the menu, and set a flag if a menu item was chosen.
    [button performClickWithFrame:bounds inView:view];

    if ([self menuItemWasChosen])
      index_ = [button indexOfSelectedItem];
  }
}

- (int)indexOfSelectedItem {
  return index_;
}

@end  // WebMenuRunner

namespace webkit_glue {

// Helper function for manufacturing input events to send to WebKit.
NSEvent* EventWithMenuAction(BOOL item_chosen, int window_num,
                             int item_height, int selected_index,
                             NSRect menu_bounds, NSRect view_bounds) {
  NSEvent* event = nil;
  double event_time = (double)(AbsoluteToDuration(UpTime())) / 1000.0;

  if (item_chosen) {
    // Construct a mouse up event to simulate the selection of an appropriate
    // menu item.
    NSPoint click_pos;
    click_pos.x = menu_bounds.size.width / 2;

    // This is going to be hard to calculate since the button is painted by
    // WebKit, the menu by Cocoa, and we have to translate the selected_item
    // index to a coordinate that WebKit's PopupMenu expects which uses a
    // different font *and* expects to draw the menu below the button like we do
    // on Windows.
    // The WebKit popup menu thinks it will draw just below the button, so
    // create the click at the offset based on the selected item's index and
    // account for the different coordinate system used by NSView.
    int item_offset = selected_index * item_height + item_height / 2;
    click_pos.y = view_bounds.size.height - item_offset;
    event = [NSEvent mouseEventWithType:NSLeftMouseUp
                               location:click_pos
                          modifierFlags:0
                              timestamp:event_time
                           windowNumber:window_num
                                context:nil
                            eventNumber:0
                             clickCount:1
                               pressure:1.0];
  } else {
    // Fake an ESC key event (keyCode = 0x1B, from webinputevent_mac.mm) and
    // forward that to WebKit.
    NSPoint key_pos;
    key_pos.x = 0;
    key_pos.y = 0;
    NSString* escape_str = [NSString stringWithFormat:@"%c", 0x1B];
    event = [NSEvent keyEventWithType:NSKeyDown
                             location:key_pos
                        modifierFlags:0
                            timestamp:event_time
                         windowNumber:window_num
                              context:nil
                           characters:@""
          charactersIgnoringModifiers:escape_str
                            isARepeat:NO
                              keyCode:0x1B];
  }

  return event;
}

}  // namespace webkit_glue