Home | History | Annotate | Download | only in glue
      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