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