Index: gtk/gtk.h
===================================================================
--- gtk/gtk.h	(revision 17811)
+++ gtk/gtk.h	(working copy)
@@ -196,6 +196,7 @@
 #include <gtk/gtkvbox.h>
 #include <gtk/gtkversion.h>
 #include <gtk/gtkviewport.h>
+#include <gtk/gtkscalebutton.h>
 #include <gtk/gtkvpaned.h>
 #include <gtk/gtkvruler.h>
 #include <gtk/gtkvscale.h>
Index: gtk/gtk.symbols
===================================================================
--- gtk/gtk.symbols	(revision 17811)
+++ gtk/gtk.symbols	(working copy)
@@ -4455,6 +4455,16 @@
 #endif
 #endif
 
+#if IN_HEADER(__GTK_SCALE_BUTTON_H__)
+#if IN_FILE(__GTK_SCALE_BUTTON_C__)
+gtk_scale_button_get_type G_GNUC_CONST
+gtk_scale_button_get_value
+gtk_scale_button_new
+gtk_scale_button_set_icons
+gtk_scale_button_set_value
+#endif
+#endif
+
 #if IN_HEADER(__GTK_VPANED_H__)
 #if IN_FILE(__GTK_VPANED_C__)
 gtk_vpaned_get_type G_GNUC_CONST
Index: gtk/Makefile.am
===================================================================
--- gtk/Makefile.am	(revision 17811)
+++ gtk/Makefile.am	(working copy)
@@ -311,6 +311,7 @@
 	gtkvbbox.h		\
 	gtkvbox.h		\
 	gtkviewport.h		\
+	gtkscalebutton.h	\
 	gtkvpaned.h		\
 	gtkvruler.h		\
 	gtkvscale.h		\
@@ -594,6 +595,7 @@
 	gtkvbbox.c		\
 	gtkvbox.c		\
 	gtkviewport.c		\
+	gtkscalebutton.c	\
 	gtkvpaned.c		\
 	gtkvruler.c		\
 	gtkvscale.c		\
