]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtkrange.c
Added per-stepper API for GtkRange's stepper sensitivity as discussed in
[~andy/gtk] / gtk / gtkrange.c
index e5b36f65d86d148ff5474b00b4287e4cfdd5f46e..f9c62932a59f23e9883600a0ef557454de0df6aa 100644 (file)
  */
 
 /*
- * 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 "gtkintl.h"
 #include "gtkmain.h"
 #include "gtkmarshalers.h"
 #include "gtkrange.h"
-#include "gtksignal.h"
 #include "gtkintl.h"
+#include "gtkscrollbar.h"
+#include "gtkprivate.h"
+#include "gtkalias.h"
 
 #define SCROLL_INITIAL_DELAY 250  /* must hold button this long before ... */
 #define SCROLL_LATER_DELAY   100  /* ... it starts repeating at this rate  */
@@ -41,13 +45,16 @@ enum {
   PROP_0,
   PROP_UPDATE_POLICY,
   PROP_ADJUSTMENT,
-  PROP_INVERTED
+  PROP_INVERTED,
+  PROP_LOWER_STEPPER_SENSITIVITY,
+  PROP_UPPER_STEPPER_SENSITIVITY
 };
 
 enum {
   VALUE_CHANGED,
   ADJUST_BOUNDS,
   MOVE_SLIDER,
+  CHANGE_VALUE,
   LAST_SIGNAL
 };
 
@@ -84,6 +91,10 @@ struct _GtkRangeLayout
   /* "grabbed" mouse location, OUTSIDE for no grab */
   MouseLocation grab_location;
   gint grab_button; /* 0 if none */
+
+  /* Stepper sensitivity */
+  GtkSensitivityType lower_sensitivity;
+  GtkSensitivityType upper_sensitivity;
 };
 
 
