]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtkrange.c
add _gtk_tree_view_column_get_editable_cell and
[~andy/gtk] / gtk / gtkrange.c
index e813a143218543f19dde4edf77b414ebc7dc9476..bdc50b99df66c19677f2a327dbc2c22a78a246b1 100644 (file)
@@ -28,6 +28,7 @@
 #include <stdio.h>
 #include "gtkintl.h"
 #include "gtkmain.h"
+#include "gtkmarshalers.h"
 #include "gtkrange.h"
 #include "gtksignal.h"
 #include "gtkintl.h"
 enum {
   PROP_0,
   PROP_UPDATE_POLICY,
-  PROP_ADJUSTMENT
+  PROP_ADJUSTMENT,
+  PROP_INVERTED
 };
 
 enum {
+  VALUE_CHANGED,
+  ADJUST_BOUNDS,
   MOVE_SLIDER,
   LAST_SIGNAL
 };
@@ -82,6 +86,7 @@ struct _GtkRangeLayout
   gint grab_button; /* 0 if none */
 };
 
+
 static void gtk_range_class_init     (GtkRangeClass    *klass);
 static void gtk_range_init           (GtkRange         *range);
 static void gtk_range_set_property   (GObject          *object,
@@ -100,6 +105,8 @@ 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,
@@ -116,6 +123,9 @@ 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);
 
 
 /* Range methods */
@@ -127,12 +137,15 @@ static void gtk_range_move_slider              (GtkRange         *range,
 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);
