1/*
2 * Copyright (C) 2010 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 "WebContextMenuProxyMac.h"
28
29#import "PageClientImpl.h"
30#import "WebContextMenuItemData.h"
31#import "WKView.h"
32
33#import <WebCore/IntRect.h>
34#import <WebKitSystemInterface.h>
35
36using namespace WebCore;
37
38@interface WebUserDataWrapper : NSObject {
39    RefPtr<WebKit::APIObject> _webUserData;
40}
41- (id)initWithUserData:(WebKit::APIObject*)userData;
42- (WebKit::APIObject*)userData;
43@end
44
45@implementation WebUserDataWrapper
46
47- (id)initWithUserData:(WebKit::APIObject*)userData
48{
49    self = [super init];
50    if (!self)
51        return nil;
52
53    _webUserData = userData;
54    return self;
55}
56
57- (WebKit::APIObject*)userData
58{
59    return _webUserData.get();
60}
61
62@end
63
64@interface WKMenuTarget : NSObject {
65    WebKit::WebContextMenuProxyMac* _menuProxy;
66}
67+ (WKMenuTarget*)sharedMenuTarget;
68- (WebKit::WebContextMenuProxyMac*)menuProxy;
69- (void)setMenuProxy:(WebKit::WebContextMenuProxyMac*)menuProxy;
70- (void)forwardContextMenuAction:(id)sender;
71@end
72
73@implementation WKMenuTarget
74
75+ (WKMenuTarget*)sharedMenuTarget
76{
77    static WKMenuTarget* target = [[WKMenuTarget alloc] init];
78    return target;
79}
80
81- (WebKit::WebContextMenuProxyMac*)menuProxy
82{
83    return _menuProxy;
84}
85
86- (void)setMenuProxy:(WebKit::WebContextMenuProxyMac*)menuProxy
87{
88    _menuProxy = menuProxy;
89}
90
91- (void)forwardContextMenuAction:(id)sender
92{
93    WebKit::WebContextMenuItemData item(ActionType, static_cast<ContextMenuAction>([sender tag]), [sender title], [sender isEnabled], [sender state] == NSOnState);
94
95    if (id representedObject = [sender representedObject]) {
96        ASSERT([representedObject isKindOfClass:[WebUserDataWrapper class]]);
97        item.setUserData([static_cast<WebUserDataWrapper *>(representedObject) userData]);
98    }
99
100    _menuProxy->contextMenuItemSelected(item);
101}
102
103@end
104
105namespace WebKit {
106
107WebContextMenuProxyMac::WebContextMenuProxyMac(WKView* webView, WebPageProxy* page)
108    : m_webView(webView)
109    , m_page(page)
110{
111}
112
113WebContextMenuProxyMac::~WebContextMenuProxyMac()
114{
115    if (m_popup)
116        [m_popup.get() setControlView:nil];
117}
118
119void WebContextMenuProxyMac::contextMenuItemSelected(const WebContextMenuItemData& item)
120{
121    m_page->contextMenuItemSelected(item);
122}
123
124static void populateNSMenu(NSMenu* menu, const Vector<RetainPtr<NSMenuItem> >& menuItemVector)
125{
126    for (unsigned i = 0; i < menuItemVector.size(); ++i) {
127        NSInteger oldState = [menuItemVector[i].get() state];
128        [menu addItem:menuItemVector[i].get()];
129        [menuItemVector[i].get() setState:oldState];
130    }
131}
132
133static Vector<RetainPtr<NSMenuItem> > nsMenuItemVector(const Vector<WebContextMenuItemData>& items)
134{
135    Vector<RetainPtr<NSMenuItem> > result;
136
137    unsigned size = items.size();
138    result.reserveCapacity(size);
139    for (unsigned i = 0; i < size; i++) {
140        switch (items[i].type()) {
141        case ActionType:
142        case CheckableActionType: {
143            NSMenuItem* menuItem = [[NSMenuItem alloc] initWithTitle:nsStringFromWebCoreString(items[i].title()) action:@selector(forwardContextMenuAction:) keyEquivalent:@""];
144            [menuItem setTag:items[i].action()];
145            [menuItem setEnabled:items[i].enabled()];
146            [menuItem setState:items[i].checked() ? NSOnState : NSOffState];
147
148            if (items[i].userData()) {
149                WebUserDataWrapper *wrapper = [[WebUserDataWrapper alloc] initWithUserData:items[i].userData()];
150                [menuItem setRepresentedObject:wrapper];
151                [wrapper release];
152            }
153
154            result.append(RetainPtr<NSMenuItem>(AdoptNS, menuItem));
155            break;
156        }
157        case SeparatorType:
158            result.append([NSMenuItem separatorItem]);
159            break;
160        case SubmenuType: {
161            NSMenu* menu = [[NSMenu alloc] initWithTitle:nsStringFromWebCoreString(items[i].title())];
162            [menu setAutoenablesItems:NO];
163            populateNSMenu(menu, nsMenuItemVector(items[i].submenu()));
164
165            NSMenuItem* menuItem = [[NSMenuItem alloc] initWithTitle:nsStringFromWebCoreString(items[i].title()) action:@selector(forwardContextMenuAction:) keyEquivalent:@""];
166            [menuItem setEnabled:items[i].enabled()];
167            [menuItem setSubmenu:menu];
168            [menu release];
169
170            result.append(RetainPtr<NSMenuItem>(AdoptNS, menuItem));
171
172            break;
173        }
174        default:
175            ASSERT_NOT_REACHED();
176        }
177    }
178
179    WKMenuTarget* target = [WKMenuTarget sharedMenuTarget];
180    for (unsigned i = 0; i < size; ++i)
181        [result[i].get() setTarget:target];
182
183    return result;
184}
185
186void WebContextMenuProxyMac::populate(const Vector<WebContextMenuItemData>& items)
187{
188    if (m_popup)
189        [m_popup.get() removeAllItems];
190    else {
191        m_popup.adoptNS([[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO]);
192        [m_popup.get() setUsesItemFromMenu:NO];
193        [m_popup.get() setAutoenablesItems:NO];
194    }
195
196    NSMenu* menu = [m_popup.get() menu];
197    populateNSMenu(menu, nsMenuItemVector(items));
198}
199
200void WebContextMenuProxyMac::showContextMenu(const IntPoint& menuLocation, const Vector<WebContextMenuItemData>& items)
201{
202    if (items.isEmpty())
203        return;
204
205    populate(items);
206    [[WKMenuTarget sharedMenuTarget] setMenuProxy:this];
207
208    NSRect menuRect = NSMakeRect(menuLocation.x(), menuLocation.y(), 0, 0);
209
210    [m_popup.get() attachPopUpWithFrame:menuRect inView:m_webView];
211
212    NSMenu* menu = [m_popup.get() menu];
213
214    // These values were borrowed from AppKit to match their placement of the menu.
215    NSRect titleFrame = [m_popup.get()  titleRectForBounds:menuRect];
216    if (titleFrame.size.width <= 0 || titleFrame.size.height <= 0)
217        titleFrame = menuRect;
218    float vertOffset = roundf((NSMaxY(menuRect) - NSMaxY(titleFrame)) + NSHeight(titleFrame));
219    NSPoint location = NSMakePoint(NSMinX(menuRect), NSMaxY(menuRect) - vertOffset);
220
221    location = [m_webView convertPoint:location toView:nil];
222    location = [m_webView.window convertBaseToScreen:location];
223
224    WKPopupContextMenu(menu, location);
225
226    [m_popup.get() dismissPopUp];
227}
228
229void WebContextMenuProxyMac::hideContextMenu()
230{
231    [m_popup.get() dismissPopUp];
232}
233
234} // namespace WebKit
235