• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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#include "webkit/glue/webmenurunner_mac.h"
6
7#include "base/sys_string_conversions.h"
8
9#if !defined(MAC_OS_X_VERSION_10_6) || \
10    MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
11enum {
12  NSUserInterfaceLayoutDirectionLeftToRight = 0,
13  NSUserInterfaceLayoutDirectionRightToLeft = 1
14};
15typedef NSInteger NSUserInterfaceLayoutDirection;
16
17@interface NSCell (SnowLeopardSDKDeclarations)
18- (void)setUserInterfaceLayoutDirection:
19    (NSUserInterfaceLayoutDirection)layoutDirection;
20@end
21
22enum {
23  NSTextWritingDirectionEmbedding = (0 << 1),
24  NSTextWritingDirectionOverride = (1 << 1)
25};
26
27static NSString* NSWritingDirectionAttributeName = @"NSWritingDirection";
28#endif
29
30@interface WebMenuRunner (PrivateAPI)
31
32// Worker function used during initialization.
33- (void)addItem:(const WebMenuItem&)item;
34
35// A callback for the menu controller object to call when an item is selected
36// from the menu. This is not called if the menu is dismissed without a
37// selection.
38- (void)menuItemSelected:(id)sender;
39
40@end  // WebMenuRunner (PrivateAPI)
41
42@implementation WebMenuRunner
43
44- (id)initWithItems:(const std::vector<WebMenuItem>&)items
45           fontSize:(CGFloat)fontSize
46       rightAligned:(BOOL)rightAligned {
47  if ((self = [super init])) {
48    menu_.reset([[NSMenu alloc] initWithTitle:@""]);
49    [menu_ setAutoenablesItems:NO];
50    index_ = -1;
51    fontSize_ = fontSize;
52    rightAligned_ = rightAligned;
53    for (size_t i = 0; i < items.size(); ++i)
54      [self addItem:items[i]];
55  }
56  return self;
57}
58
59- (void)addItem:(const WebMenuItem&)item {
60  if (item.type == WebMenuItem::SEPARATOR) {
61    [menu_ addItem:[NSMenuItem separatorItem]];
62    return;
63  }
64
65  NSString* title = base::SysUTF16ToNSString(item.label);
66  NSMenuItem* menuItem = [menu_ addItemWithTitle:title
67                                          action:@selector(menuItemSelected:)
68                                   keyEquivalent:@""];
69  [menuItem setEnabled:(item.enabled && item.type != WebMenuItem::GROUP)];
70  [menuItem setTarget:self];
71
72  // Set various alignment/language attributes. Note that many (if not most) of
73  // these attributes are functional only on 10.6 and above.
74  scoped_nsobject<NSMutableDictionary> attrs(
75      [[NSMutableDictionary alloc] initWithCapacity:3]);
76  scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
77      [[NSMutableParagraphStyle alloc] init]);
78  [paragraphStyle setAlignment:rightAligned_ ? NSRightTextAlignment
79                                             : NSLeftTextAlignment];
80  NSWritingDirection writingDirection =
81      item.rtl ? NSWritingDirectionRightToLeft
82               : NSWritingDirectionLeftToRight;
83  [paragraphStyle setBaseWritingDirection:writingDirection];
84  [attrs setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
85
86  if (item.has_directional_override) {
87    scoped_nsobject<NSNumber> directionValue(
88        [[NSNumber alloc] initWithInteger:
89            writingDirection + NSTextWritingDirectionOverride]);
90    scoped_nsobject<NSArray> directionArray(
91        [[NSArray alloc] initWithObjects:directionValue.get(), nil]);
92    [attrs setObject:directionArray forKey:NSWritingDirectionAttributeName];
93  }
94
95  [attrs setObject:[NSFont menuFontOfSize:fontSize_]
96            forKey:NSFontAttributeName];
97
98  scoped_nsobject<NSAttributedString> attrTitle(
99      [[NSAttributedString alloc] initWithString:title
100                                      attributes:attrs]);
101  [menuItem setAttributedTitle:attrTitle];
102
103  [menuItem setTag:[menu_ numberOfItems] - 1];
104}
105
106// Reflects the result of the user's interaction with the popup menu. If NO, the
107// menu was dismissed without the user choosing an item, which can happen if the
108// user clicked outside the menu region or hit the escape key. If YES, the user
109// selected an item from the menu.
110- (BOOL)menuItemWasChosen {
111  return menuItemWasChosen_;
112}
113
114- (void)menuItemSelected:(id)sender {
115  menuItemWasChosen_ = YES;
116}
117
118- (void)runMenuInView:(NSView*)view
119           withBounds:(NSRect)bounds
120         initialIndex:(int)index {
121  // Set up the button cell, converting to NSView coordinates. The menu is
122  // positioned such that the currently selected menu item appears over the
123  // popup button, which is the expected Mac popup menu behavior.
124  NSPopUpButtonCell* button = [[NSPopUpButtonCell alloc] initTextCell:@""
125                                                            pullsDown:NO];
126  [button autorelease];
127  [button setMenu:menu_];
128  // We use selectItemWithTag below so if the index is out-of-bounds nothing
129  // bad happens.
130  [button selectItemWithTag:index];
131
132  if (rightAligned_ &&
133      [button respondsToSelector:@selector(setUserInterfaceLayoutDirection:)]) {
134    [button setUserInterfaceLayoutDirection:
135        NSUserInterfaceLayoutDirectionRightToLeft];
136  }
137
138  // Create a dummy view to associate the popup with, since the OS will use
139  // that view for positioning the menu.
140  NSView* dummyView = [[[NSView alloc] initWithFrame:bounds] autorelease];
141  [view addSubview:dummyView];
142  NSRect dummyBounds = [dummyView convertRect:bounds fromView:view];
143
144  // Display the menu, and set a flag if a menu item was chosen.
145  [button performClickWithFrame:dummyBounds inView:dummyView];
146
147  if ([self menuItemWasChosen])
148    index_ = [button indexOfSelectedItem];
149
150  [dummyView removeFromSuperview];
151}
152
153- (int)indexOfSelectedItem {
154  return index_;
155}
156
157@end  // WebMenuRunner
158
159namespace webkit_glue {
160
161// Helper function for manufacturing input events to send to WebKit.
162NSEvent* EventWithMenuAction(BOOL item_chosen, int window_num,
163                             int item_height, int selected_index,
164                             NSRect menu_bounds, NSRect view_bounds) {
165  NSEvent* event = nil;
166  double event_time = (double)(AbsoluteToDuration(UpTime())) / 1000.0;
167
168  if (item_chosen) {
169    // Construct a mouse up event to simulate the selection of an appropriate
170    // menu item.
171    NSPoint click_pos;
172    click_pos.x = menu_bounds.size.width / 2;
173
174    // This is going to be hard to calculate since the button is painted by
175    // WebKit, the menu by Cocoa, and we have to translate the selected_item
176    // index to a coordinate that WebKit's PopupMenu expects which uses a
177    // different font *and* expects to draw the menu below the button like we do
178    // on Windows.
179    // The WebKit popup menu thinks it will draw just below the button, so
180    // create the click at the offset based on the selected item's index and
181    // account for the different coordinate system used by NSView.
182    int item_offset = selected_index * item_height + item_height / 2;
183    click_pos.y = view_bounds.size.height - item_offset;
184    event = [NSEvent mouseEventWithType:NSLeftMouseUp
185                               location:click_pos
186                          modifierFlags:0
187                              timestamp:event_time
188                           windowNumber:window_num
189                                context:nil
190                            eventNumber:0
191                             clickCount:1
192                               pressure:1.0];
193  } else {
194    // Fake an ESC key event (keyCode = 0x1B, from webinputevent_mac.mm) and
195    // forward that to WebKit.
196    NSPoint key_pos;
197    key_pos.x = 0;
198    key_pos.y = 0;
199    NSString* escape_str = [NSString stringWithFormat:@"%c", 0x1B];
200    event = [NSEvent keyEventWithType:NSKeyDown
201                             location:key_pos
202                        modifierFlags:0
203                            timestamp:event_time
204                         windowNumber:window_num
205                              context:nil
206                           characters:@""
207          charactersIgnoringModifiers:escape_str
208                            isARepeat:NO
209                              keyCode:0x1B];
210  }
211
212  return event;
213}
214
215}  // namespace webkit_glue
216