Home | History | Annotate | Download | only in libgtk2ui
      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