@@ -119,6 +130,12 @@ 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,
@@ -126,7 +143,7 @@ static void gtk_range_style_set      (GtkWidget        *widget,
 static void update_slider_position   (GtkRange        *range,
                                      gint              mouse_x,
                                      gint              mouse_y);
-
+static void stop_scrolling           (GtkRange         *range);
 
 /* Range methods */
 
@@ -166,7 +183,8 @@ 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 void          gtk_range_internal_set_value       (GtkRange      *range,
+static gboolean      gtk_range_real_change_value        (GtkRange      *range,
+                                                         GtkScrollType  scroll,
                                                          gdouble        value);
 static void          gtk_range_update_value             (GtkRange      *range);
 
@@ -175,28 +193,28 @@ static GtkWidgetClass *parent_class = NULL;
 static guint signals[LAST_SIGNAL];
 
 
-GtkType
+GType
 gtk_range_get_type (void)
 {
-  static GtkType range_type = 0;
+  static GType range_type = 0;
 
   if (!range_type)
     {
       static const GTypeInfo range_info =
       {
        sizeof (GtkRangeClass),
-       NULL,            /* base_init */
-       NULL,            /* base_finalize */
+       NULL,           /* base_init */
+       NULL,           /* base_finalize */
        (GClassInitFunc) gtk_range_class_init,
-       NULL,            /* class_finalize */
-       NULL,            /* class_data */
+       NULL,           /* class_finalize */
+       NULL,           /* class_data */
        sizeof (GtkRange),
-       0,               /* n_preallocs */
+       0,              /* n_preallocs */
        (GInstanceInitFunc) gtk_range_init,
-       NULL,            /* value_table */
+       NULL,           /* value_table */
       };
 
-      range_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkRange",
+      range_type = g_type_register_static (GTK_TYPE_WIDGET, I_("GtkRange"),
                                           &range_info, G_TYPE_FLAG_ABSTRACT);
     }
 
@@ -234,16 +252,20 @@ gtk_range_class_init (GtkRangeClass *class)
   widget_class->scroll_event = gtk_range_scroll_event;
   widget_class->enter_notify_event = gtk_range_enter_notify;
   widget_class->leave_notify_event = gtk_range_leave_notify;
+  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;
 
   class->move_slider = gtk_range_move_slider;
+  class->change_value = gtk_range_real_change_value;
 
   class->slider_detail = "slider";
   class->stepper_detail = "stepper";
 
   signals[VALUE_CHANGED] =
-    g_signal_new ("value_changed",
-                  G_TYPE_FROM_CLASS (object_class),
+    g_signal_new (I_("value_changed"),
+                  G_TYPE_FROM_CLASS (gobject_class),
                   G_SIGNAL_RUN_LAST,
                   G_STRUCT_OFFSET (GtkRangeClass, value_changed),
                   NULL, NULL,
@@ -251,8 +273,8 @@ gtk_range_class_init (GtkRangeClass *class)
                   G_TYPE_NONE, 0);
   
   signals[ADJUST_BOUNDS] =
-    g_signal_new ("adjust_bounds",
-                  G_TYPE_FROM_CLASS (object_class),
+    g_signal_new (I_("adjust_bounds"),
+                  G_TYPE_FROM_CLASS (gobject_class),
                   G_SIGNAL_RUN_LAST,
                   G_STRUCT_OFFSET (GtkRangeClass, adjust_bounds),
                   NULL, NULL,
@@ -261,88 +283,143 @@ gtk_range_class_init (GtkRangeClass *class)
                   G_TYPE_DOUBLE);
   
   signals[MOVE_SLIDER] =
-    g_signal_new ("move_slider",
-                  G_TYPE_FROM_CLASS (object_class),
+    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",
-                                                     _("Update policy"),
-                                                     _("How the range should be updated on the screen"),
+                                   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,
-                                                     G_PARAM_READWRITE));
+                                                     GTK_PARAM_READWRITE));
   
   g_object_class_install_property (gobject_class,
                                    PROP_ADJUSTMENT,
                                    g_param_spec_object ("adjustment",
-                                                       _("Adjustment"),
-                                                       _("The GtkAdjustment that contains the current value of this range object"),
+                                                       P_("Adjustment"),
+                                                       P_("The GtkAdjustment that contains the current value of this range object"),
                                                         GTK_TYPE_ADJUSTMENT,
-                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+                                                        GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 
   g_object_class_install_property (gobject_class,
                                    PROP_INVERTED,
                                    g_param_spec_boolean ("inverted",
-                                                       _("Inverted"),
-                                                       _("Invert direction slider moves to increase range value"),
+                                                       P_("Inverted"),
+                                                       P_("Invert direction slider moves to increase range value"),
                                                          FALSE,
-                                                         G_PARAM_READWRITE));
-  
+                                                         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));
+
   gtk_widget_class_install_style_property (widget_class,
-                                          g_param_spec_int ("slider_width",
-                                                            _("Slider Width"),
-                                                            _("Width of scrollbar or scale thumb"),
+                                          g_param_spec_int ("slider-width",
+                                                            P_("Slider Width"),
+                                                            P_("Width of scrollbar or scale thumb"),
                                                             0,
                                                             G_MAXINT,
                                                             14,
-                                                            G_PARAM_READABLE));
+                                                            GTK_PARAM_READABLE));
   gtk_widget_class_install_style_property (widget_class,
-                                          g_param_spec_int ("trough_border",
-                                                             _("Trough Border"),
-                                                             _("Spacing between thumb/steppers and outer trough bevel"),
+                                          g_param_spec_int ("trough-border",
+                                                             P_("Trough Border"),
+                                                             P_("Spacing between thumb/steppers and outer trough bevel"),
                                                              0,
                                                              G_MAXINT,
                                                              1,
-                                                             G_PARAM_READABLE));
+                                                             GTK_PARAM_READABLE));
   gtk_widget_class_install_style_property (widget_class,
-                                          g_param_spec_int ("stepper_size",
-                                                            _("Stepper Size"),
-                                                            _("Length of step buttons at ends"),
+                                          g_param_spec_int ("stepper-size",
+                                                            P_("Stepper Size"),
+                                                            P_("Length of step buttons at ends"),
                                                             0,
                                                             G_MAXINT,
                                                             14,
-                                                            G_PARAM_READABLE));
+                                                            GTK_PARAM_READABLE));
   gtk_widget_class_install_style_property (widget_class,
-                                          g_param_spec_int ("stepper_spacing",
-                                                            _("Stepper Spacing"),
-                                                            _("Spacing between step buttons and thumb"),
+                                          g_param_spec_int ("stepper-spacing",
+                                                            P_("Stepper Spacing"),
+                                                            P_("Spacing between step buttons and thumb"),
                                                              0,
                                                             G_MAXINT,
                                                             0,
-                                                            G_PARAM_READABLE));
+                                                            GTK_PARAM_READABLE));
   gtk_widget_class_install_style_property (widget_class,
-                                          g_param_spec_int ("arrow_displacement_x",
-                                                            _("Arrow X Displacement"),
-                                                            _("How far in the x direction to move the arrow when the button is depressed"),
+                                          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,
-                                                            G_PARAM_READABLE));
+                                                            GTK_PARAM_READABLE));
   gtk_widget_class_install_style_property (widget_class,
-                                          g_param_spec_int ("arrow_displacement_y",
-                                                            _("Arrow Y Displacement"),
-                                                            _("How far in the y direction to move the arrow when the button is depressed"),
+                                          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,
-                                                            G_PARAM_READABLE));
+                                                            GTK_PARAM_READABLE));
 }
 
 static void
