[gtk+] Add gtk_widget_get/set_opacity



commit fa8b71450c764bb0d4d3514c8372637ea3c64c35
Author: Alexander Larsson <alexl redhat com>
Date:   Wed Nov 7 14:32:35 2012 +0100

    Add gtk_widget_get/set_opacity
    
    This adds gtk_widget_get/set_opacity, as well as a GtkWidget.opacity
    property. Additionally it deprectates gtk_window_get/set_opacity and
    removes the GtkWindow.opacity property (in preference for the new
    identical inherited property from GtkWidget, which should be ABI/API
    compat).
    
    The implementation is using the new gdk_window_set_opacity child
    window support for windowed widgets, and cairo_push/pop_group()
    bracketing in gtk_widget_draw() for non-window widgets.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=687842

 gtk/gtk.symbols |    2 +
 gtk/gtkwidget.c |  259 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkwidget.h |    5 +
 gtk/gtkwindow.c |   54 +-----------
 gtk/gtkwindow.h |    2 +
 5 files changed, 272 insertions(+), 50 deletions(-)
---
diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols
index 9f0d61a..d0e219c 100644
--- a/gtk/gtk.symbols
+++ b/gtk/gtk.symbols
@@ -3720,6 +3720,7 @@ gtk_widget_get_modifier_style
 gtk_widget_get_name
 gtk_widget_get_no_show_all
 gtk_widget_get_pango_context
+gtk_widget_get_opacity
 gtk_widget_get_parent
 gtk_widget_get_parent_window
 gtk_widget_get_path
@@ -3874,6 +3875,7 @@ gtk_widget_set_margin_right
 gtk_widget_set_margin_top
 gtk_widget_set_name
 gtk_widget_set_no_show_all
+gtk_widget_set_opacity
 gtk_widget_set_parent
 gtk_widget_set_parent_window
 gtk_widget_set_realized
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 2118c43..a2faff5 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -27,6 +27,7 @@
 #include <stdarg.h>
 #include <string.h>
 #include <locale.h>
+#include <math.h>
 
 #include <gobject/gvaluecollector.h>
 #include <gobject/gobjectnotifyqueue.c>
@@ -357,6 +358,11 @@ struct _GtkWidgetPrivate
   /* SizeGroup related flags */
   guint have_size_groups      : 1;
 
+  guint norender_children     : 1;
+  guint norender              : 1; /* Don't expose windows, instead recurse via draw */
+
+  guint8 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.
@@ -508,6 +514,7 @@ enum {
   PROP_TOOLTIP_MARKUP,
   PROP_TOOLTIP_TEXT,
   PROP_WINDOW,
+  PROP_OPACITY,
   PROP_DOUBLE_BUFFERED,
   PROP_HALIGN,
   PROP_VALIGN,
@@ -700,6 +707,9 @@ static void gtk_widget_set_device_enabled_internal (GtkWidget *widget,
                                                     gboolean   recurse,
                                                     gboolean   enabled);
 static gboolean event_window_is_still_viewable (GdkEvent *event);
+static void gtk_cairo_set_event (cairo_t        *cr,
+				 GdkEventExpose *event);
+static void gtk_widget_update_norender (GtkWidget *widget);
 
 /* --- variables --- */
 static gpointer         gtk_widget_parent_class = NULL;
@@ -809,9 +819,23 @@ gtk_widget_draw_marshaller (GClosure     *closure,
                             gpointer      invocation_hint,
                             gpointer      marshal_data)
 {
+  GtkWidget *widget = g_value_get_object (&param_values[0]);
+  GdkEventExpose *tmp_event;
+  gboolean push_group;
   cairo_t *cr = g_value_get_boxed (&param_values[1]);
 
   cairo_save (cr);
+  tmp_event = _gtk_cairo_get_event (cr);
+
+  push_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,
@@ -820,6 +844,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);
 }
 
@@ -832,6 +865,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;
 
@@ -839,6 +875,17 @@ 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->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,
@@ -848,6 +895,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);
@@ -1421,6 +1477,25 @@ gtk_widget_class_init (GtkWidgetClass *klass)
                                                          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.
    */
