X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkwidget.c;h=37460e3419680aab8b42d52e0f51003bb474fc0a;hb=1074aa0c49f647ed4b2a969618051c59da5aad01;hp=a82e62be0bcf8ff3be0b8344317dec9c17fba706;hpb=bf35c2f044296bfc4d016010adde4bf09a1a311d;p=~andy%2Fgtk diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index a82e62be0..37460e341 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -36,6 +37,7 @@ #include "gtkaccelmapprivate.h" #include "gtkclipboard.h" #include "gtkcssstylepropertyprivate.h" +#include "gtkcssnumbervalueprivate.h" #include "gtkiconfactory.h" #include "gtkintl.h" #include "gtkmarshalers.h" @@ -272,7 +274,7 @@ * * * - * Clickable Button + * Clickable Button * * * @@ -300,12 +302,12 @@ * all flags will be set on children (think prelight or active), but we * might want to not do this for some. */ -#define GTK_STATE_FLAGS_DONT_PROPAGATE (GTK_STATE_FLAG_FOCUSED) +#define GTK_STATE_FLAGS_DONT_PROPAGATE (GTK_STATE_FLAG_FOCUSED | GTK_STATE_FLAG_DIR_LTR | GTK_STATE_FLAG_DIR_RTL) #define GTK_STATE_FLAGS_DO_PROPAGATE (~GTK_STATE_FLAGS_DONT_PROPAGATE) #define WIDGET_CLASS(w) GTK_WIDGET_GET_CLASS (w) -#define GTK_STATE_FLAGS_BITS 7 +#define GTK_STATE_FLAGS_BITS 9 struct _GtkWidgetPrivate { @@ -357,6 +359,13 @@ struct _GtkWidgetPrivate /* SizeGroup related flags */ guint have_size_groups : 1; + guint opacity_group : 1; + guint norender_children : 1; + guint norender : 1; /* Don't expose windows, instead recurse via draw */ + + guint8 alpha; + guint8 user_alpha; + /* The widget's name. If the widget does not have a name * (the name is NULL), then its name (as returned by * "gtk_widget_get_name") is its class's name. @@ -395,10 +404,14 @@ struct _GtkWidgetPrivate * GTK_NO_WINDOW flag being set). */ GdkWindow *window; + GList *registered_windows; /* The widget's parent */ GtkWidget *parent; + /* Animations and other things to update on clock ticks */ + GList *tick_callbacks; + #ifdef G_ENABLE_DEBUG /* Number of gtk_widget_push_verify_invariants () */ guint verifying_invariants_count; @@ -507,6 +520,7 @@ enum { PROP_TOOLTIP_MARKUP, PROP_TOOLTIP_TEXT, PROP_WINDOW, + PROP_OPACITY, PROP_DOUBLE_BUFFERED, PROP_HALIGN, PROP_VALIGN, @@ -524,16 +538,10 @@ enum { typedef struct _GtkStateData GtkStateData; -enum { - STATE_CHANGE_REPLACE, - STATE_CHANGE_SET, - STATE_CHANGE_UNSET -}; - struct _GtkStateData { - guint flags : GTK_STATE_FLAGS_BITS; - guint operation : 2; + guint flags_to_set; + guint flags_to_unset; }; /* --- prototypes --- */ @@ -608,7 +616,8 @@ static PangoContext* gtk_widget_peek_pango_context (GtkWidget *widget); static void gtk_widget_update_pango_context (GtkWidget *widget); static void gtk_widget_propagate_state (GtkWidget *widget, GtkStateData *data); -; +static void gtk_widget_update_alpha (GtkWidget *widget); + static gint gtk_widget_event_internal (GtkWidget *widget, GdkEvent *event); static gboolean gtk_widget_real_mnemonic_activate (GtkWidget *widget, @@ -704,7 +713,14 @@ static void gtk_widget_set_device_enabled_internal (GtkWidget *widget, GdkDevice *device, gboolean recurse, gboolean enabled); + +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 (cairo_t *cr, + GdkEventExpose *event); +static void gtk_widget_propagate_alpha (GtkWidget *widget); /* --- variables --- */ static gpointer gtk_widget_parent_class = NULL; @@ -814,9 +830,24 @@ 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, @@ -825,6 +856,15 @@ gtk_widget_draw_marshaller (GClosure *closure, invocation_hint, 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); } @@ -837,6 +877,9 @@ 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; @@ -844,6 +887,18 @@ 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, @@ -853,6 +908,15 @@ gtk_widget_draw_marshallerv (GClosure *closure, n_params, 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); @@ -1425,6 +1489,25 @@ gtk_widget_class_init (GtkWidgetClass *klass) FALSE, GTK_PARAM_READWRITE)); + /** + * GtkWidget:opacity: + * + * The requested opacity of the widget. See gtk_widget_set_opacity() for + * more details about window opacity. + * + * Before 3.8 this was only availible in GtkWindow + * + * Since: 3.8 + */ + g_object_class_install_property (gobject_class, + PROP_OPACITY, + g_param_spec_double ("opacity", + P_("Opacity for Widget"), + P_("The opacity of the widget, from 0 to 1"), + 0.0, + 1.0, + 1.0, + GTK_PARAM_READWRITE)); /** * GtkWidget::show: * @widget: the object which received the signal. @@ -1722,6 +1805,13 @@ gtk_widget_class_init (GtkWidgetClass *klass) * restore it. The signal emission takes care of calling cairo_save() * before and cairo_restore() after invoking the handler. * + * The signal handler will get a @cr with a clip region already set to the + * widget's dirty region, i.e. to the area that needs repainting. Complicated + * widgets that want to avoid redrawing themselves completely can get the full + * extents of the clip region with gdk_cairo_get_clip_rectangle(), or they can + * get a finer-grained representation of the dirty region with + * cairo_copy_clip_rectangle_list(). + * * Returns: %TRUE to stop other handlers from being invoked for the event. % %FALSE to propagate the event further. * @@ -3489,6 +3579,9 @@ gtk_widget_set_property (GObject *object, gtk_widget_set_vexpand (widget, g_value_get_boolean (value)); g_object_thaw_notify (G_OBJECT (widget)); break; + case PROP_OPACITY: + gtk_widget_set_opacity (widget, g_value_get_double (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -3645,6 +3738,9 @@ gtk_widget_get_property (GObject *object, gtk_widget_get_hexpand (widget) && gtk_widget_get_vexpand (widget)); break; + case PROP_OPACITY: + g_value_set_double (value, gtk_widget_get_opacity (widget)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -3667,6 +3763,8 @@ gtk_widget_init (GtkWidget *widget) priv->allocation.y = -1; priv->allocation.width = 1; priv->allocation.height = 1; + priv->user_alpha = 255; + priv->alpha = 255; priv->window = NULL; priv->parent = NULL; @@ -3675,6 +3773,23 @@ gtk_widget_init (GtkWidget *widget) priv->double_buffered = TRUE; priv->redraw_on_alloc = TRUE; priv->alloc_needed = TRUE; + + switch (gtk_widget_get_direction (widget)) + { + case GTK_TEXT_DIR_LTR: + priv->state_flags = GTK_STATE_FLAG_DIR_LTR; + break; + + case GTK_TEXT_DIR_RTL: + priv->state_flags = GTK_STATE_FLAG_DIR_RTL; + break; + + case GTK_TEXT_DIR_NONE: + default: + g_assert_not_reached (); + break; + } + /* this will be set to TRUE if the widget gets a child or if the * expand flag is set on the widget, but until one of those happen @@ -3940,6 +4055,8 @@ 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); } @@ -4402,6 +4519,239 @@ gtk_widget_update_devices_mask (GtkWidget *widget, gtk_widget_set_device_enabled_internal (widget, GDK_DEVICE (l->data), recurse, TRUE); } +typedef struct _GtkTickCallbackInfo GtkTickCallbackInfo; + +struct _GtkTickCallbackInfo +{ + guint refcount; + + guint id; + GtkTickCallback callback; + gpointer user_data; + GDestroyNotify notify; + + guint destroyed : 1; +}; + +static void +ref_tick_callback_info (GtkTickCallbackInfo *info) +{ + info->refcount++; +} + +static void +unref_tick_callback_info (GtkWidget *widget, + GtkTickCallbackInfo *info, + GList *link) +{ + GtkWidgetPrivate *priv = widget->priv; + + info->refcount--; + if (info->refcount == 0) + { + priv->tick_callbacks = g_list_delete_link (priv->tick_callbacks, link); + if (info->notify) + info->notify (info->user_data); + g_slice_free (GtkTickCallbackInfo, info); + } + + if (priv->tick_callbacks == NULL && priv->realized) + { + GdkFrameClock *frame_clock = gtk_widget_get_frame_clock (widget); + g_signal_handlers_disconnect_by_func (frame_clock, + (gpointer) gtk_widget_on_frame_clock_update, + widget); + gdk_frame_clock_end_updating (frame_clock); + } +} + +static void +destroy_tick_callback_info (GtkWidget *widget, + GtkTickCallbackInfo *info, + GList *link) +{ + if (!info->destroyed) + { + info->destroyed = TRUE; + unref_tick_callback_info (widget, info, link); + } +} + +static void +gtk_widget_on_frame_clock_update (GdkFrameClock *frame_clock, + GtkWidget *widget) +{ + GtkWidgetPrivate *priv = widget->priv; + GList *l; + + g_object_ref (widget); + + for (l = priv->tick_callbacks; l;) + { + GtkTickCallbackInfo *info = l->data; + GList *next; + + ref_tick_callback_info (info); + if (!info->destroyed) + { + if (info->callback (widget, + frame_clock, + info->user_data) == G_SOURCE_REMOVE) + { + destroy_tick_callback_info (widget, info, l); + } + } + + next = l->next; + unref_tick_callback_info (widget, info, l); + l = next; + } + + g_object_unref (widget); +} + +static guint tick_callback_id; + +/** + * gtk_widget_add_tick_callback: + * @widget: a #GtkWidget + * @callback: function to call for updating animations + * @user_data: data to pass to @callback + * @notify: function to call to free @user_data when the callback is removed. + * + * Queues a animation frame update and adds a callback to be called + * before each frame. Until the tick callback is removed, it will be + * called frequently (usually at the frame rate of the output device + * or as quickly as the application an be repainted, whichever is + * slower). For this reason, is most suitable for handling graphics + * that change every frame or every few frames. The tick callback does + * not automatically imply a relayout or repaint. If you want a + * repaint or relayout, and aren't changing widget properties that + * would trigger that (for example, changing the text of a #GtkLabel), + * then you will have to call gtk_widget_queue_resize() or + * gtk_widget_queue_draw_area() yourself. + * + * gdk_frame_clock_get_frame_time() should generally be used for timing + * continuous animations and + * gdk_frame_timings_get_predicted_presentation_time() if you are + * trying to display isolated frames at particular times. + * + * This is a more convenient alternative to connecting directly to the + * #GdkFrameClock::update signal of #GdkFrameClock, since you don't + * have to worry about when a #GdkFrameClock is assigned to a widget. + * + * Returns: an id for the connection of this callback. Remove the callback + * by passing it to gtk_widget_remove_tick_callback() + * + * Since: 3.8 + */ +guint +gtk_widget_add_tick_callback (GtkWidget *widget, + GtkTickCallback callback, + gpointer user_data, + GDestroyNotify notify) +{ + GtkWidgetPrivate *priv; + GtkTickCallbackInfo *info; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), 0); + + priv = widget->priv; + + if (priv->tick_callbacks == NULL && priv->realized) + { + GdkFrameClock *frame_clock = gtk_widget_get_frame_clock (widget); + g_signal_connect (frame_clock, "update", + G_CALLBACK (gtk_widget_on_frame_clock_update), + widget); + gdk_frame_clock_begin_updating (frame_clock); + } + + info = g_slice_new0 (GtkTickCallbackInfo); + + info->refcount = 1; + info->id = ++tick_callback_id; + info->callback = callback; + info->user_data = user_data; + info->notify = notify; + + priv->tick_callbacks = g_list_prepend (priv->tick_callbacks, + info); + + return info->id; +} + +/** + * gtk_widget_remove_tick_callback: + * @widget: a #GtkWidget + * @id: an id returned by gtk_widget_add_tick_callback() + * + * Removes a tick callback previously registered with + * gtk_widget_add_tick_callback(). + * + * Since: 3.8 + */ +void +gtk_widget_remove_tick_callback (GtkWidget *widget, + guint id) +{ + GtkWidgetPrivate *priv; + GList *l; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + priv = widget->priv; + + for (l = priv->tick_callbacks; l; l = l->next) + { + GtkTickCallbackInfo *info = l->data; + if (info->id == id) + destroy_tick_callback_info (widget, info, l); + } +} + +static void +gtk_widget_connect_frame_clock (GtkWidget *widget, + GdkFrameClock *frame_clock) +{ + GtkWidgetPrivate *priv = widget->priv; + + if (GTK_IS_CONTAINER (widget)) + _gtk_container_maybe_start_idle_sizer (GTK_CONTAINER (widget)); + + if (priv->tick_callbacks != NULL) + { + g_signal_connect (frame_clock, "update", + G_CALLBACK (gtk_widget_on_frame_clock_update), + widget); + gdk_frame_clock_begin_updating (frame_clock); + } + + if (priv->context) + gtk_style_context_set_frame_clock (priv->context, frame_clock); +} + +static void +gtk_widget_disconnect_frame_clock (GtkWidget *widget, + GdkFrameClock *frame_clock) +{ + GtkWidgetPrivate *priv = widget->priv; + + if (GTK_IS_CONTAINER (widget)) + _gtk_container_stop_idle_sizer (GTK_CONTAINER (widget)); + + if (priv->tick_callbacks) + { + g_signal_handlers_disconnect_by_func (frame_clock, + (gpointer) gtk_widget_on_frame_clock_update, + widget); + gdk_frame_clock_end_updating (frame_clock); + } + + if (priv->context) + gtk_style_context_set_frame_clock (priv->context, NULL); +} + /** * gtk_widget_realize: * @widget: a #GtkWidget @@ -4482,6 +4832,9 @@ gtk_widget_realize (GtkWidget *widget) _gtk_widget_enable_device_events (widget); gtk_widget_update_devices_mask (widget, TRUE); + gtk_widget_connect_frame_clock (widget, + gtk_widget_get_frame_clock (widget)); + gtk_widget_pop_verify_invariants (widget); } } @@ -4514,6 +4867,9 @@ gtk_widget_unrealize (GtkWidget *widget) if (widget->priv->mapped) gtk_widget_unmap (widget); + gtk_widget_disconnect_frame_clock (widget, + gtk_widget_get_frame_clock (widget)); + g_signal_emit (widget, widget_signals[UNREALIZE], 0); g_assert (!widget->priv->mapped); gtk_widget_set_realized (widget, FALSE); @@ -4674,6 +5030,61 @@ gtk_widget_queue_resize_no_redraw (GtkWidget *widget) _gtk_size_group_queue_resize (widget, 0); } +/** + * gtk_widget_get_frame_clock: + * @widget: a #GtkWidget + * + * Obtains the frame clock for a widget. The frame clock is a global + * "ticker" that can be used to drive animations and repaints. The + * most common reason to get the frame clock is to call + * gdk_frame_clock_get_frame_time(), in order to get a time to use for + * animating. For example you might record the start of the animation + * with an initial value from gdk_frame_clock_get_frame_time(), and + * then update the animation by calling + * gdk_frame_clock_get_frame_time() again during each repaint. + * + * gdk_frame_clock_request_phase() will result in a new frame on the + * clock, but won't necessarily repaint any widgets. To repaint a + * widget, you have to use gtk_widget_queue_draw() which invalidates + * the widget (thus scheduling it to receive a draw on the next + * frame). gtk_widget_queue_draw() will also end up requesting a frame + * on the appropriate frame clock. + * + * A widget's frame clock will not change while the widget is + * mapped. Reparenting a widget (which implies a temporary unmap) can + * change the widget's frame clock. + * + * Unrealized widgets do not have a frame clock. + * + * Return value: (transfer none): a #GdkFrameClock (or #NULL if widget is unrealized) + * + * Since: 3.0 + */ +GdkFrameClock* +gtk_widget_get_frame_clock (GtkWidget *widget) +{ + g_return_val_if_fail (GTK_IS_WIDGET (widget), 0); + + if (widget->priv->realized) + { + /* We use gtk_widget_get_toplevel() here to make it explicit that + * the frame clock is a property of the toplevel that a widget + * is anchored to; gdk_window_get_toplevel() will go up the + * hierarchy anyways, but should squash any funny business with + * reparenting windows and widgets. + */ + GtkWidget *toplevel = gtk_widget_get_toplevel (widget); + GdkWindow *window = gtk_widget_get_window (toplevel); + g_assert (window != NULL); + + return gdk_window_get_frame_clock (window); + } + else + { + return NULL; + } +} + /** * gtk_widget_size_request: * @widget: a #GtkWidget @@ -4806,6 +5217,9 @@ gtk_widget_size_allocate (GtkWidget *widget, g_return_if_fail (GTK_IS_WIDGET (widget)); + if (!priv->visible && !gtk_widget_is_toplevel (widget)) + return; + gtk_widget_push_verify_invariants (widget); #ifdef G_ENABLE_DEBUG @@ -6702,6 +7116,7 @@ gtk_widget_real_style_updated (GtkWidget *widget) GtkWidgetPrivate *priv = widget->priv; gtk_widget_update_pango_context (widget); + gtk_widget_update_alpha (widget); if (priv->style != NULL && priv->style != gtk_widget_get_default_style ()) @@ -7088,13 +7503,13 @@ gtk_widget_set_receives_default (GtkWidget *widget, * gtk_widget_get_receives_default: * @widget: a #GtkWidget * - * Determines whether @widget is alyways treated as default widget - * withing its toplevel when it has the focus, even if another widget + * Determines whether @widget is always treated as the default widget + * within its toplevel when it has the focus, even if another widget * is the default. * * See gtk_widget_set_receives_default(). * - * Return value: %TRUE if @widget acts as default widget when focussed, + * Return value: %TRUE if @widget acts as the default widget when focussed, * %FALSE otherwise * * Since: 2.18 @@ -7248,9 +7663,9 @@ gtk_widget_get_name (GtkWidget *widget) } static void -_gtk_widget_update_state_flags (GtkWidget *widget, - GtkStateFlags flags, - guint operation) +gtk_widget_update_state_flags (GtkWidget *widget, + GtkStateFlags flags_to_set, + GtkStateFlags flags_to_unset) { GtkWidgetPrivate *priv; @@ -7259,23 +7674,20 @@ _gtk_widget_update_state_flags (GtkWidget *widget, /* Handle insensitive first, since it is propagated * differently throughout the widget hierarchy. */ - if ((priv->state_flags & GTK_STATE_FLAG_INSENSITIVE) && (flags & GTK_STATE_FLAG_INSENSITIVE) && (operation == STATE_CHANGE_UNSET)) + if ((priv->state_flags & GTK_STATE_FLAG_INSENSITIVE) && (flags_to_unset & GTK_STATE_FLAG_INSENSITIVE)) gtk_widget_set_sensitive (widget, TRUE); - else if (!(priv->state_flags & GTK_STATE_FLAG_INSENSITIVE) && (flags & GTK_STATE_FLAG_INSENSITIVE) && (operation != STATE_CHANGE_UNSET)) + else if (!(priv->state_flags & GTK_STATE_FLAG_INSENSITIVE) && (flags_to_set & GTK_STATE_FLAG_INSENSITIVE)) gtk_widget_set_sensitive (widget, FALSE); - else if ((priv->state_flags & GTK_STATE_FLAG_INSENSITIVE) && !(flags & GTK_STATE_FLAG_INSENSITIVE) && (operation == STATE_CHANGE_REPLACE)) - gtk_widget_set_sensitive (widget, TRUE); - if (operation != STATE_CHANGE_REPLACE) - flags &= ~(GTK_STATE_FLAG_INSENSITIVE); + flags_to_set &= ~(GTK_STATE_FLAG_INSENSITIVE); + flags_to_unset &= ~(GTK_STATE_FLAG_INSENSITIVE); - if (flags != 0 || - operation == STATE_CHANGE_REPLACE) + if (flags_to_set != 0 || flags_to_unset != 0) { GtkStateData data; - data.flags = flags; - data.operation = operation; + data.flags_to_set = flags_to_set; + data.flags_to_unset = flags_to_unset; gtk_widget_propagate_state (widget, &data); } @@ -7311,9 +7723,9 @@ gtk_widget_set_state_flags (GtkWidget *widget, return; if (clear) - _gtk_widget_update_state_flags (widget, flags, STATE_CHANGE_REPLACE); + gtk_widget_update_state_flags (widget, flags, ~(flags ^ (GTK_STATE_FLAG_DIR_LTR | GTK_STATE_FLAG_DIR_RTL))); else - _gtk_widget_update_state_flags (widget, flags, STATE_CHANGE_SET); + gtk_widget_update_state_flags (widget, flags, 0); } /** @@ -7336,7 +7748,7 @@ gtk_widget_unset_state_flags (GtkWidget *widget, if ((widget->priv->state_flags & flags) == 0) return; - _gtk_widget_update_state_flags (widget, flags, STATE_CHANGE_UNSET); + gtk_widget_update_state_flags (widget, 0, flags); } /** @@ -7413,7 +7825,10 @@ gtk_widget_set_state (GtkWidget *widget, break; } - _gtk_widget_update_state_flags (widget, flags, STATE_CHANGE_REPLACE); + gtk_widget_update_state_flags (widget, + flags, + (GTK_STATE_FLAG_ACTIVE | GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_SELECTED + | GTK_STATE_FLAG_INSENSITIVE | GTK_STATE_FLAG_INCONSISTENT | GTK_STATE_FLAG_FOCUSED) ^ flags); } /** @@ -7483,7 +7898,17 @@ void _gtk_widget_set_visible_flag (GtkWidget *widget, gboolean visible) { - widget->priv->visible = visible; + GtkWidgetPrivate *priv = widget->priv; + + priv->visible = visible; + + if (!visible) + { + priv->allocation.x = -1; + priv->allocation.y = -1; + priv->allocation.width = 1; + priv->allocation.height = 1; + } } /** @@ -7896,12 +8321,16 @@ gtk_widget_set_sensitive (GtkWidget *widget, { GtkStateData data; - data.flags = GTK_STATE_FLAG_INSENSITIVE; - if (sensitive) - data.operation = STATE_CHANGE_UNSET; + { + data.flags_to_set = 0; + data.flags_to_unset = GTK_STATE_FLAG_INSENSITIVE; + } else - data.operation = STATE_CHANGE_SET; + { + data.flags_to_set = GTK_STATE_FLAG_INSENSITIVE; + data.flags_to_unset = 0; + } gtk_widget_propagate_state (widget, &data); @@ -8002,10 +8431,8 @@ gtk_widget_set_parent (GtkWidget *widget, /* Merge both old state and current parent state, * making sure to only propagate the right states */ - data.flags = parent_flags & GTK_STATE_FLAGS_DO_PROPAGATE; - data.flags |= priv->state_flags; - - data.operation = STATE_CHANGE_REPLACE; + data.flags_to_set = parent_flags & GTK_STATE_FLAGS_DO_PROPAGATE; + data.flags_to_unset = 0; gtk_widget_propagate_state (widget, &data); if (priv->context) @@ -8052,6 +8479,8 @@ gtk_widget_set_parent (GtkWidget *widget, gtk_widget_queue_compute_expand (parent); } + gtk_widget_propagate_alpha (widget); + gtk_widget_pop_verify_invariants (widget); } @@ -8332,6 +8761,17 @@ gtk_widget_propagate_hierarchy_changed_recurse (GtkWidget *widget, priv->anchored = new_anchored; + /* This can only happen with gtk_widget_reparent() */ + if (priv->realized) + { + if (new_anchored) + gtk_widget_connect_frame_clock (widget, + gtk_widget_get_frame_clock (widget)); + else + gtk_widget_disconnect_frame_clock (widget, + gtk_widget_get_frame_clock (info->previous_toplevel)); + } + g_signal_emit (widget, widget_signals[HIERARCHY_CHANGED], 0, info->previous_toplevel); do_screen_change (widget, info->previous_screen, info->new_screen); @@ -10134,11 +10574,32 @@ static void gtk_widget_emit_direction_changed (GtkWidget *widget, GtkTextDirection old_dir) { + GtkTextDirection direction; + GtkStateFlags state; + gtk_widget_update_pango_context (widget); - if (widget->priv->context) - gtk_style_context_set_direction (widget->priv->context, - gtk_widget_get_direction (widget)); + direction = gtk_widget_get_direction (widget); + state = widget->priv->state_flags; + state &= GTK_STATE_FLAG_DIR_LTR | GTK_STATE_FLAG_DIR_RTL; + + switch (direction) + { + case GTK_TEXT_DIR_LTR: + state |= GTK_STATE_FLAG_DIR_LTR; + break; + + case GTK_TEXT_DIR_RTL: + state |= GTK_STATE_FLAG_DIR_RTL; + break; + + case GTK_TEXT_DIR_NONE: + default: + g_assert_not_reached (); + break; + } + + gtk_widget_set_state_flags (widget, state, TRUE); g_signal_emit (widget, widget_signals[DIRECTION_CHANGED], 0, old_dir); } @@ -10319,6 +10780,7 @@ gtk_widget_real_destroy (GtkWidget *object) /* gtk_object_destroy() will already hold a refcount on object */ GtkWidget *widget = GTK_WIDGET (object); GtkWidgetPrivate *priv = widget->priv; + GList *l; if (GTK_WIDGET_GET_CLASS (widget)->priv->accessible_type != GTK_TYPE_ACCESSIBLE) { @@ -10340,6 +10802,13 @@ gtk_widget_real_destroy (GtkWidget *object) gtk_grab_remove (widget); + for (l = priv->tick_callbacks; l;) + { + GList *next = l->next; + destroy_tick_callback_info (widget, l->data, l); + l = next; + } + if (priv->style) g_object_unref (priv->style); priv->style = gtk_widget_get_default_style (); @@ -10489,7 +10958,7 @@ gtk_widget_real_unrealize (GtkWidget *widget) if (gtk_widget_get_has_window (widget)) { - gdk_window_set_user_data (priv->window, NULL); + gtk_widget_unregister_window (widget, priv->window); gdk_window_destroy (priv->window); priv->window = NULL; } @@ -10898,18 +11367,8 @@ gtk_widget_propagate_state (GtkWidget *widget, old_state = gtk_widget_get_state (widget); - switch (data->operation) - { - case STATE_CHANGE_REPLACE: - priv->state_flags = data->flags; - break; - case STATE_CHANGE_SET: - priv->state_flags |= data->flags; - break; - case STATE_CHANGE_UNSET: - priv->state_flags &= ~(data->flags); - break; - } + priv->state_flags |= data->flags_to_set; + priv->state_flags &= ~(data->flags_to_unset); /* make insensitivity unoverridable */ if (!priv->sensitive) @@ -10978,10 +11437,11 @@ gtk_widget_propagate_state (GtkWidget *widget, if (GTK_IS_CONTAINER (widget)) { - GtkStateData child_data = *data; + GtkStateData child_data; /* Make sure to only propate the right states further */ - child_data.flags &= GTK_STATE_FLAGS_DO_PROPAGATE; + child_data.flags_to_set = data->flags_to_set & GTK_STATE_FLAGS_DO_PROPAGATE; + child_data.flags_to_unset = data->flags_to_unset & GTK_STATE_FLAGS_DO_PROPAGATE; gtk_container_forall (GTK_CONTAINER (widget), (GtkCallback) gtk_widget_propagate_state, @@ -13294,7 +13754,7 @@ gtk_widget_queue_tooltip_query (GtkWidget *widget) /** * gtk_widget_set_tooltip_text: * @widget: a #GtkWidget - * @text: the contents of the tooltip for @widget + * @text: (allow-none): the contents of the tooltip for @widget * * Sets @text as the contents of the tooltip. This function will take * care of setting #GtkWidget:has-tooltip to %TRUE and of the default @@ -13488,6 +13948,7 @@ gtk_widget_set_allocation (GtkWidget *widget, GtkWidgetPrivate *priv; g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (gtk_widget_get_visible (widget) || gtk_widget_is_toplevel (widget)); g_return_if_fail (allocation != NULL); priv = widget->priv; @@ -13594,10 +14055,83 @@ gtk_widget_set_window (GtkWidget *widget, if (priv->window != window) { 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"); } } +/** + * gtk_widget_register_window: + * @widget: a #GtkWidget + * @window: a #GdkWindow + * + * Registers a #GdkWindow with the widget and sets it up so that + * the widget recieves events for it. Call gtk_widget_unregister_window() + * when destroying the window. + * + * Before 3.8 you needed to call gdk_window_set_user_data() directly to set + * this up. This is now deprecated and you should use gtk_widget_register_window() + * instead. Old code will keep working as is, although some new features like + * transparency might not work perfectly. + * + * Since: 3.8 + */ +void +gtk_widget_register_window (GtkWidget *widget, + GdkWindow *window) +{ + GtkWidgetPrivate *priv; + gpointer user_data; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (GDK_IS_WINDOW (window)); + + gdk_window_get_user_data (window, &user_data); + g_assert (user_data == NULL); + + priv = widget->priv; + + gdk_window_set_user_data (window, widget); + priv->registered_windows = g_list_prepend (priv->registered_windows, window); + + if (priv->window != window && !gdk_window_has_native (window)) + gdk_window_set_opacity (window, + priv->norender_children ? 0.0 : 1.0); +} + +/** + * gtk_widget_unregister_window: + * @widget: a #GtkWidget + * @window: a #GdkWindow + * + * Unregisters a #GdkWindow from the widget that was previously set up with + * gtk_widget_register_window(). You need to call this when the window is + * no longer used by the widget, such as when you destroy it. + * + * Since: 3.8 + */ +void +gtk_widget_unregister_window (GtkWidget *widget, + GdkWindow *window) +{ + GtkWidgetPrivate *priv; + gpointer user_data; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (GDK_IS_WINDOW (window)); + + priv = widget->priv; + + gdk_window_get_user_data (window, &user_data); + g_assert (user_data == widget); + gdk_window_set_user_data (window, NULL); + priv->registered_windows = g_list_remove (priv->registered_windows, window); +} + /** * gtk_widget_get_window: * @widget: a #GtkWidget @@ -13660,6 +14194,211 @@ gtk_widget_set_support_multidevice (GtkWidget *widget, gdk_window_set_support_multidevice (priv->window, support_multidevice); } +/* There are multiple alpha related sources. First of all the user can specify alpha + * 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)) + 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) +{ + GtkWidgetPrivate *priv; + double opacity; + guint8 alpha; + + priv = widget->priv; + + alpha = priv->user_alpha; + + if (priv->context) + { + opacity = + _gtk_css_number_value_get (_gtk_style_context_peek_property (priv->context, + GTK_CSS_PROPERTY_OPACITY), + 100); + opacity = CLAMP (opacity, 0.0, 1.0); + alpha = round (priv->user_alpha * opacity); + } + + if (alpha == priv->alpha) + return; + + priv->alpha = alpha; + + gtk_widget_propagate_alpha (widget); + +} + +static void +gtk_widget_set_has_opacity_group (GtkWidget *widget, + gboolean has_opacity_group) +{ + GtkWidgetPrivate *priv; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + priv = widget->priv; + + has_opacity_group = !!has_opacity_group; + + if (priv->opacity_group == has_opacity_group) + return; + + priv->opacity_group = has_opacity_group; + + gtk_widget_propagate_alpha (widget); +} + +/** + * gtk_widget_set_opacity: + * @widget: a #GtkWidget + * @opacity: desired opacity, between 0 and 1 + * + * Request the @widget to be rendered partially transparent, + * with opacity 0 being fully transparent and 1 fully opaque. (Opacity values + * are clamped to the [0,1] range.). + * This works on both toplevel widget, and child widgets, although there + * are some limitations: + * + * For toplevel widgets this depends on the capabilities of the windowing + * system. On X11 this has any effect only on X screens with a compositing manager + * running. See gtk_widget_is_composited(). On Windows it should work + * always, although setting a window's opacity after the window has been + * shown causes it to flicker once on Windows. + * + * For child widgets it doesn't work if any affected widget has a native window, or + * disables double buffering. + * + * Since: 3.8 + **/ +void +gtk_widget_set_opacity (GtkWidget *widget, + gdouble opacity) +{ + GtkWidgetPrivate *priv; + guint8 alpha; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + priv = widget->priv; + + opacity = CLAMP (opacity, 0.0, 1.0); + + alpha = round (opacity * 255); + + /* As a kind of hack for internal use we treat an alpha very + close to 1.0 (rounds to 255) but not 1.0 as specifying that + we want the opacity group behaviour wrt draw handling, but + not actually an alpha value. See bug #687842 for discussions. */ + gtk_widget_set_has_opacity_group (widget, + alpha == 255 && opacity != 1.0); + + if (alpha == priv->user_alpha) + return; + + priv->user_alpha = alpha; + + gtk_widget_update_alpha (widget); + +} + +/** + * gtk_widget_get_opacity: + * @widget: a #GtkWidget + * + * Fetches the requested opacity for this widget. See + * gtk_widget_set_opacity(). + * + * Return value: the requested opacity for this widget. + * + * Since: 3.8 + **/ +gdouble +gtk_widget_get_opacity (GtkWidget *widget) +{ + g_return_val_if_fail (GTK_IS_WIDGET (widget), 0.0); + + return widget->priv->alpha / 255.0; +} + static void _gtk_widget_set_has_focus (GtkWidget *widget, gboolean has_focus) @@ -13986,15 +14725,20 @@ gtk_widget_get_style_context (GtkWidget *widget) if (G_UNLIKELY (priv->context == NULL)) { GdkScreen *screen; + GdkFrameClock *frame_clock; priv->context = gtk_style_context_new (); - gtk_style_context_set_direction (priv->context, gtk_widget_get_direction (widget)); + gtk_style_context_set_state (priv->context, priv->state_flags); screen = gtk_widget_get_screen (widget); if (screen) gtk_style_context_set_screen (priv->context, screen); + frame_clock = gtk_widget_get_frame_clock (widget); + if (frame_clock) + gtk_style_context_set_frame_clock (priv->context, frame_clock); + if (priv->parent) gtk_style_context_set_parent (priv->context, gtk_widget_get_style_context (priv->parent));