| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/ui/libgtk2ui/menu_util.h" | |
| 6 | |
| 7 #include "base/strings/utf_string_conversions.h" | |
| 8 #include "chrome/app/chrome_command_ids.h" | |
| 9 #include "chrome/browser/ui/libgtk2ui/gtk2_util.h" | |
| 10 #include "chrome/browser/ui/libgtk2ui/skia_utils_gtk2.h" | |
| 11 #include "ui/base/accelerators/accelerator.h" | |
| 12 #include "ui/base/accelerators/menu_label_accelerator_util_linux.h" | |
| 13 #include "ui/base/models/menu_model.h" | |
| 14 | |
| 15 G_GNUC_BEGIN_IGNORE_DEPRECATIONS | |
| 16 | |
| 17 namespace libgtk2ui { | |
| 18 | |
| 19 GtkWidget* BuildMenuItemWithImage(const std::string& label, GtkWidget* image) { | |
| 20 GtkWidget* menu_item = gtk_image_menu_item_new_with_mnemonic(label.c_str()); | |
| 21 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item), image); | |
| 22 return menu_item; | |
| 23 } | |
| 24 | |
| 25 GtkWidget* BuildMenuItemWithImage(const std::string& label, | |
| 26 const gfx::Image& icon) { | |
| 27 GdkPixbuf* pixbuf = GdkPixbufFromSkBitmap(*icon.ToSkBitmap()); | |
| 28 | |
| 29 GtkWidget* menu_item = | |
| 30 BuildMenuItemWithImage(label, gtk_image_new_from_pixbuf(pixbuf)); | |
| 31 g_object_unref(pixbuf); | |
| 32 return menu_item; | |
| 33 } | |
| 34 | |
| 35 GtkWidget* BuildMenuItemWithLabel(const std::string& label) { | |
| 36 return gtk_menu_item_new_with_mnemonic(label.c_str()); | |
| 37 } | |
| 38 | |
| 39 ui::MenuModel* ModelForMenuItem(GtkMenuItem* menu_item) { | |
| 40 return reinterpret_cast<ui::MenuModel*>( | |
| 41 g_object_get_data(G_OBJECT(menu_item), "model")); | |
| 42 } | |
| 43 | |
| 44 GtkWidget* AppendMenuItemToMenu(int index, | |
| 45 ui::MenuModel* model, | |
| 46 GtkWidget* menu_item, | |
| 47 GtkWidget* menu, | |
| 48 bool connect_to_activate, | |
| 49 GCallback item_activated_cb, | |
| 50 void* this_ptr) { | |
| 51 // Set the ID of a menu item. | |
| 52 // Add 1 to the menu_id to avoid setting zero (null) to "menu-id". | |
| 53 g_object_set_data(G_OBJECT(menu_item), "menu-id", GINT_TO_POINTER(index + 1)); | |
| 54 | |
| 55 // Native menu items do their own thing, so only selectively listen for the | |
| 56 // activate signal. | |
| 57 if (connect_to_activate) { | |
| 58 g_signal_connect(menu_item, "activate", item_activated_cb, this_ptr); | |
| 59 } | |
| 60 | |
| 61 // AppendMenuItemToMenu is used both internally when we control menu creation | |
| 62 // from a model (where the model can choose to hide certain menu items), and | |
| 63 // with immediate commands which don't provide the option. | |
| 64 if (model) { | |
| 65 if (model->IsVisibleAt(index)) | |
| 66 gtk_widget_show(menu_item); | |
| 67 } else { | |
| 68 gtk_widget_show(menu_item); | |
| 69 } | |
| 70 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); | |
| 71 return menu_item; | |
| 72 } | |
| 73 | |
| 74 bool GetMenuItemID(GtkWidget* menu_item, int* menu_id) { | |
| 75 gpointer id_ptr = g_object_get_data(G_OBJECT(menu_item), "menu-id"); | |
| 76 if (id_ptr != NULL) { | |
| 77 *menu_id = GPOINTER_TO_INT(id_ptr) - 1; | |
| 78 return true; | |
| 79 } | |
| 80 | |
| 81 return false; | |
| 82 } | |
| 83 | |
| 84 void ExecuteCommand(ui::MenuModel* model, int id) { | |
| 85 GdkEvent* event = gtk_get_current_event(); | |
| 86 int event_flags = 0; | |
| 87 | |
| 88 if (event && event->type == GDK_BUTTON_RELEASE) | |
| 89 event_flags = EventFlagsFromGdkState(event->button.state); | |
| 90 model->ActivatedAt(id, event_flags); | |
| 91 | |
| 92 if (event) | |
| 93 gdk_event_free(event); | |
| 94 } | |
| 95 | |
| 96 void BuildSubmenuFromModel(ui::MenuModel* model, | |
| 97 GtkWidget* menu, | |
| 98 GCallback item_activated_cb, | |
| 99 bool* block_activation, | |
| 100 void* this_ptr) { | |
| 101 std::map<int, GtkWidget*> radio_groups; | |
| 102 GtkWidget* menu_item = NULL; | |
| 103 for (int i = 0; i < model->GetItemCount(); ++i) { | |
| 104 gfx::Image icon; | |
| 105 std::string label = ui::ConvertAcceleratorsFromWindowsStyle( | |
| 106 base::UTF16ToUTF8(model->GetLabelAt(i))); | |
| 107 | |
| 108 bool connect_to_activate = true; | |
| 109 | |
| 110 switch (model->GetTypeAt(i)) { | |
| 111 case ui::MenuModel::TYPE_SEPARATOR: | |
| 112 menu_item = gtk_separator_menu_item_new(); | |
| 113 break; | |
| 114 | |
| 115 case ui::MenuModel::TYPE_CHECK: | |
| 116 menu_item = gtk_check_menu_item_new_with_mnemonic(label.c_str()); | |
| 117 break; | |
| 118 | |
| 119 case ui::MenuModel::TYPE_RADIO: { | |
| 120 std::map<int, GtkWidget*>::iterator iter = | |
| 121 radio_groups.find(model->GetGroupIdAt(i)); | |
| 122 | |
| 123 if (iter == radio_groups.end()) { | |
| 124 menu_item = | |
| 125 gtk_radio_menu_item_new_with_mnemonic(NULL, label.c_str()); | |
| 126 radio_groups[model->GetGroupIdAt(i)] = menu_item; | |
| 127 } else { | |
| 128 menu_item = gtk_radio_menu_item_new_with_mnemonic_from_widget( | |
| 129 GTK_RADIO_MENU_ITEM(iter->second), label.c_str()); | |
| 130 } | |
| 131 break; | |
| 132 } | |
| 133 case ui::MenuModel::TYPE_BUTTON_ITEM: { | |
| 134 NOTIMPLEMENTED(); | |
| 135 break; | |
| 136 } | |
| 137 case ui::MenuModel::TYPE_SUBMENU: | |
| 138 case ui::MenuModel::TYPE_COMMAND: { | |
| 139 if (model->GetIconAt(i, &icon)) | |
| 140 menu_item = BuildMenuItemWithImage(label, icon); | |
| 141 else | |
| 142 menu_item = BuildMenuItemWithLabel(label); | |
| 143 if (GTK_IS_IMAGE_MENU_ITEM(menu_item)) { | |
| 144 gtk_image_menu_item_set_always_show_image( | |
| 145 GTK_IMAGE_MENU_ITEM(menu_item), TRUE); | |
| 146 } | |
| 147 break; | |
| 148 } | |
| 149 | |
| 150 default: | |
| 151 NOTREACHED(); | |
| 152 } | |
| 153 | |
| 154 if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU) { | |
| 155 GtkWidget* submenu = gtk_menu_new(); | |
| 156 ui::MenuModel* submenu_model = model->GetSubmenuModelAt(i); | |
| 157 BuildSubmenuFromModel(submenu_model, | |
| 158 submenu, | |
| 159 item_activated_cb, | |
| 160 block_activation, | |
| 161 this_ptr); | |
| 162 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu); | |
| 163 | |
| 164 // Update all the menu item info in the newly-generated menu. | |
| 165 gtk_container_foreach( | |
| 166 GTK_CONTAINER(submenu), SetMenuItemInfo, block_activation); | |
| 167 submenu_model->MenuWillShow(); | |
| 168 connect_to_activate = false; | |
| 169 } | |
| 170 | |
| 171 ui::Accelerator accelerator; | |
| 172 if (model->GetAcceleratorAt(i, &accelerator)) { | |
| 173 gtk_widget_add_accelerator(menu_item, | |
| 174 "activate", | |
| 175 NULL, | |
| 176 GetGdkKeyCodeForAccelerator(accelerator), | |
| 177 GetGdkModifierForAccelerator(accelerator), | |
| 178 GTK_ACCEL_VISIBLE); | |
| 179 } | |
| 180 | |
| 181 g_object_set_data(G_OBJECT(menu_item), "model", model); | |
| 182 AppendMenuItemToMenu(i, | |
| 183 model, | |
| 184 menu_item, | |
| 185 menu, | |
| 186 connect_to_activate, | |
| 187 item_activated_cb, | |
| 188 this_ptr); | |
| 189 | |
| 190 menu_item = NULL; | |
| 191 } | |
| 192 } | |
| 193 | |
| 194 void SetMenuItemInfo(GtkWidget* widget, void* block_activation_ptr) { | |
| 195 if (GTK_IS_SEPARATOR_MENU_ITEM(widget)) { | |
| 196 // We need to explicitly handle this case because otherwise we'll ask the | |
| 197 // menu delegate about something with an invalid id. | |
| 198 return; | |
| 199 } | |
| 200 | |
| 201 int id; | |
| 202 if (!GetMenuItemID(widget, &id)) | |
| 203 return; | |
| 204 | |
| 205 ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(widget)); | |
| 206 if (!model) { | |
| 207 // If we're not providing the sub menu, then there's no model. For | |
| 208 // example, the IME submenu doesn't have a model. | |
| 209 return; | |
| 210 } | |
| 211 bool* block_activation = static_cast<bool*>(block_activation_ptr); | |
| 212 | |
| 213 if (GTK_IS_CHECK_MENU_ITEM(widget)) { | |
| 214 GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(widget); | |
| 215 | |
| 216 // gtk_check_menu_item_set_active() will send the activate signal. Touching | |
| 217 // the underlying "active" property will also call the "activate" handler | |
| 218 // for this menu item. So we prevent the "activate" handler from | |
| 219 // being called while we set the checkbox. | |
| 220 // Why not use one of the glib signal-blocking functions? Because when we | |
| 221 // toggle a radio button, it will deactivate one of the other radio buttons, | |
| 222 // which we don't have a pointer to. | |
| 223 *block_activation = true; | |
| 224 gtk_check_menu_item_set_active(item, model->IsItemCheckedAt(id)); | |
| 225 *block_activation = false; | |
| 226 } | |
| 227 | |
| 228 if (GTK_IS_MENU_ITEM(widget)) { | |
| 229 gtk_widget_set_sensitive(widget, model->IsEnabledAt(id)); | |
| 230 | |
| 231 if (model->IsVisibleAt(id)) { | |
| 232 // Update the menu item label if it is dynamic. | |
| 233 if (model->IsItemDynamicAt(id)) { | |
| 234 std::string label = ui::ConvertAcceleratorsFromWindowsStyle( | |
| 235 base::UTF16ToUTF8(model->GetLabelAt(id))); | |
| 236 | |
| 237 gtk_menu_item_set_label(GTK_MENU_ITEM(widget), label.c_str()); | |
| 238 if (GTK_IS_IMAGE_MENU_ITEM(widget)) { | |
| 239 gfx::Image icon; | |
| 240 if (model->GetIconAt(id, &icon)) { | |
| 241 GdkPixbuf* pixbuf = GdkPixbufFromSkBitmap(*icon.ToSkBitmap()); | |
| 242 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), | |
| 243 gtk_image_new_from_pixbuf(pixbuf)); | |
| 244 g_object_unref(pixbuf); | |
| 245 } else { | |
| 246 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(widget), NULL); | |
| 247 } | |
| 248 } | |
| 249 } | |
| 250 | |
| 251 gtk_widget_show(widget); | |
| 252 } else { | |
| 253 gtk_widget_hide(widget); | |
| 254 } | |
| 255 | |
| 256 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget)); | |
| 257 if (submenu) { | |
| 258 gtk_container_foreach( | |
| 259 GTK_CONTAINER(submenu), &SetMenuItemInfo, block_activation_ptr); | |
| 260 } | |
| 261 } | |
| 262 } | |
| 263 | |
| 264 } // namespace libgtk2ui | |
| OLD | NEW |