@@ -366,6 +443,12 @@ gtk_range_set_property (GObject      *object,
     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;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -393,6 +476,12 @@ gtk_range_get_property (GObject      *object,
     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;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -421,6 +510,8 @@ gtk_range_init (GtkRange *range)
   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->timer = NULL;  
 }
 
@@ -470,7 +561,7 @@ gtk_range_set_update_policy (GtkRange      *range,
   if (range->update_policy != policy)
     {
       range->update_policy = policy;
-      g_object_notify (G_OBJECT (range), "update_policy");
+      g_object_notify (G_OBJECT (range), "update-policy");
     }
 }
 
@@ -519,25 +610,25 @@ gtk_range_set_adjustment (GtkRange      *range,
     {
       if (range->adjustment)
        {
-         gtk_signal_disconnect_by_func (GTK_OBJECT (range->adjustment),
-                                         G_CALLBACK (gtk_range_adjustment_changed),
-                                        range);
-          gtk_signal_disconnect_by_func (GTK_OBJECT (range->adjustment),
-                                         G_CALLBACK (gtk_range_adjustment_value_changed),
-                                        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));
+      g_object_ref (adjustment);
       gtk_object_sink (GTK_OBJECT (adjustment));
       
-      gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
-                         (GtkSignalFunc) gtk_range_adjustment_changed,
-                         range);
-      gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
-                         (GtkSignalFunc) gtk_range_adjustment_value_changed,
-                         range);
+      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, range);
       g_object_notify (G_OBJECT (range), "adjustment");
