From 4e3c97b3f2ebb9c12f68cf4ffbc6d3758bacf4ae Mon Sep 17 00:00:00 2001 From: Cody Russell Date: Fri, 1 Aug 2008 03:30:50 +0000 Subject: [PATCH] =?UTF-8?q?Bug=2056070=20=E2=80=93=20Can't=20click=20butto?= =?UTF-8?q?n=20after=20setting=20it=20sensitive.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2008-07-31 Cody Russell Bug 56070 – Can't click button after setting it sensitive. * gtk/gtkwidget.[ch] * gtk/gtkwindow.c * gtk/gtkmain.c * gtk/gtkbutton.c * gtk/gtkprivate.h * gdk/gdkevents.h: Synthesize crossing events events where necessary. * gtk/tests/crossingevents.c: Add unit tests for crossing events. Big thanks to Ed Catmur, Matthias Clasen, and everyone else who has worked on and helped out with this. svn path=/trunk/; revision=20924 --- ChangeLog | 16 + docs/reference/gdk/tmpl/event_structs.sgml | 11 +- gdk/gdkevents.h | 5 +- gtk/gtkbutton.c | 3 +- gtk/gtkmain.c | 53 +- gtk/gtkprivate.h | 6 +- gtk/gtkwidget.c | 288 +++++ gtk/gtkwidget.h | 8 + gtk/gtkwindow.c | 12 +- gtk/tests/Makefile.am | 4 + gtk/tests/crossingevents.c | 1254 ++++++++++++++++++++ 11 files changed, 1628 insertions(+), 32 deletions(-) create mode 100644 gtk/tests/crossingevents.c diff --git a/ChangeLog b/ChangeLog index 7172cd9f74..250e739cc6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2008-07-31 Cody Russell + + Bug 56070 – Can't click button after setting it sensitive. + + * gtk/gtkwidget.[ch] + * gtk/gtkwindow.c + * gtk/gtkmain.c + * gtk/gtkbutton.c + * gtk/gtkprivate.h + * gdk/gdkevents.h: Synthesize crossing events events where necessary. + + * gtk/tests/crossingevents.c: Add unit tests for crossing events. + + Big thanks to Ed Catmur, Matthias Clasen, and everyone else who + has worked on and helped out with this. + 2008-07-31 Matthias Clasen Bug 424207 – printing hangs on unreachable cups server diff --git a/docs/reference/gdk/tmpl/event_structs.sgml b/docs/reference/gdk/tmpl/event_structs.sgml index 58494f5c1d..8e9b65628f 100644 --- a/docs/reference/gdk/tmpl/event_structs.sgml +++ b/docs/reference/gdk/tmpl/event_structs.sgml @@ -259,8 +259,11 @@ Generated when the pointer enters or leaves a window. @y: the y coordinate of the pointer relative to the window. @x_root: the x coordinate of the pointer relative to the root of the screen. @y_root: the y coordinate of the pointer relative to the root of the screen. -@mode: the crossing mode (%GDK_CROSSING_NORMAL, %GDK_CROSSING_GRAB or - %GDK_CROSSING_UNGRAB). +@mode: the crossing mode (%GDK_CROSSING_NORMAL, %GDK_CROSSING_GRAB, + %GDK_CROSSING_UNGRAB, %GDK_CROSSING_GTK_GRAB, %GDK_CROSSING_GTK_UNGRAB or + %GDK_CROSSING_STATE_CHANGED). %GDK_CROSSING_GTK_GRAB, %GDK_CROSSING_GTK_UNGRAB, + and %GDK_CROSSING_STATE_CHANGED were added in 2.14 and are always synthesized, + never native. @detail: the kind of crossing that happened (%GDK_NOTIFY_INFERIOR, %GDK_NOTIFY_ANCESTOR, %GDK_NOTIFY_VIRTUAL, %GDK_NOTIFY_NONLINEAR or %GDK_NOTIFY_NONLINEAR_VIRTUAL). @@ -474,6 +477,10 @@ Specifies the crossing mode for #GdkEventCrossing. @GDK_CROSSING_NORMAL: crossing because of pointer motion. @GDK_CROSSING_GRAB: crossing because a grab is activated. @GDK_CROSSING_UNGRAB: crossing because a grab is deactivated. +@GDK_CROSSING_GTK_GRAB: crossing because a GTK+ grab is activated. +@GDK_CROSSING_GTK_UNGRAB: crossing because a GTK+ grab is deactivated. +@GDK_CROSSING_STATE_CHANGED: crossing because a GTK+ widget changed state (e.g. + sensitivity). diff --git a/gdk/gdkevents.h b/gdk/gdkevents.h index 87cc837340..e64acb7b9f 100644 --- a/gdk/gdkevents.h +++ b/gdk/gdkevents.h @@ -225,7 +225,10 @@ typedef enum { GDK_CROSSING_NORMAL, GDK_CROSSING_GRAB, - GDK_CROSSING_UNGRAB + GDK_CROSSING_UNGRAB, + GDK_CROSSING_GTK_GRAB, + GDK_CROSSING_GTK_UNGRAB, + GDK_CROSSING_STATE_CHANGED } GdkCrossingMode; typedef enum diff --git a/gtk/gtkbutton.c b/gtk/gtkbutton.c index 606b33d70a..58a6926125 100644 --- a/gtk/gtkbutton.c +++ b/gtk/gtkbutton.c @@ -1457,7 +1457,8 @@ gtk_button_leave_notify (GtkWidget *widget, event_widget = gtk_get_event_widget ((GdkEvent*) event); if ((event_widget == widget) && - (event->detail != GDK_NOTIFY_INFERIOR)) + (event->detail != GDK_NOTIFY_INFERIOR) && + (GTK_WIDGET_SENSITIVE (event_widget))) { button->in_button = FALSE; gtk_button_leave (button); diff --git a/gtk/gtkmain.c b/gtk/gtkmain.c index 584265645d..30328cd1c3 100644 --- a/gtk/gtkmain.c +++ b/gtk/gtkmain.c @@ -1569,25 +1569,15 @@ gtk_main_do_event (GdkEvent *event) break; case GDK_ENTER_NOTIFY: + GTK_PRIVATE_SET_FLAG (event_widget, GTK_HAS_POINTER); + _gtk_widget_set_pointer_window (event_widget, event->any.window); if (GTK_WIDGET_IS_SENSITIVE (grab_widget)) - { - g_object_ref (event_widget); - - gtk_widget_event (grab_widget, event); - if (event_widget == grab_widget) - GTK_PRIVATE_SET_FLAG (event_widget, GTK_LEAVE_PENDING); - - g_object_unref (event_widget); - } + gtk_widget_event (grab_widget, event); break; case GDK_LEAVE_NOTIFY: - if (GTK_WIDGET_LEAVE_PENDING (event_widget)) - { - GTK_PRIVATE_UNSET_FLAG (event_widget, GTK_LEAVE_PENDING); - gtk_widget_event (event_widget, event); - } - else if (GTK_WIDGET_IS_SENSITIVE (grab_widget)) + GTK_PRIVATE_UNSET_FLAG (event_widget, GTK_HAS_POINTER); + if (GTK_WIDGET_IS_SENSITIVE (grab_widget)) gtk_widget_event (grab_widget, event); break; @@ -1660,6 +1650,7 @@ typedef struct GtkWidget *new_grab_widget; gboolean was_grabbed; gboolean is_grabbed; + gboolean from_grab; } GrabNotifyInfo; static void @@ -1681,13 +1672,31 @@ gtk_grab_notify_foreach (GtkWidget *child, is_shadowed = info->new_grab_widget && !info->is_grabbed; g_object_ref (child); + + if ((was_shadowed || is_shadowed) && GTK_IS_CONTAINER (child)) + gtk_container_forall (GTK_CONTAINER (child), gtk_grab_notify_foreach, info); + if (is_shadowed) + { + GTK_PRIVATE_SET_FLAG (child, GTK_SHADOWED); + if (!was_shadowed && GTK_WIDGET_HAS_POINTER (child) + && GTK_WIDGET_IS_SENSITIVE (child)) + _gtk_widget_synthesize_crossing (child, info->new_grab_widget, + GDK_CROSSING_GTK_GRAB); + } + else + { + GTK_PRIVATE_UNSET_FLAG (child, GTK_SHADOWED); + if (was_shadowed && GTK_WIDGET_HAS_POINTER (child) + && GTK_WIDGET_IS_SENSITIVE (child)) + _gtk_widget_synthesize_crossing (info->old_grab_widget, child, + info->from_grab ? GDK_CROSSING_GTK_GRAB + : GDK_CROSSING_GTK_UNGRAB); + } + if (was_shadowed != is_shadowed) _gtk_widget_grab_notify (child, was_shadowed); - if ((was_shadowed || is_shadowed) && GTK_IS_CONTAINER (child)) - gtk_container_forall (GTK_CONTAINER (child), gtk_grab_notify_foreach, info); - g_object_unref (child); info->was_grabbed = was_grabbed; @@ -1697,7 +1706,8 @@ gtk_grab_notify_foreach (GtkWidget *child, static void gtk_grab_notify (GtkWindowGroup *group, GtkWidget *old_grab_widget, - GtkWidget *new_grab_widget) + GtkWidget *new_grab_widget, + gboolean from_grab) { GList *toplevels; GrabNotifyInfo info; @@ -1707,6 +1717,7 @@ gtk_grab_notify (GtkWindowGroup *group, info.old_grab_widget = old_grab_widget; info.new_grab_widget = new_grab_widget; + info.from_grab = from_grab; g_object_ref (group); @@ -1751,7 +1762,7 @@ gtk_grab_add (GtkWidget *widget) g_object_ref (widget); group->grabs = g_slist_prepend (group->grabs, widget); - gtk_grab_notify (group, old_grab_widget, widget); + gtk_grab_notify (group, old_grab_widget, widget, TRUE); } } @@ -1787,7 +1798,7 @@ gtk_grab_remove (GtkWidget *widget) else new_grab_widget = NULL; - gtk_grab_notify (group, widget, new_grab_widget); + gtk_grab_notify (group, widget, new_grab_widget, FALSE); g_object_unref (widget); } diff --git a/gtk/gtkprivate.h b/gtk/gtkprivate.h index c423f7b1e1..5e51844c5b 100644 --- a/gtk/gtkprivate.h +++ b/gtk/gtkprivate.h @@ -37,7 +37,8 @@ typedef enum { PRIVATE_GTK_USER_STYLE = 1 << 0, PRIVATE_GTK_RESIZE_PENDING = 1 << 2, - PRIVATE_GTK_LEAVE_PENDING = 1 << 4, + PRIVATE_GTK_HAS_POINTER = 1 << 3, /* If the pointer is above a window belonging to the widget */ + PRIVATE_GTK_SHADOWED = 1 << 4, /* If there is a grab in effect shadowing the widget */ PRIVATE_GTK_HAS_SHAPE_MASK = 1 << 5, PRIVATE_GTK_IN_REPARENT = 1 << 6, PRIVATE_GTK_DIRECTION_SET = 1 << 7, /* If the reading direction is not DIR_NONE */ @@ -54,7 +55,8 @@ typedef enum #define GTK_PRIVATE_FLAGS(wid) (GTK_WIDGET (wid)->private_flags) #define GTK_WIDGET_USER_STYLE(obj) ((GTK_PRIVATE_FLAGS (obj) & PRIVATE_GTK_USER_STYLE) != 0) #define GTK_CONTAINER_RESIZE_PENDING(obj) ((GTK_PRIVATE_FLAGS (obj) & PRIVATE_GTK_RESIZE_PENDING) != 0) -#define GTK_WIDGET_LEAVE_PENDING(obj) ((GTK_PRIVATE_FLAGS (obj) & PRIVATE_GTK_LEAVE_PENDING) != 0) +#define GTK_WIDGET_HAS_POINTER(obj) ((GTK_PRIVATE_FLAGS (obj) & PRIVATE_GTK_HAS_POINTER) != 0) +#define GTK_WIDGET_SHADOWED(obj) ((GTK_PRIVATE_FLAGS (obj) & PRIVATE_GTK_SHADOWED) != 0) #define GTK_WIDGET_HAS_SHAPE_MASK(obj) ((GTK_PRIVATE_FLAGS (obj) & PRIVATE_GTK_HAS_SHAPE_MASK) != 0) #define GTK_WIDGET_IN_REPARENT(obj) ((GTK_PRIVATE_FLAGS (obj) & PRIVATE_GTK_IN_REPARENT) != 0) #define GTK_WIDGET_DIRECTION_SET(obj) ((GTK_PRIVATE_FLAGS (obj) & PRIVATE_GTK_DIRECTION_SET) != 0) diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index 3d36038565..6d92f0d24b 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -298,6 +298,7 @@ static GQuark quark_accel_closures = 0; static GQuark quark_event_mask = 0; static GQuark quark_extension_event_mode = 0; static GQuark quark_parent_window = 0; +static GQuark quark_pointer_window = 0; static GQuark quark_shape_info = 0; static GQuark quark_input_shape_info = 0; static GQuark quark_colormap = 0; @@ -385,6 +386,7 @@ gtk_widget_class_init (GtkWidgetClass *klass) quark_event_mask = g_quark_from_static_string ("gtk-event-mask"); quark_extension_event_mode = g_quark_from_static_string ("gtk-extension-event-mode"); quark_parent_window = g_quark_from_static_string ("gtk-parent-window"); + quark_pointer_window = g_quark_from_static_string ("gtk-pointer-window"); quark_shape_info = g_quark_from_static_string ("gtk-shape-info"); quark_input_shape_info = g_quark_from_static_string ("gtk-input-shape-info"); quark_colormap = g_quark_from_static_string ("gtk-colormap"); @@ -8053,6 +8055,282 @@ _gtk_widget_peek_colormap (void) return NULL; } +/** + * _gtk_widget_set_pointer_window: + * @widget: a #GtkWidget. + * @pointer_window: the new pointer window. + * + * Sets pointer window for @widget. Does not ref @pointer_window. + * Actually stores it on the #GdkScreen, but you don't need to know that. + **/ +void +_gtk_widget_set_pointer_window (GtkWidget *widget, + GdkWindow *pointer_window) +{ + g_return_if_fail (GTK_IS_WIDGET (widget)); + + GdkScreen *screen = gdk_drawable_get_screen (GDK_DRAWABLE (widget->window)); + g_object_set_qdata (G_OBJECT (screen), quark_pointer_window, pointer_window); +} + +/** + * _gtk_widget_get_pointer_window: + * @widget: a #GtkWidget. + * + * Return value: the pointer window set on the #GdkScreen @widget is attached + * to, or %NULL. + **/ +GdkWindow * +_gtk_widget_get_pointer_window (GtkWidget *widget) +{ + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + + GdkScreen *screen = gdk_drawable_get_screen (GDK_DRAWABLE (widget->window)); + return g_object_get_qdata (G_OBJECT (screen), quark_pointer_window); +} + +static void +synth_crossing (GtkWidget *widget, + GdkEventType type, + GdkWindow *window, + GdkCrossingMode mode, + GdkNotifyType detail) +{ + GdkEvent *event; + + event = gdk_event_new (type); + + event->crossing.window = g_object_ref (window); + event->crossing.send_event = TRUE; + event->crossing.subwindow = g_object_ref (window); + event->crossing.time = GDK_CURRENT_TIME; + event->crossing.x = event->crossing.y = 0; + event->crossing.x_root = event->crossing.y_root = 0; + event->crossing.mode = mode; + event->crossing.detail = detail; + event->crossing.focus = FALSE; + event->crossing.state = 0; + + if (!widget) + widget = gtk_get_event_widget (event); + + if (widget) + gtk_widget_event_internal (widget, event); + + gdk_event_free (event); +} + +/** + * _gtk_widget_is_pointer_widget: + * @widget: a #GtkWidget + * + * Returns %TRUE if the pointer window belongs to @widget. + * + */ +gboolean +_gtk_widget_is_pointer_widget (GtkWidget *widget) +{ + if (GTK_WIDGET_HAS_POINTER (widget)) + { + GdkWindow *win; + GtkWidget *wid; + + win = _gtk_widget_get_pointer_window (widget); + if (win) + { + gdk_window_get_user_data (win, &wid); + if (wid == widget) + return TRUE; + } + } + + return FALSE; +} + +/** + * _gtk_widget_synthesize_crossing: + * @from: the #GtkWidget the virtual pointer is leaving. + * @to: the #GtkWidget the virtual pointer is moving to. + * @mode: the #GdkCrossingMode to place on the synthesized events. + * + * Generate crossing event(s) on widget state (sensitivity) or GTK+ grab change. + * + * The real pointer window is the window that most recently received an enter notify + * event. Windows that don't select for crossing events can't become the real + * poiner window. The real pointer widget that owns the real pointer window. The + * effective pointer window is the same as the real pointer window unless the real + * pointer widget is either insensitive or there is a grab on a widget that is not + * an ancestor of the real pointer widget (in which case the effective pointer + * window should be the root window). + * + * When the effective pointer window is the same as the real poiner window, we + * receive crossing events from the windowing system. When the effective pointer + * window changes to become different from the real pointer window we synthesize + * crossing events, attempting to follow X protocol rules: + * + * When the root window becomes the effective pointer window: + * - leave notify on real pointer window, detail Ancestor + * - leave notify on all of its ancestors, detail Virtual + * - enter notify on root window, detail Inferior + * + * When the root window ceases to be the effective pointer window: + * - leave notify on root window, detail Inferior + * - enter notify on all ancestors of real pointer window, detail Virtual + * - enter notify on real pointer window, detail Ancestor + */ +void +_gtk_widget_synthesize_crossing (GtkWidget *from, + GtkWidget *to, + GdkCrossingMode mode) +{ + GdkWindow *from_window = NULL, *to_window = NULL; + + g_return_if_fail (from != NULL || to != NULL); + + if (from != NULL) + from_window = GTK_WIDGET_HAS_POINTER (from) + ? _gtk_widget_get_pointer_window (from) : from->window; + if (to != NULL) + to_window = GTK_WIDGET_HAS_POINTER (to) + ? _gtk_widget_get_pointer_window (to) : to->window; + + if (from_window == NULL && to_window == NULL) + ; + else if (from_window != NULL && to_window == NULL) + { + GList *from_ancestors = NULL, *list; + GdkWindow *from_ancestor = from_window; + + while (from_ancestor != NULL) + { + if (from_ancestor != NULL) + { + from_ancestor = gdk_window_get_parent (from_ancestor); + if (from_ancestor == NULL) + break; + from_ancestors = g_list_prepend (from_ancestors, from_ancestor); + } + } + + synth_crossing (from, GDK_LEAVE_NOTIFY, from_window, + mode, GDK_NOTIFY_ANCESTOR); + for (list = g_list_last (from_ancestors); list; list = list->prev) + { + synth_crossing (NULL, GDK_LEAVE_NOTIFY, (GdkWindow *) list->data, + mode, GDK_NOTIFY_VIRTUAL); + } + + /* XXX: enter/inferior on root window? */ + + g_list_free (from_ancestors); + } + else if (from_window == NULL && to_window != NULL) + { + GList *to_ancestors = NULL, *list; + GdkWindow *to_ancestor = to_window; + + while (to_ancestor != NULL) + { + if (to_ancestor != NULL) + { + to_ancestor = gdk_window_get_parent (to_ancestor); + if (to_ancestor == NULL) + break; + to_ancestors = g_list_prepend (to_ancestors, to_ancestor); + } + } + + /* XXX: leave/inferior on root window? */ + + for (list = to_ancestors; list; list = list->next) + { + synth_crossing (NULL, GDK_ENTER_NOTIFY, (GdkWindow *) list->data, + mode, GDK_NOTIFY_VIRTUAL); + } + synth_crossing (to, GDK_ENTER_NOTIFY, to_window, + mode, GDK_NOTIFY_ANCESTOR); + + g_list_free (to_ancestors); + } + else if (from_window == to_window) + ; + else + { + GList *from_ancestors = NULL, *to_ancestors = NULL, *list; + GdkWindow *from_ancestor = from_window, *to_ancestor = to_window; + + while (from_ancestor != NULL || to_ancestor != NULL) + { + if (from_ancestor != NULL) + { + from_ancestor = gdk_window_get_parent (from_ancestor); + if (from_ancestor == to_window) + break; + from_ancestors = g_list_prepend (from_ancestors, from_ancestor); + } + if (to_ancestor != NULL) + { + to_ancestor = gdk_window_get_parent (to_ancestor); + if (to_ancestor == from_window) + break; + to_ancestors = g_list_prepend (to_ancestors, to_ancestor); + } + } + if (to_ancestor == from_window) + { + if (mode != GDK_CROSSING_GTK_UNGRAB) + synth_crossing (from, GDK_LEAVE_NOTIFY, from_window, + mode, GDK_NOTIFY_INFERIOR); + for (list = to_ancestors; list; list = list->next) + synth_crossing (NULL, GDK_ENTER_NOTIFY, (GdkWindow *) list->data, + mode, GDK_NOTIFY_VIRTUAL); + synth_crossing (to, GDK_ENTER_NOTIFY, to_window, + mode, GDK_NOTIFY_ANCESTOR); + } + else if (from_ancestor == to_window) + { + synth_crossing (from, GDK_LEAVE_NOTIFY, from_window, + mode, GDK_NOTIFY_ANCESTOR); + for (list = g_list_last (from_ancestors); list; list = list->prev) + { + synth_crossing (NULL, GDK_LEAVE_NOTIFY, (GdkWindow *) list->data, + mode, GDK_NOTIFY_VIRTUAL); + } + if (mode != GDK_CROSSING_GTK_GRAB) + synth_crossing (to, GDK_ENTER_NOTIFY, to_window, + mode, GDK_NOTIFY_INFERIOR); + } + else + { + while (from_ancestors != NULL && to_ancestors != NULL + && from_ancestors->data == to_ancestors->data) + { + from_ancestors = g_list_delete_link (from_ancestors, + from_ancestors); + to_ancestors = g_list_delete_link (to_ancestors, to_ancestors); + } + + synth_crossing (from, GDK_LEAVE_NOTIFY, from_window, + mode, GDK_NOTIFY_NONLINEAR); + + for (list = g_list_last (from_ancestors); list; list = list->prev) + { + synth_crossing (NULL, GDK_LEAVE_NOTIFY, (GdkWindow *) list->data, + mode, GDK_NOTIFY_NONLINEAR_VIRTUAL); + } + for (list = to_ancestors; list; list = list->next) + { + synth_crossing (NULL, GDK_ENTER_NOTIFY, (GdkWindow *) list->data, + mode, GDK_NOTIFY_NONLINEAR_VIRTUAL); + } + synth_crossing (to, GDK_ENTER_NOTIFY, to_window, + mode, GDK_NOTIFY_NONLINEAR); + } + g_list_free (from_ancestors); + g_list_free (to_ancestors); + } +} + static void gtk_widget_propagate_state (GtkWidget *widget, GtkStateData *data) @@ -8108,6 +8386,16 @@ gtk_widget_propagate_state (GtkWidget *widget, g_signal_emit (widget, widget_signals[STATE_CHANGED], 0, old_state); + if (GTK_WIDGET_HAS_POINTER (widget) && !GTK_WIDGET_SHADOWED (widget)) + { + if (!GTK_WIDGET_IS_SENSITIVE (widget)) + _gtk_widget_synthesize_crossing (widget, NULL, + GDK_CROSSING_STATE_CHANGED); + else if (old_state == GTK_STATE_INSENSITIVE) + _gtk_widget_synthesize_crossing (NULL, widget, + GDK_CROSSING_STATE_CHANGED); + } + if (GTK_IS_CONTAINER (widget)) { data->parent_sensitive = (GTK_WIDGET_IS_SENSITIVE (widget) != FALSE); diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h index dfc423e900..a4feb4e2fe 100644 --- a/gtk/gtkwidget.h +++ b/gtk/gtkwidget.h @@ -833,6 +833,14 @@ void _gtk_widget_propagate_screen_changed (GtkWidget *widget, GdkScreen *previous_screen); void _gtk_widget_propagate_composited_changed (GtkWidget *widget); +void _gtk_widget_set_pointer_window (GtkWidget *widget, + GdkWindow *pointer_window); +GdkWindow *_gtk_widget_get_pointer_window (GtkWidget *widget); +gboolean _gtk_widget_is_pointer_widget (GtkWidget *widget); +void _gtk_widget_synthesize_crossing (GtkWidget *from, + GtkWidget *to, + GdkCrossingMode mode); + GdkColormap* _gtk_widget_peek_colormap (void); G_END_DECLS diff --git a/gtk/gtkwindow.c b/gtk/gtkwindow.c index a565cf165f..905d3a7f7d 100644 --- a/gtk/gtkwindow.c +++ b/gtk/gtkwindow.c @@ -2052,10 +2052,6 @@ gtk_window_unset_transient_for (GtkWindow *window) if (window->transient_parent) { - if (priv->transient_parent_group) - gtk_window_group_remove_window (window->group, - window); - g_signal_handlers_disconnect_by_func (window->transient_parent, gtk_window_transient_parent_realized, window); @@ -2073,7 +2069,13 @@ gtk_window_unset_transient_for (GtkWindow *window) disconnect_parent_destroyed (window); window->transient_parent = NULL; - priv->transient_parent_group = FALSE; + + if (priv->transient_parent_group) + { + priv->transient_parent_group = FALSE; + gtk_window_group_remove_window (window->group, + window); + } } } diff --git a/gtk/tests/Makefile.am b/gtk/tests/Makefile.am index b9a5afa45a..83629393cd 100644 --- a/gtk/tests/Makefile.am +++ b/gtk/tests/Makefile.am @@ -51,6 +51,10 @@ TEST_PROGS += object object_SOURCES = object.c pixbuf-init.c object_LDADD = $(progs_ldadd) +TEST_PROGS += crossingevents +crossingevents_SOURCES = crossingevents.c +crossingevents_LDADD = $(progs_ldadd) + # this doesn't work in make distcheck, since it doesn't # find file-chooser-test-dir # TEST_PROGS += filechooser diff --git a/gtk/tests/crossingevents.c b/gtk/tests/crossingevents.c new file mode 100644 index 0000000000..7ad8d75a1c --- /dev/null +++ b/gtk/tests/crossingevents.c @@ -0,0 +1,1254 @@ +/* + * crossingevents.c: A test for crossing events + * + * Copyright (C) 2008 Cody Russell + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include + +typedef struct { + GtkWidget *window; + GtkWidget *eventbox; + GtkWidget *frame; + GtkWidget *button; + GtkWidget *check; + gboolean events_connected; + GQueue *queue; +} CrossingTest; + +typedef struct { + gboolean entered; + gchar *name; + gboolean synthesized; + GdkCrossingMode mode; + GdkNotifyType detail; +} CrossingEventData; + +#define SLEEP_DURATION 100 + +void start_events (CrossingTest *test); +void stop_events (CrossingTest *test); + +static gboolean +sleep_timeout_cb (gpointer data) +{ + gtk_main_quit (); + return FALSE; +} + +static void +sleep_in_main_loop (double fraction) +{ + /* process all pending idles and events */ + while (g_main_context_pending (NULL)) + g_main_context_iteration (NULL, FALSE); + /* sleeping probably isn't strictly necessary here */ + gdk_threads_add_timeout_full (G_MAXINT, fraction * SLEEP_DURATION, sleep_timeout_cb, NULL, NULL); + gtk_main (); + /* process any pending idles or events that arrived during sleep */ + while (g_main_context_pending (NULL)) + g_main_context_iteration (NULL, FALSE); +} + +void +set_cursor (GtkWidget *widget) +{ + int x, y, w, h; + + gdk_window_get_origin (widget->window, &x, &y); + + x += widget->allocation.x; + y += widget->allocation.y; + w = widget->allocation.width; + h = widget->allocation.height; + + gdk_display_warp_pointer (gtk_widget_get_display (widget), + gtk_widget_get_screen (widget), + x + w / 2, + y + h / 2); + + sleep_in_main_loop (0.5); +} + +static gboolean +on_enter (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data) +{ + CrossingTest *test = (CrossingTest*)user_data; + + CrossingEventData *evt = g_slice_new0 (CrossingEventData); + evt->entered = TRUE; + evt->name = g_strdup (gtk_widget_get_name (widget)); + evt->synthesized = event->send_event; + evt->mode = event->mode; + evt->detail = event->detail; + + if (!test->queue) + test->queue = g_queue_new (); + + g_queue_push_tail (test->queue, evt); + + return FALSE; +} + +static gboolean +on_leave (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data) +{ + CrossingTest *test = (CrossingTest*)user_data; + + CrossingEventData *evt = g_slice_new0 (CrossingEventData); + evt->entered = FALSE; + evt->name = g_strdup (gtk_widget_get_name (widget)); + evt->synthesized = event->send_event; + evt->mode = event->mode; + evt->detail = event->detail; + + if (!test->queue) + test->queue = g_queue_new (); + + g_queue_push_tail (test->queue, evt); + + return FALSE; +} + +static void +on_check_toggled (GtkWidget *toggle, GtkWidget *button) +{ + gtk_widget_set_sensitive (button, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle))); +} + +static void +sensitivity_setup (CrossingTest *test, + gconstpointer user_data) +{ + GtkWidget *frame; + + test->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_set_name (test->window, "W"); + frame = gtk_frame_new ("Crossing Events"); + test->eventbox = gtk_event_box_new (); + gtk_widget_set_name (test->eventbox, "E"); + + GtkWidget *vbox = gtk_vbox_new (FALSE, 10); + gtk_container_add (GTK_CONTAINER (test->window), frame); + gtk_container_add (GTK_CONTAINER (frame), test->eventbox); + gtk_container_add (GTK_CONTAINER (test->eventbox), vbox); + + test->button = gtk_button_new_with_label ("Click me!"); + gtk_widget_set_name (test->button, "B"); + gtk_box_pack_start (GTK_BOX (vbox), test->button, FALSE, TRUE, 0); + + test->check = gtk_check_button_new_with_label ("Sensitive?"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), TRUE); + g_signal_connect (G_OBJECT (test->check), + "toggled", G_CALLBACK (on_check_toggled), test->button); + gtk_widget_set_name (test->check, "C"); + gtk_box_pack_start (GTK_BOX (vbox), test->check, FALSE, TRUE, 0); + + gtk_widget_show_all (test->window); + + gtk_window_move (GTK_WINDOW (test->window), 0, 0); + + sleep_in_main_loop (0.5); +} + +static void +sensitivity_teardown (CrossingTest *test, + gconstpointer user_data) +{ + stop_events (test); + gtk_widget_destroy (test->window); + + if (test->queue != NULL) + { + g_queue_clear (test->queue); + test->queue = NULL; + } +} + +void +start_events (CrossingTest *test) +{ + if (!test->events_connected) + { + g_object_connect (G_OBJECT (test->window), + "signal::destroy", gtk_main_quit, NULL, + "signal::enter-notify-event", on_enter, test, + "signal::leave-notify-event", on_leave, test, + NULL); + g_object_connect (G_OBJECT (test->eventbox), + "signal::enter-notify-event", on_enter, test, + "signal::leave-notify-event", on_leave, test, + NULL); + g_object_connect (G_OBJECT (test->button), + "signal::enter-notify-event", on_enter, test, + "signal::leave-notify-event", on_leave, test, + NULL); + g_object_connect (G_OBJECT (test->check), + "signal::enter-notify-event", on_enter, test, + "signal::leave-notify-event", on_leave, test, + NULL); + test->events_connected = TRUE; + } + + sleep_in_main_loop (0.5); +} + +void +stop_events (CrossingTest *test) +{ + if (test->events_connected) + { + g_object_disconnect (G_OBJECT (test->window), + "any_signal", gtk_main_quit, NULL, + "any_signal", on_enter, test, + "any_signal", on_leave, test, + NULL); + g_object_disconnect (G_OBJECT (test->eventbox), + "any_signal", on_enter, test, + "any_signal", on_leave, test, + NULL); + g_object_disconnect (G_OBJECT (test->button), + "any_signal", on_enter, test, + "any_signal", on_leave, test, + NULL); + g_object_disconnect (G_OBJECT (test->check), + "any_signal", G_CALLBACK (on_check_toggled), test->button, + "any_signal", on_enter, test, + "any_signal", on_leave, test, + NULL); + test->events_connected = FALSE; + } +} + +void +move_cursor_away (CrossingTest *test) +{ + gdk_display_warp_pointer (gtk_widget_get_display (test->window), + gtk_widget_get_screen (test->window), + 1000, -1000); + + sleep_in_main_loop (0.5); +} + +void +check_event (CrossingTest *test, + const gchar *name, + gboolean entered, + gboolean synthesized, + GdkCrossingMode mode, + GdkNotifyType detail) +{ + CrossingEventData *evt; + + g_assert (test->queue != NULL); + + evt = g_queue_pop_head (test->queue); + + g_assert (evt->entered == entered); + g_assert (strcmp (evt->name, name) == 0); + g_assert (evt->synthesized == synthesized); + g_assert (evt->mode == mode); + + if (evt->detail != detail) + g_print ("detail, evt %d vs %d\n", evt->detail, detail); + + g_assert (evt->detail == detail); +} + +/* Verify crossing events when moving into and out of a sensitive widget */ +static void +cursor_on_sensitive (CrossingTest *test, + gconstpointer user_data) +{ + move_cursor_away (test); + + start_events (test); + + set_cursor (test->button); + + check_event (test, + "W", + TRUE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR_VIRTUAL); + + check_event (test, + "E", + TRUE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR_VIRTUAL); + + check_event (test, + "B", + TRUE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR); + + g_assert (g_queue_is_empty (test->queue)); + + move_cursor_away (test); + + check_event (test, + "B", + FALSE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR); + + check_event (test, + "E", + FALSE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR_VIRTUAL); + + check_event (test, + "W", + FALSE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR_VIRTUAL); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +static void +change_sensitive_to_insensitive (CrossingTest *test, + gconstpointer user_data) +{ + move_cursor_away (test); + set_cursor (test->button); + + start_events (test); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), FALSE); + + check_event (test, + "B", + FALSE, + TRUE, /* synthesized */ + GDK_CROSSING_STATE_CHANGED, + GDK_NOTIFY_ANCESTOR); + + check_event (test, + "E", + FALSE, + TRUE, /* synthesized */ + GDK_CROSSING_STATE_CHANGED, + GDK_NOTIFY_VIRTUAL); + + check_event (test, + "W", + FALSE, + TRUE, /* synthesized */ + GDK_CROSSING_STATE_CHANGED, + GDK_NOTIFY_VIRTUAL); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +static void +change_insensitive_to_sensitive (CrossingTest *test, + gconstpointer user_data) +{ + move_cursor_away (test); + set_cursor (test->button); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), FALSE); + + start_events (test); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), TRUE); + + check_event (test, + "W", + TRUE, + TRUE, /* synthesized */ + GDK_CROSSING_STATE_CHANGED, + GDK_NOTIFY_VIRTUAL); + + check_event (test, + "E", + TRUE, + TRUE, /* synthesized */ + GDK_CROSSING_STATE_CHANGED, + GDK_NOTIFY_VIRTUAL); + + check_event (test, + "B", + TRUE, + TRUE, /* synthesized */ + GDK_CROSSING_STATE_CHANGED, + GDK_NOTIFY_ANCESTOR); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +static void +cursor_from_insensitive_to_sensitive (CrossingTest *test, + gconstpointer user_data) +{ + set_cursor (test->button); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), FALSE); + + start_events (test); + + set_cursor (test->check); + + check_event (test, + "C", + TRUE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +static void +cursor_from_sensitive_to_insensitive (CrossingTest *test, + gconstpointer user_data) +{ + set_cursor (test->check); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), FALSE); + + start_events (test); + + set_cursor (test->button); + + check_event (test, + "C", + FALSE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +static void +add_gtk_grab (CrossingTest *test, + gconstpointer user_data) +{ + set_cursor (test->button); + + start_events (test); + + gtk_grab_add (test->check); + + check_event (test, + "B", + FALSE, + TRUE, /* synthesized */ + GDK_CROSSING_GTK_GRAB, + GDK_NOTIFY_ANCESTOR); + + check_event (test, + "E", + FALSE, + TRUE, /* synthesized */ + GDK_CROSSING_GTK_GRAB, + GDK_NOTIFY_ANCESTOR); + + check_event (test, + "W", + FALSE, + TRUE, /* synthesized */ + GDK_CROSSING_GTK_GRAB, + GDK_NOTIFY_ANCESTOR); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +static void +remove_gtk_grab (CrossingTest *test, + gconstpointer user_data) +{ + set_cursor (test->button); + + gtk_grab_add (test->check); + + start_events (test); + + gtk_grab_remove (test->check); + + check_event (test, + "B", + TRUE, + TRUE, /* synthesized */ + GDK_CROSSING_GTK_UNGRAB, + GDK_NOTIFY_ANCESTOR); + + check_event (test, + "E", + TRUE, + TRUE, /* synthesized */ + GDK_CROSSING_GTK_UNGRAB, + GDK_NOTIFY_ANCESTOR); + + check_event (test, + "W", + TRUE, + TRUE, /* synthesized */ + GDK_CROSSING_GTK_UNGRAB, + GDK_NOTIFY_ANCESTOR); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +static void +cursor_from_shadowed_to_unshadowed (CrossingTest *test, + gconstpointer user_data) +{ + set_cursor (test->button); + + gtk_grab_add (test->check); + + start_events (test); + + set_cursor (test->check); + + check_event (test, + "C", + FALSE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR); + + check_event (test, + "C", + TRUE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +static void +cursor_from_unshadowed_to_shadowed (CrossingTest *test, + gconstpointer user_data) +{ + set_cursor (test->check); + + gtk_grab_add (test->check); + + start_events (test); + + set_cursor (test->button); + + check_event (test, + "C", + FALSE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR); + + check_event (test, + "C", + TRUE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +int +main (int argc, + char **argv) +{ + gtk_test_init (&argc, &argv, NULL); + + g_test_add ("/crossings/cursor-on-sensitive", CrossingTest, NULL, + sensitivity_setup, cursor_on_sensitive, sensitivity_teardown); + + g_test_add ("/crossings/change-sensitive-to-insensitive", CrossingTest, NULL, + sensitivity_setup, change_sensitive_to_insensitive, sensitivity_teardown); + + g_test_add ("/crossings/cursor-from-insensitive-to-sensitive", CrossingTest, NULL, + sensitivity_setup, cursor_from_insensitive_to_sensitive, sensitivity_teardown); + + g_test_add ("/crossings/cursor-from-sensitive-to-insensitive", CrossingTest, NULL, + sensitivity_setup, cursor_from_sensitive_to_insensitive, sensitivity_teardown); + + g_test_add ("/crossings/change-insensitive-to-sensitive", CrossingTest, NULL, + sensitivity_setup, change_insensitive_to_sensitive, sensitivity_teardown); + + g_test_add ("/crossings/add-gtk-grab", CrossingTest, NULL, + sensitivity_setup, add_gtk_grab, sensitivity_teardown); + + g_test_add ("/crossings/remove-gtk-grab", CrossingTest, NULL, + sensitivity_setup, remove_gtk_grab, sensitivity_teardown); + + g_test_add ("/crossings/cursor-from-shadowed-to-unshadowed", CrossingTest, NULL, + sensitivity_setup, cursor_from_shadowed_to_unshadowed, sensitivity_teardown); + + g_test_add ("/crossings/cursor-from-unshadowed-to-shadowed", CrossingTest, NULL, + sensitivity_setup, cursor_from_unshadowed_to_shadowed, sensitivity_teardown); + + return g_test_run (); +} +/* + * crossingevents.c: A test for crossing events + * + * Copyright (C) 2008 Cody Russell + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include + +typedef struct { + GtkWidget *window; + GtkWidget *eventbox; + GtkWidget *frame; + GtkWidget *button; + GtkWidget *check; + gboolean events_connected; + GQueue *queue; +} CrossingTest; + +typedef struct { + gboolean entered; + gchar *name; + gboolean synthesized; + GdkCrossingMode mode; + GdkNotifyType detail; +} CrossingEventData; + +#define SLEEP_DURATION 100 + +void start_events (CrossingTest *test); +void stop_events (CrossingTest *test); + +static gboolean +sleep_timeout_cb (gpointer data) +{ + gtk_main_quit (); + return FALSE; +} + +static void +sleep_in_main_loop (double fraction) +{ + /* process all pending idles and events */ + while (g_main_context_pending (NULL)) + g_main_context_iteration (NULL, FALSE); + /* sleeping probably isn't strictly necessary here */ + gdk_threads_add_timeout_full (G_MAXINT, fraction * SLEEP_DURATION, sleep_timeout_cb, NULL, NULL); + gtk_main (); + /* process any pending idles or events that arrived during sleep */ + while (g_main_context_pending (NULL)) + g_main_context_iteration (NULL, FALSE); +} + +void +set_cursor (GtkWidget *widget) +{ + int x, y, w, h; + + gdk_window_get_origin (widget->window, &x, &y); + + x += widget->allocation.x; + y += widget->allocation.y; + w = widget->allocation.width; + h = widget->allocation.height; + + gdk_display_warp_pointer (gtk_widget_get_display (widget), + gtk_widget_get_screen (widget), + x + w / 2, + y + h / 2); + + sleep_in_main_loop (0.5); +} + +static gboolean +on_enter (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data) +{ + CrossingTest *test = (CrossingTest*)user_data; + + CrossingEventData *evt = g_slice_new0 (CrossingEventData); + evt->entered = TRUE; + evt->name = g_strdup (gtk_widget_get_name (widget)); + evt->synthesized = event->send_event; + evt->mode = event->mode; + evt->detail = event->detail; + + if (!test->queue) + test->queue = g_queue_new (); + + g_queue_push_tail (test->queue, evt); + + return FALSE; +} + +static gboolean +on_leave (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data) +{ + CrossingTest *test = (CrossingTest*)user_data; + + CrossingEventData *evt = g_slice_new0 (CrossingEventData); + evt->entered = FALSE; + evt->name = g_strdup (gtk_widget_get_name (widget)); + evt->synthesized = event->send_event; + evt->mode = event->mode; + evt->detail = event->detail; + + if (!test->queue) + test->queue = g_queue_new (); + + g_queue_push_tail (test->queue, evt); + + return FALSE; +} + +static void +on_check_toggled (GtkWidget *toggle, GtkWidget *button) +{ + gtk_widget_set_sensitive (button, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle))); +} + +static void +sensitivity_setup (CrossingTest *test, + gconstpointer user_data) +{ + GtkWidget *frame; + + test->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_set_name (test->window, "W"); + frame = gtk_frame_new ("Crossing Events"); + test->eventbox = gtk_event_box_new (); + gtk_widget_set_name (test->eventbox, "E"); + + GtkWidget *vbox = gtk_vbox_new (FALSE, 10); + gtk_container_add (GTK_CONTAINER (test->window), frame); + gtk_container_add (GTK_CONTAINER (frame), test->eventbox); + gtk_container_add (GTK_CONTAINER (test->eventbox), vbox); + + test->button = gtk_button_new_with_label ("Click me!"); + gtk_widget_set_name (test->button, "B"); + gtk_box_pack_start (GTK_BOX (vbox), test->button, FALSE, TRUE, 0); + + test->check = gtk_check_button_new_with_label ("Sensitive?"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), TRUE); + g_signal_connect (G_OBJECT (test->check), + "toggled", G_CALLBACK (on_check_toggled), test->button); + gtk_widget_set_name (test->check, "C"); + gtk_box_pack_start (GTK_BOX (vbox), test->check, FALSE, TRUE, 0); + + gtk_widget_show_all (test->window); + + gtk_window_move (GTK_WINDOW (test->window), 0, 0); + + sleep_in_main_loop (0.5); +} + +static void +sensitivity_teardown (CrossingTest *test, + gconstpointer user_data) +{ + stop_events (test); + gtk_widget_destroy (test->window); + + if (test->queue != NULL) + { + g_queue_clear (test->queue); + test->queue = NULL; + } +} + +void +start_events (CrossingTest *test) +{ + if (!test->events_connected) + { + g_object_connect (G_OBJECT (test->window), + "signal::destroy", gtk_main_quit, NULL, + "signal::enter-notify-event", on_enter, test, + "signal::leave-notify-event", on_leave, test, + NULL); + g_object_connect (G_OBJECT (test->eventbox), + "signal::enter-notify-event", on_enter, test, + "signal::leave-notify-event", on_leave, test, + NULL); + g_object_connect (G_OBJECT (test->button), + "signal::enter-notify-event", on_enter, test, + "signal::leave-notify-event", on_leave, test, + NULL); + g_object_connect (G_OBJECT (test->check), + "signal::enter-notify-event", on_enter, test, + "signal::leave-notify-event", on_leave, test, + NULL); + test->events_connected = TRUE; + } + + sleep_in_main_loop (0.5); +} + +void +stop_events (CrossingTest *test) +{ + if (test->events_connected) + { + g_object_disconnect (G_OBJECT (test->window), + "any_signal", gtk_main_quit, NULL, + "any_signal", on_enter, test, + "any_signal", on_leave, test, + NULL); + g_object_disconnect (G_OBJECT (test->eventbox), + "any_signal", on_enter, test, + "any_signal", on_leave, test, + NULL); + g_object_disconnect (G_OBJECT (test->button), + "any_signal", on_enter, test, + "any_signal", on_leave, test, + NULL); + g_object_disconnect (G_OBJECT (test->check), + "any_signal", G_CALLBACK (on_check_toggled), test->button, + "any_signal", on_enter, test, + "any_signal", on_leave, test, + NULL); + test->events_connected = FALSE; + } +} + +void +move_cursor_away (CrossingTest *test) +{ + gdk_display_warp_pointer (gtk_widget_get_display (test->window), + gtk_widget_get_screen (test->window), + 1000, -1000); + + sleep_in_main_loop (0.5); +} + +void +check_event (CrossingTest *test, + const gchar *name, + gboolean entered, + gboolean synthesized, + GdkCrossingMode mode, + GdkNotifyType detail) +{ + CrossingEventData *evt; + + g_assert (test->queue != NULL); + + evt = g_queue_pop_head (test->queue); + + g_assert (evt->entered == entered); + g_assert (strcmp (evt->name, name) == 0); + g_assert (evt->synthesized == synthesized); + g_assert (evt->mode == mode); + + if (evt->detail != detail) + g_print ("detail, evt %d vs %d\n", evt->detail, detail); + + g_assert (evt->detail == detail); +} + +/* Verify crossing events when moving into and out of a sensitive widget */ +static void +cursor_on_sensitive (CrossingTest *test, + gconstpointer user_data) +{ + move_cursor_away (test); + + start_events (test); + + set_cursor (test->button); + + check_event (test, + "W", + TRUE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR_VIRTUAL); + + check_event (test, + "E", + TRUE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR_VIRTUAL); + + check_event (test, + "B", + TRUE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR); + + g_assert (g_queue_is_empty (test->queue)); + + move_cursor_away (test); + + check_event (test, + "B", + FALSE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR); + + check_event (test, + "E", + FALSE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR_VIRTUAL); + + check_event (test, + "W", + FALSE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR_VIRTUAL); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +static void +change_sensitive_to_insensitive (CrossingTest *test, + gconstpointer user_data) +{ + move_cursor_away (test); + set_cursor (test->button); + + start_events (test); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), FALSE); + + check_event (test, + "B", + FALSE, + TRUE, /* synthesized */ + GDK_CROSSING_STATE_CHANGED, + GDK_NOTIFY_ANCESTOR); + + check_event (test, + "E", + FALSE, + TRUE, /* synthesized */ + GDK_CROSSING_STATE_CHANGED, + GDK_NOTIFY_VIRTUAL); + + check_event (test, + "W", + FALSE, + TRUE, /* synthesized */ + GDK_CROSSING_STATE_CHANGED, + GDK_NOTIFY_VIRTUAL); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +static void +change_insensitive_to_sensitive (CrossingTest *test, + gconstpointer user_data) +{ + move_cursor_away (test); + set_cursor (test->button); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), FALSE); + + start_events (test); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), TRUE); + + check_event (test, + "W", + TRUE, + TRUE, /* synthesized */ + GDK_CROSSING_STATE_CHANGED, + GDK_NOTIFY_VIRTUAL); + + check_event (test, + "E", + TRUE, + TRUE, /* synthesized */ + GDK_CROSSING_STATE_CHANGED, + GDK_NOTIFY_VIRTUAL); + + check_event (test, + "B", + TRUE, + TRUE, /* synthesized */ + GDK_CROSSING_STATE_CHANGED, + GDK_NOTIFY_ANCESTOR); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +static void +cursor_from_insensitive_to_sensitive (CrossingTest *test, + gconstpointer user_data) +{ + set_cursor (test->button); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), FALSE); + + start_events (test); + + set_cursor (test->check); + + check_event (test, + "C", + TRUE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +static void +cursor_from_sensitive_to_insensitive (CrossingTest *test, + gconstpointer user_data) +{ + set_cursor (test->check); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), FALSE); + + start_events (test); + + set_cursor (test->button); + + check_event (test, + "C", + FALSE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +static void +add_gtk_grab (CrossingTest *test, + gconstpointer user_data) +{ + set_cursor (test->button); + + start_events (test); + + gtk_grab_add (test->check); + + check_event (test, + "B", + FALSE, + TRUE, /* synthesized */ + GDK_CROSSING_GTK_GRAB, + GDK_NOTIFY_ANCESTOR); + + check_event (test, + "E", + FALSE, + TRUE, /* synthesized */ + GDK_CROSSING_GTK_GRAB, + GDK_NOTIFY_ANCESTOR); + + check_event (test, + "W", + FALSE, + TRUE, /* synthesized */ + GDK_CROSSING_GTK_GRAB, + GDK_NOTIFY_ANCESTOR); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +static void +remove_gtk_grab (CrossingTest *test, + gconstpointer user_data) +{ + set_cursor (test->button); + + gtk_grab_add (test->check); + + start_events (test); + + gtk_grab_remove (test->check); + + check_event (test, + "B", + TRUE, + TRUE, /* synthesized */ + GDK_CROSSING_GTK_UNGRAB, + GDK_NOTIFY_ANCESTOR); + + check_event (test, + "E", + TRUE, + TRUE, /* synthesized */ + GDK_CROSSING_GTK_UNGRAB, + GDK_NOTIFY_ANCESTOR); + + check_event (test, + "W", + TRUE, + TRUE, /* synthesized */ + GDK_CROSSING_GTK_UNGRAB, + GDK_NOTIFY_ANCESTOR); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +static void +cursor_from_shadowed_to_unshadowed (CrossingTest *test, + gconstpointer user_data) +{ + set_cursor (test->button); + + gtk_grab_add (test->check); + + start_events (test); + + set_cursor (test->check); + + check_event (test, + "C", + FALSE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR); + + check_event (test, + "C", + TRUE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +static void +cursor_from_unshadowed_to_shadowed (CrossingTest *test, + gconstpointer user_data) +{ + set_cursor (test->check); + + gtk_grab_add (test->check); + + start_events (test); + + set_cursor (test->button); + + check_event (test, + "C", + FALSE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR); + + check_event (test, + "C", + TRUE, + FALSE, /* native */ + GDK_CROSSING_NORMAL, + GDK_NOTIFY_NONLINEAR); + + g_assert (g_queue_is_empty (test->queue)); + + stop_events (test); +} + +int +main (int argc, + char **argv) +{ + gtk_test_init (&argc, &argv, NULL); + + g_test_add ("/crossings/cursor-on-sensitive", CrossingTest, NULL, + sensitivity_setup, cursor_on_sensitive, sensitivity_teardown); + + g_test_add ("/crossings/change-sensitive-to-insensitive", CrossingTest, NULL, + sensitivity_setup, change_sensitive_to_insensitive, sensitivity_teardown); + + g_test_add ("/crossings/cursor-from-insensitive-to-sensitive", CrossingTest, NULL, + sensitivity_setup, cursor_from_insensitive_to_sensitive, sensitivity_teardown); + + g_test_add ("/crossings/cursor-from-sensitive-to-insensitive", CrossingTest, NULL, + sensitivity_setup, cursor_from_sensitive_to_insensitive, sensitivity_teardown); + + g_test_add ("/crossings/change-insensitive-to-sensitive", CrossingTest, NULL, + sensitivity_setup, change_insensitive_to_sensitive, sensitivity_teardown); + + g_test_add ("/crossings/add-gtk-grab", CrossingTest, NULL, + sensitivity_setup, add_gtk_grab, sensitivity_teardown); + + g_test_add ("/crossings/remove-gtk-grab", CrossingTest, NULL, + sensitivity_setup, remove_gtk_grab, sensitivity_teardown); + + g_test_add ("/crossings/cursor-from-shadowed-to-unshadowed", CrossingTest, NULL, + sensitivity_setup, cursor_from_shadowed_to_unshadowed, sensitivity_teardown); + + g_test_add ("/crossings/cursor-from-unshadowed-to-shadowed", CrossingTest, NULL, + sensitivity_setup, cursor_from_unshadowed_to_shadowed, sensitivity_teardown); + + return g_test_run (); +}