diff options
author | Matthias Clasen <mclasen@redhat.com> | 2019-08-27 09:28:29 +0100 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2019-09-08 19:02:06 -0400 |
commit | ea44eade21a896c75b02bf8a148ba726f248f965 (patch) | |
tree | 8a5200d378ca87d7beab9aeeb1340bb2befcb317 | |
parent | c75a368bab66180c2ebd2ca9483b419fa64840d9 (diff) | |
download | gtk+-ea44eade21a896c75b02bf8a148ba726f248f965.tar.gz |
Add nesting popover menus
Add a variant of popover menus that are nesting
like traditional menus. This is a better fit for
replacing traditional main menus.
-rw-r--r-- | docs/reference/gtk/gtk4-sections.txt | 2 | ||||
-rw-r--r-- | gtk/gtkmenusectionbox.c | 82 | ||||
-rw-r--r-- | gtk/gtkmenusectionboxprivate.h | 5 | ||||
-rw-r--r-- | gtk/gtkmodelbutton.c | 167 | ||||
-rw-r--r-- | gtk/gtkpopover.c | 16 | ||||
-rw-r--r-- | gtk/gtkpopovermenu.c | 148 | ||||
-rw-r--r-- | gtk/gtkpopovermenu.h | 18 | ||||
-rw-r--r-- | gtk/gtkpopovermenubar.c | 6 | ||||
-rw-r--r-- | gtk/gtkpopovermenuprivate.h | 11 | ||||
-rw-r--r-- | tests/popover2.ui | 38 | ||||
-rw-r--r-- | tests/testpopover.c | 10 |
11 files changed, 407 insertions, 96 deletions
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index b72906d7ab..1b24d886da 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -1787,6 +1787,8 @@ gtk_map_list_model_get_type GtkMenu gtk_menu_new gtk_menu_new_from_model +GtkPopoverMenuFlags +gtk_menu_new_from_model_full gtk_menu_reorder_child gtk_menu_popup_at_rect gtk_menu_popup_at_widget diff --git a/gtk/gtkmenusectionbox.c b/gtk/gtkmenusectionbox.c index 0bb33cd68b..502de3ffc0 100644 --- a/gtk/gtkmenusectionbox.c +++ b/gtk/gtkmenusectionbox.c @@ -38,18 +38,19 @@ typedef GtkBoxClass GtkMenuSectionBoxClass; struct _GtkMenuSectionBox { - GtkBox parent_instance; - - GtkMenuSectionBox *toplevel; - GtkMenuTracker *tracker; - GtkBox *item_box; - GtkWidget *separator; - guint separator_sync_idle; - gboolean iconic; - gboolean inline_buttons; - gboolean circular; - gint depth; - GtkSizeGroup *indicators; + GtkBox parent_instance; + + GtkMenuSectionBox *toplevel; + GtkMenuTracker *tracker; + GtkBox *item_box; + GtkWidget *separator; + guint separator_sync_idle; + gboolean iconic; + gboolean inline_buttons; + gboolean circular; + gint depth; + GtkPopoverMenuFlags flags; + GtkSizeGroup *indicators; }; typedef struct @@ -295,22 +296,43 @@ gtk_menu_section_box_insert_func (GtkMenuTrackerItem *item, } else if (gtk_menu_tracker_item_get_has_link (item, G_MENU_LINK_SUBMENU)) { - GtkWidget *stack = NULL; - GtkWidget *parent = NULL; - gchar *name; + if (box->flags & GTK_POPOVER_MENU_NESTED) + { + GMenuModel *model; + GtkWidget *submenu; - widget = g_object_new (GTK_TYPE_MODEL_BUTTON, - "menu-name", gtk_menu_tracker_item_get_label (item), - "indicator-size-group", box->indicators, - NULL); - g_object_bind_property (item, "label", widget, "text", G_BINDING_SYNC_CREATE); - g_object_bind_property (item, "icon", widget, "icon", G_BINDING_SYNC_CREATE); - g_object_bind_property (item, "sensitive", widget, "sensitive", G_BINDING_SYNC_CREATE); + model = _gtk_menu_tracker_item_get_link (item, G_MENU_LINK_SUBMENU); - get_ancestors (GTK_WIDGET (box->toplevel), GTK_TYPE_STACK, &stack, &parent); - g_object_get (gtk_stack_get_page (GTK_STACK (stack), parent), "name", &name, NULL); - gtk_menu_section_box_new_submenu (item, box->toplevel, widget, name); - g_free (name); + submenu = gtk_popover_menu_new_from_model_full (NULL, model, box->flags); + gtk_popover_set_has_arrow (GTK_POPOVER (submenu), FALSE); + gtk_widget_set_valign (submenu, GTK_ALIGN_START); + + widget = g_object_new (GTK_TYPE_MODEL_BUTTON, + "popover", submenu, + NULL); + g_object_bind_property (item, "label", widget, "text", G_BINDING_SYNC_CREATE); + g_object_bind_property (item, "icon", widget, "icon", G_BINDING_SYNC_CREATE); + g_object_bind_property (item, "sensitive", widget, "sensitive", G_BINDING_SYNC_CREATE); + } + else + { + GtkWidget *stack = NULL; + GtkWidget *parent = NULL; + gchar *name; + + widget = g_object_new (GTK_TYPE_MODEL_BUTTON, + "menu-name", gtk_menu_tracker_item_get_label (item), + "indicator-size-group", box->indicators, + NULL); + g_object_bind_property (item, "label", widget, "text", G_BINDING_SYNC_CREATE); + g_object_bind_property (item, "icon", widget, "icon", G_BINDING_SYNC_CREATE); + g_object_bind_property (item, "sensitive", widget, "sensitive", G_BINDING_SYNC_CREATE); + + get_ancestors (GTK_WIDGET (box->toplevel), GTK_TYPE_STACK, &stack, &parent); + g_object_get (gtk_stack_get_page (GTK_STACK (stack), parent), "name", &name, NULL); + gtk_menu_section_box_new_submenu (item, box->toplevel, widget, name); + g_free (name); + } } else { @@ -453,13 +475,15 @@ update_popover_position_cb (GObject *source, } void -gtk_menu_section_box_new_toplevel (GtkPopoverMenu *popover, - GMenuModel *model) +gtk_menu_section_box_new_toplevel (GtkPopoverMenu *popover, + GMenuModel *model, + GtkPopoverMenuFlags flags) { GtkMenuSectionBox *box; box = g_object_new (GTK_TYPE_MENU_SECTION_BOX, NULL); box->indicators = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + box->flags = flags; gtk_popover_menu_add_submenu (popover, GTK_WIDGET (box), "main"); @@ -482,6 +506,7 @@ gtk_menu_section_box_new_submenu (GtkMenuTrackerItem *item, box = g_object_new (GTK_TYPE_MENU_SECTION_BOX, NULL); box->indicators = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + box->flags = toplevel->flags; button = g_object_new (GTK_TYPE_MODEL_BUTTON, "menu-name", name, @@ -521,6 +546,7 @@ gtk_menu_section_box_new_section (GtkMenuTrackerItem *item, box->indicators = g_object_ref (parent->indicators); box->toplevel = parent->toplevel; box->depth = parent->depth + 1; + box->flags = parent->flags; label = gtk_menu_tracker_item_get_label (item); hint = gtk_menu_tracker_item_get_display_hint (item); diff --git a/gtk/gtkmenusectionboxprivate.h b/gtk/gtkmenusectionboxprivate.h index 01508fe43a..05d6bd294e 100644 --- a/gtk/gtkmenusectionboxprivate.h +++ b/gtk/gtkmenusectionboxprivate.h @@ -41,8 +41,9 @@ G_BEGIN_DECLS typedef struct _GtkMenuSectionBox GtkMenuSectionBox; GType gtk_menu_section_box_get_type (void) G_GNUC_CONST; -void gtk_menu_section_box_new_toplevel (GtkPopoverMenu *popover, - GMenuModel *model); +void gtk_menu_section_box_new_toplevel (GtkPopoverMenu *popover, + GMenuModel *model, + GtkPopoverMenuFlags flags); G_END_DECLS diff --git a/gtk/gtkmodelbutton.c b/gtk/gtkmodelbutton.c index 80552ec33b..98d70e2226 100644 --- a/gtk/gtkmodelbutton.c +++ b/gtk/gtkmodelbutton.c @@ -43,6 +43,7 @@ #include "gtkactionable.h" #include "gtkeventcontrollermotion.h" #include "gtkeventcontrollerkey.h" +#include "gtknative.h" /** * SECTION:gtkmodelbutton @@ -162,6 +163,7 @@ struct _GtkModelButton GtkWidget *start_indicator; GtkWidget *end_box; GtkWidget *end_indicator; + GtkWidget *popover; gboolean active; gboolean centered; gboolean iconic; @@ -184,6 +186,7 @@ enum PROP_USE_MARKUP, PROP_ACTIVE, PROP_MENU_NAME, + PROP_POPOVER, PROP_ICONIC, PROP_ACCEL, PROP_INDICATOR_SIZE_GROUP, @@ -275,7 +278,8 @@ gtk_model_button_update_state (GtkModelButton *button) case GTK_BUTTON_ROLE_NORMAL: start_type = GTK_CSS_IMAGE_BUILTIN_NONE; - if (button->menu_name != NULL) + if (button->menu_name != NULL || + button->popover != NULL) end_type = GTK_CSS_IMAGE_BUILTIN_ARROW_RIGHT; else end_type = GTK_CSS_IMAGE_BUILTIN_NONE; @@ -344,7 +348,7 @@ update_node_name (GtkModelButton *button) case GTK_BUTTON_ROLE_NORMAL: a11y_role = ATK_ROLE_PUSH_BUTTON; start_name = I_("none"); - if (button->menu_name) + if (button->menu_name || button->popover) end_name = I_("arrow"); else end_name = I_("none"); @@ -520,6 +524,28 @@ gtk_model_button_set_iconic (GtkModelButton *button, } static void +gtk_model_button_set_popover (GtkModelButton *button, + GtkWidget *popover) +{ + if (button->popover) + gtk_popover_set_relative_to (GTK_POPOVER (button->popover), NULL); + + button->popover = popover; + + if (button->popover) + { + gtk_popover_set_relative_to (GTK_POPOVER (button->popover), GTK_WIDGET (button)); + gtk_popover_set_position (GTK_POPOVER (button->popover), GTK_POS_RIGHT); + } + + update_node_name (button); + gtk_model_button_update_state (button); + + gtk_widget_queue_resize (GTK_WIDGET (button)); + g_object_notify_by_pspec (G_OBJECT (button), properties[PROP_POPOVER]); +} + +static void update_accel (GtkModelButton *button, const char *accel) { @@ -591,6 +617,10 @@ gtk_model_button_get_property (GObject *object, g_value_set_string (value, button->menu_name); break; + case PROP_POPOVER: + g_value_set_object (value, button->popover); + break; + case PROP_ICONIC: g_value_set_boolean (value, button->iconic); break; @@ -643,6 +673,10 @@ gtk_model_button_set_property (GObject *object, gtk_model_button_set_menu_name (button, g_value_get_string (value)); break; + case PROP_POPOVER: + gtk_model_button_set_popover (button, (GtkWidget *)g_value_get_object (value)); + break; + case PROP_ICONIC: gtk_model_button_set_iconic (button, g_value_get_boolean (value)); break; @@ -783,7 +817,9 @@ gtk_model_button_size_allocate (GtkWidget *widget, int height, int baseline) { - if (GTK_MODEL_BUTTON (widget)->iconic) + GtkModelButton *button = GTK_MODEL_BUTTON (widget); + + if (button->iconic) { GTK_WIDGET_CLASS (gtk_model_button_parent_class)->size_allocate (widget, width, @@ -792,14 +828,12 @@ gtk_model_button_size_allocate (GtkWidget *widget, } else { - GtkModelButton *button; GtkAllocation child_allocation; GtkWidget *child; int start_width, start_height; int end_width, end_height; int min; - button = GTK_MODEL_BUTTON (widget); child = gtk_bin_get_child (GTK_BIN (widget)); gtk_widget_measure (button->start_box, @@ -860,6 +894,9 @@ gtk_model_button_size_allocate (GtkWidget *widget, gtk_widget_size_allocate (child, &child_allocation, baseline); } } + + if (button->popover) + gtk_native_check_resize (GTK_NATIVE (button->popover)); } static void @@ -888,10 +925,18 @@ close_menu (GtkModelButton *button) GtkWidget *popover; popover = gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_POPOVER); - if (popover != NULL) - gtk_popover_popdown (GTK_POPOVER (popover)); + while (popover != NULL) + { + gtk_popover_popdown (GTK_POPOVER (popover)); + if (GTK_IS_POPOVER_MENU (popover)) + popover = gtk_popover_menu_get_parent_menu (GTK_POPOVER_MENU (popover)); + else + popover = NULL; + } } +static void open_submenu (GtkPopover *popover); + static void gtk_model_button_clicked (GtkButton *button) { @@ -901,6 +946,17 @@ gtk_model_button_clicked (GtkButton *button) { switch_menu (model_button); } + else if (model_button->popover != NULL) + { + GtkPopoverMenu *menu; + GtkWidget *submenu; + + menu = (GtkPopoverMenu *)gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_POPOVER_MENU); + submenu = model_button->popover; + gtk_popover_popup (GTK_POPOVER (submenu)); + gtk_popover_menu_set_open_submenu (menu, submenu); + gtk_popover_menu_set_parent_menu (GTK_POPOVER_MENU (submenu), GTK_WIDGET (menu)); + } else if (model_button->role == GTK_BUTTON_ROLE_NORMAL) { close_menu (model_button); @@ -915,6 +971,7 @@ gtk_model_button_finalize (GObject *object) gtk_widget_unparent (button->start_box); gtk_widget_unparent (button->end_box); g_free (button->accel); + g_clear_pointer (&button->popover, gtk_widget_unparent); G_OBJECT_CLASS (gtk_model_button_parent_class)->finalize (object); } @@ -979,6 +1036,20 @@ gtk_model_button_focus (GtkWidget *widget, switch_menu (button); return TRUE; } + else if (direction == GTK_DIR_RIGHT && + button->role == GTK_BUTTON_ROLE_NORMAL && + button->popover != NULL) + { + GtkPopoverMenu *menu; + GtkWidget *submenu; + + menu = GTK_POPOVER_MENU (gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_POPOVER_MENU)); + submenu = button->popover; + gtk_popover_popup (GTK_POPOVER (submenu)); + gtk_popover_menu_set_open_submenu (menu, submenu); + gtk_popover_menu_set_parent_menu (GTK_POPOVER_MENU (submenu), GTK_WIDGET (menu)); + return TRUE; + } } else { @@ -1080,8 +1151,7 @@ gtk_model_button_class_init (GtkModelButtonClass *class) /** * GtkModelButton:menu-name: * - * The name of a submenu to open when the button is activated. - * If this is set, the button should not have an action associated with it. + * The name of a submenu to open when the button is activated. * If this is set, the button should not have an action associated with it. */ properties[PROP_MENU_NAME] = g_param_spec_string ("menu-name", @@ -1090,6 +1160,13 @@ gtk_model_button_class_init (GtkModelButtonClass *class) NULL, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + properties[PROP_POPOVER] = + g_param_spec_object ("popover", + P_("Popover"), + P_("Popover to open"), + GTK_TYPE_POPOVER, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** * GtkModelButton:iconic: * @@ -1130,32 +1207,56 @@ gtk_model_button_class_init (GtkModelButtonClass *class) } static void -enter_cb (GtkEventController *controller, - double x, - double y, - GdkCrossingMode mode, - GdkNotifyType type, - gpointer data) +close_submenus (GtkPopover *popover) { - GtkWidget *target; - GtkWidget *popover; - gboolean is; - gboolean contains; + GtkPopoverMenu *menu; - target = gtk_event_controller_get_widget (controller); - popover = gtk_widget_get_ancestor (target, GTK_TYPE_POPOVER_MENU); + if (GTK_IS_POPOVER_MENU (popover)) + { + GtkWidget *submenu; - g_object_get (controller, - "is-pointer-focus", &is, - "contains-pointer-focus", &contains, - NULL); + menu = GTK_POPOVER_MENU (popover); + submenu = gtk_popover_menu_get_open_submenu (menu); + if (submenu) + { + close_submenus (GTK_POPOVER (submenu)); + gtk_popover_popdown (GTK_POPOVER (submenu)); + gtk_popover_menu_set_open_submenu (menu, NULL); + } + } +} - if (popover && (is || contains)) - gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (popover), target); +static void +open_submenu (GtkPopover *popover) +{ + if (GTK_IS_POPOVER_MENU (popover)) + { + GtkWidget *active_item; + + active_item = gtk_popover_menu_get_active_item (GTK_POPOVER_MENU (popover)); + if (GTK_IS_MODEL_BUTTON (active_item) && + GTK_MODEL_BUTTON (active_item)->popover) + { + GtkWidget *submenu; + + submenu = GTK_MODEL_BUTTON (active_item)->popover; + if (gtk_popover_menu_get_open_submenu (GTK_POPOVER_MENU (popover)) != submenu) +{ +g_print ("close submenus %p %p\n", gtk_popover_menu_get_open_submenu (GTK_POPOVER_MENU (popover)), submenu); + close_submenus (popover); +} + + gtk_popover_popup (GTK_POPOVER (submenu)); + gtk_popover_menu_set_open_submenu (GTK_POPOVER_MENU (popover), submenu); + gtk_popover_menu_set_parent_menu (GTK_POPOVER_MENU (submenu), GTK_WIDGET (popover)); + } + } } static void -leave_cb (GtkEventController *controller, +enter_cb (GtkEventController *controller, + double x, + double y, GdkCrossingMode mode, GdkNotifyType type, gpointer data) @@ -1173,8 +1274,11 @@ leave_cb (GtkEventController *controller, "contains-pointer-focus", &contains, NULL); - if (popover && !(is || contains)) - gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (popover), NULL); + if (popover && (is || contains)) + { + gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (popover), target); + open_submenu (GTK_POPOVER (popover)); + } } static void @@ -1231,8 +1335,7 @@ gtk_model_button_init (GtkModelButton *button) update_node_ordering (button); controller = gtk_event_controller_motion_new (); - g_signal_connect (controller, "enter", G_CALLBACK (enter_cb), NULL); - g_signal_connect (controller, "leave", G_CALLBACK (leave_cb), NULL); + g_signal_connect (controller, "enter", G_CALLBACK (enter_cb), button); gtk_widget_add_controller (GTK_WIDGET (button), controller); controller = gtk_event_controller_key_new (); diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c index 3f487a1172..e7d97c55f5 100644 --- a/gtk/gtkpopover.c +++ b/gtk/gtkpopover.c @@ -107,6 +107,7 @@ #include "config.h" #include "gtkpopoverprivate.h" +#include "gtkpopovermenuprivate.h" #include "gtknative.h" #include "gtkwidgetprivate.h" #include "gtkeventcontrollerkey.h" @@ -398,6 +399,19 @@ gtk_popover_focus_out (GtkWidget *widget) { } +static void +close_menu (GtkPopover *popover) +{ + while (popover) + { + gtk_popover_popdown (popover); + if (GTK_IS_POPOVER_MENU (popover)) + popover = (GtkPopover *)gtk_popover_menu_get_parent_menu (GTK_POPOVER_MENU (popover)); + else + popover = NULL; + } +} + static gboolean gtk_popover_key_pressed (GtkWidget *widget, guint keyval, @@ -406,7 +420,7 @@ gtk_popover_key_pressed (GtkWidget *widget, { if (keyval == GDK_KEY_Escape) { - gtk_popover_popdown (GTK_POPOVER (widget)); + close_menu (GTK_POPOVER (widget)); return TRUE; } diff --git a/gtk/gtkpopovermenu.c b/gtk/gtkpopovermenu.c index 856b017cb5..98b52058bc 100644 --- a/gtk/gtkpopovermenu.c +++ b/gtk/gtkpopovermenu.c @@ -29,9 +29,12 @@ #include "gtkpopoverprivate.h" #include "gtkwidgetprivate.h" #include "gtkeventcontrollerkey.h" +#include "gtkeventcontrollermotion.h" #include "gtkmain.h" #include "gtktypebuiltins.h" #include "gtkbindings.h" +#include "gtkmodelbutton.h" +#include "gtkpopovermenubar.h" /** @@ -124,6 +127,8 @@ struct _GtkPopoverMenu GtkPopover parent_instance; GtkWidget *active_item; + GtkWidget *open_submenu; + GtkWidget *parent_menu; }; struct _GtkPopoverMenuClass @@ -137,6 +142,38 @@ enum { G_DEFINE_TYPE (GtkPopoverMenu, gtk_popover_menu, GTK_TYPE_POPOVER) +GtkWidget * +gtk_popover_menu_get_parent_menu (GtkPopoverMenu *menu) +{ + return menu->parent_menu; +} + +void +gtk_popover_menu_set_parent_menu (GtkPopoverMenu *menu, + GtkWidget *parent) +{ + menu->parent_menu = parent; +} + +GtkWidget * +gtk_popover_menu_get_open_submenu (GtkPopoverMenu *menu) +{ + return menu->open_submenu; +} + +void +gtk_popover_menu_set_open_submenu (GtkPopoverMenu *menu, + GtkWidget *submenu) +{ + menu->open_submenu = submenu; +} + +GtkWidget * +gtk_popover_menu_get_active_item (GtkPopoverMenu *menu) +{ + return menu->active_item; +} + void gtk_popover_menu_set_active_item (GtkPopoverMenu *menu, GtkWidget *item) @@ -150,8 +187,16 @@ gtk_popover_menu_set_active_item (GtkPopoverMenu *menu, if (menu->active_item) { + GtkWidget *popover; + gtk_widget_set_state_flags (menu->active_item, GTK_STATE_FLAG_SELECTED, FALSE); - gtk_widget_grab_focus (menu->active_item); + if (GTK_IS_MODEL_BUTTON (item)) + g_object_get (item, "popover", &popover, NULL); + + if (!popover || popover != menu->open_submenu) + gtk_widget_grab_focus (menu->active_item); + + g_clear_object (&popover); } } } @@ -168,14 +213,40 @@ static void focus_out (GtkEventController *controller, GdkCrossingMode mode, GdkNotifyType detail, - GtkPopover *popover) + GtkPopoverMenu *menu) { gboolean contains_focus; g_object_get (controller, "contains-focus", &contains_focus, NULL); if (!contains_focus) - gtk_popover_popdown (popover); + { + if (menu->parent_menu && + GTK_POPOVER_MENU (menu->parent_menu)->open_submenu == (GtkWidget*)menu) + GTK_POPOVER_MENU (menu->parent_menu)->open_submenu = NULL; + gtk_popover_popdown (GTK_POPOVER (menu)); + } +} + +static void +leave_cb (GtkEventController *controller, + GdkCrossingMode mode, + GdkNotifyType type, + gpointer data) +{ + GtkWidget *target; + gboolean is; + gboolean contains; + + target = gtk_event_controller_get_widget (controller); + + g_object_get (controller, + "is-pointer-focus", &is, + "contains-pointer-focus", &contains, + NULL); + + if (!(is || contains)) + gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (target), NULL); } static void @@ -199,6 +270,11 @@ gtk_popover_menu_init (GtkPopoverMenu *popover) controller = gtk_event_controller_key_new (); g_signal_connect (controller, "focus-out", G_CALLBACK (focus_out), popover); gtk_widget_add_controller (GTK_WIDGET (popover), controller); + + controller = gtk_event_controller_motion_new (); + g_signal_connect (controller, "leave", G_CALLBACK (leave_cb), popover); + gtk_widget_add_controller (GTK_WIDGET (popover), controller); + } static void @@ -310,10 +386,35 @@ gtk_popover_menu_focus (GtkWidget *widget, } else { + if (GTK_POPOVER_MENU (widget)->open_submenu) + { + if (gtk_widget_child_focus (GTK_POPOVER_MENU (widget)->open_submenu, direction)) + return TRUE; + if (direction == GTK_DIR_LEFT) + { + gtk_widget_grab_focus (GTK_POPOVER_MENU (widget)->active_item); + return TRUE; + } + return FALSE; + } + if (gtk_widget_focus_move (widget, direction)) return TRUE; - if (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN) + if (direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT) + { + /* If we are part of a menubar, we want to let the + * menubar use left/right arrows for cycling, else + * we eat them. + */ + if (gtk_widget_get_ancestor (widget, GTK_TYPE_POPOVER_MENU_BAR) || + (gtk_popover_menu_get_parent_menu (GTK_POPOVER_MENU (widget)) && + direction == GTK_DIR_LEFT)) + return FALSE; + else + return TRUE; + } + else if (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN) { GtkWidget *p; @@ -503,11 +604,47 @@ gtk_popover_menu_add_submenu (GtkPopoverMenu *popover, * Actions can also be added using gtk_widget_insert_action_group() * on the menus attach widget or on any of its parent widgets. * + * This function creates menus with sliding submenus. + * See gtk_popover_menu_new_from_model_full() for a way + * to control this. + * * Returns: the new #GtkPopoverMenu */ GtkWidget * gtk_popover_menu_new_from_model (GtkWidget *relative_to, GMenuModel *model) + +{ + return gtk_popover_menu_new_from_model_full (relative_to, model, 0); +} + +/** + * gtk_popover_menu_new_from_model_full: + * @relative_to: (allow-none): #GtkWidget the popover is related to + * @model: a #GMenuModel + * @flags: flags that affect how the menu is created + * + * Creates a #GtkPopoverMenu and populates it according to + * @model. The popover is pointed to the @relative_to widget. + * + * The created buttons are connected to actions found in the + * #GtkApplicationWindow to which the popover belongs - typically + * by means of being attached to a widget that is contained within + * the #GtkApplicationWindows widget hierarchy. + * + * Actions can also be added using gtk_widget_insert_action_group() + * on the menus attach widget or on any of its parent widgets. + * + * The only flag that is supported currently is + * #GTK_POPOVER_MENU_NESTED, which makes GTK create traditional, + * nested submenus instead of the default sliding submenus. + * + * Returns: the new #GtkPopoverMenu + */ +GtkWidget * +gtk_popover_menu_new_from_model_full (GtkWidget *relative_to, + GMenuModel *model, + GtkPopoverMenuFlags flags) { GtkWidget *popover; @@ -515,7 +652,8 @@ gtk_popover_menu_new_from_model (GtkWidget *relative_to, g_return_val_if_fail (G_IS_MENU_MODEL (model), NULL); popover = gtk_popover_menu_new (relative_to); - gtk_menu_section_box_new_toplevel (GTK_POPOVER_MENU (popover), model); + gtk_menu_section_box_new_toplevel (GTK_POPOVER_MENU (popover), model, flags); return popover; } + diff --git a/gtk/gtkpopovermenu.h b/gtk/gtkpopovermenu.h index 143a190b70..e2c1d3b3ef 100644 --- a/gtk/gtkpopovermenu.h +++ b/gtk/gtkpopovermenu.h @@ -42,6 +42,24 @@ GDK_AVAILABLE_IN_ALL GtkWidget * gtk_popover_menu_new_from_model (GtkWidget *relative_to, GMenuModel *model); +/** + * GtkPopoverMenuFlags: + * @GTK_POPOVER_MENU_NESTED: Create submenus as nested + * popovers. Without this flag, submenus are created as + * sliding pages that replace the main menu. + * + * Flags that affect how popover menus are created from + * a menu model. + */ +typedef enum { + GTK_POPOVER_MENU_NESTED = 1 << 0 +} GtkPopoverMenuFlags; + +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_popover_menu_new_from_model_full (GtkWidget *relative_to, + GMenuModel *model, + GtkPopoverMenuFlags flags); + GDK_AVAILABLE_IN_ALL void gtk_popover_menu_add_submenu (GtkPopoverMenu *popover, GtkWidget *submenu, diff --git a/gtk/gtkpopovermenubar.c b/gtk/gtkpopovermenubar.c index 7716397dfe..956f63eced 100644 --- a/gtk/gtkpopovermenubar.c +++ b/gtk/gtkpopovermenubar.c @@ -314,7 +314,11 @@ gtk_popover_menu_bar_item_size_allocate (GtkWidget *widget, static void gtk_popover_menu_bar_item_activate (GtkPopoverMenuBarItem *item) { - gtk_popover_popup (GTK_POPOVER (item->popover)); + GtkPopoverMenuBar *bar; + + bar = GTK_POPOVER_MENU_BAR (gtk_widget_get_ancestor (GTK_WIDGET (item), GTK_TYPE_POPOVER_MENU_BAR)); + + set_active_item (bar, item, TRUE); } static void diff --git a/gtk/gtkpopovermenuprivate.h b/gtk/gtkpopovermenuprivate.h index 024d20c91c..898dc60f30 100644 --- a/gtk/gtkpopovermenuprivate.h +++ b/gtk/gtkpopovermenuprivate.h @@ -22,8 +22,15 @@ G_BEGIN_DECLS -void gtk_popover_menu_set_active_item (GtkPopoverMenu *popover, - GtkWidget *item); +GtkWidget *gtk_popover_menu_get_active_item (GtkPopoverMenu *menu); +void gtk_popover_menu_set_active_item (GtkPopoverMenu *menu, + GtkWidget *item); +GtkWidget *gtk_popover_menu_get_open_submenu (GtkPopoverMenu *menu); +void gtk_popover_menu_set_open_submenu (GtkPopoverMenu *menu, + GtkWidget *submenu); +GtkWidget *gtk_popover_menu_get_parent_menu (GtkPopoverMenu *menu); +void gtk_popover_menu_set_parent_menu (GtkPopoverMenu *menu, + GtkWidget *parent); G_END_DECLS diff --git a/tests/popover2.ui b/tests/popover2.ui index d934e6f45a..49cd3f72e2 100644 --- a/tests/popover2.ui +++ b/tests/popover2.ui @@ -1,5 +1,6 @@ <interface> <object class="GtkPopoverMenu" id="popover"> + <property name="autohide">True</property> <child> <object class="GtkBox"> <property name="orientation">vertical</property> @@ -105,14 +106,14 @@ </child> <child> <object class="GtkModelButton"> - <property name="text">Submenu 1</property> - <property name="menu-name">submenu1</property> + <property name="label">Submenu 1</property> + <property name="popover">submenu1</property> </object> </child> <child> <object class="GtkModelButton"> - <property name="text">Submenu 2</property> - <property name="menu-name">submenu2</property> + <property name="label">Submenu 2</property> + <property name="popover">submenu2</property> </object> </child> <child> @@ -151,18 +152,14 @@ </child> </object> </child> + </object> + <object class="GtkPopoverMenu" id="submenu1"> <child> <object class="GtkBox"> <property name="name">submenu1</property> <property name="orientation">vertical</property> <property name="margin">10</property> <child> - <object class="GtkModelButton"> - <property name="text">Submenu 1</property> - <property name="menu-name">main</property> - </object> - </child> - <child> <object class="GtkBox"> <property name="orientation">vertical</property> <property name="margin-start">12</property> @@ -352,6 +349,8 @@ </child> </object> </child> + </object> + <object class="GtkPopoverMenu" id="submenu2"> <child> <object class="GtkBox"> <property name="name">submenu2</property> @@ -359,12 +358,6 @@ <property name="margin">10</property> <child> <object class="GtkModelButton"> - <property name="text">Submenu 2</property> - <property name="menu-name">main</property> - </object> - </child> - <child> - <object class="GtkModelButton"> <property name="action-name">top.action7</property> <property name="text">Item 7</property> </object> @@ -377,13 +370,14 @@ </child> <child> <object class="GtkModelButton"> - <property name="text">Subsubmenu</property> - <property name="icon">icon9</property> - <property name="menu-name">subsubmenu</property> + <property name="label">Subsubmenu</property> + <property name="popover">subsubmenu</property> </object> </child> </object> </child> + </object> + <object class="GtkPopoverMenu" id="subsubmenu"> <child> <object class="GtkBox"> <property name="name">subsubmenu</property> @@ -391,12 +385,6 @@ <property name="margin">10</property> <child> <object class="GtkModelButton"> - <property name="text">Subsubmenu</property> - <property name="menu-name">submenu2</property> - </object> - </child> - <child> - <object class="GtkModelButton"> <property name="action-name">action8</property> <property name="text">Item 8</property> </object> diff --git a/tests/testpopover.c b/tests/testpopover.c index 3ece11139b..ce23dc0636 100644 --- a/tests/testpopover.c +++ b/tests/testpopover.c @@ -37,6 +37,7 @@ main (int argc, char *argv[]) GtkWidget *win; GtkWidget *box; GtkWidget *button; + GtkWidget *button1; GtkWidget *button2; GtkBuilder *builder; GMenuModel *model; @@ -44,6 +45,7 @@ main (int argc, char *argv[]) GtkWidget *overlay; GtkWidget *grid; GtkWidget *popover; + GtkWidget *popover1; GtkWidget *popover2; GtkWidget *label; GtkWidget *check; @@ -93,6 +95,8 @@ main (int argc, char *argv[]) box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); button = gtk_menu_button_new (); gtk_container_add (GTK_CONTAINER (box), button); + button1 = gtk_menu_button_new (); + gtk_container_add (GTK_CONTAINER (box), button1); button2 = gtk_menu_button_new (); gtk_container_add (GTK_CONTAINER (box), button2); @@ -100,6 +104,9 @@ main (int argc, char *argv[]) gtk_menu_button_set_use_popover (GTK_MENU_BUTTON (button), TRUE); popover = GTK_WIDGET (gtk_menu_button_get_popover (GTK_MENU_BUTTON (button))); + popover1 = gtk_popover_menu_new_from_model_full (NULL, model, GTK_POPOVER_MENU_NESTED); + gtk_menu_button_set_popover (GTK_MENU_BUTTON (button1), popover1); + builder = gtk_builder_new_from_file ("popover2.ui"); popover2 = (GtkWidget *)gtk_builder_get_object (builder, "popover"); gtk_menu_button_set_popover (GTK_MENU_BUTTON (button2), popover2); @@ -110,6 +117,7 @@ main (int argc, char *argv[]) label = gtk_label_new ("Popover hexpand"); check = gtk_check_button_new (); g_object_bind_property (check, "active", popover, "hexpand", G_BINDING_SYNC_CREATE); + g_object_bind_property (check, "active", popover1, "hexpand", G_BINDING_SYNC_CREATE); g_object_bind_property (check, "active", popover2, "hexpand", G_BINDING_SYNC_CREATE); gtk_grid_attach (GTK_GRID (grid), label , 1, 1, 1, 1); gtk_grid_attach (GTK_GRID (grid), check, 2, 1, 1, 1); @@ -117,6 +125,7 @@ main (int argc, char *argv[]) label = gtk_label_new ("Popover vexpand"); check = gtk_check_button_new (); g_object_bind_property (check, "active", popover, "vexpand", G_BINDING_SYNC_CREATE); + g_object_bind_property (check, "active", popover1, "vexpand", G_BINDING_SYNC_CREATE); g_object_bind_property (check, "active", popover2, "vexpand", G_BINDING_SYNC_CREATE); gtk_grid_attach (GTK_GRID (grid), label , 1, 2, 1, 1); gtk_grid_attach (GTK_GRID (grid), check, 2, 2, 1, 1); @@ -129,6 +138,7 @@ main (int argc, char *argv[]) gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "right", "Right"); gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 1); g_object_bind_property (combo, "active", button, "direction", G_BINDING_SYNC_CREATE); + g_object_bind_property (combo, "active", button1, "direction", G_BINDING_SYNC_CREATE); g_object_bind_property (combo, "active", button2, "direction", G_BINDING_SYNC_CREATE); gtk_grid_attach (GTK_GRID (grid), label , 1, 3, 1, 1); gtk_grid_attach (GTK_GRID (grid), combo, 2, 3, 1, 1); |