@@ -587,6 +678,92 @@ 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.
+ *
+ * Sine: 2.10
+ **/
+void
+gtk_range_set_lower_stepper_sensitivity (GtkRange           *range,
+                                         GtkSensitivityType  sensitivity)
+{
+  g_return_if_fail (GTK_IS_RANGE (range));
+
+  if (range->layout->lower_sensitivity != sensitivity)
+    {
+      range->layout->lower_sensitivity = sensitivity;
+      gtk_widget_queue_draw (GTK_WIDGET (range));
+      g_object_notify (G_OBJECT (range), "lower-stepper-sensitivity");
+    }
+}
+
+/**
+ * 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_val_if_fail (GTK_IS_RANGE (range), GTK_SENSITIVITY_AUTO);
+
+  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.
+ *
+ * Sine: 2.10
+ **/
+void
+gtk_range_set_upper_stepper_sensitivity (GtkRange           *range,
+                                         GtkSensitivityType  sensitivity)
+{
+  g_return_if_fail (GTK_IS_RANGE (range));
+
+  if (range->layout->upper_sensitivity != sensitivity)
+    {
+      range->layout->upper_sensitivity = sensitivity;
+      gtk_widget_queue_draw (GTK_WIDGET (range));
+      g_object_notify (G_OBJECT (range), "upper-stepper-sensitivity");
+    }
+}
+
+/**
+ * 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_val_if_fail (GTK_IS_RANGE (range), GTK_SENSITIVITY_AUTO);
+
+  return range->layout->upper_sensitivity;
+}
+
 /**
  * gtk_range_set_increments:
  * @range: a #GtkRange
@@ -714,10 +891,13 @@ gtk_range_destroy (GtkObject *object)
   
   if (range->adjustment)
     {
-      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 = NULL;
     }
 
@@ -782,7 +962,7 @@ gtk_range_realize (GtkWidget *widget)
   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
 
   widget->window = gtk_widget_get_parent_window (widget);
-  gdk_window_ref (widget->window);
+  g_object_ref (widget->window);
   
   attributes.window_type = GDK_WINDOW_CHILD;
   attributes.x = widget->allocation.x;
@@ -791,8 +971,7 @@ gtk_range_realize (GtkWidget *widget)
   attributes.height = widget->allocation.height;
   attributes.wclass = GDK_INPUT_ONLY;
   attributes.event_mask = gtk_widget_get_events (widget);
-  attributes.event_mask |= (GDK_EXPOSURE_MASK |
-                           GDK_BUTTON_PRESS_MASK |
+  attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
                            GDK_BUTTON_RELEASE_MASK |
                            GDK_ENTER_NOTIFY_MASK |
                            GDK_LEAVE_NOTIFY_MASK |
@@ -829,8 +1008,6 @@ gtk_range_map (GtkWidget *widget)
 {
   GtkRange *range = GTK_RANGE (widget);
   
-  g_return_if_fail (GTK_IS_RANGE (widget));
-
   gdk_window_show (range->event_window);
 
   GTK_WIDGET_CLASS (parent_class)->map (widget);
@@ -841,7 +1018,7 @@ gtk_range_unmap (GtkWidget *widget)
 {
   GtkRange *range = GTK_RANGE (widget);
     
-  g_return_if_fail (GTK_IS_RANGE (widget));
+  stop_scrolling (range);
 
   gdk_window_hide (range->event_window);
 
@@ -866,14 +1043,57 @@ draw_stepper (GtkRange     *range,
   gint arrow_width;
   gint arrow_height;
 
+  gboolean arrow_sensitive = TRUE;
+
   /* More to get the right clip region than for efficiency */
   if (!gdk_rectangle_intersect (area, rect, &intersection))
     return;
 
   intersection.x += widget->allocation.x;
   intersection.y += widget->allocation.y;
-  
-  if (!GTK_WIDGET_IS_SENSITIVE (range))
+
+  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)))
+    {
+      switch (range->layout->upper_sensitivity)
+        {
+        case GTK_SENSITIVITY_AUTO:
+          arrow_sensitive =
+            (range->adjustment->value <
+             (range->adjustment->upper - range->adjustment->page_size));
+          break;
+
+        case GTK_SENSITIVITY_ON:
+          arrow_sensitive = TRUE;
+          break;
+
+        case GTK_SENSITIVITY_OFF:
+          arrow_sensitive = FALSE;
+          break;
+        }
+    }
+  else
+    {
+      switch (range->layout->lower_sensitivity)
+        {
+        case GTK_SENSITIVITY_AUTO:
+          arrow_sensitive =
+            (range->adjustment->value > range->adjustment->lower);
+          break;
+
+        case GTK_SENSITIVITY_ON:
+          arrow_sensitive = TRUE;
+          break;
+
+        case GTK_SENSITIVITY_OFF:
+          arrow_sensitive = FALSE;
+          break;
+        }
+    }
+
+  if (!GTK_WIDGET_IS_SENSITIVE (range) || !arrow_sensitive)
     state_type = GTK_STATE_INSENSITIVE;
   else if (clicked)
     state_type = GTK_STATE_ACTIVE;
