1 // Copyright 2013 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 #include "content/browser/renderer_host/webmenurunner_mac.h"
7 #include "base/strings/sys_string_conversions.h"
9 @interface WebMenuRunner (PrivateAPI)
11 // Worker function used during initialization.
12 - (void)addItem:(const content::MenuItem&)item;
14 // A callback for the menu controller object to call when an item is selected
15 // from the menu. This is not called if the menu is dismissed without a
17 - (void)menuItemSelected:(id)sender;
19 @end // WebMenuRunner (PrivateAPI)
21 @implementation WebMenuRunner
23 - (id)initWithItems:(const std::vector<content::MenuItem>&)items
24 fontSize:(CGFloat)fontSize
25 rightAligned:(BOOL)rightAligned {
26 if ((self = [super init])) {
27 menu_.reset([[NSMenu alloc] initWithTitle:@""]);
28 [menu_ setAutoenablesItems:NO];
31 rightAligned_ = rightAligned;
32 for (size_t i = 0; i < items.size(); ++i)
33 [self addItem:items[i]];
38 - (void)addItem:(const content::MenuItem&)item {
39 if (item.type == content::MenuItem::SEPARATOR) {
40 [menu_ addItem:[NSMenuItem separatorItem]];
44 NSString* title = base::SysUTF16ToNSString(item.label);
45 NSMenuItem* menuItem = [menu_ addItemWithTitle:title
46 action:@selector(menuItemSelected:)
48 if (!item.tool_tip.empty()) {
49 NSString* toolTip = base::SysUTF16ToNSString(item.tool_tip);
50 [menuItem setToolTip:toolTip];
52 [menuItem setEnabled:(item.enabled && item.type != content::MenuItem::GROUP)];
53 [menuItem setTarget:self];
55 // Set various alignment/language attributes. Note that many (if not most) of
56 // these attributes are functional only on 10.6 and above.
57 base::scoped_nsobject<NSMutableDictionary> attrs(
58 [[NSMutableDictionary alloc] initWithCapacity:3]);
59 base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
60 [[NSMutableParagraphStyle alloc] init]);
61 [paragraphStyle setAlignment:rightAligned_ ? NSRightTextAlignment
62 : NSLeftTextAlignment];
63 NSWritingDirection writingDirection =
64 item.rtl ? NSWritingDirectionRightToLeft
65 : NSWritingDirectionLeftToRight;
66 [paragraphStyle setBaseWritingDirection:writingDirection];
67 [attrs setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
69 if (item.has_directional_override) {
70 base::scoped_nsobject<NSNumber> directionValue(
71 [[NSNumber alloc] initWithInteger:
72 writingDirection + NSTextWritingDirectionOverride]);
73 base::scoped_nsobject<NSArray> directionArray(
74 [[NSArray alloc] initWithObjects:directionValue.get(), nil]);
75 [attrs setObject:directionArray forKey:NSWritingDirectionAttributeName];
78 [attrs setObject:[NSFont menuFontOfSize:fontSize_]
79 forKey:NSFontAttributeName];
81 base::scoped_nsobject<NSAttributedString> attrTitle(
82 [[NSAttributedString alloc] initWithString:title attributes:attrs]);
83 [menuItem setAttributedTitle:attrTitle];
85 // We set the title as well as the attributed title here. The attributed title
86 // will be displayed in the menu, but typeahead will use the non-attributed
87 // string that doesn't contain any leading or trailing whitespace. This is
88 // what Apple uses in WebKit as well:
89 // http://trac.webkit.org/browser/trunk/Source/WebKit2/UIProcess/mac/WebPopupMenuProxyMac.mm#L90
90 NSCharacterSet* whitespaceSet = [NSCharacterSet whitespaceCharacterSet];
91 [menuItem setTitle:[title stringByTrimmingCharactersInSet:whitespaceSet]];
93 [menuItem setTag:[menu_ numberOfItems] - 1];
96 // Reflects the result of the user's interaction with the popup menu. If NO, the
97 // menu was dismissed without the user choosing an item, which can happen if the
98 // user clicked outside the menu region or hit the escape key. If YES, the user
99 // selected an item from the menu.
100 - (BOOL)menuItemWasChosen {
101 return menuItemWasChosen_;
104 - (void)menuItemSelected:(id)sender {
105 menuItemWasChosen_ = YES;
108 - (void)runMenuInView:(NSView*)view
109 withBounds:(NSRect)bounds
110 initialIndex:(int)index {
111 // Set up the button cell, converting to NSView coordinates. The menu is
112 // positioned such that the currently selected menu item appears over the
113 // popup button, which is the expected Mac popup menu behavior.
114 base::scoped_nsobject<NSPopUpButtonCell> cell(
115 [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO]);
116 [cell setMenu:menu_];
117 // We use selectItemWithTag below so if the index is out-of-bounds nothing
119 [cell selectItemWithTag:index];
122 [cell respondsToSelector:@selector(setUserInterfaceLayoutDirection:)]) {
123 [cell setUserInterfaceLayoutDirection:
124 NSUserInterfaceLayoutDirectionRightToLeft];
127 // When popping up a menu near the Dock, Cocoa restricts the menu
128 // size to not overlap the Dock, with a scroll arrow. Below a
129 // certain point this doesn't work. At that point the menu is
130 // popped up above the element, so that the current item can be
131 // selected without mouse-tracking selecting a different item
134 // Unfortunately, instead of popping up above the passed |bounds|,
135 // it pops up above the bounds of the view passed to inView:. Use a
136 // dummy view to fake this out.
137 base::scoped_nsobject<NSView> dummyView(
138 [[NSView alloc] initWithFrame:bounds]);
139 [view addSubview:dummyView];
141 // Display the menu, and set a flag if a menu item was chosen.
142 [cell attachPopUpWithFrame:[dummyView bounds] inView:dummyView];
143 [cell performClickWithFrame:[dummyView bounds] inView:dummyView];
145 [dummyView removeFromSuperview];
147 if ([self menuItemWasChosen])
148 index_ = [cell indexOfSelectedItem];
152 [menu_ cancelTracking];
155 - (int)indexOfSelectedItem {
159 @end // WebMenuRunner