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 11 enum { 12 NSUserInterfaceLayoutDirectionLeftToRight = 0, 13 NSUserInterfaceLayoutDirectionRightToLeft = 1 14 }; 15 typedef NSInteger NSUserInterfaceLayoutDirection; 16 17 @interface NSCell (SnowLeopardSDKDeclarations) 18 - (void)setUserInterfaceLayoutDirection: 19 (NSUserInterfaceLayoutDirection)layoutDirection; 20 @end 21 22 enum { 23 NSTextWritingDirectionEmbedding = (0 << 1), 24 NSTextWritingDirectionOverride = (1 << 1) 25 }; 26 27 static 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 159 namespace webkit_glue { 160 161 // Helper function for manufacturing input events to send to WebKit. 162 NSEvent* 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