@@ -881,8 +1101,8 @@ draw_stepper (GtkRange     *range,
     state_type = GTK_STATE_PRELIGHT;
   else 
     state_type = GTK_STATE_NORMAL;
-  
-  if (clicked)
+
+  if (clicked && arrow_sensitive)
     shadow_type = GTK_SHADOW_IN;
   else
     shadow_type = GTK_SHADOW_OUT;
@@ -902,7 +1122,7 @@ draw_stepper (GtkRange     *range,
   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)
+  if (clicked && arrow_sensitive)
     {
       gint arrow_displacement_x;
       gint arrow_displacement_y;
@@ -1077,7 +1297,10 @@ range_grab_remove (GtkRange *range)
 
 static GtkScrollType
 range_get_scroll_for_grab (GtkRange      *range)
-{  
+{ 
+  gboolean invert;
+
+  invert = should_invert (range);
   switch (range->layout->grab_location)
     {
       /* Backward stepper */
@@ -1086,13 +1309,13 @@ range_get_scroll_for_grab (GtkRange      *range)
       switch (range->layout->grab_button)
         {
         case 1:
-          return GTK_SCROLL_STEP_BACKWARD;
+          return invert ? GTK_SCROLL_STEP_FORWARD : GTK_SCROLL_STEP_BACKWARD;
           break;
         case 2:
-          return GTK_SCROLL_PAGE_BACKWARD;
+          return invert ? GTK_SCROLL_PAGE_FORWARD : GTK_SCROLL_PAGE_BACKWARD;
           break;
         case 3:
-          return GTK_SCROLL_START;
+          return invert ? GTK_SCROLL_END : GTK_SCROLL_START;
           break;
         }
       break;
@@ -1103,13 +1326,13 @@ range_get_scroll_for_grab (GtkRange      *range)
       switch (range->layout->grab_button)
         {
         case 1:
-          return GTK_SCROLL_STEP_FORWARD;
+          return invert ? GTK_SCROLL_STEP_BACKWARD : GTK_SCROLL_STEP_FORWARD;
           break;
         case 2:
-          return GTK_SCROLL_PAGE_FORWARD;
+          return invert ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_PAGE_FORWARD;
           break;
         case 3:
-          return GTK_SCROLL_END;
+          return invert ? GTK_SCROLL_START : GTK_SCROLL_END;
           break;
        }
       break;
@@ -1296,7 +1519,8 @@ update_slider_position (GtkRange *range,
   gint delta;
   gint c;
   gdouble new_value;
-  
+  gboolean handled;
+
   if (range->orientation == GTK_ORIENTATION_VERTICAL)
     delta = mouse_y - range->slide_initial_coordinate;
   else
@@ -1306,7 +1530,41 @@ update_slider_position (GtkRange *range,
 
   new_value = coord_to_value (range, c);
   
-  gtk_range_internal_set_value (range, new_value);
+  g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_JUMP, new_value,
+                 &handled);
+}
+
+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));
+}
+
+static gboolean
+gtk_range_grab_broken (GtkWidget          *widget,
+                      GdkEventGrabBroken *event)
+{
+  GtkRange *range = GTK_RANGE (widget);
+
+  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;
 }
 
 static gint
@@ -1330,23 +1588,10 @@ gtk_range_button_release (GtkWidget      *widget,
   
   if (range->layout->grab_button == event->button)
     {
-      MouseLocation grab_location;
-
-      grab_location = range->layout->grab_location;
-
-      range_grab_remove (range);
-      gtk_range_remove_step_timer (range);
-      
-      if (grab_location == MOUSE_SLIDER)
+      if (range->layout->grab_location == MOUSE_SLIDER)
         update_slider_position (range, range->layout->mouse_x, range->layout->mouse_y);
 
-      /* 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 (widget);
+      stop_scrolling (range);
       
       return TRUE;
     }
@@ -1354,6 +1599,39 @@ gtk_range_button_release (GtkWidget      *widget,
   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)
@@ -1363,19 +1641,18 @@ gtk_range_scroll_event (GtkWidget      *widget,
   if (GTK_WIDGET_REALIZED (range))
     {
       GtkAdjustment *adj = GTK_RANGE (range)->adjustment;
-      gdouble increment = ((event->direction == GDK_SCROLL_UP ||
-                           event->direction == GDK_SCROLL_LEFT) ? 
-                          -adj->page_increment / 2: 
-                          adj->page_increment / 2);
-      
-      if (range->inverted)
-       increment = -increment;
-         
-      gtk_range_internal_set_value (range, adj->value + increment);
+      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 DISCONTINOUS
+       * for DISCONTINUOUS
        */
       if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
         gtk_range_update_value (range);
@@ -1438,14 +1715,58 @@ gtk_range_leave_notify (GtkWidget        *widget,
   return TRUE;
 }
 
+static void
+gtk_range_grab_notify (GtkWidget *widget,
+                      gboolean   was_grabbed)
+{
+  if (!was_grabbed)
+    stop_scrolling (GTK_RANGE (widget));
+}
+
+static void
+gtk_range_state_changed (GtkWidget    *widget,
+                        GtkStateType  previous_state)
+{
+  if (!GTK_WIDGET_IS_SENSITIVE (widget)) 
+    stop_scrolling (GTK_RANGE (widget));
+}
+
+#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; \
+  }
+
+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);
+
+  return FALSE;
+}
+
 static void
 gtk_range_adjustment_changed (GtkAdjustment *adjustment,
                              gpointer       data)
 {
   GtkRange *range = GTK_RANGE (data);
+  /* create a copy of the layout */
+  GtkRangeLayout layout = *range->layout;
 
   range->need_recalc = TRUE;
-  gtk_widget_queue_draw (GTK_WIDGET (range));
+  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
@@ -1461,13 +1782,21 @@ gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
                                    gpointer       data)
 {
   GtkRange *range = GTK_RANGE (data);
+  /* create a copy of the layout */
+  GtkRangeLayout layout = *range->layout;
 
   range->need_recalc = TRUE;
-
-  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);
+  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));
+      
+      /* 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);
+    }
   
   /* Note that we don't round off to range->round_digits here.
    * that's because it's really broken to change a value
@@ -1477,7 +1806,7 @@ gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
    * will enforce on the adjustment.
    */
 
