Home | History | Annotate | Download | only in gtk
      1 // Copyright (c) 2011 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/gtk/menu_bar_helper.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/logging.h"
     10 #include "chrome/browser/ui/gtk/gtk_util.h"
     11 #include "ui/base/gtk/gtk_signal_registrar.h"
     12 
     13 namespace {
     14 
     15 // Recursively find all the GtkMenus that are attached to menu item |child|
     16 // and add them to |data|, which is a vector of GtkWidgets.
     17 void PopulateSubmenus(GtkWidget* child, gpointer data) {
     18   std::vector<GtkWidget*>* submenus =
     19       static_cast<std::vector<GtkWidget*>*>(data);
     20   GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(child));
     21   if (submenu) {
     22     submenus->push_back(submenu);
     23     gtk_container_foreach(GTK_CONTAINER(submenu), PopulateSubmenus, submenus);
     24   }
     25 }
     26 
     27 // Is the cursor over |menu| or one of its parent menus?
     28 bool MotionIsOverMenu(GtkWidget* menu, GdkEventMotion* motion) {
     29   GtkAllocation allocation;
     30   gtk_widget_get_allocation(menu, &allocation);
     31 
     32   if (motion->x >= 0 && motion->y >= 0 &&
     33       motion->x < allocation.width &&
     34       motion->y < allocation.height) {
     35     return true;
     36   }
     37 
     38   while (menu) {
     39     GtkWidget* menu_item = gtk_menu_get_attach_widget(GTK_MENU(menu));
     40     if (!menu_item)
     41       return false;
     42     GtkWidget* parent = gtk_widget_get_parent(menu_item);
     43 
     44     if (gtk_util::WidgetContainsCursor(parent))
     45       return true;
     46     menu = parent;
     47   }
     48 
     49   return false;
     50 }
     51 
     52 }  // namespace
     53 
     54 MenuBarHelper::MenuBarHelper(Delegate* delegate)
     55     : button_showing_menu_(NULL),
     56       showing_menu_(NULL),
     57       delegate_(delegate) {
     58   DCHECK(delegate_);
     59 }
     60 
     61 MenuBarHelper::~MenuBarHelper() {
     62 }
     63 
     64 void MenuBarHelper::Add(GtkWidget* button) {
     65   buttons_.push_back(button);
     66 }
     67 
     68 void MenuBarHelper::Remove(GtkWidget* button) {
     69   std::vector<GtkWidget*>::iterator iter =
     70       find(buttons_.begin(), buttons_.end(), button);
     71   if (iter == buttons_.end()) {
     72     NOTREACHED();
     73     return;
     74   }
     75   buttons_.erase(iter);
     76 }
     77 
     78 void MenuBarHelper::Clear() {
     79   buttons_.clear();
     80 }
     81 
     82 void MenuBarHelper::MenuStartedShowing(GtkWidget* button, GtkWidget* menu) {
     83   DCHECK(GTK_IS_MENU(menu));
     84   button_showing_menu_ = button;
     85   showing_menu_ = menu;
     86 
     87   signal_handlers_.reset(new ui::GtkSignalRegistrar());
     88   signal_handlers_->Connect(menu, "destroy",
     89                             G_CALLBACK(OnMenuHiddenOrDestroyedThunk), this);
     90   signal_handlers_->Connect(menu, "hide",
     91                             G_CALLBACK(OnMenuHiddenOrDestroyedThunk), this);
     92   signal_handlers_->Connect(menu, "motion-notify-event",
     93                             G_CALLBACK(OnMenuMotionNotifyThunk), this);
     94   signal_handlers_->Connect(menu, "move-current",
     95                             G_CALLBACK(OnMenuMoveCurrentThunk), this);
     96   gtk_container_foreach(GTK_CONTAINER(menu), PopulateSubmenus, &submenus_);
     97 
     98   for (size_t i = 0; i < submenus_.size(); ++i) {
     99     signal_handlers_->Connect(submenus_[i], "motion-notify-event",
    100                               G_CALLBACK(OnMenuMotionNotifyThunk), this);
    101   }
    102 }
    103 
    104 gboolean MenuBarHelper::OnMenuMotionNotify(GtkWidget* menu,
    105                                            GdkEventMotion* motion) {
    106   // Don't do anything if pointer is in the menu.
    107   if (MotionIsOverMenu(menu, motion))
    108     return FALSE;
    109   if (buttons_.empty())
    110     return FALSE;
    111 
    112   gint x = 0;
    113   gint y = 0;
    114   GtkWidget* last_button = NULL;
    115 
    116   for (size_t i = 0; i < buttons_.size(); ++i) {
    117     GtkWidget* button = buttons_[i];
    118     // Figure out coordinates relative to this button. Avoid using
    119     // gtk_widget_get_pointer() unnecessarily.
    120     if (i == 0) {
    121       // We have to make this call because the menu is a popup window, so it
    122       // doesn't share a toplevel with the buttons and we can't just use
    123       // gtk_widget_translate_coordinates().
    124       gtk_widget_get_pointer(buttons_[0], &x, &y);
    125     } else {
    126       gint last_x = x;
    127       gint last_y = y;
    128       if (!gtk_widget_translate_coordinates(
    129           last_button, button, last_x, last_y, &x, &y)) {
    130         // |button| may not be realized.
    131         continue;
    132       }
    133     }
    134 
    135     last_button = button;
    136 
    137     GtkAllocation allocation;
    138     gtk_widget_get_allocation(button, &allocation);
    139 
    140     if (x >= 0 && y >= 0 && x < allocation.width && y < allocation.height) {
    141       if (button != button_showing_menu_)
    142         delegate_->PopupForButton(button);
    143       return TRUE;
    144     }
    145   }
    146 
    147   return FALSE;
    148 }
    149 
    150 void MenuBarHelper::OnMenuHiddenOrDestroyed(GtkWidget* menu) {
    151   DCHECK_EQ(showing_menu_, menu);
    152 
    153   signal_handlers_.reset();
    154   showing_menu_ = NULL;
    155   button_showing_menu_ = NULL;
    156   submenus_.clear();
    157 }
    158 
    159 void MenuBarHelper::OnMenuMoveCurrent(GtkWidget* menu,
    160                                       GtkMenuDirectionType dir) {
    161   // The menu directions are triggered by the arrow keys as follows
    162   //
    163   //   PARENT   left
    164   //   CHILD    right
    165   //   NEXT     down
    166   //   PREV     up
    167   //
    168   // We only care about left and right. Note that for RTL, they are swapped.
    169   switch (dir) {
    170     case GTK_MENU_DIR_CHILD: {
    171       GtkWidget* active_item = GTK_MENU_SHELL(menu)->active_menu_item;
    172       // The move is going to open a submenu; don't override default behavior.
    173       if (active_item && gtk_menu_item_get_submenu(GTK_MENU_ITEM(active_item)))
    174         return;
    175       // Fall through.
    176     }
    177     case GTK_MENU_DIR_PARENT: {
    178       delegate_->PopupForButtonNextTo(button_showing_menu_, dir);
    179       break;
    180     }
    181     default:
    182       return;
    183   }
    184 
    185   // This signal doesn't have a return value; we have to manually stop its
    186   // propagation.
    187   g_signal_stop_emission_by_name(menu, "move-current");
    188 }
    189