1/*
2 * Copyright (C) 2012 Igalia S.L.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "WebKitContextMenuItem.h"
22
23#include "APIArray.h"
24#include "WebContextMenuItem.h"
25#include "WebContextMenuItemData.h"
26#include "WebKitContextMenuActionsPrivate.h"
27#include "WebKitContextMenuItemPrivate.h"
28#include "WebKitContextMenuPrivate.h"
29#include <WebCore/ContextMenu.h>
30#include <WebCore/ContextMenuItem.h>
31#include <gtk/gtk.h>
32#include <memory>
33#include <wtf/gobject/GRefPtr.h>
34#include <wtf/gobject/GUniquePtr.h>
35
36using namespace WebKit;
37using namespace WebCore;
38
39/**
40 * SECTION: WebKitContextMenuItem
41 * @Short_description: One item of the #WebKitContextMenu
42 * @Title: WebKitContextMenuItem
43 *
44 * The #WebKitContextMenu is composed of #WebKitContextMenuItem<!--
45 * -->s. These items can be created from a #GtkAction, from a
46 * #WebKitContextMenuAction or from a #WebKitContextMenuAction and a
47 * label. These #WebKitContextMenuAction<!-- -->s denote stock actions
48 * for the items. You can also create separators and submenus.
49 *
50 */
51
52struct _WebKitContextMenuItemPrivate {
53    ~_WebKitContextMenuItemPrivate()
54    {
55        if (subMenu)
56            webkitContextMenuSetParentItem(subMenu.get(), 0);
57    }
58
59    std::unique_ptr<ContextMenuItem> menuItem;
60    GRefPtr<WebKitContextMenu> subMenu;
61};
62
63WEBKIT_DEFINE_TYPE(WebKitContextMenuItem, webkit_context_menu_item, G_TYPE_INITIALLY_UNOWNED)
64
65static void webkit_context_menu_item_class_init(WebKitContextMenuItemClass*)
66{
67}
68
69static bool checkAndWarnIfMenuHasParentItem(WebKitContextMenu* menu)
70{
71    if (menu && webkitContextMenuGetParentItem(menu)) {
72        g_warning("Attempting to set a WebKitContextMenu as submenu of "
73                  "a WebKitContextMenuItem, but the menu is already "
74                  "a submenu of a WebKitContextMenuItem");
75        return true;
76    }
77
78    return false;
79}
80
81static void webkitContextMenuItemSetSubMenu(WebKitContextMenuItem* item, GRefPtr<WebKitContextMenu> subMenu)
82{
83    if (checkAndWarnIfMenuHasParentItem(subMenu.get()))
84        return;
85
86    if (item->priv->subMenu)
87        webkitContextMenuSetParentItem(item->priv->subMenu.get(), 0);
88    item->priv->subMenu = subMenu;
89    if (subMenu)
90        webkitContextMenuSetParentItem(subMenu.get(), item);
91}
92
93WebKitContextMenuItem* webkitContextMenuItemCreate(WebContextMenuItem* webItem)
94{
95    WebKitContextMenuItem* item = WEBKIT_CONTEXT_MENU_ITEM(g_object_new(WEBKIT_TYPE_CONTEXT_MENU_ITEM, NULL));
96    WebContextMenuItemData* itemData = webItem->data();
97    item->priv->menuItem = std::make_unique<ContextMenuItem>(itemData->type(), itemData->action(), itemData->title(), itemData->enabled(), itemData->checked());
98    const Vector<WebContextMenuItemData>& subMenu = itemData->submenu();
99    if (!subMenu.size())
100        return item;
101
102    Vector<RefPtr<API::Object>> subMenuItems;
103    subMenuItems.reserveInitialCapacity(subMenu.size());
104    for (size_t i = 0; i < subMenu.size(); ++i)
105        subMenuItems.uncheckedAppend(WebContextMenuItem::create(subMenu[i]).get());
106    webkitContextMenuItemSetSubMenu(item, adoptGRef(webkitContextMenuCreate(API::Array::create(WTF::move(subMenuItems)).get())));
107
108    return item;
109}
110
111static WebKitContextMenuItem* webkitContextMenuItemCreateForGtkItem(GtkMenuItem* menuItem)
112{
113    WebKitContextMenuItem* item = WEBKIT_CONTEXT_MENU_ITEM(g_object_new(WEBKIT_TYPE_CONTEXT_MENU_ITEM, NULL));
114    item->priv->menuItem = std::make_unique<ContextMenuItem>(menuItem);
115    webkitContextMenuItemSetSubMenuFromGtkMenu(item, GTK_MENU(gtk_menu_item_get_submenu(menuItem)));
116
117    return item;
118}
119
120void webkitContextMenuItemSetSubMenuFromGtkMenu(WebKitContextMenuItem* item, GtkMenu* subMenu)
121{
122    if (!subMenu)
123        return;
124
125    GUniquePtr<GList> children(gtk_container_get_children(GTK_CONTAINER(subMenu)));
126    if (!g_list_length(children.get()))
127        return;
128
129    webkitContextMenuItemSetSubMenu(item, adoptGRef(webkit_context_menu_new()));
130    for (GList* listItem = children.get(); listItem; listItem = g_list_next(listItem)) {
131        GRefPtr<GtkWidget> widget = GTK_WIDGET(listItem->data);
132        if (!GTK_IS_MENU_ITEM(widget.get()))
133            continue;
134
135        gtk_container_remove(GTK_CONTAINER(subMenu), widget.get());
136        GtkMenuItem* menuItem = GTK_MENU_ITEM(widget.leakRef());
137        g_object_force_floating(G_OBJECT(menuItem));
138        webkit_context_menu_append(item->priv->subMenu.get(), webkitContextMenuItemCreateForGtkItem(menuItem));
139    }
140}
141
142GtkMenuItem* webkitContextMenuItemRelease(WebKitContextMenuItem* item)
143{
144    if (item->priv->subMenu) {
145        Vector<ContextMenuItem> subMenuItems;
146        webkitContextMenuPopulate(item->priv->subMenu.get(), subMenuItems);
147        ContextMenu subMenu(platformMenuDescription(subMenuItems));
148        item->priv->menuItem->setSubMenu(&subMenu);
149    }
150
151    return item->priv->menuItem->releasePlatformDescription();
152}
153
154/**
155 * webkit_context_menu_item_new:
156 * @action: a #GtkAction
157 *
158 * Creates a new #WebKitContextMenuItem for the given @action.
159 *
160 * Returns: the newly created #WebKitContextMenuItem object.
161 */
162WebKitContextMenuItem* webkit_context_menu_item_new(GtkAction* action)
163{
164    g_return_val_if_fail(GTK_IS_ACTION(action), 0);
165
166    WebKitContextMenuItem* item = WEBKIT_CONTEXT_MENU_ITEM(g_object_new(WEBKIT_TYPE_CONTEXT_MENU_ITEM, NULL));
167    item->priv->menuItem = std::make_unique<ContextMenuItem>(GTK_MENU_ITEM(gtk_action_create_menu_item(action)));
168    item->priv->menuItem->setAction(ContextMenuItemBaseApplicationTag);
169
170    return item;
171}
172
173/**
174 * webkit_context_menu_item_new_from_stock_action:
175 * @action: a #WebKitContextMenuAction stock action
176 *
177 * Creates a new #WebKitContextMenuItem for the given stock action.
178 * Stock actions are handled automatically by WebKit so that, for example,
179 * when a menu item created with a %WEBKIT_CONTEXT_MENU_ACTION_STOP is
180 * activated the action associated will be handled by WebKit and the current
181 * load operation will be stopped. You can get the #GtkAction of a
182 * #WebKitContextMenuItem created with a #WebKitContextMenuAction with
183 * webkit_context_menu_item_get_action() and connect to #GtkAction::activate signal
184 * to be notified when the item is activated. But you can't prevent the asociated
185 * action from being performed.
186 *
187 * Returns: the newly created #WebKitContextMenuItem object.
188 */
189WebKitContextMenuItem* webkit_context_menu_item_new_from_stock_action(WebKitContextMenuAction action)
190{
191    g_return_val_if_fail(action > WEBKIT_CONTEXT_MENU_ACTION_NO_ACTION && action < WEBKIT_CONTEXT_MENU_ACTION_CUSTOM, 0);
192
193    WebKitContextMenuItem* item = WEBKIT_CONTEXT_MENU_ITEM(g_object_new(WEBKIT_TYPE_CONTEXT_MENU_ITEM, NULL));
194    ContextMenuItemType type = webkitContextMenuActionIsCheckable(action) ? CheckableActionType : ActionType;
195    item->priv->menuItem = std::make_unique<ContextMenuItem>(type, webkitContextMenuActionGetActionTag(action), webkitContextMenuActionGetLabel(action));
196
197    return item;
198}
199
200/**
201 * webkit_context_menu_item_new_from_stock_action_with_label:
202 * @action: a #WebKitContextMenuAction stock action
203 * @label: a custom label text to use instead of the predefined one
204 *
205 * Creates a new #WebKitContextMenuItem for the given stock action using the given @label.
206 * Stock actions have a predefined label, this method can be used to create a
207 * #WebKitContextMenuItem for a #WebKitContextMenuAction but using a custom label.
208 *
209 * Returns: the newly created #WebKitContextMenuItem object.
210 */
211WebKitContextMenuItem* webkit_context_menu_item_new_from_stock_action_with_label(WebKitContextMenuAction action, const gchar* label)
212{
213    g_return_val_if_fail(action > WEBKIT_CONTEXT_MENU_ACTION_NO_ACTION && action < WEBKIT_CONTEXT_MENU_ACTION_CUSTOM, 0);
214
215    WebKitContextMenuItem* item = WEBKIT_CONTEXT_MENU_ITEM(g_object_new(WEBKIT_TYPE_CONTEXT_MENU_ITEM, NULL));
216    ContextMenuItemType type = webkitContextMenuActionIsCheckable(action) ? CheckableActionType : ActionType;
217    item->priv->menuItem = std::make_unique<ContextMenuItem>(type, webkitContextMenuActionGetActionTag(action), String::fromUTF8(label));
218
219    return item;
220}
221
222/**
223 * webkit_context_menu_item_new_with_submenu:
224 * @label: the menu item label text
225 * @submenu: a #WebKitContextMenu to set
226 *
227 * Creates a new #WebKitContextMenuItem using the given @label with a submenu.
228 *
229 * Returns: the newly created #WebKitContextMenuItem object.
230 */
231WebKitContextMenuItem* webkit_context_menu_item_new_with_submenu(const gchar* label, WebKitContextMenu* submenu)
232{
233    g_return_val_if_fail(label, 0);
234    g_return_val_if_fail(WEBKIT_IS_CONTEXT_MENU(submenu), 0);
235
236    if (checkAndWarnIfMenuHasParentItem(submenu))
237        return 0;
238
239    WebKitContextMenuItem* item = WEBKIT_CONTEXT_MENU_ITEM(g_object_new(WEBKIT_TYPE_CONTEXT_MENU_ITEM, NULL));
240    item->priv->menuItem = std::make_unique<ContextMenuItem>(SubmenuType, ContextMenuItemBaseApplicationTag, String::fromUTF8(label));
241    item->priv->subMenu = submenu;
242    webkitContextMenuSetParentItem(submenu, item);
243
244    return item;
245}
246
247/**
248 * webkit_context_menu_item_new_separator:
249 *
250 * Creates a new #WebKitContextMenuItem representing a separator.
251 *
252 * Returns: the newly created #WebKitContextMenuItem object.
253 */
254WebKitContextMenuItem* webkit_context_menu_item_new_separator(void)
255{
256    WebKitContextMenuItem* item = WEBKIT_CONTEXT_MENU_ITEM(g_object_new(WEBKIT_TYPE_CONTEXT_MENU_ITEM, NULL));
257    item->priv->menuItem = std::make_unique<ContextMenuItem>(SeparatorType, ContextMenuItemTagNoAction, String());
258
259    return item;
260}
261
262/**
263 * webkit_context_menu_item_get_action:
264 * @item: a #WebKitContextMenuItem
265 *
266 * Gets the action associated to @item.
267 *
268 * Returns: (transfer none): the #GtkAction associated to the #WebKitContextMenuItem,
269 *    or %NULL if @item is a separator.
270 */
271GtkAction* webkit_context_menu_item_get_action(WebKitContextMenuItem* item)
272{
273    g_return_val_if_fail(WEBKIT_IS_CONTEXT_MENU_ITEM(item), 0);
274
275    return item->priv->menuItem->gtkAction();
276}
277
278/**
279 * webkit_context_menu_item_get_stock_action:
280 * @item: a #WebKitContextMenuItem
281 *
282 * Gets the #WebKitContextMenuAction of @item. If the #WebKitContextMenuItem was not
283 * created for a stock action %WEBKIT_CONTEXT_MENU_ACTION_CUSTOM will be
284 * returned. If the #WebKitContextMenuItem is a separator %WEBKIT_CONTEXT_MENU_ACTION_NO_ACTION
285 * will be returned.
286 *
287 * Returns: the #WebKitContextMenuAction of @item
288 */
289WebKitContextMenuAction webkit_context_menu_item_get_stock_action(WebKitContextMenuItem* item)
290{
291    g_return_val_if_fail(WEBKIT_IS_CONTEXT_MENU_ITEM(item), WEBKIT_CONTEXT_MENU_ACTION_NO_ACTION);
292
293    return webkitContextMenuActionGetForContextMenuItem(item->priv->menuItem.get());
294}
295
296/**
297 * webkit_context_menu_item_is_separator:
298 * @item: a #WebKitContextMenuItem
299 *
300 * Checks whether @item is a separator.
301 *
302 * Returns: %TRUE is @item is a separator or %FALSE otherwise
303 */
304gboolean webkit_context_menu_item_is_separator(WebKitContextMenuItem* item)
305{
306    g_return_val_if_fail(WEBKIT_IS_CONTEXT_MENU_ITEM(item), FALSE);
307
308    return item->priv->menuItem->type() == SeparatorType;
309}
310
311/**
312 * webkit_context_menu_item_set_submenu:
313 * @item: a #WebKitContextMenuItem
314 * @submenu: (allow-none): a #WebKitContextMenu
315 *
316 * Sets or replaces the @item submenu. If @submenu is %NULL the current
317 * submenu of @item is removed.
318 */
319void webkit_context_menu_item_set_submenu(WebKitContextMenuItem* item, WebKitContextMenu* submenu)
320{
321    g_return_if_fail(WEBKIT_IS_CONTEXT_MENU_ITEM(item));
322
323    if (item->priv->subMenu == submenu)
324        return;
325
326    webkitContextMenuItemSetSubMenu(item, submenu);
327}
328
329/**
330 * webkit_context_menu_item_get_submenu:
331 * @item: a #WebKitContextMenuItem
332 *
333 * Gets the submenu of @item.
334 *
335 * Returns: (transfer none): the #WebKitContextMenu representing the submenu of
336 *    @item or %NULL if @item doesn't have a submenu.
337 */
338WebKitContextMenu* webkit_context_menu_item_get_submenu(WebKitContextMenuItem* item)
339{
340    g_return_val_if_fail(WEBKIT_IS_CONTEXT_MENU_ITEM(item), 0);
341
342    return item->priv->subMenu.get();
343}
344
345