-  g_signal_emit (G_OBJECT (range), signals[VALUE_CHANGED], 0);
+  g_signal_emit (range, signals[VALUE_CHANGED], 0);
 }
 
 static void
@@ -1495,19 +1824,22 @@ static void
 step_back (GtkRange *range)
 {
   gdouble newval;
+  gboolean handled;
   
   newval = range->adjustment->value - range->adjustment->step_increment;
-  gtk_range_internal_set_value (range, newval);
+  g_signal_emit (range, signals[CHANGE_VALUE], 0,
+                 GTK_SCROLL_STEP_BACKWARD, newval, &handled);
 }
 
 static void
 step_forward (GtkRange *range)
 {
   gdouble newval;
+  gboolean handled;
 
   newval = range->adjustment->value + range->adjustment->step_increment;
-
-  gtk_range_internal_set_value (range, newval);
+  g_signal_emit (range, signals[CHANGE_VALUE], 0,
+                 GTK_SCROLL_STEP_FORWARD, newval, &handled);
 }
 
 
@@ -1515,19 +1847,42 @@ static void
 page_back (GtkRange *range)
 {
   gdouble newval;
+  gboolean handled;
 
   newval = range->adjustment->value - range->adjustment->page_increment;
-  gtk_range_internal_set_value (range, newval);
+  g_signal_emit (range, signals[CHANGE_VALUE], 0,
+                 GTK_SCROLL_PAGE_BACKWARD, newval, &handled);
 }
 
 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);
