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#if USE(APPKIT)
30
31#import "NativeWebMouseEvent.h"
32#import "PageClientImpl.h"
33#import "PlatformPopupMenuData.h"
34#import "StringUtilities.h"
35#import "WKView.h"
36#import "WebPopupItem.h"
37#import <WebKitSystemInterface.h>
38
39using namespace WebCore;
40
41namespace WebKit {
42
43WebPopupMenuProxyMac::WebPopupMenuProxyMac(WKView *webView, WebPopupMenuProxy::Client* client)
44    : WebPopupMenuProxy(client)
45    , m_webView(webView)
46{
47}
48
49WebPopupMenuProxyMac::~WebPopupMenuProxyMac()
50{
51    if (m_popup)
52        [m_popup setControlView:nil];
53}
54
55void WebPopupMenuProxyMac::populate(const Vector<WebPopupItem>& items, NSFont *font, TextDirection menuTextDirection)
56{
57    if (m_popup)
58        [m_popup removeAllItems];
59    else {
60        m_popup = adoptNS([[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO]);
61        [m_popup setUsesItemFromMenu:NO];
62        [m_popup setAutoenablesItems:NO];
63    }
64
65    int size = items.size();
66
67    for (int i = 0; i < size; i++) {
68        if (items[i].m_type == WebPopupItem::Separator)
69            [[m_popup menu] addItem:[NSMenuItem separatorItem]];
70        else {
71            [m_popup addItemWithTitle:@""];
72            NSMenuItem *menuItem = [m_popup lastItem];
73
74            RetainPtr<NSMutableParagraphStyle> paragraphStyle = adoptNS([[NSParagraphStyle defaultParagraphStyle] mutableCopy]);
75            NSWritingDirection writingDirection = items[i].m_textDirection == LTR ? NSWritingDirectionLeftToRight : NSWritingDirectionRightToLeft;
76            [paragraphStyle setBaseWritingDirection:writingDirection];
77            [paragraphStyle setAlignment:menuTextDirection == LTR ? NSLeftTextAlignment : NSRightTextAlignment];
78            RetainPtr<NSMutableDictionary> attributes = adoptNS([[NSMutableDictionary alloc] initWithObjectsAndKeys:
79                paragraphStyle.get(), NSParagraphStyleAttributeName,
80                font, NSFontAttributeName,
81            nil]);
82            if (items[i].m_hasTextDirectionOverride) {
83                RetainPtr<NSNumber> writingDirectionValue = adoptNS([[NSNumber alloc] initWithInteger:writingDirection + NSTextWritingDirectionOverride]);
84                RetainPtr<NSArray> writingDirectionArray = adoptNS([[NSArray alloc] initWithObjects:writingDirectionValue.get(), nil]);
85                [attributes setObject:writingDirectionArray.get() forKey:NSWritingDirectionAttributeName];
86            }
87            RetainPtr<NSAttributedString> string = adoptNS([[NSAttributedString alloc] initWithString:nsStringFromWebCoreString(items[i].m_text) attributes:attributes.get()]);
88
89            [menuItem setAttributedTitle:string.get()];
90            // We set the title as well as the attributed title here. The attributed title will be displayed in the menu,
91            // but typeahead will use the non-attributed string that doesn't contain any leading or trailing whitespace.
92            [menuItem setTitle:[[string string] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];
93            [menuItem setEnabled:items[i].m_isEnabled];
94            [menuItem setToolTip:nsStringFromWebCoreString(items[i].m_toolTip)];
95        }
96    }
97}
98
99void WebPopupMenuProxyMac::showPopupMenu(const IntRect& rect, TextDirection textDirection, double pageScaleFactor, const Vector<WebPopupItem>& items, const PlatformPopupMenuData& data, int32_t selectedIndex)
100{
101    NSFont *font;
102    if (data.fontInfo.fontAttributeDictionary) {
103        NSFontDescriptor *fontDescriptor = [NSFontDescriptor fontDescriptorWithFontAttributes:(NSDictionary *)data.fontInfo.fontAttributeDictionary.get()];
104        font = [NSFont fontWithDescriptor:fontDescriptor size:((pageScaleFactor != 1) ? [fontDescriptor pointSize] * pageScaleFactor : 0)];
105    } else
106        font = [NSFont menuFontOfSize:0];
107
108    populate(items, font, textDirection);
109
110    [m_popup attachPopUpWithFrame:rect inView:m_webView];
111    [m_popup selectItemAtIndex:selectedIndex];
112    [m_popup setUserInterfaceLayoutDirection:textDirection == LTR ? NSUserInterfaceLayoutDirectionLeftToRight : NSUserInterfaceLayoutDirectionRightToLeft];
113
114    NSMenu *menu = [m_popup menu];
115
116    // These values were borrowed from AppKit to match their placement of the menu.
117    const int popOverHorizontalAdjust = -10;
118    const int popUnderHorizontalAdjust = 6;
119    const int popUnderVerticalAdjust = 6;
120
121    // Menus that pop-over directly obscure the node that generated the popup menu.
122    // Menus that pop-under are offset underneath it.
123    NSPoint location;
124    if (data.shouldPopOver) {
125        NSRect titleFrame = [m_popup  titleRectForBounds:rect];
126        if (titleFrame.size.width <= 0 || titleFrame.size.height <= 0)
127            titleFrame = rect;
128        float vertOffset = roundf((NSMaxY(rect) - NSMaxY(titleFrame)) + NSHeight(titleFrame));
129        location = NSMakePoint(NSMinX(rect) + popOverHorizontalAdjust, NSMaxY(rect) - vertOffset);
130    } else
131        location = NSMakePoint(NSMinX(rect) + popUnderHorizontalAdjust, NSMaxY(rect) + popUnderVerticalAdjust);
132
133    RetainPtr<NSView> dummyView = adoptNS([[NSView alloc] initWithFrame:rect]);
134    [m_webView addSubview:dummyView.get()];
135    location = [dummyView convertPoint:location fromView:m_webView];
136
137    NSControlSize controlSize;
138    switch (data.menuSize) {
139    case WebCore::PopupMenuStyle::PopupMenuSizeNormal:
140        controlSize = NSRegularControlSize;
141        break;
142    case WebCore::PopupMenuStyle::PopupMenuSizeSmall:
143        controlSize = NSSmallControlSize;
144        break;
145    case WebCore::PopupMenuStyle::PopupMenuSizeMini:
146        controlSize = NSMiniControlSize;
147        break;
148    }
149
150    WKPopupMenu(menu, location, roundf(NSWidth(rect)), dummyView.get(), selectedIndex, font, controlSize, data.hideArrows);
151
152    [m_popup dismissPopUp];
153    [dummyView removeFromSuperview];
154
155    if (!m_client)
156        return;
157
158    m_client->valueChangedForPopupMenu(this, [m_popup indexOfSelectedItem]);
159
160    // <https://bugs.webkit.org/show_bug.cgi?id=57904> This code is adopted from EventHandler::sendFakeEventsAfterWidgetTracking().
161    if (!m_client->currentlyProcessedMouseDownEvent())
162        return;
163
164    NSEvent* initiatingNSEvent = m_client->currentlyProcessedMouseDownEvent()->nativeEvent();
165    if ([initiatingNSEvent type] != NSLeftMouseDown)
166        return;
167
168    NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
169                                            location:[initiatingNSEvent locationInWindow]
170                                       modifierFlags:[initiatingNSEvent modifierFlags]
171                                           timestamp:[initiatingNSEvent timestamp]
172                                        windowNumber:[initiatingNSEvent windowNumber]
173                                             context:[initiatingNSEvent context]
174                                         eventNumber:[initiatingNSEvent eventNumber]
175                                          clickCount:[initiatingNSEvent clickCount]
176                                            pressure:[initiatingNSEvent pressure]];
177
178    [NSApp postEvent:fakeEvent atStart:YES];
179#pragma clang diagnostic push
180#pragma clang diagnostic ignored "-Wdeprecated-declarations"
181    fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
182                                   location:[[m_webView window] convertScreenToBase:[NSEvent mouseLocation]]
183                              modifierFlags:[initiatingNSEvent modifierFlags]
184                                  timestamp:[initiatingNSEvent timestamp]
185                               windowNumber:[initiatingNSEvent windowNumber]
186                                    context:[initiatingNSEvent context]
187                                eventNumber:0
188                                 clickCount:0
189                                   pressure:0];
190#pragma clang diagnostic pop
191    [NSApp postEvent:fakeEvent atStart:YES];
192}
193
194void WebPopupMenuProxyMac::hidePopupMenu()
195{
196    [m_popup dismissPopUp];
197}
198
199} // namespace WebKit
200
201#endif // USE(APPKIT)
202