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