• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
BuildMenuItemWithImage(const std::string & label,GtkWidget * image)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 
BuildMenuItemWithImage(const std::string & label,const gfx::Image & icon)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 
BuildMenuItemWithLabel(const std::string & label)33 GtkWidget* BuildMenuItemWithLabel(const std::string& label) {
34   return gtk_menu_item_new_with_mnemonic(label.c_str());
35 }
36 
ModelForMenuItem(GtkMenuItem * menu_item)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 
AppendMenuItemToMenu(int index,ui::MenuModel * model,GtkWidget * menu_item,GtkWidget * menu,bool connect_to_activate,GCallback item_activated_cb,void * this_ptr)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 
GetMenuItemID(GtkWidget * menu_item,int * menu_id)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 
ExecuteCommand(ui::MenuModel * model,int id)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 
BuildSubmenuFromModel(ui::MenuModel * model,GtkWidget * menu,GCallback item_activated_cb,bool * block_activation,void * this_ptr)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 
SetMenuItemInfo(GtkWidget * widget,void * block_activation_ptr)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