]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtkrange.c
Replace a lot of idle and timeout calls by the new gdk_threads api.
[~andy/gtk] / gtk / gtkrange.c
index 0f22f1e7420556bf9243bc1af03b90f7fc45c034..696198a1be4cb62d1339a423a35d4eec8686852c 100644 (file)
@@ -1,5 +1,6 @@
 /* GTK - The GIMP Toolkit
  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ * Copyright (C) 2001 Red Hat, Inc.
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  */
 
 /*
- * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
+ * Modified by the GTK+ Team and others 1997-2004.  See the AUTHORS
  * file for a list of people on the GTK+ Team.  See the ChangeLog
  * files for a list of changes.  These files are distributed with
  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
  */
 
+#include <config.h>
 #include <stdio.h>
+#include <math.h>
+#include <gdk/gdkkeysyms.h>
+#include "gtkintl.h"
 #include "gtkmain.h"
+#include "gtkmarshalers.h"
 #include "gtkrange.h"
-#include "gtksignal.h"
+#include "gtkscrollbar.h"
+#include "gtkprivate.h"
+#include "gtkalias.h"
 
-#define SCROLL_TIMER_LENGTH  20
-#define SCROLL_INITIAL_DELAY 100
-#define SCROLL_DELAY_LENGTH  300
+#define SCROLL_DELAY_FACTOR 5    /* Scroll repeat multiplier */
+#define UPDATE_DELAY        300  /* Delay for queued update */
 
-#define RANGE_CLASS(w)  GTK_RANGE_GET_CLASS (w)
+enum {
+  PROP_0,
+  PROP_UPDATE_POLICY,
+  PROP_ADJUSTMENT,
+  PROP_INVERTED,
+  PROP_LOWER_STEPPER_SENSITIVITY,
+  PROP_UPPER_STEPPER_SENSITIVITY,
+  PROP_SHOW_FILL_LEVEL,
+  PROP_RESTRICT_TO_FILL_LEVEL,
+  PROP_FILL_LEVEL
+};
 
 enum {
-  ARG_0,
-  ARG_UPDATE_POLICY
+  VALUE_CHANGED,
+  ADJUST_BOUNDS,
+  MOVE_SLIDER,
+  CHANGE_VALUE,
+  LAST_SIGNAL
 };
 
