1/*
2 * Copyright (C) 2011 Igalia S.L.
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#include "config.h"
27#include "WebContextMenuProxyGtk.h"
28
29#if ENABLE(CONTEXT_MENUS)
30
31#include "NativeWebMouseEvent.h"
32#include "WebContextMenuItemData.h"
33#include "WebKitWebViewBasePrivate.h"
34#include "WebPageProxy.h"
35#include <WebCore/GtkUtilities.h>
36#include <gtk/gtk.h>
37#include <wtf/text/CString.h>
38
39
40static const char* gContextMenuActionId = "webkit-context-menu-action";
41
42using namespace WebCore;
43
44namespace WebKit {
45
46static void contextMenuItemActivatedCallback(GtkAction* action, WebPageProxy* page)
47{
48    gboolean isToggle = GTK_IS_TOGGLE_ACTION(action);
49    WebKit::WebContextMenuItemData item(isToggle ? WebCore::CheckableActionType : WebCore::ActionType,
50        static_cast<WebCore::ContextMenuAction>(GPOINTER_TO_INT(g_object_get_data(G_OBJECT(action), gContextMenuActionId))),
51        String::fromUTF8(gtk_action_get_label(action)), gtk_action_get_sensitive(action),
52        isToggle ? gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action)) : false);
53    page->contextMenuItemSelected(item);
54}
55
56static void contextMenuItemVisibilityChanged(GtkAction* action, GParamSpec*, WebContextMenuProxyGtk* contextMenuProxy)
57{
58    GtkMenu* menu = contextMenuProxy->gtkMenu();
59    if (!menu)
60        return;
61
62    GOwnPtr<GList> items(gtk_container_get_children(GTK_CONTAINER(menu)));
63    bool previousVisibleItemIsNotASeparator = false;
64    GtkWidget* lastItemVisibleSeparator = 0;
65    for (GList* iter = items.get(); iter; iter = g_list_next(iter)) {
66        GtkWidget* widget = GTK_WIDGET(iter->data);
67
68        if (GTK_IS_SEPARATOR_MENU_ITEM(widget)) {
69            if (previousVisibleItemIsNotASeparator) {
70                gtk_widget_show(widget);
71                lastItemVisibleSeparator = widget;
72                previousVisibleItemIsNotASeparator = false;
73            } else
74                gtk_widget_hide(widget);
75        } else if (gtk_widget_get_visible(widget)) {
76            lastItemVisibleSeparator = 0;
77            previousVisibleItemIsNotASeparator = true;
78        }
79    }
80
81    if (lastItemVisibleSeparator)
82        gtk_widget_hide(lastItemVisibleSeparator);
83}
84
85void WebContextMenuProxyGtk::append(ContextMenuItem& menuItem)
86{
87    GtkAction* action = menuItem.gtkAction();
88    if (action) {
89        switch (menuItem.type()) {
90        case ActionType:
91        case CheckableActionType:
92            g_object_set_data(G_OBJECT(action), gContextMenuActionId, GINT_TO_POINTER(menuItem.action()));
93            g_signal_connect(action, "activate", G_CALLBACK(contextMenuItemActivatedCallback), m_page);
94            // Fall through.
95        case SubmenuType:
96            g_signal_connect(action, "notify::visible", G_CALLBACK(contextMenuItemVisibilityChanged), this);
97            break;
98        case SeparatorType:
99            break;
100        }
101    }
102
103    m_menu.appendItem(menuItem);
104}
105
106// Populate the context menu ensuring that:
107//  - There aren't separators next to each other.
108//  - There aren't separators at the beginning of the menu.
109//  - There aren't separators at the end of the menu.
110void WebContextMenuProxyGtk::populate(Vector<ContextMenuItem>& items)
111{
112    bool previousIsSeparator = false;
113    bool isEmpty = true;
114    for (size_t i = 0; i < items.size(); i++) {
115        ContextMenuItem& menuItem = items.at(i);
116        if (menuItem.type() == SeparatorType) {
117            previousIsSeparator = true;
118            continue;
119        }
120
121        if (previousIsSeparator && !isEmpty)
122            append(items.at(i - 1));
123        previousIsSeparator = false;
124
125        append(menuItem);
126        isEmpty = false;
127    }
128}
129
130void WebContextMenuProxyGtk::populate(const Vector<WebContextMenuItemData>& items)
131{
132    for (size_t i = 0; i < items.size(); i++) {
133        ContextMenuItem menuitem = items.at(i).core();
134        append(menuitem);
135    }
136}
137
138void WebContextMenuProxyGtk::showContextMenu(const WebCore::IntPoint& position, const Vector<WebContextMenuItemData>& items)
139{
140    if (!items.isEmpty())
141        populate(items);
142
143    if (!m_menu.itemCount())
144        return;
145
146    m_popupPosition = convertWidgetPointToScreenPoint(m_webView, position);
147
148    // Display menu initiated by right click (mouse button pressed = 3).
149    NativeWebMouseEvent* mouseEvent = m_page->currentlyProcessedMouseDownEvent();
150    const GdkEvent* event = mouseEvent ? mouseEvent->nativeEvent() : 0;
151    gtk_menu_attach_to_widget(m_menu.platformDescription(), GTK_WIDGET(m_webView), 0);
152    gtk_menu_popup(m_menu.platformDescription(), 0, 0, reinterpret_cast<GtkMenuPositionFunc>(menuPositionFunction), this,
153                   event ? event->button.button : 3, event ? event->button.time : GDK_CURRENT_TIME);
154}
155
156void WebContextMenuProxyGtk::hideContextMenu()
157{
158    gtk_menu_popdown(m_menu.platformDescription());
159}
160
161WebContextMenuProxyGtk::WebContextMenuProxyGtk(GtkWidget* webView, WebPageProxy* page)
162    : m_webView(webView)
163    , m_page(page)
164{
165    webkitWebViewBaseSetActiveContextMenuProxy(WEBKIT_WEB_VIEW_BASE(m_webView), this);
166}
167
168WebContextMenuProxyGtk::~WebContextMenuProxyGtk()
169{
170    webkitWebViewBaseSetActiveContextMenuProxy(WEBKIT_WEB_VIEW_BASE(m_webView), 0);
171}
172
173void WebContextMenuProxyGtk::menuPositionFunction(GtkMenu* menu, gint* x, gint* y, gboolean* pushIn, WebContextMenuProxyGtk* popupMenu)
174{
175    GtkRequisition menuSize;
176    gtk_widget_get_preferred_size(GTK_WIDGET(menu), &menuSize, 0);
177
178    GdkScreen* screen = gtk_widget_get_screen(popupMenu->m_webView);
179    *x = popupMenu->m_popupPosition.x();
180    if ((*x + menuSize.width) >= gdk_screen_get_width(screen))
181        *x -= menuSize.width;
182
183    *y = popupMenu->m_popupPosition.y();
184    if ((*y + menuSize.height) >= gdk_screen_get_height(screen))
185        *y -= menuSize.height;
186
187    *pushIn = FALSE;
188}
189
190} // namespace WebKit
191#endif // ENABLE(CONTEXT_MENUS)
192