+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          *trough_border,
-                                                         gint          *stepper_spacing);
+                                                         gint          *stepper_spacing,
+                                                        gint          *arrow_displacement_x,
+                                                        gint          *arrow_displacement_y);
 static void          gtk_range_calc_request             (GtkRange      *range,
                                                          gint           slider_width,
                                                          gint           stepper_size,
@@ -209,6 +222,8 @@ gtk_range_class_init (GtkRangeClass *class)
   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;
@@ -222,17 +237,35 @@ gtk_range_class_init (GtkRangeClass *class)
 
   class->slider_detail = "slider";
   class->stepper_detail = "stepper";
+
+  signals[VALUE_CHANGED] =
+    g_signal_new ("value_changed",
+                  G_TYPE_FROM_CLASS (object_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 ("adjust_bounds",
+                  G_TYPE_FROM_CLASS (object_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_newc ("move_slider",
-                   G_TYPE_FROM_CLASS (object_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);
-
+    g_signal_new ("move_slider",
+                  G_TYPE_FROM_CLASS (object_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);
   
   g_object_class_install_property (gobject_class,
                                    PROP_UPDATE_POLICY,
@@ -249,8 +282,16 @@ gtk_range_class_init (GtkRangeClass *class)
                                                        _("Adjustment"),
                                                        _("The GtkAdjustment that contains the current value of this range object"),
                                                         GTK_TYPE_ADJUSTMENT,
-                                                        G_PARAM_READWRITE));
+                                                        G_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"),
+                                                         FALSE,
+                                                         G_PARAM_READWRITE));
+  
   gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_int ("slider_width",
                                                             _("Slider Width"),
@@ -283,6 +324,22 @@ gtk_range_class_init (GtkRangeClass *class)
                                                             G_MAXINT,
                                                             0,
                                                             G_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_MININT,
+                                                            G_MAXINT,
+                                                            0,
+                                                            G_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_MININT,
+                                                            G_MAXINT,
+                                                            0,
+                                                            G_PARAM_READABLE));
 }
 
 static void
@@ -303,6 +360,9 @@ gtk_range_set_property (GObject      *object,
     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;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -325,7 +385,10 @@ gtk_range_get_property (GObject      *object,
       g_value_set_enum (value, range->update_policy);
       break;
     case PROP_ADJUSTMENT:
-      g_value_set_object (value, G_OBJECT (range->adjustment));
+      g_value_set_object (value, range->adjustment);
+      break;
+    case PROP_INVERTED:
+      g_value_set_boolean (value, range->inverted);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -336,6 +399,8 @@ gtk_range_get_property (GObject      *object,
 static void
 gtk_range_init (GtkRange *range)
 {
+  GTK_WIDGET_SET_FLAGS (range, GTK_NO_WINDOW);
+
   range->adjustment = NULL;
   range->update_policy = GTK_UPDATE_CONTINUOUS;
   range->inverted = FALSE;
@@ -353,10 +418,20 @@ gtk_range_init (GtkRange *range)
   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)
@@ -365,11 +440,25 @@ 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));
 
   if (range->update_policy != policy)
@@ -379,11 +468,40 @@ gtk_range_set_update_policy (GtkRange      *range,
     }
 }
 
+/**
+ * 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)
@@ -420,6 +538,17 @@ gtk_range_set_adjustment (GtkRange      *range,
     }
 }
 
+/**
+ * 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)
@@ -431,10 +560,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)
 {
@@ -443,6 +581,101 @@ gtk_range_get_inverted (GtkRange *range)
   return range->inverted;
 }
 
+/**
+ * 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_set_increments (GtkRange *range,
+                          gdouble   step,
+                          gdouble   page)
+{
+  g_return_if_fail (GTK_IS_RANGE (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_set_range (GtkRange *range,
+                     gdouble   min,
+                     gdouble   max)
+{
+  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 = CLAMP (range->adjustment->value,
+                 range->adjustment->lower,
+                 (range->adjustment->upper - range->adjustment->page_size));
+
+  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_set_value (GtkRange *range,
+                     gdouble   value)
+{
+  g_return_if_fail (GTK_IS_RANGE (range));
+  
+  value = CLAMP (value, range->adjustment->lower,
+                 (range->adjustment->upper - range->adjustment->page_size));
+
+  gtk_adjustment_set_value (range->adjustment, value);
+}
+
+/**
+ * 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 (GTK_IS_RANGE (range), 0.0);
+
+  return range->adjustment->value;
+}
+
 static gboolean
 should_invert (GtkRange *range)
 {  
@@ -458,11 +691,7 @@ should_invert (GtkRange *range)
 static void
 gtk_range_finalize (GObject *object)
 {
-  GtkRange *range;
-
-  g_return_if_fail (GTK_IS_RANGE (object));
-
-  range = GTK_RANGE (object);
+  GtkRange *range = GTK_RANGE (object);
 
   g_free (range->layout);
 
@@ -472,11 +701,7 @@ gtk_range_finalize (GObject *object)
 static void
 gtk_range_destroy (GtkObject *object)
 {
-  GtkRange *range;
-
-  g_return_if_fail (GTK_IS_RANGE (object));
-
-  range = GTK_RANGE (object);
+  GtkRange *range = GTK_RANGE (object);
 
   gtk_range_remove_step_timer (range);
   gtk_range_remove_update_timer (range);
@@ -505,7 +730,8 @@ gtk_range_size_request (GtkWidget      *widget,
   range = GTK_RANGE (widget);
   
   gtk_range_get_props (range,
-                       &slider_width, &stepper_size, &trough_border, &stepper_spacing);
+                       &slider_width, &stepper_size, &trough_border, &stepper_spacing,
+                      NULL, NULL);
 
   gtk_range_calc_request (range, 
                           slider_width, stepper_size, trough_border, stepper_spacing,
@@ -523,10 +749,17 @@ gtk_range_size_allocate (GtkWidget     *widget,
 
   range = GTK_RANGE (widget);
 
+  widget->allocation = *allocation;
+  
   range->need_recalc = TRUE;
-  gtk_range_calc_layout (range);
+  gtk_range_calc_layout (range, range->adjustment->value);
 
-  (* GTK_WIDGET_CLASS (parent_class)->size_allocate) (widget, allocation);
+  if (GTK_WIDGET_REALIZED (range))
+    gdk_window_move_resize (range->event_window,
+                           widget->allocation.x,
+                           widget->allocation.y,
+                           widget->allocation.width,
+                           widget->allocation.height);
 }
 
 static void
@@ -538,18 +771,19 @@ gtk_range_realize (GtkWidget *widget)
 
   range = GTK_RANGE (widget);
 
-  gtk_range_calc_layout (range);
+  gtk_range_calc_layout (range, range->adjustment->value);
   
   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
 
+  widget->window = gtk_widget_get_parent_window (widget);
+  gdk_window_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_OUTPUT;
-  attributes.visual = gtk_widget_get_visual (widget);
-  attributes.colormap = gtk_widget_get_colormap (widget);
+  attributes.wclass = GDK_INPUT_ONLY;
   attributes.event_mask = gtk_widget_get_events (widget);
   attributes.event_mask |= (GDK_EXPOSURE_MASK |
                            GDK_BUTTON_PRESS_MASK |
@@ -559,32 +793,55 @@ gtk_range_realize (GtkWidget *widget)
                             GDK_POINTER_MOTION_MASK |
                             GDK_POINTER_MOTION_HINT_MASK);
 
-  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
+  attributes_mask = GDK_WA_X | GDK_WA_Y;
 
-  widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
-  gdk_window_set_user_data (widget->window, range);
+  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);
-  gtk_style_set_background (widget->style, widget->window, widget->state);
 }
 
 static void
 gtk_range_unrealize (GtkWidget *widget)
 {
-  GtkRange *range;
-
-  g_return_if_fail (widget != NULL);
-  g_return_if_fail (GTK_IS_RANGE (widget));
-
-  range = GTK_RANGE (widget);
+  GtkRange *range = GTK_RANGE (widget);
 
   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 (parent_class)->unrealize)
     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
 }
 
+static void
+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);
+}
+
+static void
+gtk_range_unmap (GtkWidget *widget)
+{
+  GtkRange *range = GTK_RANGE (widget);
+    
+  g_return_if_fail (GTK_IS_RANGE (widget));
+
+  gdk_window_hide (range->event_window);
+
+  GTK_WIDGET_CLASS (parent_class)->unmap (widget);
+}
+
 static void
 draw_stepper (GtkRange     *range,
               GdkRectangle *rect,
@@ -596,10 +853,19 @@ draw_stepper (GtkRange     *range,
   GtkStateType state_type;
   GtkShadowType shadow_type;
   GdkRectangle intersection;
+  GtkWidget *widget = GTK_WIDGET (range);
+
+  gint arrow_x;
+  gint arrow_y;
+  gint arrow_width;
+  gint arrow_height;
 
   /* 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))
     state_type = GTK_STATE_INSENSITIVE;
@@ -614,14 +880,42 @@ draw_stepper (GtkRange     *range,
     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)
+    {
+      gint arrow_displacement_x;
+      gint arrow_displacement_y;
+
+      gtk_range_get_props (GTK_RANGE (widget), NULL, NULL, NULL, NULL,
+                          &arrow_displacement_x, &arrow_displacement_y);
+      
+      arrow_x += arrow_displacement_x;
+      arrow_y += arrow_displacement_y;
+    }
   
-  gtk_paint_arrow (GTK_WIDGET (range)->style,
-                   GTK_WIDGET (range)->window,
+  gtk_paint_arrow (widget->style,
+                   widget->window,
                    state_type, shadow_type, 
-                   &intersection, GTK_WIDGET (range),
+                   &intersection, widget,
                    GTK_RANGE_GET_CLASS (range)->stepper_detail,
                    arrow_type,
-                   TRUE, rect->x, rect->y, rect->width, rect->height);
+                   TRUE,
+                  arrow_x, arrow_y, arrow_width, arrow_height);
 }
 
 static gint
@@ -631,18 +925,29 @@ gtk_range_expose (GtkWidget      *widget,
   GtkRange *range;
   gboolean sensitive;
   GtkStateType state;
+  GdkRectangle expose_area;    /* Relative to widget->allocation */
   GdkRectangle area;
-  
-  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);
+  gint focus_line_width = 0;
+  gint focus_padding = 0;
 
   range = GTK_RANGE (widget);
 
-  gtk_range_calc_layout (range);
+  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);
 
   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)
