1/*
2 * Copyright (C) 2010, 2011 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "WebPopupMenuProxyMac.h"
28
29#import "NativeWebMouseEvent.h"
30#import "PageClientImpl.h"
31#import "PlatformPopupMenuData.h"
32#import "WKView.h"
33#import "WebPopupItem.h"
34#import <WebKitSystemInterface.h>
35
36using namespace WebCore;
37
38namespace WebKit {
39
40WebPopupMenuProxyMac::WebPopupMenuProxyMac(WKView *webView, WebPopupMenuProxy::Client* client)
41    : WebPopupMenuProxy(client)
42    , m_webView(webView)
43{
44}
45
46WebPopupMenuProxyMac::~WebPopupMenuProxyMac()
47{
48    if (m_popup)
49        [m_popup.get() setControlView:nil];
50}
51
52void WebPopupMenuProxyMac::populate(const Vector<WebPopupItem>& items, NSFont *font, TextDirection menuTextDirection)
53{
54    if (m_popup)
55        [m_popup.get() removeAllItems];
56    else {
57        m_popup.adoptNS([[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO]);
58        [m_popup.get() setUsesItemFromMenu:NO];
59        [m_popup.get() setAutoenablesItems:NO];
60    }
61
62    int size = items.size();
63
64    for (int i = 0; i < size; i++) {
65        if (items[i].m_type == WebPopupItem::Separator)
66            [[m_popup.get() menu] addItem:[NSMenuItem separatorItem]];
67        else {
68            [m_popup.get() addItemWithTitle:@""];
69            NSMenuItem *menuItem = [m_popup.get() lastItem];
70
71            RetainPtr<NSMutableParagraphStyle> paragraphStyle(AdoptNS, [[NSParagraphStyle defaultParagraphStyle] mutableCopy]);
72            NSWritingDirection writingDirection = items[i].m_textDirection == LTR ? NSWritingDirectionLeftToRight : NSWritingDirectionRightToLeft;
73            [paragraphStyle.get() setBaseWritingDirection:writingDirection];
74            [paragraphStyle.get() setAlignment:menuTextDirection == LTR ? NSLeftTextAlignment : NSRightTextAlignment];
75            RetainPtr<NSMutableDictionary> attributes(AdoptNS, [[NSMutableDictionary alloc] initWithObjectsAndKeys:
76                paragraphStyle.get(), NSParagraphStyleAttributeName,
77                font, NSFontAttributeName,
78            nil]);
79            if (items[i].m_hasTextDirectionOverride) {
80                RetainPtr<NSNumber> writingDirectionValue(AdoptNS, [[NSNumber alloc] initWithInteger:writingDirection + NSTextWritingDirectionOverride]);
81                RetainPtr<NSArray> writingDirectionArray(AdoptNS, [[NSArray alloc] initWithObjects:writingDirectionValue.get(), nil]);
82                [attributes.get() setObject:writingDirectionArray.get() forKey:NSWritingDirectionAttributeName];
83            }
84            RetainPtr<NSAttributedString> string(AdoptNS, [[NSAttributedString alloc] initWithString:nsStringFromWebCoreString(items[i].m_text) attributes:attributes.get()]);
85
86            [menuItem setAttributedTitle:string.get()];
87            [menuItem setEnabled:items[i].m_isEnabled];
88            [menuItem setToolTip:nsStringFromWebCoreString(items[i].m_toolTip)];
89        }
90    }
91}
92
93void WebPopupMenuProxyMac::showPopupMenu(const IntRect& rect, TextDirection textDirection, double scaleFactor, const Vector<WebPopupItem>& items, const PlatformPopupMenuData& data, int32_t selectedIndex)
94{
95    NSFont *font;
96    if (data.fontInfo.fontAttributeDictionary) {
97        NSFontDescriptor *fontDescriptor = [NSFontDescriptor fontDescriptorWithFontAttributes:(NSDictionary *)data.fontInfo.fontAttributeDictionary.get()];
98        font = [NSFont fontWithDescriptor:fontDescriptor size:((scaleFactor != 1) ? [fontDescriptor pointSize] * scaleFactor : 0)];
99    } else
100        font = [NSFont menuFontOfSize:0];
101
102    populate(items, font, textDirection);
103
104    [m_popup.get() attachPopUpWithFrame:rect inView:m_webView];
105    [m_popup.get() selectItemAtIndex:selectedIndex];
106    [m_popup.get() setUserInterfaceLayoutDirection:textDirection == LTR ? NSUserInterfaceLayoutDirectionLeftToRight : NSUserInterfaceLayoutDirectionRightToLeft];
107
108    NSMenu *menu = [m_popup.get() menu];
109
110    // These values were borrowed from AppKit to match their placement of the menu.
111    const int popOverHorizontalAdjust = -10;
112    const int popUnderHorizontalAdjust = 6;
113    const int popUnderVerticalAdjust = 6;
114
115    // Menus that pop-over directly obscure the node that generated the popup menu.
116    // Menus that pop-under are offset underneath it.
117    NSPoint location;
118    if (data.shouldPopOver) {
119        NSRect titleFrame = [m_popup.get()  titleRectForBounds:rect];
120        if (titleFrame.size.width <= 0 || titleFrame.size.height <= 0)
121            titleFrame = rect;
122        float vertOffset = roundf((NSMaxY(rect) - NSMaxY(titleFrame)) + NSHeight(titleFrame));
123        location = NSMakePoint(NSMinX(rect) + popOverHorizontalAdjust, NSMaxY(rect) - vertOffset);
124    } else
125        location = NSMakePoint(NSMinX(rect) + popUnderHorizontalAdjust, NSMaxY(rect) + popUnderVerticalAdjust);
126
127    RetainPtr<NSView> dummyView(AdoptNS, [[NSView alloc] initWithFrame:rect]);
128    [m_webView addSubview:dummyView.get()];
129    location = [dummyView.get() convertPoint:location fromView:m_webView];
130
131    WKPopupMenu(menu, location, roundf(NSWidth(rect)), dummyView.get(), selectedIndex, font);
132
133    [m_popup.get() dismissPopUp];
134    [dummyView.get() removeFromSuperview];
135
136    if (!m_client)
137        return;
138
139    m_client->valueChangedForPopupMenu(this, [m_popup.get() indexOfSelectedItem]);
140
141    // <https://bugs.webkit.org/show_bug.cgi?id=57904> This code is adopted from EventHandler::sendFakeEventsAfterWidgetTracking().
142    if (!m_client->currentlyProcessedMouseDownEvent())
143        return;
144
145    NSEvent* initiatingNSEvent = m_client->currentlyProcessedMouseDownEvent()->nativeEvent();
146    if ([initiatingNSEvent type] != NSLeftMouseDown)
147        return;
148
149    NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
150                                            location:[initiatingNSEvent locationInWindow]
151                                       modifierFlags:[initiatingNSEvent modifierFlags]
152                                           timestamp:[initiatingNSEvent timestamp]
153                                        windowNumber:[initiatingNSEvent windowNumber]
154                                             context:[initiatingNSEvent context]
155                                         eventNumber:[initiatingNSEvent eventNumber]
156                                          clickCount:[initiatingNSEvent clickCount]
157                                            pressure:[initiatingNSEvent pressure]];
158
159    [NSApp postEvent:fakeEvent atStart:YES];
160    fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
161                                   location:[[m_webView window] convertScreenToBase:[NSEvent mouseLocation]]
162                              modifierFlags:[initiatingNSEvent modifierFlags]
163                                  timestamp:[initiatingNSEvent timestamp]
164                               windowNumber:[initiatingNSEvent windowNumber]
165                                    context:[initiatingNSEvent context]
166                                eventNumber:0
167                                 clickCount:0
168                                   pressure:0];
169    [NSApp postEvent:fakeEvent atStart:YES];
170}
171
172void WebPopupMenuProxyMac::hidePopupMenu()
173{
174    [m_popup.get() dismissPopUp];
175}
176
177} // namespace WebKit
178