@@ -3484,6 +3559,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;
@@ -3640,6 +3718,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;
@@ -3662,6 +3743,7 @@ gtk_widget_init (GtkWidget *widget)
   priv->allocation.y = -1;
   priv->allocation.width = 1;
   priv->allocation.height = 1;
+  priv->alpha = 255;
   priv->window = NULL;
   priv->parent = NULL;
 
@@ -3952,6 +4034,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_update_norender (widget);
+
   gtk_widget_pop_verify_invariants (widget);
   g_object_unref (widget);
 }
@@ -8079,6 +8163,8 @@ gtk_widget_set_parent (GtkWidget *widget,
       gtk_widget_queue_compute_expand (parent);
     }
 
+  gtk_widget_update_norender (widget);
+
   gtk_widget_pop_verify_invariants (widget);
 }
 
@@ -13634,6 +13720,11 @@ 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");
     }
 }
@@ -13671,6 +13762,10 @@ gtk_widget_register_window (GtkWidget    *widget,
 
   gdk_window_set_user_data (window, widget);
   priv->registered_windows = g_list_prepend (priv->registered_windows, window);
+
+  if (!gtk_widget_get_has_window (widget) && !gdk_window_has_native (window))
+    gdk_window_set_opacity (window,
+			    (priv->norender || priv->norender_children) ? 0.0 : 1.0);
 }
 
 /**
@@ -13764,6 +13859,170 @@ gtk_widget_set_support_multidevice (GtkWidget *widget,
     gdk_window_set_support_multidevice (priv->window, support_multidevice);
 }
 
+static void apply_norender (GtkWidget *widget, gboolean norender);
+
+static void
+apply_norender_cb (GtkWidget *widget, gpointer norender)
+{
+  apply_norender (widget, GPOINTER_TO_INT (norender));
+}
+
+static void
+propagate_norender_non_window (GtkWidget *widget, gboolean norender)
+{
+  GList *l;
+
+  g_assert (!gtk_widget_get_has_window (widget));
+
+  for (l = widget->priv->registered_windows; l != NULL; l = l->next)
+    gdk_window_set_opacity (l->data,
+			    norender ? 0 : widget->priv->alpha / 255.0);
+
+  if (GTK_IS_CONTAINER (widget))
+    gtk_container_forall (GTK_CONTAINER (widget), apply_norender_cb,
+			  GINT_TO_POINTER (norender));
+}
+
+static void
+apply_norender (GtkWidget *widget, gboolean norender)
+{
+  if (widget->priv->norender == norender)
+    return;
+
+  widget->priv->norender = norender;
+
+  if (gtk_widget_get_has_window (widget))
+    {
+      if (widget->priv->window != NULL)
+	gdk_window_set_opacity (widget->priv->window,
+				norender ? 0 : widget->priv->alpha / 255.0);
+    }
+  else
+    propagate_norender_non_window (widget, norender | widget->priv->norender_children);
+}
+
+/* This is called when the norender_children state of a non-window widget changes,
+ * its parent changes, or its has_window state changes. It means we need
+ * to update the norender of all the windows owned by the widget and those
+ * of child widgets, up to and including the first windowed widgets in the hierarchy.
+ */
+static void
+gtk_widget_update_norender (GtkWidget *widget)
+{
+  gboolean norender;
+  GtkWidget *parent;
+
+  parent = widget->priv->parent;
+
+  norender =
+    parent != NULL &&
+    (parent->priv->norender_children ||
+     (parent->priv->norender && !gtk_widget_get_has_window (parent)));
+
+  apply_norender (widget, norender);
+
+  /* The above may not have propagated to children if norender_children changed but
+     not norender, so we need to enforce propagation. */
+  if (!gtk_widget_get_has_window (widget))
+    propagate_norender_non_window (widget, norender | widget->priv->norender_children);
+}
+
+/**
+ * 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;
+
+  if (opacity < 0.0)
+    opacity = 0.0;
+  else if (opacity > 1.0)
+    opacity = 1.0;
+
+  alpha = round (opacity * 255);
+  if (alpha == priv->alpha)
+    return;
+
+  priv->alpha = alpha;
+
+  if (gtk_widget_get_has_window (widget))
+    {
+      if (priv->window != NULL)
+	gdk_window_set_opacity (priv->window,
+				priv->norender ? 0 : opacity);
+    }
+  else
+    {
+      /* For non windowed widgets we can't use gdk_window_set_opacity() directly, as there is
+	 no GdkWindow at the right place in the hierarchy. For no-window widget this is not a problem,
+	 as we just push an opacity group in the draw marshaller.
+
+	 However, that only works for non-window descendant widgets. If any descendant has a
+	 window that window will not normally be rendered in the draw signal, so the opacity
+	 group will not work for it.
+
+	 To fix this we set all such windows to a zero opacity, meaning they don't get drawn
+	 by gdk, and instead we set a NULL _gtk_cairo_get_event during expose so that the draw
+	 handler recurses into windowed widgets.
+
+	 We do this by setting "norender_children", which means that any windows in this widget
+	 or its ancestors (stopping at the first such windows at each branch in the hierarchy)
+	 are set to zero opacity. This is then propagated into all necessary children as norender,
+	 which controls whether a window should be exposed or not.
+      */
+      priv->norender_children = priv->alpha != 255;
+      gtk_widget_update_norender (widget);
+    }
+
+  if (gtk_widget_get_realized (widget))
+    gtk_widget_queue_draw (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)
diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h
index ff4f417..a4c20e8 100644
--- a/gtk/gtkwidget.h
+++ b/gtk/gtkwidget.h
@@ -670,6 +670,11 @@ void	   gtk_widget_set_device_events	  (GtkWidget	       *widget,
 void       gtk_widget_add_device_events   (GtkWidget           *widget,
                                            GdkDevice           *device,
 					   GdkEventMask         events);
+GDK_AVAILABLE_IN_3_8
+void	   gtk_widget_set_opacity	  (GtkWidget	       *widget,
+					   double		opacity);
+GDK_AVAILABLE_IN_3_8
+double	   gtk_widget_get_opacity	  (GtkWidget	       *widget);
 
 void       gtk_widget_set_device_enabled  (GtkWidget    *widget,
                                            GdkDevice    *device,
diff --git a/gtk/gtkwindow.c b/gtk/gtkwindow.c
index 6fc82f8..a2f9b18 100644
--- a/gtk/gtkwindow.c
+++ b/gtk/gtkwindow.c
@@ -118,8 +118,6 @@ struct _GtkWindowPrivate
   GdkModifierType        mnemonic_modifier;
   GdkWindowTypeHint      gdk_type_hint;
 
-  gdouble  opacity;
-
   GdkWindow *grip_window;
 
   gchar   *startup_id;
@@ -166,7 +164,6 @@ struct _GtkWindowPrivate
   guint    mnemonics_visible_set     : 1;
   guint    focus_visible             : 1;
   guint    modal                     : 1;
-  guint    opacity_set               : 1;
   guint    position                  : 3;
   guint    reset_type_hint           : 1;
   guint    resizable                 : 1;
@@ -228,7 +225,6 @@ enum {
   PROP_GRAVITY,
   PROP_TRANSIENT_FOR,
   PROP_ATTACHED_TO,
-  PROP_OPACITY,
   PROP_HAS_RESIZE_GRIP,
   PROP_RESIZE_GRIP_VISIBLE,
   PROP_APPLICATION,
@@ -983,24 +979,6 @@ gtk_window_class_init (GtkWindowClass *klass)
                                                         GTK_TYPE_WIDGET,
                                                         GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 
-  /**
-   * GtkWindow:opacity:
-   *
-   * The requested opacity of the window. See gtk_window_set_opacity() for
-   * more details about window opacity.
-   *
-   * Since: 2.12
-   */
-  g_object_class_install_property (gobject_class,
-				   PROP_OPACITY,
-				   g_param_spec_double ("opacity",
-							P_("Opacity for Window"),
-							P_("The opacity of the window, from 0 to 1"),
-							0.0,
-							1.0,
-							1.0,
-							GTK_PARAM_READWRITE));
-
   /* Style properties.
    */
   gtk_widget_class_install_style_property (widget_class,
@@ -1178,7 +1156,6 @@ gtk_window_init (GtkWindow *window)
   priv->focus_on_map = TRUE;
   priv->deletable = TRUE;
   priv->type_hint = GDK_WINDOW_TYPE_HINT_NORMAL;
-  priv->opacity = 1.0;
   priv->startup_id = NULL;
   priv->initial_timestamp = GDK_CURRENT_TIME;
   priv->has_resize_grip = TRUE;
@@ -1300,9 +1277,6 @@ gtk_window_set_property (GObject      *object,
     case PROP_ATTACHED_TO:
       gtk_window_set_attached_to (window, g_value_get_object (value));
       break;
-    case PROP_OPACITY:
-      gtk_window_set_opacity (window, g_value_get_double (value));
-      break;
     case PROP_HAS_RESIZE_GRIP:
       gtk_window_set_has_resize_grip (window, g_value_get_boolean (value));
       break;
@@ -1424,9 +1398,6 @@ gtk_window_get_property (GObject      *object,
     case PROP_ATTACHED_TO:
       g_value_set_object (value, gtk_window_get_attached_to (window));
       break;
-    case PROP_OPACITY:
-      g_value_set_double (value, gtk_window_get_opacity (window));
-      break;
     case PROP_HAS_RESIZE_GRIP:
       g_value_set_boolean (value, priv->has_resize_grip);
       break;
@@ -2707,28 +2678,13 @@ gtk_window_get_attached_to (GtkWindow *window)
  * shown causes it to flicker once on Windows.
  *
  * Since: 2.12
+ * Deprecated: 3.8: Use gtk_widget_set_opacity instead.
  **/
 void       
 gtk_window_set_opacity  (GtkWindow *window, 
 			 gdouble    opacity)
 {
-  GtkWindowPrivate *priv;
-
-  g_return_if_fail (GTK_IS_WINDOW (window));
-
-  priv = window->priv;
-
-  if (opacity < 0.0)
-    opacity = 0.0;
-  else if (opacity > 1.0)
-    opacity = 1.0;
-
-  priv->opacity_set = TRUE;
-  priv->opacity = opacity;
-
-  if (gtk_widget_get_realized (GTK_WIDGET (window)))
-    gdk_window_set_opacity (gtk_widget_get_window (GTK_WIDGET (window)),
-                            priv->opacity);
+  gtk_widget_set_opacity (GTK_WIDGET (window), opacity);
 }
 
 /**
@@ -2741,13 +2697,14 @@ gtk_window_set_opacity  (GtkWindow *window,
  * Return value: the requested opacity for this window.
  *
  * Since: 2.12
+ * Deprecated: 3.8: Use gtk_widget_get_opacity instead.
  **/
 gdouble
 gtk_window_get_opacity (GtkWindow *window)
 {
   g_return_val_if_fail (GTK_IS_WINDOW (window), 0.0);
 
-  return window->priv->opacity;
+  return gtk_widget_get_opacity (GTK_WIDGET (window));
 }
 
 /**
@@ -5281,9 +5238,6 @@ gtk_window_realize (GtkWidget *widget)
   gdk_window = gdk_window_new (parent_window, &attributes, attributes_mask);
   gtk_widget_set_window (widget, gdk_window);
 
-  if (priv->opacity_set)
-    gdk_window_set_opacity (gdk_window, priv->opacity);
-
   gdk_window_enable_synchronized_configure (gdk_window);
 
   gtk_widget_register_window (widget, gdk_window);
diff --git a/gtk/gtkwindow.h b/gtk/gtkwindow.h
index 22197b2..7b0c0dc 100644
--- a/gtk/gtkwindow.h
+++ b/gtk/gtkwindow.h
@@ -138,8 +138,10 @@ void       gtk_window_set_attached_to          (GtkWindow           *window,
                                                 GtkWidget           *attach_widget);
 GDK_AVAILABLE_IN_3_4
 GtkWidget *gtk_window_get_attached_to          (GtkWindow           *window);
+GDK_DEPRECATED_IN_3_8_FOR(gtk_widget_set_opacity)
 void       gtk_window_set_opacity              (GtkWindow           *window, 
 						gdouble              opacity);
+GDK_DEPRECATED_IN_3_8_FOR(gtk_widget_get_opacity)
 gdouble    gtk_window_get_opacity              (GtkWindow           *window);
 void       gtk_window_set_type_hint            (GtkWindow           *window, 
 						GdkWindowTypeHint    hint);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]