+}
 
-  gtk_range_internal_set_value (range, newval);
+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
@@ -1609,13 +1964,11 @@ gtk_range_scroll (GtkRange     *range,
       break;
 
     case GTK_SCROLL_START:
-      gtk_range_internal_set_value (range,
-                                    range->adjustment->lower);
+      scroll_begin (range);
       break;
 
     case GTK_SCROLL_END:
-      gtk_range_internal_set_value (range,
-                                    range->adjustment->upper - range->adjustment->page_size);
+      scroll_end (range);
       break;
 
     case GTK_SCROLL_JUMP:
@@ -1635,7 +1988,7 @@ gtk_range_move_slider (GtkRange     *range,
 
   /* Policy DELAYED makes sense with key events,
    * but DISCONTINUOUS doesn't, so we update immediately
-   * for DISCONTINOUS
+   * for DISCONTINUOUS
    */
   if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
     gtk_range_update_value (range);
@@ -1655,12 +2008,12 @@ gtk_range_get_props (GtkRange  *range,
   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,
-                       "arrow_displacement_x", &tmp_arrow_displacement_x,
-                       "arrow_displacement_y", &tmp_arrow_displacement_y,
+                        "slider-width", &tmp_slider_width,
+                        "trough-border", &tmp_trough_border,
+                        "stepper-size", &tmp_stepper_size,
+                        "stepper-spacing", &tmp_stepper_spacing,
+                       "arrow-displacement-x", &tmp_arrow_displacement_x,
+                       "arrow-displacement-y", &tmp_arrow_displacement_y,
                         NULL);
   
   if (GTK_WIDGET_CAN_FOCUS (range))
@@ -2253,24 +2606,28 @@ get_area (GtkRange     *range,
   return NULL;
 }
 
-static void
-gtk_range_internal_set_value (GtkRange *range,
-                              gdouble   value)
+static gboolean
+gtk_range_real_change_value (GtkRange     *range,
+                             GtkScrollType scroll,
+                             gdouble       value)
 {
   /* potentially adjust the bounds _before we clamp */
-  g_signal_emit (G_OBJECT (range), signals[ADJUST_BOUNDS], 0, value);
+  g_signal_emit (range, signals[ADJUST_BOUNDS], 0, value);
 
   value = CLAMP (value, range->adjustment->lower,
                  (range->adjustment->upper - range->adjustment->page_size));
 
   if (range->round_digits >= 0)
     {
-      char buffer[128];
+      gdouble power;
+      gint i;
 
-      /* This is just so darn lame. */
-      g_snprintf (buffer, 128, "%0.*f",
-                  range->round_digits, value);
-      sscanf (buffer, "%lf", &value);
+      i = range->round_digits;
+      power = 1;
+      while (i--)
+        power *= 10;
+      
+      value = floor ((value * power) + 0.5) / power;
     }
   
   if (range->adjustment->value != value)
@@ -2298,6 +2655,7 @@ gtk_range_internal_set_value (GtkRange *range,
           break;
         }
     }
+  return FALSE;
 }
 
 static void
@@ -2415,3 +2773,6 @@ gtk_range_remove_update_timer (GtkRange *range)
       range->update_timeout_id = 0;
     }
 }
+
+#define __GTK_RANGE_C__
+#include "gtkaliasdef.c"