@@ -650,27 +955,29 @@ gtk_range_expose (GtkWidget      *widget,
   /* The gdk_rectangle_intersect is more to get the right
    * clip region (limited to range_rect) than for efficiency
    */
-  if (gdk_rectangle_intersect (&event->area, &range->range_rect,
+  if (gdk_rectangle_intersect (&expose_area, &range->range_rect,
                                &area))
     {
+      area.x += widget->allocation.x;
+      area.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), "trough",
-                     range->range_rect.x,
-                     range->range_rect.y,
-                     range->range_rect.width,
-                     range->range_rect.height);
+                     widget->allocation.x + range->range_rect.x + focus_line_width + focus_padding,
+                     widget->allocation.y + range->range_rect.y + focus_line_width + focus_padding,
+                     range->range_rect.width - 2 * (focus_line_width + focus_padding),
+                     range->range_rect.height - 2 * (focus_line_width + focus_padding));
       
                  
       if (sensitive &&
           GTK_WIDGET_HAS_FOCUS (range))
-        gtk_paint_focus (widget->style,
-                         widget->window,
+        gtk_paint_focus (widget->style, widget->window, GTK_WIDGET_STATE (widget),
                          &area, widget, "trough",
-                         range->range_rect.x,
-                         range->range_rect.y,
+                         widget->allocation.x + range->range_rect.x,
+                         widget->allocation.y + range->range_rect.y,
                          range->range_rect.width,
                          range->range_rect.height);
     }
@@ -682,19 +989,22 @@ gtk_range_expose (GtkWidget      *widget,
   else
     state = GTK_STATE_NORMAL;
 
-  if (gdk_rectangle_intersect (&event->area,
+  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,
                         GTK_SHADOW_OUT,
-                        &event->area,
+                        &area,
                         widget,
                         GTK_RANGE_GET_CLASS (range)->slider_detail,
-                        range->layout->slider.x,
-                        range->layout->slider.y,
+                        widget->allocation.x + range->layout->slider.x,
+                        widget->allocation.y + range->layout->slider.y,
                         range->layout->slider.width,
                         range->layout->slider.height,
                         range->orientation);
@@ -705,28 +1015,28 @@ gtk_range_expose (GtkWidget      *widget,
                   range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
                   range->layout->grab_location == MOUSE_STEPPER_A,
                   range->layout->mouse_location == MOUSE_STEPPER_A,
-                  &event->area);
+                  &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,
                   range->layout->mouse_location == MOUSE_STEPPER_B,
-                  &event->area);
+                  &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,
                   range->layout->mouse_location == MOUSE_STEPPER_C,
-                  &event->area);
+                  &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,
                   range->layout->mouse_location == MOUSE_STEPPER_D,
-                  &event->area);
+                  &expose_area);
   
   return FALSE;
 }