-static void gtk_range_class_init               (GtkRangeClass    *klass);
-static void gtk_range_init                     (GtkRange         *range);
-static void gtk_range_set_arg                 (GtkObject        *object,
-                                               GtkArg           *arg,
-                                               guint             arg_id);
-static void gtk_range_get_arg                 (GtkObject        *object,
-                                               GtkArg           *arg,
-                                               guint             arg_id);
-static void gtk_range_destroy                  (GtkObject        *object);
-static void gtk_range_draw_focus               (GtkWidget        *widget);
-static void gtk_range_unrealize                (GtkWidget        *widget);
-static gint gtk_range_expose                   (GtkWidget        *widget,
-                                               GdkEventExpose   *event);
-static gint gtk_range_button_press             (GtkWidget        *widget,
-                                               GdkEventButton   *event);
-static gint gtk_range_button_release           (GtkWidget        *widget,
-                                               GdkEventButton   *event);
-static gint gtk_range_motion_notify            (GtkWidget        *widget,
-                                               GdkEventMotion   *event);
-static gint gtk_range_key_press                (GtkWidget         *widget,
-                                               GdkEventKey       *event);
-static gint gtk_range_enter_notify             (GtkWidget        *widget,
-                                               GdkEventCrossing *event);
-static gint gtk_range_leave_notify             (GtkWidget        *widget,
-                                               GdkEventCrossing *event);
-static gint gtk_range_focus_in                 (GtkWidget        *widget,
-                                               GdkEventFocus    *event);
-static gint gtk_range_focus_out                (GtkWidget        *widget,
-                                               GdkEventFocus    *event);
-static gint gtk_range_scroll_event             (GtkWidget        *widget,
-                                               GdkEventScroll   *event);
-static void gtk_range_style_set                 (GtkWidget       *widget,
-                                                GtkStyle        *previous_style);
-
-static void gtk_real_range_draw_trough         (GtkRange         *range);
-static void gtk_real_range_draw_slider         (GtkRange         *range);
-static gint gtk_real_range_timer               (GtkRange         *range);
-static gint gtk_range_scroll                   (GtkRange         *range,
-                                               gfloat            jump_perc);
-
-static void gtk_range_add_timer                (GtkRange         *range);
-static void gtk_range_remove_timer             (GtkRange         *range);
-
-static void gtk_range_adjustment_changed       (GtkAdjustment    *adjustment,
-                                               gpointer          data);
-static void gtk_range_adjustment_value_changed (GtkAdjustment    *adjustment,
-                                               gpointer          data);
-
-static void gtk_range_trough_hdims             (GtkRange         *range,
-                                               gint             *left,
-                                               gint             *right);
-static void gtk_range_trough_vdims             (GtkRange         *range,
-                                               gint             *top,
-                                               gint             *bottom);
-
-static GtkWidgetClass *parent_class = NULL;
-
-
-GtkType
-gtk_range_get_type (void)
-{
-  static GtkType range_type = 0;
-
-  if (!range_type)
-    {
-      static const GtkTypeInfo range_info =
-      {
-       "GtkRange",
-       sizeof (GtkRange),
-       sizeof (GtkRangeClass),
-       (GtkClassInitFunc) gtk_range_class_init,
-       (GtkObjectInitFunc) gtk_range_init,
-       /* reserved_1 */ NULL,
-        /* reserved_2 */ NULL,
-        (GtkClassInitFunc) NULL,
-      };
-
-      range_type = gtk_type_unique (GTK_TYPE_WIDGET, &range_info);
-    }
+typedef enum {
+  MOUSE_OUTSIDE,
+  MOUSE_STEPPER_A,
+  MOUSE_STEPPER_B,
+  MOUSE_STEPPER_C,
+  MOUSE_STEPPER_D,
+  MOUSE_TROUGH,
+  MOUSE_SLIDER,
+  MOUSE_WIDGET /* inside widget but not in any of the above GUI elements */
+} MouseLocation;
+
+#define GTK_RANGE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_RANGE, GtkRangeLayout))
+
+struct _GtkRangeLayout
+{
+  /* These are in widget->window coordinates */
+  GdkRectangle stepper_a;
+  GdkRectangle stepper_b;
+  GdkRectangle stepper_c;
+  GdkRectangle stepper_d;
+  /* The trough rectangle is the area the thumb can slide in, not the
+   * entire range_rect
+   */
+  GdkRectangle trough;
+  GdkRectangle slider;
+
+  /* Layout-related state */
+  
+  MouseLocation mouse_location;
+  /* last mouse coords we got, or -1 if mouse is outside the range */
+  gint mouse_x;
+  gint mouse_y;
+
+  /* "grabbed" mouse location, OUTSIDE for no grab */
+  MouseLocation grab_location;
+  guint grab_button : 8; /* 0 if none */
+
+  /* Stepper sensitivity */
+  guint lower_sensitive : 1;
+  guint upper_sensitive : 1;
+
+  /* Fill level */
+  guint show_fill_level : 1;
+  guint restrict_to_fill_level : 1;
+
+  GtkSensitivityType lower_sensitivity;
+  GtkSensitivityType upper_sensitivity;
+
+  gdouble fill_level;
+};
 
-  return range_type;
-}
+
+static void gtk_range_set_property   (GObject          *object,
+                                      guint             prop_id,
+                                      const GValue     *value,
+                                      GParamSpec       *pspec);
+static void gtk_range_get_property   (GObject          *object,
+                                      guint             prop_id,
+                                      GValue           *value,
+                                      GParamSpec       *pspec);
+static void gtk_range_destroy        (GtkObject        *object);
+static void gtk_range_size_request   (GtkWidget        *widget,
+                                      GtkRequisition   *requisition);
+static void gtk_range_size_allocate  (GtkWidget        *widget,
+                                      GtkAllocation    *allocation);
+static void gtk_range_realize        (GtkWidget        *widget);
+static void gtk_range_unrealize      (GtkWidget        *widget);
+static void gtk_range_map            (GtkWidget        *widget);
+static void gtk_range_unmap          (GtkWidget        *widget);
+static gint gtk_range_expose         (GtkWidget        *widget,
+                                      GdkEventExpose   *event);
+static gint gtk_range_button_press   (GtkWidget        *widget,
+                                      GdkEventButton   *event);
+static gint gtk_range_button_release (GtkWidget        *widget,
+                                      GdkEventButton   *event);
+static gint gtk_range_motion_notify  (GtkWidget        *widget,
+                                      GdkEventMotion   *event);
+static gint gtk_range_enter_notify   (GtkWidget        *widget,
+                                      GdkEventCrossing *event);
+static gint gtk_range_leave_notify   (GtkWidget        *widget,
+                                      GdkEventCrossing *event);
+static gboolean gtk_range_grab_broken (GtkWidget          *widget,
+                                      GdkEventGrabBroken *event);
+static void gtk_range_grab_notify    (GtkWidget          *widget,
+                                     gboolean            was_grabbed);
+static void gtk_range_state_changed  (GtkWidget          *widget,
+                                     GtkStateType        previous_state);
+static gint gtk_range_scroll_event   (GtkWidget        *widget,
+                                      GdkEventScroll   *event);
+static void gtk_range_style_set      (GtkWidget        *widget,
+                                      GtkStyle         *previous_style);
+static void update_slider_position   (GtkRange        *range,
+                                     gint              mouse_x,
+                                     gint              mouse_y);
+static void stop_scrolling           (GtkRange         *range);
+
+/* Range methods */
+
+static void gtk_range_move_slider              (GtkRange         *range,
+                                                GtkScrollType     scroll);
+
+/* Internals */
+static void          gtk_range_scroll                   (GtkRange      *range,
+                                                         GtkScrollType  scroll);
+static gboolean      gtk_range_update_mouse_location    (GtkRange      *range);
+static void          gtk_range_calc_layout              (GtkRange      *range,
+                                                        gdouble        adjustment_value);
+static void          gtk_range_get_props                (GtkRange      *range,
+                                                         gint          *slider_width,
+                                                         gint          *stepper_size,
+                                                         gint          *focus_width,
+                                                         gint          *trough_border,
+                                                         gint          *stepper_spacing,
+                                                         gboolean      *trough_under_steppers,
+                                                        gint          *arrow_displacement_x,
+                                                        gint          *arrow_displacement_y);
+static void          gtk_range_calc_request             (GtkRange      *range,
+                                                         gint           slider_width,
+                                                         gint           stepper_size,
+                                                         gint           focus_width,
+                                                         gint           trough_border,
+                                                         gint           stepper_spacing,
+                                                         GdkRectangle  *range_rect,
+                                                         GtkBorder     *border,
+                                                         gint          *n_steppers_p,
+                                                         gboolean      *has_steppers_ab,
+                                                         gboolean      *has_steppers_cd,
+                                                         gint          *slider_length_p);
+static void          gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
+                                                         gpointer       data);
+static void          gtk_range_adjustment_changed       (GtkAdjustment *adjustment,
+                                                         gpointer       data);
+static void          gtk_range_add_step_timer           (GtkRange      *range,
+                                                         GtkScrollType  step);
+static void          gtk_range_remove_step_timer        (GtkRange      *range);
+static void          gtk_range_reset_update_timer       (GtkRange      *range);
+static void          gtk_range_remove_update_timer      (GtkRange      *range);
+static GdkRectangle* get_area                           (GtkRange      *range,
+                                                         MouseLocation  location);
+static gboolean      gtk_range_real_change_value        (GtkRange      *range,
+                                                         GtkScrollType  scroll,
+                                                         gdouble        value);
+static void          gtk_range_update_value             (GtkRange      *range);
+static gboolean      gtk_range_key_press                (GtkWidget     *range,
+                                                        GdkEventKey   *event);
+
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_ABSTRACT_TYPE (GtkRange, gtk_range, GTK_TYPE_WIDGET)
 
 static void
 gtk_range_class_init (GtkRangeClass *class)
 {
+  GObjectClass   *gobject_class;
   GtkObjectClass *object_class;
   GtkWidgetClass *widget_class;
 
+  gobject_class = G_OBJECT_CLASS (class);
   object_class = (GtkObjectClass*) class;
   widget_class = (GtkWidgetClass*) class;
 
-  parent_class = gtk_type_class (GTK_TYPE_WIDGET);
-
-  object_class->set_arg = gtk_range_set_arg;
-  object_class->get_arg = gtk_range_get_arg;
+  gobject_class->set_property = gtk_range_set_property;
+  gobject_class->get_property = gtk_range_get_property;
   object_class->destroy = gtk_range_destroy;
 
-  widget_class->draw_focus = gtk_range_draw_focus;
-  widget_class->unrealize = gtk_range_unrealize;
+  widget_class->size_request = gtk_range_size_request;
+  widget_class->size_allocate = gtk_range_size_allocate;
+  widget_class->realize = gtk_range_realize;
+  widget_class->unrealize = gtk_range_unrealize;  
+  widget_class->map = gtk_range_map;
+  widget_class->unmap = gtk_range_unmap;
   widget_class->expose_event = gtk_range_expose;
   widget_class->button_press_event = gtk_range_button_press;
   widget_class->button_release_event = gtk_range_button_release;
   widget_class->motion_notify_event = gtk_range_motion_notify;
   widget_class->scroll_event = gtk_range_scroll_event;
-  widget_class->key_press_event = gtk_range_key_press;
   widget_class->enter_notify_event = gtk_range_enter_notify;
   widget_class->leave_notify_event = gtk_range_leave_notify;
-  widget_class->focus_in_event = gtk_range_focus_in;
-  widget_class->focus_out_event = gtk_range_focus_out;
+  widget_class->grab_broken_event = gtk_range_grab_broken;
+  widget_class->grab_notify = gtk_range_grab_notify;
+  widget_class->state_changed = gtk_range_state_changed;
   widget_class->style_set = gtk_range_style_set;
+  widget_class->key_press_event = gtk_range_key_press;
+
+  class->move_slider = gtk_range_move_slider;
+  class->change_value = gtk_range_real_change_value;
+
+  class->slider_detail = "slider";
+  class->stepper_detail = "stepper";
 
-  class->slider_width = 11;
-  class->stepper_size = 11;
-  class->stepper_slider_spacing = 1;
-  class->min_slider_size = 7;
-  class->trough = 1;
-  class->slider = 2;
-  class->step_forw = 3;
-  class->step_back = 4;
-  class->draw_background = NULL;
-  class->clear_background = NULL;
-  class->draw_trough = gtk_real_range_draw_trough;
-  class->draw_slider = gtk_real_range_draw_slider;
-  class->draw_step_forw = NULL;
-  class->draw_step_back = NULL;
-  class->trough_click = NULL;
-  class->trough_keys = NULL;
-  class->motion = NULL;
-  class->timer = gtk_real_range_timer;
-
-  gtk_object_add_arg_type ("GtkRange::update_policy",
-                          GTK_TYPE_UPDATE_TYPE,
-                          GTK_ARG_READWRITE,
-                          ARG_UPDATE_POLICY);
+  signals[VALUE_CHANGED] =
+    g_signal_new (I_("value_changed"),
+                  G_TYPE_FROM_CLASS (gobject_class),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (GtkRangeClass, value_changed),
+                  NULL, NULL,
+                  _gtk_marshal_NONE__NONE,
+                  G_TYPE_NONE, 0);
+  
+  signals[ADJUST_BOUNDS] =
+    g_signal_new (I_("adjust_bounds"),
+                  G_TYPE_FROM_CLASS (gobject_class),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (GtkRangeClass, adjust_bounds),
+                  NULL, NULL,
+                  _gtk_marshal_VOID__DOUBLE,
+                  G_TYPE_NONE, 1,
+                  G_TYPE_DOUBLE);
+  
+  signals[MOVE_SLIDER] =
+    g_signal_new (I_("move_slider"),
+                  G_TYPE_FROM_CLASS (gobject_class),
+                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                  G_STRUCT_OFFSET (GtkRangeClass, move_slider),
+                  NULL, NULL,
+                  _gtk_marshal_VOID__ENUM,
+                  G_TYPE_NONE, 1,
+                  GTK_TYPE_SCROLL_TYPE);
+
+  /**
+   * GtkRange::change-value:
+   * @range: the range that received the signal.
+   * @scroll: the type of scroll action that was performed.
+   * @value: the new value resulting from the scroll action.
+   * @returns: %TRUE to prevent other handlers from being invoked for the
+   * signal.  %FALSE to propagate the signal further.
+   *
+   * The ::change-value signal is emitted when a scroll action is
+   * performed on a range.  It allows an application to determine the
+   * type of scroll event that occurred and the resultant new value.
+   * The application can handle the event itself and return %TRUE to
+   * prevent further processing.  Or, by returning %FALSE, it can pass
+   * the event to other handlers until the default GTK+ handler is
+   * reached.
+   *
+   * The value parameter is unrounded.  An application that overrides
+   * the ::change-value signal is responsible for clamping the value to
+   * the desired number of decimal digits; the default GTK+ handler 
+   * clamps the value based on @range->round_digits.
+   *
+   * It is not possible to use delayed update policies in an overridden
+   * ::change-value handler.
+   *
+   * Since: 2.6
+   */
+  signals[CHANGE_VALUE] =
+    g_signal_new (I_("change_value"),
+                  G_TYPE_FROM_CLASS (gobject_class),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (GtkRangeClass, change_value),
+                  _gtk_boolean_handled_accumulator, NULL,
+                  _gtk_marshal_BOOLEAN__ENUM_DOUBLE,
+                  G_TYPE_BOOLEAN, 2,
+                  GTK_TYPE_SCROLL_TYPE,
+                  G_TYPE_DOUBLE);
+  
+  g_object_class_install_property (gobject_class,
+                                   PROP_UPDATE_POLICY,
+                                   g_param_spec_enum ("update-policy",
+                                                     P_("Update policy"),
+                                                     P_("How the range should be updated on the screen"),
+                                                     GTK_TYPE_UPDATE_TYPE,
+                                                     GTK_UPDATE_CONTINUOUS,
+                                                     GTK_PARAM_READWRITE));
+  
+  g_object_class_install_property (gobject_class,
+                                   PROP_ADJUSTMENT,
+                                   g_param_spec_object ("adjustment",
+                                                       P_("Adjustment"),
+                                                       P_("The GtkAdjustment that contains the current value of this range object"),
+                                                        GTK_TYPE_ADJUSTMENT,
+                                                        GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_INVERTED,
+                                   g_param_spec_boolean ("inverted",
+                                                       P_("Inverted"),
+                                                       P_("Invert direction slider moves to increase range value"),
+                                                         FALSE,
+                                                         GTK_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_LOWER_STEPPER_SENSITIVITY,
+                                   g_param_spec_enum ("lower-stepper-sensitivity",
+                                                     P_("Lower stepper sensitivity"),
+                                                     P_("The sensitivity policy for the stepper that points to the adjustment's lower side"),
+                                                     GTK_TYPE_SENSITIVITY_TYPE,
+                                                     GTK_SENSITIVITY_AUTO,
+                                                     GTK_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class,
+                                   PROP_UPPER_STEPPER_SENSITIVITY,
+                                   g_param_spec_enum ("upper-stepper-sensitivity",
+                                                     P_("Upper stepper sensitivity"),
+                                                     P_("The sensitivity policy for the stepper that points to the adjustment's upper side"),
+                                                     GTK_TYPE_SENSITIVITY_TYPE,
+                                                     GTK_SENSITIVITY_AUTO,
+                                                     GTK_PARAM_READWRITE));
+
+  /**
+   * GtkRange:show-fill-level:
+   *
+   * The show-fill-level property controls wether fill level indicator
+   * graphics are displayed on the trough. See
+   * gtk_range_set_show_fill_level().
+   *
+   * Since: 2.12
+   **/
+  g_object_class_install_property (gobject_class,
+                                   PROP_SHOW_FILL_LEVEL,
+                                   g_param_spec_boolean ("show-fill-level",
+                                                         P_("Show Fill Level"),
+                                                         P_("Whether to display a fill level indicator graphics on trough."),
+                                                         FALSE,
+                                                         GTK_PARAM_READWRITE));
+
+  /**
+   * GtkRange:restrict-to-fill-level:
+   *
+   * The restrict-to-fill-level proeprty controls whether slider
+   * movement is restricted to an upper boundary set by the
+   * fill-level. See gtk_range_set_restrict_to_fill_level().
+   *
+   * Since: 2.12
+   **/
+  g_object_class_install_property (gobject_class,
+                                   PROP_RESTRICT_TO_FILL_LEVEL,
+                                   g_param_spec_boolean ("restrict-to-fill-level",
+                                                         P_("Restrict to Fill Level"),
+                                                         P_("Whether to restrict the upper boundary to the fill level."),
+                                                         TRUE,
+                                                         GTK_PARAM_READWRITE));
+
+  /**
+   * GtkRange:fill-level:
+   *
+   * The fill level (e.g. prebuffering of a network stream).
+   * See gtk_range_set_fill_level().
+   *
+   * Since: 2.12
+   **/
+  g_object_class_install_property (gobject_class,
+                                   PROP_FILL_LEVEL,
+                                   g_param_spec_double ("fill-level",
+                                                       P_("Fill Level"),
+                                                       P_("The fill level."),
+                                                       -G_MAXDOUBLE,
+                                                       G_MAXDOUBLE,
+                                                        G_MAXDOUBLE,
+                                                        GTK_PARAM_READWRITE));
+
+  gtk_widget_class_install_style_property (widget_class,
+                                          g_param_spec_int ("slider-width",
+                                                            P_("Slider Width"),
+                                                            P_("Width of scrollbar or scale thumb"),
+                                                            0,
+                                                            G_MAXINT,
+                                                            14,
+                                                            GTK_PARAM_READABLE));
+  gtk_widget_class_install_style_property (widget_class,
+                                          g_param_spec_int ("trough-border",
+                                                             P_("Trough Border"),
+                                                             P_("Spacing between thumb/steppers and outer trough bevel"),
+                                                             0,
+                                                             G_MAXINT,
+                                                             1,
+                                                             GTK_PARAM_READABLE));
+  gtk_widget_class_install_style_property (widget_class,
+                                          g_param_spec_int ("stepper-size",
+                                                            P_("Stepper Size"),
+                                                            P_("Length of step buttons at ends"),
+                                                            0,
+                                                            G_MAXINT,
+                                                            14,
+                                                            GTK_PARAM_READABLE));
+  /**
+   * GtkRange:stepper-spacing:
+   *
+   * The spacing between the stepper buttons and thumb. Note that
+   * setting this value to anything > 0 will automatically set the
+   * trough-under-steppers style property to TRUE as well. Also,
+   * stepper-spacing won't have any effect if there are no steppers.
+   */
+  gtk_widget_class_install_style_property (widget_class,
+                                          g_param_spec_int ("stepper-spacing",
+                                                            P_("Stepper Spacing"),
+                                                            P_("Spacing between step buttons and thumb"),
+                                                             0,
+                                                            G_MAXINT,
+                                                            0,
+                                                            GTK_PARAM_READABLE));
+  gtk_widget_class_install_style_property (widget_class,
+                                          g_param_spec_int ("arrow-displacement-x",
+                                                            P_("Arrow X Displacement"),
+                                                            P_("How far in the x direction to move the arrow when the button is depressed"),
+                                                            G_MININT,
+                                                            G_MAXINT,
+                                                            0,
+                                                            GTK_PARAM_READABLE));
+  gtk_widget_class_install_style_property (widget_class,
+                                          g_param_spec_int ("arrow-displacement-y",
+                                                            P_("Arrow Y Displacement"),
+                                                            P_("How far in the y direction to move the arrow when the button is depressed"),
+                                                            G_MININT,
+                                                            G_MAXINT,
+                                                            0,
+                                                            GTK_PARAM_READABLE));
+
+  gtk_widget_class_install_style_property (widget_class,
+                                          g_param_spec_boolean ("activate-slider",
+                                                                 P_("Draw slider ACTIVE during drag"),
+                                                                P_("With this option set to TRUE, sliders will be drawn ACTIVE and with shadow IN while they are dragged"),
+                                                                FALSE,
+                                                                GTK_PARAM_READABLE));
+
+  /**
+   * GtkRange:trough-side-details:
+   *
+   * Since: 2.10
+   */
+  gtk_widget_class_install_style_property (widget_class,
+                                           g_param_spec_boolean ("trough-side-details",
+                                                                 P_("Trough Side Details"),
+                                                                 P_("When TRUE, the parts of the trough on the two sides of the slider are drawn with different details"),
+                                                                 FALSE,
+                                                                 GTK_PARAM_READABLE));
+
+  /**
+   * GtkRange:trough-under-steppers:
+   *
+   * Whether to draw the trough across the full length of the range or
+   * to exclude the steppers and their spacing. Note that setting the
+   * stepper-spacing style property to any value > 0 will
+   * automatically enable trough-under-steppers too.
+   *
+   * Since: 2.10
+   */
+  gtk_widget_class_install_style_property (widget_class,
+                                           g_param_spec_boolean ("trough-under-steppers",
+                                                                 P_("Trough Under Steppers"),
+                                                                 P_("Whether to draw trought for full length of range or exclude the steppers and spacing"),
+                                                                 TRUE,
+                                                                 GTK_PARAM_READABLE));
+
+  g_type_class_add_private (class, sizeof (GtkRangeLayout));
 }
 
 static void
-gtk_range_set_arg (GtkObject      *object,
-                  GtkArg         *arg,
-                  guint           arg_id)
+gtk_range_set_property (GObject      *object,
+                       guint         prop_id,
+                       const GValue *value,
+                       GParamSpec   *pspec)
 {
   GtkRange *range;
 
   range = GTK_RANGE (object);
 
-  switch (arg_id)
+  switch (prop_id)
     {
-    case ARG_UPDATE_POLICY:
-      gtk_range_set_update_policy (range, GTK_VALUE_ENUM (*arg));
+    case PROP_UPDATE_POLICY:
+      gtk_range_set_update_policy (range, g_value_get_enum (value));
+      break;
+    case PROP_ADJUSTMENT:
+      gtk_range_set_adjustment (range, g_value_get_object (value));
+      break;
+    case PROP_INVERTED:
+      gtk_range_set_inverted (range, g_value_get_boolean (value));
+      break;
+    case PROP_LOWER_STEPPER_SENSITIVITY:
+      gtk_range_set_lower_stepper_sensitivity (range, g_value_get_enum (value));
+      break;
+    case PROP_UPPER_STEPPER_SENSITIVITY:
+      gtk_range_set_upper_stepper_sensitivity (range, g_value_get_enum (value));
+      break;
+    case PROP_SHOW_FILL_LEVEL:
+      gtk_range_set_show_fill_level (range, g_value_get_boolean (value));
+      break;
+    case PROP_RESTRICT_TO_FILL_LEVEL:
+      gtk_range_set_restrict_to_fill_level (range, g_value_get_boolean (value));
+      break;
+    case PROP_FILL_LEVEL:
+      gtk_range_set_fill_level (range, g_value_get_double (value));
       break;
     default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
     }
 }
 
 static void
-gtk_range_get_arg (GtkObject      *object,
-                  GtkArg         *arg,
-                  guint           arg_id)
+gtk_range_get_property (GObject      *object,
+                       guint         prop_id,
+                       GValue       *value,
+                       GParamSpec   *pspec)
 {
   GtkRange *range;
 
   range = GTK_RANGE (object);
 
-  switch (arg_id)
+  switch (prop_id)
     {
-    case ARG_UPDATE_POLICY:
-      GTK_VALUE_ENUM (*arg) = range->policy;
+    case PROP_UPDATE_POLICY:
+      g_value_set_enum (value, range->update_policy);
+      break;
+    case PROP_ADJUSTMENT:
+      g_value_set_object (value, range->adjustment);
+      break;
+    case PROP_INVERTED:
+      g_value_set_boolean (value, range->inverted);
+      break;
+    case PROP_LOWER_STEPPER_SENSITIVITY:
+      g_value_set_enum (value, gtk_range_get_lower_stepper_sensitivity (range));
+      break;
+    case PROP_UPPER_STEPPER_SENSITIVITY:
+      g_value_set_enum (value, gtk_range_get_upper_stepper_sensitivity (range));
+      break;
+    case PROP_SHOW_FILL_LEVEL:
+      g_value_set_boolean (value, gtk_range_get_show_fill_level (range));
+      break;
+    case PROP_RESTRICT_TO_FILL_LEVEL:
+      g_value_set_boolean (value, gtk_range_get_restrict_to_fill_level (range));
+      break;
+    case PROP_FILL_LEVEL:
+      g_value_set_double (value, gtk_range_get_fill_level (range));
       break;
     default:
-      arg->type = GTK_TYPE_INVALID;
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
     }
 }
@@ -219,33 +595,49 @@ gtk_range_get_arg (GtkObject      *object,
 static void
 gtk_range_init (GtkRange *range)
 {
-  range->trough = NULL;
-  range->slider = NULL;
-  range->step_forw = NULL;
-  range->step_back = NULL;
-
-  range->x_click_point = 0;
-  range->y_click_point = 0;
-  range->button = 0;
-  range->digits = -1;
-  range->policy = GTK_UPDATE_CONTINUOUS;
-  range->scroll_type = GTK_SCROLL_NONE;
-  range->in_child = 0;
-  range->click_child = 0;
-  range->need_timer = FALSE;
-  range->timer = 0;
-  range->flippable = 0;
-  range->old_value = 0.0;
-  range->old_lower = 0.0;
-  range->old_upper = 0.0;
-  range->old_page_size = 0.0;
+  GTK_WIDGET_SET_FLAGS (range, GTK_NO_WINDOW);
+
   range->adjustment = NULL;
+  range->update_policy = GTK_UPDATE_CONTINUOUS;
+  range->inverted = FALSE;
+  range->flippable = FALSE;
+  range->min_slider_size = 1;
+  range->has_stepper_a = FALSE;
+  range->has_stepper_b = FALSE;
+  range->has_stepper_c = FALSE;
+  range->has_stepper_d = FALSE;
+  range->need_recalc = TRUE;
+  range->round_digits = -1;
+  range->layout = GTK_RANGE_GET_PRIVATE (range);
+  range->layout->mouse_location = MOUSE_OUTSIDE;
+  range->layout->mouse_x = -1;
+  range->layout->mouse_y = -1;
+  range->layout->grab_location = MOUSE_OUTSIDE;
+  range->layout->grab_button = 0;
+  range->layout->lower_sensitivity = GTK_SENSITIVITY_AUTO;
+  range->layout->upper_sensitivity = GTK_SENSITIVITY_AUTO;
+  range->layout->lower_sensitive = TRUE;
+  range->layout->upper_sensitive = TRUE;
+  range->layout->show_fill_level = FALSE;
+  range->layout->restrict_to_fill_level = TRUE;
+  range->layout->fill_level = G_MAXDOUBLE;
+  range->timer = NULL;  
 }
 
+/**
+ * gtk_range_get_adjustment:
+ * @range: a #GtkRange
+ * 
+ * Get the #GtkAdjustment which is the "model" object for #GtkRange.
+ * See gtk_range_set_adjustment() for details.
+ * The return value does not have a reference added, so should not
+ * be unreferenced.
+ * 
+ * Return value: a #GtkAdjustment
+ **/
 GtkAdjustment*
 gtk_range_get_adjustment (GtkRange *range)
 {
-  g_return_val_if_fail (range != NULL, NULL);
   g_return_val_if_fail (GTK_IS_RANGE (range), NULL);
 
   if (!range->adjustment)
@@ -254,21 +646,68 @@ gtk_range_get_adjustment (GtkRange *range)
   return range->adjustment;
 }
 
+/**
+ * gtk_range_set_update_policy:
+ * @range: a #GtkRange
+ * @policy: update policy
+ *
+ * Sets the update policy for the range. #GTK_UPDATE_CONTINUOUS means that
+ * anytime the range slider is moved, the range value will change and the
+ * value_changed signal will be emitted. #GTK_UPDATE_DELAYED means that
+ * the value will be updated after a brief timeout where no slider motion
+ * occurs, so updates are spaced by a short time rather than
+ * continuous. #GTK_UPDATE_DISCONTINUOUS means that the value will only
+ * be updated when the user releases the button and ends the slider
+ * drag operation.
+ * 
+ **/
 void
 gtk_range_set_update_policy (GtkRange      *range,
                             GtkUpdateType  policy)
 {
-  g_return_if_fail (range != NULL);
   g_return_if_fail (GTK_IS_RANGE (range));
 
-  range->policy = policy;
+  if (range->update_policy != policy)
+    {
+      range->update_policy = policy;
+      g_object_notify (G_OBJECT (range), "update-policy");
+    }
+}
+
+/**
+ * gtk_range_get_update_policy:
+ * @range: a #GtkRange
+ *
+ * Gets the update policy of @range. See gtk_range_set_update_policy().
+ *
+ * Return value: the current update policy
+ **/
+GtkUpdateType
+gtk_range_get_update_policy (GtkRange *range)
+{
+  g_return_val_if_fail (GTK_IS_RANGE (range), GTK_UPDATE_CONTINUOUS);
+
+  return range->update_policy;
 }
 
+/**
+ * gtk_range_set_adjustment:
+ * @range: a #GtkRange
+ * @adjustment: a #GtkAdjustment
+ *
+ * Sets the adjustment to be used as the "model" object for this range
+ * widget. The adjustment indicates the current range value, the
+ * minimum and maximum range values, the step/page increments used
+ * for keybindings and scrolling, and the page size. The page size
+ * is normally 0 for #GtkScale and nonzero for #GtkScrollbar, and
+ * indicates the size of the visible area of the widget being scrolled.
+ * The page size affects the size of the scrollbar slider.
+ * 
+ **/
 void
 gtk_range_set_adjustment (GtkRange      *range,
                          GtkAdjustment *adjustment)
 {
-  g_return_if_fail (range != NULL);
   g_return_if_fail (GTK_IS_RANGE (range));
   
   if (!adjustment)
@@ -280,31 +719,41 @@ gtk_range_set_adjustment (GtkRange      *range,
     {
       if (range->adjustment)
        {
-         gtk_signal_disconnect_by_data (GTK_OBJECT (range->adjustment),
-                                        (gpointer) range);
-         gtk_object_unref (GTK_OBJECT (range->adjustment));
+         g_signal_handlers_disconnect_by_func (range->adjustment,
+                                               gtk_range_adjustment_changed,
+                                               range);
+         g_signal_handlers_disconnect_by_func (range->adjustment,
+                                               gtk_range_adjustment_value_changed,
+                                               range);
+         g_object_unref (range->adjustment);
        }
 
       range->adjustment = adjustment;
-      gtk_object_ref (GTK_OBJECT (adjustment));
-      gtk_object_sink (GTK_OBJECT (adjustment));
-      
-      gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
-                         (GtkSignalFunc) gtk_range_adjustment_changed,
-                         (gpointer) range);
-      gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
-                         (GtkSignalFunc) gtk_range_adjustment_value_changed,
-                         (gpointer) range);
+      g_object_ref_sink (adjustment);
       
-      range->old_value = adjustment->value;
-      range->old_lower = adjustment->lower;
-      range->old_upper = adjustment->upper;
-      range->old_page_size = adjustment->page_size;
+      g_signal_connect (adjustment, "changed",
+                       G_CALLBACK (gtk_range_adjustment_changed),
+                       range);
+      g_signal_connect (adjustment, "value_changed",
+                       G_CALLBACK (gtk_range_adjustment_value_changed),
+                       range);
       
-      gtk_range_adjustment_changed (adjustment, (gpointer) range);
+      gtk_range_adjustment_changed (adjustment, range);
+      g_object_notify (G_OBJECT (range), "adjustment");
     }
 }
 
+/**
+ * gtk_range_set_inverted:
+ * @range: a #GtkRange
+ * @setting: %TRUE to invert the range
+ *
+ * Ranges normally move from lower to higher values as the
+ * slider moves from top to bottom or left to right. Inverted
+ * ranges have higher values at the top or on the right rather than
+ * on the bottom or left.
+ * 
+ **/
 void
 gtk_range_set_inverted (GtkRange *range,
                         gboolean  setting)
@@ -316,10 +765,19 @@ gtk_range_set_inverted (GtkRange *range,
   if (setting != range->inverted)
     {
       range->inverted = setting;
+      g_object_notify (G_OBJECT (range), "inverted");
       gtk_widget_queue_resize (GTK_WIDGET (range));
     }
 }
 
+/**
+ * gtk_range_get_inverted:
+ * @range: a #GtkRange
+ * 
+ * Gets the value set by gtk_range_set_inverted().
+ * 
+ * Return value: %TRUE if the range is inverted
+ **/
 gboolean
 gtk_range_get_inverted (GtkRange *range)
 {
@@ -328,727 +786,1346 @@ gtk_range_get_inverted (GtkRange *range)
   return range->inverted;
 }
 
+/**
+ * gtk_range_set_lower_stepper_sensitivity:
+ * @range:       a #GtkRange
+ * @sensitivity: the lower stepper's sensitivity policy.
+ *
+ * Sets the sensitivity policy for the stepper that points to the
+ * 'lower' end of the GtkRange's adjustment.
+ *
+ * Since: 2.10
+ **/
 void
-gtk_range_draw_background (GtkRange *range)
+gtk_range_set_lower_stepper_sensitivity (GtkRange           *range,
+                                         GtkSensitivityType  sensitivity)
 {
-  g_return_if_fail (range != NULL);
   g_return_if_fail (GTK_IS_RANGE (range));
 
-  if (range->trough && RANGE_CLASS (range)->draw_background)
-    (* RANGE_CLASS (range)->draw_background) (range);
+  if (range->layout->lower_sensitivity != sensitivity)
+    {
+      range->layout->lower_sensitivity = sensitivity;
+
+      range->need_recalc = TRUE;
+      gtk_range_calc_layout (range, range->adjustment->value);
+      gtk_widget_queue_draw (GTK_WIDGET (range));
+
+      g_object_notify (G_OBJECT (range), "lower-stepper-sensitivity");
+    }
 }
 
-void
-gtk_range_clear_background (GtkRange *range)
+/**
+ * gtk_range_get_lower_stepper_sensitivity:
+ * @range: a #GtkRange
+ *
+ * Gets the sensitivity policy for the stepper that points to the
+ * 'lower' end of the GtkRange's adjustment.
+ *
+ * Return value: The lower stepper's sensitivity policy.
+ *
+ * Since: 2.10
+ **/
+GtkSensitivityType
+gtk_range_get_lower_stepper_sensitivity (GtkRange *range)
 {
-  g_return_if_fail (range != NULL);
-  g_return_if_fail (GTK_IS_RANGE (range));
+  g_return_val_if_fail (GTK_IS_RANGE (range), GTK_SENSITIVITY_AUTO);
 
-  if (range->trough && RANGE_CLASS (range)->clear_background)
-    (* RANGE_CLASS (range)->clear_background) (range);
+  return range->layout->lower_sensitivity;
 }
 
+/**
+ * gtk_range_set_upper_stepper_sensitivity:
+ * @range:       a #GtkRange
+ * @sensitivity: the upper stepper's sensitivity policy.
+ *
+ * Sets the sensitivity policy for the stepper that points to the
+ * 'upper' end of the GtkRange's adjustment.
+ *
+ * Since: 2.10
+ **/
 void
-gtk_range_draw_trough (GtkRange *range)
+gtk_range_set_upper_stepper_sensitivity (GtkRange           *range,
+                                         GtkSensitivityType  sensitivity)
 {
-  g_return_if_fail (range != NULL);
   g_return_if_fail (GTK_IS_RANGE (range));
 
-  if (range->trough && RANGE_CLASS (range)->draw_trough)
-    (* RANGE_CLASS (range)->draw_trough) (range);
+  if (range->layout->upper_sensitivity != sensitivity)
+    {
+      range->layout->upper_sensitivity = sensitivity;
+
+      range->need_recalc = TRUE;
+      gtk_range_calc_layout (range, range->adjustment->value);
+      gtk_widget_queue_draw (GTK_WIDGET (range));
+
+      g_object_notify (G_OBJECT (range), "upper-stepper-sensitivity");
+    }
 }
 
-void
-gtk_range_draw_slider (GtkRange *range)
+/**
+ * gtk_range_get_upper_stepper_sensitivity:
+ * @range: a #GtkRange
+ *
+ * Gets the sensitivity policy for the stepper that points to the
+ * 'upper' end of the GtkRange's adjustment.
+ *
+ * Return value: The upper stepper's sensitivity policy.
+ *
+ * Since: 2.10
+ **/
+GtkSensitivityType
+gtk_range_get_upper_stepper_sensitivity (GtkRange *range)
 {
-  g_return_if_fail (range != NULL);
-  g_return_if_fail (GTK_IS_RANGE (range));
+  g_return_val_if_fail (GTK_IS_RANGE (range), GTK_SENSITIVITY_AUTO);
 
-  if (range->slider && RANGE_CLASS (range)->draw_slider)
-    (* RANGE_CLASS (range)->draw_slider) (range);
+  return range->layout->upper_sensitivity;
 }
 
+/**
+ * gtk_range_set_increments:
+ * @range: a #GtkRange
+ * @step: step size
+ * @page: page size
+ *
+ * Sets the step and page sizes for the range.
+ * The step size is used when the user clicks the #GtkScrollbar
+ * arrows or moves #GtkScale via arrow keys. The page size
+ * is used for example when moving via Page Up or Page Down keys.
+ * 
+ **/
 void
-gtk_range_draw_step_forw (GtkRange *range)
+gtk_range_set_increments (GtkRange *range,
+                          gdouble   step,
+                          gdouble   page)
 {
-  g_return_if_fail (range != NULL);
   g_return_if_fail (GTK_IS_RANGE (range));
 
-  if (range->step_forw && RANGE_CLASS (range)->draw_step_forw)
-    (* RANGE_CLASS (range)->draw_step_forw) (range);
+  range->adjustment->step_increment = step;
+  range->adjustment->page_increment = page;
+
+  gtk_adjustment_changed (range->adjustment);
 }
 
+/**
+ * gtk_range_set_range:
+ * @range: a #GtkRange
+ * @min: minimum range value
+ * @max: maximum range value
+ * 
+ * Sets the allowable values in the #GtkRange, and clamps the range
+ * value to be between @min and @max. (If the range has a non-zero
+ * page size, it is clamped between @min and @max - page-size.)
+ **/
 void
-gtk_range_draw_step_back (GtkRange *range)
+gtk_range_set_range (GtkRange *range,
+                     gdouble   min,
+                     gdouble   max)
 {
-  g_return_if_fail (range != NULL);
+  gdouble value;
+  
   g_return_if_fail (GTK_IS_RANGE (range));
+  g_return_if_fail (min < max);
+  
+  range->adjustment->lower = min;
+  range->adjustment->upper = max;
+
+  value = range->adjustment->value;
+
+  if (range->layout->restrict_to_fill_level)
+    value = MIN (value, MAX (range->adjustment->lower,
+                             range->layout->fill_level));
+
+  value = CLAMP (value, range->adjustment->lower,
+                 (range->adjustment->upper - range->adjustment->page_size));
 
-  if (range->step_back && RANGE_CLASS (range)->draw_step_back)
-    (* RANGE_CLASS (range)->draw_step_back) (range);
+  gtk_adjustment_set_value (range->adjustment, value);
+  gtk_adjustment_changed (range->adjustment);
 }
 
+/**
+ * gtk_range_set_value:
+ * @range: a #GtkRange
+ * @value: new value of the range
+ *
+ * Sets the current value of the range; if the value is outside the
+ * minimum or maximum range values, it will be clamped to fit inside
+ * them. The range emits the "value_changed" signal if the value
+ * changes.
+ * 
+ **/
 void
-gtk_range_slider_update (GtkRange *range)
+gtk_range_set_value (GtkRange *range,
+                     gdouble   value)
 {
-  g_return_if_fail (range != NULL);
   g_return_if_fail (GTK_IS_RANGE (range));
 
-  if (RANGE_CLASS (range)->slider_update)
-    (* RANGE_CLASS (range)->slider_update) (range);
+  if (range->layout->restrict_to_fill_level)
+    value = MIN (value, MAX (range->adjustment->lower,
+                             range->layout->fill_level));
+
+  value = CLAMP (value, range->adjustment->lower,
+                 (range->adjustment->upper - range->adjustment->page_size));
+
+  gtk_adjustment_set_value (range->adjustment, value);
 }
 
-gint
-gtk_range_trough_click (GtkRange *range,
-                       gint      x,
-                       gint      y,
-                       gfloat   *jump_perc)
+/**
+ * gtk_range_get_value:
+ * @range: a #GtkRange
+ * 
+ * Gets the current value of the range.
+ * 
+ * Return value: current value of the range.
+ **/
+gdouble
+gtk_range_get_value (GtkRange *range)
 {
-  g_return_val_if_fail (range != NULL, GTK_TROUGH_NONE);
-  g_return_val_if_fail (GTK_IS_RANGE (range), GTK_TROUGH_NONE);
-
-  if (RANGE_CLASS (range)->trough_click)
-    return (* RANGE_CLASS (range)->trough_click) (range, x, y, jump_perc);
+  g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
 
-  return GTK_TROUGH_NONE;
+  return range->adjustment->value;
 }
 
-static GdkRegion *
-get_window_region (GdkWindow *window)
+/**
+ * gtk_range_set_show_fill_level:
+ * @range:           A #GtkRange
+ * @show_fill_level: Whether a fill level indicator graphics is shown.
+ *
+ * Sets whether a graphical fill level is show on the trough. See
+ * gtk_range_set_fill_level() for a general description of the fill
+ * level concept.
+ *
+ * Since: 2.12
+ **/
+void
+gtk_range_set_show_fill_level (GtkRange *range,
+                               gboolean  show_fill_level)
 {
-  GdkRectangle rect;
+  g_return_if_fail (GTK_IS_RANGE (range));
 
-  gdk_window_get_position (window, &rect.x, &rect.y);
-  gdk_window_get_size (window, &rect.width, &rect.height);
+  show_fill_level = show_fill_level ? TRUE : FALSE;
 
-  return gdk_region_rectangle (&rect);
+  if (show_fill_level != range->layout->show_fill_level)
+    {
+      range->layout->show_fill_level = show_fill_level;
+      g_object_notify (G_OBJECT (range), "show-fill-level");
+      gtk_widget_queue_draw (GTK_WIDGET (range));
+    }
 }
 
-static void
-move_and_update_window (GdkWindow *window, gint x, gint y)
-{
-  GdkRegion *old_region;
-  GdkRegion *new_region;
-  GdkWindow *parent = gdk_window_get_parent (window);
-
-  old_region = get_window_region (window);
-  gdk_window_move (window, x, y);
-  new_region = get_window_region (window);
-                  
-  gdk_region_subtract (old_region, new_region);
-  gdk_window_invalidate_region (parent, old_region, TRUE);
-  gdk_region_destroy (old_region);
-  gdk_region_destroy (new_region);
-  
-  gdk_window_process_updates (parent, TRUE);
-}
+/**
+ * gtk_range_get_show_fill_level:
+ * @range: A #GtkRange
+ *
+ * Return value: Whether GtkRange displays a fill level graphics.
+ *
+ * Since: 2.12
+ **/
+gboolean
+gtk_range_get_show_fill_level (GtkRange *range)
+{
+  g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
 
-static gboolean
-should_invert (GtkRange *range,
-               gboolean  horizontal)
-{  
-  if (horizontal)
-    return
-      (range->inverted && !range->flippable) ||
-      (range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) ||
-      (!range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL);
-  else
-    return range->inverted;
+  return range->layout->show_fill_level;
 }
 
+/**
+ * gtk_range_set_restrict_to_fill_level:
+ * @range:                  A #GtkRange
+ * @restrict_to_fill_level: Whether the fill level restricts slider movement.
+ *
+ * Sets whether the slider is restricted to the fill level. See
+ * gtk_range_set_fill_level() for a general description of the fill
+ * level concept.
+ *
+ * Since: 2.12
+ **/
 void
-gtk_range_default_hslider_update (GtkRange *range)
+gtk_range_set_restrict_to_fill_level (GtkRange *range,
+                                      gboolean  restrict_to_fill_level)
 {
-  gint left;
-  gint right;
-  gint x;
-
-  g_return_if_fail (range != NULL);
   g_return_if_fail (GTK_IS_RANGE (range));
 
-  if (GTK_WIDGET_REALIZED (range))
-    {
-      gtk_range_trough_hdims (range, &left, &right);
-      x = left;
+  restrict_to_fill_level = restrict_to_fill_level ? TRUE : FALSE;
 
-      if (range->adjustment->value < range->adjustment->lower)
-       {
-         range->adjustment->value = range->adjustment->lower;
-         gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed");
-       }
-      else if (range->adjustment->value > range->adjustment->upper)
-       {
-         range->adjustment->value = range->adjustment->upper;
-         gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed");
-       }
+  if (restrict_to_fill_level != range->layout->restrict_to_fill_level)
+    {
+      range->layout->restrict_to_fill_level = restrict_to_fill_level;
+      g_object_notify (G_OBJECT (range), "restrict-to-fill-level");
 
-      if (range->adjustment->lower != (range->adjustment->upper - range->adjustment->page_size))
-       x += ((right - left) * (range->adjustment->value - range->adjustment->lower) /
-             (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
+      gtk_range_set_value (range, gtk_range_get_value (range));
+    }
+}
 
-      if (x < left)
-       x = left;
-      else if (x > right)
-       x = right;
+/**
+ * gtk_range_get_restrict_to_fill_level:
+ * @range: A #GtkRange
+ *
+ * Return value: Whether GtkRange is restricted to the fill level.
+ *
+ * Since: 2.12
+ **/
+gboolean
+gtk_range_get_restrict_to_fill_level (GtkRange *range)
+{
+  g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
 
-      if (should_invert (range, TRUE))
-       x = right - (x - left);
-      
-      move_and_update_window (range->slider, x, GTK_WIDGET (range)->style->ythickness);
-    }
+  return range->layout->restrict_to_fill_level;
 }
 
+/**
+ * gtk_range_set_fill_level:
+ * @range:   A #GtkRange
+ * @positon: The new position of the fill level indicator
+ *
+ * Set the new position of the fill level indicator.
+ *
+ * The "fill level" is probably best described by its most prominent
+ * use case, which is an indicator for the amount of pre-buffering in
+ * a streaming media player. In that use case, the value of the range
+ * would indicate the current play position, and the fill level would
+ * be the position up to which the file/stream has been downloaded.
+ *
+ * This amount of prebuffering can be displayed on the range's trough
+ * and is themeable separately from the trough. To enable fill level
+ * display, use gtk_range_set_show_fill_level(). The range defaults
+ * to not showing the fill level.
+ *
+ * Additionally, it's possible to restrict the range's slider position
+ * to values which are smaller than the fill level. This is controller
+ * by gtk_range_set_restrict_to_fill_level() and is by default
+ * enabled.
+ *
+ * Since: 2.12
+ **/
 void
-gtk_range_default_vslider_update (GtkRange *range)
+gtk_range_set_fill_level (GtkRange *range,
+                          gdouble   fill_level)
 {
-  gint top;
-  gint bottom;
-  gint y;
-
-  g_return_if_fail (range != NULL);
   g_return_if_fail (GTK_IS_RANGE (range));
 
-  if (GTK_WIDGET_REALIZED (range))
+  if (fill_level != range->layout->fill_level)
     {
-      gtk_range_trough_vdims (range, &top, &bottom);
-      y = top;
-
-      if (range->adjustment->value < range->adjustment->lower)
-       {
-         range->adjustment->value = range->adjustment->lower;
-         gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed");
-       }
-      else if (range->adjustment->value > range->adjustment->upper)
-       {
-         range->adjustment->value = range->adjustment->upper;
-         gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed");
-       }
-
-      if (range->adjustment->lower != (range->adjustment->upper - range->adjustment->page_size))
-       y += ((bottom - top) * (range->adjustment->value - range->adjustment->lower) /
-             (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
+      range->layout->fill_level = fill_level;
+      g_object_notify (G_OBJECT (range), "fill-level");
 
-      if (y < top)
-       y = top;
-      else if (y > bottom)
-       y = bottom;
+      if (range->layout->show_fill_level)
+        gtk_widget_queue_draw (GTK_WIDGET (range));
 
-      if (should_invert (range, FALSE))
-       y = bottom - (y - top);
-      
-      move_and_update_window (range->slider, GTK_WIDGET (range)->style->xthickness, y);
+      if (range->layout->restrict_to_fill_level)
+        gtk_range_set_value (range, gtk_range_get_value (range));
     }
 }
 
-gint
-gtk_range_default_htrough_click (GtkRange *range,
-                                gint      x,
-                                gint      y,
-                                gfloat   *jump_perc)
+/**
+ * gtk_range_get_fill_level:
+ * @range : A #GtkRange
+ *
+ * Return value: The current position of the fill level indicator.
+ *
+ * Since: 2.12
+ **/
+gdouble
+gtk_range_get_fill_level (GtkRange *range)
 {
-  gint ythickness;
-  gint trough_width;
-  gint trough_height;
-  gint slider_x;
-  gint slider_length;
-  gint left, right;
+  g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
 
-  g_return_val_if_fail (range != NULL, GTK_TROUGH_NONE);
-  g_return_val_if_fail (GTK_IS_RANGE (range), GTK_TROUGH_NONE);
-
-  ythickness = GTK_WIDGET (range)->style->ythickness;
+  return range->layout->fill_level;
+}
 
-  gtk_range_trough_hdims (range, &left, &right);
-  gdk_window_get_size (range->slider, &slider_length, NULL);
-  right += slider_length;
+static gboolean
+should_invert (GtkRange *range)
+{  
+  if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
+    return
+      (range->inverted && !range->flippable) ||
+      (range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) ||
+      (!range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL);
+  else
+    return range->inverted;
+}
 
-  if (should_invert (range, TRUE))
-    x = (right - x) + left;
+static void
+gtk_range_destroy (GtkObject *object)
+{
+  GtkRange *range = GTK_RANGE (object);
 
-  if ((x > left) && (y > ythickness))
+  gtk_range_remove_step_timer (range);
+  gtk_range_remove_update_timer (range);
+  
+  if (range->adjustment)
     {
-      gdk_window_get_size (range->trough, &trough_width, &trough_height);
-
-      if ((x < right) && (y < (trough_height - ythickness)))
-       {
-         if (jump_perc)
-           {
-             *jump_perc = ((gdouble) (x - left)) / ((gdouble) (right - left));
-             return GTK_TROUGH_JUMP;
-           }
-         
-         gdk_window_get_position (range->slider, &slider_x, NULL);
-         
-         if (x < slider_x)
-           return GTK_TROUGH_START;
-         else
-           return GTK_TROUGH_END;
-       }
+      g_signal_handlers_disconnect_by_func (range->adjustment,
+                                           gtk_range_adjustment_changed,
+                                           range);
+      g_signal_handlers_disconnect_by_func (range->adjustment,
+                                           gtk_range_adjustment_value_changed,
+                                           range);
+      g_object_unref (range->adjustment);
+      range->adjustment = NULL;
     }
 
-  return GTK_TROUGH_NONE;
+  (* GTK_OBJECT_CLASS (gtk_range_parent_class)->destroy) (object);
 }
 
-gint
-gtk_range_default_vtrough_click (GtkRange *range,
-                                gint      x,
-                                gint      y,
-                                gfloat   *jump_perc)
+static void
+gtk_range_size_request (GtkWidget      *widget,
+                        GtkRequisition *requisition)
 {
-  gint xthickness;
-  gint trough_width;
-  gint trough_height;
-  gint slider_y;
-  gint top, bottom;
-  gint slider_length;
-
-  g_return_val_if_fail (range != NULL, GTK_TROUGH_NONE);
-  g_return_val_if_fail (GTK_IS_RANGE (range), GTK_TROUGH_NONE);
+  GtkRange *range;
+  gint slider_width, stepper_size, focus_width, trough_border, stepper_spacing;
+  GdkRectangle range_rect;
+  GtkBorder border;
+  
+  range = GTK_RANGE (widget);
+  
+  gtk_range_get_props (range,
+                       &slider_width, &stepper_size,
+                       &focus_width, &trough_border,
+                       &stepper_spacing, NULL,
+                       NULL, NULL);
+
+  gtk_range_calc_request (range, 
+                          slider_width, stepper_size,
+                          focus_width, trough_border, stepper_spacing,
+                          &range_rect, &border, NULL, NULL, NULL, NULL);
+
+  requisition->width = range_rect.width + border.left + border.right;
+  requisition->height = range_rect.height + border.top + border.bottom;
+}
 
-  xthickness = GTK_WIDGET (range)->style->xthickness;
+static void
+gtk_range_size_allocate (GtkWidget     *widget,
+                         GtkAllocation *allocation)
+{
+  GtkRange *range;
 
-  gtk_range_trough_vdims (range, &top, &bottom);
-  gdk_window_get_size (range->slider, NULL, &slider_length);
-  bottom += slider_length;
+  range = GTK_RANGE (widget);
 
-  if (should_invert (range, FALSE))
-    y = (bottom - y) + top;
+  widget->allocation = *allocation;
   
-  if ((x > xthickness) && (y > top))
-    {
-      gdk_window_get_size (range->trough, &trough_width, &trough_height);
-
-      if ((x < (trough_width - xthickness) && (y < bottom)))
-       {
-         if (jump_perc)
-           {
-             *jump_perc = ((gdouble) (y - top)) / ((gdouble) (bottom - top));
-
-             return GTK_TROUGH_JUMP;
-           }
-         
-         gdk_window_get_position (range->slider, NULL, &slider_y);
-         
-         if (y < slider_y)
-           return GTK_TROUGH_START;
-         else
-           return GTK_TROUGH_END;
-       }
-    }
+  range->need_recalc = TRUE;
+  gtk_range_calc_layout (range, range->adjustment->value);
 
-  return GTK_TROUGH_NONE;
+  if (GTK_WIDGET_REALIZED (range))
+    gdk_window_move_resize (range->event_window,
+                           widget->allocation.x,
+                           widget->allocation.y,
+                           widget->allocation.width,
+                           widget->allocation.height);
 }
 
-void
-gtk_range_default_hmotion (GtkRange *range,
-                          gint      xdelta,
-                          gint      ydelta)
+static void
+gtk_range_realize (GtkWidget *widget)
 {
-  gdouble old_value;
-  gint left, right;
-  gint slider_x, slider_y;
-  gint new_pos;
-
-  g_return_if_fail (GTK_IS_RANGE (range));
-  g_return_if_fail (GTK_WIDGET_REALIZED (range));
+  GtkRange *range;
+  GdkWindowAttr attributes;
+  gint attributes_mask;  
 
-  gdk_window_get_position (range->slider, &slider_x, &slider_y);
-  gtk_range_trough_hdims (range, &left, &right);
+  range = GTK_RANGE (widget);
 
-  if (left == right)
-    return;
+  gtk_range_calc_layout (range, range->adjustment->value);
+  
+  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
 
-  new_pos = slider_x + xdelta;
+  widget->window = gtk_widget_get_parent_window (widget);
+  g_object_ref (widget->window);
+  
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.x = widget->allocation.x;
+  attributes.y = widget->allocation.y;
+  attributes.width = widget->allocation.width;
+  attributes.height = widget->allocation.height;
+  attributes.wclass = GDK_INPUT_ONLY;
+  attributes.event_mask = gtk_widget_get_events (widget);
+  attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
+                           GDK_BUTTON_RELEASE_MASK |
+                           GDK_ENTER_NOTIFY_MASK |
+                           GDK_LEAVE_NOTIFY_MASK |
+                            GDK_POINTER_MOTION_MASK |
+                            GDK_POINTER_MOTION_HINT_MASK);
+
+  attributes_mask = GDK_WA_X | GDK_WA_Y;
+
+  range->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
+                                       &attributes, attributes_mask);
+  gdk_window_set_user_data (range->event_window, range);
+
+  widget->style = gtk_style_attach (widget->style, widget->window);
+}
 
-  if (should_invert (range, TRUE))
-    new_pos = (right - new_pos) + left;
+static void
+gtk_range_unrealize (GtkWidget *widget)
+{
+  GtkRange *range = GTK_RANGE (widget);
 
-  if (new_pos < left)
-    new_pos = left;
-  else if (new_pos > right)
-    new_pos = right;
+  gtk_range_remove_step_timer (range);
+  gtk_range_remove_update_timer (range);
+  
+  gdk_window_set_user_data (range->event_window, NULL);
+  gdk_window_destroy (range->event_window);
+  range->event_window = NULL;
+  
+  if (GTK_WIDGET_CLASS (gtk_range_parent_class)->unrealize)
+    (* GTK_WIDGET_CLASS (gtk_range_parent_class)->unrealize) (widget);
+}
 
-  old_value = range->adjustment->value;
-  range->adjustment->value = ((range->adjustment->upper -
-                              range->adjustment->lower -
-                              range->adjustment->page_size) *
-                             (new_pos - left) / (right - left) +
-                             range->adjustment->lower);
+static void
+gtk_range_map (GtkWidget *widget)
+{
+  GtkRange *range = GTK_RANGE (widget);
+  
+  gdk_window_show (range->event_window);
 
-  if (range->digits >= 0)
-    {
-      char buffer[64];
+  GTK_WIDGET_CLASS (gtk_range_parent_class)->map (widget);
+}
 
-      sprintf (buffer, "%0.*f", range->digits, range->adjustment->value);
-      sscanf (buffer, "%f", &range->adjustment->value);
-    }
+static void
+gtk_range_unmap (GtkWidget *widget)
+{
+  GtkRange *range = GTK_RANGE (widget);
+    
+  stop_scrolling (range);
 
-  if (old_value != range->adjustment->value)
-    {
-      if (range->policy == GTK_UPDATE_CONTINUOUS)
-       {
-         gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed");
-       }
-      else
-       {
-         gtk_range_slider_update (range);
-         gtk_range_clear_background (range);
+  gdk_window_hide (range->event_window);
 
-         if (range->policy == GTK_UPDATE_DELAYED)
-           {
-             gtk_range_remove_timer (range);
-             range->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
-                                             (GtkFunction) RANGE_CLASS (range)->timer,
-                                             (gpointer) range);
-           }
-       }
-    }
+  GTK_WIDGET_CLASS (gtk_range_parent_class)->unmap (widget);
 }
 
-void
-gtk_range_default_vmotion (GtkRange *range,
-                          gint      xdelta,
-                          gint      ydelta)
+static void
+draw_stepper (GtkRange     *range,
+              GdkRectangle *rect,
+              GtkArrowType  arrow_type,
+              gboolean      clicked,
+              gboolean      prelighted,
+              GdkRectangle *area)
 {
-  gdouble old_value;
-  gint top, bottom;
-  gint slider_x, slider_y;
-  gint new_pos;
-
-  g_return_if_fail (GTK_IS_RANGE (range));
-  g_return_if_fail (GTK_WIDGET_REALIZED (range));
+  GtkStateType state_type;
+  GtkShadowType shadow_type;
+  GdkRectangle intersection;
+  GtkWidget *widget = GTK_WIDGET (range);
 
-  range = GTK_RANGE (range);
+  gint arrow_x;
+  gint arrow_y;
+  gint arrow_width;
+  gint arrow_height;
 
-  gdk_window_get_position (range->slider, &slider_x, &slider_y);
-  gtk_range_trough_vdims (range, &top, &bottom);
+  gboolean arrow_sensitive = TRUE;
 
-  if (bottom == top)
+  /* More to get the right clip region than for efficiency */
+  if (!gdk_rectangle_intersect (area, rect, &intersection))
     return;
 
-  new_pos = slider_y + ydelta;
+  intersection.x += widget->allocation.x;
+  intersection.y += widget->allocation.y;
 
-  if (should_invert (range, FALSE))
-    new_pos = (bottom - new_pos) + top;
-  
-  if (new_pos < top)
-    new_pos = top;
-  else if (new_pos > bottom)
-    new_pos = bottom;
-
-  old_value = range->adjustment->value;
-  range->adjustment->value = ((range->adjustment->upper -
-                              range->adjustment->lower -
-                              range->adjustment->page_size) *
-                             (new_pos - top) / (bottom - top) +
-                             range->adjustment->lower);
-
-  if (range->digits >= 0)
+  if ((!range->inverted && (arrow_type == GTK_ARROW_DOWN ||
+                            arrow_type == GTK_ARROW_RIGHT)) ||
+      (range->inverted  && (arrow_type == GTK_ARROW_UP ||
+                            arrow_type == GTK_ARROW_LEFT)))
     {
-      char buffer[64];
-
-      sprintf (buffer, "%0.*f", range->digits, range->adjustment->value);
-      sscanf (buffer, "%f", &range->adjustment->value);
+      arrow_sensitive = range->layout->upper_sensitive;
+    }
+  else
+    {
+      arrow_sensitive = range->layout->lower_sensitive;
     }
 
-  if (old_value != range->adjustment->value)
+  if (!GTK_WIDGET_IS_SENSITIVE (range) || !arrow_sensitive)
+    state_type = GTK_STATE_INSENSITIVE;
+  else if (clicked)
+    state_type = GTK_STATE_ACTIVE;
+  else if (prelighted)
+    state_type = GTK_STATE_PRELIGHT;
+  else 
+    state_type = GTK_STATE_NORMAL;
+
+  if (clicked && arrow_sensitive)
+    shadow_type = GTK_SHADOW_IN;
+  else
+    shadow_type = GTK_SHADOW_OUT;
+
+  gtk_paint_box (widget->style,
+                widget->window,
+                state_type, shadow_type,
+                &intersection, widget,
+                GTK_RANGE_GET_CLASS (range)->stepper_detail,
+                widget->allocation.x + rect->x,
+                widget->allocation.y + rect->y,
+                rect->width,
+                rect->height);
+
+  arrow_width = rect->width / 2;
+  arrow_height = rect->height / 2;
+  arrow_x = widget->allocation.x + rect->x + (rect->width - arrow_width) / 2;
+  arrow_y = widget->allocation.y + rect->y + (rect->height - arrow_height) / 2;
+  
+  if (clicked && arrow_sensitive)
     {
-      if (range->policy == GTK_UPDATE_CONTINUOUS)
-       {
-         gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed");
-       }
-      else
-       {
-         gtk_range_slider_update (range);
-         gtk_range_clear_background (range);
+      gint arrow_displacement_x;
+      gint arrow_displacement_y;
 
-         if (range->policy == GTK_UPDATE_DELAYED)
-           {
-             gtk_range_remove_timer (range);
-             range->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
-                                             (GtkFunction) RANGE_CLASS (range)->timer,
-                                             (gpointer) range);
-           }
-       }
+      gtk_range_get_props (GTK_RANGE (widget),
+                           NULL, NULL, NULL, NULL, NULL, NULL,
+                          &arrow_displacement_x, &arrow_displacement_y);
+      
+      arrow_x += arrow_displacement_x;
+      arrow_y += arrow_displacement_y;
     }
+  
+  gtk_paint_arrow (widget->style,
+                   widget->window,
+                   state_type, shadow_type, 
+                   &intersection, widget,
+                   GTK_RANGE_GET_CLASS (range)->stepper_detail,
+                   arrow_type,
+                   TRUE,
+                  arrow_x, arrow_y, arrow_width, arrow_height);
 }
 
-
-static void
-gtk_range_destroy (GtkObject *object)
+static gint
+gtk_range_expose (GtkWidget      *widget,
+                 GdkEventExpose *event)
 {
   GtkRange *range;
+  gboolean sensitive;
+  GtkStateType state;
+  GtkShadowType shadow_type;
+  GdkRectangle expose_area;    /* Relative to widget->allocation */
+  GdkRectangle area;
+  gint focus_line_width = 0;
+  gint focus_padding = 0;
+  gboolean touchscreen;
+
+  g_object_get (gtk_widget_get_settings (widget),
+                "gtk-touchscreen-mode", &touchscreen,
+                NULL);
 
-  g_return_if_fail (object != NULL);
-  g_return_if_fail (GTK_IS_RANGE (object));
+  range = GTK_RANGE (widget);
 
-  range = GTK_RANGE (object);
+  if (GTK_WIDGET_CAN_FOCUS (range))
+    {
+      gtk_widget_style_get (GTK_WIDGET (range),
+                           "focus-line-width", &focus_line_width,
+                           "focus-padding", &focus_padding,
+                           NULL);
+    }
+  
+  expose_area = event->area;
+  expose_area.x -= widget->allocation.x;
+  expose_area.y -= widget->allocation.y;
+  
+  gtk_range_calc_layout (range, range->adjustment->value);
 
-  gtk_range_remove_timer (range);
-  if (range->adjustment)
+  sensitive = GTK_WIDGET_IS_SENSITIVE (widget);
+
+  /* Just to be confusing, we draw the trough for the whole
+   * range rectangle, not the trough rectangle (the trough
+   * rectangle is just for hit detection)
+   */
+  /* The gdk_rectangle_intersect is more to get the right
+   * clip region (limited to range_rect) than for efficiency
+   */
+  if (gdk_rectangle_intersect (&expose_area, &range->range_rect,
+                               &area))
     {
-      if (range->adjustment)
-       gtk_signal_disconnect_by_data (GTK_OBJECT (range->adjustment),
-                                      (gpointer) range);
-      gtk_object_unref (GTK_OBJECT (range->adjustment));
-      range->adjustment = NULL;
+      gint     x      = (widget->allocation.x + range->range_rect.x +
+                         focus_line_width + focus_padding);
+      gint     y      = (widget->allocation.y + range->range_rect.y +
+                         focus_line_width + focus_padding);
+      gint     width  = (range->range_rect.width -
+                         2 * (focus_line_width + focus_padding));
+      gint     height = (range->range_rect.height -
+                         2 * (focus_line_width + focus_padding));
+      gboolean trough_side_details;
+      gboolean trough_under_steppers;
+      gint     stepper_size;
+      gint     stepper_spacing;
+
+      area.x += widget->allocation.x;
+      area.y += widget->allocation.y;
+
+      gtk_widget_style_get (GTK_WIDGET (range),
+                            "trough-side-details",   &trough_side_details,
+                            "trough-under-steppers", &trough_under_steppers,
+                            "stepper-size",          &stepper_size,
+                            "stepper-spacing",       &stepper_spacing,
+                            NULL);
+
+      if (stepper_spacing > 0)
+        trough_under_steppers = FALSE;
+
+      if (! trough_under_steppers)
+        {
+          gint offset  = 0;
+          gint shorter = 0;
+
+          if (range->has_stepper_a)
+            offset += stepper_size;
+
+          if (range->has_stepper_b)
+            offset += stepper_size;
+
+          shorter += offset;
+
+          if (range->has_stepper_c)
+            shorter += stepper_size;
+
+          if (range->has_stepper_d)
+            shorter += stepper_size;
+
+          if (range->has_stepper_a || range->has_stepper_b)
+            {
+              offset  += stepper_spacing;
+              shorter += stepper_spacing;
+            }
+
+          if (range->has_stepper_c || range->has_stepper_d)
+            {
+              shorter += stepper_spacing;
+            }
+
+          if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
+            {
+              x     += offset;
+              width -= shorter;
+            }
+          else
+            {
+              y      += offset;
+              height -= shorter;
+            }
+       }
+
+      if (! trough_side_details)
+        {
+          gtk_paint_box (widget->style,
+                         widget->window,
+                         sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
+                         GTK_SHADOW_IN,
+                         &area, GTK_WIDGET(range), "trough",
+                         x, y,
+                         width, height);
+        }
+      else
+        {
+         gint trough_change_pos_x = width;
+         gint trough_change_pos_y = height;
+
+         if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
+           trough_change_pos_x = (range->layout->slider.x +
+                                   range->layout->slider.width / 2 -
+                                   (x - widget->allocation.x));
+         else
+           trough_change_pos_y = (range->layout->slider.y +
+                                   range->layout->slider.height / 2 -
+                                   (y - widget->allocation.y));
+
+          gtk_paint_box (widget->style,
+                         widget->window,
+                         sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
+                         GTK_SHADOW_IN,
+                         &area, GTK_WIDGET (range),
+                         should_invert (range) ? "trough-upper" : "trough-lower",
+                         x, y,
+                         trough_change_pos_x, trough_change_pos_y);
+
+         if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
+           trough_change_pos_y = 0;
+         else
+           trough_change_pos_x = 0;
+
+          gtk_paint_box (widget->style,
+                         widget->window,
+                         sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
+                         GTK_SHADOW_IN,
+                         &area, GTK_WIDGET (range),
+                         should_invert (range) ? "trough-lower" : "trough-upper",
+                         x + trough_change_pos_x, y + trough_change_pos_y,
+                         width - trough_change_pos_x,
+                         height - trough_change_pos_y);
+        }
+
+      if (range->layout->show_fill_level &&
+          range->adjustment->upper - range->adjustment->page_size -
+          range->adjustment->lower != 0)
+       {
+          gdouble  fill_level  = range->layout->fill_level;
+         gint     fill_x      = x;
+         gint     fill_y      = y;
+         gint     fill_width  = width;
+         gint     fill_height = height;
+         gchar   *fill_detail;
+
+          fill_level = CLAMP (fill_level, range->adjustment->lower,
+                              range->adjustment->upper -
+                              range->adjustment->page_size);
+
+         if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
+           {
+             fill_x     = widget->allocation.x + range->layout->trough.x;
+             fill_width = (range->layout->slider.width +
+                            (fill_level - range->adjustment->lower) /
+                            (range->adjustment->upper -
+                             range->adjustment->lower -
+                             range->adjustment->page_size) *
+                            (range->layout->trough.width -
+                             range->layout->slider.width));
+
+              if (should_invert (range))
+                fill_x += range->layout->trough.width - fill_width;
+           }
+         else
+           {
+             fill_y      = widget->allocation.y + range->layout->trough.y;
+             fill_height = (range->layout->slider.height +
+                             (fill_level - range->adjustment->lower) /
+                             (range->adjustment->upper -
+                              range->adjustment->lower -
+                              range->adjustment->page_size) *
+                             (range->layout->trough.height -
+                              range->layout->slider.height));
+
+              if (should_invert (range))
+                fill_y += range->layout->trough.height - fill_height;
+           }
+
+         if (fill_level < range->adjustment->upper - range->adjustment->page_size)
+           fill_detail = "trough-fill-level-full";
+         else
+           fill_detail = "trough-fill-level";
+
+          gtk_paint_box (widget->style,
+                         widget->window,
+                         sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
+                         GTK_SHADOW_OUT,
+                         &area, GTK_WIDGET (range), fill_detail,
+                         fill_x, fill_y,
+                         fill_width, fill_height);
+       }
+
+      if (sensitive &&
+          GTK_WIDGET_HAS_FOCUS (range))
+        gtk_paint_focus (widget->style, widget->window, GTK_WIDGET_STATE (widget),
+                         &area, widget, "trough",
+                         widget->allocation.x + range->range_rect.x,
+                         widget->allocation.y + range->range_rect.y,
+                         range->range_rect.width,
+                         range->range_rect.height);
+    }
+
+  shadow_type = GTK_SHADOW_OUT;
+
+  if (!sensitive)
+    state = GTK_STATE_INSENSITIVE;
+  else if (!touchscreen && range->layout->mouse_location == MOUSE_SLIDER)
+    state = GTK_STATE_PRELIGHT;
+  else
+    state = GTK_STATE_NORMAL;
+
+  if (range->layout->grab_location == MOUSE_SLIDER)
+    {
+      gboolean activate_slider;
+
+      gtk_widget_style_get (widget, "activate-slider", &activate_slider, NULL);
+
+      if (activate_slider)
+        {
+          state = GTK_STATE_ACTIVE;
+          shadow_type = GTK_SHADOW_IN;
+        }
     }
 
-  (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+  if (gdk_rectangle_intersect (&expose_area,
+                               &range->layout->slider,
+                               &area))
+    {
+      area.x += widget->allocation.x;
+      area.y += widget->allocation.y;
+      
+      gtk_paint_slider (widget->style,
+                        widget->window,
+                        state,
+                        shadow_type,
+                        &area,
+                        widget,
+                        GTK_RANGE_GET_CLASS (range)->slider_detail,
+                        widget->allocation.x + range->layout->slider.x,
+                        widget->allocation.y + range->layout->slider.y,
+                        range->layout->slider.width,
+                        range->layout->slider.height,
+                        range->orientation);
+    }
+  
+  if (range->has_stepper_a)
+    draw_stepper (range, &range->layout->stepper_a,
+                  range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
+                  range->layout->grab_location == MOUSE_STEPPER_A,
+                  !touchscreen && range->layout->mouse_location == MOUSE_STEPPER_A,
+                  &expose_area);
+
+  if (range->has_stepper_b)
+    draw_stepper (range, &range->layout->stepper_b,
+                  range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
+                  range->layout->grab_location == MOUSE_STEPPER_B,
+                  !touchscreen && range->layout->mouse_location == MOUSE_STEPPER_B,
+                  &expose_area);
+
+  if (range->has_stepper_c)
+    draw_stepper (range, &range->layout->stepper_c,
+                  range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
+                  range->layout->grab_location == MOUSE_STEPPER_C,
+                  !touchscreen && range->layout->mouse_location == MOUSE_STEPPER_C,
+                  &expose_area);
+
+  if (range->has_stepper_d)
+    draw_stepper (range, &range->layout->stepper_d,
+                  range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
+                  range->layout->grab_location == MOUSE_STEPPER_D,
+                  !touchscreen && range->layout->mouse_location == MOUSE_STEPPER_D,
+                  &expose_area);
+  
+  return FALSE;
 }
 
 static void
-gtk_range_draw_focus (GtkWidget *widget)
+range_grab_add (GtkRange      *range,
+                MouseLocation  location,
+                gint           button)
 {
-  g_return_if_fail (widget != NULL);
-  g_return_if_fail (GTK_IS_RANGE (widget));
+  /* we don't actually gtk_grab, since a button is down */
 
-  if (GTK_WIDGET_DRAWABLE (widget))
-    gtk_range_draw_trough (GTK_RANGE (widget));
+  gtk_grab_add (GTK_WIDGET (range));
+  
+  range->layout->grab_location = location;
+  range->layout->grab_button = button;
+  
+  if (gtk_range_update_mouse_location (range))
+    gtk_widget_queue_draw (GTK_WIDGET (range));
 }
 
 static void
-gtk_range_unrealize (GtkWidget *widget)
+range_grab_remove (GtkRange *range)
 {
-  GtkRange *range;
+  gtk_grab_remove (GTK_WIDGET (range));
+  
+  range->layout->grab_location = MOUSE_OUTSIDE;
+  range->layout->grab_button = 0;
 
-  g_return_if_fail (widget != NULL);
-  g_return_if_fail (GTK_IS_RANGE (widget));
+  if (gtk_range_update_mouse_location (range))
+    gtk_widget_queue_draw (GTK_WIDGET (range));
+}
 
-  range = GTK_RANGE (widget);
+static GtkScrollType
+range_get_scroll_for_grab (GtkRange      *range)
+{ 
+  gboolean invert;
 
-  if (range->slider)
+  invert = should_invert (range);
+  switch (range->layout->grab_location)
     {
-      gdk_window_set_user_data (range->slider, NULL);
-      gdk_window_destroy (range->slider);
-      range->slider = NULL;
+      /* Backward stepper */
+    case MOUSE_STEPPER_A:
+    case MOUSE_STEPPER_C:
+      switch (range->layout->grab_button)
+        {
+        case 1:
+          return invert ? GTK_SCROLL_STEP_FORWARD : GTK_SCROLL_STEP_BACKWARD;
+          break;
+        case 2:
+          return invert ? GTK_SCROLL_PAGE_FORWARD : GTK_SCROLL_PAGE_BACKWARD;
+          break;
+        case 3:
+          return invert ? GTK_SCROLL_END : GTK_SCROLL_START;
+          break;
+        }
+      break;
+
+      /* Forward stepper */
+    case MOUSE_STEPPER_B:
+    case MOUSE_STEPPER_D:
+      switch (range->layout->grab_button)
+        {
+        case 1:
+          return invert ? GTK_SCROLL_STEP_BACKWARD : GTK_SCROLL_STEP_FORWARD;
+          break;
+        case 2:
+          return invert ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_PAGE_FORWARD;
+          break;
+        case 3:
+          return invert ? GTK_SCROLL_START : GTK_SCROLL_END;
+          break;
+       }
+      break;
+
+      /* In the trough */
+    case MOUSE_TROUGH:
+      {
+        if (range->trough_click_forward)
+         return GTK_SCROLL_PAGE_FORWARD;
+        else
+         return GTK_SCROLL_PAGE_BACKWARD;
+      }
+      break;
+
+    case MOUSE_OUTSIDE:
+    case MOUSE_SLIDER:
+    case MOUSE_WIDGET:
+      break;
     }
-  if (range->trough)
+
+  return GTK_SCROLL_NONE;
+}
+
+static gdouble
+coord_to_value (GtkRange *range,
+                gint      coord)
+{
+  gdouble frac;
+  gdouble value;
+  gint    trough_length;
+  gint    trough_start;
+  gint    slider_length;
+  gint    trough_border;
+  gint    trough_under_steppers;
+
+  if (range->orientation == GTK_ORIENTATION_VERTICAL)
     {
-      gdk_window_set_user_data (range->trough, NULL);
-      gdk_window_destroy (range->trough);
-      range->trough = NULL;
+      trough_length = range->layout->trough.height;
+      trough_start  = range->layout->trough.y;
+      slider_length = range->layout->slider.height;
     }
-  if (range->step_forw)
+  else
     {
-      gdk_window_set_user_data (range->step_forw, NULL);
-      gdk_window_destroy (range->step_forw);
-      range->step_forw = NULL;
+      trough_length = range->layout->trough.width;
+      trough_start  = range->layout->trough.x;
+      slider_length = range->layout->slider.width;
     }
-  if (range->step_back)
+
+  gtk_range_get_props (range, NULL, NULL, NULL, &trough_border, NULL,
+                       &trough_under_steppers, NULL, NULL);
+
+  if (! trough_under_steppers)
     {
-      gdk_window_set_user_data (range->step_back, NULL);
-      gdk_window_destroy (range->step_back);
-      range->step_back = NULL;
+      trough_start += trough_border;
+      trough_length -= 2 * trough_border;
     }
 
-  if (GTK_WIDGET_CLASS (parent_class)->unrealize)
-    (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
+  if (trough_length == slider_length)
+    frac = 1.0;
+  else
+    frac = (MAX (0, coord - trough_start) /
+            (gdouble) (trough_length - slider_length));
+
+  if (should_invert (range))
+    frac = 1.0 - frac;
+
+  value = range->adjustment->lower + frac * (range->adjustment->upper -
+                                             range->adjustment->lower -
+                                             range->adjustment->page_size);
+
+  return value;
 }
 
-static gint
-gtk_range_expose (GtkWidget      *widget,
-                 GdkEventExpose *event)
+static gboolean
+gtk_range_key_press (GtkWidget   *widget,
+                    GdkEventKey *event)
 {
-  GtkRange *range;
+  GtkRange *range = GTK_RANGE (widget);
 
-  g_return_val_if_fail (widget != NULL, FALSE);
-  g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
+  if (event->keyval == GDK_Escape &&
+      range->layout->grab_location != MOUSE_OUTSIDE)
+    {
+      stop_scrolling (range);
 
-  range = GTK_RANGE (widget);
+      update_slider_position (range,
+                             range->slide_initial_coordinate,
+                             range->slide_initial_coordinate);
 
-  /* We should really pass another argument - 
-   *the redrawn area - to all the drawing functions)
-   */
-  if (event->window == range->trough)
-    {
-      gtk_range_draw_trough (range);
-    }
-  else if (event->window == widget->window)
-    {
-      gtk_range_draw_background (range); 
-    }
-  else if (event->window == range->slider)
-    {
-      gtk_range_draw_slider (range);
-    }
-  else if (event->window == range->step_forw)
-    {
-      gtk_range_draw_step_forw (range);
-    }
-  else if (event->window == range->step_back)
-    {
-      gtk_range_draw_step_back (range);
+      return TRUE;
     }
-  return FALSE;
+
+  return GTK_WIDGET_CLASS (gtk_range_parent_class)->key_press_event (widget, event);
 }
 
 static gint
 gtk_range_button_press (GtkWidget      *widget,
                        GdkEventButton *event)
 {
-  GtkRange *range;
-  gint trough_part;
-  gfloat jump_perc;
-
-  g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
-
+  GtkRange *range = GTK_RANGE (widget);
+  
   if (!GTK_WIDGET_HAS_FOCUS (widget))
     gtk_widget_grab_focus (widget);
 
-  jump_perc = -1;
-  range = GTK_RANGE (widget);
-  if (range->button == 0)
+  /* ignore presses when we're already doing something else. */
+  if (range->layout->grab_location != MOUSE_OUTSIDE)
+    return FALSE;
+
+  range->layout->mouse_x = event->x;
+  range->layout->mouse_y = event->y;
+  if (gtk_range_update_mouse_location (range))
+    gtk_widget_queue_draw (widget);
+    
+  if (range->layout->mouse_location == MOUSE_TROUGH  &&
+      event->button == 1)
     {
-      gtk_grab_add (widget);
+      /* button 1 steps by page increment, as with button 2 on a stepper
+       */
+      GtkScrollType scroll;
+      gdouble click_value;
+      
+      click_value = coord_to_value (range,
+                                    range->orientation == GTK_ORIENTATION_VERTICAL ?
+                                    event->y : event->x);
+      
+      range->trough_click_forward = click_value > range->adjustment->value;
+      range_grab_add (range, MOUSE_TROUGH, event->button);
+      
+      scroll = range_get_scroll_for_grab (range);
+      
+      gtk_range_add_step_timer (range, scroll);
 
-      range->button = event->button;
-      range->x_click_point = event->x;
-      range->y_click_point = event->y;
+      return TRUE;
+    }
+  else if ((range->layout->mouse_location == MOUSE_STEPPER_A ||
+            range->layout->mouse_location == MOUSE_STEPPER_B ||
+            range->layout->mouse_location == MOUSE_STEPPER_C ||
+            range->layout->mouse_location == MOUSE_STEPPER_D) &&
+           (event->button == 1 || event->button == 2 || event->button == 3))
+    {
+      GdkRectangle *stepper_area;
+      GtkScrollType scroll;
+      
+      range_grab_add (range, range->layout->mouse_location, event->button);
 
-      if (event->window == range->trough)
-       {
-         range->click_child = RANGE_CLASS (range)->trough;
-         
-         if (range->button == 2)
-           trough_part = gtk_range_trough_click (range, event->x, event->y, &jump_perc);
-         else
-           trough_part = gtk_range_trough_click (range, event->x, event->y, NULL);
-         
-         range->scroll_type = GTK_SCROLL_NONE;
-         if (trough_part == GTK_TROUGH_START)
-           range->scroll_type = GTK_SCROLL_PAGE_BACKWARD;
-         else if (trough_part == GTK_TROUGH_END)
-           range->scroll_type = GTK_SCROLL_PAGE_FORWARD;
-         else if (trough_part == GTK_TROUGH_JUMP &&
-                  jump_perc >= 0 && jump_perc <= 1)
-           range->scroll_type = GTK_SCROLL_JUMP;
-         
-         if (range->scroll_type != GTK_SCROLL_NONE)
-           {
-             gtk_range_scroll (range, jump_perc);
-             gtk_range_add_timer (range);
-           }
-       }
-      else if (event->window == range->slider)
-       {
-         range->click_child = RANGE_CLASS (range)->slider;
-         range->scroll_type = GTK_SCROLL_NONE;
-       }
-      else if (event->window == range->step_forw ||
-              event->window == range->step_back)
-       {
-         gboolean back = (event->window == range->step_back);
-         
-         if (range->button == 3)
-           {
-             range->scroll_type = GTK_SCROLL_JUMP;
-             gtk_range_scroll (range, back ? 0.0 : 1.0);
-           }
-         else
-           {
-             range->click_child =
-               back ? RANGE_CLASS (range)->step_back
-                    : RANGE_CLASS (range)->step_forw;
-
-             if (range->button == 2)
-               range->scroll_type =
-                 back ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_PAGE_FORWARD;
-             else
-               range->scroll_type =
-                 back ? GTK_SCROLL_STEP_BACKWARD : GTK_SCROLL_STEP_FORWARD;
-
-             gtk_range_scroll (range, -1);
-             gtk_range_add_timer (range);
-             
-             if (back)
-               gtk_range_draw_step_back (range);
-             else
-               gtk_range_draw_step_forw (range);
-           }
-       }
+      stepper_area = get_area (range, range->layout->mouse_location);
+      gtk_widget_queue_draw_area (widget,
+                                  widget->allocation.x + stepper_area->x,
+                                  widget->allocation.y + stepper_area->y,
+                                  stepper_area->width,
+                                  stepper_area->height);
+
+      scroll = range_get_scroll_for_grab (range);
+      if (scroll != GTK_SCROLL_NONE)
+        gtk_range_add_step_timer (range, scroll);
+      
+      return TRUE;
     }
+  else if ((range->layout->mouse_location == MOUSE_TROUGH &&
+            event->button == 2) ||
+           range->layout->mouse_location == MOUSE_SLIDER)
+    {
+      gboolean need_value_update = FALSE;
+      gboolean activate_slider;
 
-  return TRUE;
+      /* Any button can be used to drag the slider, but you can start
+       * dragging the slider with a trough click using button 2;
+       * On button 2 press, we warp the slider to mouse position,
+       * then begin the slider drag.
+       */
+      if (event->button == 2)
+        {
+          gdouble slider_low_value, slider_high_value, new_value;
+          
+          slider_high_value =
+            coord_to_value (range,
+                            range->orientation == GTK_ORIENTATION_VERTICAL ?
+                            event->y : event->x);
+          slider_low_value =
+            coord_to_value (range,
+                            range->orientation == GTK_ORIENTATION_VERTICAL ?
+                            event->y - range->layout->slider.height :
+                            event->x - range->layout->slider.width);
+          
+          /* compute new value for warped slider */
+          new_value = slider_low_value + (slider_high_value - slider_low_value) / 2;
+
+         /* recalc slider, so we can set slide_initial_slider_position
+           * properly
+           */
+         range->need_recalc = TRUE;
+          gtk_range_calc_layout (range, new_value);
+
+         /* defer adjustment updates to update_slider_position() in order
+          * to keep pixel quantisation
+          */
+         need_value_update = TRUE;
+        }
+      
+      if (range->orientation == GTK_ORIENTATION_VERTICAL)
+        {
+          range->slide_initial_slider_position = range->layout->slider.y;
+          range->slide_initial_coordinate = event->y;
+        }
+      else
+        {
+          range->slide_initial_slider_position = range->layout->slider.x;
+          range->slide_initial_coordinate = event->x;
+        }
+
+      range_grab_add (range, MOUSE_SLIDER, event->button);
+
+      gtk_widget_style_get (widget, "activate-slider", &activate_slider, NULL);
+
+      /* force a redraw, if the active slider is drawn differently to the
+       * prelight one
+       */
+      if (activate_slider)
+        gtk_widget_queue_draw (widget);
+
+      if (need_value_update)
+        update_slider_position (range, event->x, event->y);
+
+      return TRUE;
+    }
+  
+  return FALSE;
 }
 
-static gint
-gtk_range_button_release (GtkWidget      *widget,
-                         GdkEventButton *event)
+/* During a slide, move the slider as required given new mouse position */
+static void
+update_slider_position (GtkRange *range,
+                        gint      mouse_x,
+                        gint      mouse_y)
 {
-  GtkRange *range;
+  gint delta;
+  gint c;
+  gdouble new_value;
+  gboolean handled;
 
-  g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
+  if (range->orientation == GTK_ORIENTATION_VERTICAL)
+    delta = mouse_y - range->slide_initial_coordinate;
+  else
+    delta = mouse_x - range->slide_initial_coordinate;
 
-  range = GTK_RANGE (widget);
+  c = range->slide_initial_slider_position + delta;
 
-  if (range->button == event->button)
-    {
-      gtk_grab_remove (widget);
+  new_value = coord_to_value (range, c);
+  
+  g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_JUMP, new_value,
+                 &handled);
+}
 
-      range->button = 0;
-      range->x_click_point = -1;
-      range->y_click_point = -1;
+static void 
+stop_scrolling (GtkRange *range)
+{
+  range_grab_remove (range);
+  gtk_range_remove_step_timer (range);
+  /* Flush any pending discontinuous/delayed updates */
+  gtk_range_update_value (range);
+  
+  /* Just be lazy about this, if we scrolled it will all redraw anyway,
+   * so no point optimizing the button deactivate case
+   */
+  gtk_widget_queue_draw (GTK_WIDGET (range));
+}
 
-      if (range->click_child == RANGE_CLASS (range)->slider)
-       {
-         if (range->policy == GTK_UPDATE_DELAYED)
-           gtk_range_remove_timer (range);
+static gboolean
+gtk_range_grab_broken (GtkWidget          *widget,
+                      GdkEventGrabBroken *event)
+{
+  GtkRange *range = GTK_RANGE (widget);
 
-         if ((range->policy != GTK_UPDATE_CONTINUOUS) &&
-             (range->old_value != range->adjustment->value))
-           gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed");
-       }
-      else if ((range->click_child == RANGE_CLASS (range)->trough) ||
-              (range->click_child == RANGE_CLASS (range)->step_forw) ||
-              (range->click_child == RANGE_CLASS (range)->step_back))
-       {
-         gtk_range_remove_timer (range);
+  if (range->layout->grab_location != MOUSE_OUTSIDE)
+    {
+      if (range->layout->grab_location == MOUSE_SLIDER)
+       update_slider_position (range, range->layout->mouse_x, range->layout->mouse_y);
+      
+      stop_scrolling (range);
+      
+      return TRUE;
+    }
+  
+  return FALSE;
+}
 
-         if ((range->policy != GTK_UPDATE_CONTINUOUS) &&
-             (range->old_value != range->adjustment->value))
-           gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed");
+static gint
+gtk_range_button_release (GtkWidget      *widget,
+                         GdkEventButton *event)
+{
+  GtkRange *range = GTK_RANGE (widget);
 
-         if (range->click_child == RANGE_CLASS (range)->step_forw)
-           {
-             range->click_child = 0;
-             gtk_range_draw_step_forw (range);
-           }
-         else if (range->click_child == RANGE_CLASS (range)->step_back)
-           {
-             range->click_child = 0;
-             gtk_range_draw_step_back (range);
-           }
-       }
+  if (event->window == range->event_window)
+    {
+      range->layout->mouse_x = event->x;
+      range->layout->mouse_y = event->y;
+    }
+  else
+    {
+      gdk_window_get_pointer (range->event_window,
+                             &range->layout->mouse_x,
+                             &range->layout->mouse_y,
+                             NULL);
+    }
+  
+  if (range->layout->grab_button == event->button)
+    {
+      if (range->layout->grab_location == MOUSE_SLIDER)
+        update_slider_position (range, range->layout->mouse_x, range->layout->mouse_y);
 
-      range->click_child = 0;
+      stop_scrolling (range);
+      
+      return TRUE;
     }
 
-  return TRUE;
+  return FALSE;
 }
 
+/**
+ * _gtk_range_get_wheel_delta:
+ * @range: a #GtkRange
+ * @direction: A #GdkScrollDirection
+ * 
+ * Returns a good step value for the mouse wheel.
+ * 
+ * Return value: A good step value for the mouse wheel. 
+ * 
+ * Since: 2.4
+ **/
+gdouble
+_gtk_range_get_wheel_delta (GtkRange           *range,
+                           GdkScrollDirection  direction)
+{
+  GtkAdjustment *adj = range->adjustment;
+  gdouble delta;
+
+  if (GTK_IS_SCROLLBAR (range))
+    delta = pow (adj->page_size, 2.0 / 3.0);
+  else
+    delta = adj->step_increment * 2;
+  
+  if (direction == GDK_SCROLL_UP ||
+      direction == GDK_SCROLL_LEFT)
+    delta = - delta;
+  
+  if (range->inverted)
+    delta = - delta;
+
+  return delta;
+}
+      
 static gint
 gtk_range_scroll_event (GtkWidget      *widget,
                        GdkEventScroll *event)
 {
-  GtkRange *range;
-
-  g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
-
-  range = GTK_RANGE (widget);
+  GtkRange *range = GTK_RANGE (widget);
 
   if (GTK_WIDGET_REALIZED (range))
     {
       GtkAdjustment *adj = GTK_RANGE (range)->adjustment;
-      gfloat new_value = adj->value + ((event->direction == GDK_SCROLL_UP) ? 
-                                      -adj->page_increment / 2: 
-                                      adj->page_increment / 2);
-      new_value = CLAMP (new_value, adj->lower, adj->upper - adj->page_size);
-      gtk_adjustment_set_value (adj, new_value);
+      gdouble delta;
+      gboolean handled;
+
+      delta = _gtk_range_get_wheel_delta (range, event->direction);
+
+      g_signal_emit (range, signals[CHANGE_VALUE], 0,
+                     GTK_SCROLL_JUMP, adj->value + delta,
+                     &handled);
+      
+      /* Policy DELAYED makes sense with scroll events,
+       * but DISCONTINUOUS doesn't, so we update immediately
+       * for DISCONTINUOUS
+       */
+      if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
+        gtk_range_update_value (range);
     }
 
   return TRUE;
@@ -1059,705 +2136,1260 @@ gtk_range_motion_notify (GtkWidget      *widget,
                         GdkEventMotion *event)
 {
   GtkRange *range;
-
-  g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
+  gint x, y;
 
   range = GTK_RANGE (widget);
 
-  if (range->click_child == RANGE_CLASS (range)->slider)
-    {
-      GdkModifierType mods;
-      gint x, y, mask, x2, y2;
+  gdk_window_get_pointer (range->event_window, &x, &y, NULL);
+  
+  range->layout->mouse_x = x;
+  range->layout->mouse_y = y;
 
-      gdk_window_get_pointer (range->trough, &x, &y, &mods);
-      gdk_window_get_position (range->slider, &x2, &y2);
+  if (gtk_range_update_mouse_location (range))
+    gtk_widget_queue_draw (widget);
 
-      x -= x2;
-      y -= y2;
+  if (range->layout->grab_location == MOUSE_SLIDER)
+    update_slider_position (range, x, y);
 
-      switch (range->button)
-       {
-       case 1:
-         mask = GDK_BUTTON1_MASK;
-         break;
-       case 2:
-         mask = GDK_BUTTON2_MASK;
-         break;
-       case 3:
-         mask = GDK_BUTTON3_MASK;
-         break;
-       default:
-         mask = 0;
-         break;
-       }
+  /* We handled the event if the mouse was in the range_rect */
+  return range->layout->mouse_location != MOUSE_OUTSIDE;
+}
 
-      if (mods & mask)
-       {
-         if (RANGE_CLASS (range)->motion)
-           (* RANGE_CLASS (range)->motion) (range, x - range->x_click_point, y - range->y_click_point);
-       }
-    }
+static gint
+gtk_range_enter_notify (GtkWidget        *widget,
+                       GdkEventCrossing *event)
+{
+  GtkRange *range = GTK_RANGE (widget);
+
+  range->layout->mouse_x = event->x;
+  range->layout->mouse_y = event->y;
 
+  if (gtk_range_update_mouse_location (range))
+    gtk_widget_queue_draw (widget);
+  
   return TRUE;
 }
 
 static gint
-gtk_range_key_press (GtkWidget   *widget,
-                    GdkEventKey *event)
+gtk_range_leave_notify (GtkWidget        *widget,
+                       GdkEventCrossing *event)
 {
-  GtkRange *range;
-  gint return_val;
-  GtkScrollType scroll = GTK_SCROLL_NONE;
-  GtkTroughType pos = GTK_TROUGH_NONE;
+  GtkRange *range = GTK_RANGE (widget);
 
-  g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
+  range->layout->mouse_x = -1;
+  range->layout->mouse_y = -1;
 
-  range = GTK_RANGE (widget);
-  return_val = FALSE;
+  if (gtk_range_update_mouse_location (range))
+    gtk_widget_queue_draw (widget);
   
-  if (RANGE_CLASS (range)->trough_keys)
-    return_val = (* RANGE_CLASS (range)->trough_keys) (range, event, &scroll, &pos);
+  return TRUE;
+}
 
-  if (return_val)
-    {
-      if (scroll != GTK_SCROLL_NONE)
-       {
-         range->scroll_type = scroll;
+static void
+gtk_range_grab_notify (GtkWidget *widget,
+                      gboolean   was_grabbed)
+{
+  if (!was_grabbed)
+    stop_scrolling (GTK_RANGE (widget));
+}
 
-         gtk_range_scroll (range, -1);
-         if (range->old_value != range->adjustment->value)
-           {
-             gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed");
-             switch (range->scroll_type)
-               {
-                case GTK_SCROLL_STEP_LEFT:
-                  if (should_invert (range, TRUE))
-                    gtk_range_draw_step_forw (range);
-                  else
-                    gtk_range_draw_step_back (range);
-                  break;
-                    
-                case GTK_SCROLL_STEP_UP:
-                  if (should_invert (range, FALSE))
-                    gtk_range_draw_step_forw (range);
-                  else
-                    gtk_range_draw_step_back (range);
-                  break;
-
-                case GTK_SCROLL_STEP_RIGHT:
-                  if (should_invert (range, TRUE))
-                    gtk_range_draw_step_back (range);
-                  else
-                    gtk_range_draw_step_forw (range);
-                  break;
-                    
-                case GTK_SCROLL_STEP_DOWN:
-                  if (should_invert (range, FALSE))
-                    gtk_range_draw_step_back (range);
-                  else
-                    gtk_range_draw_step_forw (range);
-                  break;
-                  
-               case GTK_SCROLL_STEP_BACKWARD:
-                 gtk_range_draw_step_back (range);
-                 break;
-                  
-               case GTK_SCROLL_STEP_FORWARD:
-                 gtk_range_draw_step_forw (range);
-                 break;
-               }
-           }
-       }
-      if (pos != GTK_TROUGH_NONE)
-       {
-         if (pos == GTK_TROUGH_START)
-           range->adjustment->value = range->adjustment->lower;
-         else if (pos == GTK_TROUGH_END)
-           range->adjustment->value =
-             range->adjustment->upper - range->adjustment->page_size;
+static void
+gtk_range_state_changed (GtkWidget    *widget,
+                        GtkStateType  previous_state)
+{
+  if (!GTK_WIDGET_IS_SENSITIVE (widget)) 
+    stop_scrolling (GTK_RANGE (widget));
+}
 
-         if (range->old_value != range->adjustment->value)
-           {
-             gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment),
-                                      "value_changed");
+#define check_rectangle(rectangle1, rectangle2)              \
+  {                                                          \
+    if (rectangle1.x != rectangle2.x) return TRUE;           \
+    if (rectangle1.y != rectangle2.y) return TRUE;           \
+    if (rectangle1.width  != rectangle2.width)  return TRUE; \
+    if (rectangle1.height != rectangle2.height) return TRUE; \
+  }
 
-             gtk_range_slider_update (range);
-             gtk_range_clear_background (range);
-           }
-       }
-    }
-  return return_val;
+static gboolean
+layout_changed (GtkRangeLayout *layout1, 
+               GtkRangeLayout *layout2)
+{
+  check_rectangle (layout1->slider, layout2->slider);
+  check_rectangle (layout1->trough, layout2->trough);
+  check_rectangle (layout1->stepper_a, layout2->stepper_a);
+  check_rectangle (layout1->stepper_d, layout2->stepper_d);
+  check_rectangle (layout1->stepper_b, layout2->stepper_b);
+  check_rectangle (layout1->stepper_c, layout2->stepper_c);
+
+  if (layout1->upper_sensitive != layout2->upper_sensitive) return TRUE;
+  if (layout1->lower_sensitive != layout2->lower_sensitive) return TRUE;
+
+  return FALSE;
 }
 
-static gint
-gtk_range_enter_notify (GtkWidget        *widget,
-                       GdkEventCrossing *event)
+static void
+gtk_range_adjustment_changed (GtkAdjustment *adjustment,
+                             gpointer       data)
 {
-  GtkRange *range;
+  GtkRange *range = GTK_RANGE (data);
+  /* create a copy of the layout */
+  GtkRangeLayout layout = *range->layout;
 
-  g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
+  range->need_recalc = TRUE;
+  gtk_range_calc_layout (range, range->adjustment->value);
+  
+  /* now check whether the layout changed  */
+  if (layout_changed (range->layout, &layout))
+    gtk_widget_queue_draw (GTK_WIDGET (range));
+
+  /* Note that we don't round off to range->round_digits here.
+   * that's because it's really broken to change a value
+   * in response to a change signal on that value; round_digits
+   * is therefore defined to be a filter on what the GtkRange
+   * can input into the adjustment, not a filter that the GtkRange
+   * will enforce on the adjustment.
+   */
+}
 
-  range = GTK_RANGE (widget);
+static void
+gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
+                                   gpointer       data)
+{
+  GtkRange *range = GTK_RANGE (data);
+  /* create a copy of the layout */
+  GtkRangeLayout layout = *range->layout;
 
-  if (event->window == range->trough)
+  range->need_recalc = TRUE;
+  gtk_range_calc_layout (range, range->adjustment->value);
+  
+  /* now check whether the layout changed  */
+  if (layout_changed (range->layout, &layout))
     {
-      range->in_child = RANGE_CLASS (range)->trough;
+      gtk_widget_queue_draw (GTK_WIDGET (range));
+      
+      /* This is so we don't lag the widget being scrolled. */
+      if (GTK_WIDGET_REALIZED (range))
+        gdk_window_process_updates (GTK_WIDGET (range)->window, FALSE);
     }
-  else if (event->window == range->slider)
-    {
-      range->in_child = RANGE_CLASS (range)->slider;
+  
+  /* Note that we don't round off to range->round_digits here.
+   * that's because it's really broken to change a value
+   * in response to a change signal on that value; round_digits
+   * is therefore defined to be a filter on what the GtkRange
+   * can input into the adjustment, not a filter that the GtkRange
+   * will enforce on the adjustment.
+   */
 
-      if ((range->click_child == 0) ||
-         (range->click_child == RANGE_CLASS (range)->trough))
-       gtk_range_draw_slider (range);
-    }
-  else if (event->window == range->step_forw)
-    {
-      range->in_child = RANGE_CLASS (range)->step_forw;
+  g_signal_emit (range, signals[VALUE_CHANGED], 0);
+}
 
-      if ((range->click_child == 0) ||
-         (range->click_child == RANGE_CLASS (range)->trough))
-       gtk_range_draw_step_forw (range);
-    }
-  else if (event->window == range->step_back)
-    {
-      range->in_child = RANGE_CLASS (range)->step_back;
+static void
+gtk_range_style_set (GtkWidget *widget,
+                     GtkStyle  *previous_style)
+{
+  GtkRange *range = GTK_RANGE (widget);
 
-      if ((range->click_child == 0) ||
-         (range->click_child == RANGE_CLASS (range)->trough))
-       gtk_range_draw_step_back (range);
-    }
+  range->need_recalc = TRUE;
 
-  return TRUE;
+  (* GTK_WIDGET_CLASS (gtk_range_parent_class)->style_set) (widget, previous_style);
 }
 
-static gint
-gtk_range_leave_notify (GtkWidget        *widget,
-                       GdkEventCrossing *event)
+static void
+step_back (GtkRange *range)
 {
-  GtkRange *range;
+  gdouble newval;
+  gboolean handled;
+  
+  newval = range->adjustment->value - range->adjustment->step_increment;
+  g_signal_emit (range, signals[CHANGE_VALUE], 0,
+                 GTK_SCROLL_STEP_BACKWARD, newval, &handled);
+}
 
-  g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
+static void
+step_forward (GtkRange *range)
+{
+  gdouble newval;
+  gboolean handled;
 
-  range = GTK_RANGE (widget);
+  newval = range->adjustment->value + range->adjustment->step_increment;
+  g_signal_emit (range, signals[CHANGE_VALUE], 0,
+                 GTK_SCROLL_STEP_FORWARD, newval, &handled);
+}
+
+
+static void
+page_back (GtkRange *range)
+{
+  gdouble newval;
+  gboolean handled;
 
-  range->in_child = 0;
+  newval = range->adjustment->value - range->adjustment->page_increment;
+  g_signal_emit (range, signals[CHANGE_VALUE], 0,
+                 GTK_SCROLL_PAGE_BACKWARD, newval, &handled);
+}
 
-  if (event->window == range->trough)
+static void
+page_forward (GtkRange *range)
+{
+  gdouble newval;
+  gboolean handled;
+
+  newval = range->adjustment->value + range->adjustment->page_increment;
+  g_signal_emit (range, signals[CHANGE_VALUE], 0,
+                 GTK_SCROLL_PAGE_FORWARD, newval, &handled);
+}
+
+static void
+scroll_begin (GtkRange *range)
+{
+  gboolean handled;
+  g_signal_emit (range, signals[CHANGE_VALUE], 0,
+                 GTK_SCROLL_START, range->adjustment->lower,
+                 &handled);
+}
+
+static void
+scroll_end (GtkRange *range)
+{
+  gdouble newval;
+  gboolean handled;
+
+  newval = range->adjustment->upper - range->adjustment->page_size;
+  g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_END, newval,
+                 &handled);
+}
+
+static void
+gtk_range_scroll (GtkRange     *range,
+                  GtkScrollType scroll)
+{
+  switch (scroll)
     {
+    case GTK_SCROLL_STEP_LEFT:
+      if (should_invert (range))
+        step_forward (range);
+      else
+        step_back (range);
+      break;
+                    
+    case GTK_SCROLL_STEP_UP:
+      if (should_invert (range))
+        step_forward (range);
+      else
+        step_back (range);
+      break;
+
+    case GTK_SCROLL_STEP_RIGHT:
+      if (should_invert (range))
+        step_back (range);
+      else
+        step_forward (range);
+      break;
+                    
+    case GTK_SCROLL_STEP_DOWN:
+      if (should_invert (range))
+        step_back (range);
+      else
+        step_forward (range);
+      break;
+                  
+    case GTK_SCROLL_STEP_BACKWARD:
+      step_back (range);
+      break;
+                  
+    case GTK_SCROLL_STEP_FORWARD:
+      step_forward (range);
+      break;
+
+    case GTK_SCROLL_PAGE_LEFT:
+      if (should_invert (range))
+        page_forward (range);
+      else
+        page_back (range);
+      break;
+                    
+    case GTK_SCROLL_PAGE_UP:
+      if (should_invert (range))
+        page_forward (range);
+      else
+        page_back (range);
+      break;
+
+    case GTK_SCROLL_PAGE_RIGHT:
+      if (should_invert (range))
+        page_back (range);
+      else
+        page_forward (range);
+      break;
+                    
+    case GTK_SCROLL_PAGE_DOWN:
+      if (should_invert (range))
+        page_back (range);
+      else
+        page_forward (range);
+      break;
+                  
+    case GTK_SCROLL_PAGE_BACKWARD:
+      page_back (range);
+      break;
+                  
+    case GTK_SCROLL_PAGE_FORWARD:
+      page_forward (range);
+      break;
+
+    case GTK_SCROLL_START:
+      scroll_begin (range);
+      break;
+
+    case GTK_SCROLL_END:
+      scroll_end (range);
+      break;
+
+    case GTK_SCROLL_JUMP:
+      /* Used by CList, range doesn't use it. */
+      break;
+
+    case GTK_SCROLL_NONE:
+      break;
     }
-  else if (event->window == range->slider)
+}
+
+static void
+gtk_range_move_slider (GtkRange     *range,
+                       GtkScrollType scroll)
+{
+  gboolean cursor_only;
+
+  g_object_get (gtk_widget_get_settings (GTK_WIDGET (range)),
+                "gtk-keynav-cursor-only", &cursor_only,
+                NULL);
+
+  if (cursor_only)
     {
-      if ((range->click_child == 0) ||
-         (range->click_child == RANGE_CLASS (range)->trough))
-       gtk_range_draw_slider (range);
+      GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (range));
+
+      if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
+        {
+          if (scroll == GTK_SCROLL_STEP_UP ||
+              scroll == GTK_SCROLL_STEP_DOWN)
+            {
+              if (toplevel)
+                gtk_widget_child_focus (toplevel,
+                                        scroll == GTK_SCROLL_STEP_UP ?
+                                        GTK_DIR_UP : GTK_DIR_DOWN);
+              return;
+            }
+        }
+      else
+        {
+          if (scroll == GTK_SCROLL_STEP_LEFT ||
+              scroll == GTK_SCROLL_STEP_RIGHT)
+            {
+              if (toplevel)
+                gtk_widget_child_focus (toplevel,
+                                        scroll == GTK_SCROLL_STEP_LEFT ?
+                                        GTK_DIR_LEFT : GTK_DIR_RIGHT);
+              return;
+            }
+        }
     }
-  else if (event->window == range->step_forw)
+
+  gtk_range_scroll (range, scroll);
+
+  /* Policy DELAYED makes sense with key events,
+   * but DISCONTINUOUS doesn't, so we update immediately
+   * for DISCONTINUOUS
+   */
+  if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
+    gtk_range_update_value (range);
+}
+
+static void
+gtk_range_get_props (GtkRange  *range,
+                     gint      *slider_width,
+                     gint      *stepper_size,
+                     gint      *focus_width,
+                     gint      *trough_border,
+                     gint      *stepper_spacing,
+                     gboolean  *trough_under_steppers,
+                    gint      *arrow_displacement_x,
+                    gint      *arrow_displacement_y)
+{
+  GtkWidget *widget =  GTK_WIDGET (range);
+  gint tmp_slider_width, tmp_stepper_size, tmp_focus_width, tmp_trough_border;
+  gint tmp_stepper_spacing, tmp_trough_under_steppers;
+  gint tmp_arrow_displacement_x, tmp_arrow_displacement_y;
+  
+  gtk_widget_style_get (widget,
+                        "slider-width", &tmp_slider_width,
+                        "trough-border", &tmp_trough_border,
+                        "stepper-size", &tmp_stepper_size,
+                        "stepper-spacing", &tmp_stepper_spacing,
+                        "trough-under-steppers", &tmp_trough_under_steppers,
+                       "arrow-displacement-x", &tmp_arrow_displacement_x,
+                       "arrow-displacement-y", &tmp_arrow_displacement_y,
+                        NULL);
+
+  if (tmp_stepper_spacing > 0)
+    tmp_trough_under_steppers = FALSE;
+
+  if (GTK_WIDGET_CAN_FOCUS (range))
     {
-      if ((range->click_child == 0) ||
-         (range->click_child == RANGE_CLASS (range)->trough))
-       gtk_range_draw_step_forw (range);
+      gint focus_line_width;
+      gint focus_padding;
+      
+      gtk_widget_style_get (GTK_WIDGET (range),
+                           "focus-line-width", &focus_line_width,
+                           "focus-padding", &focus_padding,
+                           NULL);
+
+      tmp_focus_width = focus_line_width + focus_padding;
     }
-  else if (event->window == range->step_back)
+  else
     {
-      if ((range->click_child == 0) ||
-         (range->click_child == RANGE_CLASS (range)->trough))
-       gtk_range_draw_step_back (range);
+      tmp_focus_width = 0;
     }
+  
+  if (slider_width)
+    *slider_width = tmp_slider_width;
 
-  return TRUE;
-}
+  if (focus_width)
+    *focus_width = tmp_focus_width;
 
-static gint
-gtk_range_focus_in (GtkWidget     *widget,
-                   GdkEventFocus *event)
-{
-  g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
+  if (trough_border)
+    *trough_border = tmp_trough_border;
+
+  if (stepper_size)
+    *stepper_size = tmp_stepper_size;
+
+  if (stepper_spacing)
+    *stepper_spacing = tmp_stepper_spacing;
+
+  if (trough_under_steppers)
+    *trough_under_steppers = tmp_trough_under_steppers;
 
-  GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
-  gtk_widget_draw_focus (widget);
+  if (arrow_displacement_x)
+    *arrow_displacement_x = tmp_arrow_displacement_x;
 
-  return TRUE;
+  if (arrow_displacement_y)
+    *arrow_displacement_y = tmp_arrow_displacement_y;
 }
 
-static gint
-gtk_range_focus_out (GtkWidget     *widget,
-                    GdkEventFocus *event)
+#define POINT_IN_RECT(xcoord, ycoord, rect) \
+ ((xcoord) >= (rect).x &&                   \
+  (xcoord) <  ((rect).x + (rect).width) &&  \
+  (ycoord) >= (rect).y &&                   \
+  (ycoord) <  ((rect).y + (rect).height))
+
+/* Update mouse location, return TRUE if it changes */
+static gboolean
+gtk_range_update_mouse_location (GtkRange *range)
 {
-  g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
+  gint x, y;
+  MouseLocation old;
+  GtkWidget *widget;
 
-  GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
-  gtk_widget_draw_focus (widget);
+  widget = GTK_WIDGET (range);
+  
+  old = range->layout->mouse_location;
+  
+  x = range->layout->mouse_x;
+  y = range->layout->mouse_y;
+
+  if (range->layout->grab_location != MOUSE_OUTSIDE)
+    range->layout->mouse_location = range->layout->grab_location;
+  else if (POINT_IN_RECT (x, y, range->layout->stepper_a))
+    range->layout->mouse_location = MOUSE_STEPPER_A;
+  else if (POINT_IN_RECT (x, y, range->layout->stepper_b))
+    range->layout->mouse_location = MOUSE_STEPPER_B;
+  else if (POINT_IN_RECT (x, y, range->layout->stepper_c))
+    range->layout->mouse_location = MOUSE_STEPPER_C;
+  else if (POINT_IN_RECT (x, y, range->layout->stepper_d))
+    range->layout->mouse_location = MOUSE_STEPPER_D;
+  else if (POINT_IN_RECT (x, y, range->layout->slider))
+    range->layout->mouse_location = MOUSE_SLIDER;
+  else if (POINT_IN_RECT (x, y, range->layout->trough))
+    range->layout->mouse_location = MOUSE_TROUGH;
+  else if (POINT_IN_RECT (x, y, widget->allocation))
+    range->layout->mouse_location = MOUSE_WIDGET;
+  else
+    range->layout->mouse_location = MOUSE_OUTSIDE;
 
-  return TRUE;
+  return old != range->layout->mouse_location;
 }
 
+/* Clamp rect, border inside widget->allocation, such that we prefer
+ * to take space from border not rect in all directions, and prefer to
+ * give space to border over rect in one direction.
+ */
 static void
-gtk_real_range_draw_trough (GtkRange *range)
+clamp_dimensions (GtkWidget    *widget,
+                  GdkRectangle *rect,
+                  GtkBorder    *border,
+                  gboolean      border_expands_horizontally)
 {
-  g_return_if_fail (GTK_IS_RANGE (range));
+  gint extra, shortage;
+  
+  g_return_if_fail (rect->x == 0);
+  g_return_if_fail (rect->y == 0);  
+  g_return_if_fail (rect->width >= 0);
+  g_return_if_fail (rect->height >= 0);
 
-  if (range->trough)
-     {
-       gtk_paint_box (GTK_WIDGET (range)->style, range->trough,
-                      GTK_STATE_ACTIVE, GTK_SHADOW_IN,
-                      NULL, GTK_WIDGET(range), "trough",
-                      0, 0, -1, -1);
-       if (GTK_WIDGET_HAS_FOCUS (range))
-         gtk_paint_focus (GTK_WIDGET (range)->style,
-                         range->trough,
-                          NULL, GTK_WIDGET(range), "trough",
-                         0, 0, -1, -1);
+  /* Width */
+  
+  extra = widget->allocation.width - border->left - border->right - rect->width;
+  if (extra > 0)
+    {
+      if (border_expands_horizontally)
+        {
+          border->left += extra / 2;
+          border->right += extra / 2 + extra % 2;
+        }
+      else
+        {
+          rect->width += extra;
+        }
+    }
+  
+  /* See if we can fit rect, if not kill the border */
+  shortage = rect->width - widget->allocation.width;
+  if (shortage > 0)
+    {
+      rect->width = widget->allocation.width;
+      /* lose the border */
+      border->left = 0;
+      border->right = 0;
+    }
+  else
+    {
+      /* See if we can fit rect with borders */
+      shortage = rect->width + border->left + border->right -
+        widget->allocation.width;
+      if (shortage > 0)
+        {
+          /* Shrink borders */
+          border->left -= shortage / 2;
+          border->right -= shortage / 2 + shortage % 2;
+        }
     }
-}
 
-static void
-gtk_real_range_draw_slider (GtkRange *range)
-{
-  GtkStateType state_type;
-   
-  g_return_if_fail (GTK_IS_RANGE (range));
-   
-  if (range->slider)
+  /* Height */
+  
+  extra = widget->allocation.height - border->top - border->bottom - rect->height;
+  if (extra > 0)
     {
-      if ((range->in_child == RANGE_CLASS (range)->slider) ||
-         (range->click_child == RANGE_CLASS (range)->slider))
-       state_type = GTK_STATE_PRELIGHT;
+      if (border_expands_horizontally)
+        {
+          /* don't expand border vertically */
+          rect->height += extra;
+        }
       else
-       state_type = GTK_STATE_NORMAL;
-      gtk_paint_box (GTK_WIDGET (range)->style, range->slider,
-                    state_type, GTK_SHADOW_OUT,
-                    NULL, GTK_WIDGET (range), "slider",
-                    0, 0, -1, -1);
+        {
+          border->top += extra / 2;
+          border->bottom += extra / 2 + extra % 2;
+        }
+    }
+  
+  /* See if we can fit rect, if not kill the border */
+  shortage = rect->height - widget->allocation.height;
+  if (shortage > 0)
+    {
+      rect->height = widget->allocation.height;
+      /* lose the border */
+      border->top = 0;
+      border->bottom = 0;
+    }
+  else
+    {
+      /* See if we can fit rect with borders */
+      shortage = rect->height + border->top + border->bottom -
+        widget->allocation.height;
+      if (shortage > 0)
+        {
+          /* Shrink borders */
+          border->top -= shortage / 2;
+          border->bottom -= shortage / 2 + shortage % 2;
+        }
     }
 }
 
-static gint
-gtk_real_range_timer (GtkRange *range)
+static void
+gtk_range_calc_request (GtkRange      *range,
+                        gint           slider_width,
+                        gint           stepper_size,
+                        gint           focus_width,
+                        gint           trough_border,
+                        gint           stepper_spacing,
+                        GdkRectangle  *range_rect,
+                        GtkBorder     *border,
+                        gint          *n_steppers_p,
+                        gboolean      *has_steppers_ab,
+                        gboolean      *has_steppers_cd,
+                        gint          *slider_length_p)
 {
-  gint return_val;
+  gint slider_length;
+  gint n_steppers;
+  gint n_steppers_ab;
+  gint n_steppers_cd;
+
+  border->left = 0;
+  border->right = 0;
+  border->top = 0;
+  border->bottom = 0;
+
+  if (GTK_RANGE_GET_CLASS (range)->get_range_border)
+    (* GTK_RANGE_GET_CLASS (range)->get_range_border) (range, border);
+
+  n_steppers_ab = 0;
+  n_steppers_cd = 0;
+
+  if (range->has_stepper_a)
+    n_steppers_ab += 1;
+  if (range->has_stepper_b)
+    n_steppers_ab += 1;
+  if (range->has_stepper_c)
+    n_steppers_cd += 1;
+  if (range->has_stepper_d)
+    n_steppers_cd += 1;
+
+  n_steppers = n_steppers_ab + n_steppers_cd;
 
-  GDK_THREADS_ENTER ();
+  slider_length = range->min_slider_size;
 
-  return_val = TRUE;
-  if (range->click_child == RANGE_CLASS (range)->slider)
+  range_rect->x = 0;
+  range_rect->y = 0;
+  
+  /* We never expand to fill available space in the small dimension
+   * (i.e. vertical scrollbars are always a fixed width)
+   */
+  if (range->orientation == GTK_ORIENTATION_VERTICAL)
     {
-      if (range->policy == GTK_UPDATE_DELAYED)
-       gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed");
-      return_val = FALSE;
+      range_rect->width = (focus_width + trough_border) * 2 + slider_width;
+      range_rect->height = stepper_size * n_steppers + (focus_width + trough_border) * 2 + slider_length;
+
+      if (n_steppers_ab > 0)
+        range_rect->height += stepper_spacing;
+
+      if (n_steppers_cd > 0)
+        range_rect->height += stepper_spacing;
     }
   else
     {
-      GdkModifierType mods, mask;
+      range_rect->width = stepper_size * n_steppers + (focus_width + trough_border) * 2 + slider_length;
+      range_rect->height = (focus_width + trough_border) * 2 + slider_width;
 
-      if (!range->timer)
-       {
-         return_val = FALSE;
-         if (range->need_timer)
-           range->timer = gtk_timeout_add (SCROLL_TIMER_LENGTH,
-                                           (GtkFunction) RANGE_CLASS (range)->timer,
-                                           (gpointer) range);
-         else
-           {
-             GDK_THREADS_LEAVE ();
-             return FALSE;
-           }
-         range->need_timer = FALSE;
-       }
+      if (n_steppers_ab > 0)
+        range_rect->width += stepper_spacing;
 
-      switch (range->button)
-       {
-       case 1:
-         mask = GDK_BUTTON1_MASK;
-         break;
-       case 2:
-         mask = GDK_BUTTON2_MASK;
-         break;
-       case 3:
-         mask = GDK_BUTTON3_MASK;
-         break;
-       default:
-         mask = 0;
-         break;
-       }
+      if (n_steppers_cd > 0)
+        range_rect->width += stepper_spacing;
+    }
 
-      gdk_window_get_pointer (range->slider, NULL, NULL, &mods);
+  if (n_steppers_p)
+    *n_steppers_p = n_steppers;
 
-      if (mods & mask)
-       return_val = gtk_range_scroll (range, -1);
-    }
+  if (has_steppers_ab)
+    *has_steppers_ab = (n_steppers_ab > 0);
 
-  GDK_THREADS_LEAVE ();
+  if (has_steppers_cd)
+    *has_steppers_cd = (n_steppers_cd > 0);
 
-  return return_val;
+  if (slider_length_p)
+    *slider_length_p = slider_length;
 }
 
-static gint
-gtk_range_scroll (GtkRange *range,
-                 gfloat    jump_perc)
+static void
+gtk_range_calc_layout (GtkRange *range,
+                      gdouble   adjustment_value)
 {
-  gfloat new_value;
-  gint return_val;
-  GtkScrollType scroll_type;
+  gint slider_width, stepper_size, focus_width, trough_border, stepper_spacing;
+  gint slider_length;
+  GtkBorder border;
+  gint n_steppers;
+  gboolean has_steppers_ab;
+  gboolean has_steppers_cd;
+  gboolean trough_under_steppers;
+  GdkRectangle range_rect;
+  GtkRangeLayout *layout;
+  GtkWidget *widget;
   
-  g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
-
-  new_value = range->adjustment->value;
-  return_val = TRUE;
+  if (!range->need_recalc)
+    return;
 
-  /* Translate visual to logical */
+  /* If we have a too-small allocation, we prefer the steppers over
+   * the trough/slider, probably the steppers are a more useful
+   * feature in small spaces.
+   *
+   * Also, we prefer to draw the range itself rather than the border
+   * areas if there's a conflict, since the borders will be decoration
+   * not controls. Though this depends on subclasses cooperating by
+   * not drawing on range->range_rect.
+   */
 
-  scroll_type = range->scroll_type;
-  switch (scroll_type)
+  widget = GTK_WIDGET (range);
+  layout = range->layout;
+  
+  gtk_range_get_props (range,
+                       &slider_width, &stepper_size,
+                       &focus_width, &trough_border,
+                       &stepper_spacing, &trough_under_steppers,
+                      NULL, NULL);
+
+  gtk_range_calc_request (range, 
+                          slider_width, stepper_size,
+                          focus_width, trough_border, stepper_spacing,
+                          &range_rect, &border, &n_steppers,
+                          &has_steppers_ab, &has_steppers_cd, &slider_length);
+  
+  /* We never expand to fill available space in the small dimension
+   * (i.e. vertical scrollbars are always a fixed width)
+   */
+  if (range->orientation == GTK_ORIENTATION_VERTICAL)
     {
-    case GTK_SCROLL_STEP_UP:
-      if (should_invert (range, FALSE))
-        scroll_type = GTK_SCROLL_STEP_FORWARD;
+      clamp_dimensions (widget, &range_rect, &border, TRUE);
+    }
+  else
+    {
+      clamp_dimensions (widget, &range_rect, &border, FALSE);
+    }
+  
+  range_rect.x = border.left;
+  range_rect.y = border.top;
+  
+  range->range_rect = range_rect;
+  
+  if (range->orientation == GTK_ORIENTATION_VERTICAL)
+    {
+      gint stepper_width, stepper_height;
+
+      /* Steppers are the width of the range, and stepper_size in
+       * height, or if we don't have enough height, divided equally
+       * among available space.
+       */
+      stepper_width = range_rect.width - focus_width * 2;
+
+      if (trough_under_steppers)
+        stepper_width -= trough_border * 2;
+
+      if (stepper_width < 1)
+        stepper_width = range_rect.width; /* screw the trough border */
+
+      if (n_steppers == 0)
+        stepper_height = 0; /* avoid divide by n_steppers */
       else
-        scroll_type = GTK_SCROLL_STEP_BACKWARD;
-      break;
+        stepper_height = MIN (stepper_size, (range_rect.height / n_steppers));
 
-    case GTK_SCROLL_STEP_DOWN:
-      if (should_invert (range, FALSE))
-        scroll_type = GTK_SCROLL_STEP_BACKWARD;
+      /* Stepper A */
+      
+      layout->stepper_a.x = range_rect.x + focus_width + trough_border * trough_under_steppers;
+      layout->stepper_a.y = range_rect.y + focus_width + trough_border * trough_under_steppers;
+
+      if (range->has_stepper_a)
+        {
+          layout->stepper_a.width = stepper_width;
+          layout->stepper_a.height = stepper_height;
+        }
       else
-        scroll_type = GTK_SCROLL_STEP_FORWARD;
-      break;
+        {
+          layout->stepper_a.width = 0;
+          layout->stepper_a.height = 0;
+        }
 
-    case GTK_SCROLL_PAGE_UP:
-      if (should_invert (range, FALSE))
-        scroll_type = GTK_SCROLL_PAGE_FORWARD;
+      /* Stepper B */
+      
+      layout->stepper_b.x = layout->stepper_a.x;
+      layout->stepper_b.y = layout->stepper_a.y + layout->stepper_a.height;
+
+      if (range->has_stepper_b)
+        {
+          layout->stepper_b.width = stepper_width;
+          layout->stepper_b.height = stepper_height;
+        }
       else
-        scroll_type = GTK_SCROLL_PAGE_BACKWARD;
-      break;
+        {
+          layout->stepper_b.width = 0;
+          layout->stepper_b.height = 0;
+        }
+
+      /* Stepper D */
+
+      if (range->has_stepper_d)
+        {
+          layout->stepper_d.width = stepper_width;
+          layout->stepper_d.height = stepper_height;
+        }
+      else
+        {
+          layout->stepper_d.width = 0;
+          layout->stepper_d.height = 0;
+        }
+      
+      layout->stepper_d.x = layout->stepper_a.x;
+      layout->stepper_d.y = range_rect.y + range_rect.height - layout->stepper_d.height - focus_width - trough_border * trough_under_steppers;
 
-    case GTK_SCROLL_PAGE_DOWN:
-      if (should_invert (range, FALSE))
-        scroll_type = GTK_SCROLL_PAGE_BACKWARD;
+      /* Stepper C */
+
+      if (range->has_stepper_c)
+        {
+          layout->stepper_c.width = stepper_width;
+          layout->stepper_c.height = stepper_height;
+        }
       else
-        scroll_type = GTK_SCROLL_PAGE_FORWARD;
-      break;
+        {
+          layout->stepper_c.width = 0;
+          layout->stepper_c.height = 0;
+        }
+      
+      layout->stepper_c.x = layout->stepper_a.x;
+      layout->stepper_c.y = layout->stepper_d.y - layout->stepper_c.height;
 
-    case GTK_SCROLL_STEP_LEFT:
-      if (should_invert (range, TRUE))
-        scroll_type = GTK_SCROLL_STEP_FORWARD;
+      /* Now the trough is the remaining space between steppers B and C,
+       * if any, minus spacing
+       */
+      layout->trough.x = range_rect.x;
+      layout->trough.y = layout->stepper_b.y + layout->stepper_b.height + stepper_spacing * has_steppers_ab;
+      layout->trough.width = range_rect.width;
+      layout->trough.height = layout->stepper_c.y - layout->trough.y - stepper_spacing * has_steppers_cd;
+
+      /* Slider fits into the trough, with stepper_spacing on either side,
+       * and the size/position based on the adjustment or fixed, depending.
+       */
+      layout->slider.x = layout->trough.x + focus_width + trough_border;
+      layout->slider.width = layout->trough.width - (focus_width + trough_border) * 2;
+
+      /* Compute slider position/length */
+      {
+        gint y, bottom, top, height;
+        
+        top = layout->trough.y;
+        bottom = layout->trough.y + layout->trough.height;
+
+        if (! trough_under_steppers)
+          {
+            top += trough_border;
+            bottom -= trough_border;
+          }
+
+        /* slider height is the fraction (page_size /
+         * total_adjustment_range) times the trough height in pixels
+         */
+
+       if (range->adjustment->upper - range->adjustment->lower != 0)
+         height = ((bottom - top) * (range->adjustment->page_size /
+                                      (range->adjustment->upper - range->adjustment->lower)));
+       else
+         height = range->min_slider_size;
+        
+        if (height < range->min_slider_size ||
+            range->slider_size_fixed)
+          height = range->min_slider_size;
+
+        height = MIN (height, layout->trough.height);
+        
+        y = top;
+        
+       if (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size != 0)
+         y += (bottom - top - height) * ((adjustment_value - range->adjustment->lower) /
+                                         (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
+        
+        y = CLAMP (y, top, bottom);
+        
+        if (should_invert (range))
+          y = bottom - (y - top + height);
+        
+        layout->slider.y = y;
+        layout->slider.height = height;
+
+        /* These are publically exported */
+        range->slider_start = layout->slider.y;
+        range->slider_end = layout->slider.y + layout->slider.height;
+      }
+    }
+  else
+    {
+      gint stepper_width, stepper_height;
+
+      /* Steppers are the height of the range, and stepper_size in
+       * width, or if we don't have enough width, divided equally
+       * among available space.
+       */
+      stepper_height = range_rect.height + focus_width * 2;
+
+      if (trough_under_steppers)
+        stepper_height -= trough_border * 2;
+
+      if (stepper_height < 1)
+        stepper_height = range_rect.height; /* screw the trough border */
+
+      if (n_steppers == 0)
+        stepper_width = 0; /* avoid divide by n_steppers */
       else
-        scroll_type = GTK_SCROLL_STEP_BACKWARD;
-      break;
+        stepper_width = MIN (stepper_size, (range_rect.width / n_steppers));
 
-    case GTK_SCROLL_STEP_RIGHT:
-      if (should_invert (range, TRUE))
-        scroll_type = GTK_SCROLL_STEP_BACKWARD;
+      /* Stepper A */
+      
+      layout->stepper_a.x = range_rect.x + focus_width + trough_border * trough_under_steppers;
+      layout->stepper_a.y = range_rect.y + focus_width + trough_border * trough_under_steppers;
+
+      if (range->has_stepper_a)
+        {
+          layout->stepper_a.width = stepper_width;
+          layout->stepper_a.height = stepper_height;
+        }
       else
-        scroll_type = GTK_SCROLL_STEP_FORWARD;
-      break;
+        {
+          layout->stepper_a.width = 0;
+          layout->stepper_a.height = 0;
+        }
 
-    case GTK_SCROLL_PAGE_LEFT:
-      if (should_invert (range, TRUE))
-        scroll_type = GTK_SCROLL_PAGE_FORWARD;
+      /* Stepper B */
+      
+      layout->stepper_b.x = layout->stepper_a.x + layout->stepper_a.width;
+      layout->stepper_b.y = layout->stepper_a.y;
+
+      if (range->has_stepper_b)
+        {
+          layout->stepper_b.width = stepper_width;
+          layout->stepper_b.height = stepper_height;
+        }
       else
-        scroll_type = GTK_SCROLL_PAGE_BACKWARD;
-      break;
+        {
+          layout->stepper_b.width = 0;
+          layout->stepper_b.height = 0;
+        }
+
+      /* Stepper D */
+
+      if (range->has_stepper_d)
+        {
+          layout->stepper_d.width = stepper_width;
+          layout->stepper_d.height = stepper_height;
+        }
+      else
+        {
+          layout->stepper_d.width = 0;
+          layout->stepper_d.height = 0;
+        }
 
-    case GTK_SCROLL_PAGE_RIGHT:
-      if (should_invert (range, TRUE))
-        scroll_type = GTK_SCROLL_PAGE_BACKWARD;
+      layout->stepper_d.x = range_rect.x + range_rect.width - layout->stepper_d.width - focus_width - trough_border * trough_under_steppers;
+      layout->stepper_d.y = layout->stepper_a.y;
+
+
+      /* Stepper C */
+
+      if (range->has_stepper_c)
+        {
+          layout->stepper_c.width = stepper_width;
+          layout->stepper_c.height = stepper_height;
+        }
       else
-        scroll_type = GTK_SCROLL_PAGE_FORWARD;
-      break;
+        {
+          layout->stepper_c.width = 0;
+          layout->stepper_c.height = 0;
+        }
+      
+      layout->stepper_c.x = layout->stepper_d.x - layout->stepper_c.width;
+      layout->stepper_c.y = layout->stepper_a.y;
 
-    default:
-      break;
+      /* Now the trough is the remaining space between steppers B and C,
+       * if any
+       */
+      layout->trough.x = layout->stepper_b.x + layout->stepper_b.width + stepper_spacing * has_steppers_ab;
+      layout->trough.y = range_rect.y;
+
+      layout->trough.width = layout->stepper_c.x - layout->trough.x - stepper_spacing * has_steppers_cd;
+      layout->trough.height = range_rect.height;
+
+      /* Slider fits into the trough, with stepper_spacing on either side,
+       * and the size/position based on the adjustment or fixed, depending.
+       */
+      layout->slider.y = layout->trough.y + focus_width + trough_border;
+      layout->slider.height = layout->trough.height - (focus_width + trough_border) * 2;
+
+      /* Compute slider position/length */
+      {
+        gint x, left, right, width;
+        
+        left = layout->trough.x;
+        right = layout->trough.x + layout->trough.width;
+
+        if (! trough_under_steppers)
+          {
+            left += trough_border;
+            right -= trough_border;
+          }
+
+        /* slider width is the fraction (page_size /
+         * total_adjustment_range) times the trough width in pixels
+         */
+       
+       if (range->adjustment->upper - range->adjustment->lower != 0)
+         width = ((right - left) * (range->adjustment->page_size /
+                                   (range->adjustment->upper - range->adjustment->lower)));
+       else
+         width = range->min_slider_size;
+        
+        if (width < range->min_slider_size ||
+            range->slider_size_fixed)
+          width = range->min_slider_size;
+        
+        width = MIN (width, layout->trough.width);
+        
+        x = left;
+        
+       if (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size != 0)
+          x += (right - left - width) * ((adjustment_value - range->adjustment->lower) /
+                                         (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
+        
+        x = CLAMP (x, left, right);
+        
+        if (should_invert (range))
+          x = right - (x - left + width);
+        
+        layout->slider.x = x;
+        layout->slider.width = width;
+
+        /* These are publically exported */
+        range->slider_start = layout->slider.x;
+        range->slider_end = layout->slider.x + layout->slider.width;
+      }
     }
   
-  switch (scroll_type)
+  gtk_range_update_mouse_location (range);
+
+  switch (range->layout->upper_sensitivity)
     {
-    case GTK_SCROLL_NONE:
-      break;
-      
-    case GTK_SCROLL_JUMP:
-      if (jump_perc >= 0 && jump_perc <= 1)
-       {
-         new_value = (range->adjustment->lower +
-                      (range->adjustment->upper - range->adjustment->page_size -
-                       range->adjustment->lower) * jump_perc);
-       }
-      break;
-      
-    case GTK_SCROLL_STEP_BACKWARD:
-      new_value -= range->adjustment->step_increment;
-      if (new_value <= range->adjustment->lower)
-       {
-         new_value = range->adjustment->lower;
-         return_val = FALSE;
-         range->timer = 0;
-       }
+    case GTK_SENSITIVITY_AUTO:
+      range->layout->upper_sensitive =
+        (range->adjustment->value <
+         (range->adjustment->upper - range->adjustment->page_size));
       break;
 
-    case GTK_SCROLL_STEP_FORWARD:
-      new_value += range->adjustment->step_increment;
-      if (new_value >= (range->adjustment->upper - range->adjustment->page_size))
-       {
-         new_value = range->adjustment->upper - range->adjustment->page_size;
-         return_val = FALSE;
-         range->timer = 0;
-       }
+    case GTK_SENSITIVITY_ON:
+      range->layout->upper_sensitive = TRUE;
       break;
 
-    case GTK_SCROLL_PAGE_BACKWARD:
-      new_value -= range->adjustment->page_increment;
-      if (new_value <= range->adjustment->lower)
-       {
-         new_value = range->adjustment->lower;
-         return_val = FALSE;
-         range->timer = 0;
-       }
+    case GTK_SENSITIVITY_OFF:
+      range->layout->upper_sensitive = FALSE;
       break;
+    }
 
-    case GTK_SCROLL_PAGE_FORWARD:
-      new_value += range->adjustment->page_increment;
-      if (new_value >= (range->adjustment->upper - range->adjustment->page_size))
-       {
-         new_value = range->adjustment->upper - range->adjustment->page_size;
-         return_val = FALSE;
-         range->timer = 0;
-       }
+  switch (range->layout->lower_sensitivity)
+    {
+    case GTK_SENSITIVITY_AUTO:
+      range->layout->lower_sensitive =
+        (range->adjustment->value > range->adjustment->lower);
       break;
 
-    case GTK_SCROLL_STEP_UP:
-    case GTK_SCROLL_STEP_DOWN:
-    case GTK_SCROLL_PAGE_UP:
-    case GTK_SCROLL_PAGE_DOWN:
-    case GTK_SCROLL_STEP_LEFT:
-    case GTK_SCROLL_STEP_RIGHT:
-    case GTK_SCROLL_PAGE_LEFT:
-    case GTK_SCROLL_PAGE_RIGHT:
-      g_assert_not_reached ();
+    case GTK_SENSITIVITY_ON:
+      range->layout->lower_sensitive = TRUE;
       break;
 
+    case GTK_SENSITIVITY_OFF:
+      range->layout->lower_sensitive = FALSE;
+      break;
     }
-  
-  if (new_value != range->adjustment->value)
-    {
-      range->adjustment->value = new_value;
-
-      if ((range->policy == GTK_UPDATE_CONTINUOUS) ||
-         (!return_val && (range->policy == GTK_UPDATE_DELAYED)))
-       {
-         gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed");
-       }
-      else
-       {
-         gtk_range_slider_update (range);
-         gtk_range_clear_background (range);
-       }
-    }
-
-  return return_val;
 }
 
-
-static void
-gtk_range_add_timer (GtkRange *range)
+static GdkRectangle*
+get_area (GtkRange     *range,
+          MouseLocation location)
 {
-  g_return_if_fail (GTK_IS_RANGE (range));
-
-  if (!range->timer)
+  switch (location)
     {
-      range->need_timer = TRUE;
-      range->timer = gtk_timeout_add (SCROLL_INITIAL_DELAY,
-                                     (GtkFunction) RANGE_CLASS (range)->timer,
-                                     (gpointer) range);
+    case MOUSE_STEPPER_A:
+      return &range->layout->stepper_a;
+    case MOUSE_STEPPER_B:
+      return &range->layout->stepper_b;
+    case MOUSE_STEPPER_C:
+      return &range->layout->stepper_c;
+    case MOUSE_STEPPER_D:
+      return &range->layout->stepper_d;
+    case MOUSE_TROUGH:
+      return &range->layout->trough;
+    case MOUSE_SLIDER:
+      return &range->layout->slider;
+    case MOUSE_WIDGET:
+    case MOUSE_OUTSIDE:
+      break;
     }
-}
-
-static void
-gtk_range_remove_timer (GtkRange *range)
-{
-  g_return_if_fail (GTK_IS_RANGE (range));
 
-  if (range->timer)
-    {
-      gtk_timeout_remove (range->timer);
-      range->timer = 0;
-    }
-  range->need_timer = FALSE;
+  g_warning (G_STRLOC": bug");
+  return NULL;
 }
 
-static void
-gtk_range_adjustment_changed (GtkAdjustment *adjustment,
-                             gpointer       data)
+static gboolean
+gtk_range_real_change_value (GtkRange     *range,
+                             GtkScrollType scroll,
+                             gdouble       value)
 {
-  GtkRange *range;
+  /* potentially adjust the bounds _before we clamp */
+  g_signal_emit (range, signals[ADJUST_BOUNDS], 0, value);
 
-  g_return_if_fail (adjustment != NULL);
-  g_return_if_fail (data != NULL);
+  if (range->layout->restrict_to_fill_level)
+    value = MIN (value, MAX (range->adjustment->lower,
+                             range->layout->fill_level));
 
-  range = GTK_RANGE (data);
+  value = CLAMP (value, range->adjustment->lower,
+                 (range->adjustment->upper - range->adjustment->page_size));
 
-  if (((range->old_lower != adjustment->lower) ||
-       (range->old_upper != adjustment->upper) ||
-       (range->old_page_size != adjustment->page_size)) &&
-      (range->old_value == adjustment->value))
+  if (range->round_digits >= 0)
     {
-      if ((adjustment->lower == adjustment->upper) ||
-         (range->old_lower == (range->old_upper - range->old_page_size)))
-       {
-         adjustment->value = adjustment->lower;
-         gtk_signal_emit_by_name (GTK_OBJECT (adjustment), "value_changed");
-       }
-    }
+      gdouble power;
+      gint i;
 
-  if ((range->old_value != adjustment->value) ||
-      (range->old_lower != adjustment->lower) ||
-      (range->old_upper != adjustment->upper) ||
-      (range->old_page_size != adjustment->page_size))
+      i = range->round_digits;
+      power = 1;
+      while (i--)
+        power *= 10;
+      
+      value = floor ((value * power) + 0.5) / power;
+    }
+  
+  if (range->adjustment->value != value)
     {
-      gtk_range_slider_update (range);
-      gtk_range_clear_background (range);
+      range->need_recalc = TRUE;
 
-      range->old_value = adjustment->value;
-      range->old_lower = adjustment->lower;
-      range->old_upper = adjustment->upper;
-      range->old_page_size = adjustment->page_size;
+      gtk_widget_queue_draw (GTK_WIDGET (range));
+      
+      switch (range->update_policy)
+        {
+        case GTK_UPDATE_CONTINUOUS:
+          gtk_adjustment_set_value (range->adjustment, value);
+          break;
+
+          /* Delayed means we update after a period of inactivity */
+        case GTK_UPDATE_DELAYED:
+          gtk_range_reset_update_timer (range);
+          /* FALL THRU */
+
+          /* Discontinuous means we update on button release */
+        case GTK_UPDATE_DISCONTINUOUS:
+          /* don't emit value_changed signal */
+          range->adjustment->value = value;
+          range->update_pending = TRUE;
+          break;
+        }
     }
+  return FALSE;
 }
 
 static void
-gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
-                                   gpointer       data)
+gtk_range_update_value (GtkRange *range)
 {
-  GtkRange *range;
-
-  g_return_if_fail (adjustment != NULL);
-  g_return_if_fail (data != NULL);
-
-  range = GTK_RANGE (data);
-
-  if (range->old_value != adjustment->value)
+  gtk_range_remove_update_timer (range);
+  
+  if (range->update_pending)
     {
-      gtk_range_slider_update (range);
-      gtk_range_clear_background (range);
+      gtk_adjustment_value_changed (range->adjustment);
 
-      range->old_value = adjustment->value;
+      range->update_pending = FALSE;
     }
 }
 
-
-static void
-gtk_range_trough_hdims (GtkRange *range,
-                       gint     *left,
-                       gint     *right)
+struct _GtkRangeStepTimer
 {
-  gint trough_width;
-  gint slider_length;
-  gint tmp_width;
-  gint tleft;
-  gint tright;
-
-  g_return_if_fail (range != NULL);
+  guint timeout_id;
+  GtkScrollType step;
+};
 
-  gdk_window_get_size (range->trough, &trough_width, NULL);
-  gdk_window_get_size (range->slider, &slider_length, NULL);
+static gboolean
+second_timeout (gpointer data)
+{
+  GtkRange *range;
 
-  tleft = GTK_WIDGET (range)->style->xthickness;
-  tright = trough_width - slider_length - GTK_WIDGET (range)->style->xthickness;
+  range = GTK_RANGE (data);
+  gtk_range_scroll (range, range->timer->step);
+  
+  return TRUE;
+}
 
-  if (range->step_back)
-    {
-      gdk_window_get_size (range->step_back, &tmp_width, NULL);
-      tleft += (tmp_width + RANGE_CLASS (range)->stepper_slider_spacing);
-    }
+static gboolean
+initial_timeout (gpointer data)
+{
+  GtkRange    *range;
+  GtkSettings *settings;
+  guint        timeout;
 
-  if (range->step_forw)
-    {
-      gdk_window_get_size (range->step_forw, &tmp_width, NULL);
-      tright -= (tmp_width + RANGE_CLASS (range)->stepper_slider_spacing);
-    }
+  settings = gtk_widget_get_settings (GTK_WIDGET (data));
+  g_object_get (settings, "gtk-timeout-repeat", &timeout, NULL);
 
-  if (left)
-    *left = tleft;
-  if (right)
-    *right = tright;
+  range = GTK_RANGE (data);
+  range->timer->timeout_id = gdk_threads_add_timeout (timeout * SCROLL_DELAY_FACTOR,
+                                            second_timeout,
+                                            range);
+  /* remove self */
+  return FALSE;
 }
 
 static void
-gtk_range_trough_vdims (GtkRange *range,
-                       gint     *top,
-                       gint     *bottom)
+gtk_range_add_step_timer (GtkRange      *range,
+                          GtkScrollType  step)
 {
-  gint trough_height;
-  gint slider_length;
-  gint tmp_height;
-  gint ttop;
-  gint tbottom;
+  GtkSettings *settings;
+  guint        timeout;
 
-  g_return_if_fail (range != NULL);
+  g_return_if_fail (range->timer == NULL);
+  g_return_if_fail (step != GTK_SCROLL_NONE);
 
-  gdk_window_get_size (range->trough, NULL, &trough_height);
-  gdk_window_get_size (range->slider, NULL, &slider_length);
+  settings = gtk_widget_get_settings (GTK_WIDGET (range));
+  g_object_get (settings, "gtk-timeout-initial", &timeout, NULL);
 
-  ttop = GTK_WIDGET (range)->style->ythickness;
-  tbottom = trough_height - slider_length - GTK_WIDGET (range)->style->ythickness;
+  range->timer = g_new (GtkRangeStepTimer, 1);
 
-  if (range->step_back)
-    {
-      gdk_window_get_size (range->step_back, NULL, &tmp_height);
-      ttop += (tmp_height + RANGE_CLASS (range)->stepper_slider_spacing);
-    }
+  range->timer->timeout_id = gdk_threads_add_timeout (timeout,
+                                            initial_timeout,
+                                            range);
+  range->timer->step = step;
+
+  gtk_range_scroll (range, range->timer->step);
+}
 
-  if (range->step_forw)
+static void
+gtk_range_remove_step_timer (GtkRange *range)
+{
+  if (range->timer)
     {
-      gdk_window_get_size (range->step_forw, NULL, &tmp_height);
-      tbottom -= (tmp_height + RANGE_CLASS (range)->stepper_slider_spacing);
-    }
+      if (range->timer->timeout_id != 0)
+        g_source_remove (range->timer->timeout_id);
 
-  if (top)
-    *top = ttop;
-  if (bottom)
-    *bottom = tbottom;
+      g_free (range->timer);
+
+      range->timer = NULL;
+    }
 }
 
-static void
-gtk_range_style_set (GtkWidget *widget,
-                     GtkStyle  *previous_style)
+static gboolean
+update_timeout (gpointer data)
 {
   GtkRange *range;
 
-  g_return_if_fail (widget != NULL);
-  g_return_if_fail (GTK_IS_RANGE (widget));
-
-  range = GTK_RANGE (widget);
+  range = GTK_RANGE (data);
+  gtk_range_update_value (range);
+  range->update_timeout_id = 0;
 
-  if (GTK_WIDGET_REALIZED (widget))
-    {
-      if (range->trough)
-       gtk_style_set_background (widget->style, range->trough, GTK_STATE_ACTIVE);
+  /* self-remove */
+  return FALSE;
+}
 
-      if (range->slider)
-       gtk_style_set_background (widget->style, range->slider, GTK_STATE_NORMAL);
-      
-      /* The backgrounds of the step_forw and step_back never actually
-       * get drawn in draw calls, so we call gdk_window_clear() here
-       * so they get the correct colors. This is a hack. OWT.
-       */
+static void
+gtk_range_reset_update_timer (GtkRange *range)
+{
+  gtk_range_remove_update_timer (range);
 
-      if (range->step_forw)
-       {
-         gtk_style_set_background (widget->style, range->step_forw, GTK_STATE_ACTIVE);
-         gdk_window_clear (range->step_forw);
-       }
+  range->update_timeout_id = gdk_threads_add_timeout (UPDATE_DELAY,
+                                            update_timeout,
+                                            range);
+}
 
-      if (range->step_back)
-       {
-         gtk_style_set_background (widget->style, range->step_back, GTK_STATE_ACTIVE);
-         gdk_window_clear (range->step_back);
-       }
+static void
+gtk_range_remove_update_timer (GtkRange *range)
+{
+  if (range->update_timeout_id != 0)
+    {
+      g_source_remove (range->update_timeout_id);
+      range->update_timeout_id = 0;
     }
 }
+
+#define __GTK_RANGE_C__
+#include "gtkaliasdef.c"