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