blob: 7dce0a6bbc89ad605c8338da753f1b03c9cca43f [file] [log] [blame]
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "webkit/glue/webmenurunner_mac.h"
#include "base/sys_string_conversions.h"
#if !defined(MAC_OS_X_VERSION_10_6) || \
MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
enum {
NSUserInterfaceLayoutDirectionLeftToRight = 0,
NSUserInterfaceLayoutDirectionRightToLeft = 1
};
typedef NSInteger NSUserInterfaceLayoutDirection;
@interface NSCell (SnowLeopardSDKDeclarations)
- (void)setUserInterfaceLayoutDirection:
(NSUserInterfaceLayoutDirection)layoutDirection;
@end
enum {
NSTextWritingDirectionEmbedding = (0 << 1),
NSTextWritingDirectionOverride = (1 << 1)
};
static NSString* NSWritingDirectionAttributeName = @"NSWritingDirection";
#endif
@interface WebMenuRunner (PrivateAPI)
// Worker function used during initialization.
- (void)addItem:(const WebMenuItem&)item;
// A callback for the menu controller object to call when an item is selected
// from the menu. This is not called if the menu is dismissed without a
// selection.
- (void)menuItemSelected:(id)sender;
@end // WebMenuRunner (PrivateAPI)
@implementation WebMenuRunner
- (id)initWithItems:(const std::vector<WebMenuItem>&)items
fontSize:(CGFloat)fontSize
rightAligned:(BOOL)rightAligned {
if ((self = [super init])) {
menu_.reset([[NSMenu alloc] initWithTitle:@""]);
[menu_ setAutoenablesItems:NO];
index_ = -1;
fontSize_ = fontSize;
rightAligned_ = rightAligned;
for (size_t i = 0; i < items.size(); ++i)
[self addItem:items[i]];
}
return self;
}
- (void)addItem:(const WebMenuItem&)item {
if (item.type == WebMenuItem::SEPARATOR) {
[menu_ addItem:[NSMenuItem separatorItem]];
return;
}
NSString* title = base::SysUTF16ToNSString(item.label);
NSMenuItem* menuItem = [menu_ addItemWithTitle:title
action:@selector(menuItemSelected:)
keyEquivalent:@""];
[menuItem setEnabled:(item.enabled && item.type != WebMenuItem::GROUP)];
[menuItem setTarget:self];
// Set various alignment/language attributes. Note that many (if not most) of
// these attributes are functional only on 10.6 and above.
scoped_nsobject<NSMutableDictionary> attrs(
[[NSMutableDictionary alloc] initWithCapacity:3]);
scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
[[NSMutableParagraphStyle alloc] init]);
[paragraphStyle setAlignment:rightAligned_ ? NSRightTextAlignment
: NSLeftTextAlignment];
NSWritingDirection writingDirection =
item.rtl ? NSWritingDirectionRightToLeft
: NSWritingDirectionLeftToRight;
[paragraphStyle setBaseWritingDirection:writingDirection];
[attrs setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
if (item.has_directional_override) {
scoped_nsobject<NSNumber> directionValue(
[[NSNumber alloc] initWithInteger:
writingDirection + NSTextWritingDirectionOverride]);
scoped_nsobject<NSArray> directionArray(
[[NSArray alloc] initWithObjects:directionValue.get(), nil]);
[attrs setObject:directionArray forKey:NSWritingDirectionAttributeName];
}
[attrs setObject:[NSFont menuFontOfSize:fontSize_]
forKey:NSFontAttributeName];
scoped_nsobject<NSAttributedString> attrTitle(
[[NSAttributedString alloc] initWithString:title
attributes:attrs]);
[menuItem setAttributedTitle:attrTitle];
[menuItem setTag:[menu_ numberOfItems] - 1];
}
// Reflects the result of the user's interaction with the popup menu. If NO, the
// menu was dismissed without the user choosing an item, which can happen if the
// user clicked outside the menu region or hit the escape key. If YES, the user
// selected an item from the menu.
- (BOOL)menuItemWasChosen {
return menuItemWasChosen_;
}
- (void)menuItemSelected:(id)sender {
menuItemWasChosen_ = YES;
}
- (void)runMenuInView:(NSView*)view
withBounds:(NSRect)bounds
initialIndex:(int)index {
// Set up the button cell, converting to NSView coordinates. The menu is
// positioned such that the currently selected menu item appears over the
// popup button, which is the expected Mac popup menu behavior.
NSPopUpButtonCell* button = [[NSPopUpButtonCell alloc] initTextCell:@""
pullsDown:NO];
[button autorelease];
[button setMenu:menu_];
// We use selectItemWithTag below so if the index is out-of-bounds nothing
// bad happens.
[button selectItemWithTag:index];
if (rightAligned_ &&
[button respondsToSelector:@selector(setUserInterfaceLayoutDirection:)]) {
[button setUserInterfaceLayoutDirection:
NSUserInterfaceLayoutDirectionRightToLeft];
}
// Create a dummy view to associate the popup with, since the OS will use
// that view for positioning the menu.
NSView* dummyView = [[[NSView alloc] initWithFrame:bounds] autorelease];
[view addSubview:dummyView];
NSRect dummyBounds = [dummyView convertRect:bounds fromView:view];
// Display the menu, and set a flag if a menu item was chosen.
[button performClickWithFrame:dummyBounds inView:dummyView];
if ([self menuItemWasChosen])
index_ = [button indexOfSelectedItem];
[dummyView removeFromSuperview];
}
- (int)indexOfSelectedItem {
return index_;
}
@end // WebMenuRunner
namespace webkit_glue {
// Helper function for manufacturing input events to send to WebKit.
NSEvent* EventWithMenuAction(BOOL item_chosen, int window_num,
int item_height, int selected_index,
NSRect menu_bounds, NSRect view_bounds) {
NSEvent* event = nil;
double event_time = (double)(AbsoluteToDuration(UpTime())) / 1000.0;
if (item_chosen) {
// Construct a mouse up event to simulate the selection of an appropriate
// menu item.
NSPoint click_pos;
click_pos.x = menu_bounds.size.width / 2;
// This is going to be hard to calculate since the button is painted by
// WebKit, the menu by Cocoa, and we have to translate the selected_item
// index to a coordinate that WebKit's PopupMenu expects which uses a
// different font *and* expects to draw the menu below the button like we do
// on Windows.
// The WebKit popup menu thinks it will draw just below the button, so
// create the click at the offset based on the selected item's index and
// account for the different coordinate system used by NSView.
int item_offset = selected_index * item_height + item_height / 2;
click_pos.y = view_bounds.size.height - item_offset;
event = [NSEvent mouseEventWithType:NSLeftMouseUp
location:click_pos
modifierFlags:0
timestamp:event_time
windowNumber:window_num
context:nil
eventNumber:0
clickCount:1
pressure:1.0];
} else {
// Fake an ESC key event (keyCode = 0x1B, from webinputevent_mac.mm) and
// forward that to WebKit.
NSPoint key_pos;
key_pos.x = 0;
key_pos.y = 0;
NSString* escape_str = [NSString stringWithFormat:@"%c", 0x1B];
event = [NSEvent keyEventWithType:NSKeyDown
location:key_pos
modifierFlags:0
timestamp:event_time
windowNumber:window_num
context:nil
characters:@""
charactersIgnoringModifiers:escape_str
isARepeat:NO
keyCode:0x1B];
}
return event;
}
} // namespace webkit_glue