Bug 56070 – Can't click button after setting it sensitive.

2008-07-31  Cody Russell  <bratsche@gnome.org>

        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
This commit is contained in:
Cody Russell 2008-08-01 03:30:50 +00:00 committed by Cody Russell
parent e9d978dff9
commit 4e3c97b3f2
11 changed files with 1628 additions and 32 deletions

View File

@ -1,3 +1,19 @@
2008-07-31 Cody Russell <bratsche@gnome.org>
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 <mclasen@redhat.com>
Bug 424207 printing hangs on unreachable cups server

View File

@ -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).
<!-- ##### ENUM GdkNotifyType ##### -->
<para>

View File

@ -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

View File

@ -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);

View File

@ -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);
}

View File

@ -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)

View File

@ -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);

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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

1254
gtk/tests/crossingevents.c Normal file

File diff suppressed because it is too large Load Diff