From cbc0a8447d12aee306d2c2e56a53527384142240 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 13 Jun 2019 00:13:21 +0000 Subject: [PATCH] popover menu: Unify hover and focus Menus traditionally don't have separate hover and focus locations. Make the same change here that we already did for popover menubars: Track the active item and set its selected state. Both keynav and mouse change the active item. --- gtk/gtkmodelbutton.c | 79 ++++++++++++++++++++++++++++++++++++- gtk/gtkpopovermenu.c | 23 +++++++++++ gtk/gtkpopovermenuprivate.h | 30 ++++++++++++++ 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 gtk/gtkpopovermenuprivate.h diff --git a/gtk/gtkmodelbutton.c b/gtk/gtkmodelbutton.c index 57c333536b..80552ec33b 100644 --- a/gtk/gtkmodelbutton.c +++ b/gtk/gtkmodelbutton.c @@ -31,7 +31,7 @@ #include "gtkstylecontext.h" #include "gtktypebuiltins.h" #include "gtkstack.h" -#include "gtkpopover.h" +#include "gtkpopovermenuprivate.h" #include "gtkintl.h" #include "gtkcssnodeprivate.h" #include "gtkcsstypesprivate.h" @@ -41,6 +41,8 @@ #include "gtksizegroup.h" #include "gtkaccellabelprivate.h" #include "gtkactionable.h" +#include "gtkeventcontrollermotion.h" +#include "gtkeventcontrollerkey.h" /** * SECTION:gtkmodelbutton @@ -1127,9 +1129,75 @@ gtk_model_button_class_init (GtkModelButtonClass *class) gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (class), I_("modelbutton")); } +static void +enter_cb (GtkEventController *controller, + double x, + double y, + GdkCrossingMode mode, + GdkNotifyType type, + gpointer data) +{ + GtkWidget *target; + GtkWidget *popover; + gboolean is; + gboolean contains; + + target = gtk_event_controller_get_widget (controller); + popover = gtk_widget_get_ancestor (target, GTK_TYPE_POPOVER_MENU); + + g_object_get (controller, + "is-pointer-focus", &is, + "contains-pointer-focus", &contains, + NULL); + + if (popover && (is || contains)) + gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (popover), target); +} + +static void +leave_cb (GtkEventController *controller, + GdkCrossingMode mode, + GdkNotifyType type, + gpointer data) +{ + GtkWidget *target; + GtkWidget *popover; + gboolean is; + gboolean contains; + + target = gtk_event_controller_get_widget (controller); + popover = gtk_widget_get_ancestor (target, GTK_TYPE_POPOVER_MENU); + + g_object_get (controller, + "is-pointer-focus", &is, + "contains-pointer-focus", &contains, + NULL); + + if (popover && !(is || contains)) + gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (popover), NULL); +} + +static void +focus_in_cb (GtkEventController *controller, + GdkCrossingMode mode, + GdkNotifyType type, + gpointer data) +{ + GtkWidget *target; + GtkWidget *popover; + + target = gtk_event_controller_get_widget (controller); + popover = gtk_widget_get_ancestor (target, GTK_TYPE_POPOVER_MENU); + + if (popover) + gtk_popover_menu_set_active_item (GTK_POPOVER_MENU (popover), target); +} + static void gtk_model_button_init (GtkModelButton *button) { + GtkEventController *controller; + button->role = GTK_BUTTON_ROLE_NORMAL; gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); button->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); @@ -1161,6 +1229,15 @@ gtk_model_button_init (GtkModelButton *button) gtk_widget_hide (button->start_indicator); gtk_widget_hide (button->end_indicator); 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); + gtk_widget_add_controller (GTK_WIDGET (button), controller); + + controller = gtk_event_controller_key_new (); + g_signal_connect (controller, "focus-in", G_CALLBACK (focus_in_cb), NULL); + gtk_widget_add_controller (GTK_WIDGET (button), controller); } /** diff --git a/gtk/gtkpopovermenu.c b/gtk/gtkpopovermenu.c index 93a0ba8953..856b017cb5 100644 --- a/gtk/gtkpopovermenu.c +++ b/gtk/gtkpopovermenu.c @@ -17,6 +17,8 @@ #include "config.h" #include "gtkpopovermenu.h" +#include "gtkpopovermenuprivate.h" + #include "gtkstack.h" #include "gtkstylecontext.h" #include "gtkintl.h" @@ -120,6 +122,8 @@ typedef struct _GtkPopoverMenuClass GtkPopoverMenuClass; struct _GtkPopoverMenu { GtkPopover parent_instance; + + GtkWidget *active_item; }; struct _GtkPopoverMenuClass @@ -133,6 +137,25 @@ enum { G_DEFINE_TYPE (GtkPopoverMenu, gtk_popover_menu, GTK_TYPE_POPOVER) +void +gtk_popover_menu_set_active_item (GtkPopoverMenu *menu, + GtkWidget *item) +{ + if (menu->active_item != item) + { + if (menu->active_item) + gtk_widget_unset_state_flags (menu->active_item, GTK_STATE_FLAG_SELECTED); + + menu->active_item = item; + + if (menu->active_item) + { + gtk_widget_set_state_flags (menu->active_item, GTK_STATE_FLAG_SELECTED, FALSE); + gtk_widget_grab_focus (menu->active_item); + } + } +} + static void visible_submenu_changed (GObject *object, GParamSpec *pspec, diff --git a/gtk/gtkpopovermenuprivate.h b/gtk/gtkpopovermenuprivate.h new file mode 100644 index 0000000000..024d20c91c --- /dev/null +++ b/gtk/gtkpopovermenuprivate.h @@ -0,0 +1,30 @@ +/* GTK - The GIMP Toolkit + * Copyright © 2019 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef __GTK_POPOVER_MENU_PRIVATE_H__ +#define __GTK_POPOVER_MENU_PRIVATE_H__ + +#include "gtkpopovermenu.h" + +G_BEGIN_DECLS + +void gtk_popover_menu_set_active_item (GtkPopoverMenu *popover, + GtkWidget *item); + +G_END_DECLS + +#endif /* __GTK_POPOVER_PRIVATE_H__ */