diff --git a/gdk/gdkwindow.c b/gdk/gdkwindow.c index 6782b283a7..cdebf2ea76 100644 --- a/gdk/gdkwindow.c +++ b/gdk/gdkwindow.c @@ -3651,23 +3651,6 @@ _gdk_window_process_updates_recurse (GdkWindow *window, g_object_unref (window); } - else if (window->window_type != GDK_WINDOW_FOREIGN) - { - /* No exposure mask set, so nothing will be drawn, the - * app relies on the background being what it specified - * for the window. So, we need to clear this manually. - * - * For foreign windows if expose is not set that generally - * means some other client paints them, so don't clear - * there. - * - * We use begin/end_paint around the clear so that we can - * piggyback on the implicit paint */ - - gdk_window_begin_paint_region (window, clipped_expose_region); - /* The actual clear happens in begin_paint_region */ - gdk_window_end_paint (window); - } } cairo_region_destroy (clipped_expose_region); diff --git a/gtk/gtkcontainer.c b/gtk/gtkcontainer.c index 2d7ab23d34..dc81530908 100644 --- a/gtk/gtkcontainer.c +++ b/gtk/gtkcontainer.c @@ -3366,7 +3366,7 @@ gtk_container_propagate_draw (GtkContainer *container, { GdkEventExpose *event; GtkAllocation allocation; - GdkWindow *window, *w; + GdkWindow *window, *w, *event_window, *child_in_window; int x, y; g_return_if_fail (GTK_IS_CONTAINER (container)); @@ -3375,13 +3375,26 @@ gtk_container_propagate_draw (GtkContainer *container, g_assert (gtk_widget_get_parent (child) == GTK_WIDGET (container)); + if (!gtk_widget_is_drawable (child)) + return; + + /* Only propagate to native child window if we're not handling + an expose (i.e. in a pure gtk_widget_draw() call */ event = _gtk_cairo_get_event (cr); - if (event) - { - if (gtk_widget_get_has_window (child) || - gtk_widget_get_window (child) != event->window) - return; - } + if (event && + (gtk_widget_get_has_window (child) && + gdk_window_has_native (gtk_widget_get_window (child)))) + return; + + /* Never propagate to a child window when exposing a window + that is not the one the child widget is in. */ + event_window = _gtk_cairo_get_event_window (cr); + if (gtk_widget_get_has_window (child)) + child_in_window = gdk_window_get_parent (gtk_widget_get_window (child)); + else + child_in_window = gtk_widget_get_window (child); + if (event_window != NULL && child_in_window != event_window) + return; cairo_save (cr); @@ -3423,7 +3436,7 @@ gtk_container_propagate_draw (GtkContainer *container, cairo_translate (cr, x, y); - _gtk_widget_draw_internal (child, cr, TRUE); + _gtk_widget_draw (child, cr); cairo_restore (cr); } diff --git a/gtk/gtkmain.c b/gtk/gtkmain.c index eeef8edcf1..4a5c7af479 100644 --- a/gtk/gtkmain.c +++ b/gtk/gtkmain.c @@ -1615,9 +1615,17 @@ gtk_main_do_event (GdkEvent *event) case GDK_EXPOSE: if (event->any.window && gtk_widget_get_double_buffered (event_widget)) { - gdk_window_begin_paint_region (event->any.window, event->expose.region); - gtk_widget_send_expose (event_widget, event); - gdk_window_end_paint (event->any.window); + /* We handle exposes only on native windows, relying on the + * draw() handler to propagate down to non-native windows. + * This is ok now that we child windows always are considered + * (semi)transparent. + */ + if (gdk_window_has_native (event->expose.window)) + { + gdk_window_begin_paint_region (event->any.window, event->expose.region); + gtk_widget_send_expose (event_widget, event); + gdk_window_end_paint (event->any.window); + } } else { diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index 15f6e62ec1..2001718f3b 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -854,9 +854,10 @@ static void gtk_widget_on_frame_clock_update (GdkFrameClock *frame_clock, GtkWidget *widget); static gboolean event_window_is_still_viewable (GdkEvent *event); +static void gtk_cairo_set_event_window (cairo_t *cr, + GdkWindow *window); static void gtk_cairo_set_event (cairo_t *cr, GdkEventExpose *event); -static void gtk_widget_propagate_alpha (GtkWidget *widget); /* --- variables --- */ static gpointer gtk_widget_parent_class = NULL; @@ -967,24 +968,9 @@ gtk_widget_draw_marshaller (GClosure *closure, gpointer invocation_hint, gpointer marshal_data) { - GtkWidget *widget = g_value_get_object (¶m_values[0]); - GdkEventExpose *tmp_event; - gboolean push_group; cairo_t *cr = g_value_get_boxed (¶m_values[1]); cairo_save (cr); - tmp_event = _gtk_cairo_get_event (cr); - - push_group = - widget->priv->opacity_group || - (widget->priv->alpha != 255 && - (!gtk_widget_get_has_window (widget) || tmp_event == NULL)); - - if (push_group) - { - cairo_push_group (cr); - gtk_cairo_set_event (cr, NULL); - } _gtk_marshal_BOOLEAN__BOXED (closure, return_value, @@ -994,14 +980,6 @@ gtk_widget_draw_marshaller (GClosure *closure, marshal_data); - if (push_group) - { - cairo_pop_group_to_source (cr); - cairo_set_operator (cr, CAIRO_OPERATOR_OVER); - cairo_paint_with_alpha (cr, widget->priv->alpha / 255.0); - } - - gtk_cairo_set_event (cr, tmp_event); cairo_restore (cr); } @@ -1014,9 +992,6 @@ gtk_widget_draw_marshallerv (GClosure *closure, int n_params, GType *param_types) { - GtkWidget *widget = GTK_WIDGET (instance); - GdkEventExpose *tmp_event; - gboolean push_group; cairo_t *cr; va_list args_copy; @@ -1024,18 +999,6 @@ gtk_widget_draw_marshallerv (GClosure *closure, cr = va_arg (args_copy, gpointer); cairo_save (cr); - tmp_event = _gtk_cairo_get_event (cr); - - push_group = - widget->priv->opacity_group || - (widget->priv->alpha != 255 && - (!gtk_widget_get_has_window (widget) || tmp_event == NULL)); - - if (push_group) - { - cairo_push_group (cr); - gtk_cairo_set_event (cr, NULL); - } _gtk_marshal_BOOLEAN__BOXEDv (closure, return_value, @@ -1046,14 +1009,6 @@ gtk_widget_draw_marshallerv (GClosure *closure, param_types); - if (push_group) - { - cairo_pop_group_to_source (cr); - cairo_set_operator (cr, CAIRO_OPERATOR_OVER); - cairo_paint_with_alpha (cr, widget->priv->alpha / 255.0); - } - - gtk_cairo_set_event (cr, tmp_event); cairo_restore (cr); va_end (args_copy); @@ -4246,8 +4201,6 @@ gtk_widget_unparent (GtkWidget *widget) g_object_notify_queue_clear (G_OBJECT (widget), nqueue); g_object_notify_queue_thaw (G_OBJECT (widget), nqueue); - gtk_widget_propagate_alpha (widget); - gtk_widget_pop_verify_invariants (widget); g_object_unref (widget); } @@ -6326,6 +6279,23 @@ gtk_widget_real_mnemonic_activate (GtkWidget *widget, return TRUE; } +static const cairo_user_data_key_t event_window_key; + +GdkWindow * +_gtk_cairo_get_event_window (cairo_t *cr) +{ + g_return_val_if_fail (cr != NULL, NULL); + + return cairo_get_user_data (cr, &event_window_key); +} + +static void +gtk_cairo_set_event_window (cairo_t *cr, + GdkWindow *event_window) +{ + cairo_set_user_data (cr, &event_window_key, event_window, NULL); +} + static const cairo_user_data_key_t event_key; GdkEventExpose * @@ -6338,7 +6308,7 @@ _gtk_cairo_get_event (cairo_t *cr) static void gtk_cairo_set_event (cairo_t *cr, - GdkEventExpose *event) + GdkEventExpose *event) { cairo_set_user_data (cr, &event_key, event, NULL); } @@ -6367,15 +6337,15 @@ gboolean gtk_cairo_should_draw_window (cairo_t *cr, GdkWindow *window) { - GdkEventExpose *event; + GdkWindow *event_window; g_return_val_if_fail (cr != NULL, FALSE); g_return_val_if_fail (GDK_IS_WINDOW (window), FALSE); - event = _gtk_cairo_get_event (cr); + event_window = _gtk_cairo_get_event_window (cr); - return event == NULL || - event->window == window; + return event_window == NULL || + event_window == window; } static gboolean @@ -6392,17 +6362,20 @@ gtk_widget_get_clip_draw (GtkWidget *widget) return TRUE; } -/* code shared by gtk_container_propagate_draw() and - * gtk_widget_draw() - */ -void +static void _gtk_widget_draw_internal (GtkWidget *widget, cairo_t *cr, - gboolean clip_to_size) + gboolean clip_to_size, + GdkWindow *window) { + GdkWindow *tmp_event_window; + if (!gtk_widget_is_drawable (widget)) return; + tmp_event_window = _gtk_cairo_get_event_window (cr); + gtk_cairo_set_event_window (cr, window); + clip_to_size &= gtk_widget_get_clip_draw (widget); if (clip_to_size) @@ -6443,7 +6416,7 @@ _gtk_widget_draw_internal (GtkWidget *widget, #endif if (cairo_status (cr) && - _gtk_cairo_get_event (cr)) + _gtk_cairo_get_event_window (cr)) { /* We check the event so we only warn about internal GTK calls. * Errors might come from PDF streams having write failures and @@ -6455,8 +6428,174 @@ _gtk_widget_draw_internal (GtkWidget *widget, cairo_status_to_string (cairo_status (cr))); } } + + gtk_cairo_set_event_window (cr, tmp_event_window); } +/* Emit draw() on the widget that owns window, + and on any child windows that also belong + to the widget. */ +static void +_gtk_widget_draw_windows (GdkWindow *window, + cairo_t *cr, + int window_x, + int window_y) +{ + cairo_pattern_t *pattern; + gboolean do_clip; + GtkWidget *widget = NULL; + GList *l; + int x, y; + + if (!gdk_window_is_viewable (window)) + return; + + cairo_save (cr); + cairo_translate (cr, window_x, window_y); + cairo_rectangle (cr, 0, 0, + gdk_window_get_width (window), + gdk_window_get_height (window)); + cairo_clip (cr); + + if (gdk_cairo_get_clip_rectangle (cr, NULL)) + { + gdk_window_get_user_data (window, (gpointer *) &widget); + + /* Only clear bg if double bufferer. This is what we used + to do before, where begin_paint() did the clearing. */ + pattern = gdk_window_get_background_pattern (window); + if (pattern != NULL && + widget->priv->double_buffered) + { + cairo_save (cr); + cairo_set_source (cr, pattern); + cairo_paint (cr); + cairo_restore (cr); + } + + do_clip = _gtk_widget_get_translation_to_window (widget, window, + &x, &y); + cairo_save (cr); + cairo_translate (cr, -x, -y); + _gtk_widget_draw_internal (widget, cr, do_clip, window); + cairo_restore (cr); + + for (l = g_list_last (gdk_window_peek_children (window)); + l != NULL; + l = l->prev) + { + GdkWindow *child_window = l->data; + gpointer child_data; + GdkWindowType type; + int wx, wy; + + type = gdk_window_get_window_type (child_window); + if (!gdk_window_is_visible (child_window) || + gdk_window_is_input_only (child_window) || + type == GDK_WINDOW_OFFSCREEN || + type == GDK_WINDOW_FOREIGN) + continue; + + gdk_window_get_user_data (child_window, &child_data); + if (child_data == (gpointer)widget) + { + gdk_window_get_position (child_window, &wx, &wy); + _gtk_widget_draw_windows (child_window, cr, wx,wy); + } + } + } + + cairo_restore (cr); +} + +void +_gtk_widget_draw (GtkWidget *widget, + cairo_t *cr) +{ + GdkWindow *window, *child_window; + gpointer child_data; + GList *l; + int wx, wy; + gboolean push_group; + GdkWindowType type; + + /* We get expose events only on native windows, so the draw + * implementation has to walk the entire widget hierarchy, except + * that it stops at native subwindows while we're in an expose + * event (_gtk_cairo_get_event () != NULL). + * + * However, we need to properly clip drawing into child windows + * to avoid drawing outside if widgets use e.g. cairo_paint(), so + * we traverse over GdkWindows as well as GtkWidgets. + * + * In order to be able to have opacity groups for entire widgets + * that consists of multiple windows we collect all the windows + * that belongs to a widget and draw them in one go. This means + * we may somewhat reorder GdkWindows when we paint them, but + * thats not generally a problem, as if you want a guaranteed + * order you generally use a windowed widget where you control + * the window hierarchy. + */ + + cairo_save (cr); + + push_group = + widget->priv->opacity_group || + (widget->priv->alpha != 255 && + !gtk_widget_is_toplevel (widget)); + + if (push_group) + cairo_push_group (cr); + + window = gtk_widget_get_window (widget); + if (gtk_widget_get_has_window (widget)) + { + /* The widget will be completely contained in its window, so just + * expose that (and any child window belonging to the widget) */ + _gtk_widget_draw_windows (window, cr, 0, 0); + } + else + { + /* The widget draws in its parent window, so we send a draw() for + * that. */ + _gtk_widget_draw_internal (widget, cr, TRUE, window); + + /* But, it may also have child windows in the parent which we should + * draw (after having drawn on the parent) */ + for (l = g_list_last (gdk_window_peek_children (window)); + l != NULL; + l = l->prev) + { + child_window = l->data; + type = gdk_window_get_window_type (child_window); + if (!gdk_window_is_visible (child_window) || + gdk_window_is_input_only (child_window) || + type == GDK_WINDOW_OFFSCREEN || + type == GDK_WINDOW_FOREIGN) + continue; + + gdk_window_get_user_data (child_window, &child_data); + if (child_data == (gpointer)widget) + { + gdk_window_get_position (child_window, &wx, &wy); + _gtk_widget_draw_windows (child_window, cr, + wx - widget->priv->allocation.x, + wy - widget->priv->allocation.y); + } + } + } + + if (push_group) + { + cairo_pop_group_to_source (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + cairo_paint_with_alpha (cr, widget->priv->alpha / 255.0); + } + + cairo_restore (cr); +} + + /** * gtk_widget_draw: * @widget: the widget to draw. It must be drawable (see @@ -6487,24 +6626,11 @@ void gtk_widget_draw (GtkWidget *widget, cairo_t *cr) { - GdkEventExpose *tmp_event; - g_return_if_fail (GTK_IS_WIDGET (widget)); g_return_if_fail (!widget->priv->alloc_needed); g_return_if_fail (cr != NULL); - cairo_save (cr); - /* We have to reset the event here so that draw functions can call - * gtk_widget_draw() on random other widgets and get the desired - * effect: Drawing all contents, not just the current window. - */ - tmp_event = _gtk_cairo_get_event (cr); - gtk_cairo_set_event (cr, NULL); - - _gtk_widget_draw_internal (widget, cr, TRUE); - - gtk_cairo_set_event (cr, tmp_event); - cairo_restore (cr); + _gtk_widget_draw (widget, cr); } static gboolean @@ -6799,8 +6925,6 @@ gtk_widget_send_expose (GtkWidget *widget, { gboolean result = FALSE; cairo_t *cr; - int x, y; - gboolean do_clip; g_return_val_if_fail (GTK_IS_WIDGET (widget), TRUE); g_return_val_if_fail (gtk_widget_get_realized (widget), TRUE); @@ -6808,21 +6932,18 @@ gtk_widget_send_expose (GtkWidget *widget, g_return_val_if_fail (event->type == GDK_EXPOSE, TRUE); cr = gdk_cairo_create (event->expose.window); - gtk_cairo_set_event (cr, &event->expose); - gdk_cairo_region (cr, event->expose.region); cairo_clip (cr); - do_clip = _gtk_widget_get_translation_to_window (widget, - event->expose.window, - &x, &y); - cairo_translate (cr, -x, -y); + gtk_cairo_set_event (cr, &event->expose); - _gtk_widget_draw_internal (widget, cr, do_clip); + if (event->expose.window == widget->priv->window) + _gtk_widget_draw (widget, cr); + else + _gtk_widget_draw_windows (event->expose.window, cr, 0, 0); - /* unset here, so if someone keeps a reference to cr we - * don't leak the window. */ gtk_cairo_set_event (cr, NULL); + cairo_destroy (cr); return result; @@ -8504,6 +8625,9 @@ gtk_widget_get_app_paintable (GtkWidget *widget) * expose events, since even the clearing to the background color or * pixmap will not happen automatically (as it is done in * gdk_window_begin_paint_region()). + * + * Since 3.10 this function only works for widgets with native + * windows. **/ void gtk_widget_set_double_buffered (GtkWidget *widget, @@ -8762,8 +8886,6 @@ gtk_widget_set_parent (GtkWidget *widget, gtk_widget_queue_compute_expand (parent); } - gtk_widget_propagate_alpha (widget); - gtk_widget_pop_verify_invariants (widget); } @@ -14603,10 +14725,6 @@ gtk_widget_set_window (GtkWidget *widget, { priv->window = window; - if (gtk_widget_get_has_window (widget) && window != NULL && !gdk_window_has_native (window)) - gdk_window_set_opacity (window, - priv->norender ? 0 : priv->alpha / 255.0); - g_object_notify (G_OBJECT (widget), "window"); } } @@ -14745,86 +14863,8 @@ gtk_widget_set_support_multidevice (GtkWidget *widget, * in gtk_widget_set_opacity, secondly we can get it from the css opacity. These two * are multiplied together to form the total alpha. Secondly, the user can specify * an opacity group for a widget, which means we must essentially handle it as having alpha. - * - * We handle opacity in two ways. For a windowed widget, with opacity set but no opacity - * group we directly set the opacity of widget->window. This will cause gdk to properly - * redirect drawing inside the window to a buffer and do OVER paint_with_alpha. - * - * However, if the widget is not windowed, or the user specified an opacity group for the - * widget we do the opacity handling in the ::draw marshaller for the widget. A naive - * implementation of this would break for windowed widgets or descendant widgets with - * windows, as these would not be handled by the ::draw signal. To handle this we set - * all such gdkwindows as fully transparent and then override gtk_cairo_should_draw_window() - * to make the draw signal propagate to *all* child widgets/windows. - * - * Note: We don't make all child windows fully transparent, we stop at the first one - * in each branch when propagating down the hierarchy. */ - -/* This is called when priv->alpha or priv->opacity_group group changes, and should - * update priv->norender and GdkWindow opacity for this widget and any children that - * needs changing. It is also called whenver the parent changes, the parents - * norender_children state changes, or the has_window state of the widget changes. - */ -static void -gtk_widget_propagate_alpha (GtkWidget *widget) -{ - GtkWidgetPrivate *priv = widget->priv; - GtkWidget *parent; - gboolean norender, norender_children; - GList *l; - - parent = priv->parent; - - /* Norender affects only windowed widget and means don't render widget->window in the - normal fashion. - We only set this if the parent has norender_children, because: - a) For an opacity group (that does not have a norender_children parent) we still - need to render the window or we will never get an expose event. - b) For alpha we set the opacity of window->widget directly, so no other - work is needed. - */ - norender = (parent != NULL && parent->priv->norender_children); - - /* windows under this widget should not render if: - a) This widget has an opacity group - b) This widget has alpha and is no-windowed (otherwise we'd set alpha on widget->window) - c) This widget has norender but is no-windowed (a windowed widget would "swallow" the norender) - */ - norender_children = - priv->opacity_group || - (!gtk_widget_get_has_window (widget) && - ( norender || priv->alpha != 255)); - - if (gtk_widget_get_has_window (widget)) - { - if (priv->window != NULL && - (!gdk_window_has_native (priv->window) || gtk_widget_is_toplevel (widget))) - gdk_window_set_opacity (priv->window, - norender ? 0 : priv->alpha / 255.0); - } - - for (l = priv->registered_windows; l != NULL; l = l->next) - { - GdkWindow *w = l->data; - if (w != priv->window && !gdk_window_has_native (w)) - gdk_window_set_opacity (w, norender_children ? 0.0 : 1.0); - } - - priv->norender = norender; - if (priv->norender_children != norender_children) - { - priv->norender_children = norender_children; - - if (GTK_IS_CONTAINER (widget)) - gtk_container_forall (GTK_CONTAINER (widget), (GtkCallback)gtk_widget_propagate_alpha, NULL); - } - - if (gtk_widget_get_realized (widget)) - gtk_widget_queue_draw (widget); -} - static void gtk_widget_update_alpha (GtkWidget *widget) { @@ -14851,8 +14891,12 @@ gtk_widget_update_alpha (GtkWidget *widget) priv->alpha = alpha; - gtk_widget_propagate_alpha (widget); + if (gtk_widget_is_toplevel (widget)) + gdk_window_set_opacity (priv->window, + priv->alpha / 255.0); + if (gtk_widget_get_realized (widget)) + gtk_widget_queue_draw (widget); } static void @@ -14872,7 +14916,8 @@ gtk_widget_set_has_opacity_group (GtkWidget *widget, priv->opacity_group = has_opacity_group; - gtk_widget_propagate_alpha (widget); + if (gtk_widget_get_realized (widget)) + gtk_widget_queue_draw (widget); } /** diff --git a/gtk/gtkwidgetprivate.h b/gtk/gtkwidgetprivate.h index 7af552d51e..87943904ce 100644 --- a/gtk/gtkwidgetprivate.h +++ b/gtk/gtkwidgetprivate.h @@ -45,6 +45,8 @@ void _gtk_widget_set_shadowed (GtkWidget *widget, gboolean _gtk_widget_get_alloc_needed (GtkWidget *widget); void _gtk_widget_set_alloc_needed (GtkWidget *widget, gboolean alloc_needed); +void _gtk_widget_draw (GtkWidget *widget, + cairo_t *cr); void _gtk_widget_add_sizegroup (GtkWidget *widget, gpointer group); @@ -91,11 +93,9 @@ const gchar* _gtk_widget_get_accel_path (GtkWidget *widget, AtkObject * _gtk_widget_peek_accessible (GtkWidget *widget); +GdkWindow * _gtk_cairo_get_event_window (cairo_t *cr); GdkEventExpose * _gtk_cairo_get_event (cairo_t *cr); -void _gtk_widget_draw_internal (GtkWidget *widget, - cairo_t *cr, - gboolean clip_to_size); void _gtk_widget_set_has_default (GtkWidget *widget, gboolean has_default); void _gtk_widget_set_has_grab (GtkWidget *widget,