1/* 2 * Copyright (C) 2006, 2008, 2010, 2011 Apple Inc. All rights reserved. 3 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Library General Public 7 * License as published by the Free Software Foundation; either 8 * version 2 of the License, or (at your option) any later version. 9 * 10 * This library is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Library General Public License for more details. 14 * 15 * You should have received a copy of the GNU Library General Public License 16 * along with this library; see the file COPYING.LIB. If not, write to 17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 * Boston, MA 02110-1301, USA. 19 */ 20 21#import "PopupMenuMac.h" 22 23#import "WebDelegateImplementationCaching.h" 24#import "WebFrameInternal.h" 25#import <WebCore/IntRect.h> 26#import <WebCore/AXObjectCache.h> 27#import <WebCore/BlockExceptions.h> 28#import <WebCore/Chrome.h> 29#import <WebCore/ChromeClient.h> 30#import <WebCore/EventHandler.h> 31#import <WebCore/Frame.h> 32#import <WebCore/FrameView.h> 33#import <WebCore/Page.h> 34#import <WebCore/PopupMenuClient.h> 35#import <WebCore/SimpleFontData.h> 36#import <WebCore/WebCoreSystemInterface.h> 37 38using namespace WebCore; 39 40PopupMenuMac::PopupMenuMac(PopupMenuClient* client) 41 : m_client(client) 42{ 43} 44 45PopupMenuMac::~PopupMenuMac() 46{ 47 [m_popup setControlView:nil]; 48} 49 50void PopupMenuMac::clear() 51{ 52 [m_popup removeAllItems]; 53} 54 55void PopupMenuMac::populate() 56{ 57 if (m_popup) 58 clear(); 59 else { 60 m_popup = adoptNS([[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:!m_client->shouldPopOver()]); 61 [m_popup setUsesItemFromMenu:NO]; 62 [m_popup setAutoenablesItems:NO]; 63 } 64 65 BOOL messagesEnabled = [[m_popup menu] menuChangedMessagesEnabled]; 66 [[m_popup menu] setMenuChangedMessagesEnabled:NO]; 67 68 // For pullDown menus the first item is hidden. 69 if (!m_client->shouldPopOver()) 70 [m_popup addItemWithTitle:@""]; 71 72 TextDirection menuTextDirection = m_client->menuStyle().textDirection(); 73 [m_popup setUserInterfaceLayoutDirection:menuTextDirection == LTR ? NSUserInterfaceLayoutDirectionLeftToRight : NSUserInterfaceLayoutDirectionRightToLeft]; 74 75 ASSERT(m_client); 76 int size = m_client->listSize(); 77 78 for (int i = 0; i < size; i++) { 79 if (m_client->itemIsSeparator(i)) { 80 [[m_popup menu] addItem:[NSMenuItem separatorItem]]; 81 continue; 82 } 83 84 PopupMenuStyle style = m_client->itemStyle(i); 85 RetainPtr<NSMutableDictionary> attributes = adoptNS([[NSMutableDictionary alloc] init]); 86 if (style.font() != Font()) { 87 NSFont *font = style.font().primaryFont()->getNSFont(); 88 if (!font) { 89 CGFloat size = style.font().primaryFont()->platformData().size(); 90 font = style.font().weight() < FontWeightBold ? [NSFont systemFontOfSize:size] : [NSFont boldSystemFontOfSize:size]; 91 } 92 [attributes setObject:font forKey:NSFontAttributeName]; 93 } 94 95 RetainPtr<NSMutableParagraphStyle> paragraphStyle = adoptNS([[NSParagraphStyle defaultParagraphStyle] mutableCopy]); 96 [paragraphStyle setAlignment:menuTextDirection == LTR ? NSLeftTextAlignment : NSRightTextAlignment]; 97 NSWritingDirection writingDirection = style.textDirection() == LTR ? NSWritingDirectionLeftToRight : NSWritingDirectionRightToLeft; 98 [paragraphStyle setBaseWritingDirection:writingDirection]; 99 if (style.hasTextDirectionOverride()) { 100 RetainPtr<NSNumber> writingDirectionValue = adoptNS([[NSNumber alloc] initWithInteger:writingDirection + NSTextWritingDirectionOverride]); 101 RetainPtr<NSArray> writingDirectionArray = adoptNS([[NSArray alloc] initWithObjects:writingDirectionValue.get(), nil]); 102 [attributes setObject:writingDirectionArray.get() forKey:NSWritingDirectionAttributeName]; 103 } 104 [attributes setObject:paragraphStyle.get() forKey:NSParagraphStyleAttributeName]; 105 106 // FIXME: Add support for styling the foreground and background colors. 107 // FIXME: Find a way to customize text color when an item is highlighted. 108 RetainPtr<NSAttributedString> string = adoptNS([[NSAttributedString alloc] initWithString:m_client->itemText(i) attributes:attributes.get()]); 109 110 [m_popup addItemWithTitle:@""]; 111 NSMenuItem *menuItem = [m_popup lastItem]; 112 [menuItem setAttributedTitle:string.get()]; 113 // We set the title as well as the attributed title here. The attributed title will be displayed in the menu, 114 // but typeahead will use the non-attributed string that doesn't contain any leading or trailing whitespace. 115 [menuItem setTitle:[[string string] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]]; 116 [menuItem setEnabled:m_client->itemIsEnabled(i)]; 117 [menuItem setToolTip:m_client->itemToolTip(i)]; 118 119#pragma clang diagnostic push 120#pragma clang diagnostic ignored "-Wdeprecated-declarations" 121 // Allow the accessible text of the item to be overriden if necessary. 122 if (AXObjectCache::accessibilityEnabled()) { 123 NSString *accessibilityOverride = m_client->itemAccessibilityText(i); 124 if ([accessibilityOverride length]) 125 [menuItem accessibilitySetOverrideValue:accessibilityOverride forAttribute:NSAccessibilityDescriptionAttribute]; 126 } 127#pragma clang diagnostic pop 128 } 129 130 [[m_popup menu] setMenuChangedMessagesEnabled:messagesEnabled]; 131} 132 133void PopupMenuMac::show(const IntRect& r, FrameView* v, int index) 134{ 135 populate(); 136 int numItems = [m_popup numberOfItems]; 137 if (numItems <= 0) { 138 if (m_client) 139 m_client->popupDidHide(); 140 return; 141 } 142 ASSERT(numItems > index); 143 144 // Workaround for crazy bug where a selected index of -1 for a menu with only 1 item will cause a blank menu. 145 if (index == -1 && numItems == 2 && !m_client->shouldPopOver() && ![[m_popup itemAtIndex:1] isEnabled]) 146 index = 0; 147 148 NSView* view = v->documentView(); 149 150 [m_popup attachPopUpWithFrame:r inView:view]; 151 [m_popup selectItemAtIndex:index]; 152 153 NSMenu* menu = [m_popup menu]; 154 155 NSPoint location; 156 NSFont* font = m_client->menuStyle().font().primaryFont()->getNSFont(); 157 158 // These values were borrowed from AppKit to match their placement of the menu. 159 const int popOverHorizontalAdjust = -10; 160 const int popUnderHorizontalAdjust = 6; 161 const int popUnderVerticalAdjust = 6; 162 if (m_client->shouldPopOver()) { 163 NSRect titleFrame = [m_popup titleRectForBounds:r]; 164 if (titleFrame.size.width <= 0 || titleFrame.size.height <= 0) 165 titleFrame = r; 166 float vertOffset = roundf((NSMaxY(r) - NSMaxY(titleFrame)) + NSHeight(titleFrame)); 167 // Adjust for fonts other than the system font. 168 NSFont* defaultFont = [NSFont systemFontOfSize:[font pointSize]]; 169 vertOffset += [font descender] - [defaultFont descender]; 170 vertOffset = fminf(NSHeight(r), vertOffset); 171 172 location = NSMakePoint(NSMinX(r) + popOverHorizontalAdjust, NSMaxY(r) - vertOffset); 173 } else 174 location = NSMakePoint(NSMinX(r) + popUnderHorizontalAdjust, NSMaxY(r) + popUnderVerticalAdjust); 175 176 // Save the current event that triggered the popup, so we can clean up our event 177 // state after the NSMenu goes away. 178 Ref<Frame> frame(v->frame()); 179 RetainPtr<NSEvent> event = frame->eventHandler().currentNSEvent(); 180 181 Ref<PopupMenuMac> protector(*this); 182 183 RetainPtr<NSView> dummyView = adoptNS([[NSView alloc] initWithFrame:r]); 184 [view addSubview:dummyView.get()]; 185 location = [dummyView convertPoint:location fromView:view]; 186 187 if (Page* page = frame->page()) { 188 WebView* webView = kit(page); 189 BEGIN_BLOCK_OBJC_EXCEPTIONS; 190 CallUIDelegate(webView, @selector(webView:willPopupMenu:), menu); 191 END_BLOCK_OBJC_EXCEPTIONS; 192 } 193 194 NSControlSize controlSize; 195 switch (m_client->menuStyle().menuSize()) { 196 case PopupMenuStyle::PopupMenuSizeNormal: 197 controlSize = NSRegularControlSize; 198 break; 199 case PopupMenuStyle::PopupMenuSizeSmall: 200 controlSize = NSSmallControlSize; 201 break; 202 case PopupMenuStyle::PopupMenuSizeMini: 203 controlSize = NSMiniControlSize; 204 break; 205 } 206 207 wkPopupMenu(menu, location, roundf(NSWidth(r)), dummyView.get(), index, font, controlSize, !m_client->menuStyle().hasDefaultAppearance()); 208 209 [m_popup dismissPopUp]; 210 [dummyView removeFromSuperview]; 211 212 if (!m_client) 213 return; 214 215 int newIndex = [m_popup indexOfSelectedItem]; 216 m_client->popupDidHide(); 217 218 // Adjust newIndex for hidden first item. 219 if (!m_client->shouldPopOver()) 220 newIndex--; 221 222 if (index != newIndex && newIndex >= 0) 223 m_client->valueChanged(newIndex); 224 225 // Give the frame a chance to fix up its event state, since the popup eats all the 226 // events during tracking. 227 frame->eventHandler().sendFakeEventsAfterWidgetTracking(event.get()); 228} 229 230void PopupMenuMac::hide() 231{ 232 [m_popup dismissPopUp]; 233} 234 235void PopupMenuMac::updateFromElement() 236{ 237} 238 239void PopupMenuMac::disconnectClient() 240{ 241 m_client = 0; 242} 243