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. 4 5 #include "content/browser/renderer_host/webmenurunner_mac.h" 6 7 #include "base/strings/sys_string_conversions.h" 8 9 @interface WebMenuRunner (PrivateAPI) 10 11 // Worker function used during initialization. 12 - (void)addItem:(const content::MenuItem&)item; 13 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 16 // selection. 17 - (void)menuItemSelected:(id)sender; 18 19 @end // WebMenuRunner (PrivateAPI) 20 21 @implementation WebMenuRunner 22 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]; 29 index_ = -1; 30 fontSize_ = fontSize; 31 rightAligned_ = rightAligned; 32 for (size_t i = 0; i < items.size(); ++i) 33 [self addItem:items[i]]; 34 } 35 return self; 36 } 37 38 - (void)addItem:(const content::MenuItem&)item { 39 if (item.type == content::MenuItem::SEPARATOR) { 40 [menu_ addItem:[NSMenuItem separatorItem]]; 41 return; 42 } 43 44 NSString* title = base::SysUTF16ToNSString(item.label); 45 NSMenuItem* menuItem = [menu_ addItemWithTitle:title 46 action:@selector(menuItemSelected:) 47 keyEquivalent:@""]; 48 if (!item.tool_tip.empty()) { 49 NSString* toolTip = base::SysUTF16ToNSString(item.tool_tip); 50 [menuItem setToolTip:toolTip]; 51 } 52 [menuItem setEnabled:(item.enabled && item.type != content::MenuItem::GROUP)]; 53 [menuItem setTarget:self]; 54 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]; 68 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]; 76 } 77 78 [attrs setObject:[NSFont menuFontOfSize:fontSize_] 79 forKey:NSFontAttributeName]; 80 81 base::scoped_nsobject<NSAttributedString> attrTitle( 82 [[NSAttributedString alloc] initWithString:title attributes:attrs]); 83 [menuItem setAttributedTitle:attrTitle]; 84 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]]; 92 93 [menuItem setTag:[menu_ numberOfItems] - 1]; 94 } 95 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_; 102 } 103 104 - (void)menuItemSelected:(id)sender { 105 menuItemWasChosen_ = YES; 106 } 107 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 118 // bad happens. 119 [cell selectItemWithTag:index]; 120 121 if (rightAligned_ && 122 [cell respondsToSelector:@selector(setUserInterfaceLayoutDirection:)]) { 123 [cell setUserInterfaceLayoutDirection: 124 NSUserInterfaceLayoutDirectionRightToLeft]; 125 } 126 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 132 // immediately. 133 // 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]; 140 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]; 144 145 [dummyView removeFromSuperview]; 146 147 if ([self menuItemWasChosen]) 148 index_ = [cell indexOfSelectedItem]; 149 } 150 151 - (void)hide { 152 [menu_ cancelTracking]; 153 } 154 155 - (int)indexOfSelectedItem { 156 return index_; 157 } 158 159 @end // WebMenuRunner 160