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 "config.h" 22 #import "PopupMenuMac.h" 23 24 #import "AXObjectCache.h" 25 #import "Chrome.h" 26 #import "ChromeClient.h" 27 #import "EventHandler.h" 28 #import "Frame.h" 29 #import "FrameView.h" 30 #import "HTMLNames.h" 31 #import "HTMLOptGroupElement.h" 32 #import "HTMLOptionElement.h" 33 #import "HTMLSelectElement.h" 34 #import "Page.h" 35 #import "PopupMenuClient.h" 36 #import "SimpleFontData.h" 37 #import "WebCoreSystemInterface.h" 38 39 namespace WebCore { 40 41 using namespace HTMLNames; 42 43 PopupMenuMac::PopupMenuMac(PopupMenuClient* client) 44 : m_popupClient(client) 45 { 46 } 47 48 PopupMenuMac::~PopupMenuMac() 49 { 50 if (m_popup) 51 [m_popup.get() setControlView:nil]; 52 } 53 54 void PopupMenuMac::clear() 55 { 56 if (m_popup) 57 [m_popup.get() removeAllItems]; 58 } 59 60 void PopupMenuMac::populate() 61 { 62 if (m_popup) 63 clear(); 64 else { 65 m_popup = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:!client()->shouldPopOver()]; 66 [m_popup.get() release]; // release here since the RetainPtr has retained the object already 67 [m_popup.get() setUsesItemFromMenu:NO]; 68 [m_popup.get() setAutoenablesItems:NO]; 69 } 70 71 BOOL messagesEnabled = [[m_popup.get() menu] menuChangedMessagesEnabled]; 72 [[m_popup.get() menu] setMenuChangedMessagesEnabled:NO]; 73 74 // For pullDown menus the first item is hidden. 75 if (!client()->shouldPopOver()) 76 [m_popup.get() addItemWithTitle:@""]; 77 78 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 79 TextDirection menuTextDirection = client()->menuStyle().textDirection(); 80 [m_popup.get() setUserInterfaceLayoutDirection:menuTextDirection == LTR ? NSUserInterfaceLayoutDirectionLeftToRight : NSUserInterfaceLayoutDirectionRightToLeft]; 81 #endif // !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 82 83 ASSERT(client()); 84 int size = client()->listSize(); 85 86 for (int i = 0; i < size; i++) { 87 if (client()->itemIsSeparator(i)) 88 [[m_popup.get() menu] addItem:[NSMenuItem separatorItem]]; 89 else { 90 PopupMenuStyle style = client()->itemStyle(i); 91 NSMutableDictionary* attributes = [[NSMutableDictionary alloc] init]; 92 if (style.font() != Font()) { 93 NSFont *font = style.font().primaryFont()->getNSFont(); 94 if (!font) { 95 CGFloat size = style.font().primaryFont()->platformData().size(); 96 font = style.font().weight() < FontWeightBold ? [NSFont systemFontOfSize:size] : [NSFont boldSystemFontOfSize:size]; 97 } 98 [attributes setObject:font forKey:NSFontAttributeName]; 99 } 100 101 #if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 102 RetainPtr<NSMutableParagraphStyle> paragraphStyle(AdoptNS, [[NSParagraphStyle defaultParagraphStyle] mutableCopy]); 103 [paragraphStyle.get() setAlignment:menuTextDirection == LTR ? NSLeftTextAlignment : NSRightTextAlignment]; 104 NSWritingDirection writingDirection = style.textDirection() == LTR ? NSWritingDirectionLeftToRight : NSWritingDirectionRightToLeft; 105 [paragraphStyle.get() setBaseWritingDirection:writingDirection]; 106 if (style.hasTextDirectionOverride()) { 107 RetainPtr<NSNumber> writingDirectionValue(AdoptNS, [[NSNumber alloc] initWithInteger:writingDirection + NSTextWritingDirectionOverride]); 108 RetainPtr<NSArray> writingDirectionArray(AdoptNS, [[NSArray alloc] initWithObjects:writingDirectionValue.get(), nil]); 109 [attributes setObject:writingDirectionArray.get() forKey:NSWritingDirectionAttributeName]; 110 } 111 [attributes setObject:paragraphStyle.get() forKey:NSParagraphStyleAttributeName]; 112 #endif // !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) 113 114 // FIXME: Add support for styling the foreground and background colors. 115 // FIXME: Find a way to customize text color when an item is highlighted. 116 NSAttributedString *string = [[NSAttributedString alloc] initWithString:client()->itemText(i) attributes:attributes]; 117 [attributes release]; 118 119 [m_popup.get() addItemWithTitle:@""]; 120 NSMenuItem *menuItem = [m_popup.get() lastItem]; 121 [menuItem setAttributedTitle:string]; 122 [menuItem setEnabled:client()->itemIsEnabled(i)]; 123 [menuItem setToolTip:client()->itemToolTip(i)]; 124 [string release]; 125 126 // Allow the accessible text of the item to be overriden if necessary. 127 if (AXObjectCache::accessibilityEnabled()) { 128 NSString *accessibilityOverride = client()->itemAccessibilityText(i); 129 if ([accessibilityOverride length]) 130 [menuItem accessibilitySetOverrideValue:accessibilityOverride forAttribute:NSAccessibilityDescriptionAttribute]; 131 } 132 } 133 } 134 135 [[m_popup.get() menu] setMenuChangedMessagesEnabled:messagesEnabled]; 136 } 137 138 void PopupMenuMac::show(const IntRect& r, FrameView* v, int index) 139 { 140 populate(); 141 int numItems = [m_popup.get() numberOfItems]; 142 if (numItems <= 0) { 143 if (client()) 144 client()->popupDidHide(); 145 return; 146 } 147 ASSERT(numItems > index); 148 149 // Workaround for crazy bug where a selected index of -1 for a menu with only 1 item will cause a blank menu. 150 if (index == -1 && numItems == 2 && !client()->shouldPopOver() && ![[m_popup.get() itemAtIndex:1] isEnabled]) 151 index = 0; 152 153 NSView* view = v->documentView(); 154 155 [m_popup.get() attachPopUpWithFrame:r inView:view]; 156 [m_popup.get() selectItemAtIndex:index]; 157 158 NSMenu* menu = [m_popup.get() menu]; 159 160 NSPoint location; 161 NSFont* font = client()->menuStyle().font().primaryFont()->getNSFont(); 162 163 // These values were borrowed from AppKit to match their placement of the menu. 164 const int popOverHorizontalAdjust = -10; 165 const int popUnderHorizontalAdjust = 6; 166 const int popUnderVerticalAdjust = 6; 167 if (client()->shouldPopOver()) { 168 NSRect titleFrame = [m_popup.get() titleRectForBounds:r]; 169 if (titleFrame.size.width <= 0 || titleFrame.size.height <= 0) 170 titleFrame = r; 171 float vertOffset = roundf((NSMaxY(r) - NSMaxY(titleFrame)) + NSHeight(titleFrame)); 172 // Adjust for fonts other than the system font. 173 NSFont* defaultFont = [NSFont systemFontOfSize:[font pointSize]]; 174 vertOffset += [font descender] - [defaultFont descender]; 175 vertOffset = fminf(NSHeight(r), vertOffset); 176 177 location = NSMakePoint(NSMinX(r) + popOverHorizontalAdjust, NSMaxY(r) - vertOffset); 178 } else 179 location = NSMakePoint(NSMinX(r) + popUnderHorizontalAdjust, NSMaxY(r) + popUnderVerticalAdjust); 180 181 // Save the current event that triggered the popup, so we can clean up our event 182 // state after the NSMenu goes away. 183 RefPtr<Frame> frame = v->frame(); 184 NSEvent* event = [frame->eventHandler()->currentNSEvent() retain]; 185 186 RefPtr<PopupMenuMac> protector(this); 187 188 RetainPtr<NSView> dummyView(AdoptNS, [[NSView alloc] initWithFrame:r]); 189 [view addSubview:dummyView.get()]; 190 location = [dummyView.get() convertPoint:location fromView:view]; 191 192 if (Page* page = frame->page()) 193 page->chrome()->client()->willPopUpMenu(menu); 194 wkPopupMenu(menu, location, roundf(NSWidth(r)), dummyView.get(), index, font); 195 196 [m_popup.get() dismissPopUp]; 197 [dummyView.get() removeFromSuperview]; 198 199 if (client()) { 200 int newIndex = [m_popup.get() indexOfSelectedItem]; 201 client()->popupDidHide(); 202 203 // Adjust newIndex for hidden first item. 204 if (!client()->shouldPopOver()) 205 newIndex--; 206 207 if (index != newIndex && newIndex >= 0) 208 client()->valueChanged(newIndex); 209 210 // Give the frame a chance to fix up its event state, since the popup eats all the 211 // events during tracking. 212 frame->eventHandler()->sendFakeEventsAfterWidgetTracking(event); 213 } 214 215 [event release]; 216 } 217 218 void PopupMenuMac::hide() 219 { 220 [m_popup.get() dismissPopUp]; 221 } 222 223 void PopupMenuMac::updateFromElement() 224 { 225 } 226 227 void PopupMenuMac::disconnectClient() 228 { 229 m_popupClient = 0; 230 } 231 232 } 233