1/*
2 *  Copyright (C) 2007 Holger Hans Peter Freyther
3 *  Copyright (C) 2010 Igalia S.L
4 * Portions Copyright (c) 2010 Motorola Mobility, Inc.  All rights reserved.
5 *
6 *  This library is free software; you can redistribute it and/or
7 *  modify it under the terms of the GNU Lesser General Public
8 *  License as published by the Free Software Foundation; either
9 *  version 2 of the License, or (at your option) any later version.
10 *
11 *  This library is distributed in the hope that it will be useful,
12 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 *  Lesser General Public License for more details.
15 *
16 *  You should have received a copy of the GNU Lesser General Public
17 *  License along with this library; if not, write to the Free Software
18 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19 */
20
21#include "config.h"
22
23#if ENABLE(CONTEXT_MENUS)
24
25#include "ContextMenuItem.h"
26
27#include "ContextMenu.h"
28#include <gtk/gtk.h>
29#include <wtf/gobject/GRefPtr.h>
30#include <wtf/gobject/GUniquePtr.h>
31#include <wtf/text/CString.h>
32
33#define WEBKIT_CONTEXT_MENU_ACTION "webkit-context-menu"
34
35namespace WebCore {
36
37static const char* gtkStockIDFromContextMenuAction(const ContextMenuAction& action)
38{
39    switch (action) {
40    case ContextMenuItemTagCopyLinkToClipboard:
41    case ContextMenuItemTagCopyImageToClipboard:
42    case ContextMenuItemTagCopyMediaLinkToClipboard:
43    case ContextMenuItemTagCopy:
44        return GTK_STOCK_COPY;
45    case ContextMenuItemTagOpenLinkInNewWindow:
46    case ContextMenuItemTagOpenImageInNewWindow:
47    case ContextMenuItemTagOpenFrameInNewWindow:
48    case ContextMenuItemTagOpenMediaInNewWindow:
49        return GTK_STOCK_OPEN;
50    case ContextMenuItemTagDownloadLinkToDisk:
51    case ContextMenuItemTagDownloadImageToDisk:
52        return GTK_STOCK_SAVE;
53    case ContextMenuItemTagGoBack:
54        return GTK_STOCK_GO_BACK;
55    case ContextMenuItemTagGoForward:
56        return GTK_STOCK_GO_FORWARD;
57    case ContextMenuItemTagStop:
58        return GTK_STOCK_STOP;
59    case ContextMenuItemTagReload:
60        return GTK_STOCK_REFRESH;
61    case ContextMenuItemTagCut:
62        return GTK_STOCK_CUT;
63    case ContextMenuItemTagPaste:
64        return GTK_STOCK_PASTE;
65    case ContextMenuItemTagDelete:
66        return GTK_STOCK_DELETE;
67    case ContextMenuItemTagSelectAll:
68        return GTK_STOCK_SELECT_ALL;
69    case ContextMenuItemTagSpellingGuess:
70        return 0;
71    case ContextMenuItemTagIgnoreSpelling:
72        return GTK_STOCK_NO;
73    case ContextMenuItemTagLearnSpelling:
74        return GTK_STOCK_OK;
75    case ContextMenuItemTagOther:
76        return GTK_STOCK_MISSING_IMAGE;
77    case ContextMenuItemTagSearchInSpotlight:
78        return GTK_STOCK_FIND;
79    case ContextMenuItemTagSearchWeb:
80        return GTK_STOCK_FIND;
81    case ContextMenuItemTagOpenWithDefaultApplication:
82        return GTK_STOCK_OPEN;
83    case ContextMenuItemPDFZoomIn:
84        return GTK_STOCK_ZOOM_IN;
85    case ContextMenuItemPDFZoomOut:
86        return GTK_STOCK_ZOOM_OUT;
87    case ContextMenuItemPDFAutoSize:
88        return GTK_STOCK_ZOOM_FIT;
89    case ContextMenuItemPDFNextPage:
90        return GTK_STOCK_GO_FORWARD;
91    case ContextMenuItemPDFPreviousPage:
92        return GTK_STOCK_GO_BACK;
93    // New tags, not part of API
94    case ContextMenuItemTagOpenLink:
95        return GTK_STOCK_OPEN;
96    case ContextMenuItemTagCheckSpelling:
97        return GTK_STOCK_SPELL_CHECK;
98    case ContextMenuItemTagFontMenu:
99        return GTK_STOCK_SELECT_FONT;
100    case ContextMenuItemTagShowFonts:
101        return GTK_STOCK_SELECT_FONT;
102    case ContextMenuItemTagBold:
103        return GTK_STOCK_BOLD;
104    case ContextMenuItemTagItalic:
105        return GTK_STOCK_ITALIC;
106    case ContextMenuItemTagUnderline:
107        return GTK_STOCK_UNDERLINE;
108    case ContextMenuItemTagShowColors:
109        return GTK_STOCK_SELECT_COLOR;
110    case ContextMenuItemTagToggleMediaControls:
111    case ContextMenuItemTagToggleMediaLoop:
112    case ContextMenuItemTagCopyImageUrlToClipboard:
113        // No icon for this.
114        return 0;
115    case ContextMenuItemTagEnterVideoFullscreen:
116        return GTK_STOCK_FULLSCREEN;
117    default:
118        return 0;
119    }
120}
121
122static PlatformMenuItemDescription createPlatformMenuItemDescription(ContextMenuItemType type, ContextMenuAction action, const String& title, bool enabled, bool checked)
123{
124    if (type == SeparatorType)
125        return GTK_MENU_ITEM(gtk_separator_menu_item_new());
126
127    GUniquePtr<char> actionName(g_strdup_printf("context-menu-action-%d", action));
128    GRefPtr<GtkAction> platformAction;
129
130    if (type == CheckableActionType) {
131        platformAction = adoptGRef(GTK_ACTION(gtk_toggle_action_new(actionName.get(), title.utf8().data(), 0, gtkStockIDFromContextMenuAction(action))));
132        gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(platformAction.get()), checked);
133    } else
134        platformAction = adoptGRef(gtk_action_new(actionName.get(), title.utf8().data(), 0, gtkStockIDFromContextMenuAction(action)));
135    gtk_action_set_sensitive(platformAction.get(), enabled);
136
137    GtkMenuItem* item = GTK_MENU_ITEM(gtk_action_create_menu_item(platformAction.get()));
138    g_object_set_data(G_OBJECT(item), WEBKIT_CONTEXT_MENU_ACTION, GINT_TO_POINTER(action));
139
140    return item;
141}
142
143// Extract the ActionType from the menu item
144ContextMenuItem::ContextMenuItem(PlatformMenuItemDescription item)
145    : m_platformDescription(item)
146{
147    // Don't show accel labels in context menu items.
148    GtkAction* action = gtkAction();
149    if (!action)
150        return;
151
152    if (!gtk_action_get_accel_path(action))
153        return;
154
155    GtkWidget* child = gtk_bin_get_child(GTK_BIN(item));
156    if (GTK_IS_ACCEL_LABEL(child))
157        gtk_accel_label_set_accel_closure(GTK_ACCEL_LABEL(child), 0);
158}
159
160ContextMenuItem::ContextMenuItem(ContextMenu* subMenu)
161{
162    m_platformDescription = GTK_MENU_ITEM(gtk_menu_item_new());
163    if (subMenu)
164        setSubMenu(subMenu);
165}
166
167ContextMenuItem::ContextMenuItem(ContextMenuItemType type, ContextMenuAction action, const String& title, ContextMenu* subMenu)
168{
169    m_platformDescription = createPlatformMenuItemDescription(type, action, title, true, false);
170    if (subMenu)
171        setSubMenu(subMenu);
172}
173
174ContextMenuItem::ContextMenuItem(ContextMenuItemType type, ContextMenuAction action, const String& title, bool enabled, bool checked)
175{
176    m_platformDescription = createPlatformMenuItemDescription(type, action, title, enabled, checked);
177}
178
179ContextMenuItem::ContextMenuItem(ContextMenuAction action, const String& title, bool enabled, bool checked, Vector<ContextMenuItem>& subMenuItems)
180{
181    m_platformDescription = createPlatformMenuItemDescription(SubmenuType, action, title, enabled, checked);
182    setSubMenu(subMenuItems);
183}
184
185ContextMenuItem::~ContextMenuItem()
186{
187}
188
189PlatformMenuItemDescription ContextMenuItem::releasePlatformDescription()
190{
191    PlatformMenuItemDescription platformDescription = m_platformDescription;
192    m_platformDescription = 0;
193    return platformDescription;
194}
195
196ContextMenuItemType ContextMenuItem::type() const
197{
198    if (GTK_IS_SEPARATOR_MENU_ITEM(m_platformDescription))
199        return SeparatorType;
200    if (GTK_IS_CHECK_MENU_ITEM(m_platformDescription))
201        return CheckableActionType;
202    if (gtk_menu_item_get_submenu(m_platformDescription))
203        return SubmenuType;
204    return ActionType;
205}
206
207void ContextMenuItem::setType(ContextMenuItemType type)
208{
209    if (type == SeparatorType)
210        m_platformDescription = GTK_MENU_ITEM(gtk_separator_menu_item_new());
211}
212
213ContextMenuAction ContextMenuItem::action() const
214{
215    return static_cast<ContextMenuAction>(GPOINTER_TO_INT(g_object_get_data(G_OBJECT(m_platformDescription), WEBKIT_CONTEXT_MENU_ACTION)));
216}
217
218void ContextMenuItem::setAction(ContextMenuAction action)
219{
220    g_object_set_data(G_OBJECT(m_platformDescription), WEBKIT_CONTEXT_MENU_ACTION, GINT_TO_POINTER(action));
221}
222
223String ContextMenuItem::title() const
224{
225    GtkAction* action = gtkAction();
226    return action ? String::fromUTF8(gtk_action_get_label(action)) : String();
227}
228
229void ContextMenuItem::setTitle(const String& title)
230{
231    GtkAction* action = gtkAction();
232    if (action)
233        gtk_action_set_label(action, title.utf8().data());
234}
235
236PlatformMenuDescription ContextMenuItem::platformSubMenu() const
237{
238    GtkWidget* subMenu = gtk_menu_item_get_submenu(m_platformDescription);
239    return subMenu ? GTK_MENU(subMenu) : 0;
240}
241
242void ContextMenuItem::setSubMenu(ContextMenu* menu)
243{
244    gtk_menu_item_set_submenu(m_platformDescription, GTK_WIDGET(menu->releasePlatformDescription()));
245}
246
247void ContextMenuItem::setSubMenu(Vector<ContextMenuItem>& subMenuItems)
248{
249    ContextMenu menu(platformMenuDescription(subMenuItems));
250    setSubMenu(&menu);
251}
252
253void ContextMenuItem::setChecked(bool shouldCheck)
254{
255    GtkAction* action = gtkAction();
256    if (action && GTK_IS_TOGGLE_ACTION(action))
257        gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), shouldCheck);
258}
259
260bool ContextMenuItem::checked() const
261{
262    GtkAction* action = gtkAction();
263    if (action && GTK_IS_TOGGLE_ACTION(action))
264        return gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
265    return false;
266}
267
268bool ContextMenuItem::enabled() const
269{
270    GtkAction* action = gtkAction();
271    return action ? gtk_action_get_sensitive(action) : false;
272}
273
274void ContextMenuItem::setEnabled(bool shouldEnable)
275{
276    GtkAction* action = gtkAction();
277    if (action)
278        gtk_action_set_sensitive(action, shouldEnable);
279}
280
281GtkAction* ContextMenuItem::gtkAction() const
282{
283    return gtk_activatable_get_related_action(GTK_ACTIVATABLE(m_platformDescription));
284}
285
286}
287
288#endif // ENABLE(CONTEXT_MENUS)
289