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/gtk_custom_menu.h"
      6 
      7 #include "chrome/browser/ui/gtk/gtk_custom_menu_item.h"
      8 
      9 G_DEFINE_TYPE(GtkCustomMenu, gtk_custom_menu, GTK_TYPE_MENU)
     10 
     11 // Stolen directly from gtkmenushell.c. I'd love to call the library version
     12 // instead, but it's static and isn't exported. :(
     13 static gint gtk_menu_shell_is_item(GtkMenuShell* menu_shell,
     14                                    GtkWidget* child) {
     15   GtkWidget *parent;
     16 
     17   g_return_val_if_fail(GTK_IS_MENU_SHELL(menu_shell), FALSE);
     18   g_return_val_if_fail(child != NULL, FALSE);
     19 
     20   parent = child->parent;
     21   while (GTK_IS_MENU_SHELL(parent)) {
     22     if (parent == reinterpret_cast<GtkWidget*>(menu_shell))
     23       return TRUE;
     24     parent = GTK_MENU_SHELL(parent)->parent_menu_shell;
     25   }
     26 
     27   return FALSE;
     28 }
     29 
     30 // Stolen directly from gtkmenushell.c. I'd love to call the library version
     31 // instead, but it's static and isn't exported. :(
     32 static GtkWidget* gtk_menu_shell_get_item(GtkMenuShell* menu_shell,
     33                                           GdkEvent* event) {
     34   GtkWidget* menu_item = gtk_get_event_widget(event);
     35 
     36   while (menu_item && !GTK_IS_MENU_ITEM(menu_item))
     37     menu_item = menu_item->parent;
     38 
     39   if (menu_item && gtk_menu_shell_is_item(menu_shell, menu_item))
     40     return menu_item;
     41   else
     42     return NULL;
     43 }
     44 
     45 // When processing a button event, abort processing if the cursor isn't in a
     46 // clickable region.
     47 static gboolean gtk_custom_menu_button_press(GtkWidget* widget,
     48                                              GdkEventButton* event) {
     49   GtkWidget* menu_item = gtk_menu_shell_get_item(
     50       GTK_MENU_SHELL(widget), reinterpret_cast<GdkEvent*>(event));
     51   if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) {
     52     if (!gtk_custom_menu_item_is_in_clickable_region(
     53             GTK_CUSTOM_MENU_ITEM(menu_item))) {
     54       return TRUE;
     55     }
     56   }
     57 
     58   return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)->
     59       button_press_event(widget, event);
     60 }
     61 
     62 // When processing a button event, abort processing if the cursor isn't in a
     63 // clickable region. If it's in a button that doesn't dismiss the menu, fire
     64 // that event and abort having the normal GtkMenu code run.
     65 static gboolean gtk_custom_menu_button_release(GtkWidget* widget,
     66                                                GdkEventButton* event) {
     67   GtkWidget* menu_item = gtk_menu_shell_get_item(
     68       GTK_MENU_SHELL(widget), reinterpret_cast<GdkEvent*>(event));
     69   if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) {
     70     if (!gtk_custom_menu_item_is_in_clickable_region(
     71             GTK_CUSTOM_MENU_ITEM(menu_item))) {
     72       // Stop processing this event. This isn't a clickable region.
     73       return TRUE;
     74     }
     75 
     76     if (gtk_custom_menu_item_try_no_dismiss_command(
     77             GTK_CUSTOM_MENU_ITEM(menu_item))) {
     78       return TRUE;
     79     }
     80   }
     81 
     82   return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)->
     83       button_release_event(widget, event);
     84 }
     85 
     86 // Manually forward button press events to the menu item (and then do what we'd
     87 // do normally).
     88 static gboolean gtk_custom_menu_motion_notify(GtkWidget* widget,
     89                                               GdkEventMotion* event) {
     90   GtkWidget* menu_item = gtk_menu_shell_get_item(
     91       GTK_MENU_SHELL(widget), (GdkEvent*)event);
     92   if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) {
     93     gtk_custom_menu_item_receive_motion_event(GTK_CUSTOM_MENU_ITEM(menu_item),
     94                                               event->x, event->y);
     95   }
     96 
     97   return GTK_WIDGET_CLASS(gtk_custom_menu_parent_class)->
     98       motion_notify_event(widget, event);
     99 }
    100 
    101 static void gtk_custom_menu_move_current(GtkMenuShell* menu_shell,
    102                                          GtkMenuDirectionType direction) {
    103   // If the currently selected item is custom, we give it first chance to catch
    104   // up/down events.
    105 
    106   // TODO(erg): We are breaking a GSEAL by directly accessing this. We'll need
    107   // to fix this by the time gtk3 comes out.
    108   GtkWidget* menu_item = GTK_MENU_SHELL(menu_shell)->active_menu_item;
    109   if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) {
    110     switch (direction) {
    111       case GTK_MENU_DIR_PREV:
    112       case GTK_MENU_DIR_NEXT:
    113         if (gtk_custom_menu_item_handle_move(GTK_CUSTOM_MENU_ITEM(menu_item),
    114                                              direction))
    115           return;
    116         break;
    117       default:
    118         break;
    119     }
    120   }
    121 
    122   GTK_MENU_SHELL_CLASS(gtk_custom_menu_parent_class)->
    123       move_current(menu_shell, direction);
    124 
    125   // In the case of hitting PREV and transitioning to a custom menu, we want to
    126   // make sure we're selecting the final item in the list, not the first one.
    127   menu_item = GTK_MENU_SHELL(menu_shell)->active_menu_item;
    128   if (GTK_IS_CUSTOM_MENU_ITEM(menu_item)) {
    129     gtk_custom_menu_item_select_item_by_direction(
    130         GTK_CUSTOM_MENU_ITEM(menu_item), direction);
    131   }
    132 }
    133 
    134 static void gtk_custom_menu_init(GtkCustomMenu* menu) {
    135 }
    136 
    137 static void gtk_custom_menu_class_init(GtkCustomMenuClass* klass) {
    138   GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
    139   GtkMenuShellClass* menu_shell_class = GTK_MENU_SHELL_CLASS(klass);
    140 
    141   widget_class->button_press_event = gtk_custom_menu_button_press;
    142   widget_class->button_release_event = gtk_custom_menu_button_release;
    143   widget_class->motion_notify_event = gtk_custom_menu_motion_notify;
    144 
    145   menu_shell_class->move_current = gtk_custom_menu_move_current;
    146 }
    147 
    148 GtkWidget* gtk_custom_menu_new() {
    149   return GTK_WIDGET(g_object_new(GTK_TYPE_CUSTOM_MENU, NULL));
    150 }
    151