@@ -738,6 +1048,8 @@ range_grab_add (GtkRange      *range,
 {
   /* we don't actually gtk_grab, since a button is down */
 
+  gtk_grab_add (GTK_WIDGET (range));
+  
   range->layout->grab_location = location;
   range->layout->grab_button = button;
   
@@ -748,6 +1060,8 @@ range_grab_add (GtkRange      *range,
 static void
 range_grab_remove (GtkRange *range)
 {
+  gtk_grab_remove (GTK_WIDGET (range));
+  
   range->layout->grab_location = MOUSE_OUTSIDE;
   range->layout->grab_button = 0;
 
@@ -791,22 +1105,16 @@ range_get_scroll_for_grab (GtkRange      *range)
         case 3:
           return GTK_SCROLL_END;
           break;
-        }
+       }
       break;
 
       /* In the trough */
     case MOUSE_TROUGH:
       {
         if (range->trough_click_forward)
-          {
-            return range->layout->grab_button == 3
-              ? GTK_SCROLL_PAGE_FORWARD : GTK_SCROLL_STEP_FORWARD;
-          }
+         return GTK_SCROLL_PAGE_FORWARD;
         else
-          {
-            return range->layout->grab_button == 3
-              ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_STEP_BACKWARD;
-          }
+         return GTK_SCROLL_PAGE_BACKWARD;
       }
       break;
 
@@ -827,11 +1135,17 @@ coord_to_value (GtkRange *range,
   gdouble value;
   
   if (range->orientation == GTK_ORIENTATION_VERTICAL)
-    frac = ((coord - range->layout->trough.y) /
-            (gdouble) (range->layout->trough.height - range->layout->slider.height));
+    if (range->layout->trough.height == range->layout->slider.height)
+      frac = 1.0;
+    else 
+      frac = ((coord - range->layout->trough.y) /
+             (gdouble) (range->layout->trough.height - range->layout->slider.height));
   else
-    frac = ((coord - range->layout->trough.x) /
-            (gdouble) (range->layout->trough.width - range->layout->slider.width));
+    if (range->layout->trough.width == range->layout->slider.width)
+      frac = 1.0;
+    else
+      frac = ((coord - range->layout->trough.x) /
+             (gdouble) (range->layout->trough.width - range->layout->slider.width));
 
   if (should_invert (range))
     frac = 1.0 - frac;
@@ -846,12 +1160,7 @@ static gint
 gtk_range_button_press (GtkWidget      *widget,
                        GdkEventButton *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_HAS_FOCUS (widget))
     gtk_widget_grab_focus (widget);
@@ -866,10 +1175,9 @@ gtk_range_button_press (GtkWidget      *widget,
     gtk_widget_queue_draw (widget);
     
   if (range->layout->mouse_location == MOUSE_TROUGH  &&
-      (event->button == 1 || event->button == 3))
+      event->button == 1)
     {
-      /* button 1 steps by step increment, as with button 1 on a stepper,
-       * button 3 steps by page increment, as with button 2 on a stepper
+      /* button 1 steps by page increment, as with button 2 on a stepper
        */
       GtkScrollType scroll;
       gdouble click_value;
@@ -900,8 +1208,8 @@ gtk_range_button_press (GtkWidget      *widget,
 
       stepper_area = get_area (range, range->layout->mouse_location);
       gtk_widget_queue_draw_area (widget,
-                                  stepper_area->x,
-                                  stepper_area->y,
+                                  widget->allocation.x + stepper_area->x,
+                                  widget->allocation.y + stepper_area->y,
                                   stepper_area->width,
                                   stepper_area->height);
 
@@ -915,6 +1223,8 @@ gtk_range_button_press (GtkWidget      *widget,
             event->button == 2) ||
            range->layout->mouse_location == MOUSE_SLIDER)
     {
+      gboolean need_value_update = FALSE;
+
       /* 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,
@@ -922,20 +1232,31 @@ gtk_range_button_press (GtkWidget      *widget,
        */
       if (event->button == 2)
         {
-          gdouble click_value;
+          gdouble slider_low_value, slider_high_value, new_value;
           
-          click_value = coord_to_value (range,
-                                        range->orientation == GTK_ORIENTATION_VERTICAL ?
-                                        event->y : event->x);
+          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);
           
-          
-          /* middle button jumps to point */
-          gtk_range_internal_set_value (range, click_value);
+          /* compute new value for warped slider */
+          new_value = slider_low_value + (slider_high_value - slider_low_value) / 2;
 
-          /* Calc layout so we can set slide_initial_slider_position
+         /* recalc slider, so we can set slide_initial_slider_position
            * properly
            */
-          gtk_range_calc_layout (range);
+         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)
@@ -949,6 +1270,9 @@ gtk_range_button_press (GtkWidget      *widget,
           range->slide_initial_coordinate = event->x;
         }
 
+      if (need_value_update)
+        update_slider_position (range, event->x, event->y);
+
       range_grab_add (range, MOUSE_SLIDER, event->button);
       
       return TRUE;
@@ -983,39 +1307,32 @@ static gint
 gtk_range_button_release (GtkWidget      *widget,
                          GdkEventButton *event)
 {
-  GtkRange *range;
-
-  g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
+  GtkRange *range = GTK_RANGE (widget);
 
-  range = GTK_RANGE (widget);
-
-  range->layout->mouse_x = event->x;
-  range->layout->mouse_y = event->y;
+  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)
     {
-      GtkScrollType scroll;
       MouseLocation grab_location;
 
       grab_location = range->layout->grab_location;
 
-      scroll = range_get_scroll_for_grab (range);
-      
       range_grab_remove (range);
       gtk_range_remove_step_timer (range);
       
-      /* We only do the move if we're still on top of the button at
-       * release
-       */
-      if (grab_location == range->layout->mouse_location &&
-          scroll != GTK_SCROLL_NONE)
-        {
-          gtk_range_scroll (range, scroll);
-        }
-
       if (grab_location == MOUSE_SLIDER)
-        update_slider_position (range, event->x, event->y);
+        update_slider_position (range, range->layout->mouse_x, range->layout->mouse_y);
 
       /* Flush any pending discontinuous/delayed updates */
       gtk_range_update_value (range);
@@ -1035,18 +1352,13 @@ 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;
       gdouble new_value = adj->value + ((event->direction == GDK_SCROLL_UP ||
-                                         event->direction == GDK_SCROLL_RIGHT) ? 
+                                         event->direction == GDK_SCROLL_LEFT) ? 
                                        -adj->page_increment / 2: 
                                        adj->page_increment / 2);
 
@@ -1070,12 +1382,9 @@ gtk_range_motion_notify (GtkWidget      *widget,
   GtkRange *range;
   gint x, y;
 
-  g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
-
   range = GTK_RANGE (widget);
 
-  gdk_window_get_pointer (widget->window, &x, &y, NULL);
+  gdk_window_get_pointer (range->event_window, &x, &y, NULL);
   
   range->layout->mouse_x = x;
   range->layout->mouse_y = y;
@@ -1084,7 +1393,7 @@ gtk_range_motion_notify (GtkWidget      *widget,
     gtk_widget_queue_draw (widget);
 
   if (range->layout->grab_location == MOUSE_SLIDER)
-    update_slider_position (range, event->x, event->y);
+    update_slider_position (range, x, y);
 
   /* We handled the event if the mouse was in the range_rect */
   return range->layout->mouse_location != MOUSE_OUTSIDE;
@@ -1094,12 +1403,7 @@ static gint
 gtk_range_enter_notify (GtkWidget        *widget,
                        GdkEventCrossing *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);
 
   range->layout->mouse_x = event->x;
   range->layout->mouse_y = event->y;
@@ -1114,12 +1418,7 @@ static gint
 gtk_range_leave_notify (GtkWidget        *widget,
                        GdkEventCrossing *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);
 
   range->layout->mouse_x = -1;
   range->layout->mouse_y = -1;
@@ -1134,12 +1433,7 @@ static void
 gtk_range_adjustment_changed (GtkAdjustment *adjustment,
                              gpointer       data)
 {
-  GtkRange *range;
-
-  g_return_if_fail (adjustment != NULL);
-  g_return_if_fail (data != NULL);
-
-  range = GTK_RANGE (data);
+  GtkRange *range = GTK_RANGE (data);
 
   range->need_recalc = TRUE;
   gtk_widget_queue_draw (GTK_WIDGET (range));
@@ -1157,16 +1451,15 @@ static void
 gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
                                    gpointer       data)
 {
-  GtkRange *range;
-
-  g_return_if_fail (adjustment != NULL);
-  g_return_if_fail (data != NULL);
-
-  range = GTK_RANGE (data);
+  GtkRange *range = GTK_RANGE (data);
 
   range->need_recalc = TRUE;
-  gtk_widget_queue_draw (GTK_WIDGET (range));
 
+  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
    * in response to a change signal on that value; round_digits
@@ -1174,18 +1467,15 @@ gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
    * can input into the adjustment, not a filter that the GtkRange
    * will enforce on the adjustment.
    */
+
+  g_signal_emit (G_OBJECT (range), signals[VALUE_CHANGED], 0);
 }
 
 static void
 gtk_range_style_set (GtkWidget *widget,
                      GtkStyle  *previous_style)
 {
-  GtkRange *range;
-
-  g_return_if_fail (widget != NULL);
-  g_return_if_fail (GTK_IS_RANGE (widget));
-
-  range = GTK_RANGE (widget);
+  GtkRange *range = GTK_RANGE (widget);
 
   range->need_recalc = TRUE;
 
@@ -1347,18 +1637,36 @@ gtk_range_get_props (GtkRange  *range,
                      gint      *slider_width,
                      gint      *stepper_size,
                      gint      *trough_border,
-                     gint      *stepper_spacing)
+                     gint      *stepper_spacing,
+                    gint      *arrow_displacement_x,
+                    gint      *arrow_displacement_y)
 {
   GtkWidget *widget =  GTK_WIDGET (range);
   gint tmp_slider_width, tmp_stepper_size, tmp_trough_border, tmp_stepper_spacing;
+  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,
                         NULL);
   
+  if (GTK_WIDGET_CAN_FOCUS (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_trough_border += focus_line_width + focus_padding;
+    }
+  
   if (slider_width)
     *slider_width = tmp_slider_width;
 
@@ -1370,6 +1678,12 @@ gtk_range_get_props (GtkRange  *range,
 
   if (stepper_spacing)
     *stepper_spacing = tmp_stepper_spacing;
+
+  if (arrow_displacement_x)
+    *arrow_displacement_x = tmp_arrow_displacement_x;
+
+  if (arrow_displacement_y)
+    *arrow_displacement_y = tmp_arrow_displacement_y;
 }
 
 #define POINT_IN_RECT(xcoord, ycoord, rect) \
@@ -1523,7 +1837,7 @@ gtk_range_calc_request (GtkRange      *range,
 {
   gint slider_length;
   gint n_steppers;
-  
+
   border->left = 0;
   border->right = 0;
   border->top = 0;
@@ -1569,7 +1883,8 @@ gtk_range_calc_request (GtkRange      *range,
 }
 
 static void
-gtk_range_calc_layout (GtkRange *range)
+gtk_range_calc_layout (GtkRange *range,
+                      gdouble   adjustment_value)
 {
   gint slider_width, stepper_size, trough_border, stepper_spacing;
   gint slider_length;
@@ -1596,7 +1911,8 @@ gtk_range_calc_layout (GtkRange *range)
   layout = range->layout;
   
   gtk_range_get_props (range,
-                       &slider_width, &stepper_size, &trough_border, &stepper_spacing);
+                       &slider_width, &stepper_size, &trough_border, &stepper_spacing,
+                      NULL, NULL);
 
   gtk_range_calc_request (range, 
                           slider_width, stepper_size, trough_border, stepper_spacing,
@@ -1736,8 +2052,9 @@ gtk_range_calc_layout (GtkRange *range)
         
         y = top;
         
-        y += (bottom - top - height) * ((range->adjustment->value - range->adjustment->lower) /
-                                        (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
+       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);
         
@@ -1871,8 +2188,9 @@ gtk_range_calc_layout (GtkRange *range)
         
         x = left;
         
-        x += (right - left - width) * ((range->adjustment->value - range->adjustment->lower) /
-                                       (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
+       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);
         
@@ -1922,6 +2240,9 @@ static void
 gtk_range_internal_set_value (GtkRange *range,
                               gdouble   value)
 {
+  /* potentially adjust the bounds _before we clamp */
+  g_signal_emit (G_OBJECT (range), signals[ADJUST_BOUNDS], 0, value);
+
   value = CLAMP (value, range->adjustment->lower,
                  (range->adjustment->upper - range->adjustment->page_size));
 
@@ -1986,9 +2307,10 @@ second_timeout (gpointer data)
 {
   GtkRange *range;
 
+  GDK_THREADS_ENTER ();
   range = GTK_RANGE (data);
-
   gtk_range_scroll (range, range->timer->step);
+  GDK_THREADS_LEAVE ();
   
   return TRUE;
 }
@@ -1998,12 +2320,13 @@ initial_timeout (gpointer data)
 {
   GtkRange *range;
 
+  GDK_THREADS_ENTER ();
   range = GTK_RANGE (data);
-
   range->timer->timeout_id = 
     g_timeout_add (SCROLL_LATER_DELAY,
                    second_timeout,
                    range);
+  GDK_THREADS_LEAVE ();
 
   /* remove self */
   return FALSE;
@@ -2023,6 +2346,8 @@ gtk_range_add_step_timer (GtkRange      *range,
                    initial_timeout,
                    range);
   range->timer->step = step;
+
+  gtk_range_scroll (range, range->timer->step);
 }
 
 static void
@@ -2044,11 +2369,11 @@ update_timeout (gpointer data)
 {
   GtkRange *range;
 
+  GDK_THREADS_ENTER ();
   range = GTK_RANGE (data);
-
   gtk_range_update_value (range);
-
   range->update_timeout_id = 0;
+  GDK_THREADS_LEAVE ();
 
   /* self-remove */
   return FALSE;