Index: gtk/gtkscalebutton.c
===================================================================
--- gtk/gtkscalebutton.c	(revision 0)
+++ gtk/gtkscalebutton.c	(revision 0)
@@ -0,0 +1,993 @@
+/* Volume Button / popup widget
+ * (c) copyright 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+#define _GNU_SOURCE
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gtkmain.h"
+#include "gtkintl.h"
+#include "gtkrange.h"
+#include "gtkscale.h"
+#include "gtkvscale.h"
+#include "gtkframe.h"
+#include "gtkvbox.h"
+#include "gtkwindow.h"
+#include "gtkmarshalers.h"
+#include "gtkstock.h"
+
+#include <gdk/gdkkeysyms.h>
+#include "gtkscalebutton.h"
+
+#define SCALE_SIZE 100
+#define CLICK_TIMEOUT 250
+
+enum {
+  VALUE_CHANGED,
+  LAST_SIGNAL
+};
+
+enum {
+  PROP_0,
+  PROP_VALUE,
+  PROP_SIZE,
+  PROP_ADJUSTMENT
+};
+
+struct GtkScaleButton {
+  GtkButton parent;
+
+  /* popup */
+  GtkWidget *dock, *scale, *image, *plus, *min;
+  GtkIconSize size;
+  guint click_id;
+  gdouble direction;
+  guint32 pop_time;
+  GList *icon_list;
+  gint click_timeout;
+  guint timeout : 1;
+};
+
+static void	gtk_scale_button_class_init	(GtkScaleButtonClass * klass);
+static void	gtk_scale_button_init		(GtkScaleButton * button);
+static void	gtk_scale_button_dispose	(GObject        * object);
+static void	gtk_scale_button_set_property	(GObject      *object,
+						 guint         prop_id,
+						 const GValue *value,
+						 GParamSpec   *pspec);
+static void	gtk_scale_button_get_property	(GObject     *object,
+						 guint        prop_id,
+						 GValue      *value,
+						 GParamSpec  *pspec);
+static gboolean	gtk_scale_button_scroll		(GtkWidget      *widget,
+						 GdkEventScroll *event);
+static void gtk_scale_button_screen_changed	(GtkWidget *widget,
+						 GdkScreen *previous_screen);
+static gboolean	gtk_scale_button_press		(GtkWidget      *widget,
+						 GdkEventButton *event);
+static gboolean gtk_scale_button_key_release	(GtkWidget      *widget,
+    						 GdkEventKey    *event);
+static gboolean cb_dock_button_press		(GtkWidget      *widget,
+						 GdkEventButton *event,
+						 gpointer         user_data);
+static gboolean cb_dock_key_release		(GtkWidget      *widget,
+						 GdkEventKey    *event,
+						 gpointer        user_data);
+static gboolean cb_dock_key_press		(GtkWidget      *widget,
+						 GdkEventKey    *event,
+						 gpointer        user_data);
+
+static gboolean cb_button_press			(GtkWidget      *widget,
+						 GdkEventButton *event,
+						 gpointer        user_data);
+static gboolean cb_button_release		(GtkWidget      *widget,
+						 GdkEventButton *event,
+						 gpointer        user_data);
+static void cb_dock_grab_notify			(GtkWidget      *widget,
+						 gboolean        was_grabbed,
+						 gpointer        user_data);
+static gboolean cb_dock_grab_broken_event	(GtkWidget      *widget,
+						 gboolean        was_grabbed,
+						 gpointer        user_data);
+static void gtk_scale_button_update_icon	(GtkScaleButton *button);
+static void gtk_scale_button_scale_value_changed(GtkRange *range);
+
+/* see below for scale definitions */
+static GtkWidget *gtk_scale_button_scale_new	(GtkScaleButton * button,
+						 gdouble min,
+						 gdouble max,
+						 gdouble step);
+
+static GtkButtonClass *parent_class = NULL;
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GtkScaleButton, gtk_scale_button, GTK_TYPE_BUTTON)
+
+static void
+gtk_scale_button_class_init (GtkScaleButtonClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  object_class->dispose = gtk_scale_button_dispose;
+  object_class->set_property = gtk_scale_button_set_property;
+  object_class->get_property = gtk_scale_button_get_property;
+  widget_class->button_press_event = gtk_scale_button_press;
+  widget_class->key_release_event = gtk_scale_button_key_release;
+  widget_class->scroll_event = gtk_scale_button_scroll;
+  widget_class->screen_changed = gtk_scale_button_screen_changed;
+
+  g_object_class_install_property (object_class,
+				   PROP_VALUE,
+				   g_param_spec_double ("value",
+							P_("Value"),
+							P_("The value of the scale"),
+							-G_MAXDOUBLE,
+							G_MAXDOUBLE,
+							0,
+							G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+				   PROP_SIZE,
+				   g_param_spec_enum ("size",
+						      P_("Icon size"),
+						      P_("The icon size"),
+						      GTK_TYPE_ICON_SIZE,
+						      GTK_ICON_SIZE_SMALL_TOOLBAR,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+                                   PROP_ADJUSTMENT,
+                                   g_param_spec_object ("adjustment",
+							P_("Adjustment"),
+							P_("The GtkAdjustment that contains the current value of this scale button object"),
+                                                        GTK_TYPE_ADJUSTMENT,
+                                                        G_PARAM_READWRITE));
+
+  /**
+   * GtkScaleButton::value-changed:
+   * @button: the object that received the signal
+   *
+   * Emitted when the value field has changed.
+   */
+  signals[VALUE_CHANGED] =
+    g_signal_new (I_("value-changed"),
+		  G_TYPE_FROM_CLASS (klass),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (GtkScaleButtonClass, value_changed),
+		  NULL, NULL,
+		  _gtk_marshal_VOID__DOUBLE,
+		  G_TYPE_NONE, 1, G_TYPE_DOUBLE);
+}
+
+static void
+gtk_scale_button_init (GtkScaleButton *button)
+{
+  GtkWidget *frame, *box;
+
+  button->timeout = FALSE;
+  button->click_id = 0;
+  button->click_timeout = CLICK_TIMEOUT;
+
+  gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+  gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
+
+  /* image */
+  button->image = gtk_image_new ();
+  gtk_container_add (GTK_CONTAINER (button), button->image);
+  gtk_widget_show_all (button->image);
+
+  /* window */
+  button->dock = gtk_window_new (GTK_WINDOW_POPUP);
+  g_signal_connect (button->dock, "button-press-event",
+		    G_CALLBACK (cb_dock_button_press), button);
+  g_signal_connect (button->dock, "key-release-event",
+		    G_CALLBACK (cb_dock_key_release), button);
+  g_signal_connect (button->dock, "key-press-event",
+		    G_CALLBACK (cb_dock_key_press), button);
+  g_signal_connect (button->dock, "grab-notify",
+		    G_CALLBACK (cb_dock_grab_notify), button);
+  g_signal_connect (button->dock, "grab-broken-event",
+		    G_CALLBACK (cb_dock_grab_broken_event), button);
+  gtk_window_set_decorated (GTK_WINDOW (button->dock), FALSE);
+
+  /* frame */
+  frame = gtk_frame_new (NULL);
+  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
+  gtk_container_add (GTK_CONTAINER (button->dock), frame);
+  box = gtk_vbox_new (FALSE, 0);
+  gtk_container_add (GTK_CONTAINER (frame), box);
+
+  /* + */
+  button->plus = gtk_button_new_with_label ("+");
+  gtk_button_set_relief (GTK_BUTTON (button->plus), GTK_RELIEF_NONE);
+  g_signal_connect (button->plus, "button-press-event",
+		    G_CALLBACK (cb_button_press), button);
+  g_signal_connect (button->plus, "button-release-event",
+		    G_CALLBACK (cb_button_release), button);
+  gtk_box_pack_start (GTK_BOX (box), button->plus, TRUE, FALSE, 0);
+
+  /* scale */
+  button->scale = gtk_scale_button_scale_new (button, 0., 100., 2.);
+  gtk_widget_set_size_request (button->scale, -1, SCALE_SIZE);
+  gtk_scale_set_draw_value (GTK_SCALE (button->scale), FALSE);
+  gtk_range_set_inverted (GTK_RANGE (button->scale), TRUE);
+  gtk_box_pack_start (GTK_BOX (box), button->scale, TRUE, FALSE, 0);
+
+  /* - */
+  button->min = gtk_button_new_with_label ("-");
+  gtk_button_set_relief (GTK_BUTTON (button->min), GTK_RELIEF_NONE);
+  g_signal_connect (button->min, "button-press-event",
+		   G_CALLBACK (cb_button_press), button);
+  g_signal_connect (button->min, "button-release-event",
+		    G_CALLBACK (cb_button_release), button);
+  gtk_box_pack_start (GTK_BOX (box), button->min, TRUE, FALSE, 0);
+
+  /* set button text and size */
+  button->size = GTK_ICON_SIZE_SMALL_TOOLBAR;
+  gtk_scale_button_update_icon (button);
+}
+
+static void
+gtk_scale_button_set_property (GObject      *object,
+				guint         prop_id,
+				const GValue *value,
+				GParamSpec   *pspec)
+{
+  GtkScaleButton *button;
+
+  button = GTK_SCALE_BUTTON (object);
+
+  switch (prop_id)
+    {
+    case PROP_VALUE:
+      gtk_scale_button_set_value (button, g_value_get_double (value));
+      break;
+    case PROP_SIZE:
+      {
+	GtkIconSize size;
+	size = g_value_get_enum (value);
+	if (button->size != size)
+	  {
+	    button->size = size;
+	    gtk_scale_button_update_icon (button);
+	  }
+      }
+    case PROP_ADJUSTMENT:
+      gtk_range_set_adjustment (GTK_RANGE (button->scale), g_value_get_object (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void 
+gtk_scale_button_get_property (GObject     *object,
+				guint        prop_id,
+				GValue      *value,
+				GParamSpec  *pspec)
+{
+  GtkScaleButton *button;
+
+  button = GTK_SCALE_BUTTON (object);
+
+  switch (prop_id)
+    {
+    case PROP_VALUE:
+      g_value_set_float (value, gtk_scale_button_get_value (button));
+      break;
+    case PROP_SIZE:
+      g_value_set_enum (value, button->size);
+      break;
+    case PROP_ADJUSTMENT:
+      g_value_set_object (value, GTK_RANGE (button->scale)->adjustment);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_scale_button_dispose (GObject *object)
+{
+  GtkScaleButton *button = GTK_SCALE_BUTTON (object);
+  GList *l;
+
+  if (button->dock) {
+    gtk_widget_destroy (button->dock);
+    button->dock = NULL;
+  }
+
+  if (button->click_id != 0) {
+    g_source_remove (button->click_id);
+    button->click_id = 0;
+  }
+  for (l = button->icon_list; l != NULL; l = l->next) {
+    g_free (l->data);
+  }
+  g_list_free (button->icon_list);
+  button->icon_list = NULL;
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+/**
+ * gtk_scale_button_new
+ * @size: a stock icon size
+ * @lower: the minimum value of the scale (usually 0)
+ * @upper: the maximum value of the scale (usually 100)
+ * @step_increment: the stepping of value when a scroll-wheel event,
+ * or up/down arrow event occurs (usually 2)
+ * @icons: a %NULL-terminated array of icon names, or %NULL if
+ * you want to set the list later with #gtk_scale_button_set_icons. See
+ * #gtk_scale_button_set_icons for details.
+ *
+ * Creates a #GtkScaleButton, with a range between lower and upper, with
+ * a stepping of step_increment.
+ *
+ * Return value: a new #GtkScaleButton
+ */
+GtkWidget *
+gtk_scale_button_new (GtkIconSize size,
+		       gdouble lower,
+		       gdouble upper,
+		       gdouble step_increment,
+		       const char **icons)
+{
+  GtkScaleButton *button;
+
+  button = g_object_new (GTK_TYPE_SCALE_BUTTON, NULL);
+
+  if (icons != NULL)
+    gtk_scale_button_set_icons (button, icons);
+
+  gtk_range_set_range (GTK_RANGE (button->scale), lower, upper);
+  gtk_range_set_increments (GTK_RANGE (button->scale),
+			    step_increment,
+			    10 * step_increment);
+
+  if (button->size != size)
+    {
+      button->size = size;
+      gtk_scale_button_update_icon (button);
+    }
+
+  return GTK_WIDGET (button);
+}
+
+/**
+ * gtk_scale_button_get_value
+ * @button: a #GtkScaleButton
+ *
+ * Gets the current value of the scale button.
+ *
+ * Return value: current value of the scale button
+ */
+gdouble
+gtk_scale_button_get_value (GtkScaleButton * button)
+{
+  g_return_val_if_fail (button != NULL, 0);
+
+  return gtk_range_get_value (GTK_RANGE (button->scale));
+}
+
+/**
+ * gtk_scale_button_set_value
+ * @button: a #GtkScaleButton
+ * @value: new value of the scale button
+ *
+ * Sets the current value of the scale; if the value is outside the minimum or maximum range values, it will be clamped to fit inside them. The scale button emits the "value_changed" signal if the value changes.
+ */
+void
+gtk_scale_button_set_value (GtkScaleButton * button,
+			    gdouble value)
+{
+  g_return_if_fail (button != NULL);
+
+  gtk_range_set_value (GTK_RANGE (button->scale), value);
+}
+
+/**
+ * gtk_scale_button_set_icons
+ * @button: a #GtkScaleButton
+ * @icons: a %NULL-terminated array of icon names
+ *
+ * Sets the icons to be used by the scale button. The first item in the array will be used in the button when the current value is the lowest value, the second item for the highest value. All the subsequent icons will be used for all the other values, spread evenly over the range of values. As such, the @icons array must include at least 3 icon names.
+ */
+void
+gtk_scale_button_set_icons (GtkScaleButton *button,
+			    const char **icons)
+{
+  GList *l;
+  guint i, num_icons;
+
+  /* Count the number of icons passed */
+  for (num_icons = 0; icons && icons[num_icons] != NULL; num_icons++) {}
+  g_return_if_fail (num_icons >= 4);
+
+  /* Empty the original list */
+  for (l = button->icon_list; l != NULL; l = l->next)
+    g_free (l->data);
+  g_list_free (button->icon_list);
+  button->icon_list = NULL;
+
+  for (i = 0; icons[i] != NULL; i++)
+    {
+      button->icon_list = g_list_prepend (button->icon_list,
+					  g_strdup (icons[i]));
+    }
+  button->icon_list = g_list_reverse (button->icon_list);
+  gtk_scale_button_update_icon (button);
+}
+
+/*
+ * button callbacks.
+ */
+
+static gboolean
+gtk_scale_button_scroll (GtkWidget      * widget,
+			 GdkEventScroll * event)
+{
+  GtkScaleButton *button = GTK_SCALE_BUTTON (widget);
+  GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
+  gdouble d;
+
+  if (event->type != GDK_SCROLL)
+    return FALSE;
+
+  d = gtk_scale_button_get_value (button);
+  if (event->direction == GDK_SCROLL_UP) {
+    d += adj->step_increment;
+    if (d > adj->upper)
+      d = adj->upper;
+  } else {
+    d -= adj->step_increment;
+    if (d < adj->lower)
+      d = adj->lower;
+  }
+  gtk_scale_button_set_value (button, d);
+
+  return TRUE;
+}
+
+static void
+gtk_scale_button_screen_changed (GtkWidget *widget,
+				 GdkScreen *previous_screen)
+{
+  GtkScaleButton *button = (GtkScaleButton *) widget;
+  GdkScreen *screen;
+  GValue value = { 0, };
+
+  if (gtk_widget_has_screen (widget) == FALSE)
+    return;
+  screen = gtk_widget_get_screen (widget);
+  g_value_init (&value, G_TYPE_INT);
+  if (gdk_screen_get_setting (screen,
+			  "gtk-double-click-time",
+			  &value) == FALSE)
+    {
+      button->click_timeout = CLICK_TIMEOUT;
+      return;
+    }
+
+  button->click_timeout = g_value_get_int (&value);
+}
+
+static gboolean
+gtk_scale_popup (GtkWidget *widget,
+		 GdkEvent  *event,
+		 guint32    time)
+{
+  GtkScaleButton *button = GTK_SCALE_BUTTON (widget);
+  GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
+  gint x, y, m, dx, dy, sx, sy, ystartoff;
+  gdouble v;
+  GdkDisplay *display;
+  GdkScreen *screen;
+
+  display = gtk_widget_get_display (widget);
+  screen = gtk_widget_get_screen (widget);
+
+  /* position roughly */
+  gtk_window_set_screen (GTK_WINDOW (button->dock), screen);
+
+  gdk_window_get_origin (widget->window, &x, &y);
+  x += widget->allocation.x;
+  y += widget->allocation.y;
+  gtk_window_move (GTK_WINDOW (button->dock), x, y - (SCALE_SIZE / 2));
+  gtk_widget_show_all (button->dock);
+  gdk_window_get_origin (button->dock->window, &dx, &dy);
+  dy += button->dock->allocation.y;
+  gdk_window_get_origin (button->scale->window, &sx, &sy);
+  sy += button->scale->allocation.y;
+  ystartoff = sy - dy;
+  button->timeout = TRUE;
+
+  /* position (needs widget to be shown already) */
+  v = gtk_scale_button_get_value (button) / (adj->upper - adj->lower);
+  x += (widget->allocation.width - button->dock->allocation.width) / 2;
+  y -= ystartoff;
+  y -= GTK_RANGE (button->scale)->min_slider_size / 2;
+  m = button->scale->allocation.height -
+      GTK_RANGE (button->scale)->min_slider_size;
+  y -= m * (1.0 - v);
+
+  /* Make sure the dock stays inside the monitor */
+  if (event->type == GDK_BUTTON_PRESS) {
+    int monitor;
+    GdkEventButton *button_event = (GdkEventButton *) event;
+    GdkRectangle rect;
+    GtkWidget *d;
+
+    d = GTK_WIDGET (button->dock);
+    monitor = gdk_screen_get_monitor_at_point (screen,
+					       button_event->x,
+					       button_event->y);
+    gdk_screen_get_monitor_geometry (screen, monitor, &rect);
+
+    y += button_event->y;
+    if (y < rect.y) {
+      y = rect.y;
+    } else if (y + d->allocation.height > rect.height + rect.y) {
+      y = rect.y + rect.height - d->allocation.height;
+    }
+    if (x < rect.x) {
+      x = rect.x;
+    } else if (x + d->allocation.width > rect.width - rect.x) {
+      x = rect.x + rect.width - d->allocation.width;
+    }
+  }
+
+  gtk_window_move (GTK_WINDOW (button->dock), x, y);
+
+  if (event->type == GDK_BUTTON_PRESS)
+    GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, (GdkEventButton *) event);
+
+  /* grab focus */
+  gtk_grab_add (button->dock);
+
+  if (gdk_pointer_grab (button->dock->window, TRUE,
+			GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+			GDK_POINTER_MOTION_MASK, NULL, NULL, time)
+      != GDK_GRAB_SUCCESS) {
+    gtk_grab_remove (button->dock);
+    gtk_widget_hide (button->dock);
+    return FALSE;
+  }
+
+  if (gdk_keyboard_grab (button->dock->window, TRUE, time) != GDK_GRAB_SUCCESS) {
+    gdk_display_pointer_ungrab (display, time);
+    gtk_grab_remove (button->dock);
+    gtk_widget_hide (button->dock);
+    return FALSE;
+  }
+
+  gtk_widget_grab_focus (button->dock);
+
+  if (event->type == GDK_BUTTON_PRESS) {
+    GdkEventButton *e;
+    GdkEventButton *button_event = (GdkEventButton *) event;
+
+    /* forward event to the slider */
+    e = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
+    e->window = button->scale->window;
+
+    /* position: the X position isn't relevant, halfway will work just fine.
+     * The vertical position should be *exactly* in the middle of the slider
+     * of the scale; if we don't do that correctly, it'll move from its current
+     * position, which means a position change on-click, which is bad. */
+    e->x = button->scale->allocation.width / 2;
+    m = button->scale->allocation.height -
+      GTK_RANGE (button->scale)->min_slider_size;
+    e->y = ((1.0 - v) * m) + GTK_RANGE (button->scale)->min_slider_size / 2;
+    gtk_widget_event (button->scale, (GdkEvent *) e);
+    e->window = button_event->window;
+    gdk_event_free ((GdkEvent *) e);
+  }
+
+  gtk_widget_grab_focus (button->scale);
+
+  button->pop_time = time;
+
+  return TRUE;
+}
+
+
+static gboolean
+gtk_scale_button_press (GtkWidget      *widget,
+			GdkEventButton *event)
+{
+  return gtk_scale_popup (widget, (GdkEvent *) event, event->time);
+}
+
+static gboolean
+gtk_scale_button_key_release (GtkWidget   *widget,
+			      GdkEventKey *event)
+{
+  if (event->keyval != GDK_space && event->keyval != GDK_Return)
+    return FALSE;
+
+  return gtk_scale_popup (widget, (GdkEvent *) event, event->time);
+}
+
+/*
+ * +/- button callbacks.
+ */
+
+static gboolean
+cb_button_timeout (gpointer user_data)
+{
+  GtkScaleButton *button = GTK_SCALE_BUTTON (user_data);
+  GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
+  gdouble val;
+  gboolean res = TRUE;
+
+  if (button->click_id == 0)
+    return FALSE;
+
+  val = gtk_scale_button_get_value (button);
+  val += button->direction;
+  if (val <= adj->lower) {
+    res = FALSE;
+    val = adj->lower;
+  } else if (val > adj->upper) {
+    res = FALSE;
+    val = adj->upper;
+  }
+  gtk_scale_button_set_value (button, val);
+
+  if (!res) {
+    g_source_remove (button->click_id);
+    button->click_id = 0;
+  }
+
+  return res;
+}
+
+static gboolean
+cb_button_press (GtkWidget      *widget,
+		 GdkEventButton *event,
+		 gpointer        user_data)
+{
+  GtkScaleButton *button = GTK_SCALE_BUTTON (user_data);
+  GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
+
+  if (button->click_id != 0)
+    g_source_remove (button->click_id);
+  button->direction = (widget == button->plus) ?
+      fabs (adj->page_increment) : - fabs (adj->page_increment);
+
+  button->click_id = g_timeout_add (button->click_timeout,
+				    (GSourceFunc) cb_button_timeout, button);
+  cb_button_timeout (button);
+
+  return TRUE;
+}
+
+static gboolean
+cb_button_release (GtkWidget      *widget,
+		   GdkEventButton *event,
+		   gpointer        user_data)
+{
+  GtkScaleButton *button = GTK_SCALE_BUTTON (user_data);
+
+  if (button->click_id != 0) {
+    g_source_remove (button->click_id);
+    button->click_id = 0;
+  }
+
+  return TRUE;
+}
+
+static void
+cb_dock_grab_notify (GtkWidget *widget,
+		     gboolean   was_grabbed,
+		     gpointer   user_data)
+{
+  GdkDisplay *display;
+  GtkScaleButton *button = (GtkScaleButton *) user_data;
+
+  g_message ("cb_dock_grab_notify %d", was_grabbed);
+
+  if (was_grabbed != FALSE)
+    return;
+
+  if (gtk_widget_is_ancestor (gtk_grab_get_current (), button->dock))
+    return;
+
+  display = gtk_widget_get_display (widget);
+  gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME);
+  gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME);
+  gtk_grab_remove (button->dock);
+
+  /* hide again */
+  gtk_widget_hide (button->dock);
+  button->timeout = FALSE;
+}
+
+static gboolean
+cb_dock_grab_broken_event (GtkWidget *widget,
+			   gboolean   was_grabbed,
+			   gpointer   user_data)
+{
+  GtkScaleButton *button = (GtkScaleButton *) user_data;
+
+  g_message ("cb_dock_grab_broken_event");
+  cb_dock_grab_notify (button->dock, FALSE, user_data);
+
+  return FALSE;
+}
+
+/*
+ * Scale callbacks.
+ */
+
+static void
+gtk_scale_button_release_grab (GtkScaleButton *button,
+			       GdkEventButton * event)
+{
+  GdkEventButton *e;
+  GdkDisplay *display;
+
+  /* ungrab focus */
+  display = gtk_widget_get_display (GTK_WIDGET (button));
+  gdk_display_keyboard_ungrab (display, event->time);
+  gdk_display_pointer_ungrab (display, event->time);
+  gtk_grab_remove (button->dock);
+
+  /* hide again */
+  gtk_widget_hide (button->dock);
+  button->timeout = FALSE;
+
+  e = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
+  e->window = GTK_WIDGET (button)->window;
+  e->type = GDK_BUTTON_RELEASE;
+  gtk_widget_event (GTK_WIDGET (button), (GdkEvent *) e);
+  e->window = event->window;
+  gdk_event_free ((GdkEvent *) e);
+}
+
+static gboolean
+cb_dock_button_press (GtkWidget      *widget,
+		      GdkEventButton *event,
+		      gpointer         user_data)
+{
+  GtkScaleButton *button = GTK_SCALE_BUTTON (user_data);
+
+  if (event->type == GDK_BUTTON_PRESS)
+    {
+      gtk_scale_button_release_grab (button, event);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+cb_dock_key_release (GtkWidget   *widget,
+		     GdkEventKey *event,
+		     gpointer     user_data)
+{
+  GtkScaleButton *button = GTK_SCALE_BUTTON (user_data);
+
+  if (event->keyval == GDK_Escape)
+    {
+      GdkDisplay *display;
+
+      /* ungrab focus */
+      display = gtk_widget_get_display (widget);
+      gdk_display_keyboard_ungrab (display, event->time);
+      gdk_display_pointer_ungrab (display, event->time);
+      gtk_grab_remove (button->dock);
+
+      /* hide again */
+      gtk_widget_hide (button->dock);
+      button->timeout = FALSE;
+
+      return TRUE;
+    }
+  return FALSE;
+}
+
+static gboolean
+cb_dock_key_press (GtkWidget   *widget,
+		   GdkEventKey *event,
+		   gpointer     user_data)
+{
+  if (event->keyval == GDK_Escape)
+    return TRUE;
+  return FALSE;
+}
+
+/*
+ * Scale stuff.
+ */
+
+#define GTK_TYPE_SCALE_BUTTON_SCALE \
+  (gtk_scale_button_scale_get_type ())
+#define GTK_SCALE_BUTTON_SCALE(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SCALE_BUTTON_SCALE, \
+			       GtkScaleButtonScale))
+
+typedef struct _GtkScaleButtonScaleClass {
+  GtkVScaleClass parent_class;
+} GtkScaleButtonScaleClass;
+
+typedef struct _GtkScaleButtonScale {
+  GtkVScale parent;
+  GtkScaleButton *button;
+} GtkScaleButtonScale;
+
+static GType	gtk_scale_button_scale_get_type	 (void);
+
+static void	gtk_scale_button_scale_class_init    (GtkScaleButtonScaleClass * klass);
+
+static gboolean	gtk_scale_button_scale_press	 (GtkWidget      * widget,
+						  GdkEventButton * event);
+static gboolean gtk_scale_button_scale_release	 (GtkWidget      * widget,
+						  GdkEventButton * event);
+
+static GtkVScaleClass *scale_parent_class = NULL;
+
+G_DEFINE_TYPE (GtkScaleButtonScale, gtk_scale_button_scale, GTK_TYPE_VSCALE)
+
+static void
+gtk_scale_button_scale_class_init (GtkScaleButtonScaleClass * klass)
+{
+  GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS (klass);
+  GtkRangeClass *gtkrange_class = GTK_RANGE_CLASS (klass);
+
+  scale_parent_class = g_type_class_peek_parent (klass);
+
+  gtkwidget_class->button_press_event = gtk_scale_button_scale_press;
+  gtkwidget_class->button_release_event = gtk_scale_button_scale_release;
+  gtkrange_class->value_changed = gtk_scale_button_scale_value_changed;
+}
+
+static void
+gtk_scale_button_scale_init (GtkScaleButtonScale *scale)
+{
+}
+
+static GtkWidget *
+gtk_scale_button_scale_new (GtkScaleButton * button,
+			     gdouble min,
+			     gdouble max,
+			     gdouble step)
+{
+  GtkScaleButtonScale *scale = g_object_new (GTK_TYPE_SCALE_BUTTON_SCALE, NULL);
+  GtkObject *adj;
+
+  adj = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
+  gtk_range_set_adjustment (GTK_RANGE (scale), GTK_ADJUSTMENT (adj));
+  scale->button = button;
+
+  return GTK_WIDGET (scale);
+}
+
+static gboolean
+gtk_scale_button_scale_press (GtkWidget      * widget,
+			  GdkEventButton * event)
+{
+  GtkScaleButtonScale *scale = GTK_SCALE_BUTTON_SCALE (widget);
+  GtkScaleButton *button = scale->button;
+
+  /* the scale will grab input; if we have input grabbed, all goes
+   * horribly wrong, so let's not do that. */
+  gtk_grab_remove (button->dock);
+
+  return GTK_WIDGET_CLASS (scale_parent_class)->button_press_event (widget, event);
+}
+
+static gboolean
+gtk_scale_button_scale_release (GtkWidget      * widget,
+			    GdkEventButton * event)
+{
+  GtkScaleButtonScale *scale = GTK_SCALE_BUTTON_SCALE (widget);
+  GtkScaleButton *button = scale->button;
+  gboolean res;
+
+  if (button->timeout)
+    {
+      /* if we did a quick click, leave the window open; else, hide it */
+      if (event->time > button->pop_time + button->click_timeout)
+        {
+	  gtk_scale_button_release_grab (button, event);
+	  GTK_WIDGET_CLASS (scale_parent_class)->button_release_event (widget, event);
+	  return TRUE;
+	}
+      button->timeout = FALSE;
+    }
+
+  res = GTK_WIDGET_CLASS (scale_parent_class)->button_release_event (widget, event);
+
+  /* the scale will release input; right after that, we *have to* grab
+   * it back so we can catch out-of-scale clicks and hide the popup,
+   * so I basically want a g_signal_connect_after_always(), but I can't
+   * find that, so we do this complex 'first-call-parent-then-do-actual-
+   * action' thingy... */
+  gtk_grab_add (button->dock);
+
+  return res;
+}
+
+static void
+gtk_scale_button_update_icon (GtkScaleButton *button)
+{
+  GtkRange *range;
+  GtkAdjustment *adj;
+  gdouble step, value;
+  const char *name;
+  guint num_icons;
+
+  if (button->icon_list == NULL)
+    {
+      gtk_image_set_from_stock (GTK_IMAGE (button->image),
+				GTK_STOCK_MISSING_IMAGE,
+				button->size);
+      return;
+    }
+
+  range = GTK_RANGE (button->scale);
+  adj = gtk_range_get_adjustment (range);
+  num_icons = g_list_length (button->icon_list);
+  step = (adj->upper - adj->lower) / (num_icons - 2);
+  value = gtk_scale_button_get_value (button);
+
+  if (value == adj->lower) {
+    name = g_list_nth_data (button->icon_list, 0);
+  } else if (value == adj->upper) {
+    GList *l;
+    l = g_list_last (button->icon_list);
+    name = l->data;
+  } else {
+    guint i;
+    i = (guint) (value - adj->lower) / step + 1;
+    name = g_list_nth_data (button->icon_list, i);
+  }
+
+  gtk_image_set_from_icon_name (GTK_IMAGE (button->image),
+				name,
+				button->size);
+}
+
+static void
+gtk_scale_button_scale_value_changed (GtkRange *range)
+{
+  GtkScaleButtonScale *scale = GTK_SCALE_BUTTON_SCALE (range);
+  GtkScaleButton *button = scale->button;
+  gdouble value;
+
+  value = gtk_range_get_value (range);
+
+  gtk_scale_button_update_icon (button);
+
+  g_signal_emit (button, signals[VALUE_CHANGED], 0, value);
+  g_object_notify (G_OBJECT (button), "value");
+}
+ 
+/*
+ * vim: sw=2 ts=8 cindent noai bs=2
+ */
Index: gtk/gtkscalebutton.h
===================================================================
--- gtk/gtkscalebutton.h	(revision 0)
+++ gtk/gtkscalebutton.h	(revision 0)
@@ -0,0 +1,66 @@
+/* Volume Button / popup widget
+ * (c) copyright 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GTK_SCALE_BUTTON_H__
+#define __GTK_SCALE_BUTTON_H__
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkicontheme.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SCALE_BUTTON                 (gtk_scale_button_get_type ())
+#define GTK_SCALE_BUTTON(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SCALE_BUTTON, GtkScaleButton))
+#define GTK_SCALE_BUTTON_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SCALE_BUTTON, GtkButtonClass))
+#define GTK_IS_SCALE_BUTTON(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SCALE_BUTTON))
+#define GTK_IS_SCALE_BUTTON_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SCALE_BUTTON))
+#define GTK_SCALE_BUTTON_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SCALE_BUTTON, GtkButtonScaleClass))
+
+typedef struct GtkScaleButton GtkScaleButton;
+
+typedef struct _GtkScaleButtonClass {
+  GtkButtonClass parent_class;
+
+  /* signals */
+  void	(* value_changed) (GtkScaleButton *button, gdouble value);
+
+  /* Padding for future expansion */
+  void (*_gtk_reserved1) (void);
+  void (*_gtk_reserved2) (void);
+  void (*_gtk_reserved3) (void);
+  void (*_gtk_reserved4) (void);
+} GtkScaleButtonClass;
+
+GType		gtk_scale_button_get_type	(void);
+GtkWidget*	gtk_scale_button_new		(GtkIconSize size,
+						 gdouble min,
+						 gdouble max,
+						 gdouble step,
+						 const char **icons);
+void		gtk_scale_button_set_icons	(GtkScaleButton *button,
+						 const char **icons);
+gdouble		gtk_scale_button_get_value	(GtkScaleButton *button);
+void		gtk_scale_button_set_value	(GtkScaleButton *button,
+						 gdouble value);
+
+G_END_DECLS
+
+#endif /* __GTK_SCALE_BUTTON_H__ */
+
Index: docs/reference/gdk/tmpl/threads.sgml
===================================================================
--- docs/reference/gdk/tmpl/threads.sgml	(revision 17811)
+++ docs/reference/gdk/tmpl/threads.sgml	(working copy)
@@ -308,3 +308,49 @@
 @leave_fn: 
 
 
+<!-- ##### FUNCTION gdk_threads_add_idle ##### -->
+<para>
+
+</para>
+
+@function: 
+@data: 
+@Returns: 
+
+
+<!-- ##### FUNCTION gdk_threads_add_idle_full ##### -->
+<para>
+
+</para>
+
+@priority: 
+@function: 
+@data: 
+@notify: 
+@Returns: 
+
+
+<!-- ##### FUNCTION gdk_threads_add_timeout ##### -->
+<para>
+
+</para>
+
+@interval: 
+@function: 
+@data: 
+@Returns: 
+
+
+<!-- ##### FUNCTION gdk_threads_add_timeout_full ##### -->
+<para>
+
+</para>
+
+@priority: 
+@interval: 
+@function: 
+@data: 
+@notify: 
+@Returns: 
+
+
Index: docs/reference/gdk/tmpl/colors.sgml
===================================================================
--- docs/reference/gdk/tmpl/colors.sgml	(revision 17811)
+++ docs/reference/gdk/tmpl/colors.sgml	(working copy)
@@ -289,3 +289,12 @@
 @Returns: 
 
 
+<!-- ##### FUNCTION gdk_color_to_string ##### -->
+<para>
+
+</para>
+
+@color: 
+@Returns: 
+
+
Index: docs/reference/gtk/gtk-sections.txt
===================================================================
--- docs/reference/gtk/gtk-sections.txt	(revision 17811)
+++ docs/reference/gtk/gtk-sections.txt	(working copy)
@@ -3013,6 +3013,23 @@
 </SECTION>
 
 <SECTION>
+<FILE>gtkscalebutton</FILE>
+<TITLE>GtkScaleButton</TITLE>
+GtkScaleButton
+gtk_scale_button_new
+gtk_scale_button_set_value
+gtk_scale_button_get_value
+gtk_scale_button_set_icons
+<SUBSECTION Standard>
+GTK_SCALE_BUTTON
+GTK_SCALE_IS_BUTTON
+GTK_TYPE_SCALE_BUTTON
+GTK_SCALE_BUTTON_GET_CLASS
+<SUBSECTION Private>
+gtk_scale_button_get_type
+</SECTION>
+
+<SECTION>
 <FILE>gtkscrollbar</FILE>
 <TITLE>GtkScrollbar</TITLE>
 GtkScrollbar
Index: docs/reference/gtk/gtk-docs.sgml
===================================================================
--- docs/reference/gtk/gtk-docs.sgml	(revision 17811)
+++ docs/reference/gtk/gtk-docs.sgml	(working copy)
@@ -108,6 +108,7 @@
 <!ENTITY GtkRecentManager SYSTEM "xml/gtkrecentmanager.xml">
 <!ENTITY GtkRuler SYSTEM "xml/gtkruler.xml">
 <!ENTITY GtkScale SYSTEM "xml/gtkscale.xml">
+<!ENTITY GtkScaleButton SYSTEM "xml/gtkscalebutton.xml">
 <!ENTITY GtkScrollbar SYSTEM "xml/gtkscrollbar.xml">
 <!ENTITY GtkScrolledWindow SYSTEM "xml/gtkscrolledwindow.xml">
 <!ENTITY GtkSeparator SYSTEM "xml/gtkseparator.xml">
Index: tests/testvolumebutton.c
===================================================================
--- tests/testvolumebutton.c	(revision 0)
+++ tests/testvolumebutton.c	(revision 0)
@@ -0,0 +1,91 @@
+/* testvolumebutton.c
+ * Copyright (C) 2007  Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <gtk/gtk.h>
+
+static void
+value_changed (GtkWidget *button,
+	       gdouble volume,
+	       gpointer user_data)
+{
+	g_message ("volume changed to %f", volume);
+}
+
+static const char *icons[] = {
+	"audio-volume-muted",
+	"audio-volume-low",
+	"audio-volume-medium",
+	"audio-volume-high",
+	NULL
+};
+
+static void
+response_cb (GtkDialog *dialog,
+	     gint       arg1,
+	     gpointer   user_data)
+{
+	gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static gboolean
+show_error (gpointer data)
+{
+	GtkWindow *window = (GtkWindow *) data;
+	GtkWidget *dialog;
+
+	g_message ("showing error");
+
+	dialog = gtk_message_dialog_new (window,
+					 GTK_DIALOG_MODAL,
+					 GTK_MESSAGE_INFO,
+					 GTK_BUTTONS_CLOSE,
+					 "This should have unbroken the grab");
+	g_signal_connect (G_OBJECT (dialog),
+			  "response",
+			  G_CALLBACK (response_cb), NULL);
+	gtk_widget_show (dialog);
+
+	return FALSE;
+}
+
+int main (int argc, char **argv)
+{
+	GtkWidget *window;
+	GtkWidget *button;
+
+	gtk_init (&argc, &argv);
+
+	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	button = gtk_scale_button_new (GTK_ICON_SIZE_SMALL_TOOLBAR,
+					0, 100, 2, NULL);
+	gtk_scale_button_set_icons (GTK_SCALE_BUTTON (button), icons);
+	g_signal_connect (G_OBJECT (button),
+			  "value-changed",
+			  G_CALLBACK (value_changed), NULL);
+	gtk_container_add (GTK_CONTAINER (window), button);
+
+	gtk_widget_show_all (window);
+	gtk_button_clicked (GTK_BUTTON (button));
+	g_timeout_add (4000, (GSourceFunc) show_error, window);
+
+	gtk_main ();
+
+	return 0;
+}
+
Index: tests/Makefile.am
===================================================================
--- tests/Makefile.am	(revision 17811)
+++ tests/Makefile.am	(working copy)
@@ -85,7 +85,8 @@
 	testmerge			\
 	testactions			\
 	testgrouping			\
-	testtooltips
+	testtooltips			\
+	testvolumebutton
 
 autotestfilechooser_DEPENDENCIES = $(TEST_DEPS)
 simple_DEPENDENCIES = $(TEST_DEPS)
@@ -140,6 +141,7 @@
 testactions_DEPENDENCIES = $(TEST_DEPS)
 testgrouping_DEPENDENCIES = $(TEST_DEPS)
 testtooltips_DEPENDENCIES = $(TEST_DEPS)
+testvolumebutton_DEPENDENCIES = $(TEST_DEPS)
 
 autotestfilechooser_LDADD = $(LDADDS)
 simple_LDADD = $(LDADDS)
@@ -201,6 +203,7 @@
 testactions_LDADD = $(LDADDS)
 testgrouping_LDADD = $(LDADDS)
 testtooltips_LDADD = $(LDADDS)
+testvolumebutton_LDADD = $(LDADDS)
 
 autotestfilechooser_SOURCES =	\
 	autotestfilechooser.c
@@ -285,6 +288,9 @@
 testrecentchoosermenu_SOURCES =	\
 	testrecentchoosermenu.c
 
+testvolumebutton_SOURCES =	\
+	testvolumebutton.c
+
 EXTRA_DIST = 			\
 	prop-editor.h		\
 	testgtk.1 		\
