| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/ui/gtk/menu_gtk.h" | 5 #include "chrome/browser/ui/gtk/menu_gtk.h" |
| 6 | 6 |
| 7 #include <map> | 7 #include <map> |
| 8 | 8 |
| 9 #include "base/bind.h" | 9 #include "base/bind.h" |
| 10 #include "base/i18n/rtl.h" | 10 #include "base/i18n/rtl.h" |
| (...skipping 489 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 500 | 500 |
| 501 if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU) { | 501 if (model->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU) { |
| 502 GtkWidget* submenu = gtk_menu_new(); | 502 GtkWidget* submenu = gtk_menu_new(); |
| 503 g_object_set_data(G_OBJECT(submenu), "menu-item", menu_item); | 503 g_object_set_data(G_OBJECT(submenu), "menu-item", menu_item); |
| 504 ui::MenuModel* submenu_model = model->GetSubmenuModelAt(i); | 504 ui::MenuModel* submenu_model = model->GetSubmenuModelAt(i); |
| 505 g_object_set_data(G_OBJECT(menu_item), "submenu-model", submenu_model); | 505 g_object_set_data(G_OBJECT(menu_item), "submenu-model", submenu_model); |
| 506 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu); | 506 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu); |
| 507 // We will populate the submenu on demand when shown. | 507 // We will populate the submenu on demand when shown. |
| 508 g_signal_connect(submenu, "show", G_CALLBACK(OnSubMenuShowThunk), this); | 508 g_signal_connect(submenu, "show", G_CALLBACK(OnSubMenuShowThunk), this); |
| 509 g_signal_connect(submenu, "hide", G_CALLBACK(OnSubMenuHiddenThunk), this); | 509 g_signal_connect(submenu, "hide", G_CALLBACK(OnSubMenuHiddenThunk), this); |
| 510 connect_to_activate = false; |
| 510 } | 511 } |
| 511 | 512 |
| 512 ui::AcceleratorGtk accelerator; | 513 ui::AcceleratorGtk accelerator; |
| 513 if (model->GetAcceleratorAt(i, &accelerator)) { | 514 if (model->GetAcceleratorAt(i, &accelerator)) { |
| 514 gtk_widget_add_accelerator(menu_item, | 515 gtk_widget_add_accelerator(menu_item, |
| 515 "activate", | 516 "activate", |
| 516 dummy_accel_group_, | 517 dummy_accel_group_, |
| 517 accelerator.GetGdkKeyCode(), | 518 accelerator.GetGdkKeyCode(), |
| 518 accelerator.gdk_modifier_type(), | 519 accelerator.gdk_modifier_type(), |
| 519 GTK_ACCEL_VISIBLE); | 520 GTK_ACCEL_VISIBLE); |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 591 | 592 |
| 592 return menu_item; | 593 return menu_item; |
| 593 } | 594 } |
| 594 | 595 |
| 595 void MenuGtk::OnMenuItemActivated(GtkWidget* menu_item) { | 596 void MenuGtk::OnMenuItemActivated(GtkWidget* menu_item) { |
| 596 if (block_activation_) | 597 if (block_activation_) |
| 597 return; | 598 return; |
| 598 | 599 |
| 599 ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(menu_item)); | 600 ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(menu_item)); |
| 600 | 601 |
| 601 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)); | |
| 602 if (submenu) { | |
| 603 // We receive activation messages when highlighting a menu that has a | |
| 604 // submenu. We build submenus on show, and tear them down on hide, to | |
| 605 // allow submenu models to be constructed and destroyed on demand. So, | |
| 606 // there's nothing further to do here in that case. | |
| 607 return; | |
| 608 } | |
| 609 | |
| 610 // The activate signal is sent to radio items as they get deselected; | 602 // The activate signal is sent to radio items as they get deselected; |
| 611 // ignore it in this case. | 603 // ignore it in this case. |
| 612 if (GTK_IS_RADIO_MENU_ITEM(menu_item) && | 604 if (GTK_IS_RADIO_MENU_ITEM(menu_item) && |
| 613 !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item))) { | 605 !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item))) { |
| 614 return; | 606 return; |
| 615 } | 607 } |
| 616 | 608 |
| 617 int id; | 609 int id; |
| 618 if (!GetMenuItemID(menu_item, &id)) | 610 if (!GetMenuItemID(menu_item, &id)) |
| 619 return; | 611 return; |
| (...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 748 } | 740 } |
| 749 | 741 |
| 750 gboolean MenuGtk::OnMenuFocusOut(GtkWidget* widget, GdkEventFocus* event) { | 742 gboolean MenuGtk::OnMenuFocusOut(GtkWidget* widget, GdkEventFocus* event) { |
| 751 gtk_widget_hide(menu_); | 743 gtk_widget_hide(menu_); |
| 752 return TRUE; | 744 return TRUE; |
| 753 } | 745 } |
| 754 | 746 |
| 755 void MenuGtk::OnSubMenuShow(GtkWidget* submenu) { | 747 void MenuGtk::OnSubMenuShow(GtkWidget* submenu) { |
| 756 GtkWidget* menu_item = static_cast<GtkWidget*>( | 748 GtkWidget* menu_item = static_cast<GtkWidget*>( |
| 757 g_object_get_data(G_OBJECT(submenu), "menu-item")); | 749 g_object_get_data(G_OBJECT(submenu), "menu-item")); |
| 758 | 750 // TODO(mdm): Figure out why this can sometimes be NULL. See bug 131974. |
| 751 CHECK(menu_item); |
| 759 // Notify the submenu model that the menu will be shown. | 752 // Notify the submenu model that the menu will be shown. |
| 760 ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>( | 753 ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>( |
| 761 g_object_get_data(G_OBJECT(menu_item), "submenu-model")); | 754 g_object_get_data(G_OBJECT(menu_item), "submenu-model")); |
| 762 // TODO(mdm): Figure out why this can sometimes be NULL. See bug 131974. | 755 // We're extra cautious here, and bail out if the submenu model is NULL. In |
| 756 // some cases we clear it out from a parent menu; we shouldn't ever show the |
| 757 // menu after that, but we play it safe since we're dealing with wacky |
| 758 // injected libraries that toy with our menus. (See comments below.) |
| 763 if (!submenu_model) | 759 if (!submenu_model) |
| 764 return; | 760 return; |
| 761 |
| 762 // If the submenu is already built, then return right away. This means we |
| 763 // recently showed this submenu, and have not yet processed the fact that it |
| 764 // was hidden before being shown again. |
| 765 if (g_object_get_data(G_OBJECT(submenu), "submenu-built")) |
| 766 return; |
| 767 g_object_set_data(G_OBJECT(submenu), "submenu-built", GINT_TO_POINTER(1)); |
| 768 |
| 765 submenu_model->MenuWillShow(); | 769 submenu_model->MenuWillShow(); |
| 766 | 770 |
| 767 // Actually build the submenu and attach it to the parent menu item. | 771 // Actually build the submenu and attach it to the parent menu item. |
| 768 BuildSubmenuFromModel(submenu_model, submenu); | 772 BuildSubmenuFromModel(submenu_model, submenu); |
| 769 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu); | 773 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu); |
| 770 | 774 |
| 771 // Update all the menu item info in the newly-generated menu. | 775 // Update all the menu item info in the newly-generated menu. |
| 772 gtk_container_foreach(GTK_CONTAINER(submenu), SetMenuItemInfo, this); | 776 gtk_container_foreach(GTK_CONTAINER(submenu), SetMenuItemInfo, this); |
| 773 } | 777 } |
| 774 | 778 |
| 775 void MenuGtk::OnSubMenuHidden(GtkWidget* submenu) { | 779 void MenuGtk::OnSubMenuHidden(GtkWidget* submenu) { |
| 776 // Increase the reference count of the old submenu, and schedule it to be | 780 // Increase the reference count of the old submenu, and schedule it to be |
| 777 // deleted later. We get this hide notification before we've processed menu | 781 // deleted later. We get this hide notification before we've processed menu |
| 778 // activations, so if we were to delete the submenu now, we might lose the | 782 // activations, so if we were to delete the submenu now, we might lose the |
| 779 // activation. Note that we disconnect it from the parent menu item below. | 783 // activation. This also lets us reuse the menu if it is shown again before |
| 784 // it gets deleted; in that case, OnSubMenuHiddenCallback() just decrements |
| 785 // the reference count again. Note that the delay is just an optimization; we |
| 786 // could use PostTask() and this would still work correctly. |
| 780 g_object_ref(G_OBJECT(submenu)); | 787 g_object_ref(G_OBJECT(submenu)); |
| 781 MessageLoop::current()->PostTask( | 788 MessageLoop::current()->PostDelayedTask( |
| 782 FROM_HERE, | 789 FROM_HERE, |
| 783 base::Bind(&MenuGtk::OnSubMenuHiddenCallback, submenu)); | 790 base::Bind(&MenuGtk::OnSubMenuHiddenCallback, submenu), |
| 791 base::TimeDelta::FromSeconds(2)); |
| 792 } |
| 784 | 793 |
| 785 // Build a new submenu, which will be populated when it is next shown. | 794 namespace { |
| 786 GtkWidget* menu_item = static_cast<GtkWidget*>( | 795 |
| 787 g_object_get_data(G_OBJECT(submenu), "menu-item")); | 796 // Remove all descendant submenu-model data pointers. |
| 788 submenu = gtk_menu_new(); | 797 void RemoveSubMenuModels(GtkWidget* menu_item, void* unused) { |
| 789 g_object_set_data(G_OBJECT(submenu), "menu-item", menu_item); | 798 if (!GTK_IS_MENU_ITEM(menu_item)) |
| 790 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu); | 799 return; |
| 791 g_signal_connect(submenu, "show", G_CALLBACK(OnSubMenuShowThunk), this); | 800 g_object_steal_data(G_OBJECT(menu_item), "submenu-model"); |
| 792 g_signal_connect(submenu, "hide", G_CALLBACK(OnSubMenuHiddenThunk), this); | 801 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)); |
| 802 if (submenu) |
| 803 gtk_container_foreach(GTK_CONTAINER(submenu), RemoveSubMenuModels, NULL); |
| 793 } | 804 } |
| 794 | 805 |
| 806 } // namespace |
| 807 |
| 795 // static | 808 // static |
| 796 void MenuGtk::OnSubMenuHiddenCallback(GtkWidget* submenu) { | 809 void MenuGtk::OnSubMenuHiddenCallback(GtkWidget* submenu) { |
| 797 // Notify the submenu model that the menu has been hidden. | 810 if (!gtk_widget_get_visible(submenu)) { |
| 798 GtkWidget* menu_item = static_cast<GtkWidget*>( | 811 // Remove all the children of this menu, clearing out their submenu-model |
| 799 g_object_get_data(G_OBJECT(submenu), "menu-item")); | 812 // pointers in case they have pending calls to OnSubMenuHiddenCallback(). |
| 800 ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>( | 813 // (Normally that won't happen: we'd have hidden them first, and so they'd |
| 801 g_object_get_data(G_OBJECT(menu_item), "submenu-model")); | 814 // have already been deleted. But in some cases [e.g. on Ubuntu 12.04], |
| 802 // TODO(mdm): Figure out why this can sometimes be NULL. See bug 124110. | 815 // GTK menu operations may be hooked to allow external applications to |
| 803 if (submenu_model) | 816 // mirror the menu structure, and the hooks may show and hide menus in |
| 804 submenu_model->MenuClosed(); | 817 // order to trigger exactly the kind of dynamic menu building we're doing. |
| 818 // The result is that we see show and hide events in strange orders.) |
| 819 GList* children = gtk_container_get_children(GTK_CONTAINER(submenu)); |
| 820 for (GList* child = children; child; child = g_list_next(child)) { |
| 821 RemoveSubMenuModels(GTK_WIDGET(child->data), NULL); |
| 822 gtk_container_remove(GTK_CONTAINER(submenu), GTK_WIDGET(child->data)); |
| 823 } |
| 824 g_list_free(children); |
| 825 |
| 826 // Clear out the bit that says the menu is built. |
| 827 // We'll rebuild it next time it is shown. |
| 828 g_object_steal_data(G_OBJECT(submenu), "submenu-built"); |
| 829 |
| 830 // Notify the submenu model that the menu has been hidden. This may cause |
| 831 // it to delete descendant submenu models, which is why we cleared those |
| 832 // pointers out above. |
| 833 GtkWidget* menu_item = static_cast<GtkWidget*>( |
| 834 g_object_get_data(G_OBJECT(submenu), "menu-item")); |
| 835 // TODO(mdm): Figure out why this can sometimes be NULL. See bug 124110. |
| 836 CHECK(menu_item); |
| 837 ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>( |
| 838 g_object_get_data(G_OBJECT(menu_item), "submenu-model")); |
| 839 if (submenu_model) |
| 840 submenu_model->MenuClosed(); |
| 841 } |
| 805 | 842 |
| 806 // Remove the reference we grabbed in OnSubMenuHidden() above. | 843 // Remove the reference we grabbed in OnSubMenuHidden() above. |
| 807 g_object_unref(G_OBJECT(submenu)); | 844 g_object_unref(G_OBJECT(submenu)); |
| 808 } | 845 } |
| 809 | 846 |
| 810 // static | 847 // static |
| 811 void MenuGtk::SetButtonItemInfo(GtkWidget* button, gpointer userdata) { | 848 void MenuGtk::SetButtonItemInfo(GtkWidget* button, gpointer userdata) { |
| 812 ui::ButtonMenuItemModel* model = | 849 ui::ButtonMenuItemModel* model = |
| 813 reinterpret_cast<ui::ButtonMenuItemModel*>( | 850 reinterpret_cast<ui::ButtonMenuItemModel*>( |
| 814 g_object_get_data(G_OBJECT(button), "button-model")); | 851 g_object_get_data(G_OBJECT(button), "button-model")); |
| (...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 896 gtk_widget_hide(widget); | 933 gtk_widget_hide(widget); |
| 897 } | 934 } |
| 898 | 935 |
| 899 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget)); | 936 GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(widget)); |
| 900 if (submenu) { | 937 if (submenu) { |
| 901 gtk_container_foreach(GTK_CONTAINER(submenu), &SetMenuItemInfo, | 938 gtk_container_foreach(GTK_CONTAINER(submenu), &SetMenuItemInfo, |
| 902 userdata); | 939 userdata); |
| 903 } | 940 } |
| 904 } | 941 } |
| 905 } | 942 } |
| OLD | NEW |