X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkwidget.c;h=5716a04ae04f83a0c0a81b55371357c342e2db7a;hb=HEAD;hp=f624250f70580e71644b08b45881bab31a68eccc;hpb=828a97d7739e04e12ef7a5cb91405ab804f03d2a;p=~andy%2Fgtk diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index f624250f7..5716a04ae 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -35,12 +36,15 @@ #include "gtkcontainer.h" #include "gtkaccelmapprivate.h" #include "gtkclipboard.h" +#include "gtkcssstylepropertyprivate.h" +#include "gtkcssnumbervalueprivate.h" #include "gtkiconfactory.h" #include "gtkintl.h" #include "gtkmarshalers.h" #include "gtkselectionprivate.h" #include "gtksettingsprivate.h" #include "gtksizegroup-private.h" +#include "gtksizerequestcacheprivate.h" #include "gtkwidget.h" #include "gtkwidgetprivate.h" #include "gtkwindowprivate.h" @@ -54,7 +58,6 @@ #include "gtkbuilderprivate.h" #include "gtksizerequest.h" #include "gtkstylecontextprivate.h" -#include "gtksymboliccolor.h" #include "gtkcssprovider.h" #include "gtkmodifierstyle.h" #include "gtkversion.h" @@ -63,6 +66,9 @@ #include "gtktypebuiltins.h" #include "a11y/gtkwidgetaccessible.h" +/* for the use of round() */ +#include "fallback-c89.c" + /** * SECTION:gtkwidget * @Short_description: Base class for all widgets @@ -271,7 +277,7 @@ * * * - * Clickable Button + * Clickable Button * * * @@ -299,12 +305,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 { @@ -343,8 +349,6 @@ struct _GtkWidgetPrivate /* Queue-resize related flags */ guint alloc_needed : 1; - guint width_request_needed : 1; - guint height_request_needed : 1; /* Expand-related flags */ guint need_compute_expand : 1; /* Need to recompute computed_[hv]_expand */ @@ -356,10 +360,15 @@ struct _GtkWidgetPrivate guint vexpand_set : 1; /* instead of computing from children */ /* SizeGroup related flags */ - guint sizegroup_visited : 1; - guint sizegroup_bumping : 1; 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. @@ -398,10 +407,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; @@ -510,6 +523,7 @@ enum { PROP_TOOLTIP_MARKUP, PROP_TOOLTIP_TEXT, PROP_WINDOW, + PROP_OPACITY, PROP_DOUBLE_BUFFERED, PROP_HALIGN, PROP_VALIGN, @@ -527,16 +541,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 --- */ @@ -611,7 +619,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, @@ -642,7 +651,6 @@ static AtkObject* gtk_widget_ref_accessible (AtkImplementor *implementor); static void gtk_widget_invalidate_widget_windows (GtkWidget *widget, cairo_region_t *region); static GdkScreen * gtk_widget_get_screen_unchecked (GtkWidget *widget); -static void gtk_widget_queue_shallow_draw (GtkWidget *widget); static gboolean gtk_widget_real_can_activate_accel (GtkWidget *widget, guint signal_id); @@ -708,7 +716,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; @@ -818,9 +833,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, @@ -829,6 +859,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); } @@ -841,6 +880,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; @@ -848,6 +890,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, @@ -857,7 +911,18 @@ 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); } static void @@ -1427,6 +1492,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. @@ -1724,6 +1808,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. * @@ -1961,7 +2052,7 @@ gtk_widget_class_init (GtkWidgetClass *klass) * button press events for buttons 4 and 5 when the wheel is turned. * * To receive this signal, the #GdkWindow associated to the widget needs - * to enable the #GDK_BUTTON_PRESS_MASK mask. + * to enable the #GDK_SCROLL_MASK mask. * * This signal will be sent to the grab widget if there is one. * @@ -2516,7 +2607,7 @@ gtk_widget_class_init (GtkWidgetClass *klass) /** * GtkWidget::drag-leave: * @widget: the object which received the signal. - * @drag_context: the drag context + * @context: the drag context * @time: the timestamp of the motion event * * The ::drag-leave signal is emitted on the drop site when the cursor @@ -2538,7 +2629,7 @@ gtk_widget_class_init (GtkWidgetClass *klass) /** * GtkWidget::drag-begin: * @widget: the object which received the signal - * @drag_context: the drag context + * @context: the drag context * * The ::drag-begin signal is emitted on the drag source when a drag is * started. A typical reason to connect to this signal is to set up a @@ -2561,7 +2652,7 @@ gtk_widget_class_init (GtkWidgetClass *klass) /** * GtkWidget::drag-end: * @widget: the object which received the signal - * @drag_context: the drag context + * @context: the drag context * * The ::drag-end signal is emitted on the drag source when a drag is * finished. A typical reason to connect to this signal is to undo @@ -2580,7 +2671,7 @@ gtk_widget_class_init (GtkWidgetClass *klass) /** * GtkWidget::drag-data-delete: * @widget: the object which received the signal - * @drag_context: the drag context + * @context: the drag context * * The ::drag-data-delete signal is emitted on the drag source when a drag * with the action %GDK_ACTION_MOVE is successfully completed. The signal @@ -2600,7 +2691,7 @@ gtk_widget_class_init (GtkWidgetClass *klass) /** * GtkWidget::drag-failed: * @widget: the object which received the signal - * @drag_context: the drag context + * @context: the drag context * @result: the result of the drag operation * * The ::drag-failed signal is emitted on the drag source when a drag has @@ -2627,12 +2718,12 @@ gtk_widget_class_init (GtkWidgetClass *klass) /** * GtkWidget::drag-motion: * @widget: the object which received the signal - * @drag_context: the drag context + * @context: the drag context * @x: the x coordinate of the current cursor position * @y: the y coordinate of the current cursor position * @time: the timestamp of the motion event * - * The drag-motion signal is emitted on the drop site when the user + * The ::drag-motion signal is emitted on the drop site when the user * moves the cursor over the widget during a drag. The signal handler * must determine whether the cursor position is in a drop zone or not. * If it is not in a drop zone, it returns %FALSE and no further processing @@ -2655,11 +2746,11 @@ gtk_widget_class_init (GtkWidgetClass *klass) * the drop site with gtk_drag_highlight(). * |[ * static void - * drag_motion (GtkWidget *widget, + * drag_motion (GtkWidget *widget, * GdkDragContext *context, - * gint x, - * gint y, - * guint time) + * gint x, + * gint y, + * guint time) * { * GdkAtom target; * @@ -2676,7 +2767,7 @@ gtk_widget_class_init (GtkWidgetClass *klass) * gdk_drag_status (context, 0, time); * else * { - * private_data->pending_status = context->suggested_action; + * private_data->pending_status = gdk_drag_context_get_suggested_action (context); * gtk_drag_get_data (widget, context, target, time); * } * @@ -2698,11 +2789,11 @@ gtk_widget_class_init (GtkWidgetClass *klass) * { * private_data->suggested_action = 0; * - * /* We are getting this data due to a request in drag_motion, - * * rather than due to a request in drag_drop, so we are just - * * supposed to call gdk_drag_status (), not actually paste in - * * the data. - * */ + * /* We are getting this data due to a request in drag_motion, + * * rather than due to a request in drag_drop, so we are just + * * supposed to call gdk_drag_status(), not actually paste in + * * the data. + * */ * str = gtk_selection_data_get_text (selection_data); * if (!data_is_acceptable (str)) * gdk_drag_status (context, 0, time); @@ -2734,7 +2825,7 @@ gtk_widget_class_init (GtkWidgetClass *klass) /** * GtkWidget::drag-drop: * @widget: the object which received the signal - * @drag_context: the drag context + * @context: the drag context * @x: the x coordinate of the current cursor position * @y: the y coordinate of the current cursor position * @time: the timestamp of the motion event @@ -2768,7 +2859,7 @@ gtk_widget_class_init (GtkWidgetClass *klass) /** * GtkWidget::drag-data-get: * @widget: the object which received the signal - * @drag_context: the drag context + * @context: the drag context * @data: the #GtkSelectionData to be filled with the dragged data * @info: the info that has been registered with the target in the * #GtkTargetList @@ -2796,7 +2887,7 @@ gtk_widget_class_init (GtkWidgetClass *klass) /** * GtkWidget::drag-data-received: * @widget: the object which received the signal - * @drag_context: the drag context + * @context: the drag context * @x: where the drop happened * @y: where the drop happened * @data: the received data @@ -2811,16 +2902,17 @@ gtk_widget_class_init (GtkWidgetClass *klass) * If the data was received in response to a #GtkWidget::drag-drop signal * (and this is the last target to be received), the handler for this * signal is expected to process the received data and then call - * gtk_drag_finish(), setting the @success parameter depending on whether - * the data was processed successfully. + * gtk_drag_finish(), setting the @success parameter depending on + * whether the data was processed successfully. * - * The handler may inspect and modify @drag_context->action before calling - * gtk_drag_finish(), e.g. to implement %GDK_ACTION_ASK as shown in the - * following example: + * The handler may inspect the selected action with + * gdk_drag_context_get_selected_action() before calling + * gtk_drag_finish(), e.g. to implement %GDK_ACTION_ASK as + * shown in the following example: * |[ * void * drag_data_received (GtkWidget *widget, - * GdkDragContext *drag_context, + * GdkDragContext *context, * gint x, * gint y, * GtkSelectionData *data, @@ -2829,7 +2921,12 @@ gtk_widget_class_init (GtkWidgetClass *klass) * { * if ((data->length >= 0) && (data->format == 8)) * { - * if (drag_context->action == GDK_ACTION_ASK) + * GdkDragAction action; + * + * /* handle data here */ + * + * action = gdk_drag_context_get_selected_action (context); + * if (action == GDK_ACTION_ASK) * { * GtkWidget *dialog; * gint response; @@ -2844,16 +2941,15 @@ gtk_widget_class_init (GtkWidgetClass *klass) * gtk_widget_destroy (dialog); * * if (response == GTK_RESPONSE_YES) - * drag_context->action = GDK_ACTION_MOVE; + * action = GDK_ACTION_MOVE; * else - * drag_context->action = GDK_ACTION_COPY; + * action = GDK_ACTION_COPY; * } * - * gtk_drag_finish (drag_context, TRUE, FALSE, time); - * return; + * gtk_drag_finish (context, TRUE, action == GDK_ACTION_MOVE, time); * } - * - * gtk_drag_finish (drag_context, FALSE, FALSE, time); + * else + * gtk_drag_finish (context, FALSE, FALSE, time); * } * ]| */ @@ -3292,6 +3388,19 @@ gtk_widget_class_init (GtkWidgetClass *klass) 1, G_MAXINT, 16, GTK_PARAM_READABLE)); + gtk_widget_class_install_style_property (klass, + g_param_spec_int ("text-handle-width", + P_("Width of text selection handles"), + P_("Width of text selection handles"), + 1, G_MAXINT, 16, + GTK_PARAM_READABLE)); + gtk_widget_class_install_style_property (klass, + g_param_spec_int ("text-handle-height", + P_("Height of text selection handles"), + P_("Height of text selection handles"), + 1, G_MAXINT, 20, + GTK_PARAM_READABLE)); + g_type_class_add_private (klass, sizeof (GtkWidgetPrivate)); gtk_widget_class_set_accessible_type (klass, GTK_TYPE_WIDGET_ACCESSIBLE); @@ -3473,6 +3582,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; @@ -3629,6 +3741,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; @@ -3651,6 +3766,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; @@ -3658,9 +3775,24 @@ gtk_widget_init (GtkWidget *widget) priv->composite_child = composite_child_stack != 0; priv->double_buffered = TRUE; priv->redraw_on_alloc = TRUE; - priv->width_request_needed = TRUE; - priv->height_request_needed = 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 @@ -3671,6 +3803,8 @@ gtk_widget_init (GtkWidget *widget) */ priv->need_compute_expand = FALSE; + _gtk_size_request_cache_init (&priv->requests); + priv->style = gtk_widget_get_default_style (); g_object_ref (priv->style); } @@ -3924,6 +4058,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); } @@ -4139,9 +4275,6 @@ gtk_widget_real_hide (GtkWidget *widget) if (gtk_widget_get_mapped (widget)) gtk_widget_unmap (widget); - - if (widget->priv->context) - _gtk_style_context_stop_animations (widget->priv->context); } } @@ -4222,6 +4355,9 @@ gtk_widget_map (GtkWidget *widget) if (!gtk_widget_get_has_window (widget)) gdk_window_invalidate_rect (priv->window, &priv->allocation, FALSE); + if (widget->priv->context) + _gtk_style_context_update_animating (widget->priv->context); + gtk_widget_pop_verify_invariants (widget); } } @@ -4249,6 +4385,10 @@ gtk_widget_unmap (GtkWidget *widget) if (!gtk_widget_get_has_window (widget)) gdk_window_invalidate_rect (priv->window, &priv->allocation, FALSE); _gtk_tooltip_hide (widget); + + if (widget->priv->context) + _gtk_style_context_update_animating (widget->priv->context); + g_signal_emit (widget, widget_signals[UNMAP], 0); gtk_widget_pop_verify_invariants (widget); @@ -4382,6 +4522,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 @@ -4462,6 +4835,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); } } @@ -4494,6 +4870,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); @@ -4632,7 +5011,7 @@ gtk_widget_queue_resize (GtkWidget *widget) g_return_if_fail (GTK_IS_WIDGET (widget)); if (gtk_widget_get_realized (widget)) - gtk_widget_queue_shallow_draw (widget); + gtk_widget_queue_draw (widget); _gtk_size_group_queue_resize (widget, 0); } @@ -4654,6 +5033,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 @@ -4754,29 +5188,6 @@ gtk_widget_invalidate_widget_windows (GtkWidget *widget, invalidate_predicate, widget); } -/** - * gtk_widget_queue_shallow_draw: - * @widget: a #GtkWidget - * - * Like gtk_widget_queue_draw(), but only windows owned - * by @widget are invalidated. - **/ -static void -gtk_widget_queue_shallow_draw (GtkWidget *widget) -{ - GdkRectangle rect; - cairo_region_t *region; - - if (!gtk_widget_get_realized (widget)) - return; - - gtk_widget_get_allocation (widget, &rect); - - region = cairo_region_create_rectangle (&rect); - gtk_widget_invalidate_widget_windows (widget, region); - cairo_region_destroy (region); -} - /** * gtk_widget_size_allocate: * @widget: a #GtkWidget @@ -4809,6 +5220,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 @@ -4834,9 +5248,8 @@ gtk_widget_size_allocate (GtkWidget *widget, #endif /* G_ENABLE_DEBUG */ alloc_needed = priv->alloc_needed; - if (!priv->width_request_needed && !priv->height_request_needed) - /* Preserve request/allocate ordering */ - priv->alloc_needed = FALSE; + /* Preserve request/allocate ordering */ + priv->alloc_needed = FALSE; old_allocation = priv->allocation; real_allocation = *allocation; @@ -4928,9 +5341,7 @@ gtk_widget_size_allocate (GtkWidget *widget, g_signal_emit (widget, widget_signals[SIZE_ALLOCATE], 0, &real_allocation); /* Size allocation is god... after consulting god, no further requests or allocations are needed */ - priv->width_request_needed = FALSE; - priv->height_request_needed = FALSE; - priv->alloc_needed = FALSE; + priv->alloc_needed = FALSE; if (gtk_widget_get_mapped (widget)) { @@ -5838,7 +6249,7 @@ static gboolean gtk_widget_real_focus_in_event (GtkWidget *widget, GdkEventFocus *event) { - gtk_widget_queue_shallow_draw (widget); + gtk_widget_queue_draw (widget); return FALSE; } @@ -5847,7 +6258,7 @@ static gboolean gtk_widget_real_focus_out_event (GtkWidget *widget, GdkEventFocus *event) { - gtk_widget_queue_shallow_draw (widget); + gtk_widget_queue_draw (widget); return FALSE; } @@ -6708,6 +7119,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 ()) @@ -6723,15 +7135,27 @@ gtk_widget_real_style_updated (GtkWidget *widget) if (widget->priv->context) { + const GtkBitmask *changes = _gtk_style_context_get_changes (widget->priv->context); + if (gtk_widget_get_realized (widget) && gtk_widget_get_has_window (widget) && !gtk_widget_get_app_paintable (widget)) gtk_style_context_set_background (widget->priv->context, widget->priv->window); - } - if (widget->priv->anchored) - gtk_widget_queue_resize (widget); + if (widget->priv->anchored) + { + if (changes && _gtk_css_style_property_changes_affect_size (changes)) + gtk_widget_queue_resize (widget); + else + gtk_widget_queue_draw (widget); + } + } + else + { + if (widget->priv->anchored) + gtk_widget_queue_resize (widget); + } } static gboolean @@ -7082,13 +7506,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 @@ -7242,9 +7666,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; @@ -7253,27 +7677,22 @@ _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); - - gtk_widget_queue_resize (widget); } } @@ -7307,9 +7726,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); } /** @@ -7332,7 +7751,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); } /** @@ -7409,7 +7828,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); } /** @@ -7479,16 +7901,28 @@ 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; + } } /** * gtk_widget_get_visible: * @widget: a #GtkWidget * - * Determines whether the widget is visible. Note that this doesn't - * take into account whether the widget's parent is also visible - * or the widget is obscured in any way. + * Determines whether the widget is visible. If you want to + * take into account whether the widget's parent is also marked as + * visible, use gtk_widget_is_visible() instead. + * + * This function does not check if the widget is obscured in any way. * * See gtk_widget_set_visible(). * @@ -7504,6 +7938,39 @@ gtk_widget_get_visible (GtkWidget *widget) return widget->priv->visible; } +/** + * gtk_widget_is_visible: + * @widget: a #GtkWidget + * + * Determines whether the widget and all its parents are marked as + * visible. + * + * This function does not check if the widget is obscured in any way. + * + * See also gtk_widget_get_visible() and gtk_widget_set_visible() + * + * Return value: %TRUE if the widget and all its parents are visible + * + * Since: 3.8 + **/ +gboolean +gtk_widget_is_visible (GtkWidget *widget) +{ + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + + while (widget) + { + GtkWidgetPrivate *priv = widget->priv; + + if (!priv->visible) + return FALSE; + + widget = priv->parent; + } + + return TRUE; +} + /** * gtk_widget_set_has_window: * @widget: a #GtkWidget @@ -7857,12 +8324,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); @@ -7963,10 +8434,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) @@ -8013,6 +8482,8 @@ gtk_widget_set_parent (GtkWidget *widget, gtk_widget_queue_compute_expand (parent); } + gtk_widget_propagate_alpha (widget); + gtk_widget_pop_verify_invariants (widget); } @@ -8293,6 +8764,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); @@ -8666,12 +9148,8 @@ gtk_widget_peek_pango_context (GtkWidget *widget) * by gtk_widget_create_pango_context(), this context is owned by * the widget (it can be used until the screen for the widget changes * or the widget is removed from its toplevel), and will be updated to - * match any changes to the widget's attributes. - * - * If you create and keep a #PangoLayout using this context, you must - * deal with changes to the context by calling pango_layout_context_changed() - * on the layout in response to the #GtkWidget::style-updated and - * #GtkWidget::direction-changed signals for the widget. + * match any changes to the widget's attributes. This can be tracked + * by using the #GtkWidget::screen-changed signal on the widget. * * Return value: (transfer none): the #PangoContext for the widget. **/ @@ -8699,18 +9177,21 @@ static void update_pango_context (GtkWidget *widget, PangoContext *context) { - const PangoFontDescription *font_desc; + PangoFontDescription *font_desc; GtkStyleContext *style_context; style_context = gtk_widget_get_style_context (widget); - - font_desc = gtk_style_context_get_font (style_context, - gtk_widget_get_state_flags (widget)); + gtk_style_context_get (style_context, + gtk_widget_get_state_flags (widget), + "font", &font_desc, + NULL); pango_context_set_font_description (context, font_desc); pango_context_set_base_dir (context, gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR ? PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL); + + pango_font_description_free (font_desc); } static void @@ -8779,11 +9260,10 @@ gtk_widget_create_pango_context (GtkWidget *widget) * font description, and base direction for drawing text for * this widget. * - * If you keep a #PangoLayout created in this way around, in order to - * notify the layout of changes to the base direction or font of this - * widget, you must call pango_layout_context_changed() in response to - * the #GtkWidget::style-updated and #GtkWidget::direction-changed signals - * for the widget. + * If you keep a #PangoLayout created in this way around, you need + * to re-create it when the widget #PangoContext is replaced. + * This can be tracked by using the #GtkWidget::screen-changed signal + * on the widget. * * Return value: (transfer full): the new #PangoLayout **/ @@ -9336,9 +9816,8 @@ gtk_widget_set_usize_internal (GtkWidget *widget, * @height: height @widget should request, or -1 to unset * * Sets the minimum size of a widget; that is, the widget's size - * request will be @width by @height. You can use this function to - * force a widget to be either larger or smaller than it normally - * would be. + * request will be at least @width by @height. You can use this + * function to force a widget to be larger than it normally would be. * * In most cases, gtk_window_set_default_size() is a better choice for * toplevel windows than this function; setting the default size will @@ -9362,9 +9841,6 @@ gtk_widget_set_usize_internal (GtkWidget *widget, * If the size request in a given direction is -1 (unset), then * the "natural" size request of the widget will be used instead. * - * Widgets can't actually be allocated a size less than 1 by 1, but - * you can pass 0,0 to this function to mean "as small as possible." - * * The size request set here does not include any margin from the * #GtkWidget properties margin-left, margin-right, margin-top, and * margin-bottom, but it does include pretty much all other padding @@ -10101,11 +10577,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); } @@ -10286,6 +10783,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) { @@ -10307,6 +10805,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 (); @@ -10345,7 +10850,7 @@ gtk_widget_finalize (GObject *object) g_object_unref (priv->context); } - _gtk_widget_free_cached_sizes (widget); + _gtk_size_request_cache_free (&priv->requests); if (g_object_is_floating (object)) g_warning ("A floating object was finalized. This means that someone\n" @@ -10456,7 +10961,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; } @@ -10865,18 +11370,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) @@ -10945,10 +11440,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, @@ -13261,7 +13757,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 @@ -13455,6 +13951,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; @@ -13561,10 +14058,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 @@ -13627,6 +14197,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) @@ -13763,58 +14538,6 @@ _gtk_widget_set_alloc_needed (GtkWidget *widget, widget->priv->alloc_needed = alloc_needed; } -gboolean -_gtk_widget_get_width_request_needed (GtkWidget *widget) -{ - return widget->priv->width_request_needed; -} - -void -_gtk_widget_set_width_request_needed (GtkWidget *widget, - gboolean width_request_needed) -{ - widget->priv->width_request_needed = width_request_needed; -} - -gboolean -_gtk_widget_get_height_request_needed (GtkWidget *widget) -{ - return widget->priv->height_request_needed; -} - -void -_gtk_widget_set_height_request_needed (GtkWidget *widget, - gboolean height_request_needed) -{ - widget->priv->height_request_needed = height_request_needed; -} - -gboolean -_gtk_widget_get_sizegroup_visited (GtkWidget *widget) -{ - return widget->priv->sizegroup_visited; -} - -void -_gtk_widget_set_sizegroup_visited (GtkWidget *widget, - gboolean visited) -{ - widget->priv->sizegroup_visited = visited; -} - -gboolean -_gtk_widget_get_sizegroup_bumping (GtkWidget *widget) -{ - return widget->priv->sizegroup_bumping; -} - -void -_gtk_widget_set_sizegroup_bumping (GtkWidget *widget, - gboolean bumping) -{ - widget->priv->sizegroup_bumping = bumping; -} - void _gtk_widget_add_sizegroup (GtkWidget *widget, gpointer group) @@ -13982,9 +14705,6 @@ _gtk_widget_style_context_invalidated (GtkWidget *widget) */ widget->priv->style_update_pending = TRUE; } - - if (widget->priv->anchored) - gtk_widget_queue_resize (widget); } /** @@ -14008,15 +14728,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));