diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 911135eb3986853c14527ddf8cf2b74fae83e560..8bd4732e04b0b32884eeaa3596149f1d66e2611a 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -904,7 +904,6 @@ gtk_text_set_attributes gtk_text_get_attributes gtk_text_set_tabs gtk_text_get_tabs -gtk_text_grab_focus_without_selecting gtk_text_get_type @@ -6700,6 +6699,12 @@ gtk_event_controller_motion_get_type GtkEventControllerKey GtkEventControllerKey gtk_event_controller_key_new +gtk_event_controller_key_set_im_context +gtk_event_controller_key_get_im_context +gtk_event_controller_key_forward +gtk_event_controller_key_get_group +gtk_event_controller_key_get_focus_origin +gtk_event_controller_key_get_focus_target GTK_TYPE_EVENT_CONTROLLER_KEY diff --git a/gdk/gdk-private.h b/gdk/gdk-private.h index 9344ac2e497755f323f54e3989b9a8df85aa12e8..b5551b750cbfef0a835b0bb815b5b42b1c0e5112 100644 --- a/gdk/gdk-private.h +++ b/gdk/gdk-private.h @@ -20,8 +20,6 @@ void gdk_surface_thaw_toplevel_updates (GdkSurface *surface); gboolean gdk_surface_supports_edge_constraints (GdkSurface *surface); -GObject * gdk_event_get_user_data (const GdkEvent *event); - guint32 gdk_display_get_last_seen_time (GdkDisplay *display); void gdk_display_set_double_click_time (GdkDisplay *display, diff --git a/gdk/gdkevents.c b/gdk/gdkevents.c index 3b0944d3cc4c80c5f888717410f221973409127e..d9e9af6eeff5b825abf5a54b934d785180ef2413 100644 --- a/gdk/gdkevents.c +++ b/gdk/gdkevents.c @@ -564,8 +564,8 @@ gdk_event_copy (const GdkEvent *event) g_object_ref (new_event->any.device); if (new_event->any.source_device) g_object_ref (new_event->any.source_device); - if (new_event->any.user_data) - g_object_ref (new_event->any.user_data); + if (new_event->any.target) + g_object_ref (new_event->any.target); switch ((guint) event->any.type) { @@ -573,6 +573,13 @@ gdk_event_copy (const GdkEvent *event) case GDK_LEAVE_NOTIFY: if (event->crossing.child_surface != NULL) g_object_ref (event->crossing.child_surface); + if (event->crossing.related_target) + g_object_ref (event->crossing.related_target); + break; + + case GDK_FOCUS_CHANGE: + if (event->focus_change.related_target) + g_object_ref (event->focus_change.related_target); break; case GDK_DRAG_ENTER: @@ -634,6 +641,11 @@ gdk_event_finalize (GObject *object) case GDK_ENTER_NOTIFY: case GDK_LEAVE_NOTIFY: g_clear_object (&event->crossing.child_surface); + g_clear_object (&event->crossing.related_target); + break; + + case GDK_FOCUS_CHANGE: + g_clear_object (&event->focus_change.related_target); break; case GDK_DRAG_ENTER: @@ -675,7 +687,7 @@ gdk_event_finalize (GObject *object) g_clear_object (&event->any.device); g_clear_object (&event->any.source_device); - g_clear_object (&event->any.user_data); + g_clear_object (&event->any.target); G_OBJECT_CLASS (gdk_event_parent_class)->finalize (object); } @@ -1904,16 +1916,39 @@ gdk_event_get_scancode (GdkEvent *event) } void -gdk_event_set_user_data (GdkEvent *event, - GObject *user_data) +gdk_event_set_target (GdkEvent *event, + GObject *target) +{ + g_set_object (&event->any.target, target); +} + +GObject * +gdk_event_get_target (const GdkEvent *event) +{ + return event->any.target; +} + +void +gdk_event_set_related_target (GdkEvent *event, + GObject *target) { - g_set_object (&event->any.user_data, user_data); + if (event->any.type == GDK_ENTER_NOTIFY || + event->any.type == GDK_LEAVE_NOTIFY) + g_set_object (&event->crossing.related_target, target); + else if (event->any.type == GDK_FOCUS_CHANGE) + g_set_object (&event->focus_change.related_target, target); } GObject * -gdk_event_get_user_data (const GdkEvent *event) +gdk_event_get_related_target (const GdkEvent *event) { - return event->any.user_data; + if (event->any.type == GDK_ENTER_NOTIFY || + event->any.type == GDK_LEAVE_NOTIFY) + return event->crossing.related_target; + else if (event->any.type == GDK_FOCUS_CHANGE) + return event->focus_change.related_target; + + return NULL; } /** @@ -1980,6 +2015,11 @@ gdk_event_get_crossing_mode (const GdkEvent *event, *mode = event->crossing.mode; return TRUE; } + else if (event->any.type == GDK_FOCUS_CHANGE) + { + *mode = event->focus_change.mode; + return TRUE; + } return FALSE; } @@ -2006,6 +2046,11 @@ gdk_event_get_crossing_detail (const GdkEvent *event, *detail = event->crossing.detail; return TRUE; } + else if (event->any.type == GDK_FOCUS_CHANGE) + { + *detail = event->focus_change.detail; + return TRUE; + } return FALSE; } diff --git a/gdk/gdkeventsprivate.h b/gdk/gdkeventsprivate.h index b81faba5895b416c692489d55b750aa1201311b2..2db43d9a828d44dc17053682b71464fbd330f95a 100644 --- a/gdk/gdkeventsprivate.h +++ b/gdk/gdkeventsprivate.h @@ -61,7 +61,7 @@ struct _GdkEventAny GdkDevice *device; GdkDevice *source_device; GdkDisplay *display; - GObject *user_data; + GObject *target; }; /* @@ -303,6 +303,7 @@ struct _GdkEventCrossing GdkNotifyType detail; gboolean focus; guint state; + GObject *related_target; }; /* @@ -312,6 +313,8 @@ struct _GdkEventCrossing * @send_event: %TRUE if the event was sent explicitly. * @in: %TRUE if the surface has gained the keyboard focus, %FALSE if * it has lost the focus. + * @mode: the crossing mode + * @detail: the kind of crossing that happened * * Describes a change of keyboard focus. */ @@ -319,6 +322,9 @@ struct _GdkEventFocus { GdkEventAny any; gint16 in; + GdkCrossingMode mode; + GdkNotifyType detail; + GObject *related_target; }; /* @@ -632,8 +638,12 @@ union _GdkEvent GdkEventPadGroupMode pad_group_mode; }; -void gdk_event_set_user_data (GdkEvent *event, - GObject *user_data); +void gdk_event_set_target (GdkEvent *event, + GObject *user_data); +GObject * gdk_event_get_target (const GdkEvent *event); +void gdk_event_set_related_target (GdkEvent *event, + GObject *user_data); +GObject * gdk_event_get_related_target (const GdkEvent *event); #endif /* __GDK_EVENTS_PRIVATE_H__ */ diff --git a/gtk/gtkbox.c b/gtk/gtkbox.c index 307276c63f4c3567f1369ce2b87d397b3c83a115..851b3efde538856f980edfe845453316b84e2c14 100644 --- a/gtk/gtkbox.c +++ b/gtk/gtkbox.c @@ -123,6 +123,9 @@ static void gtk_box_measure (GtkWidget *widget, int *natural, int *minimum_baseline, int *natural_baseline); +GtkWidget * gtk_box_next_focus_child (GtkWidget *widget, + GtkWidget *child, + GtkDirectionType direction); G_DEFINE_TYPE_WITH_CODE (GtkBox, gtk_box, GTK_TYPE_CONTAINER, G_ADD_PRIVATE (GtkBox) @@ -140,6 +143,7 @@ gtk_box_class_init (GtkBoxClass *class) widget_class->size_allocate = gtk_box_size_allocate; widget_class->measure = gtk_box_measure; + widget_class->next_focus_child = gtk_box_next_focus_child; container_class->add = gtk_box_add; container_class->remove = gtk_box_remove; @@ -1149,3 +1153,26 @@ gtk_box_reorder_child_after (GtkBox *box, gtk_widget_get_css_node (child), sibling ? gtk_widget_get_css_node (sibling) : NULL); } + +GtkWidget * +gtk_box_next_focus_child (GtkWidget *widget, + GtkWidget *child, + GtkDirectionType direction) +{ + if (direction == GTK_DIR_TAB_FORWARD) + { + if (child) + return gtk_widget_get_next_sibling (child); + else + return gtk_widget_get_first_child (widget); + } + else if (direction == GTK_DIR_TAB_BACKWARD) + { + if (child) + return gtk_widget_get_prev_sibling (child); + else + return gtk_widget_get_last_child (widget); + } + + return GTK_WIDGET_CLASS (gtk_box_parent_class)->next_focus_child (widget, child, direction); +} diff --git a/gtk/gtkcalendar.c b/gtk/gtkcalendar.c index 6b941ced3e2827367085e5f135c7f459f697c0d2..32df686852f350da4b64f49f96c66b535cdda591 100644 --- a/gtk/gtkcalendar.c +++ b/gtk/gtkcalendar.c @@ -295,6 +295,8 @@ static gboolean gtk_calendar_key_controller_key_pressed (GtkEventControllerKey * GdkModifierType state, GtkWidget *widget); static void gtk_calendar_key_controller_focus (GtkEventControllerKey *controller, + GdkCrossingMode mode, + GdkNotifyType detail, GtkWidget *widget); static void gtk_calendar_grab_notify (GtkWidget *widget, gboolean was_grabbed); @@ -2854,6 +2856,8 @@ gtk_calendar_key_controller_key_pressed (GtkEventControllerKey *controller, static void gtk_calendar_key_controller_focus (GtkEventControllerKey *key, + GdkCrossingMode mode, + GdkNotifyType detail, GtkWidget *widget) { GtkCalendar *calendar = GTK_CALENDAR (widget); diff --git a/gtk/gtkcolorchooserwidget.c b/gtk/gtkcolorchooserwidget.c index 9f2d286017d916f02461c99594ec164a5320634d..3d2ce5a71d422c62a6352f9f4ea6be758d7f89e0 100644 --- a/gtk/gtkcolorchooserwidget.c +++ b/gtk/gtkcolorchooserwidget.c @@ -129,6 +129,22 @@ swatch_activate (GtkColorSwatch *swatch, _gtk_color_chooser_color_activated (GTK_COLOR_CHOOSER (cc), &color); } +static void +show_editor (GtkColorChooserWidget *cc, + gboolean show) +{ + gtk_widget_set_visible (cc->priv->palette, !show); + gtk_widget_set_visible (cc->priv->editor, show); + gtk_widget_set_child_focusable (cc->priv->palette, !show); + gtk_widget_set_child_focusable (cc->priv->editor, show); + if (show) + gtk_widget_child_focus (cc->priv->editor, GTK_DIR_TAB_FORWARD); + else + gtk_widget_child_focus (cc->priv->palette, GTK_DIR_TAB_FORWARD); + + g_object_notify (G_OBJECT (cc), "show-editor"); +} + static void swatch_customize (GtkColorSwatch *swatch, GtkColorChooserWidget *cc) @@ -138,9 +154,7 @@ swatch_customize (GtkColorSwatch *swatch, gtk_color_swatch_get_rgba (swatch, &color); gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (cc->priv->editor), &color); - gtk_widget_hide (cc->priv->palette); - gtk_widget_show (cc->priv->editor); - g_object_notify (G_OBJECT (cc), "show-editor"); + show_editor (cc, TRUE); } static void @@ -174,9 +188,7 @@ button_activate (GtkColorSwatch *swatch, gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (cc->priv->editor), &color); - gtk_widget_hide (cc->priv->palette); - gtk_widget_show (cc->priv->editor); - g_object_notify (G_OBJECT (cc), "show-editor"); + show_editor (cc, TRUE); } static void @@ -260,9 +272,9 @@ gtk_color_chooser_widget_set_use_alpha (GtkColorChooserWidget *cc, static void gtk_color_chooser_widget_set_show_editor (GtkColorChooserWidget *cc, - gboolean show_editor) + gboolean show) { - if (show_editor) + if (show) { GdkRGBA color = { 0.75, 0.25, 0.25, 1.0 }; @@ -271,8 +283,7 @@ gtk_color_chooser_widget_set_show_editor (GtkColorChooserWidget *cc, gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (cc->priv->editor), &color); } - gtk_widget_set_visible (cc->priv->editor, show_editor); - gtk_widget_set_visible (cc->priv->palette, !show_editor); + show_editor (cc, show); } static void @@ -592,6 +603,7 @@ gtk_color_chooser_widget_init (GtkColorChooserWidget *cc) gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (cc), &color); gtk_widget_hide (GTK_WIDGET (cc->priv->editor)); + gtk_widget_set_child_focusable (GTK_WIDGET (cc->priv->editor), FALSE); cc->priv->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); gtk_size_group_add_widget (cc->priv->size_group, cc->priv->palette); diff --git a/gtk/gtkcoloreditor.c b/gtk/gtkcoloreditor.c index 34f5991ca2bf9fa8a0330d71acc7abd76fa69951..8670027631ab0fe6509e57f2ad3446e0fd138f5d 100644 --- a/gtk/gtkcoloreditor.c +++ b/gtk/gtkcoloreditor.c @@ -36,6 +36,7 @@ #include "gtkspinbutton.h" #include "gtkstylecontext.h" #include "gtkeventcontrollerkey.h" +#include "gtkroot.h" #include @@ -224,7 +225,7 @@ popup_edit (GtkWidget *widget, { dismiss_current_popup (editor); toplevel = gtk_widget_get_toplevel (GTK_WIDGET (editor)); - g_set_object (&editor->priv->popdown_focus, gtk_window_get_focus (GTK_WINDOW (toplevel))); + g_set_object (&editor->priv->popdown_focus, gtk_root_get_focus (GTK_ROOT (toplevel))); editor->priv->current_popup = popup; editor->priv->popup_position = position; gtk_widget_show (popup); diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c index ff52184a414fa1e1f6463a3233d9c0ae2968e999..a57fb5e50f3477febfbedbdf023bf74b14008104 100644 --- a/gtk/gtkentry.c +++ b/gtk/gtkentry.c @@ -1652,10 +1652,23 @@ gtk_entry_snapshot (GtkWidget *widget, void gtk_entry_grab_focus_without_selecting (GtkEntry *entry) { - g_return_if_fail (GTK_IS_ENTRY (entry)); GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); + gboolean select, set; + + g_return_if_fail (GTK_IS_ENTRY (entry)); + + g_object_get (priv->text, + "select-on-focus", &select, + "select-on-focus-set", &set, + NULL); + g_object_set (priv->text, "select-on-focus", FALSE, NULL); + + gtk_widget_grab_focus (priv->text); - gtk_text_grab_focus_without_selecting (GTK_TEXT (priv->text)); + g_object_set (priv->text, + "select-on-focus", select, + "select-on-focus-set", set, + NULL); } static void diff --git a/gtk/gtkeventcontrollerkey.c b/gtk/gtkeventcontrollerkey.c index f1b28d999b3ff28842a7bd94dd767394f82f568a..f7f3fe1017e03de187b8df3bb87bc9a451044ddc 100644 --- a/gtk/gtkeventcontrollerkey.c +++ b/gtk/gtkeventcontrollerkey.c @@ -36,6 +36,7 @@ #include "gtkeventcontrollerkey.h" #include "gtkbindings.h" #include "gtkenums.h" +#include "gtkmain.h" #include @@ -46,6 +47,9 @@ struct _GtkEventControllerKey GHashTable *pressed_keys; const GdkEvent *current_event; + + guint is_focus : 1; + guint contains_focus : 1; }; struct _GtkEventControllerKeyClass @@ -65,11 +69,19 @@ enum { static guint signals[N_SIGNALS] = { 0 }; +enum { + PROP_IS_FOCUS = 1, + PROP_CONTAINS_FOCUS, + NUM_PROPERTIES +}; + +static GParamSpec *props[NUM_PROPERTIES] = { NULL, }; + G_DEFINE_TYPE (GtkEventControllerKey, gtk_event_controller_key, GTK_TYPE_EVENT_CONTROLLER) static void -gtk_event_controller_finalize (GObject *object) +gtk_event_controller_key_finalize (GObject *object) { GtkEventControllerKey *key = GTK_EVENT_CONTROLLER_KEY (object); @@ -79,6 +91,50 @@ gtk_event_controller_finalize (GObject *object) G_OBJECT_CLASS (gtk_event_controller_key_parent_class)->finalize (object); } +static void +update_focus (GtkEventControllerKey *key, + gboolean focus_in, + GdkNotifyType detail) +{ + gboolean is_focus; + gboolean contains_focus; + + switch (detail) + { + case GDK_NOTIFY_VIRTUAL: + case GDK_NOTIFY_NONLINEAR_VIRTUAL: + is_focus = FALSE; + contains_focus = focus_in; + break; + case GDK_NOTIFY_ANCESTOR: + case GDK_NOTIFY_NONLINEAR: + is_focus = focus_in; + contains_focus = FALSE; + break; + case GDK_NOTIFY_INFERIOR: + is_focus = focus_in; + contains_focus = !focus_in; + break; + case GDK_NOTIFY_UNKNOWN: + default: + g_warning ("Unknown focus change detail"); + return; + } + + g_object_freeze_notify (G_OBJECT (key)); + if (key->is_focus != is_focus) + { + key->is_focus = is_focus; + g_object_notify (G_OBJECT (key), "is-focus"); + } + if (key->contains_focus != contains_focus) + { + key->contains_focus = contains_focus; + g_object_notify (G_OBJECT (key), "contains-focus"); + } + g_object_thaw_notify (G_OBJECT (key)); +} + static gboolean gtk_event_controller_key_handle_event (GtkEventController *controller, const GdkEvent *event) @@ -93,11 +149,23 @@ gtk_event_controller_key_handle_event (GtkEventController *controller, if (event_type == GDK_FOCUS_CHANGE) { gboolean focus_in; + GdkCrossingMode mode; + GdkNotifyType detail; + + gdk_event_get_focus_in (event, &focus_in); + gdk_event_get_crossing_mode (event, &mode); + gdk_event_get_crossing_detail (event, &detail); + + update_focus (key, focus_in, detail); - if (gdk_event_get_focus_in (event, &focus_in) && focus_in) - g_signal_emit (controller, signals[FOCUS_IN], 0); + key->current_event = event; + + if (focus_in) + g_signal_emit (controller, signals[FOCUS_IN], 0, mode, detail); else - g_signal_emit (controller, signals[FOCUS_OUT], 0); + g_signal_emit (controller, signals[FOCUS_OUT], 0, mode, detail); + + key->current_event = NULL; return FALSE; } @@ -158,15 +226,76 @@ gtk_event_controller_key_handle_event (GtkEventController *controller, return handled; } +static void +gtk_event_controller_key_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkEventControllerKey *controller = GTK_EVENT_CONTROLLER_KEY (object); + + switch (prop_id) + { + case PROP_IS_FOCUS: + g_value_set_boolean (value, controller->is_focus); + break; + + case PROP_CONTAINS_FOCUS: + g_value_set_boolean (value, controller->contains_focus); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + static void gtk_event_controller_key_class_init (GtkEventControllerKeyClass *klass) { GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); - object_class->finalize = gtk_event_controller_finalize; + object_class->finalize = gtk_event_controller_key_finalize; + object_class->get_property = gtk_event_controller_key_get_property; controller_class->handle_event = gtk_event_controller_key_handle_event; + /** + * GtkEventControllerKey:is-focus: + * + * Whether focus is in the controllers widget itself, + * as opposed to in a descendent widget. See + * #GtkEventControllerKey:contains-focus. + * + * When handling focus events, this property is updated + * before #GtkEventControllerKey::focus-in or + * #GtkEventControllerKey::focus-out are emitted. + */ + props[PROP_IS_FOCUS] = + g_param_spec_boolean ("is-focus", + P_("Is Focus"), + P_("Whether the focus is in the controllers widget"), + FALSE, + G_PARAM_READABLE); + + /** + * GtkEventControllerKey:contains-focus: + * + * Whether focus is in a descendant of the controllers widget. + * See #GtkEventControllerKey:is-focus. + * + * When handling focus events, this property is updated + * before #GtkEventControllerKey::focus-in or + * #GtkEventControllerKey::focus-out are emitted. + */ + props[PROP_CONTAINS_FOCUS] = + g_param_spec_boolean ("contains-focus", + P_("Contains Focus"), + P_("Whether the focus is in a descendant of the controllers widget"), + FALSE, + G_PARAM_READABLE); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, props); + /** * GtkEventControllerKey::key-pressed: * @controller: the object which received the signal. @@ -233,38 +362,50 @@ gtk_event_controller_key_class_init (GtkEventControllerKeyClass *klass) GTK_TYPE_EVENT_CONTROLLER_KEY, G_SIGNAL_RUN_LAST, 0, NULL, NULL, - g_cclosure_marshal_VOID__VOID, + NULL, G_TYPE_NONE, 0); /** * GtkEventControllerKey::focus-in: * @controller: the object which received the signal. + * @mode: crossing mode indicating what caused this change + * @detail: detail indication where the focus is coming from * - * This signal is emitted whenever the #GtkEventController:widget controlled - * by the @controller is given the keyboard focus. + * This signal is emitted whenever the widget controlled + * by the @controller or one of its descendants) is given + * the keyboard focus. */ signals[FOCUS_IN] = g_signal_new (I_("focus-in"), GTK_TYPE_EVENT_CONTROLLER_KEY, G_SIGNAL_RUN_LAST, 0, NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); + NULL, + G_TYPE_NONE, + 2, + GDK_TYPE_CROSSING_MODE, + GDK_TYPE_NOTIFY_TYPE); /** * GtkEventControllerKey::focus-out: * @controller: the object which received the signal. + * @mode: crossing mode indicating what caused this change + * @detail: detail indication where the focus is going * - * This signal is emitted whenever the #GtkEventController:widget controlled - * by the @controller loses the keyboard focus. + * This signal is emitted whenever the widget controlled + * by the @controller (or one of its descendants) loses + * the keyboard focus. */ signals[FOCUS_OUT] = g_signal_new (I_("focus-out"), GTK_TYPE_EVENT_CONTROLLER_KEY, G_SIGNAL_RUN_LAST, 0, NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); + NULL, + G_TYPE_NONE, + 2, + GDK_TYPE_CROSSING_MODE, + GDK_TYPE_NOTIFY_TYPE); } static void @@ -283,8 +424,7 @@ gtk_event_controller_key_init (GtkEventControllerKey *controller) GtkEventController * gtk_event_controller_key_new (void) { - return g_object_new (GTK_TYPE_EVENT_CONTROLLER_KEY, - NULL); + return g_object_new (GTK_TYPE_EVENT_CONTROLLER_KEY, NULL); } /** @@ -330,6 +470,13 @@ gtk_event_controller_key_get_im_context (GtkEventControllerKey *controller) * * Forwards the current event of this @controller to a @widget. * + * This function can only be used in handlers for the + * #GtkEventControllerKey::key-pressed, + * #GtkEventControllerKey::key-released + * or + * #GtkEventControllerKey::modifiers + * signals. + * * Returns: whether the @widget handled the event **/ gboolean @@ -339,6 +486,8 @@ gtk_event_controller_key_forward (GtkEventControllerKey *controller, g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_KEY (controller), FALSE); g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); g_return_val_if_fail (controller->current_event != NULL, FALSE); + g_return_val_if_fail (gdk_event_get_event_type (controller->current_event) == GDK_KEY_PRESS || + gdk_event_get_event_type (controller->current_event) == GDK_KEY_RELEASE, FALSE); if (!gtk_widget_get_realized (widget)) gtk_widget_realize (widget); @@ -377,3 +526,64 @@ gtk_event_controller_key_get_group (GtkEventControllerKey *controller) return group; } + +/** + * gtk_event_controller_key_get_focus_origin: + * @controller: a #GtkEventControllerKey + * + * Returns the widget that was holding focus before. + * + * This function can only be used in handlers for the + * #GtkEventControllerKey::focus-in and + * #GtkEventControllerKey::focus-out signals. + * + * Returns: (transfer none): the previous focus + */ +GtkWidget * +gtk_event_controller_key_get_focus_origin (GtkEventControllerKey *controller) +{ + gboolean focus_in; + GtkWidget *origin; + + g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_KEY (controller), NULL); + g_return_val_if_fail (controller->current_event != NULL, NULL); + g_return_val_if_fail (gdk_event_get_event_type (controller->current_event) == GDK_FOCUS_CHANGE, NULL); + + gdk_event_get_focus_in (controller->current_event, &focus_in); + + if (focus_in) + origin = (GtkWidget *)gdk_event_get_related_target (controller->current_event); + else + origin = (GtkWidget *)gdk_event_get_target (controller->current_event); + + return origin; +} + +/** + * gtk_event_controller_key_get_focus_target: + * @controller: a #GtkEventControllerKey + * + * Returns the widget that will be holding focus afterwards. + * + * This function can only be used in handlers for the + * #GtkEventControllerKey::focus-in and + * #GtkEventControllerKey::focus-out signals. + * + * Returns: (transfer none): the next focus + */ +GtkWidget * +gtk_event_controller_key_get_focus_target (GtkEventControllerKey *controller) +{ + gboolean focus_in; + + g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_KEY (controller), NULL); + g_return_val_if_fail (controller->current_event != NULL, NULL); + g_return_val_if_fail (gdk_event_get_event_type (controller->current_event) == GDK_FOCUS_CHANGE, NULL); + + gdk_event_get_focus_in (controller->current_event, &focus_in); + + if (focus_in) + return (GtkWidget *)gdk_event_get_target (controller->current_event); + else + return (GtkWidget *)gdk_event_get_related_target (controller->current_event); +} diff --git a/gtk/gtkeventcontrollerkey.h b/gtk/gtkeventcontrollerkey.h index c8a579773a44943ec7292c4645bcd407420d28f2..b036bde7ec46859b326c899f6252f14e4eeb7ff6 100644 --- a/gtk/gtkeventcontrollerkey.h +++ b/gtk/gtkeventcontrollerkey.h @@ -58,6 +58,11 @@ gboolean gtk_event_controller_key_forward (GtkEventControllerK GDK_AVAILABLE_IN_ALL guint gtk_event_controller_key_get_group (GtkEventControllerKey *controller); +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_event_controller_key_get_focus_origin (GtkEventControllerKey *controller); +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_event_controller_key_get_focus_target (GtkEventControllerKey *controller); + G_END_DECLS #endif /* __GTK_EVENT_CONTROLLER_KEY_H__ */ diff --git a/gtk/gtkexpander.c b/gtk/gtkexpander.c index cba7916c1216ed8282c9a774fa631741915b0bbf..c03c0257c351f2c478d22cd30a9afd6d7f86920f 100644 --- a/gtk/gtkexpander.c +++ b/gtk/gtkexpander.c @@ -177,8 +177,6 @@ static void gtk_expander_size_allocate (GtkWidget *widget, int width, int height, int baseline); -static gboolean gtk_expander_focus (GtkWidget *widget, - GtkDirectionType direction); static gboolean gtk_expander_drag_motion (GtkWidget *widget, GdkDrop *drop, gint x, @@ -253,7 +251,6 @@ gtk_expander_class_init (GtkExpanderClass *klass) widget_class->destroy = gtk_expander_destroy; widget_class->size_allocate = gtk_expander_size_allocate; - widget_class->focus = gtk_expander_focus; widget_class->drag_motion = gtk_expander_drag_motion; widget_class->drag_leave = gtk_expander_drag_leave; widget_class->measure = gtk_expander_measure; @@ -562,142 +559,6 @@ gtk_expander_drag_leave (GtkWidget *widget, } } -typedef enum -{ - FOCUS_NONE, - FOCUS_WIDGET, - FOCUS_LABEL, - FOCUS_CHILD -} FocusSite; - -static gboolean -focus_current_site (GtkExpander *expander, - GtkDirectionType direction) -{ - GtkWidget *current_focus; - - current_focus = gtk_widget_get_focus_child (GTK_WIDGET (expander)); - - if (!current_focus) - return FALSE; - - return gtk_widget_child_focus (current_focus, direction); -} - -static gboolean -focus_in_site (GtkExpander *expander, - FocusSite site, - GtkDirectionType direction) -{ - GtkExpanderPrivate *priv = gtk_expander_get_instance_private (expander); - - switch (site) - { - case FOCUS_WIDGET: - gtk_widget_grab_focus (GTK_WIDGET (expander)); - return TRUE; - case FOCUS_LABEL: - if (priv->label_widget) - return gtk_widget_child_focus (priv->label_widget, direction); - else - return FALSE; - case FOCUS_CHILD: - { - GtkWidget *child = priv->child; - - if (child && gtk_widget_get_child_visible (child)) - return gtk_widget_child_focus (child, direction); - else - return FALSE; - } - case FOCUS_NONE: - default: - break; - } - - g_assert_not_reached (); - return FALSE; -} - -static FocusSite -get_next_site (GtkExpander *expander, - FocusSite site, - GtkDirectionType direction) -{ - gboolean ltr; - - ltr = gtk_widget_get_direction (GTK_WIDGET (expander)) != GTK_TEXT_DIR_RTL; - - switch (site) - { - case FOCUS_NONE: - switch (direction) - { - case GTK_DIR_TAB_BACKWARD: - case GTK_DIR_LEFT: - case GTK_DIR_UP: - return FOCUS_CHILD; - case GTK_DIR_TAB_FORWARD: - case GTK_DIR_DOWN: - case GTK_DIR_RIGHT: - default: - return FOCUS_WIDGET; - } - break; - case FOCUS_WIDGET: - switch (direction) - { - case GTK_DIR_TAB_BACKWARD: - case GTK_DIR_UP: - return FOCUS_NONE; - case GTK_DIR_LEFT: - return ltr ? FOCUS_NONE : FOCUS_LABEL; - case GTK_DIR_TAB_FORWARD: - case GTK_DIR_DOWN: - default: - return FOCUS_LABEL; - case GTK_DIR_RIGHT: - return ltr ? FOCUS_LABEL : FOCUS_NONE; - } - break; - case FOCUS_LABEL: - switch (direction) - { - case GTK_DIR_TAB_BACKWARD: - case GTK_DIR_UP: - return FOCUS_WIDGET; - case GTK_DIR_LEFT: - return ltr ? FOCUS_WIDGET : FOCUS_CHILD; - case GTK_DIR_TAB_FORWARD: - case GTK_DIR_DOWN: - default: - return FOCUS_CHILD; - case GTK_DIR_RIGHT: - return ltr ? FOCUS_CHILD : FOCUS_WIDGET; - } - break; - case FOCUS_CHILD: - switch (direction) - { - case GTK_DIR_TAB_BACKWARD: - case GTK_DIR_LEFT: - case GTK_DIR_UP: - return FOCUS_LABEL; - case GTK_DIR_TAB_FORWARD: - case GTK_DIR_DOWN: - case GTK_DIR_RIGHT: - default: - return FOCUS_NONE; - } - break; - default: - g_assert_not_reached (); - break; - } - - return FOCUS_NONE; -} - static void gtk_expander_resize_toplevel (GtkExpander *expander) { @@ -731,41 +592,6 @@ gtk_expander_resize_toplevel (GtkExpander *expander) } } -static gboolean -gtk_expander_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - GtkExpander *expander = GTK_EXPANDER (widget); - GtkExpanderPrivate *priv = gtk_expander_get_instance_private (expander); - - if (!focus_current_site (expander, direction)) - { - GtkWidget *old_focus_child; - gboolean widget_is_focus; - FocusSite site = FOCUS_NONE; - - widget_is_focus = gtk_widget_is_focus (widget); - old_focus_child = gtk_widget_get_focus_child (GTK_WIDGET (widget)); - - if (old_focus_child && old_focus_child == priv->label_widget) - site = FOCUS_LABEL; - else if (old_focus_child) - site = FOCUS_CHILD; - else if (widget_is_focus) - site = FOCUS_WIDGET; - - while ((site = get_next_site (expander, site, direction)) != FOCUS_NONE) - { - if (focus_in_site (expander, site, direction)) - return TRUE; - } - - return FALSE; - } - - return TRUE; -} - static void gtk_expander_add (GtkContainer *container, GtkWidget *widget) diff --git a/gtk/gtkfilechooserwidget.c b/gtk/gtkfilechooserwidget.c index 4d9ab00e051201dc07b42f3714fb59037cbe12ce..3227a84e1f6751b3d7655a33f04284db8b2d35c7 100644 --- a/gtk/gtkfilechooserwidget.c +++ b/gtk/gtkfilechooserwidget.c @@ -82,6 +82,7 @@ #include "gtkdebug.h" #include "gtkfilechoosererrorstackprivate.h" #include "gtkentryprivate.h" +#include "gtkroot.h" #include @@ -340,6 +341,7 @@ struct _GtkFileChooserWidgetPrivate { GSource *focus_entry_idle; gulong toplevel_set_focus_id; + GtkWidget *toplevel_current_focus_widget; GtkWidget *toplevel_last_focus_widget; gint sort_column; @@ -1361,7 +1363,7 @@ key_press_cb (GtkEventController *controller, GtkWidget *default_widget, *focus_widget; default_widget = gtk_window_get_default_widget (window); - focus_widget = gtk_window_get_focus (window); + focus_widget = gtk_root_get_focus (GTK_ROOT (window)); if (widget != default_widget && !(widget == focus_widget && (!default_widget || !gtk_widget_get_sensitive (default_widget)))) @@ -2709,7 +2711,7 @@ location_mode_set (GtkFileChooserWidget *impl, switch_to_file_list = FALSE; if (toplevel) { - current_focus = gtk_window_get_focus (toplevel); + current_focus = gtk_root_get_focus (GTK_ROOT (toplevel)); if (!current_focus || current_focus == priv->location_entry) switch_to_file_list = TRUE; } @@ -3560,13 +3562,14 @@ gtk_file_chooser_widget_dispose (GObject *object) * widget on our toplevel. See gtk_file_chooser_widget_hierarchy_changed() */ static void -toplevel_set_focus_cb (GtkWindow *window, - GtkWidget *focus, +toplevel_set_focus_cb (GtkWindow *window, + GParamSpec *pspec, GtkFileChooserWidget *impl) { GtkFileChooserWidgetPrivate *priv = impl->priv; - priv->toplevel_last_focus_widget = gtk_window_get_focus (window); + priv->toplevel_last_focus_widget = priv->toplevel_current_focus_widget; + priv->toplevel_current_focus_widget = gtk_root_get_focus (GTK_ROOT (window)); } /* We monitor the focus widget on our toplevel to be able to know which widget @@ -3584,9 +3587,10 @@ gtk_file_chooser_widget_root (GtkWidget *widget) toplevel = gtk_widget_get_toplevel (widget); g_assert (priv->toplevel_set_focus_id == 0); - priv->toplevel_set_focus_id = g_signal_connect (toplevel, "set-focus", + priv->toplevel_set_focus_id = g_signal_connect (toplevel, "notify::focus-widget", G_CALLBACK (toplevel_set_focus_cb), impl); - priv->toplevel_last_focus_widget = gtk_window_get_focus (GTK_WINDOW (toplevel)); + priv->toplevel_last_focus_widget = NULL; + priv->toplevel_current_focus_widget = gtk_root_get_focus (GTK_ROOT (toplevel)); } static void @@ -3602,6 +3606,7 @@ gtk_file_chooser_widget_unroot (GtkWidget *widget) g_signal_handler_disconnect (toplevel, priv->toplevel_set_focus_id); priv->toplevel_set_focus_id = 0; priv->toplevel_last_focus_widget = NULL; + priv->toplevel_current_focus_widget = NULL; } GTK_WIDGET_CLASS (gtk_file_chooser_widget_parent_class)->unroot (widget); @@ -5806,7 +5811,7 @@ gtk_file_chooser_widget_get_files (GtkFileChooser *chooser) toplevel = get_toplevel (GTK_WIDGET (impl)); if (toplevel) - current_focus = gtk_window_get_focus (toplevel); + current_focus = gtk_root_get_focus (GTK_ROOT (toplevel)); else current_focus = NULL; @@ -6655,7 +6660,7 @@ gtk_file_chooser_widget_should_respond (GtkFileChooserEmbed *chooser_embed) retval = FALSE; - current_focus = gtk_window_get_focus (GTK_WINDOW (toplevel)); + current_focus = gtk_root_get_focus (GTK_ROOT (toplevel)); if (current_focus == priv->browse_files_tree_view) { diff --git a/gtk/gtkflowbox.c b/gtk/gtkflowbox.c index 65c491fe4b9e6ee32f37cd75adf7cd0abf7efaa1..b40ffca25ace60d6a82264ceb4483be02eee0fbe 100644 --- a/gtk/gtkflowbox.c +++ b/gtk/gtkflowbox.c @@ -303,92 +303,8 @@ gtk_flow_box_child_get_box (GtkFlowBoxChild *child) return NULL; } -static void -gtk_flow_box_child_set_focus (GtkFlowBoxChild *child) -{ - GtkFlowBox *box = gtk_flow_box_child_get_box (child); - gboolean modify; - gboolean extend; - - get_current_selection_modifiers (GTK_WIDGET (box), &modify, &extend); - - if (modify) - gtk_flow_box_update_cursor (box, child); - else - gtk_flow_box_update_selection (box, child, FALSE, FALSE); -} - /* GtkWidget implementation {{{2 */ -static gboolean -gtk_flow_box_child_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - gboolean had_focus = FALSE; - GtkWidget *child; - - child = gtk_bin_get_child (GTK_BIN (widget)); - - /* Without "can-focus" flag try to pass the focus to the child immediately */ - if (!gtk_widget_get_can_focus (widget)) - { - if (child) - { - if (gtk_widget_child_focus (child, direction)) - { - GtkFlowBox *box; - box = gtk_flow_box_child_get_box (GTK_FLOW_BOX_CHILD (widget)); - if (box) - gtk_flow_box_update_cursor (box, GTK_FLOW_BOX_CHILD (widget)); - return TRUE; - } - } - return FALSE; - } - - g_object_get (widget, "has-focus", &had_focus, NULL); - if (had_focus) - { - /* If on row, going right, enter into possible container */ - if (child && - (direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD)) - { - if (gtk_widget_child_focus (GTK_WIDGET (child), direction)) - return TRUE; - } - - return FALSE; - } - else if (gtk_widget_get_focus_child (widget) != NULL) - { - /* Child has focus, always navigate inside it first */ - if (gtk_widget_child_focus (child, direction)) - return TRUE; - - /* If exiting child container to the left, select child */ - if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD) - { - gtk_flow_box_child_set_focus (GTK_FLOW_BOX_CHILD (widget)); - return TRUE; - } - - return FALSE; - } - else - { - /* If coming from the left, enter into possible container */ - if (child && - (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD)) - { - if (gtk_widget_child_focus (child, direction)) - return TRUE; - } - - gtk_flow_box_child_set_focus (GTK_FLOW_BOX_CHILD (widget)); - return TRUE; - } -} - static void gtk_flow_box_child_activate (GtkFlowBoxChild *child) { @@ -422,7 +338,6 @@ gtk_flow_box_child_class_init (GtkFlowBoxChildClass *class) GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); widget_class->get_request_mode = gtk_flow_box_child_get_request_mode; - widget_class->focus = gtk_flow_box_child_focus; class->activate = gtk_flow_box_child_activate; @@ -2893,77 +2808,6 @@ gtk_flow_box_child_type (GtkContainer *container) /* Keynav {{{2 */ -static gboolean -gtk_flow_box_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - GtkFlowBox *box = GTK_FLOW_BOX (widget); - GtkWidget *focus_child; - GSequenceIter *iter; - GtkFlowBoxChild *next_focus_child; - - /* Without "can-focus" flag fall back to the default behavior immediately */ - if (!gtk_widget_get_can_focus (widget)) - { - return GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->focus (widget, direction); - } - - focus_child = gtk_widget_get_focus_child (widget); - next_focus_child = NULL; - - if (focus_child != NULL) - { - if (gtk_widget_child_focus (focus_child, direction)) - return TRUE; - - iter = CHILD_PRIV (focus_child)->iter; - - if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD) - iter = gtk_flow_box_get_previous_focusable (box, iter); - else if (direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD) - iter = gtk_flow_box_get_next_focusable (box, iter); - else if (direction == GTK_DIR_UP) - iter = gtk_flow_box_get_above_focusable (box, iter); - else if (direction == GTK_DIR_DOWN) - iter = gtk_flow_box_get_below_focusable (box, iter); - - if (iter != NULL) - next_focus_child = g_sequence_get (iter); - } - else - { - if (BOX_PRIV (box)->selected_child) - next_focus_child = BOX_PRIV (box)->selected_child; - else - { - if (direction == GTK_DIR_UP || direction == GTK_DIR_TAB_BACKWARD) - iter = gtk_flow_box_get_last_focusable (box); - else - iter = gtk_flow_box_get_first_focusable (box); - - if (iter != NULL) - next_focus_child = g_sequence_get (iter); - } - } - - if (next_focus_child == NULL) - { - if (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN || - direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT) - { - if (gtk_widget_keynav_failed (GTK_WIDGET (box), direction)) - return TRUE; - } - - return FALSE; - } - - if (gtk_widget_child_focus (GTK_WIDGET (next_focus_child), direction)) - return TRUE; - - return TRUE; -} - static void gtk_flow_box_add_move_binding (GtkBindingSet *binding_set, guint keyval, @@ -3373,7 +3217,6 @@ gtk_flow_box_class_init (GtkFlowBoxClass *class) widget_class->size_allocate = gtk_flow_box_size_allocate; widget_class->unmap = gtk_flow_box_unmap; - widget_class->focus = gtk_flow_box_focus; widget_class->snapshot = gtk_flow_box_snapshot; widget_class->get_request_mode = gtk_flow_box_get_request_mode; widget_class->measure = gtk_flow_box_measure; diff --git a/gtk/gtkgizmo.c b/gtk/gtkgizmo.c index 305e0b524e5d4b28552db3aad72866a14b7a9dba..b3272578a99719e4f4c55394d776228a1e7cfadd 100644 --- a/gtk/gtkgizmo.c +++ b/gtk/gtkgizmo.c @@ -46,6 +46,19 @@ gtk_gizmo_snapshot (GtkWidget *widget, GTK_WIDGET_CLASS (gtk_gizmo_parent_class)->snapshot (widget, snapshot); } +static GtkWidget * +gtk_gizmo_next_focus_child (GtkWidget *widget, + GtkWidget *child, + GtkDirectionType direction) +{ + GtkGizmo *self = GTK_GIZMO (widget); + + if (self->focus_func) + return self->focus_func (self, child, direction); + else + return GTK_WIDGET_CLASS (gtk_gizmo_parent_class)->next_focus_child (widget, child, direction); +} + static void gtk_gizmo_finalize (GObject *object) { @@ -76,6 +89,7 @@ gtk_gizmo_class_init (GtkGizmoClass *klass) widget_class->measure = gtk_gizmo_measure; widget_class->size_allocate = gtk_gizmo_size_allocate; widget_class->snapshot = gtk_gizmo_snapshot; + widget_class->next_focus_child = gtk_gizmo_next_focus_child; } static void @@ -89,6 +103,16 @@ gtk_gizmo_new (const char *css_name, GtkGizmoMeasureFunc measure_func, GtkGizmoAllocateFunc allocate_func, GtkGizmoSnapshotFunc snapshot_func) +{ + return gtk_gizmo_new_with_focus (css_name, measure_func, allocate_func, snapshot_func, NULL); +} + +GtkWidget * +gtk_gizmo_new_with_focus (const char *css_name, + GtkGizmoMeasureFunc measure_func, + GtkGizmoAllocateFunc allocate_func, + GtkGizmoSnapshotFunc snapshot_func, + GtkGizmoFocusFunc focus_func) { GtkGizmo *gizmo = GTK_GIZMO (g_object_new (GTK_TYPE_GIZMO, "css-name", css_name, @@ -97,6 +121,7 @@ gtk_gizmo_new (const char *css_name, gizmo->measure_func = measure_func; gizmo->allocate_func = allocate_func; gizmo->snapshot_func = snapshot_func; + gizmo->focus_func = focus_func; return GTK_WIDGET (gizmo); } diff --git a/gtk/gtkgizmoprivate.h b/gtk/gtkgizmoprivate.h index c65a4303517fcea9d91a89eba459ffd80ef4e990..d1e879d632614dc118d71a18785a501f4e12b19b 100644 --- a/gtk/gtkgizmoprivate.h +++ b/gtk/gtkgizmoprivate.h @@ -27,7 +27,9 @@ typedef void (* GtkGizmoAllocateFunc) (GtkGizmo *gizmo, int baseline); typedef void (* GtkGizmoSnapshotFunc) (GtkGizmo *gizmo, GtkSnapshot *snapshot); - +typedef GtkWidget * (* GtkGizmoFocusFunc) (GtkGizmo *gizmo, + GtkWidget *child, + GtkDirectionType direction); struct _GtkGizmo { @@ -36,6 +38,7 @@ struct _GtkGizmo GtkGizmoMeasureFunc measure_func; GtkGizmoAllocateFunc allocate_func; GtkGizmoSnapshotFunc snapshot_func; + GtkGizmoFocusFunc focus_func; }; struct _GtkGizmoClass @@ -50,5 +53,11 @@ GtkWidget *gtk_gizmo_new (const char *css_name, GtkGizmoAllocateFunc allocate_func, GtkGizmoSnapshotFunc snapshot_func); +GtkWidget *gtk_gizmo_new_with_focus (const char *css_name, + GtkGizmoMeasureFunc measure_func, + GtkGizmoAllocateFunc allocate_func, + GtkGizmoSnapshotFunc snapshot_func, + GtkGizmoFocusFunc focus_func); + #endif diff --git a/gtk/gtklabel.c b/gtk/gtklabel.c index 86442d16a1d5bbc98254ef97b076e6a1a0949f37..95a2ea42b56f552bb0142b0bf50450c04c802cb8 100644 --- a/gtk/gtklabel.c +++ b/gtk/gtklabel.c @@ -33,6 +33,7 @@ #include "gtkcssshadowsvalueprivate.h" #include "gtkcssstylepropertyprivate.h" #include "gtkdnd.h" +#include "gtkeventcontrollerkey.h" #include "gtkeventcontrollermotion.h" #include "gtkgesturedrag.h" #include "gtkgesturemultipress.h" @@ -413,8 +414,6 @@ static void gtk_label_state_flags_changed (GtkWidget *widget, static void gtk_label_style_updated (GtkWidget *widget); static void gtk_label_snapshot (GtkWidget *widget, GtkSnapshot *snapshot); -static gboolean gtk_label_focus (GtkWidget *widget, - GtkDirectionType direction); static void gtk_label_realize (GtkWidget *widget); static void gtk_label_unrealize (GtkWidget *widget); @@ -525,6 +524,10 @@ static void gtk_label_activate_current_link (GtkLabel *label); static GtkLabelLink *gtk_label_get_current_link (GtkLabel *label); static void emit_activate_link (GtkLabel *label, GtkLabelLink *link); +static gboolean range_is_in_ellipsis (GtkLabel *label, + gint range_start, + gint range_end); +static GtkLabelLink *gtk_label_get_focus_link (GtkLabel *label); /* Event controller callbacks */ static void gtk_label_multipress_gesture_pressed (GtkGestureMultiPress *gesture, @@ -612,7 +615,6 @@ gtk_label_class_init (GtkLabelClass *class) widget_class->drag_data_get = gtk_label_drag_data_get; widget_class->grab_focus = gtk_label_grab_focus; widget_class->popup_menu = gtk_label_popup_menu; - widget_class->focus = gtk_label_focus; widget_class->get_request_mode = gtk_label_get_request_mode; widget_class->measure = gtk_label_measure; @@ -1122,6 +1124,177 @@ gtk_label_class_init (GtkLabelClass *class) quark_link = g_quark_from_static_string ("link"); } +static void +focus_in_cb (GtkEventControllerKey *controller, + GdkCrossingMode mode, + GdkNotifyType detail, + GtkWidget *widget) +{ + GtkLabel *label = GTK_LABEL (widget); + GtkLabelPrivate *priv = gtk_label_get_instance_private (label); + GtkLabelSelectionInfo *info = priv->select_info; + + if (!info) + return; + + if (info->selectable) + { + gboolean select_on_focus; + + g_object_get (gtk_widget_get_settings (widget), + "gtk-label-select-on-focus", + &select_on_focus, + NULL); + + if (select_on_focus && !priv->in_click) + gtk_label_select_region (label, 0, -1); + } + else + { + if (info->links && !priv->in_click) + { + GList *l; + int i; + + for (l = info->links, i = 0; l; l = l->next, i++) + { + GtkLabelLink *link = l->data; + if (!range_is_in_ellipsis (label, link->start, link->end)) + { + info->selection_anchor = link->start; + info->selection_end = link->start; + _gtk_label_accessible_focus_link_changed (label); + gtk_widget_queue_draw (widget); + break; + } + } + } + } +} + +static gboolean +key_press_cb (GtkEventControllerKey *controller, + guint keyval, + guint keycode, + GdkModifierType modifiers, + GtkWidget *widget) +{ + GtkLabel *label = GTK_LABEL (widget); + GtkLabelPrivate *priv = gtk_label_get_instance_private (label); + GtkLabelSelectionInfo *info = priv->select_info; + GtkDirectionType direction; + GtkLabelLink *focus_link; + GList *l; + + if (keyval != GDK_KEY_Tab && keyval != GDK_KEY_KP_Tab) + return FALSE; + + if ((modifiers & GDK_SHIFT_MASK) != 0) + direction = GTK_DIR_TAB_BACKWARD; + else + direction = GTK_DIR_TAB_FORWARD; + + if (!info) + return FALSE; + + if (info->selectable) + { + int index; + int i; + + if (info->selection_anchor != info->selection_end) + return FALSE; + + index = info->selection_anchor; + + if (direction == GTK_DIR_TAB_FORWARD) + { + for (l = info->links, i = 0; l; l = l->next, i++) + { + GtkLabelLink *link = l->data; + + if (link->start > index) + { + if (!range_is_in_ellipsis (label, link->start, link->end)) + { + gtk_label_select_region_index (label, link->start, link->start); + _gtk_label_accessible_focus_link_changed (label); + gtk_widget_queue_draw (widget); + return TRUE; + } + } + } + } + else + { + for (l = g_list_last (info->links), i = g_list_length (info->links) - 1; l; l = l->prev, i--) + { + GtkLabelLink *link = l->data; + + if (link->end < index) + { + if (!range_is_in_ellipsis (label, link->start, link->end)) + { + gtk_label_select_region_index (label, link->start, link->start); + _gtk_label_accessible_focus_link_changed (label); + gtk_widget_queue_draw (widget); + return TRUE; + } + } + } + } + } + else + { + focus_link = gtk_label_get_focus_link (label); + if (direction == GTK_DIR_TAB_FORWARD) + { + if (focus_link) + { + l = g_list_find (info->links, focus_link); + l = l->next; + } + else + l = info->links; + for (; l; l = l->next) + { + GtkLabelLink *link = l->data; + if (!range_is_in_ellipsis (label, link->start, link->end)) + break; + } + } + else + { + if (focus_link) + { + l = g_list_find (info->links, focus_link); + l = l->prev; + } + else + l = g_list_last (info->links); + for (; l; l = l->prev) + { + GtkLabelLink *link = l->data; + if (!range_is_in_ellipsis (label, link->start, link->end)) + break; + } + } + + if (l) + { + focus_link = l->data; + info->selection_anchor = focus_link->start; + info->selection_end = focus_link->start; + _gtk_label_accessible_focus_link_changed (label); + gtk_widget_queue_draw (widget); + + return TRUE; + } + } + + return FALSE; +} + static void gtk_label_set_property (GObject *object, guint prop_id, @@ -1273,6 +1446,7 @@ static void gtk_label_init (GtkLabel *label) { GtkLabelPrivate *priv = gtk_label_get_instance_private (label); + GtkEventController *controller; gtk_widget_set_has_surface (GTK_WIDGET (label), FALSE); @@ -1303,6 +1477,11 @@ gtk_label_init (GtkLabel *label) priv->mnemonic_window = NULL; priv->mnemonics_visible = TRUE; + + controller = gtk_event_controller_key_new (); + g_signal_connect (controller, "focus-in", G_CALLBACK (focus_in_cb), label); + g_signal_connect (controller, "key-pressed", G_CALLBACK (key_press_cb), label); + gtk_widget_add_controller (GTK_WIDGET (label), controller); } @@ -4260,185 +4439,11 @@ gtk_label_grab_focus (GtkWidget *widget) { GtkLabel *label = GTK_LABEL (widget); GtkLabelPrivate *priv = gtk_label_get_instance_private (label); - gboolean select_on_focus; - GtkLabelLink *link; - GList *l; if (priv->select_info == NULL) return; GTK_WIDGET_CLASS (gtk_label_parent_class)->grab_focus (widget); - - if (priv->select_info->selectable) - { - g_object_get (gtk_widget_get_settings (widget), - "gtk-label-select-on-focus", - &select_on_focus, - NULL); - - if (select_on_focus && !priv->in_click) - gtk_label_select_region (label, 0, -1); - } - else - { - if (priv->select_info->links && !priv->in_click) - { - for (l = priv->select_info->links; l; l = l->next) - { - link = l->data; - if (!range_is_in_ellipsis (label, link->start, link->end)) - { - priv->select_info->selection_anchor = link->start; - priv->select_info->selection_end = link->start; - _gtk_label_accessible_focus_link_changed (label); - break; - } - } - } - } -} - -static gboolean -gtk_label_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - GtkLabel *label = GTK_LABEL (widget); - GtkLabelPrivate *priv = gtk_label_get_instance_private (label); - GtkLabelSelectionInfo *info = priv->select_info; - GtkLabelLink *focus_link; - GList *l; - - if (!gtk_widget_is_focus (widget)) - { - gtk_widget_grab_focus (widget); - if (info) - { - focus_link = gtk_label_get_focus_link (label); - if (focus_link && direction == GTK_DIR_TAB_BACKWARD) - { - for (l = g_list_last (info->links); l; l = l->prev) - { - focus_link = l->data; - if (!range_is_in_ellipsis (label, focus_link->start, focus_link->end)) - { - info->selection_anchor = focus_link->start; - info->selection_end = focus_link->start; - _gtk_label_accessible_focus_link_changed (label); - } - } - } - - return TRUE; - } - - return FALSE; - } - - if (!info) - return FALSE; - - if (info->selectable) - { - gint index; - - if (info->selection_anchor != info->selection_end) - goto out; - - index = info->selection_anchor; - - if (direction == GTK_DIR_TAB_FORWARD) - for (l = info->links; l; l = l->next) - { - GtkLabelLink *link = l->data; - - if (link->start > index) - { - if (!range_is_in_ellipsis (label, link->start, link->end)) - { - gtk_label_select_region_index (label, link->start, link->start); - _gtk_label_accessible_focus_link_changed (label); - return TRUE; - } - } - } - else if (direction == GTK_DIR_TAB_BACKWARD) - for (l = g_list_last (info->links); l; l = l->prev) - { - GtkLabelLink *link = l->data; - - if (link->end < index) - { - if (!range_is_in_ellipsis (label, link->start, link->end)) - { - gtk_label_select_region_index (label, link->start, link->start); - _gtk_label_accessible_focus_link_changed (label); - return TRUE; - } - } - } - - goto out; - } - else - { - focus_link = gtk_label_get_focus_link (label); - switch (direction) - { - case GTK_DIR_TAB_FORWARD: - if (focus_link) - { - l = g_list_find (info->links, focus_link); - l = l->next; - } - else - l = info->links; - for (; l; l = l->next) - { - GtkLabelLink *link = l->data; - if (!range_is_in_ellipsis (label, link->start, link->end)) - break; - } - break; - - case GTK_DIR_TAB_BACKWARD: - if (focus_link) - { - l = g_list_find (info->links, focus_link); - l = l->prev; - } - else - l = g_list_last (info->links); - for (; l; l = l->prev) - { - GtkLabelLink *link = l->data; - if (!range_is_in_ellipsis (label, link->start, link->end)) - break; - } - break; - - default: - case GTK_DIR_UP: - case GTK_DIR_DOWN: - case GTK_DIR_LEFT: - case GTK_DIR_RIGHT: - goto out; - } - - if (l) - { - focus_link = l->data; - info->selection_anchor = focus_link->start; - info->selection_end = focus_link->start; - _gtk_label_accessible_focus_link_changed (label); - gtk_widget_queue_draw (widget); - - return TRUE; - } - } - -out: - - return FALSE; } static void @@ -6222,7 +6227,7 @@ gtk_label_activate_current_link (GtkLabel *label) if (window) { default_widget = gtk_window_get_default_widget (window); - focus_widget = gtk_window_get_focus (window); + focus_widget = gtk_root_get_focus (GTK_ROOT (window)); if (default_widget != widget && !(widget == focus_widget && (!default_widget || !gtk_widget_is_sensitive (default_widget)))) diff --git a/gtk/gtklistbox.c b/gtk/gtklistbox.c index f6ccfb1931b52c28fb63ad89147511d6077ac68a..b3edb45fec198ab0c0ecba6d772ed76740a2dbc2 100644 --- a/gtk/gtklistbox.c +++ b/gtk/gtklistbox.c @@ -208,8 +208,6 @@ static void gtk_list_box_update_cursor (GtkListBo GtkListBoxRow *row, gboolean grab_focus); static void gtk_list_box_show (GtkWidget *widget); -static gboolean gtk_list_box_focus (GtkWidget *widget, - GtkDirectionType direction); static GSequenceIter* gtk_list_box_get_previous_visible (GtkListBox *box, GSequenceIter *iter); static GtkListBoxRow *gtk_list_box_get_first_focusable (GtkListBox *box); @@ -407,7 +405,6 @@ gtk_list_box_class_init (GtkListBoxClass *klass) object_class->set_property = gtk_list_box_set_property; object_class->finalize = gtk_list_box_finalize; widget_class->show = gtk_list_box_show; - widget_class->focus = gtk_list_box_focus; widget_class->compute_expand = gtk_list_box_compute_expand; widget_class->get_request_mode = gtk_list_box_get_request_mode; widget_class->measure = gtk_list_box_measure; @@ -1870,129 +1867,6 @@ gtk_list_box_show (GtkWidget *widget) GTK_WIDGET_CLASS (gtk_list_box_parent_class)->show (widget); } -static gboolean -gtk_list_box_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - GtkListBox *box = GTK_LIST_BOX (widget); - GtkListBoxPrivate *priv = BOX_PRIV (box); - GtkWidget *focus_child; - GtkListBoxRow *next_focus_row; - GtkWidget *row; - GtkWidget *header; - - focus_child = gtk_widget_get_focus_child (widget); - - next_focus_row = NULL; - if (focus_child != NULL) - { - GSequenceIter *i; - - if (gtk_widget_child_focus (focus_child, direction)) - return TRUE; - - if (direction == GTK_DIR_UP || direction == GTK_DIR_TAB_BACKWARD) - { - if (GTK_IS_LIST_BOX_ROW (focus_child)) - { - header = ROW_PRIV (GTK_LIST_BOX_ROW (focus_child))->header; - if (header && gtk_widget_child_focus (header, direction)) - return TRUE; - } - - if (GTK_IS_LIST_BOX_ROW (focus_child)) - row = focus_child; - else - row = g_hash_table_lookup (priv->header_hash, focus_child); - - if (GTK_IS_LIST_BOX_ROW (row)) - i = gtk_list_box_get_previous_visible (box, ROW_PRIV (GTK_LIST_BOX_ROW (row))->iter); - else - i = NULL; - - while (i != NULL) - { - if (gtk_widget_get_sensitive (g_sequence_get (i))) - { - next_focus_row = g_sequence_get (i); - break; - } - - i = gtk_list_box_get_previous_visible (box, i); - } - } - else if (direction == GTK_DIR_DOWN || direction == GTK_DIR_TAB_FORWARD) - { - if (GTK_IS_LIST_BOX_ROW (focus_child)) - i = gtk_list_box_get_next_visible (box, ROW_PRIV (GTK_LIST_BOX_ROW (focus_child))->iter); - else - { - row = g_hash_table_lookup (priv->header_hash, focus_child); - if (GTK_IS_LIST_BOX_ROW (row)) - i = ROW_PRIV (GTK_LIST_BOX_ROW (row))->iter; - else - i = NULL; - } - - while (!g_sequence_iter_is_end (i)) - { - if (gtk_widget_get_sensitive (g_sequence_get (i))) - { - next_focus_row = g_sequence_get (i); - break; - } - - i = gtk_list_box_get_next_visible (box, i); - } - } - } - else - { - /* No current focus row */ - switch (direction) - { - case GTK_DIR_UP: - case GTK_DIR_TAB_BACKWARD: - next_focus_row = priv->selected_row; - if (next_focus_row == NULL) - next_focus_row = gtk_list_box_get_last_focusable (box); - break; - case GTK_DIR_DOWN: - case GTK_DIR_TAB_FORWARD: - case GTK_DIR_LEFT: - case GTK_DIR_RIGHT: - default: - next_focus_row = priv->selected_row; - if (next_focus_row == NULL) - next_focus_row = gtk_list_box_get_first_focusable (box); - break; - } - } - - if (next_focus_row == NULL) - { - if (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN) - { - if (gtk_widget_keynav_failed (GTK_WIDGET (box), direction)) - return TRUE; - } - - return FALSE; - } - - if (direction == GTK_DIR_DOWN || direction == GTK_DIR_TAB_FORWARD) - { - header = ROW_PRIV (next_focus_row)->header; - if (header && gtk_widget_child_focus (header, direction)) - return TRUE; - } - - if (gtk_widget_child_focus (GTK_WIDGET (next_focus_row), direction)) - return TRUE; - - return FALSE; -} - static void list_box_add_visible_rows (GtkListBox *box, gint n) @@ -2882,77 +2756,6 @@ gtk_list_box_row_new (void) return g_object_new (GTK_TYPE_LIST_BOX_ROW, NULL); } -static void -gtk_list_box_row_set_focus (GtkListBoxRow *row) -{ - GtkListBox *box = gtk_list_box_row_get_box (row); - gboolean modify; - gboolean extend; - - if (!box) - return; - - get_current_selection_modifiers (GTK_WIDGET (row), &modify, &extend); - - if (modify) - gtk_list_box_update_cursor (box, row, TRUE); - else - gtk_list_box_update_selection (box, row, FALSE, FALSE); -} - -static gboolean -gtk_list_box_row_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - GtkListBoxRow *row = GTK_LIST_BOX_ROW (widget); - gboolean had_focus = FALSE; - GtkWidget *child; - - child = gtk_bin_get_child (GTK_BIN (widget)); - - g_object_get (widget, "has-focus", &had_focus, NULL); - if (had_focus) - { - /* If on row, going right, enter into possible container */ - if (child && - (direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD)) - { - if (gtk_widget_child_focus (GTK_WIDGET (child), direction)) - return TRUE; - } - - return FALSE; - } - else if (gtk_widget_get_focus_child (widget) != NULL) - { - /* Child has focus, always navigate inside it first */ - if (gtk_widget_child_focus (gtk_widget_get_focus_child (widget), direction)) - return TRUE; - - /* If exiting child container to the left, select row */ - if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD) - { - gtk_list_box_row_set_focus (row); - return TRUE; - } - - return FALSE; - } - else - { - /* If coming from the left, enter into possible container */ - if (child && - (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD)) - { - if (gtk_widget_child_focus (child, direction)) - return TRUE; - } - - gtk_list_box_row_set_focus (row); - return TRUE; - } -} - static void gtk_list_box_row_activate (GtkListBoxRow *row) { @@ -3384,7 +3187,6 @@ gtk_list_box_row_class_init (GtkListBoxRowClass *klass) widget_class->show = gtk_list_box_row_show; widget_class->hide = gtk_list_box_row_hide; - widget_class->focus = gtk_list_box_row_focus; widget_class->grab_focus = gtk_list_box_row_grab_focus; klass->activate = gtk_list_box_row_activate; diff --git a/gtk/gtkmain.c b/gtk/gtkmain.c index 8aa278e9eb4f164a1872d6d5763ac0e6a579eae6..13a2aa5e76efce6d5fb3a8542f384fc6dfb0e375 100644 --- a/gtk/gtkmain.c +++ b/gtk/gtkmain.c @@ -1407,62 +1407,83 @@ static void synth_crossing (GtkWidget *widget, GtkWidget *toplevel, gboolean enter, - GtkWidget *other_widget, + GtkWidget *target, + GtkWidget *related_target, GdkEvent *source, GdkNotifyType notify_type, GdkCrossingMode crossing_mode) { GdkEvent *event; - gdouble x, y; + GtkStateFlags flags; - event = gdk_event_new (enter ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY); - gdk_event_set_user_data (event, G_OBJECT (widget)); + if (gdk_event_get_event_type (source) == GDK_FOCUS_CHANGE) + { + event = gdk_event_new (GDK_FOCUS_CHANGE); + event->focus_change.in = enter; + event->focus_change.mode = crossing_mode; + event->focus_change.detail = notify_type; + + flags = GTK_STATE_FLAG_FOCUSED; + if (!GTK_IS_WINDOW (toplevel) || gtk_window_get_focus_visible (GTK_WINDOW (toplevel))) + flags |= GTK_STATE_FLAG_FOCUS_VISIBLE; + } + else + { + gdouble x, y; + event = gdk_event_new (enter ? GDK_ENTER_NOTIFY : GDK_LEAVE_NOTIFY); + if (related_target) + event->crossing.child_surface = g_object_ref (gtk_widget_get_surface (related_target)); + gdk_event_get_coords (source, &x, &y); + event->crossing.x = x; + event->crossing.y = y; + event->crossing.mode = crossing_mode; + event->crossing.detail = notify_type; + + flags = GTK_STATE_FLAG_PRELIGHT; + } + + gdk_event_set_target (event, G_OBJECT (target)); + gdk_event_set_related_target (event, G_OBJECT (related_target)); gdk_event_set_device (event, gdk_event_get_device (source)); gdk_event_set_source_device (event, gdk_event_get_source_device (source)); - event->any.surface = g_object_ref (gtk_widget_get_surface (toplevel)); - if (other_widget) - event->crossing.child_surface = g_object_ref (gtk_widget_get_surface (other_widget)); + event->any.surface = gtk_widget_get_surface (toplevel); + if (event->any.surface) + g_object_ref (event->any.surface); if (enter) - gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_PRELIGHT, FALSE); + gtk_widget_set_state_flags (widget, flags, FALSE); else - gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_PRELIGHT); + gtk_widget_unset_state_flags (widget, flags); - gdk_event_get_coords (source, &x, &y); - event->crossing.x = x; - event->crossing.y = y; - event->crossing.mode = crossing_mode; - event->crossing.detail = notify_type; + if (gdk_event_get_event_type (source) == GDK_FOCUS_CHANGE) + { + /* maintain focus chain */ + if (enter || notify_type == GDK_NOTIFY_INFERIOR) + { + GtkWidget *parent = gtk_widget_get_parent (widget); + if (parent) + gtk_widget_set_focus_child (parent, widget); + } + else if (!enter && notify_type != GDK_NOTIFY_INFERIOR) + { + GtkWidget *parent = gtk_widget_get_parent (widget); + if (parent) + gtk_widget_set_focus_child (parent, NULL); + } + /* maintain widget state */ + if (notify_type == GDK_NOTIFY_ANCESTOR || + notify_type == GDK_NOTIFY_INFERIOR || + notify_type == GDK_NOTIFY_NONLINEAR) + gtk_widget_set_has_focus (widget, enter); + } + gtk_widget_event (widget, event); g_object_unref (event); } -static GtkWidget * -update_pointer_focus_state (GtkWindow *toplevel, - GdkEvent *event, - GtkWidget *new_target) -{ - GtkWidget *old_target = NULL; - GdkEventSequence *sequence; - GdkDevice *device; - gdouble x, y; - - device = gdk_event_get_device (event); - sequence = gdk_event_get_event_sequence (event); - old_target = gtk_window_lookup_pointer_focus_widget (toplevel, device, sequence); - if (old_target == new_target) - return old_target; - - gdk_event_get_coords (event, &x, &y); - gtk_window_update_pointer_focus (toplevel, device, sequence, - new_target, x, y); - - return old_target; -} - -static void +void gtk_synthesize_crossing_events (GtkWindow *toplevel, GtkWidget *old_target, GtkWidget *new_target, @@ -1492,13 +1513,16 @@ gtk_synthesize_crossing_events (GtkWindow *toplevel, { widget = old_target; - while (widget != ancestor) + while (widget) { notify_type = (widget == old_target) ? leave_type : get_virtual_notify_type (leave_type); - synth_crossing (widget, GTK_WIDGET (toplevel), FALSE, - new_target, event, notify_type, mode); + if (widget != ancestor || widget == old_target) + synth_crossing (widget, GTK_WIDGET (toplevel), FALSE, + old_target, new_target, event, notify_type, mode); + if (widget == ancestor) + break; widget = gtk_widget_get_parent (widget); } } @@ -1509,9 +1533,11 @@ gtk_synthesize_crossing_events (GtkWindow *toplevel, widget = new_target; - while (widget != ancestor) + while (widget) { widgets = g_slist_prepend (widgets, widget); + if (widget == ancestor) + break; widget = gtk_widget_get_parent (widget); } @@ -1522,12 +1548,37 @@ gtk_synthesize_crossing_events (GtkWindow *toplevel, notify_type = (widget == new_target) ? enter_type : get_virtual_notify_type (enter_type); - synth_crossing (widget, GTK_WIDGET (toplevel), TRUE, - old_target, event, notify_type, mode); + if (widget != ancestor || widget == new_target) + synth_crossing (widget, GTK_WIDGET (toplevel), TRUE, + new_target, old_target, event, notify_type, mode); } } } + +static GtkWidget * +update_pointer_focus_state (GtkWindow *toplevel, + GdkEvent *event, + GtkWidget *new_target) +{ + GtkWidget *old_target = NULL; + GdkEventSequence *sequence; + GdkDevice *device; + gdouble x, y; + + device = gdk_event_get_device (event); + sequence = gdk_event_get_event_sequence (event); + old_target = gtk_window_lookup_pointer_focus_widget (toplevel, device, sequence); + if (old_target == new_target) + return old_target; + + gdk_event_get_coords (event, &x, &y); + gtk_window_update_pointer_focus (toplevel, device, sequence, + new_target, x, y); + + return old_target; +} + static gboolean is_pointing_event (GdkEvent *event) { @@ -1761,7 +1812,7 @@ gtk_main_do_event (GdkEvent *event) if (is_pointing_event (event)) target_widget = handle_pointing_event (event); - else if (GTK_IS_WINDOW (target_widget) && + else if (GTK_IS_ROOT (target_widget) && (event->any.type == GDK_KEY_PRESS || event->any.type == GDK_KEY_RELEASE)) { @@ -1771,7 +1822,7 @@ gtk_main_do_event (GdkEvent *event) gtk_window_activate_key (GTK_WINDOW (target_widget), (GdkEventKey *) event)) goto cleanup; - focus_widget = gtk_window_get_focus (GTK_WINDOW (target_widget)); + focus_widget = gtk_root_get_focus (GTK_ROOT (target_widget)); if (focus_widget) target_widget = focus_widget; } @@ -1779,7 +1830,7 @@ gtk_main_do_event (GdkEvent *event) if (!target_widget) goto cleanup; - gdk_event_set_user_data (event, G_OBJECT (target_widget)); + gdk_event_set_target (event, G_OBJECT (target_widget)); window_group = gtk_main_get_window_group (target_widget); device = gdk_event_get_device (event); @@ -2398,7 +2449,13 @@ gtk_get_event_widget (const GdkEvent *event) GtkWidget * gtk_get_event_target (const GdkEvent *event) { - return GTK_WIDGET (gdk_event_get_user_data (event)); + return GTK_WIDGET (gdk_event_get_target (event)); +} + +GtkWidget * +gtk_get_event_related_target (const GdkEvent *event) +{ + return GTK_WIDGET (gdk_event_get_related_target (event)); } /** diff --git a/gtk/gtkmain.h b/gtk/gtkmain.h index 81c1e574ada79f83f62ea92ce41d25dc80fea812..b1199e6f93212d33e597fa7c6a57d3738e32d7ab 100644 --- a/gtk/gtkmain.h +++ b/gtk/gtkmain.h @@ -155,6 +155,9 @@ GtkWidget *gtk_get_event_widget (const GdkEvent *event); GDK_AVAILABLE_IN_ALL GtkWidget *gtk_get_event_target (const GdkEvent *event); +GDK_AVAILABLE_IN_ALL +GtkWidget *gtk_get_event_related_target (const GdkEvent *event); + GDK_AVAILABLE_IN_ALL GtkWidget *gtk_get_event_target_with_type (GdkEvent *event, GType type); diff --git a/gtk/gtkmenu.c b/gtk/gtkmenu.c index 247dd4f12eeca0b57faaa18e7a33f2ed65e7b70a..a3a22cbcb5c3df15264ab9a7449d692540a16d52 100644 --- a/gtk/gtkmenu.c +++ b/gtk/gtkmenu.c @@ -251,8 +251,9 @@ static void gtk_menu_handle_scrolling (GtkMenu *menu, gint event_y, gboolean enter, gboolean motion); -static gboolean gtk_menu_focus (GtkWidget *widget, - GtkDirectionType direction); +static GtkWidget *gtk_menu_next_focus_child (GtkWidget *widget, + GtkWidget *child, + GtkDirectionType direction); static gint gtk_menu_get_popup_delay (GtkMenuShell *menu_shell); static void gtk_menu_move_current (GtkMenuShell *menu_shell, GtkMenuDirectionType direction); @@ -492,7 +493,7 @@ gtk_menu_class_init (GtkMenuClass *class) widget_class->size_allocate = gtk_menu_size_allocate; widget_class->show = gtk_menu_show; widget_class->snapshot = gtk_menu_snapshot; - widget_class->focus = gtk_menu_focus; + widget_class->next_focus_child = gtk_menu_next_focus_child; widget_class->can_activate_accel = gtk_menu_real_can_activate_accel; widget_class->grab_notify = gtk_menu_grab_notify; widget_class->measure = gtk_menu_measure; @@ -2254,12 +2255,13 @@ gtk_menu_realize (GtkWidget *widget) GTK_MENU_SHELL (widget)->priv->active_menu_item); } -static gboolean -gtk_menu_focus (GtkWidget *widget, - GtkDirectionType direction) +static GtkWidget * +gtk_menu_next_focus_child (GtkWidget *widget, + GtkWidget *child, + GtkDirectionType direction) { /* A menu or its menu items cannot have focus */ - return FALSE; + return NULL; } static GdkSurface * diff --git a/gtk/gtknotebook.c b/gtk/gtknotebook.c index d11ee425848dfe77ef1ee5e67687c1f64116372b..099b470d3b0e49eeed755fef762afc995db0add8 100644 --- a/gtk/gtknotebook.c +++ b/gtk/gtknotebook.c @@ -211,7 +211,6 @@ struct _GtkNotebookPrivate guint32 timer; - guint child_has_focus : 1; guint click_child : 3; guint remove_in_detach : 1; guint focus_out : 1; /* Flag used by ::move-focus-out implementation */ @@ -303,7 +302,6 @@ struct _GtkNotebookPage GtkWidget *child; GtkWidget *tab_label; GtkWidget *menu_label; - GtkWidget *last_focus_child; /* Last descendant of the page that had focus */ GtkWidget *tab_widget; /* widget used for the tab itself */ @@ -645,8 +643,6 @@ static void gtk_notebook_grab_notify (GtkWidget *widget, gboolean was_grabbed); static void gtk_notebook_state_flags_changed (GtkWidget *widget, GtkStateFlags previous_state); -static gboolean gtk_notebook_focus (GtkWidget *widget, - GtkDirectionType direction); /*** Drag and drop Methods ***/ static void gtk_notebook_drag_begin (GtkWidget *widget, @@ -678,8 +674,6 @@ static void gtk_notebook_add (GtkContainer *container, GtkWidget *widget); static void gtk_notebook_remove (GtkContainer *container, GtkWidget *widget); -static void gtk_notebook_set_focus_child (GtkContainer *container, - GtkWidget *child); static GType gtk_notebook_child_type (GtkContainer *container); static void gtk_notebook_forall (GtkContainer *container, GtkCallback callback, @@ -710,6 +704,9 @@ static void gtk_notebook_allocate_tabs (GtkGizmo *gizmo, int baseline); static void gtk_notebook_snapshot_tabs (GtkGizmo *gizmo, GtkSnapshot *snapshot); +static GtkWidget *gtk_notebook_next_focus_child_tabs (GtkGizmo *gizmo, + GtkWidget *child, + GtkDirectionType direction); /*** GtkNotebook Private Functions ***/ static void gtk_notebook_real_remove (GtkNotebook *notebook, @@ -923,7 +920,6 @@ gtk_notebook_class_init (GtkNotebookClass *class) widget_class->popup_menu = gtk_notebook_popup_menu; widget_class->grab_notify = gtk_notebook_grab_notify; widget_class->state_flags_changed = gtk_notebook_state_flags_changed; - widget_class->focus = gtk_notebook_focus; widget_class->drag_begin = gtk_notebook_drag_begin; widget_class->drag_end = gtk_notebook_drag_end; widget_class->drag_motion = gtk_notebook_drag_motion; @@ -937,7 +933,6 @@ gtk_notebook_class_init (GtkNotebookClass *class) container_class->add = gtk_notebook_add; container_class->remove = gtk_notebook_remove; container_class->forall = gtk_notebook_forall; - container_class->set_focus_child = gtk_notebook_set_focus_child; container_class->child_type = gtk_notebook_child_type; class->switch_page = gtk_notebook_real_switch_page; @@ -1240,7 +1235,6 @@ gtk_notebook_init (GtkNotebook *notebook) GtkEventController *controller; GtkGesture *gesture; - gtk_widget_set_can_focus (GTK_WIDGET (notebook), TRUE); gtk_widget_set_has_surface (GTK_WIDGET (notebook), FALSE); notebook->priv = gtk_notebook_get_instance_private (notebook); @@ -1258,7 +1252,6 @@ gtk_notebook_init (GtkNotebook *notebook) priv->scrollable = FALSE; priv->click_child = ARROW_NONE; priv->need_timer = 0; - priv->child_has_focus = FALSE; priv->focus_out = FALSE; priv->group = 0; @@ -1291,10 +1284,11 @@ gtk_notebook_init (GtkNotebook *notebook) gtk_widget_hide (priv->header_widget); gtk_container_add (GTK_CONTAINER (priv->box), priv->header_widget); - priv->tabs_widget = gtk_gizmo_new ("tabs", - gtk_notebook_measure_tabs, - gtk_notebook_allocate_tabs, - gtk_notebook_snapshot_tabs); + priv->tabs_widget = gtk_gizmo_new_with_focus ("tabs", + gtk_notebook_measure_tabs, + gtk_notebook_allocate_tabs, + gtk_notebook_snapshot_tabs, + gtk_notebook_next_focus_child_tabs); gtk_widget_set_hexpand (priv->tabs_widget, TRUE); gtk_container_add (GTK_CONTAINER (priv->header_widget), priv->tabs_widget); @@ -3215,13 +3209,7 @@ gtk_notebook_switch_tab_timeout (gpointer data) priv->switch_tab = NULL; if (switch_tab) - { - /* FIXME: hack, we don't want the - * focus to move fom the source widget - */ - priv->child_has_focus = FALSE; - gtk_notebook_switch_focus_tab (notebook, switch_tab); - } + gtk_notebook_switch_focus_tab (notebook, switch_tab); return FALSE; } @@ -3480,8 +3468,6 @@ gtk_notebook_drag_data_received (GtkWidget *widget, * * gtk_notebook_add * gtk_notebook_remove - * gtk_notebook_focus - * gtk_notebook_set_focus_child * gtk_notebook_child_type * gtk_notebook_forall */ @@ -3546,7 +3532,6 @@ focus_tabs_in (GtkNotebook *notebook) if (priv->show_tabs && gtk_notebook_has_current_page (notebook)) { gtk_widget_grab_focus (GTK_WIDGET (notebook)); - gtk_notebook_set_focus_child (GTK_CONTAINER (notebook), NULL); gtk_notebook_switch_focus_tab (notebook, g_list_find (priv->children, priv->cur_page)); @@ -3557,30 +3542,6 @@ focus_tabs_in (GtkNotebook *notebook) return FALSE; } -static gboolean -focus_tabs_move (GtkNotebook *notebook, - GtkDirectionType direction, - gint search_direction) -{ - GtkNotebookPrivate *priv = notebook->priv; - GList *new_page; - - new_page = gtk_notebook_search_page (notebook, priv->focus_tab, - search_direction, TRUE); - if (!new_page) - { - new_page = gtk_notebook_search_page (notebook, NULL, - search_direction, TRUE); - } - - if (new_page) - gtk_notebook_switch_focus_tab (notebook, new_page); - else - gtk_widget_error_bell (GTK_WIDGET (notebook)); - - return TRUE; -} - static gboolean focus_child_in (GtkNotebook *notebook, GtkDirectionType direction) @@ -3593,269 +3554,6 @@ focus_child_in (GtkNotebook *notebook, return FALSE; } -static gboolean -focus_action_in (GtkNotebook *notebook, - gint action, - GtkDirectionType direction) -{ - GtkNotebookPrivate *priv = notebook->priv; - - if (priv->action_widget[action] && - gtk_widget_get_visible (priv->action_widget[action])) - return gtk_widget_child_focus (priv->action_widget[action], direction); - else - return FALSE; -} - -/* Focus in the notebook can either be on the pages, or on - * the tabs or on the action_widgets. - */ -static gboolean -gtk_notebook_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - GtkNotebook *notebook = GTK_NOTEBOOK (widget); - GtkNotebookPrivate *priv = notebook->priv; - GtkWidget *old_focus_child; - GtkDirectionType effective_direction; - gint first_action; - gint last_action; - - gboolean widget_is_focus; - - if (priv->tab_pos == GTK_POS_TOP || - priv->tab_pos == GTK_POS_LEFT) - { - first_action = ACTION_WIDGET_START; - last_action = ACTION_WIDGET_END; - } - else - { - first_action = ACTION_WIDGET_END; - last_action = ACTION_WIDGET_START; - } - - if (priv->focus_out) - { - priv->focus_out = FALSE; /* Clear this to catch the wrap-around case */ - return FALSE; - } - - widget_is_focus = gtk_widget_is_focus (widget); - old_focus_child = gtk_widget_get_focus_child (widget); - - effective_direction = get_effective_direction (notebook, direction); - - if (old_focus_child) /* Focus on page child or action widget */ - { - if (gtk_widget_child_focus (old_focus_child, direction)) - return TRUE; - - if (old_focus_child == priv->action_widget[ACTION_WIDGET_START]) - { - switch ((guint) effective_direction) - { - case GTK_DIR_DOWN: - return focus_child_in (notebook, GTK_DIR_TAB_FORWARD); - case GTK_DIR_RIGHT: - return focus_tabs_in (notebook); - case GTK_DIR_LEFT: - return FALSE; - case GTK_DIR_UP: - return FALSE; - default: - switch ((guint) direction) - { - case GTK_DIR_TAB_FORWARD: - if ((priv->tab_pos == GTK_POS_RIGHT || priv->tab_pos == GTK_POS_BOTTOM) && - focus_child_in (notebook, direction)) - return TRUE; - return focus_tabs_in (notebook); - case GTK_DIR_TAB_BACKWARD: - return FALSE; - default: - g_assert_not_reached (); - break; - } - } - } - else if (old_focus_child == priv->action_widget[ACTION_WIDGET_END]) - { - switch ((guint) effective_direction) - { - case GTK_DIR_DOWN: - return focus_child_in (notebook, GTK_DIR_TAB_FORWARD); - case GTK_DIR_RIGHT: - return FALSE; - case GTK_DIR_LEFT: - return focus_tabs_in (notebook); - case GTK_DIR_UP: - return FALSE; - default: - switch ((guint) direction) - { - case GTK_DIR_TAB_FORWARD: - return FALSE; - case GTK_DIR_TAB_BACKWARD: - if ((priv->tab_pos == GTK_POS_TOP || priv->tab_pos == GTK_POS_LEFT) && - focus_child_in (notebook, direction)) - return TRUE; - return focus_tabs_in (notebook); - default: - g_assert_not_reached (); - break; - } - } - } - else - { - switch ((guint) effective_direction) - { - case GTK_DIR_TAB_BACKWARD: - case GTK_DIR_UP: - /* Focus onto the tabs */ - return focus_tabs_in (notebook); - case GTK_DIR_DOWN: - case GTK_DIR_LEFT: - case GTK_DIR_RIGHT: - return FALSE; - case GTK_DIR_TAB_FORWARD: - return focus_action_in (notebook, last_action, direction); - default: - break; - } - } - } - else if (widget_is_focus) /* Focus was on tabs */ - { - switch ((guint) effective_direction) - { - case GTK_DIR_TAB_BACKWARD: - return focus_action_in (notebook, first_action, direction); - case GTK_DIR_UP: - return FALSE; - case GTK_DIR_TAB_FORWARD: - if (focus_child_in (notebook, GTK_DIR_TAB_FORWARD)) - return TRUE; - return focus_action_in (notebook, last_action, direction); - case GTK_DIR_DOWN: - /* We use TAB_FORWARD rather than direction so that we focus a more - * predictable widget for the user; users may be using arrow focusing - * in this situation even if they don't usually use arrow focusing. - */ - return focus_child_in (notebook, GTK_DIR_TAB_FORWARD); - case GTK_DIR_LEFT: - return focus_tabs_move (notebook, direction, STEP_PREV); - case GTK_DIR_RIGHT: - return focus_tabs_move (notebook, direction, STEP_NEXT); - default: - break; - } - } - else /* Focus was not on widget */ - { - switch ((guint) effective_direction) - { - case GTK_DIR_TAB_FORWARD: - case GTK_DIR_DOWN: - if (focus_action_in (notebook, first_action, direction)) - return TRUE; - if (focus_tabs_in (notebook)) - return TRUE; - if (focus_action_in (notebook, last_action, direction)) - return TRUE; - if (focus_child_in (notebook, direction)) - return TRUE; - return FALSE; - case GTK_DIR_TAB_BACKWARD: - if (focus_action_in (notebook, last_action, direction)) - return TRUE; - if (focus_child_in (notebook, direction)) - return TRUE; - if (focus_tabs_in (notebook)) - return TRUE; - if (focus_action_in (notebook, first_action, direction)) - return TRUE; - case GTK_DIR_UP: - case GTK_DIR_LEFT: - case GTK_DIR_RIGHT: - return focus_child_in (notebook, direction); - default: - break; - } - } - - g_assert_not_reached (); - return FALSE; -} - -static void -gtk_notebook_set_focus_child (GtkContainer *container, - GtkWidget *child) -{ - GtkNotebook *notebook = GTK_NOTEBOOK (container); - GtkNotebookPrivate *priv = notebook->priv; - GtkWidget *page_child; - GtkWidget *toplevel; - - /* If the old focus widget was within a page of the notebook, - * (child may either be NULL or not in this case), record it - * for future use if we switch to the page with a mnemonic. - */ - - toplevel = gtk_widget_get_toplevel (GTK_WIDGET (container)); - if (toplevel && gtk_widget_is_toplevel (toplevel)) - { - page_child = gtk_window_get_focus (GTK_WINDOW (toplevel)); - while (page_child) - { - if (gtk_widget_get_parent (page_child) == GTK_WIDGET (container)) - { - GList *list = gtk_notebook_find_child (notebook, page_child); - if (list != NULL) - { - GtkNotebookPage *page = list->data; - - if (page->last_focus_child) - g_object_remove_weak_pointer (G_OBJECT (page->last_focus_child), (gpointer *)&page->last_focus_child); - - page->last_focus_child = gtk_window_get_focus (GTK_WINDOW (toplevel)); - g_object_add_weak_pointer (G_OBJECT (page->last_focus_child), (gpointer *)&page->last_focus_child); - - break; - } - } - - page_child = gtk_widget_get_parent (page_child); - } - } - - if (child) - { - g_return_if_fail (GTK_IS_WIDGET (child)); - - priv->child_has_focus = TRUE; - if (!priv->focus_tab) - { - GList *children; - GtkNotebookPage *page; - - children = priv->children; - while (children) - { - page = children->data; - if (page->child == child || page->tab_label == child) - gtk_notebook_switch_focus_tab (notebook, children); - children = children->next; - } - } - } - else - priv->child_has_focus = FALSE; - - GTK_CONTAINER_CLASS (gtk_notebook_parent_class)->set_focus_child (container, child); -} - static void gtk_notebook_forall (GtkContainer *container, GtkCallback callback, @@ -4305,12 +4003,6 @@ gtk_notebook_real_remove (GtkNotebook *notebook, g_list_free (list); - if (page->last_focus_child) - { - g_object_remove_weak_pointer (G_OBJECT (page->last_focus_child), (gpointer *)&page->last_focus_child); - page->last_focus_child = NULL; - } - gtk_widget_unparent (page->tab_widget); g_object_unref (page); @@ -4550,6 +4242,27 @@ gtk_notebook_snapshot_tabs (GtkGizmo *gizmo, gtk_widget_snapshot_child (GTK_WIDGET (gizmo), priv->cur_page->tab_widget, snapshot); } +static GtkWidget * +gtk_notebook_next_focus_child_tabs (GtkGizmo *gizmo, + GtkWidget *child, + GtkDirectionType direction) +{ + GtkWidget *widget = gtk_widget_get_parent (gtk_widget_get_parent (GTK_WIDGET (gizmo))); + GtkNotebook *notebook = GTK_NOTEBOOK (gtk_widget_get_parent (widget)); + GtkNotebookPrivate *priv = notebook->priv; + + if (direction == GTK_DIR_TAB_FORWARD || + direction == GTK_DIR_TAB_BACKWARD) + { + if (child == NULL) + return priv->cur_page->tab_widget; + else + return NULL; + } + + return gtk_widget_next_focus_child (GTK_WIDGET (gizmo), child, direction); +} + /* Private GtkNotebook Size Allocate Functions: * * gtk_notebook_calculate_shown_tabs @@ -5405,6 +5118,25 @@ gtk_notebook_calc_tabs (GtkNotebook *notebook, } } +static GtkWidget * +find_last_focus (GtkWidget *widget) +{ + GtkWidget *f = widget; + + while (f) + { + GtkWidget *focus_child = gtk_widget_get_focus_child (f); + if (focus_child == NULL) + break; + f = focus_child; + } + + if (f != widget) + return f; + + return NULL; +} + /* Private GtkNotebook Page Switch Methods: * * gtk_notebook_real_switch_page @@ -5417,16 +5149,19 @@ gtk_notebook_real_switch_page (GtkNotebook *notebook, GtkNotebookPrivate *priv = notebook->priv; GList *list = gtk_notebook_find_child (notebook, GTK_WIDGET (child)); GtkNotebookPage *page = GTK_NOTEBOOK_PAGE_FROM_LIST (list); - gboolean child_has_focus; + gboolean child_has_focus = FALSE; if (priv->cur_page == page || !gtk_widget_get_visible (GTK_WIDGET (child))) return; - /* save the value here, changing visibility changes focus */ - child_has_focus = priv->child_has_focus; - if (priv->cur_page) - gtk_widget_unset_state_flags (priv->cur_page->tab_widget, GTK_STATE_FLAG_CHECKED); + { + GtkRoot *root = gtk_widget_get_root (GTK_WIDGET (notebook)); + GtkWidget *focus = gtk_root_get_focus (root); + if (focus) + child_has_focus = gtk_widget_is_ancestor (focus, priv->cur_page->child); + gtk_widget_unset_state_flags (priv->cur_page->tab_widget, GTK_STATE_FLAG_CHECKED); + } priv->cur_page = page; gtk_widget_set_state_flags (page->tab_widget, GTK_STATE_FLAG_CHECKED, FALSE); @@ -5446,12 +5181,11 @@ gtk_notebook_real_switch_page (GtkNotebook *notebook, */ if (child_has_focus) { - if (priv->cur_page->last_focus_child && - gtk_widget_is_ancestor (priv->cur_page->last_focus_child, priv->cur_page->child)) - gtk_widget_grab_focus (priv->cur_page->last_focus_child); - else - if (!gtk_widget_child_focus (priv->cur_page->child, GTK_DIR_TAB_FORWARD)) - gtk_widget_grab_focus (GTK_WIDGET (notebook)); + GtkWidget *last_focus = find_last_focus (priv->cur_page->child); + if (last_focus) + gtk_widget_grab_focus (last_focus); + else if (!gtk_widget_child_focus (priv->cur_page->child, GTK_DIR_TAB_FORWARD)) + gtk_widget_grab_focus (GTK_WIDGET (notebook)); } update_arrow_state (notebook); @@ -7331,4 +7065,3 @@ gtk_notebook_get_pages (GtkNotebook *notebook) return priv->pages; } - diff --git a/gtk/gtkpaned.c b/gtk/gtkpaned.c index 9fc18c9667dc9718ac6d0d9b7b3175b2fa8eebf9..24508b2c7e4e5e37d8c79bcbb12d44ec254bbb7a 100644 --- a/gtk/gtkpaned.c +++ b/gtk/gtkpaned.c @@ -132,8 +132,6 @@ typedef struct GtkPaned *first_paned; GtkWidget *child1; GtkWidget *child2; - GtkWidget *last_child1_focus; - GtkWidget *last_child2_focus; GtkWidget *saved_focus; GtkOrientation orientation; @@ -218,8 +216,6 @@ static void gtk_paned_size_allocate (GtkWidget *widget, int height, int baseline); static void gtk_paned_unrealize (GtkWidget *widget); -static gboolean gtk_paned_focus (GtkWidget *widget, - GtkDirectionType direction); static void gtk_paned_add (GtkContainer *container, GtkWidget *widget); static void gtk_paned_remove (GtkContainer *container, @@ -231,16 +227,10 @@ static void gtk_paned_calc_position (GtkPaned *paned, gint allocation, gint child1_req, gint child2_req); -static void gtk_paned_set_focus_child (GtkContainer *container, - GtkWidget *child); static void gtk_paned_set_saved_focus (GtkPaned *paned, GtkWidget *widget); static void gtk_paned_set_first_paned (GtkPaned *paned, GtkPaned *first_paned); -static void gtk_paned_set_last_child1_focus (GtkPaned *paned, - GtkWidget *widget); -static void gtk_paned_set_last_child2_focus (GtkPaned *paned, - GtkWidget *widget); static gboolean gtk_paned_cycle_child_focus (GtkPaned *paned, gboolean reverse); static gboolean gtk_paned_cycle_handle_focus (GtkPaned *paned, @@ -365,14 +355,12 @@ gtk_paned_class_init (GtkPanedClass *class) widget_class->measure = gtk_paned_measure; widget_class->size_allocate = gtk_paned_size_allocate; widget_class->unrealize = gtk_paned_unrealize; - widget_class->focus = gtk_paned_focus; widget_class->pick = gtk_paned_pick; container_class->add = gtk_paned_add; container_class->remove = gtk_paned_remove; container_class->forall = gtk_paned_forall; container_class->child_type = gtk_paned_child_type; - container_class->set_focus_child = gtk_paned_set_focus_child; container_class->set_child_property = gtk_paned_set_child_property; container_class->get_child_property = gtk_paned_get_child_property; @@ -1353,8 +1341,6 @@ gtk_paned_unrealize (GtkWidget *widget) { GtkPaned *paned = GTK_PANED (widget); - gtk_paned_set_last_child1_focus (paned, NULL); - gtk_paned_set_last_child2_focus (paned, NULL); gtk_paned_set_saved_focus (paned, NULL); gtk_paned_set_first_paned (paned, NULL); @@ -1410,8 +1396,6 @@ gtk_paned_init (GtkPaned *paned) priv->position_set = FALSE; priv->last_allocation = -1; - priv->last_child1_focus = NULL; - priv->last_child2_focus = NULL; priv->in_recursion = FALSE; priv->original_position = -1; priv->max_position = G_MAXINT; @@ -1498,24 +1482,6 @@ update_drag (GtkPaned *paned, gtk_paned_set_position (paned, size); } -static gboolean -gtk_paned_focus (GtkWidget *widget, - GtkDirectionType direction) - -{ - gboolean retval; - - /* This is a hack, but how can this be done without - * excessive cut-and-paste from gtkcontainer.c? - */ - - gtk_widget_set_can_focus (widget, FALSE); - retval = GTK_WIDGET_CLASS (gtk_paned_parent_class)->focus (widget, direction); - gtk_widget_set_can_focus (widget, TRUE); - - return retval; -} - /** * gtk_paned_new: * @orientation: the paned’s orientation. @@ -1860,86 +1826,23 @@ gtk_paned_set_first_paned (GtkPaned *paned, GtkPaned *first_paned) (gpointer *)&(priv->first_paned)); } -static void -gtk_paned_set_last_child1_focus (GtkPaned *paned, GtkWidget *widget) -{ - GtkPanedPrivate *priv = gtk_paned_get_instance_private (paned); - - if (priv->last_child1_focus) - g_object_remove_weak_pointer (G_OBJECT (priv->last_child1_focus), - (gpointer *)&(priv->last_child1_focus)); - - priv->last_child1_focus = widget; - - if (priv->last_child1_focus) - g_object_add_weak_pointer (G_OBJECT (priv->last_child1_focus), - (gpointer *)&(priv->last_child1_focus)); -} - -static void -gtk_paned_set_last_child2_focus (GtkPaned *paned, GtkWidget *widget) -{ - GtkPanedPrivate *priv = gtk_paned_get_instance_private (paned); - - if (priv->last_child2_focus) - g_object_remove_weak_pointer (G_OBJECT (priv->last_child2_focus), - (gpointer *)&(priv->last_child2_focus)); - - priv->last_child2_focus = widget; - - if (priv->last_child2_focus) - g_object_add_weak_pointer (G_OBJECT (priv->last_child2_focus), - (gpointer *)&(priv->last_child2_focus)); -} - static GtkWidget * -paned_get_focus_widget (GtkPaned *paned) +find_last_focus (GtkWidget *widget) { - GtkWidget *toplevel; - - toplevel = gtk_widget_get_toplevel (GTK_WIDGET (paned)); - if (gtk_widget_is_toplevel (toplevel)) - return gtk_window_get_focus (GTK_WINDOW (toplevel)); + GtkWidget *f = widget; - return NULL; -} - -static void -gtk_paned_set_focus_child (GtkContainer *container, - GtkWidget *focus_child) -{ - GtkPaned *paned = GTK_PANED (container); - GtkPanedPrivate *priv = gtk_paned_get_instance_private (paned); - GtkWidget *container_focus_child; - - g_return_if_fail (GTK_IS_PANED (container)); - - if (focus_child == NULL) + while (f) { - GtkWidget *last_focus; - GtkWidget *w; - - last_focus = paned_get_focus_widget (paned); - - if (last_focus) - { - /* If there is one or more paned widgets between us and the - * focus widget, we want the topmost of those as last_focus - */ - for (w = last_focus; w != GTK_WIDGET (paned); w = gtk_widget_get_parent (w)) - if (GTK_IS_PANED (w)) - last_focus = w; - - container_focus_child = gtk_widget_get_focus_child (GTK_WIDGET (container)); - if (container_focus_child == priv->child1) - gtk_paned_set_last_child1_focus (paned, last_focus); - else if (container_focus_child == priv->child2) - gtk_paned_set_last_child2_focus (paned, last_focus); - } + GtkWidget *focus_child = gtk_widget_get_focus_child (f); + if (focus_child == NULL) + break; + f = focus_child; } - if (GTK_CONTAINER_CLASS (gtk_paned_parent_class)->set_focus_child) - GTK_CONTAINER_CLASS (gtk_paned_parent_class)->set_focus_child (container, focus_child); + if (f != widget) + return f; + + return NULL; } static void @@ -1954,24 +1857,14 @@ gtk_paned_get_cycle_chain (GtkPaned *paned, GtkWidget *widget = GTK_WIDGET (paned); GList *temp_list = NULL; GList *list; + GtkWidget *last_child1_focus; + GtkWidget *last_child2_focus; if (priv->in_recursion) return; g_assert (widgets != NULL); - if (priv->last_child1_focus && - !gtk_widget_is_ancestor (priv->last_child1_focus, widget)) - { - gtk_paned_set_last_child1_focus (paned, NULL); - } - - if (priv->last_child2_focus && - !gtk_widget_is_ancestor (priv->last_child2_focus, widget)) - { - gtk_paned_set_last_child2_focus (paned, NULL); - } - parent = gtk_widget_get_parent (widget); if (parent) ancestor = gtk_widget_get_ancestor (parent, GTK_TYPE_PANED); @@ -1984,26 +1877,28 @@ gtk_paned_get_cycle_chain (GtkPaned *paned, * priv->last_child?_focus before priv->child?, both when we * are going forward and backward. */ + last_child1_focus = find_last_focus (priv->child1); + last_child2_focus = find_last_focus (priv->child2); focus_child = gtk_widget_get_focus_child (GTK_WIDGET (paned)); if (direction == GTK_DIR_TAB_FORWARD) { if (focus_child == priv->child1) { - temp_list = g_list_append (temp_list, priv->last_child2_focus); + temp_list = g_list_append (temp_list, last_child2_focus); temp_list = g_list_append (temp_list, priv->child2); temp_list = g_list_append (temp_list, ancestor); } else if (focus_child == priv->child2) { temp_list = g_list_append (temp_list, ancestor); - temp_list = g_list_append (temp_list, priv->last_child1_focus); + temp_list = g_list_append (temp_list, last_child1_focus); temp_list = g_list_append (temp_list, priv->child1); } else { - temp_list = g_list_append (temp_list, priv->last_child1_focus); + temp_list = g_list_append (temp_list, last_child1_focus); temp_list = g_list_append (temp_list, priv->child1); - temp_list = g_list_append (temp_list, priv->last_child2_focus); + temp_list = g_list_append (temp_list, last_child2_focus); temp_list = g_list_append (temp_list, priv->child2); temp_list = g_list_append (temp_list, ancestor); } @@ -2013,20 +1908,20 @@ gtk_paned_get_cycle_chain (GtkPaned *paned, if (focus_child == priv->child1) { temp_list = g_list_append (temp_list, ancestor); - temp_list = g_list_append (temp_list, priv->last_child2_focus); + temp_list = g_list_append (temp_list, last_child2_focus); temp_list = g_list_append (temp_list, priv->child2); } else if (focus_child == priv->child2) { - temp_list = g_list_append (temp_list, priv->last_child1_focus); + temp_list = g_list_append (temp_list, last_child1_focus); temp_list = g_list_append (temp_list, priv->child1); temp_list = g_list_append (temp_list, ancestor); } else { - temp_list = g_list_append (temp_list, priv->last_child2_focus); + temp_list = g_list_append (temp_list, last_child2_focus); temp_list = g_list_append (temp_list, priv->child2); - temp_list = g_list_append (temp_list, priv->last_child1_focus); + temp_list = g_list_append (temp_list, last_child1_focus); temp_list = g_list_append (temp_list, priv->child1); temp_list = g_list_append (temp_list, ancestor); } @@ -2256,10 +2151,8 @@ gtk_paned_restore_focus (GtkPaned *paned) if (!gtk_widget_child_focus (GTK_WIDGET (paned), GTK_DIR_TAB_FORWARD)) { - GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (paned)); - - if (GTK_IS_WINDOW (toplevel)) - gtk_window_set_focus (GTK_WINDOW (toplevel), NULL); + GtkRoot *root = gtk_widget_get_root (GTK_WIDGET (paned)); + gtk_root_set_focus (root, NULL); } } @@ -2364,7 +2257,6 @@ gtk_paned_cycle_handle_focus (GtkPaned *paned, { GtkPaned *focus; GtkPaned *first; - GtkWidget *toplevel; GtkWidget *focus_child; gtk_paned_find_neighbours (paned, &next, &prev); @@ -2410,10 +2302,7 @@ gtk_paned_cycle_handle_focus (GtkPaned *paned, first = next; } - toplevel = gtk_widget_get_toplevel (GTK_WIDGET (paned)); - - if (GTK_IS_WINDOW (toplevel)) - gtk_paned_set_saved_focus (focus, gtk_window_get_focus (GTK_WINDOW (toplevel))); + gtk_paned_set_saved_focus (focus, gtk_root_get_focus (gtk_widget_get_root (GTK_WIDGET (paned)))); gtk_paned_set_first_paned (focus, first); priv->original_position = gtk_paned_get_position (focus); diff --git a/gtk/gtkplacesview.c b/gtk/gtkplacesview.c index bb03ef9d01d3404d393a72bd94a21b6aa62e9eea..dabb48604647277552b75966eb1c26f327c3386e 100644 --- a/gtk/gtkplacesview.c +++ b/gtk/gtkplacesview.c @@ -1766,7 +1766,7 @@ on_key_press_event (GtkEventController *controller, if (!toplevel) return FALSE; - focus_widget = gtk_window_get_focus (toplevel); + focus_widget = gtk_root_get_focus (GTK_ROOT (toplevel)); if (!GTK_IS_PLACES_VIEW_ROW (focus_widget)) return FALSE; diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c index cfd697a234fbf26b5ab5defd1f766230644f8f06..7370b4951f57abae333e1aeb8281d1c29e373c56 100644 --- a/gtk/gtkpopover.c +++ b/gtk/gtkpopover.c @@ -614,10 +614,11 @@ window_active_changed (GtkWindow *window, static void window_set_focus (GtkWindow *window, - GtkWidget *widget, + GParamSpec *pspec, GtkPopover *popover) { GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); + GtkWidget *widget = gtk_root_get_focus (GTK_ROOT (window)); if (!priv->modal || !widget || !gtk_widget_is_drawable (GTK_WIDGET (popover))) return; @@ -673,7 +674,7 @@ gtk_popover_apply_modality (GtkPopover *popover, g_signal_connect (priv->window, "notify::is-active", G_CALLBACK (window_active_changed), popover); - g_signal_connect (priv->window, "set-focus", + g_signal_connect (priv->window, "notify::focus-widget", G_CALLBACK (window_set_focus), popover); } else @@ -1455,39 +1456,6 @@ gtk_popover_grab_focus (GtkWidget *widget) gtk_widget_child_focus (child, GTK_DIR_TAB_FORWARD); } -static gboolean -gtk_popover_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - GtkPopover *popover = GTK_POPOVER (widget); - GtkPopoverPrivate *priv = gtk_popover_get_instance_private (popover); - - if (!priv->visible) - return FALSE; - - if (!GTK_WIDGET_CLASS (gtk_popover_parent_class)->focus (widget, direction)) - { - GtkWidget *focus; - - focus = gtk_window_get_focus (priv->window); - focus = gtk_widget_get_parent (focus); - - /* Unset focus child through children, so it is next stepped from - * scratch. - */ - while (focus && focus != widget) - { - gtk_widget_set_focus_child (focus, NULL); - focus = gtk_widget_get_parent (focus); - } - - return gtk_widget_child_focus (gtk_bin_get_child (GTK_BIN (widget)), - direction); - } - - return TRUE; -} - static void gtk_popover_show (GtkWidget *widget) { @@ -1582,7 +1550,6 @@ gtk_popover_class_init (GtkPopoverClass *klass) widget_class->size_allocate = gtk_popover_size_allocate; widget_class->snapshot = gtk_popover_snapshot; widget_class->grab_focus = gtk_popover_grab_focus; - widget_class->focus = gtk_popover_focus; widget_class->show = gtk_popover_show; widget_class->hide = gtk_popover_hide; widget_class->state_flags_changed = gtk_popover_state_flags_changed; diff --git a/gtk/gtkradiobutton.c b/gtk/gtkradiobutton.c index e87c0d7d496686383c1bd67461a23543b76c7cdb..64bda0def9161433e2cc8522c8209a1595098452 100644 --- a/gtk/gtkradiobutton.c +++ b/gtk/gtkradiobutton.c @@ -149,8 +149,6 @@ static GParamSpec *radio_button_props[LAST_PROP] = { NULL, }; static guint signals[N_SIGNALS] = { 0 }; static void gtk_radio_button_destroy (GtkWidget *widget); -static gboolean gtk_radio_button_focus (GtkWidget *widget, - GtkDirectionType direction); static void gtk_radio_button_clicked (GtkButton *button); static void gtk_radio_button_set_property (GObject *object, guint prop_id, @@ -192,7 +190,6 @@ gtk_radio_button_class_init (GtkRadioButtonClass *class) g_object_class_install_properties (gobject_class, LAST_PROP, radio_button_props); widget_class->destroy = gtk_radio_button_destroy; - widget_class->focus = gtk_radio_button_focus; button_class->clicked = gtk_radio_button_clicked; @@ -615,99 +612,6 @@ gtk_radio_button_destroy (GtkWidget *widget) GTK_WIDGET_CLASS (gtk_radio_button_parent_class)->destroy (widget); } -static gboolean -gtk_radio_button_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - GtkRadioButton *radio_button = GTK_RADIO_BUTTON (widget); - GtkRadioButtonPrivate *priv = gtk_radio_button_get_instance_private (radio_button); - GSList *tmp_slist; - - /* Radio buttons with draw_indicator unset focus "normally", since - * they look like buttons to the user. - */ - if (!gtk_check_button_get_draw_indicator (GTK_CHECK_BUTTON (widget))) - return GTK_WIDGET_CLASS (gtk_radio_button_parent_class)->focus (widget, direction); - - if (gtk_widget_is_focus (widget)) - { - GPtrArray *child_array; - GtkWidget *new_focus = NULL; - GSList *l; - guint index; - gboolean found; - guint i; - - if (direction == GTK_DIR_TAB_FORWARD || - direction == GTK_DIR_TAB_BACKWARD) - return FALSE; - - child_array = g_ptr_array_sized_new (g_slist_length (priv->group)); - for (l = priv->group; l; l = l->next) - g_ptr_array_add (child_array, l->data); - - gtk_widget_focus_sort (widget, direction, child_array); - found = g_ptr_array_find (child_array, widget, &index); - - if (found) - { - /* Start at the *next* widget in the list */ - if (index < child_array->len - 1) - index ++; - } - else - { - /* Search from the start of the list */ - index = 0; - } - - for (i = index; i < child_array->len; i ++) - { - GtkWidget *child = g_ptr_array_index (child_array, i); - - if (gtk_widget_get_mapped (child) && gtk_widget_is_sensitive (child)) - { - new_focus = child; - break; - } - } - - - if (new_focus) - { - gtk_widget_grab_focus (new_focus); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (new_focus), TRUE); - } - - g_ptr_array_free (child_array, TRUE); - - return TRUE; - } - else - { - GtkRadioButton *selected_button = NULL; - - /* We accept the focus if, we don't have the focus and - * - we are the currently active button in the group - * - there is no currently active radio button. - */ - tmp_slist = priv->group; - while (tmp_slist) - { - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (tmp_slist->data)) && - gtk_widget_get_visible (tmp_slist->data)) - selected_button = tmp_slist->data; - tmp_slist = tmp_slist->next; - } - - if (selected_button && selected_button != radio_button) - return FALSE; - - gtk_widget_grab_focus (widget); - return TRUE; - } -} - static void gtk_radio_button_clicked (GtkButton *button) { diff --git a/gtk/gtkroot.c b/gtk/gtkroot.c index ad93f8791a170184d9c7ae95e786a216f9aa1822..3ed7812856e500a625965d346b9d08017617b3a0 100644 --- a/gtk/gtkroot.c +++ b/gtk/gtkroot.c @@ -21,6 +21,8 @@ #include "gtkrootprivate.h" #include "gdk/gdk-private.h" +#include "gtkprivate.h" +#include "gtkintl.h" /** * SECTION:gtkroot @@ -65,6 +67,13 @@ gtk_root_default_init (GtkRootInterface *iface) iface->get_display = gtk_root_default_get_display; iface->get_renderer = gtk_root_default_get_renderer; iface->get_surface_transform = gtk_root_default_get_surface_transform; + + g_object_interface_install_property (iface, + g_param_spec_object ("focus-widget", + P_("Focus widget"), + P_("The focus widget"), + GTK_TYPE_WIDGET, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); } GdkDisplay * @@ -124,3 +133,64 @@ gtk_root_get_for_surface (GdkSurface *surface) return NULL; } + +/** + * gtk_root_set_focus: + * @self: a #GtkRoot + * @focus: (allow-none): widget to be the new focus widget, or %NULL + * to unset the focus widget + * + * If @focus is not the current focus widget, and is focusable, sets + * it as the focus widget for the root. If @focus is %NULL, unsets + * the focus widget for the root. + * + * To set the focus to a particular widget in the root, it is usually + * more convenient to use gtk_widget_grab_focus() instead of this function. + */ +void +gtk_root_set_focus (GtkRoot *self, + GtkWidget *focus) +{ + g_return_if_fail (GTK_IS_ROOT (self)); + g_return_if_fail (focus == NULL || + (GTK_IS_WIDGET (focus) && gtk_widget_get_root (focus) == self)); + + g_object_set (self, "focus-widget", focus, NULL); +} + +/** + * gtk_root_get_focus: + * @self: a #GtkRoot + * + * Retrieves the current focused widget within the root. + * + * Note that this is the widget that would have the focus + * if the root is active; if the root is not focused then + * `gtk_widget_has_focus (widget)` will be %FALSE for the + * widget. + * + * Returns: (nullable) (transfer none): the currently focused widget, + * or %NULL if there is none. + */ +GtkWidget * +gtk_root_get_focus (GtkRoot *self) +{ + GtkWidget *focus; + + g_return_val_if_fail (GTK_IS_ROOT (self), NULL); + + g_object_get (self, "focus-widget", &focus, NULL); + + if (focus) + g_object_unref (focus); + + return focus; +} + +guint +gtk_root_install_properties (GObjectClass *object_class, + guint first_prop) +{ + g_object_class_override_property (object_class, first_prop + GTK_ROOT_PROP_FOCUS_WIDGET, "focus-widget"); + return GTK_ROOT_NUM_PROPERTIES; +} diff --git a/gtk/gtkroot.h b/gtk/gtkroot.h index 45e777ba684e5d9ff755a09dc57539cdad998d47..4c7ae89e37766d6f56f82758bccbbf923737c5c3 100644 --- a/gtk/gtkroot.h +++ b/gtk/gtkroot.h @@ -56,6 +56,12 @@ struct _GtkRootInterface GDK_AVAILABLE_IN_ALL GtkWidget * gtk_root_get_for_surface (GdkSurface *surface); +GDK_AVAILABLE_IN_ALL +void gtk_root_set_focus (GtkRoot *self, + GtkWidget *focus); +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_root_get_focus (GtkRoot *self); + G_END_DECLS #endif /* __GTK_ROOT_H__ */ diff --git a/gtk/gtkrootprivate.h b/gtk/gtkrootprivate.h index 07ddc380e54ff1f83d6953dc5cf590d33627b722..357bc6441f441de3d1a205455363ace9fd0dfd28 100644 --- a/gtk/gtkrootprivate.h +++ b/gtk/gtkrootprivate.h @@ -11,6 +11,14 @@ GskRenderer * gtk_root_get_renderer (GtkRoot void gtk_root_get_surface_transform (GtkRoot *self, int *x, int *y); +enum { + GTK_ROOT_PROP_FOCUS_WIDGET, + GTK_ROOT_NUM_PROPERTIES +} GtkRootProperties; + +guint gtk_root_install_properties (GObjectClass *object_class, + guint first_prop); + G_END_DECLS #endif /* __GTK_ROOT_PRIVATE_H__ */ diff --git a/gtk/gtkscrolledwindow.c b/gtk/gtkscrolledwindow.c index ea1b402c4b5c1129aa7b31efe66ad0704a9a70b6..b6cc8c3179e193d45979883cae50d28030a5f085 100644 --- a/gtk/gtkscrolledwindow.c +++ b/gtk/gtkscrolledwindow.c @@ -322,8 +322,6 @@ static void gtk_scrolled_window_size_allocate (GtkWidget *widge int width, int height, int baseline); -static gboolean gtk_scrolled_window_focus (GtkWidget *widget, - GtkDirectionType direction); static void gtk_scrolled_window_add (GtkContainer *container, GtkWidget *widget); static void gtk_scrolled_window_remove (GtkContainer *container, @@ -507,7 +505,6 @@ gtk_scrolled_window_class_init (GtkScrolledWindowClass *class) widget_class->destroy = gtk_scrolled_window_destroy; widget_class->snapshot = gtk_scrolled_window_snapshot; widget_class->size_allocate = gtk_scrolled_window_size_allocate; - widget_class->focus = gtk_scrolled_window_focus; widget_class->measure = gtk_scrolled_window_measure; widget_class->map = gtk_scrolled_window_map; widget_class->unmap = gtk_scrolled_window_unmap; @@ -3330,45 +3327,6 @@ gtk_scrolled_window_start_deceleration (GtkScrolledWindow *scrolled_window) (GDestroyNotify) kinetic_scroll_data_free); } -static gboolean -gtk_scrolled_window_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget); - GtkScrolledWindowPrivate *priv = gtk_scrolled_window_get_instance_private (scrolled_window); - GtkWidget *child; - gboolean had_focus_child; - - had_focus_child = gtk_widget_get_focus_child (widget) != NULL; - - if (priv->focus_out) - { - priv->focus_out = FALSE; /* Clear this to catch the wrap-around case */ - return FALSE; - } - - if (gtk_widget_is_focus (widget)) - return FALSE; - - /* We only put the scrolled window itself in the focus chain if it - * isn't possible to focus any children. - */ - child = gtk_bin_get_child (GTK_BIN (widget)); - if (child) - { - if (gtk_widget_child_focus (child, direction)) - return TRUE; - } - - if (!had_focus_child && gtk_widget_get_can_focus (widget)) - { - gtk_widget_grab_focus (widget); - return TRUE; - } - else - return FALSE; -} - static void gtk_scrolled_window_adjustment_changed (GtkAdjustment *adjustment, gpointer data) diff --git a/gtk/gtksearchentry.c b/gtk/gtksearchentry.c index fd6dfed05fba35d32f11ccdcb6658c7099ce0840..b9495241a5fa10103f23436eee3efb0b22126916 100644 --- a/gtk/gtksearchentry.c +++ b/gtk/gtksearchentry.c @@ -255,7 +255,7 @@ gtk_search_entry_grab_focus (GtkWidget *widget) GtkSearchEntry *entry = GTK_SEARCH_ENTRY (widget); GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); - gtk_text_grab_focus_without_selecting (GTK_TEXT (priv->entry)); + gtk_widget_grab_focus (priv->entry); } static gboolean @@ -523,6 +523,7 @@ gtk_search_entry_init (GtkSearchEntry *entry) gtk_widget_set_vexpand (priv->box, FALSE); priv->entry = gtk_text_new (); + g_object_set (priv->entry, "select-on-focus", FALSE, NULL); gtk_widget_set_hexpand (priv->entry, TRUE); gtk_widget_set_vexpand (priv->entry, TRUE); gtk_container_add (GTK_CONTAINER (priv->box), GTK_WIDGET (priv->entry)); diff --git a/gtk/gtkspinbutton.c b/gtk/gtkspinbutton.c index 8e16faa8370fff50ab106a440fd0c0ef4fcdeba3..b273b2e30dde0374fa794b61526b5960243846e3 100644 --- a/gtk/gtkspinbutton.c +++ b/gtk/gtkspinbutton.c @@ -824,6 +824,8 @@ key_controller_key_released (GtkEventControllerKey *key, static void key_controller_focus_out (GtkEventControllerKey *key, + GdkCrossingMode mode, + GdkNotifyType detail, GtkSpinButton *spin_button) { GtkSpinButtonPrivate *priv = gtk_spin_button_get_instance_private (spin_button); diff --git a/gtk/gtkstack.c b/gtk/gtkstack.c index f9a7723448f3cf71c978bb89474b3b25195262eb..04b4b0b422546173ee6ccf96101f45016841ecd1 100644 --- a/gtk/gtkstack.c +++ b/gtk/gtkstack.c @@ -533,6 +533,9 @@ static void gtk_stack_forall (GtkContainer *contain static void gtk_stack_compute_expand (GtkWidget *widget, gboolean *hexpand, gboolean *vexpand); +static GtkWidget *gtk_stack_next_focus_child (GtkWidget *widget, + GtkWidget *child, + GtkDirectionType direction); static void gtk_stack_size_allocate (GtkWidget *widget, int width, int height, @@ -724,6 +727,7 @@ gtk_stack_class_init (GtkStackClass *klass) widget_class->snapshot = gtk_stack_snapshot; widget_class->measure = gtk_stack_measure; widget_class->compute_expand = gtk_stack_compute_expand; + widget_class->next_focus_child = gtk_stack_next_focus_child; container_class->add = gtk_stack_add; container_class->remove = gtk_stack_remove; @@ -1123,7 +1127,6 @@ set_visible_child (GtkStack *stack, GtkStackPage *info; GtkWidget *widget = GTK_WIDGET (stack); GList *l; - GtkWidget *toplevel; GtkWidget *focus; gboolean contains_focus = FALSE; guint old_pos = GTK_INVALID_LIST_POSITION; @@ -1165,24 +1168,23 @@ set_visible_child (GtkStack *stack, } } - toplevel = gtk_widget_get_toplevel (widget); - if (GTK_IS_WINDOW (toplevel)) + if (gtk_widget_get_root (widget)) + focus = gtk_root_get_focus (gtk_widget_get_root (widget)); + else + focus = NULL; + if (focus && + priv->visible_child && + priv->visible_child->widget && + gtk_widget_is_ancestor (focus, priv->visible_child->widget)) { - focus = gtk_window_get_focus (GTK_WINDOW (toplevel)); - if (focus && - priv->visible_child && - priv->visible_child->widget && - gtk_widget_is_ancestor (focus, priv->visible_child->widget)) - { - contains_focus = TRUE; - - if (priv->visible_child->last_focus) - g_object_remove_weak_pointer (G_OBJECT (priv->visible_child->last_focus), - (gpointer *)&priv->visible_child->last_focus); - priv->visible_child->last_focus = focus; - g_object_add_weak_pointer (G_OBJECT (priv->visible_child->last_focus), - (gpointer *)&priv->visible_child->last_focus); - } + contains_focus = TRUE; + + if (priv->visible_child->last_focus) + g_object_remove_weak_pointer (G_OBJECT (priv->visible_child->last_focus), + (gpointer *)&priv->visible_child->last_focus); + priv->visible_child->last_focus = focus; + g_object_add_weak_pointer (G_OBJECT (priv->visible_child->last_focus), + (gpointer *)&priv->visible_child->last_focus); } if (priv->last_visible_child) @@ -2436,6 +2438,19 @@ gtk_stack_measure (GtkWidget *widget, } } +static GtkWidget * +gtk_stack_next_focus_child (GtkWidget *widget, + GtkWidget *child, + GtkDirectionType direction) +{ + GtkStackPrivate *priv = gtk_stack_get_instance_private (GTK_STACK (widget)); + + if (child == NULL) + return priv->visible_child->widget; + + return NULL; +} + static void gtk_stack_init (GtkStack *stack) { diff --git a/gtk/gtktext.c b/gtk/gtktext.c index 2ee20ffce7748efd883881fdbb65c82293fc1561..b33889556ebd69f402d8a6ddd91c73cd8e774945 100644 --- a/gtk/gtktext.c +++ b/gtk/gtktext.c @@ -225,6 +225,8 @@ struct _GtkTextPrivate guint cursor_handle_dragged : 1; guint selection_handle_dragged : 1; guint populate_all : 1; + guint select_on_focus : 1; + guint select_on_focus_set : 1; }; struct _GtkTextPasswordHint @@ -268,6 +270,8 @@ enum { PROP_POPULATE_ALL, PROP_TABS, PROP_ENABLE_EMOJI_COMPLETION, + PROP_SELECT_ON_FOCUS, + PROP_SELECT_ON_FOCUS_SET, NUM_PROPERTIES }; @@ -321,7 +325,6 @@ static void gtk_text_snapshot (GtkWidget *widget, GtkSnapshot *snapshot); static void gtk_text_focus_in (GtkWidget *widget); static void gtk_text_focus_out (GtkWidget *widget); -static void gtk_text_grab_focus (GtkWidget *widget); static void gtk_text_style_updated (GtkWidget *widget); static void gtk_text_direction_changed (GtkWidget *widget, GtkTextDirection previous_dir); @@ -675,7 +678,6 @@ gtk_text_class_init (GtkTextClass *class) widget_class->measure = gtk_text_measure; widget_class->size_allocate = gtk_text_size_allocate; widget_class->snapshot = gtk_text_snapshot; - widget_class->grab_focus = gtk_text_grab_focus; widget_class->style_updated = gtk_text_style_updated; widget_class->drag_begin = gtk_text_drag_begin; widget_class->drag_end = gtk_text_drag_end; @@ -898,6 +900,26 @@ gtk_text_class_init (GtkTextClass *class) TRUE, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + /** + * GtkEntry::select-on-focus: + * + * Whether to select the contents of the text when focus enters it. + * When set, this property overrides the system-widget setting for + * this feature. See #GtkEntry::select-on-enter-set + */ + text_props[PROP_SELECT_ON_FOCUS] = + g_param_spec_boolean ("select-on-focus", + P_("Selet on focus"), + P_("Whether to select the text on focus"), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + text_props[PROP_SELECT_ON_FOCUS_SET] = + g_param_spec_boolean ("select-on-focus-set", + P_("Select on focus set"), + P_("Whether the select-on-focus property has been set"), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_properties (gobject_class, NUM_PROPERTIES, text_props); gtk_editable_install_properties (gobject_class, NUM_PROPERTIES); @@ -1515,6 +1537,27 @@ gtk_text_set_property (GObject *object, set_enable_emoji_completion (self, g_value_get_boolean (value)); break; + case PROP_SELECT_ON_FOCUS: + if (priv->select_on_focus != g_value_get_boolean (value)) + { + priv->select_on_focus = g_value_get_boolean (value); + g_object_notify_by_pspec (object, pspec); + } + if (!priv->select_on_focus_set) + { + priv->select_on_focus_set = TRUE; + g_object_notify_by_pspec (object, text_props[PROP_SELECT_ON_FOCUS_SET]); + } + break; + + case PROP_SELECT_ON_FOCUS_SET: + if (priv->select_on_focus_set != g_value_get_boolean (value)) + { + priv->select_on_focus_set = g_value_get_boolean (value); + g_object_notify_by_pspec (object, pspec); + } + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1630,6 +1673,14 @@ gtk_text_get_property (GObject *object, g_value_set_boolean (value, priv->enable_emoji_completion); break; + case PROP_SELECT_ON_FOCUS: + g_value_set_boolean (value, priv->select_on_focus); + break; + + case PROP_SELECT_ON_FOCUS_SET: + g_value_set_boolean (value, priv->select_on_focus_set); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2896,6 +2947,22 @@ gtk_text_focus_in (GtkWidget *widget) GtkTextPrivate *priv = gtk_text_get_instance_private (self); GdkKeymap *keymap; + if (priv->editable && !priv->in_click) + { + gboolean select_on_focus; + + if (priv->select_on_focus_set) + select_on_focus = priv->select_on_focus; + else + g_object_get (gtk_widget_get_settings (widget), + "gtk-entry-select-on-focus", + &select_on_focus, + NULL); + + if (select_on_focus) + gtk_text_set_selection_bounds (self, 0, -1); + } + gtk_widget_queue_draw (widget); keymap = gdk_display_get_keymap (gtk_widget_get_display (widget)); @@ -2941,47 +3008,6 @@ gtk_text_focus_out (GtkWidget *widget) g_signal_handlers_disconnect_by_func (keymap, keymap_direction_changed, self); } -static void -gtk_text_grab_focus (GtkWidget *widget) -{ - GtkText *self = GTK_TEXT (widget); - GtkTextPrivate *priv = gtk_text_get_instance_private (self); - gboolean select_on_focus; - - GTK_WIDGET_CLASS (gtk_text_parent_class)->grab_focus (GTK_WIDGET (self)); - - if (priv->editable && !priv->in_click) - { - g_object_get (gtk_widget_get_settings (widget), - "gtk-entry-select-on-focus", - &select_on_focus, - NULL); - - if (select_on_focus) - gtk_text_set_selection_bounds (self, 0, -1); - } -} - -/** - * gtk_text_grab_focus_without_selecting: - * @self: a #GtkText - * - * Causes @self to have keyboard focus. - * - * It behaves like gtk_widget_grab_focus(), - * except that it doesn't select the contents of the self. - * You only want to call this on some special entries - * which the user usually doesn't want to replace all text in, - * such as search-as-you-type entries. - */ -void -gtk_text_grab_focus_without_selecting (GtkText *self) -{ - g_return_if_fail (GTK_IS_TEXT (self)); - - GTK_WIDGET_CLASS (gtk_text_parent_class)->grab_focus (GTK_WIDGET (self)); -} - static void gtk_text_direction_changed (GtkWidget *widget, GtkTextDirection previous_dir) @@ -3827,7 +3853,7 @@ gtk_text_real_activate (GtkText *self) if (window) { default_widget = gtk_window_get_default_widget (window); - focus_widget = gtk_window_get_focus (window); + focus_widget = gtk_root_get_focus (GTK_ROOT (window)); if (widget != default_widget && !(widget == focus_widget && (!default_widget || !gtk_widget_get_sensitive (default_widget)))) gtk_window_activate_default (window); diff --git a/gtk/gtktext.h b/gtk/gtktext.h index 9907daee9d609833bf9312bf3bb3f3eb3b646c90..96488a1f3312656b33857f4600295418139166aa 100644 --- a/gtk/gtktext.h +++ b/gtk/gtktext.h @@ -131,9 +131,6 @@ void gtk_text_set_tabs (GtkText *self, GDK_AVAILABLE_IN_ALL PangoTabArray * gtk_text_get_tabs (GtkText *self); -GDK_AVAILABLE_IN_ALL -void gtk_text_grab_focus_without_selecting (GtkText *self); - G_END_DECLS #endif /* __GTK_TEXT_H__ */ diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c index 6da0b597e9202d827a62eb6b30539660328bd2d8..040e122f570dce788e74cba8a6c513b399094380 100644 --- a/gtk/gtktextview.c +++ b/gtk/gtktextview.c @@ -407,8 +407,6 @@ static void gtk_text_view_motion (GtkEventController *controller, gpointer user_data); static void gtk_text_view_snapshot (GtkWidget *widget, GtkSnapshot *snapshot); -static gboolean gtk_text_view_focus (GtkWidget *widget, - GtkDirectionType direction); static void gtk_text_view_select_all (GtkWidget *widget, gboolean select); static gboolean get_middle_click_paste (GtkTextView *text_view); @@ -694,7 +692,6 @@ gtk_text_view_class_init (GtkTextViewClass *klass) widget_class->measure = gtk_text_view_measure; widget_class->size_allocate = gtk_text_view_size_allocate; widget_class->snapshot = gtk_text_view_snapshot; - widget_class->focus = gtk_text_view_focus; widget_class->drag_begin = gtk_text_view_drag_begin; widget_class->drag_end = gtk_text_view_drag_end; widget_class->drag_data_get = gtk_text_view_drag_data_get; @@ -5447,39 +5444,6 @@ gtk_text_view_snapshot (GtkWidget *widget, } } -static gboolean -gtk_text_view_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - gboolean result; - - if (!gtk_widget_is_focus (widget) && - gtk_widget_get_focus_child (widget) == NULL) - { - if (gtk_widget_get_can_focus (widget)) - { - gtk_widget_grab_focus (widget); - return TRUE; - } - - return FALSE; - } - else - { - gboolean can_focus; - /* - * Unset CAN_FOCUS flag so that gtk_container_focus() allows - * children to get the focus - */ - can_focus = gtk_widget_get_can_focus (widget); - gtk_widget_set_can_focus (widget, FALSE); - result = GTK_WIDGET_CLASS (gtk_text_view_parent_class)->focus (widget, direction); - gtk_widget_set_can_focus (widget, can_focus); - - return result; - } -} - /* * Container */ diff --git a/gtk/gtktoolbar.c b/gtk/gtktoolbar.c index 6c455cb3d4af673c40017040f0dcdcf3ebd7df81..c2453fc7a8d94be288142e6a9c07d1cedfdc8a80 100644 --- a/gtk/gtktoolbar.c +++ b/gtk/gtktoolbar.c @@ -189,8 +189,6 @@ static void gtk_toolbar_size_allocate (GtkWidget *widget, int height, int baseline); static void gtk_toolbar_style_updated (GtkWidget *widget); -static gboolean gtk_toolbar_focus (GtkWidget *widget, - GtkDirectionType dir); static void gtk_toolbar_move_focus (GtkWidget *widget, GtkDirectionType dir); static void gtk_toolbar_display_changed (GtkWidget *widget, @@ -347,6 +345,19 @@ add_ctrl_tab_bindings (GtkBindingSet *binding_set, GTK_TYPE_DIRECTION_TYPE, direction); } +static GtkWidget * +gtk_toolbar_next_focus_child (GtkWidget *widget, + GtkWidget *child, + GtkDirectionType direction) +{ + if (child && + (direction == GTK_DIR_TAB_FORWARD || + direction == GTK_DIR_TAB_BACKWARD)) + return NULL; + + return GTK_WIDGET_CLASS (gtk_toolbar_parent_class)->next_focus_child (widget, child, direction); +} + static void gtk_toolbar_class_init (GtkToolbarClass *klass) { @@ -368,7 +379,7 @@ gtk_toolbar_class_init (GtkToolbarClass *klass) widget_class->measure = gtk_toolbar_measure; widget_class->size_allocate = gtk_toolbar_size_allocate; widget_class->style_updated = gtk_toolbar_style_updated; - widget_class->focus = gtk_toolbar_focus; + widget_class->next_focus_child = gtk_toolbar_next_focus_child; gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_TOOL_BAR); @@ -1611,43 +1622,6 @@ gtk_toolbar_move_focus (GtkWidget *widget, g_list_free (children); } -/* The focus handler for the toolbar. It called when the user presses - * TAB or otherwise tries to focus the toolbar. - */ -static gboolean -gtk_toolbar_focus (GtkWidget *widget, - GtkDirectionType dir) -{ - GtkToolbar *toolbar = GTK_TOOLBAR (widget); - GList *children, *list; - gboolean result = FALSE; - - /* if focus is already somewhere inside the toolbar then return FALSE. - * The only way focus can stay inside the toolbar is when the user presses - * arrow keys or Ctrl TAB (both of which are handled by the - * gtk_toolbar_move_focus() keybinding function. - */ - if (gtk_widget_get_focus_child (widget)) - return FALSE; - - children = gtk_toolbar_list_children_in_focus_order (toolbar, dir); - - for (list = children; list != NULL; list = list->next) - { - GtkWidget *child = list->data; - - if (gtk_widget_get_mapped (child) && gtk_widget_child_focus (child, dir)) - { - result = TRUE; - break; - } - } - - g_list_free (children); - - return result; -} - static GtkSettings * toolbar_get_settings (GtkToolbar *toolbar) { diff --git a/gtk/gtktreeview.c b/gtk/gtktreeview.c index 1a4ed0488a0eaea8b9e79d27938e4cd07633d5b9..8262be20cb6c4c347df8bbaf9d4fb98c2b45e735 100644 --- a/gtk/gtktreeview.c +++ b/gtk/gtktreeview.c @@ -614,10 +614,10 @@ static void gtk_tree_view_key_controller_key_released (GtkEventControllerKey GdkModifierType state, GtkTreeView *tree_view); static void gtk_tree_view_key_controller_focus_out (GtkEventControllerKey *key, + GdkCrossingMode mode, + GdkNotifyType detail, GtkTreeView *tree_view); -static gint gtk_tree_view_focus (GtkWidget *widget, - GtkDirectionType direction); static void gtk_tree_view_grab_focus (GtkWidget *widget); static void gtk_tree_view_style_updated (GtkWidget *widget); @@ -985,7 +985,6 @@ gtk_tree_view_class_init (GtkTreeViewClass *class) widget_class->drag_motion = gtk_tree_view_drag_motion; widget_class->drag_drop = gtk_tree_view_drag_drop; widget_class->drag_data_received = gtk_tree_view_drag_data_received; - widget_class->focus = gtk_tree_view_focus; widget_class->grab_focus = gtk_tree_view_grab_focus; widget_class->style_updated = gtk_tree_view_style_updated; @@ -5475,6 +5474,8 @@ gtk_tree_view_motion_controller_leave (GtkEventControllerMotion *controller, static void gtk_tree_view_key_controller_focus_out (GtkEventControllerKey *key, + GdkCrossingMode mode, + GdkNotifyType detail, GtkTreeView *tree_view) { gtk_widget_queue_draw (GTK_WIDGET (tree_view)); @@ -7709,170 +7710,6 @@ gtk_tree_view_get_fixed_height_mode (GtkTreeView *tree_view) return tree_view->priv->fixed_height_mode; } -/* Returns TRUE if the focus is within the headers, after the focus operation is - * done - */ -static gboolean -gtk_tree_view_header_focus (GtkTreeView *tree_view, - GtkDirectionType dir, - gboolean clamp_column_visible) -{ - GtkTreeViewColumn *column; - GtkWidget *button; - GtkWidget *focus_child; - GList *last_column, *first_column; - GList *tmp_list; - gboolean rtl; - - if (! tree_view->priv->headers_visible) - return FALSE; - - focus_child = gtk_widget_get_focus_child (GTK_WIDGET (tree_view)); - - first_column = tree_view->priv->columns; - while (first_column) - { - column = GTK_TREE_VIEW_COLUMN (first_column->data); - button = gtk_tree_view_column_get_button (column); - - if (gtk_widget_get_can_focus (button) && - gtk_tree_view_column_get_visible (column) && - (gtk_tree_view_column_get_clickable (column) || - gtk_tree_view_column_get_reorderable (column))) - break; - first_column = first_column->next; - } - - /* No headers are visible, or are focusable. We can't focus in or out. - */ - if (first_column == NULL) - return FALSE; - - last_column = g_list_last (tree_view->priv->columns); - while (last_column) - { - column = GTK_TREE_VIEW_COLUMN (last_column->data); - button = gtk_tree_view_column_get_button (column); - - if (gtk_widget_get_can_focus (button) && - gtk_tree_view_column_get_visible (column) && - (gtk_tree_view_column_get_clickable (column) || - gtk_tree_view_column_get_reorderable (column))) - break; - last_column = last_column->prev; - } - - - rtl = (gtk_widget_get_direction (GTK_WIDGET (tree_view)) == GTK_TEXT_DIR_RTL); - - switch (dir) - { - case GTK_DIR_TAB_BACKWARD: - case GTK_DIR_TAB_FORWARD: - case GTK_DIR_UP: - case GTK_DIR_DOWN: - if (focus_child == NULL) - { - if (tree_view->priv->focus_column != NULL) - button = gtk_tree_view_column_get_button (tree_view->priv->focus_column); - else - button = NULL; - - if (button && gtk_widget_get_can_focus (button)) - focus_child = button; - else - focus_child = gtk_tree_view_column_get_button (GTK_TREE_VIEW_COLUMN (first_column->data)); - - gtk_widget_grab_focus (focus_child); - break; - } - return FALSE; - - case GTK_DIR_LEFT: - case GTK_DIR_RIGHT: - if (focus_child == NULL) - { - if (tree_view->priv->focus_column != NULL) - focus_child = gtk_tree_view_column_get_button (tree_view->priv->focus_column); - else if (dir == GTK_DIR_LEFT) - focus_child = gtk_tree_view_column_get_button (GTK_TREE_VIEW_COLUMN (last_column->data)); - else - focus_child = gtk_tree_view_column_get_button (GTK_TREE_VIEW_COLUMN (first_column->data)); - - gtk_widget_grab_focus (focus_child); - break; - } - - if (gtk_widget_child_focus (focus_child, dir)) - { - /* The focus moves inside the button. */ - /* This is probably a great example of bad UI */ - break; - } - - /* We need to move the focus among the row of buttons. */ - for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next) - if (gtk_tree_view_column_get_button (GTK_TREE_VIEW_COLUMN (tmp_list->data)) == focus_child) - break; - - if ((tmp_list == first_column && dir == (rtl ? GTK_DIR_RIGHT : GTK_DIR_LEFT)) - || (tmp_list == last_column && dir == (rtl ? GTK_DIR_LEFT : GTK_DIR_RIGHT))) - { - gtk_widget_error_bell (GTK_WIDGET (tree_view)); - break; - } - - while (tmp_list) - { - if (dir == (rtl ? GTK_DIR_LEFT : GTK_DIR_RIGHT)) - tmp_list = tmp_list->next; - else - tmp_list = tmp_list->prev; - - if (tmp_list == NULL) - { - g_warning ("Internal button not found"); - break; - } - column = tmp_list->data; - button = gtk_tree_view_column_get_button (column); - if (button && - gtk_tree_view_column_get_visible (column) && - gtk_widget_get_can_focus (button)) - { - focus_child = button; - gtk_widget_grab_focus (button); - break; - } - } - break; - default: - g_assert_not_reached (); - break; - } - - /* if focus child is non-null, we assume it's been set to the current focus child - */ - if (focus_child) - { - for (tmp_list = tree_view->priv->columns; tmp_list; tmp_list = tmp_list->next) - if (gtk_tree_view_column_get_button (GTK_TREE_VIEW_COLUMN (tmp_list->data)) == focus_child) - { - _gtk_tree_view_set_focus_column (tree_view, GTK_TREE_VIEW_COLUMN (tmp_list->data)); - break; - } - - if (clamp_column_visible) - { - gtk_tree_view_clamp_column_visible (tree_view, - tree_view->priv->focus_column, - FALSE); - } - } - - return (focus_child != NULL); -} - /* This function returns in 'path' the first focusable path, if the given path * is already focusable, it’s the returned one. */ @@ -7919,59 +7756,6 @@ search_first_focusable_path (GtkTreeView *tree_view, return (*path != NULL); } -static gint -gtk_tree_view_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - GtkTreeView *tree_view = GTK_TREE_VIEW (widget); - GtkWidget *focus_child; - - if (!gtk_widget_is_sensitive (widget) || !gtk_widget_get_can_focus (widget)) - return FALSE; - - focus_child = gtk_widget_get_focus_child (widget); - - gtk_tree_view_stop_editing (GTK_TREE_VIEW (widget), FALSE); - /* Case 1. Headers currently have focus. */ - if (focus_child) - { - switch (direction) - { - case GTK_DIR_LEFT: - case GTK_DIR_RIGHT: - gtk_tree_view_header_focus (tree_view, direction, TRUE); - return TRUE; - case GTK_DIR_TAB_BACKWARD: - case GTK_DIR_UP: - return FALSE; - case GTK_DIR_TAB_FORWARD: - case GTK_DIR_DOWN: - gtk_widget_grab_focus (widget); - return TRUE; - default: - g_assert_not_reached (); - return FALSE; - } - } - - /* Case 2. We don't have focus at all. */ - if (!gtk_widget_has_focus (widget)) - { - gtk_widget_grab_focus (widget); - return TRUE; - } - - /* Case 3. We have focus already. */ - if (direction == GTK_DIR_TAB_BACKWARD) - return (gtk_tree_view_header_focus (tree_view, direction, FALSE)); - else if (direction == GTK_DIR_TAB_FORWARD) - return FALSE; - - /* Other directions caught by the keybindings */ - gtk_widget_grab_focus (widget); - return TRUE; -} - static void gtk_tree_view_grab_focus (GtkWidget *widget) { @@ -10151,7 +9935,8 @@ send_focus_change (GtkWidget *widget, fevent->focus_change.in = in; gdk_event_set_device (fevent, device); - gtk_widget_send_focus_change (widget, fevent); + gtk_widget_set_has_focus (widget, in); + gtk_widget_event (widget, fevent); g_object_unref (fevent); } diff --git a/gtk/gtktreeviewcolumn.c b/gtk/gtktreeviewcolumn.c index 5817a5f2f86da1ce3dccf6493278518d6ce9e446..78e22c5ef2ffdc8af9f7c6dad95f2c1b6128fee2 100644 --- a/gtk/gtktreeviewcolumn.c +++ b/gtk/gtktreeviewcolumn.c @@ -814,6 +814,8 @@ gtk_tree_view_column_cell_layout_get_area (GtkCellLayout *cell_layout) static void focus_in (GtkEventControllerKey *controller, + GdkCrossingMode mode, + GdkNotifyType detail, GtkTreeViewColumn *column) { _gtk_tree_view_set_focus_column (GTK_TREE_VIEW (column->priv->tree_view), column); @@ -1016,11 +1018,8 @@ gtk_tree_view_column_update_button (GtkTreeViewColumn *tree_column) gtk_widget_set_can_focus (priv->button, FALSE); if (gtk_widget_has_focus (priv->button)) { - GtkWidget *toplevel = gtk_widget_get_toplevel (priv->tree_view); - if (gtk_widget_is_toplevel (toplevel)) - { - gtk_window_set_focus (GTK_WINDOW (toplevel), NULL); - } + GtkRoot *root = gtk_widget_get_root (priv->tree_view); + gtk_root_set_focus (root, NULL); } } /* Queue a resize on the assumption that we always want to catch all changes diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index cf7f1b1f489572762fe4a2da145b07a1b0f43586..5bd58fb4501405a383d0d21322932465e9867d00 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -484,7 +484,6 @@ enum { GRAB_NOTIFY, CHILD_NOTIFY, MNEMONIC_ACTIVATE, - FOCUS, MOVE_FOCUS, KEYNAV_FAILED, DRAG_BEGIN, @@ -589,7 +588,9 @@ static void gtk_widget_real_size_allocate (GtkWidget *widget, static void gtk_widget_real_direction_changed(GtkWidget *widget, GtkTextDirection previous_direction); -static void gtk_widget_real_grab_focus (GtkWidget *focus_widget); +static void gtk_widget_real_grab_focus (GtkWidget *focus_widget); +static void gtk_widget_real_set_focus_child (GtkWidget *widget, + GtkWidget *child); static gboolean gtk_widget_real_query_tooltip (GtkWidget *widget, gint x, gint y, @@ -600,8 +601,6 @@ static void gtk_widget_real_style_updated (GtkWidget *widget); static void gtk_widget_dispatch_child_properties_changed (GtkWidget *object, guint n_pspecs, GParamSpec **pspecs); -static gboolean gtk_widget_real_focus (GtkWidget *widget, - GtkDirectionType direction); static void gtk_widget_real_move_focus (GtkWidget *widget, GtkDirectionType direction); static gboolean gtk_widget_real_keynav_failed (GtkWidget *widget, @@ -949,7 +948,8 @@ gtk_widget_class_init (GtkWidgetClass *klass) klass->snapshot = gtk_widget_real_snapshot; klass->mnemonic_activate = gtk_widget_real_mnemonic_activate; klass->grab_focus = gtk_widget_real_grab_focus; - klass->focus = gtk_widget_real_focus; + klass->set_focus_child = gtk_widget_real_set_focus_child; + klass->next_focus_child = gtk_widget_next_focus_child; klass->move_focus = gtk_widget_real_move_focus; klass->keynav_failed = gtk_widget_real_keynav_failed; klass->drag_begin = NULL; @@ -1670,23 +1670,6 @@ gtk_widget_class_init (GtkWidgetClass *klass) G_TYPE_BOOLEAN, 1, G_TYPE_BOOLEAN); - /** - * GtkWidget::focus: - * @widget: the object which received the signal. - * @direction: - * - * Returns: %TRUE to stop other handlers from being invoked for the event. %FALSE to propagate the event further. - */ - widget_signals[FOCUS] = - g_signal_new (I_("focus"), - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GtkWidgetClass, focus), - _gtk_boolean_handled_accumulator, NULL, - _gtk_marshal_BOOLEAN__ENUM, - G_TYPE_BOOLEAN, 1, - GTK_TYPE_DIRECTION_TYPE); - /** * GtkWidget::move-focus: * @widget: the object which received the signal. @@ -2843,6 +2826,7 @@ gtk_widget_init (GTypeInstance *instance, gpointer g_class) priv->visible = gtk_widget_class_get_visible_by_default (g_class); priv->child_visible = TRUE; + priv->child_focusable = TRUE; priv->name = NULL; priv->user_alpha = 255; priv->alpha = 255; @@ -3130,10 +3114,11 @@ gtk_widget_unparent (GtkWidget *widget) toplevel = NULL; /* Removing a widget from a container restores the child visible - * flag to the default state, so it doesn't affect the child - * in the next parent. + * and focusable flags to the default state, so they don't affect + * the child in the next parent. */ priv->child_visible = TRUE; + priv->child_focusable = TRUE; old_parent = priv->parent; if (old_parent) @@ -5321,107 +5306,25 @@ _gtk_widget_grab_notify (GtkWidget *widget, * gtk_widget_grab_focus: * @widget: a #GtkWidget * - * Causes @widget to have the keyboard focus for the #GtkWindow it's - * inside. @widget must be a focusable widget, such as a #GtkEntry; - * something like #GtkFrame won’t work. - * - * More precisely, it must have the %GTK_CAN_FOCUS flag set. Use - * gtk_widget_set_can_focus() to modify that flag. + * Causes @widget (or one of its descendents) to have the keyboard focus + * for the #GtkWindow it's inside. * - * The widget also needs to be realized and mapped. This is indicated by the - * related signals. Grabbing the focus immediately after creating the widget - * will likely fail and cause critical warnings. + * @widget must be focusable, or have a ::grab_focus implementation that + * transfers the focus to a descendant of @widget that is focusable. **/ void gtk_widget_grab_focus (GtkWidget *widget) { g_return_if_fail (GTK_IS_WIDGET (widget)); - if (!gtk_widget_is_sensitive (widget)) - return; - - g_object_ref (widget); GTK_WIDGET_GET_CLASS (widget)->grab_focus (widget); - g_object_notify_by_pspec (G_OBJECT (widget), widget_props[PROP_HAS_FOCUS]); - g_object_unref (widget); -} - -static void -reset_focus_recurse (GtkWidget *widget, - gpointer data) -{ - gtk_widget_set_focus_child (widget, NULL); - - gtk_widget_forall (widget, - reset_focus_recurse, - NULL); } static void gtk_widget_real_grab_focus (GtkWidget *focus_widget) { - GtkWidget *toplevel; - GtkWidget *widget; - - /* clear the current focus setting, break if the current widget - * is the focus widget's parent, since containers above that will - * be set by the next loop. - */ - toplevel = _gtk_widget_get_toplevel (focus_widget); - if (_gtk_widget_is_toplevel (toplevel) && GTK_IS_WINDOW (toplevel)) - { - widget = gtk_window_get_focus (GTK_WINDOW (toplevel)); - - if (widget == focus_widget) - { - /* We call _gtk_window_internal_set_focus() here so that the - * toplevel window can request the focus if necessary. - * This is needed when the toplevel is a GtkPlug - */ - if (!gtk_widget_has_focus (widget)) - _gtk_window_internal_set_focus (GTK_WINDOW (toplevel), focus_widget); - - return; - } - - if (widget) - { - GtkWidget *common_ancestor = gtk_widget_common_ancestor (widget, focus_widget); - - if (widget != common_ancestor) - { - while (widget->priv->parent) - { - widget = widget->priv->parent; - gtk_widget_set_focus_child (widget, NULL); - if (widget == common_ancestor) - break; - } - } - } - } - else if (toplevel != focus_widget) - { - /* gtk_widget_grab_focus() operates on a tree without window... - * actually, this is very questionable behavior. - */ - - gtk_widget_forall (toplevel, - reset_focus_recurse, - NULL); - } - - /* now propagate the new focus up the widget tree and finally - * set it on the window - */ - widget = focus_widget; - while (widget->priv->parent) - { - gtk_widget_set_focus_child (widget->priv->parent, widget); - widget = widget->priv->parent; - } - if (GTK_IS_WINDOW (widget)) - _gtk_window_internal_set_focus (GTK_WINDOW (widget), focus_widget); + GtkWidgetPrivate *priv = gtk_widget_get_instance_private (focus_widget); + gtk_root_set_focus (priv->root, focus_widget); } static gboolean @@ -5517,53 +5420,22 @@ gtk_widget_real_style_updated (GtkWidget *widget) } } -static gboolean -gtk_widget_real_focus (GtkWidget *widget, - GtkDirectionType direction) -{ - if (gtk_widget_get_can_focus (widget)) - { - if (!gtk_widget_is_focus (widget)) - { - gtk_widget_grab_focus (widget); - return TRUE; - } - } - else if (_gtk_widget_get_first_child (widget) == NULL) - { - /* No children, no possibility to focus anything */ - return FALSE; - } - else - { - GPtrArray *focus_order = g_ptr_array_new (); - gboolean ret = FALSE; - - /* Try focusing any of the child widgets, depending on the given @direction */ - - gtk_widget_focus_sort (widget, direction, focus_order); - ret = gtk_widget_focus_move (widget, direction, focus_order); - - g_ptr_array_unref (focus_order); - - if (ret) - return TRUE; - } - - return FALSE; -} - static void gtk_widget_real_move_focus (GtkWidget *widget, GtkDirectionType direction) { - GtkWidget *toplevel = _gtk_widget_get_toplevel (widget); + GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); + GtkWidget *focus_child; + GtkWidget *next_focus; - if (widget != toplevel && GTK_IS_WINDOW (toplevel)) - { - g_signal_emit (toplevel, widget_signals[MOVE_FOCUS], 0, - direction); - } + focus_child = gtk_root_get_focus (gtk_widget_get_root (widget)); + if (focus_child) + next_focus = gtk_widget_get_next_focus (focus_child, direction); + else + next_focus = gtk_widget_get_next_focus (widget, direction); + + if (next_focus) + gtk_root_set_focus (priv->root, next_focus); } static gboolean @@ -5594,9 +5466,15 @@ gtk_widget_real_keynav_failed (GtkWidget *widget, * @widget: a #GtkWidget * @can_focus: whether or not @widget can own the input focus. * - * Specifies whether @widget can own the input focus. See - * gtk_widget_grab_focus() for actually setting the input focus on a - * widget. + * Specifies whether @widget can own the input focus. + * + * Note that having @can_focus be %TRUE is only one of the + * necessary conditions for being focusable. A widget must + * also be sensitive and not have a ancestor that is marked + * as not child-focusable in order to receive input focus. + * + * See gtk_widget_grab_focus() for actually setting the input + * focus on a widget. **/ void gtk_widget_set_can_focus (GtkWidget *widget, @@ -5709,16 +5587,14 @@ gtk_widget_has_visible_focus (GtkWidget *widget) gboolean gtk_widget_is_focus (GtkWidget *widget) { - GtkWidget *toplevel; + GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); - toplevel = _gtk_widget_get_toplevel (widget); + if (priv->root) + return widget == gtk_root_get_focus (priv->root); - if (GTK_IS_WINDOW (toplevel)) - return widget == gtk_window_get_focus (GTK_WINDOW (toplevel)); - else - return FALSE; + return FALSE; } /** @@ -7580,25 +7456,27 @@ gboolean gtk_widget_child_focus (GtkWidget *widget, GtkDirectionType direction) { - gboolean return_val; + GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); + GtkWidget *focus_child; + GtkWidget *next_focus; g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); - if (!_gtk_widget_get_visible (widget) || - !gtk_widget_is_sensitive (widget)) + if (!gtk_widget_get_root (widget)) return FALSE; - /* Emit ::focus in any case, even if can-focus is FALSE, - * since any widget might have child widgets that will take - * focus - */ - - g_signal_emit (widget, - widget_signals[FOCUS], - 0, - direction, &return_val); + focus_child = gtk_root_get_focus (gtk_widget_get_root (widget)); + if (focus_child) + next_focus = gtk_widget_get_next_focus (focus_child, direction); + else + next_focus = gtk_widget_get_next_focus (widget, direction); + if (next_focus && gtk_widget_is_ancestor (next_focus, widget)) + { + gtk_root_set_focus (priv->root, next_focus); + return TRUE; + } - return return_val; + return FALSE; } /** @@ -8924,14 +8802,7 @@ gtk_widget_propagate_state (GtkWidget *widget, priv->state_flags |= GTK_STATE_FLAG_INSENSITIVE; if (gtk_widget_is_focus (widget) && !gtk_widget_is_sensitive (widget)) - { - GtkWidget *window; - - window = _gtk_widget_get_toplevel (widget); - - if (window && _gtk_widget_is_toplevel (window)) - gtk_window_set_focus (GTK_WINDOW (window), NULL); - } + gtk_root_set_focus (priv->root, NULL); new_flags = priv->state_flags; @@ -11696,59 +11567,17 @@ gtk_widget_get_overflow (GtkWidget *widget) return priv->overflow; } -/** - * gtk_widget_send_focus_change: - * @widget: a #GtkWidget - * @event: a #GdkEvent of type GDK_FOCUS_CHANGE - * - * Sends the focus change @event to @widget - * - * This function is not meant to be used by applications. The only time it - * should be used is when it is necessary for a #GtkWidget to assign focus - * to a widget that is semantically owned by the first widget even though - * it’s not a direct child - for instance, a search entry in a floating - * window similar to the quick search in #GtkTreeView. - * - * An example of its usage is: - * - * |[ - * GdkEvent *fevent = gdk_event_new (GDK_FOCUS_CHANGE); - * - * fevent->focus_change.type = GDK_FOCUS_CHANGE; - * fevent->focus_change.in = TRUE; - * fevent->focus_change.surface = _gtk_widget_get_surface (widget); - * if (fevent->focus_change.surface != NULL) - * g_object_ref (fevent->focus_change.surface); - * - * gtk_widget_send_focus_change (widget, fevent); - * - * g_object_unref (event); - * ]| - * - * Returns: the return value from the event signal emission: %TRUE - * if the event was handled, and %FALSE otherwise - */ -gboolean -gtk_widget_send_focus_change (GtkWidget *widget, - GdkEvent *event) +void +gtk_widget_set_has_focus (GtkWidget *widget, + gboolean has_focus) { GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); - gboolean res; - - g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); - g_return_val_if_fail (event != NULL && event->any.type == GDK_FOCUS_CHANGE, FALSE); - - g_object_ref (widget); - - priv->has_focus = event->focus_change.in; - res = gtk_widget_event (widget, event); + if (priv->has_focus == has_focus) + return; + priv->has_focus = has_focus; g_object_notify_by_pspec (G_OBJECT (widget), widget_props[PROP_HAS_FOCUS]); - - g_object_unref (widget); - - return res; } /** @@ -13460,8 +13289,6 @@ void gtk_widget_set_focus_child (GtkWidget *widget, GtkWidget *child) { - GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); - g_return_if_fail (GTK_IS_WIDGET (widget)); if (child != NULL) @@ -13470,26 +13297,16 @@ gtk_widget_set_focus_child (GtkWidget *widget, g_return_if_fail (gtk_widget_get_parent (child) == widget); } - if (priv->focus_child) - gtk_widget_unset_state_flags (priv->focus_child, - GTK_STATE_FLAG_FOCUSED|GTK_STATE_FLAG_FOCUS_VISIBLE); - - if (child) - { - GtkWidget *toplevel; - GtkStateFlags flags = GTK_STATE_FLAG_FOCUSED; - - toplevel = _gtk_widget_get_toplevel (widget); - if (!GTK_IS_WINDOW (toplevel) || gtk_window_get_focus_visible (GTK_WINDOW (toplevel))) - flags |= GTK_STATE_FLAG_FOCUS_VISIBLE; + GTK_WIDGET_GET_CLASS (widget)->set_focus_child (widget, child); +} - gtk_widget_set_state_flags (child, flags, FALSE); - } +static void +gtk_widget_real_set_focus_child (GtkWidget *widget, + GtkWidget *child) +{ + GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); g_set_object (&priv->focus_child, child); - - if (GTK_IS_CONTAINER (widget)) - gtk_container_set_focus_child (GTK_CONTAINER (widget), child); } /** @@ -13676,3 +13493,66 @@ gtk_widget_get_height (GtkWidget *widget) return priv->height; } + +/** + * gtk_widget_get_child_focusable: + * @widget: a #GtkWidget + * + * Gets the value set with gtk_widget_set_child_focusable(). + * If you feel a need to use this function, your code probably + * needs reorganization. + * + * This function is only useful for widget implementations and + * never should be called by an application. + * + * Returns: %TRUE if the widget is focusable + **/ +gboolean +gtk_widget_get_child_focusable (GtkWidget *widget) +{ + GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); + + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + + return priv->child_focusable; +} + +/** + * gtk_widget_set_child_focusable: + * @widget: a #GtkWidget + * @focusable: if %TRUE, @widget should be focusable + * + * Sets whether @widget should be able to receive focus + * + * Child focusability will be reset to its default state of %TRUE + * when the widget is removed from its parent. + * + * This function is only useful for widget implementations and + * never should be called by an application. + **/ +void +gtk_widget_set_child_focusable (GtkWidget *widget, + gboolean focusable) +{ + GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget); + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + focusable = !!focusable; + + if (priv->child_focusable == focusable) + return; + + if (focusable) + priv->child_focusable = TRUE; + else + { + GtkWidget *toplevel; + + priv->child_focusable = FALSE; + + toplevel = _gtk_widget_get_toplevel (widget); + if (toplevel != widget && _gtk_widget_is_toplevel (toplevel)) + _gtk_window_unset_focus_and_default (GTK_WINDOW (toplevel), widget); + } +} diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h index f3012b07a4c97ed4b72b5b79a0e68356067c8061..c7581e77c0ea152c42a288f01867194ae76828dc 100644 --- a/gtk/gtkwidget.h +++ b/gtk/gtkwidget.h @@ -185,7 +185,12 @@ struct _GtkWidget * %FALSE, and just grabs the focus if @group_cycling is %TRUE. * @grab_focus: Causes @widget to have the keyboard focus for the * #GtkWindow it’s inside. - * @focus: + * @set_focus_child: Allows widget implementations to do extra actions when + * the child containing the focus is changed. Must chain up + * @next_focus_child: Returns the next child that is a candidate for receiving focus. + * Note that the returned child does not have to be focusable itself, it might just + * contain focusable children. The default implementation returns all children, in + * their logical order. * @move_focus: Signal emitted when a change of focus is requested * @keynav_failed: Signal emitted if keyboard navigation fails. * @drag_begin: Signal emitted on the drag source when a drag is @@ -277,8 +282,11 @@ struct _GtkWidgetClass gboolean group_cycling); /* explicit focus */ - void (* grab_focus) (GtkWidget *widget); - gboolean (* focus) (GtkWidget *widget, + void (* grab_focus) (GtkWidget *widget); + void (* set_focus_child) (GtkWidget *widget, + GtkWidget *child); + GtkWidget * (* next_focus_child) (GtkWidget *widget, + GtkWidget *child, GtkDirectionType direction); /* keyboard navigation */ @@ -451,9 +459,6 @@ gboolean gtk_widget_mnemonic_activate (GtkWidget *widget, GDK_AVAILABLE_IN_ALL gboolean gtk_widget_event (GtkWidget *widget, const GdkEvent *event); -GDK_AVAILABLE_IN_ALL -gboolean gtk_widget_send_focus_change (GtkWidget *widget, - GdkEvent *event); GDK_AVAILABLE_IN_ALL gboolean gtk_widget_activate (GtkWidget *widget); @@ -480,6 +485,9 @@ gboolean gtk_widget_has_visible_focus (GtkWidget *widget); GDK_AVAILABLE_IN_ALL void gtk_widget_grab_focus (GtkWidget *widget); GDK_AVAILABLE_IN_ALL +GtkWidget *gtk_widget_get_next_focus (GtkWidget *widget, + GtkDirectionType direction); +GDK_AVAILABLE_IN_ALL void gtk_widget_set_focus_on_click (GtkWidget *widget, gboolean focus_on_click); GDK_AVAILABLE_IN_ALL @@ -1062,6 +1070,15 @@ void gtk_widget_insert_before (GtkWidget *widget, GDK_AVAILABLE_IN_ALL void gtk_widget_set_focus_child (GtkWidget *widget, GtkWidget *child); +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_widget_get_focus_child (GtkWidget *widget); + +GDK_AVAILABLE_IN_ALL +void gtk_widget_set_child_focusable (GtkWidget *widget, + gboolean focusable); +GDK_AVAILABLE_IN_ALL +gboolean gtk_widget_get_child_focusable (GtkWidget *widget); + GDK_AVAILABLE_IN_ALL void gtk_widget_snapshot_child (GtkWidget *widget, GtkWidget *child, diff --git a/gtk/gtkwidgetfocus.c b/gtk/gtkwidgetfocus.c index af68bd7a563cd3dfd8f319fb3b7baf9e27124247..e6f4e3060fe2a9a4843165eda9ccb2f76cfe9ac4 100644 --- a/gtk/gtkwidgetfocus.c +++ b/gtk/gtkwidgetfocus.c @@ -15,6 +15,8 @@ * License along with this library. If not, see . */ +#include "config.h" + #include "gtkwidgetprivate.h" typedef struct _CompareInfo CompareInfo; @@ -155,15 +157,11 @@ static gboolean old_focus_coords (GtkWidget *widget, graphene_rect_t *old_focus_bounds) { - GtkWidget *toplevel = _gtk_widget_get_toplevel (widget); GtkWidget *old_focus; - if (GTK_IS_WINDOW (toplevel)) - { - old_focus = gtk_window_get_focus (GTK_WINDOW (toplevel)); - if (old_focus) - return gtk_widget_compute_bounds (old_focus, widget, old_focus_bounds); - } + old_focus = gtk_root_get_focus (gtk_widget_get_root (widget)); + if (old_focus) + return gtk_widget_compute_bounds (old_focus, widget, old_focus_bounds); return FALSE; } @@ -426,7 +424,9 @@ gtk_widget_focus_sort (GtkWidget *widget, child != NULL; child = _gtk_widget_get_next_sibling (child)) { - if (_gtk_widget_get_realized (child)) + if (_gtk_widget_get_realized (child) && + gtk_widget_get_child_focusable (child) && + gtk_widget_get_sensitive (child)) g_ptr_array_add (focus_order, child); } } @@ -451,36 +451,111 @@ gtk_widget_focus_sort (GtkWidget *widget, } } - gboolean -gtk_widget_focus_move (GtkWidget *widget, - GtkDirectionType direction, - GPtrArray *focus_order) +gtk_widget_can_take_focus (GtkWidget *widget) { - GtkWidget *focus_child = gtk_widget_get_focus_child (widget); + GtkWidget *w; + + if (!gtk_widget_is_sensitive (widget) || + !gtk_widget_get_can_focus (widget)) + return FALSE; + + w = widget; + do { + if (!gtk_widget_get_child_focusable (w)) + return FALSE; + w = gtk_widget_get_parent (w); + } while (w != NULL); + + return TRUE; +} + +/** + * gtk_widget_get_next_focus: + * @widget: a #GtkWidget + * @direction: diretion to move in + * + * Finds the widget that would get focused if @widget was + * the focus widget, and focus was moved in @direcion. + * + * Returns: (transfer none): the next focus widget + */ +GtkWidget * +gtk_widget_get_next_focus (GtkWidget *widget, + GtkDirectionType dir) +{ + GtkWidget *prev; + GtkWidget *next; + GHashTable *seen; + + seen = g_hash_table_new (g_direct_hash, g_direct_equal); + + prev = NULL; + do { + next = GTK_WIDGET_GET_CLASS (widget)->next_focus_child (widget, prev, dir); + if (next == NULL) + { + if (GTK_IS_ROOT (widget)) + { + prev = NULL; + } + else + { + prev = widget; + widget = gtk_widget_get_parent (widget); + } + } + else if (gtk_widget_can_take_focus (next)) + { + break; + } + else if (g_hash_table_contains (seen, next)) + { + next = NULL; + break; + } + else + { + g_hash_table_add (seen, next); + widget = next; + prev = NULL; + } + } while (widget); + + g_hash_table_unref (seen); + + return next; +} + +GtkWidget * +gtk_widget_next_focus_child (GtkWidget *widget, + GtkWidget *focus_child, + GtkDirectionType direction) +{ + GPtrArray *focus_order; int i; + GtkWidget *next_child = NULL; + + focus_order = g_ptr_array_new (); + gtk_widget_focus_sort (widget, direction, focus_order); - for (i = 0; i < focus_order->len; i ++) + for (i = 0; i < focus_order->len; i++) { GtkWidget *child = g_ptr_array_index (focus_order, i); if (focus_child) { if (focus_child == child) - { - focus_child = NULL; - - if (gtk_widget_child_focus (child, direction)) - return TRUE; - } + focus_child = NULL; } - else if (_gtk_widget_is_drawable (child) && - gtk_widget_is_ancestor (child, widget)) + else { - if (gtk_widget_child_focus (child, direction)) - return TRUE; + next_child = child; + break; } } - return FALSE; + g_ptr_array_unref (focus_order); + + return next_child; } diff --git a/gtk/gtkwidgetprivate.h b/gtk/gtkwidgetprivate.h index ae8595e4dd43bb4b65674219fbffac36d1eb719c..c5f6949d1698255a4fbe8bb157295f9df38672de 100644 --- a/gtk/gtkwidgetprivate.h +++ b/gtk/gtkwidgetprivate.h @@ -72,6 +72,7 @@ struct _GtkWidgetPrivate guint has_grab : 1; guint shadowed : 1; guint child_visible : 1; + guint child_focusable : 1; guint multidevice : 1; guint can_pick : 1; @@ -242,6 +243,12 @@ GdkSurface * _gtk_widget_get_device_surface (GtkWidget *widget, GdkDevice *device); GList * _gtk_widget_list_devices (GtkWidget *widget); +void gtk_synthesize_crossing_events (GtkWindow *toplevel, + GtkWidget *from, + GtkWidget *to, + GdkEvent *event, + GdkCrossingMode mode); + void _gtk_widget_synthesize_crossing (GtkWidget *from, GtkWidget *to, GdkDevice *device, @@ -311,14 +318,14 @@ void gtk_widget_forall (GtkWidget GtkCallback callback, gpointer user_data); -GtkWidget *gtk_widget_get_focus_child (GtkWidget *widget); - void gtk_widget_focus_sort (GtkWidget *widget, GtkDirectionType direction, GPtrArray *focus_order); -gboolean gtk_widget_focus_move (GtkWidget *widget, - GtkDirectionType direction, - GPtrArray *focus_order); +GtkWidget * gtk_widget_next_focus_child (GtkWidget *widget, + GtkWidget *child, + GtkDirectionType direction); +void gtk_widget_set_has_focus (GtkWidget *widget, + gboolean has_focus); void gtk_widget_get_surface_allocation (GtkWidget *widget, GtkAllocation *allocation); @@ -335,6 +342,9 @@ gboolean gtk_widget_run_controllers (GtkWidget const GdkEvent *event, GtkPropagationPhase phase); +gboolean gtk_widget_can_take_focus (GtkWidget *widget); + + /* inline getters */ static inline GtkWidget * diff --git a/gtk/gtkwindow.c b/gtk/gtkwindow.c index 600e96e515a330d4057f1eae0a03b421bfd9eddc..0caa84e78bdcadb06a43aee7f72feeff7dc674bd 100644 --- a/gtk/gtkwindow.c +++ b/gtk/gtkwindow.c @@ -122,16 +122,12 @@ * elements representing the #GtkAccelGroup objects you want to add to * your window (synonymous with gtk_window_add_accel_group(). * - * It also supports the element, whose name property names - * the widget to receive the focus when the window is mapped. - * * An example of a UI definition fragment with accel groups: * |[ * * * * - * * * * ... @@ -299,7 +295,6 @@ static const char *dnd_dest_targets [] = { #endif enum { - SET_FOCUS, ACTIVATE_FOCUS, ACTIVATE_DEFAULT, KEYS_CHANGED, @@ -436,12 +431,6 @@ static void gtk_window_remove (GtkContainer *container, static void gtk_window_forall (GtkContainer *container, GtkCallback callback, gpointer callback_data); -static gint gtk_window_focus (GtkWidget *widget, - GtkDirectionType direction); -static void gtk_window_move_focus (GtkWidget *widget, - GtkDirectionType dir); -static void gtk_window_real_set_focus (GtkWindow *window, - GtkWidget *focus); static void gtk_window_real_activate_default (GtkWindow *window); static void gtk_window_real_activate_focus (GtkWindow *window); @@ -804,8 +793,6 @@ gtk_window_class_init (GtkWindowClass *klass) widget_class->realize = gtk_window_realize; widget_class->unrealize = gtk_window_unrealize; widget_class->size_allocate = gtk_window_size_allocate; - widget_class->focus = gtk_window_focus; - widget_class->move_focus = gtk_window_move_focus; widget_class->measure = gtk_window_measure; widget_class->state_flags_changed = gtk_window_state_flags_changed; widget_class->style_updated = gtk_window_style_updated; @@ -816,8 +803,6 @@ gtk_window_class_init (GtkWindowClass *klass) container_class->remove = gtk_window_remove; container_class->forall = gtk_window_forall; - klass->set_focus = gtk_window_real_set_focus; - klass->activate_default = gtk_window_real_activate_default; klass->activate_focus = gtk_window_real_activate_focus; klass->keys_changed = gtk_window_keys_changed; @@ -1118,24 +1103,7 @@ gtk_window_class_init (GtkWindowClass *klass) GTK_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (gobject_class, LAST_ARG, window_props); - - /** - * GtkWindow:set-focus: - * @window: the window which received the signal - * @widget: (nullable): the newly focused widget (or %NULL for no focus) - * - * This signal is emitted whenever the currently focused widget in - * this window changes. - */ - window_signals[SET_FOCUS] = - g_signal_new (I_("set-focus"), - G_TYPE_FROM_CLASS (gobject_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GtkWindowClass, set_focus), - NULL, NULL, - NULL, - G_TYPE_NONE, 1, - GTK_TYPE_WIDGET); + gtk_root_install_properties (gobject_class, LAST_ARG); /** * GtkWindow::activate-focus: @@ -2095,6 +2063,9 @@ gtk_window_set_property (GObject *object, case PROP_FOCUS_VISIBLE: gtk_window_set_focus_visible (window, g_value_get_boolean (value)); break; + case LAST_ARG + GTK_ROOT_PROP_FOCUS_WIDGET: + gtk_window_set_focus (window, g_value_get_object (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2210,6 +2181,9 @@ gtk_window_get_property (GObject *object, case PROP_IS_MAXIMIZED: g_value_set_boolean (value, gtk_window_is_maximized (window)); break; + case LAST_ARG + GTK_ROOT_PROP_FOCUS_WIDGET: + g_value_set_object (value, gtk_window_get_focus (window)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2365,55 +2339,6 @@ static const GMarkupParser window_parser = window_start_element }; -typedef struct { - GObject *object; - GtkBuilder *builder; - gchar *name; - gint line; - gint col; -} NameSubParserData; - -static void -focus_start_element (GMarkupParseContext *context, - const gchar *element_name, - const gchar **names, - const gchar **values, - gpointer user_data, - GError **error) -{ - NameSubParserData *data = (NameSubParserData*)user_data; - - if (strcmp (element_name, "initial-focus") == 0) - { - const gchar *name; - - if (!_gtk_builder_check_parent (data->builder, context, "object", error)) - return; - - if (!g_markup_collect_attributes (element_name, names, values, error, - G_MARKUP_COLLECT_STRING, "name", &name, - G_MARKUP_COLLECT_INVALID)) - { - _gtk_builder_prefix_error (data->builder, context, error); - return; - } - - data->name = g_strdup (name); - g_markup_parse_context_get_position (context, &data->line, &data->col); - } - else - { - _gtk_builder_error_unhandled_tag (data->builder, context, - "GtkWindow", element_name, - error); - } -} - -static const GMarkupParser focus_parser = -{ - focus_start_element -}; - static gboolean gtk_window_buildable_custom_tag_start (GtkBuildable *buildable, GtkBuilder *builder, @@ -2441,21 +2366,6 @@ gtk_window_buildable_custom_tag_start (GtkBuildable *buildable, return TRUE; } - if (strcmp (tagname, "initial-focus") == 0) - { - NameSubParserData *data; - - data = g_slice_new0 (NameSubParserData); - data->name = NULL; - data->object = G_OBJECT (buildable); - data->builder = builder; - - *parser = focus_parser; - *parser_data = data; - - return TRUE; - } - return FALSE; } @@ -2478,23 +2388,6 @@ gtk_window_buildable_custom_finished (GtkBuildable *buildable, g_slice_free (GSListSubParserData, data); } - - if (strcmp (tagname, "initial-focus") == 0) - { - NameSubParserData *data = (NameSubParserData*)user_data; - - if (data->name) - { - GObject *object; - - object = _gtk_builder_lookup_object (builder, data->name, data->line, data->col); - if (object) - gtk_window_set_focus (GTK_WINDOW (buildable), GTK_WIDGET (object)); - g_free (data->name); - } - - g_slice_free (NameSubParserData, data); - } } static GdkDisplay * @@ -2771,70 +2664,6 @@ gtk_window_get_role (GtkWindow *window) return priv->wm_role; } -/** - * gtk_window_set_focus: - * @window: a #GtkWindow - * @focus: (allow-none): widget to be the new focus widget, or %NULL to unset - * any focus widget for the toplevel window. - * - * If @focus is not the current focus widget, and is focusable, sets - * it as the focus widget for the window. If @focus is %NULL, unsets - * the focus widget for this window. To set the focus to a particular - * widget in the toplevel, it is usually more convenient to use - * gtk_widget_grab_focus() instead of this function. - **/ -void -gtk_window_set_focus (GtkWindow *window, - GtkWidget *focus) -{ - GtkWindowPrivate *priv = gtk_window_get_instance_private (window); - GtkWidget *parent; - - g_return_if_fail (GTK_IS_WINDOW (window)); - - if (focus) - { - g_return_if_fail (GTK_IS_WIDGET (focus)); - g_return_if_fail (gtk_widget_get_can_focus (focus)); - - if (!gtk_widget_get_visible (GTK_WIDGET (window))) - priv->initial_focus = focus; - else - gtk_widget_grab_focus (focus); - } - else - { - /* Clear the existing focus chain, so that when we focus into - * the window again, we start at the beginnning. - */ - GtkWidget *widget = priv->focus_widget; - if (widget) - { - while ((parent = _gtk_widget_get_parent (widget))) - { - widget = parent; - gtk_widget_set_focus_child (widget, NULL); - } - } - - _gtk_window_internal_set_focus (window, NULL); - } -} - -void -_gtk_window_internal_set_focus (GtkWindow *window, - GtkWidget *focus) -{ - GtkWindowPrivate *priv = gtk_window_get_instance_private (window); - - g_return_if_fail (GTK_IS_WINDOW (window)); - - priv->initial_focus = NULL; - if ((priv->focus_widget != focus) || - (focus && !gtk_widget_has_focus (focus))) - g_signal_emit (window, window_signals[SET_FOCUS], 0, focus); -} - /** * gtk_window_set_default: * @window: a #GtkWindow @@ -5774,7 +5603,7 @@ gtk_window_show (GtkWidget *widget) if (priv->initial_focus) gtk_window_set_focus (window, priv->initial_focus); else - gtk_window_move_focus (widget, GTK_DIR_TAB_FORWARD); + gtk_widget_child_focus (widget, GTK_DIR_TAB_FORWARD); } if (priv->modal) @@ -7072,42 +6901,31 @@ gtk_window_real_activate_focus (GtkWindow *window) static void do_focus_change (GtkWidget *widget, - gboolean in) + gboolean in) { GdkSeat *seat; - GList *devices, *d; - - g_object_ref (widget); + GdkDevice *device; + GdkEvent *event; seat = gdk_display_get_default_seat (gtk_widget_get_display (widget)); - devices = gdk_seat_get_slaves (seat, GDK_SEAT_CAPABILITY_KEYBOARD); - devices = g_list_prepend (devices, gdk_seat_get_keyboard (seat)); + device = gdk_seat_get_keyboard (seat); - for (d = devices; d; d = d->next) - { - GdkDevice *dev = d->data; - GdkEvent *fevent; - GdkSurface *surface; - - surface = _gtk_widget_get_surface (widget); + event = gdk_event_new (GDK_FOCUS_CHANGE); + gdk_event_set_display (event, gtk_widget_get_display (widget)); + gdk_event_set_device (event, device); - fevent = gdk_event_new (GDK_FOCUS_CHANGE); - gdk_event_set_display (fevent, gtk_widget_get_display (widget)); + event->any.type = GDK_FOCUS_CHANGE; + event->any.surface = _gtk_widget_get_surface (widget); + if (event->any.surface) + g_object_ref (event->any.surface); + event->focus_change.in = in; + event->focus_change.mode = GDK_CROSSING_STATE_CHANGED; + event->focus_change.detail = GDK_NOTIFY_ANCESTOR; - fevent->any.type = GDK_FOCUS_CHANGE; - fevent->any.surface = surface; - if (surface) - g_object_ref (surface); - fevent->focus_change.in = in; - gdk_event_set_device (fevent, dev); - - gtk_widget_send_focus_change (widget, fevent); - - g_object_unref (fevent); - } + gtk_widget_set_has_focus (widget, in); + gtk_widget_event (widget, event); - g_list_free (devices); - g_object_unref (widget); + g_object_unref (event); } static gboolean @@ -7233,172 +7051,56 @@ gtk_window_forall (GtkContainer *container, (* callback) (priv->title_box, callback_data); } -static gboolean -gtk_window_focus (GtkWidget *widget, - GtkDirectionType direction) +/** + * gtk_window_set_focus: + * @window: a #GtkWindow + * @focus: (allow-none): widget to be the new focus widget, or %NULL to unset + * any focus widget for the toplevel window. + * + * If @focus is not the current focus widget, and is focusable, sets + * it as the focus widget for the window. If @focus is %NULL, unsets + * the focus widget for this window. To set the focus to a particular + * widget in the toplevel, it is usually more convenient to use + * gtk_widget_grab_focus() instead of this function. + **/ +void +gtk_window_set_focus (GtkWindow *window, + GtkWidget *focus) { - GtkWindow *window = GTK_WINDOW (widget); GtkWindowPrivate *priv = gtk_window_get_instance_private (window); - GtkBin *bin; - GtkContainer *container; - GtkWidget *child; - GtkWidget *old_focus_child; - GtkWidget *parent; - - if (!_gtk_widget_is_toplevel (widget)) - return GTK_WIDGET_CLASS (gtk_window_parent_class)->focus (widget, direction); - - container = GTK_CONTAINER (widget); - bin = GTK_BIN (widget); - - old_focus_child = gtk_widget_get_focus_child (widget); - - /* We need a special implementation here to deal properly with wrapping - * around in the tab chain without the danger of going into an - * infinite loop. - */ - if (old_focus_child) - { - if (gtk_widget_child_focus (old_focus_child, direction)) - return TRUE; - } - - if (priv->focus_widget) - { - if (direction == GTK_DIR_LEFT || - direction == GTK_DIR_RIGHT || - direction == GTK_DIR_UP || - direction == GTK_DIR_DOWN) - { - return FALSE; - } - - /* Wrapped off the end, clear the focus setting for the toplpevel */ - parent = _gtk_widget_get_parent (priv->focus_widget); - while (parent) - { - gtk_widget_set_focus_child (parent, NULL); - parent = _gtk_widget_get_parent (parent); - } - - gtk_window_set_focus (GTK_WINDOW (container), NULL); - } - - /* Now try to focus the first widget in the window, - * taking care to hook titlebar widgets into the - * focus chain. - */ - if (priv->title_box != NULL && - old_focus_child != NULL && - priv->title_box != old_focus_child) - child = priv->title_box; - else - child = gtk_bin_get_child (bin); - - if (child) - { - if (gtk_widget_child_focus (child, direction)) - return TRUE; - else if (priv->title_box != NULL && - priv->title_box != child && - gtk_widget_child_focus (priv->title_box, direction)) - return TRUE; - - } - - return FALSE; -} - -static void -gtk_window_move_focus (GtkWidget *widget, - GtkDirectionType dir) -{ - if (!_gtk_widget_is_toplevel (widget)) - { - GTK_WIDGET_CLASS (gtk_window_parent_class)->move_focus (widget, dir); - return; - } - - gtk_widget_child_focus (widget, dir); - - if (!gtk_widget_get_focus_child (widget)) - gtk_window_set_focus (GTK_WINDOW (widget), NULL); -} + GtkWidget *old_focus = NULL; + GdkSeat *seat; + GdkDevice *device; + GdkEvent *event; -static void -gtk_window_real_set_focus (GtkWindow *window, - GtkWidget *focus) -{ - GtkWindowPrivate *priv = gtk_window_get_instance_private (window); - GtkWidget *old_focus = priv->focus_widget; + g_return_if_fail (GTK_IS_WINDOW (window)); - if (old_focus) - { - g_object_ref (old_focus); - g_object_freeze_notify (G_OBJECT (old_focus)); - } - if (focus) - { - g_object_ref (focus); - g_object_freeze_notify (G_OBJECT (focus)); - } + if (focus && !gtk_widget_can_take_focus (focus)) + return; if (priv->focus_widget) - { - if (gtk_widget_get_receives_default (priv->focus_widget) && - (priv->focus_widget != priv->default_widget)) - { - _gtk_widget_set_has_default (priv->focus_widget, FALSE); + old_focus = g_object_ref (priv->focus_widget); + g_set_object (&priv->focus_widget, NULL); - if (priv->default_widget) - _gtk_widget_set_has_default (priv->default_widget, TRUE); - } - - priv->focus_widget = NULL; - - if (priv->is_active) - do_focus_change (old_focus, FALSE); - - g_object_notify (G_OBJECT (old_focus), "is-focus"); - } + seat = gdk_display_get_default_seat (gtk_widget_get_display (GTK_WIDGET (window))); + device = gdk_seat_get_keyboard (seat); - /* The above notifications may have set a new focus widget, - * if so, we don't want to override it. - */ - if (focus && !priv->focus_widget) - { - priv->focus_widget = focus; + event = gdk_event_new (GDK_FOCUS_CHANGE); + gdk_event_set_display (event, gtk_widget_get_display (GTK_WIDGET (window))); + gdk_event_set_device (event, device); + event->any.surface = _gtk_widget_get_surface (GTK_WIDGET (window)); + if (event->any.surface) + g_object_ref (event->any.surface); - if (gtk_widget_get_receives_default (priv->focus_widget) && - (priv->focus_widget != priv->default_widget)) - { - if (gtk_widget_get_can_default (priv->focus_widget)) - _gtk_widget_set_has_default (priv->focus_widget, TRUE); + gtk_synthesize_crossing_events (window, old_focus, focus, event, GDK_CROSSING_NORMAL); - if (priv->default_widget) - _gtk_widget_set_has_default (priv->default_widget, FALSE); - } + g_object_unref (event); - if (priv->is_active) - do_focus_change (priv->focus_widget, TRUE); + g_set_object (&priv->focus_widget, focus); - /* It's possible for do_focus_change() above to have callbacks - * that clear priv->focus_widget here. - */ - if (priv->focus_widget) - g_object_notify (G_OBJECT (priv->focus_widget), "is-focus"); - } + g_clear_object (&old_focus); - if (old_focus) - { - g_object_thaw_notify (G_OBJECT (old_focus)); - g_object_unref (old_focus); - } - if (focus) - { - g_object_thaw_notify (G_OBJECT (focus)); - g_object_unref (focus); - } + g_object_notify (G_OBJECT (window), "focus-widget"); } static void diff --git a/gtk/gtkwindow.h b/gtk/gtkwindow.h index fb30ab99b8e475169272f5133102cc9d53dea60e..280658b278cfc4991072105ed774ac3b59f1cf9c 100644 --- a/gtk/gtkwindow.h +++ b/gtk/gtkwindow.h @@ -70,11 +70,6 @@ struct _GtkWindowClass { GtkBinClass parent_class; - /*< public >*/ - - void (* set_focus) (GtkWindow *window, - GtkWidget *focus); - /* G_SIGNAL_ACTION signals for keybindings */ void (* activate_focus) (GtkWindow *window); diff --git a/gtk/inspector/focusoverlay.c b/gtk/inspector/focusoverlay.c new file mode 100644 index 0000000000000000000000000000000000000000..a46d29ec50eafa2235ec69d34c57eeb15633a646 --- /dev/null +++ b/gtk/inspector/focusoverlay.c @@ -0,0 +1,135 @@ +#include "config.h" +#include "focusoverlay.h" +#include "gtkwidgetprivate.h" + +struct _GtkFocusOverlay +{ + GtkInspectorOverlay parent_instance; +}; + +struct _GtkFocusOverlayClass +{ + GtkInspectorOverlayClass parent_class; +}; + +G_DEFINE_TYPE (GtkFocusOverlay, gtk_focus_overlay, GTK_TYPE_INSPECTOR_OVERLAY) + +static void +draw_focus_location (GtkWidget *widget, + GtkWidget *child, + GtkSnapshot *snapshot, + const char *color, + const char *text) +{ + GtkAllocation allocation; + graphene_rect_t bounds; + PangoLayout *layout; + PangoRectangle rect; + GskRoundedRect rrect; + int x, y; + GdkRGBA rgba; + + if (child == NULL) + return; + + gdk_rgba_parse (&rgba, color); + + gtk_widget_get_allocation (child, &allocation); + + gtk_snapshot_save (snapshot); + + gtk_widget_translate_coordinates (child, widget, 0, 0, &x, &y); + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y)); + + gtk_snapshot_push_debug (snapshot, "Widget focus debugging"); + + layout = gtk_widget_create_pango_layout (widget, NULL); + pango_layout_set_markup (layout, text, -1); + pango_layout_get_extents (layout, NULL, &rect); + graphene_rect_init (&bounds, + 0, 0, + rect.width / PANGO_SCALE + 20, + rect.height / PANGO_SCALE + 20); + gsk_rounded_rect_init_from_rect (&rrect, &bounds, 10.0f); + + gtk_snapshot_push_rounded_clip (snapshot, &rrect); + + gtk_snapshot_append_color (snapshot, &rgba, &bounds); + + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (10, 10)); + gtk_snapshot_append_layout (snapshot, layout, &(GdkRGBA) { 1, 1, 1, 1 }); + g_object_unref (layout); + + gtk_snapshot_pop (snapshot); + gtk_snapshot_pop (snapshot); + + gtk_snapshot_restore (snapshot); +} + +static void +draw_focus_locations (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + GtkWidget *focus; + GtkWidget *next; + int i; + char text[20]; + + if (!gtk_widget_get_mapped (widget)) + return; + + focus = gtk_root_get_focus (GTK_ROOT (widget)); + if (focus == NULL) + return; + + draw_focus_location (widget, focus, snapshot, "rgba(0,0,255,0.4)", "Focus"); + + next = focus; + for (i = 1; i < 4; i++) + { + g_snprintf (text, 20, " %d", i); + next = gtk_widget_get_next_focus (next, GTK_DIR_TAB_FORWARD); + if (!next) + break; + draw_focus_location (widget, next, snapshot, "rgba(255,0,255,0.4)", text); + } + + next = focus; + for (i = 1; i < 4; i++) + { + g_snprintf (text, 20, " %d", i); + next = gtk_widget_get_next_focus (next, GTK_DIR_TAB_BACKWARD); + if (!next) + break; + draw_focus_location (widget, next, snapshot, "rgba(255,0,255,0.4)", text); + } +} + +static void +gtk_focus_overlay_snapshot (GtkInspectorOverlay *overlay, + GtkSnapshot *snapshot, + GskRenderNode *node, + GtkWidget *widget) +{ + draw_focus_locations (widget, snapshot); +} + +static void +gtk_focus_overlay_init (GtkFocusOverlay *self) +{ + +} + +static void +gtk_focus_overlay_class_init (GtkFocusOverlayClass *klass) +{ + GtkInspectorOverlayClass *overlay_class = (GtkInspectorOverlayClass *)klass; + + overlay_class->snapshot = gtk_focus_overlay_snapshot; +} + +GtkInspectorOverlay * +gtk_focus_overlay_new (void) +{ + return g_object_new (GTK_TYPE_FOCUS_OVERLAY, NULL); +} diff --git a/gtk/inspector/focusoverlay.h b/gtk/inspector/focusoverlay.h new file mode 100644 index 0000000000000000000000000000000000000000..429a3ef94d04a4a0d8b7c681ac3658049f696a71 --- /dev/null +++ b/gtk/inspector/focusoverlay.h @@ -0,0 +1,19 @@ + + +#ifndef __GTK_FOCUS_OVERLAY_H__ +#define __GTK_FOCUS_OVERLAY_H__ + +#include "inspectoroverlay.h" + +G_BEGIN_DECLS + +#define GTK_TYPE_FOCUS_OVERLAY (gtk_focus_overlay_get_type ()) +G_DECLARE_FINAL_TYPE (GtkFocusOverlay, gtk_focus_overlay, GTK, FOCUS_OVERLAY, GtkInspectorOverlay) + +GtkInspectorOverlay * gtk_focus_overlay_new (void); + +G_END_DECLS + + + +#endif diff --git a/gtk/inspector/meson.build b/gtk/inspector/meson.build index a134f8f278d347ef6c45b75bf34a841db54cc056..92bd8020d1d6aa037af39f5c1a852f2fd1fa70a1 100644 --- a/gtk/inspector/meson.build +++ b/gtk/inspector/meson.build @@ -6,6 +6,7 @@ inspector_sources = files( 'css-editor.c', 'css-node-tree.c', 'data-list.c', + 'focusoverlay.c', 'fpsoverlay.c', 'general.c', 'graphdata.c', diff --git a/gtk/inspector/misc-info.c b/gtk/inspector/misc-info.c index 9c3b3ec05f960708c332a11d1dff07eba2f4064c..1d9d053d7db11d4556bd2e48b7247fb2f3edb87a 100644 --- a/gtk/inspector/misc-info.c +++ b/gtk/inspector/misc-info.c @@ -46,9 +46,6 @@ struct _GtkInspectorMiscInfoPrivate { GtkWidget *default_widget_row; GtkWidget *default_widget; GtkWidget *default_widget_button; - GtkWidget *focus_widget_row; - GtkWidget *focus_widget; - GtkWidget *focus_widget_button; GtkWidget *mnemonic_label_row; GtkWidget *mnemonic_label; GtkWidget *request_mode_row; @@ -217,43 +214,6 @@ show_default_widget (GtkWidget *button, GtkInspectorMiscInfo *sl) show_object (sl, G_OBJECT (widget), "properties"); } -static void -update_focus_widget (GtkInspectorMiscInfo *sl) -{ - GtkWidget *widget; - - widget = gtk_window_get_focus (GTK_WINDOW (sl->priv->object)); - if (widget) - { - gchar *tmp; - tmp = g_strdup_printf ("%p", widget); - gtk_label_set_label (GTK_LABEL (sl->priv->focus_widget), tmp); - g_free (tmp); - gtk_widget_set_sensitive (sl->priv->focus_widget_button, TRUE); - } - else - { - gtk_label_set_label (GTK_LABEL (sl->priv->focus_widget), "NULL"); - gtk_widget_set_sensitive (sl->priv->focus_widget_button, FALSE); - } -} - -static void -set_focus_cb (GtkWindow *window, GtkWidget *focus, GtkInspectorMiscInfo *sl) -{ - update_focus_widget (sl); -} - -static void -show_focus_widget (GtkWidget *button, GtkInspectorMiscInfo *sl) -{ - GtkWidget *widget; - - widget = gtk_window_get_focus (GTK_WINDOW (sl->priv->object)); - if (widget) - show_object (sl, G_OBJECT (widget), "properties"); -} - static void show_mnemonic_label (GtkWidget *button, GtkInspectorMiscInfo *sl) { @@ -358,7 +318,6 @@ update_info (gpointer data) if (GTK_IS_WINDOW (sl->priv->object)) { update_default_widget (sl); - update_focus_widget (sl); } if (GDK_IS_FRAME_CLOCK (sl->priv->object)) @@ -408,7 +367,6 @@ gtk_inspector_misc_info_set_object (GtkInspectorMiscInfo *sl, if (sl->priv->object) { g_signal_handlers_disconnect_by_func (sl->priv->object, state_flags_changed, sl); - g_signal_handlers_disconnect_by_func (sl->priv->object, set_focus_cb, sl); g_signal_handlers_disconnect_by_func (sl->priv->object, allocation_changed, sl); disconnect_each_other (sl->priv->object, G_OBJECT (sl)); disconnect_each_other (sl, sl->priv->object); @@ -475,14 +433,10 @@ gtk_inspector_misc_info_set_object (GtkInspectorMiscInfo *sl, if (GTK_IS_WINDOW (object)) { gtk_widget_show (sl->priv->default_widget_row); - gtk_widget_show (sl->priv->focus_widget_row); - - g_signal_connect_object (object, "set-focus", G_CALLBACK (set_focus_cb), sl, G_CONNECT_AFTER); } else { gtk_widget_hide (sl->priv->default_widget_row); - gtk_widget_hide (sl->priv->focus_widget_row); } if (GDK_IS_FRAME_CLOCK (object)) @@ -595,9 +549,6 @@ gtk_inspector_misc_info_class_init (GtkInspectorMiscInfoClass *klass) gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorMiscInfo, default_widget_row); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorMiscInfo, default_widget); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorMiscInfo, default_widget_button); - gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorMiscInfo, focus_widget_row); - gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorMiscInfo, focus_widget); - gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorMiscInfo, focus_widget_button); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorMiscInfo, mnemonic_label_row); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorMiscInfo, mnemonic_label); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorMiscInfo, request_mode_row); @@ -631,7 +582,6 @@ gtk_inspector_misc_info_class_init (GtkInspectorMiscInfoClass *klass) gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorMiscInfo, child_visible); gtk_widget_class_bind_template_callback (widget_class, show_default_widget); - gtk_widget_class_bind_template_callback (widget_class, show_focus_widget); gtk_widget_class_bind_template_callback (widget_class, show_frame_clock); } diff --git a/gtk/inspector/misc-info.ui b/gtk/inspector/misc-info.ui index 0ee5082e240a7688dfa24d701eae3d5b1f48400c..cbb3ef65b43f39eff3fa1df03f717d4a0d840316 100644 --- a/gtk/inspector/misc-info.ui +++ b/gtk/inspector/misc-info.ui @@ -157,42 +157,6 @@ - - - 0 - - - 10 - 40 - - - Focus Widget - start - baseline - 0.0 - 1 - - - - - 1 - end - baseline - end - - - - - end - baseline - Properties - - - - - - - 0 @@ -616,7 +580,6 @@ - diff --git a/gtk/inspector/visual.c b/gtk/inspector/visual.c index 5a21d1d316ee29a04fc1f11b7d222d7518830064..930c51659fdf2d4be4d36bb63536a4ea5370c7f1 100644 --- a/gtk/inspector/visual.c +++ b/gtk/inspector/visual.c @@ -20,6 +20,7 @@ #include "visual.h" +#include "focusoverlay.h" #include "fpsoverlay.h" #include "updatesoverlay.h" #include "layoutoverlay.h" @@ -83,6 +84,7 @@ struct _GtkInspectorVisualPrivate GtkInspectorOverlay *fps_overlay; GtkInspectorOverlay *updates_overlay; GtkInspectorOverlay *layout_overlay; + GtkInspectorOverlay *focus_overlay; }; G_DEFINE_TYPE_WITH_PRIVATE (GtkInspectorVisual, gtk_inspector_visual, GTK_TYPE_SCROLLED_WINDOW) @@ -298,6 +300,41 @@ updates_activate (GtkSwitch *sw, redraw_everything (); } +static void +focus_activate (GtkSwitch *sw, + GParamSpec *pspec, + GtkInspectorVisual *vis) +{ + GtkInspectorVisualPrivate *priv = vis->priv; + GtkInspectorWindow *iw; + gboolean focus; + + focus = gtk_switch_get_active (sw); + iw = GTK_INSPECTOR_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (vis))); + if (iw == NULL) + return; + + if (focus) + { + if (priv->focus_overlay == NULL) + { + priv->focus_overlay = gtk_focus_overlay_new (); + gtk_inspector_window_add_overlay (iw, priv->focus_overlay); + g_object_unref (priv->focus_overlay); + } + } + else + { + if (priv->focus_overlay != NULL) + { + gtk_inspector_window_remove_overlay (iw, priv->focus_overlay); + priv->focus_overlay = NULL; + } + } + + redraw_everything (); +} + static void baselines_activate (GtkSwitch *sw) { @@ -952,6 +989,7 @@ gtk_inspector_visual_class_init (GtkInspectorVisualClass *klass) gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorVisual, font_scale_entry); gtk_widget_class_bind_template_child_private (widget_class, GtkInspectorVisual, font_scale_adjustment); + gtk_widget_class_bind_template_callback (widget_class, focus_activate); gtk_widget_class_bind_template_callback (widget_class, fps_activate); gtk_widget_class_bind_template_callback (widget_class, updates_activate); gtk_widget_class_bind_template_callback (widget_class, direction_changed); diff --git a/gtk/inspector/visual.ui b/gtk/inspector/visual.ui index 1b143fd291dea6fb92c3191845fbc54692a608eb..a9948e7d61b133a28cb5bc98b56cb72faa97a62d 100644 --- a/gtk/inspector/visual.ui +++ b/gtk/inspector/visual.ui @@ -371,6 +371,33 @@ none + + + 0 + + + 10 + 40 + + + Show focus overlay + start + baseline + 0.0 + + + + + end + baseline + 1 + + + + + + + 0 diff --git a/tests/dialog.ui b/tests/dialog.ui index 93aa2015bc65379fc2af4f58407e2ef125f007a9..baa032ec48f74eaeef6ba9fd1cecfac30fe4e48a 100644 --- a/tests/dialog.ui +++ b/tests/dialog.ui @@ -239,6 +239,5 @@ cancel_button confirm_button - diff --git a/tests/testtoolbar.c b/tests/testtoolbar.c index 57c7ceb1b3fac473cc62ca5e1a057181f4e9a939..12160c8115f6823d7926abcfbc8ef820c4adff53 100644 --- a/tests/testtoolbar.c +++ b/tests/testtoolbar.c @@ -344,7 +344,7 @@ popup_context_menu (GtkToolbar *toolbar, gint x, gint y, gint button_number) GtkWidget *widget; window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (toolbar))); - widget = gtk_window_get_focus (window); + widget = gtk_root_get_focus (GTK_ROOT (window)); if (!widget) widget = GTK_WIDGET (toolbar); diff --git a/testsuite/gtk/focus-chain/boxes.txt b/testsuite/gtk/focus-chain/boxes.txt new file mode 100644 index 0000000000000000000000000000000000000000..623adf43f78aaf44fcaebe1a9b13b788abb72e96 --- /dev/null +++ b/testsuite/gtk/focus-chain/boxes.txt @@ -0,0 +1,9 @@ +button1 +button2 +button3 +button4 +button5 +button6 +button7 +button8 +button9 diff --git a/testsuite/gtk/focus-chain/boxes.ui b/testsuite/gtk/focus-chain/boxes.ui new file mode 100644 index 0000000000000000000000000000000000000000..1bed0042a71b17ef6c89746befcf2029ed7f2837 --- /dev/null +++ b/testsuite/gtk/focus-chain/boxes.ui @@ -0,0 +1,60 @@ + + + + + horizontal + + + vertical + + + + + + + + + + + + + + + + + vertical + + + + + + + + + + + + + + + + + vertical + + + + + + + + + + + + + + + + + + diff --git a/testsuite/gtk/focus-chain/focus.test.in b/testsuite/gtk/focus-chain/focus.test.in new file mode 100644 index 0000000000000000000000000000000000000000..2b15a4cf0a2b70cff341106e938e08708e08514c --- /dev/null +++ b/testsuite/gtk/focus-chain/focus.test.in @@ -0,0 +1,4 @@ +[Test] +Exec=@libexecdir@/installed-tests/gtk-4.0/gtk/focus/test-focus --tap -k +Type=session +Output=TAP diff --git a/testsuite/gtk/focus-chain/meson.build b/testsuite/gtk/focus-chain/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..4de0970d378333a85f2fb426e3e5f13921c0916e --- /dev/null +++ b/testsuite/gtk/focus-chain/meson.build @@ -0,0 +1,34 @@ +testexecdir = join_paths(installed_test_bindir, 'gtk', 'focus-chain') +testdatadir = join_paths(installed_test_datadir, 'gtk', 'focus-chain') + +test_nodes = executable('test-focus', 'test-focus.c', + install: get_option('install-tests'), + install_dir: testexecdir, + dependencies: libgtk_dep) +test('nodes', test_nodes, + args: [ '--tap', '-k' ], + env: [ 'GIO_USE_VOLUME_MONITOR=unix', + 'GSETTINGS_BACKEND=memory', + 'GTK_CSD=1', + 'G_ENABLE_DIAGNOSTIC=0', + 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), + 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()) + ], + suite: 'css') + +test_data = [ + 'widget-factory.ui', + 'widget-factory.txt', +] + +if get_option('install-tests') + conf = configuration_data() + conf.set('libexecdir', gtk_libexecdir) + configure_file(input: 'focus.test.in', + output: 'focus.test', + configuration: conf, + install_dir: testdatadir) + + install_data(test_data, install_dir: testexecdir) + +endif diff --git a/testsuite/gtk/focus-chain/notebook.txt b/testsuite/gtk/focus-chain/notebook.txt new file mode 100644 index 0000000000000000000000000000000000000000..3fb79f17b02e8bd473341a2d0858090b1fb880cb --- /dev/null +++ b/testsuite/gtk/focus-chain/notebook.txt @@ -0,0 +1,2 @@ +notebook1 +page1 diff --git a/testsuite/gtk/focus-chain/notebook.ui b/testsuite/gtk/focus-chain/notebook.ui new file mode 100644 index 0000000000000000000000000000000000000000..e18759ca4274ad5230fd7eaa4f0230b05b84b3be --- /dev/null +++ b/testsuite/gtk/focus-chain/notebook.ui @@ -0,0 +1,37 @@ + + + popup + + + + + + + Yes + + + + + Tab 1 + + + + + + + + + No + + + + + Tab 2 + + + + + + + + diff --git a/testsuite/gtk/focus-chain/stack.txt b/testsuite/gtk/focus-chain/stack.txt new file mode 100644 index 0000000000000000000000000000000000000000..8687924026038248fdf5e7d0dc65f88d7174b344 --- /dev/null +++ b/testsuite/gtk/focus-chain/stack.txt @@ -0,0 +1,3 @@ +GtkToggleButton +GtkToggleButton +page1 diff --git a/testsuite/gtk/focus-chain/stack.ui b/testsuite/gtk/focus-chain/stack.ui new file mode 100644 index 0000000000000000000000000000000000000000..2ce89fd034223fd34d0fe2cc028d8ed02b9d9cfd --- /dev/null +++ b/testsuite/gtk/focus-chain/stack.ui @@ -0,0 +1,40 @@ + + + popup + + + + + stack1 + + + + + + + page1 + Page 1 + + + Yes + + + + + + + page2 + Page 2 + + + No + + + + + + + + + + diff --git a/testsuite/gtk/focus-chain/stack2.txt b/testsuite/gtk/focus-chain/stack2.txt new file mode 100644 index 0000000000000000000000000000000000000000..09079ee932cad45d05973e9faf06a8528f4b7962 --- /dev/null +++ b/testsuite/gtk/focus-chain/stack2.txt @@ -0,0 +1,3 @@ +page1 +GtkToggleButton +GtkToggleButton diff --git a/testsuite/gtk/focus-chain/stack2.ui b/testsuite/gtk/focus-chain/stack2.ui new file mode 100644 index 0000000000000000000000000000000000000000..3b43c3f3c429bc6e19bd4293c649466a62022831 --- /dev/null +++ b/testsuite/gtk/focus-chain/stack2.ui @@ -0,0 +1,40 @@ + + + popup + + + + + + + page1 + Page 1 + + + Yes + + + + + + + page2 + Page 2 + + + No + + + + + + + + + stack1 + + + + + + diff --git a/testsuite/gtk/focus-chain/test-focus.c b/testsuite/gtk/focus-chain/test-focus.c new file mode 100644 index 0000000000000000000000000000000000000000..468d0f4c5f7c8f28407e1022f70176ab58c701b7 --- /dev/null +++ b/testsuite/gtk/focus-chain/test-focus.c @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2019 Red Hat Inc. + * + * Author: + * Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library. If not, see . + */ + +#include +#include +#include + +#ifdef G_OS_WIN32 +# include +#endif + + +static char * +test_get_reference_file (const char *ui_file) +{ + GString *file = g_string_new (NULL); + + if (g_str_has_suffix (ui_file, ".ui")) + g_string_append_len (file, ui_file, strlen (ui_file) - 3); + else + g_string_append (file, ui_file); + + g_string_append (file, ".txt"); + + if (!g_file_test (file->str, G_FILE_TEST_EXISTS)) + { + g_string_free (file, TRUE); + return g_strdup (ui_file); + } + + return g_string_free (file, FALSE); +} + +static char * +diff_with_file (const char *file1, + char *text, + gssize len, + GError **error) +{ + const char *command[] = { "diff", "-u", file1, NULL, NULL }; + char *diff, *tmpfile; + int fd; + + diff = NULL; + + if (len < 0) + len = strlen (text); + + /* write the text buffer to a temporary file */ + fd = g_file_open_tmp (NULL, &tmpfile, error); + if (fd < 0) + return NULL; + + if (write (fd, text, len) != (int) len) + { + close (fd); + g_set_error (error, + G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Could not write data to temporary file '%s'", tmpfile); + goto done; + } + close (fd); + command[3] = tmpfile; + + /* run diff command */ + g_spawn_sync (NULL, + (char **) command, + NULL, + G_SPAWN_SEARCH_PATH, + NULL, NULL, + &diff, + NULL, NULL, + error); + +done: + g_unlink (tmpfile); + g_free (tmpfile); + + return diff; +} + +static char * +generate_output (GtkWidget *window) +{ + GString *s = g_string_new (""); + GtkWidget *initial_focus; + GtkWidget *f; + + initial_focus = gtk_root_get_focus (GTK_ROOT (window)); + if (initial_focus == NULL) + { + g_string_append (s, "NULL\n"); + initial_focus = gtk_widget_get_next_focus (window, GTK_DIR_TAB_FORWARD); + } + + f = initial_focus; + while (f) + { + const char *name = gtk_buildable_get_name (GTK_BUILDABLE (f)); + if (name == NULL) + name = G_OBJECT_TYPE_NAME (f); + g_string_append_printf (s, "%s\n", name); + f = gtk_widget_get_next_focus (f, GTK_DIR_TAB_FORWARD); + if (f == initial_focus) + break; + } + + if (f == NULL) + g_string_append (s, "NULL\n"); + + return g_string_free (s, FALSE); +} + +static void +load_ui_file (GFile *file, gboolean generate) +{ + GtkBuilder *builder; + GtkWidget *window; + char *output, *diff; + char *ui_file, *reference_file; + GError *error = NULL; + + ui_file = g_file_get_path (file); + + builder = gtk_builder_new_from_file (ui_file); + window = GTK_WIDGET (gtk_builder_get_object (builder, "window1")); + + g_assert (window != NULL); + + gtk_widget_show (window); + + output = generate_output (window); + + if (generate) + { + g_print ("%s", output); + goto out; + } + + reference_file = test_get_reference_file (ui_file); + + diff = diff_with_file (reference_file, output, -1, &error); + g_assert_no_error (error); + + if (diff && diff[0]) + { + g_test_message ("Resulting output doesn't match reference:\n%s", diff); + g_test_fail (); + } + g_free (reference_file); + g_free (diff); + +out: + g_free (output); + g_free (ui_file); +} + +static void +test_ui_file (GFile *file) +{ + load_ui_file (file, FALSE); +} + +static void +add_test_for_file (GFile *file) +{ + char *path; + + path = g_file_get_path (file); + + g_test_add_vtable (path, + 0, + g_object_ref (file), + NULL, + (GTestFixtureFunc) test_ui_file, + (GTestFixtureFunc) g_object_unref); + + g_free (path); +} + +static int +compare_files (gconstpointer a, gconstpointer b) +{ + GFile *file1 = G_FILE (a); + GFile *file2 = G_FILE (b); + char *path1, *path2; + int result; + + path1 = g_file_get_path (file1); + path2 = g_file_get_path (file2); + + result = strcmp (path1, path2); + + g_free (path1); + g_free (path2); + + return result; +} + +static void +add_tests_for_files_in_directory (GFile *dir) +{ + GFileEnumerator *enumerator; + GFileInfo *info; + GList *files; + GError *error = NULL; + + enumerator = g_file_enumerate_children (dir, G_FILE_ATTRIBUTE_STANDARD_NAME, 0, NULL, &error); + g_assert_no_error (error); + files = NULL; + + while ((info = g_file_enumerator_next_file (enumerator, NULL, &error))) + { + const char *filename; + + filename = g_file_info_get_name (info); + + if (!g_str_has_suffix (filename, ".ui") || + g_str_has_suffix (filename, ".txt")) + { + g_object_unref (info); + continue; + } + + files = g_list_prepend (files, g_file_get_child (dir, filename)); + + g_object_unref (info); + } + + g_assert_no_error (error); + g_object_unref (enumerator); + + files = g_list_sort (files, compare_files); + g_list_foreach (files, (GFunc) add_test_for_file, NULL); + g_list_free_full (files, g_object_unref); +} + +int +main (int argc, char *argv[]) +{ + gtk_test_init (&argc, &argv); + + if (argc < 2) + { + const char *basedir; + GFile *dir; + + basedir = g_test_get_dir (G_TEST_DIST); + dir = g_file_new_for_path (basedir); + add_tests_for_files_in_directory (dir); + + g_object_unref (dir); + } + else if (strcmp (argv[1], "--generate") == 0) + { + if (argc >= 3) + { + GFile *file = g_file_new_for_commandline_arg (argv[2]); + + load_ui_file (file, TRUE); + + g_object_unref (file); + } + } + else + { + guint i; + + for (i = 1; i < argc; i++) + { + GFile *file = g_file_new_for_commandline_arg (argv[i]); + + add_test_for_file (file); + + g_object_unref (file); + } + } + + return g_test_run (); +} diff --git a/testsuite/gtk/focus.c b/testsuite/gtk/focus.c index 0605c8eb5387effb0d212f77f4079e0427fe8aa7..82c188e0e2459f4e485a49fcf7da0e72ca03cc15 100644 --- a/testsuite/gtk/focus.c +++ b/testsuite/gtk/focus.c @@ -1,41 +1,230 @@ #include +const char * +widget_name (GtkWidget *widget) +{ + if (!widget) + return NULL; + else if (gtk_widget_get_name (widget)) + return gtk_widget_get_name (widget); + else if (GTK_IS_LABEL (widget)) + return gtk_label_get_label (GTK_LABEL (widget)); + else if (GTK_IS_EDITABLE (widget)) + return gtk_editable_get_text (GTK_EDITABLE (widget)); + else + return G_OBJECT_TYPE_NAME (widget); +} + +static char * +mode_to_string (GdkCrossingMode mode) +{ + return g_enum_to_string (GDK_TYPE_CROSSING_MODE, mode); +} + +static char * +detail_to_string (GdkNotifyType detail) +{ + return g_enum_to_string (GDK_TYPE_NOTIFY_TYPE, detail); +} + +static void +add_event (GtkEventController *controller, + gboolean in, + GdkCrossingMode mode, + GdkNotifyType detail, + GString *s) +{ + GtkEventControllerKey *key = GTK_EVENT_CONTROLLER_KEY (controller); + gboolean is_focus; + gboolean contains_focus; + GtkWidget *widget = gtk_event_controller_get_widget (controller); + GtkWidget *origin = gtk_event_controller_key_get_focus_origin (key); + GtkWidget *target = gtk_event_controller_key_get_focus_target (key); + + g_object_get (controller, + "is-focus", &is_focus, + "contains-focus", &contains_focus, + NULL); + + g_string_append_printf (s, "%s: %s %s %s is-focus: %d contains-focus: %d origin: %s target: %s\n", + widget_name (widget), + in ? "focus-in" : "focus-out", + mode_to_string (mode), + detail_to_string (detail), + is_focus, + contains_focus, + widget_name (origin), + widget_name (target)); +} + +static void +focus_in (GtkEventController *controller, + GdkCrossingMode mode, + GdkNotifyType detail, + GString *s) +{ + add_event (controller, TRUE, mode, detail, s); +} + +static void +focus_out (GtkEventController *controller, + GdkCrossingMode mode, + GdkNotifyType detail, + GString *s) +{ + add_event (controller, FALSE, mode, detail, s); +} + +static void +verify_focus_chain (GtkWidget *window) +{ + GtkWidget *child, *last; + + child = window; + while (child) + { + last = child; + child = gtk_widget_get_focus_child (child); + } + + g_assert (last == gtk_root_get_focus (GTK_ROOT (window))); +} + +static void +add_controller (GtkWidget *widget, GString *s) +{ + GtkEventController *controller; + + controller = gtk_event_controller_key_new (); + g_signal_connect (controller, "focus-in", G_CALLBACK (focus_in), s); + g_signal_connect (controller, "focus-out", G_CALLBACK (focus_out), s); + gtk_widget_add_controller (widget, controller); +} + static void test_window_focus (void) { GtkWidget *window; GtkWidget *box; + GtkWidget *box1; + GtkWidget *box2; + GtkWidget *label1; + GtkWidget *label2; GtkWidget *entry1; GtkWidget *entry2; + GString *s = g_string_new (""); + + /* + * The tree look like this, with [] indicating + * focus locations: + * + * window + * | + * +----[box]-----+ + * | | | + * label1 box1 box2------+ + * | | | + * [entry1] label2 [entry2] + */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_set_name (window, "window"); + add_controller (window, s); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_set_can_focus (box, TRUE); + gtk_widget_set_name (box, "box"); + add_controller (box, s); gtk_container_add (GTK_CONTAINER (window), box); - gtk_container_add (GTK_CONTAINER (box), gtk_label_new ("label1")); + label1 = gtk_label_new ("label1"); + gtk_widget_set_name (label1, "label1"); + add_controller (label1, s); + gtk_container_add (GTK_CONTAINER (box), label1); + box1 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_set_name (box1, "box1"); + add_controller (box1, s); + gtk_container_add (GTK_CONTAINER (box), box1); entry1 = gtk_text_new (); - gtk_container_add (GTK_CONTAINER (box), entry1); - gtk_container_add (GTK_CONTAINER (box), gtk_label_new ("label2")); + gtk_widget_set_name (entry1, "entry1"); + add_controller (entry1, s); + gtk_container_add (GTK_CONTAINER (box1), entry1); + box2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_set_name (box2, "box2"); + add_controller (box2, s); + gtk_container_add (GTK_CONTAINER (box), box2); + label2 = gtk_label_new ("label2"); + gtk_widget_set_name (label2, "label2"); + add_controller (label2, s); + gtk_container_add (GTK_CONTAINER (box2), label2); entry2 = gtk_text_new (); - gtk_container_add (GTK_CONTAINER (box), entry2); + gtk_widget_set_name (entry2, "entry2"); + add_controller (entry2, s); + gtk_container_add (GTK_CONTAINER (box2), entry2); g_assert_null (gtk_window_get_focus (GTK_WINDOW (window))); - gtk_window_set_focus (GTK_WINDOW (window), entry1); + gtk_widget_show (window); - g_assert (gtk_window_get_focus (GTK_WINDOW (window)) == entry1); + /* show puts the initial focus on box */ + g_assert (gtk_window_get_focus (GTK_WINDOW (window)) == box); + verify_focus_chain (window); - gtk_widget_show (window); + if (g_test_verbose ()) + g_print ("-> box\n%s\n", s->str); + + g_assert_cmpstr (s->str, ==, +"window: focus-in GDK_CROSSING_NORMAL GDK_NOTIFY_VIRTUAL is-focus: 0 contains-focus: 1 origin: (null) target: box\n" +"box: focus-in GDK_CROSSING_NORMAL GDK_NOTIFY_ANCESTOR is-focus: 1 contains-focus: 0 origin: (null) target: box\n"); + + g_string_truncate (s, 0); + + gtk_widget_grab_focus (entry1); + + if (g_test_verbose ()) + g_print ("box -> entry1\n%s\n", s->str); + + g_assert_cmpstr (s->str, ==, +"box: focus-out GDK_CROSSING_NORMAL GDK_NOTIFY_INFERIOR is-focus: 0 contains-focus: 1 origin: box target: entry1\n" +"box1: focus-in GDK_CROSSING_NORMAL GDK_NOTIFY_VIRTUAL is-focus: 0 contains-focus: 1 origin: box target: entry1\n" +"entry1: focus-in GDK_CROSSING_NORMAL GDK_NOTIFY_ANCESTOR is-focus: 1 contains-focus: 0 origin: box target: entry1\n"); + + g_string_truncate (s, 0); g_assert (gtk_window_get_focus (GTK_WINDOW (window)) == entry1); + verify_focus_chain (window); gtk_widget_grab_focus (entry2); - g_assert (gtk_window_get_focus (GTK_WINDOW (window)) == entry2); + if (g_test_verbose ()) + g_print ("entry1 -> entry2\n%s\n", s->str); - gtk_widget_hide (window); + g_assert_cmpstr (s->str, ==, +"entry1: focus-out GDK_CROSSING_NORMAL GDK_NOTIFY_NONLINEAR is-focus: 0 contains-focus: 0 origin: entry1 target: entry2\n" +"box1: focus-out GDK_CROSSING_NORMAL GDK_NOTIFY_NONLINEAR_VIRTUAL is-focus: 0 contains-focus: 0 origin: entry1 target: entry2\n" +"box2: focus-in GDK_CROSSING_NORMAL GDK_NOTIFY_NONLINEAR_VIRTUAL is-focus: 0 contains-focus: 1 origin: entry1 target: entry2\n" +"entry2: focus-in GDK_CROSSING_NORMAL GDK_NOTIFY_NONLINEAR is-focus: 1 contains-focus: 0 origin: entry1 target: entry2\n"); + + g_string_truncate (s, 0); g_assert (gtk_window_get_focus (GTK_WINDOW (window)) == entry2); + verify_focus_chain (window); + + gtk_widget_grab_focus (box); + + if (g_test_verbose ()) + g_print ("entry2 -> box\n%s", s->str); + + g_assert_cmpstr (s->str, ==, +"entry2: focus-out GDK_CROSSING_NORMAL GDK_NOTIFY_ANCESTOR is-focus: 0 contains-focus: 0 origin: entry2 target: box\n" +"box2: focus-out GDK_CROSSING_NORMAL GDK_NOTIFY_VIRTUAL is-focus: 0 contains-focus: 0 origin: entry2 target: box\n" +"box: focus-in GDK_CROSSING_NORMAL GDK_NOTIFY_INFERIOR is-focus: 1 contains-focus: 0 origin: entry2 target: box\n"); + + g_string_truncate (s, 0); + + gtk_widget_hide (window); + g_assert (gtk_window_get_focus (GTK_WINDOW (window)) == box); + verify_focus_chain (window); + gtk_window_set_focus (GTK_WINDOW (window), entry1); g_assert (gtk_window_get_focus (GTK_WINDOW (window)) == entry1); diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build index 97e5f47fce8f6a46c724d8c3638d6807eace6a58..f56a3d4c7912d93126fc37e53aeddfd01a23cc16 100644 --- a/testsuite/gtk/meson.build +++ b/testsuite/gtk/meson.build @@ -1,3 +1,5 @@ +subdir('focus-chain') + testexecdir = join_paths(installed_test_bindir, 'gtk') testdatadir = join_paths(installed_test_datadir, 'gtk')