/* GTK - The GIMP Toolkit
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/*
 * Modified by the GTK+ Team and others 1997-2000.  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 <stdio.h>
#include "gtkmain.h"
#include "gtkrange.h"
#include "gtksignal.h"

#define SCROLL_TIMER_LENGTH  20
#define SCROLL_INITIAL_DELAY 100
#define SCROLL_DELAY_LENGTH  300

#define RANGE_CLASS(w)  GTK_RANGE_GET_CLASS (w)

enum {
  ARG_0,
  ARG_UPDATE_POLICY
};

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);
    }

  return range_type;
}

static void
gtk_range_class_init (GtkRangeClass *class)
{
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_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;
  object_class->destroy = gtk_range_destroy;

  widget_class->draw_focus = gtk_range_draw_focus;
  widget_class->unrealize = gtk_range_unrealize;
  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->style_set = gtk_range_style_set;

  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);
}

static void
gtk_range_set_arg (GtkObject      *object,
		   GtkArg         *arg,
		   guint           arg_id)
{
  GtkRange *range;

  range = GTK_RANGE (object);

  switch (arg_id)
    {
    case ARG_UPDATE_POLICY:
      gtk_range_set_update_policy (range, GTK_VALUE_ENUM (*arg));
      break;
    default:
      break;
    }
}

static void
gtk_range_get_arg (GtkObject      *object,
		   GtkArg         *arg,
		   guint           arg_id)
{
  GtkRange *range;

  range = GTK_RANGE (object);

  switch (arg_id)
    {
    case ARG_UPDATE_POLICY:
      GTK_VALUE_ENUM (*arg) = range->policy;
      break;
    default:
      arg->type = GTK_TYPE_INVALID;
      break;
    }
}

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;
  range->adjustment = NULL;
}

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)
    gtk_range_set_adjustment (range, NULL);

  return range->adjustment;
}

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;
}

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)
    adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
  else
    g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));

  if (range->adjustment != adjustment)
    {
      if (range->adjustment)
	{
	  gtk_signal_disconnect_by_data (GTK_OBJECT (range->adjustment),
					 (gpointer) range);
	  gtk_object_unref (GTK_OBJECT (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);
      
      range->old_value = adjustment->value;
      range->old_lower = adjustment->lower;
      range->old_upper = adjustment->upper;
      range->old_page_size = adjustment->page_size;
      
      gtk_range_adjustment_changed (adjustment, (gpointer) range);
    }
}

void
gtk_range_draw_background (GtkRange *range)
{
  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);
}

void
gtk_range_clear_background (GtkRange *range)
{
  g_return_if_fail (range != NULL);
  g_return_if_fail (GTK_IS_RANGE (range));

  if (range->trough && RANGE_CLASS (range)->clear_background)
    (* RANGE_CLASS (range)->clear_background) (range);
}

void
gtk_range_draw_trough (GtkRange *range)
{
  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);
}

void
gtk_range_draw_slider (GtkRange *range)
{
  g_return_if_fail (range != NULL);
  g_return_if_fail (GTK_IS_RANGE (range));

  if (range->slider && RANGE_CLASS (range)->draw_slider)
    (* RANGE_CLASS (range)->draw_slider) (range);
}

void
gtk_range_draw_step_forw (GtkRange *range)
{
  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);
}

void
gtk_range_draw_step_back (GtkRange *range)
{
  g_return_if_fail (range != NULL);
  g_return_if_fail (GTK_IS_RANGE (range));

  if (range->step_back && RANGE_CLASS (range)->draw_step_back)
    (* RANGE_CLASS (range)->draw_step_back) (range);
}

void
gtk_range_slider_update (GtkRange *range)
{
  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);
}

gint
gtk_range_trough_click (GtkRange *range,
			gint      x,
			gint      y,
			gfloat   *jump_perc)
{
  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);

  return GTK_TROUGH_NONE;
}

static GdkRegion *
get_window_region (GdkWindow *window)
{
  GdkRectangle rect;

  gdk_window_get_position (window, &rect.x, &rect.y);
  gdk_window_get_size (window, &rect.width, &rect.height);

  return gdk_region_rectangle (&rect);
}

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);
}

void
gtk_range_default_hslider_update (GtkRange *range)
{
  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;

      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))
	x += ((right - left) * (range->adjustment->value - range->adjustment->lower) /
	      (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));

      if (x < left)
	x = left;
      else if (x > right)
	x = right;

      if (range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL)
	x = right - (x - left);
      
      move_and_update_window (range->slider, x, GTK_WIDGET (range)->style->ythickness);
    }
}

void
gtk_range_default_vslider_update (GtkRange *range)
{
  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))
    {
      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));

      if (y < top)
	y = top;
      else if (y > bottom)
	y = bottom;

      move_and_update_window (range->slider, GTK_WIDGET (range)->style->xthickness, y);
    }
}

gint
gtk_range_default_htrough_click (GtkRange *range,
				 gint      x,
				 gint      y,
				 gfloat	  *jump_perc)
{
  gint ythickness;
  gint trough_width;
  gint trough_height;
  gint slider_x;
  gint slider_length;
  gint left, right;

  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;

  gtk_range_trough_hdims (range, &left, &right);
  gdk_window_get_size (range->slider, &slider_length, NULL);
  right += slider_length;

  if (range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL)
    x = (right - x) + left;

  if ((x > left) && (y > ythickness))
    {
      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;
	}
    }

  return GTK_TROUGH_NONE;
}

gint
gtk_range_default_vtrough_click (GtkRange *range,
				 gint      x,
				 gint      y,
				 gfloat   *jump_perc)
{
  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);

  xthickness = GTK_WIDGET (range)->style->xthickness;

  gtk_range_trough_vdims (range, &top, &bottom);
  gdk_window_get_size (range->slider, NULL, &slider_length);
  bottom += slider_length;
	      
  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;
	}
    }

  return GTK_TROUGH_NONE;
}

void
gtk_range_default_hmotion (GtkRange *range,
			   gint      xdelta,
			   gint      ydelta)
{
  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));

  gdk_window_get_position (range->slider, &slider_x, &slider_y);
  gtk_range_trough_hdims (range, &left, &right);

  if (left == right)
    return;

  new_pos = slider_x + xdelta;

  if (range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL)
    new_pos = (right - new_pos) + left;

  if (new_pos < left)
    new_pos = left;
  else if (new_pos > right)
    new_pos = right;

  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);

  if (range->digits >= 0)
    {
      char buffer[64];

      sprintf (buffer, "%0.*f", range->digits, range->adjustment->value);
      sscanf (buffer, "%f", &range->adjustment->value);
    }

  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);

	  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);
	    }
	}
    }
}

void
gtk_range_default_vmotion (GtkRange *range,
			   gint      xdelta,
			   gint      ydelta)
{
  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));

  range = GTK_RANGE (range);

  gdk_window_get_position (range->slider, &slider_x, &slider_y);
  gtk_range_trough_vdims (range, &top, &bottom);

  if (bottom == top)
    return;

  new_pos = slider_y + ydelta;

  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)
    {
      char buffer[64];

      sprintf (buffer, "%0.*f", range->digits, range->adjustment->value);
      sscanf (buffer, "%f", &range->adjustment->value);
    }

  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);

	  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);
	    }
	}
    }
}


static void
gtk_range_destroy (GtkObject *object)
{
  GtkRange *range;

  g_return_if_fail (object != NULL);
  g_return_if_fail (GTK_IS_RANGE (object));

  range = GTK_RANGE (object);

  gtk_range_remove_timer (range);
  if (range->adjustment)
    {
      if (range->adjustment)
	gtk_signal_disconnect_by_data (GTK_OBJECT (range->adjustment),
				       (gpointer) range);
      gtk_object_unref (GTK_OBJECT (range->adjustment));
      range->adjustment = NULL;
    }

  (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}

static void
gtk_range_draw_focus (GtkWidget *widget)
{
  g_return_if_fail (widget != NULL);
  g_return_if_fail (GTK_IS_RANGE (widget));

  if (GTK_WIDGET_DRAWABLE (widget))
    gtk_range_draw_trough (GTK_RANGE (widget));
}

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);

  if (range->slider)
    {
      gdk_window_set_user_data (range->slider, NULL);
      gdk_window_destroy (range->slider);
      range->slider = NULL;
    }
  if (range->trough)
    {
      gdk_window_set_user_data (range->trough, NULL);
      gdk_window_destroy (range->trough);
      range->trough = NULL;
    }
  if (range->step_forw)
    {
      gdk_window_set_user_data (range->step_forw, NULL);
      gdk_window_destroy (range->step_forw);
      range->step_forw = NULL;
    }
  if (range->step_back)
    {
      gdk_window_set_user_data (range->step_back, NULL);
      gdk_window_destroy (range->step_back);
      range->step_back = NULL;
    }

  if (GTK_WIDGET_CLASS (parent_class)->unrealize)
    (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
}

static gint
gtk_range_expose (GtkWidget      *widget,
		  GdkEventExpose *event)
{
  GtkRange *range;

  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);

  range = GTK_RANGE (widget);

  /* 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 FALSE;
}

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);

  if (!GTK_WIDGET_HAS_FOCUS (widget))
    gtk_widget_grab_focus (widget);

  jump_perc = -1;
  range = GTK_RANGE (widget);
  if (range->button == 0)
    {
      gtk_grab_add (widget);

      range->button = event->button;
      range->x_click_point = event->x;
      range->y_click_point = event->y;

      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);
	    }
	}
    }

  return TRUE;
}

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);

  range = GTK_RANGE (widget);

  if (range->button == event->button)
    {
      gtk_grab_remove (widget);

      range->button = 0;
      range->x_click_point = -1;
      range->y_click_point = -1;

      if (range->click_child == RANGE_CLASS (range)->slider)
	{
	  if (range->policy == GTK_UPDATE_DELAYED)
	    gtk_range_remove_timer (range);

	  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->policy != GTK_UPDATE_CONTINUOUS) &&
	      (range->old_value != range->adjustment->value))
	    gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed");

	  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);
	    }
	}

      range->click_child = 0;
    }

  return TRUE;
}

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);

  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);
    }

  return TRUE;
}

static gint
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);

  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->trough, &x, &y, &mods);
      gdk_window_get_position (range->slider, &x2, &y2);

      x -= x2;
      y -= y2;

      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 (mods & mask)
	{
	  if (RANGE_CLASS (range)->motion)
	    (* RANGE_CLASS (range)->motion) (range, x - range->x_click_point, y - range->y_click_point);
	}
    }

  return TRUE;
}

static gint
gtk_range_key_press (GtkWidget   *widget,
		     GdkEventKey *event)
{
  GtkRange *range;
  gint return_val;
  GtkScrollType scroll = GTK_SCROLL_NONE;
  GtkTroughType pos = GTK_TROUGH_NONE;

  g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  range = GTK_RANGE (widget);
  return_val = FALSE;

  if (RANGE_CLASS (range)->trough_keys)
    return_val = (* RANGE_CLASS (range)->trough_keys) (range, event, &scroll, &pos);

  if (return_val)
    {
      if (scroll != GTK_SCROLL_NONE)
	{
	  range->scroll_type = scroll;
	  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_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;

	  if (range->old_value != range->adjustment->value)
	    {
	      gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment),
				       "value_changed");

	      gtk_range_slider_update (range);
	      gtk_range_clear_background (range);
	    }
	}
    }
  return return_val;
}

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);

  if (event->window == range->trough)
    {
      range->in_child = RANGE_CLASS (range)->trough;
    }
  else if (event->window == range->slider)
    {
      range->in_child = RANGE_CLASS (range)->slider;

      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;

      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;

      if ((range->click_child == 0) ||
	  (range->click_child == RANGE_CLASS (range)->trough))
	gtk_range_draw_step_back (range);
    }

  return TRUE;
}

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);

  range->in_child = 0;

  if (event->window == range->trough)
    {
    }
  else if (event->window == range->slider)
    {
      if ((range->click_child == 0) ||
	  (range->click_child == RANGE_CLASS (range)->trough))
	gtk_range_draw_slider (range);
    }
  else if (event->window == range->step_forw)
    {
      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)
    {
      if ((range->click_child == 0) ||
	  (range->click_child == RANGE_CLASS (range)->trough))
	gtk_range_draw_step_back (range);
    }

  return TRUE;
}

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);

  GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
  gtk_widget_draw_focus (widget);

  return TRUE;
}

static gint
gtk_range_focus_out (GtkWidget     *widget,
		     GdkEventFocus *event)
{
  g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
  g_return_val_if_fail (event != NULL, FALSE);

  GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
  gtk_widget_draw_focus (widget);

  return TRUE;
}

static void
gtk_real_range_draw_trough (GtkRange *range)
{
  g_return_if_fail (GTK_IS_RANGE (range));

  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);
    }
}

static void
gtk_real_range_draw_slider (GtkRange *range)
{
  GtkStateType state_type;
   
  g_return_if_fail (GTK_IS_RANGE (range));
   
  if (range->slider)
    {
      if ((range->in_child == RANGE_CLASS (range)->slider) ||
	  (range->click_child == RANGE_CLASS (range)->slider))
	state_type = GTK_STATE_PRELIGHT;
      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);
    }
}

static gint
gtk_real_range_timer (GtkRange *range)
{
  gint return_val;

  GDK_THREADS_ENTER ();

  return_val = TRUE;
  if (range->click_child == RANGE_CLASS (range)->slider)
    {
      if (range->policy == GTK_UPDATE_DELAYED)
	gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed");
      return_val = FALSE;
    }
  else
    {
      GdkModifierType mods, mask;

      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;
	}

      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;
	}

      gdk_window_get_pointer (range->slider, NULL, NULL, &mods);

      if (mods & mask)
	return_val = gtk_range_scroll (range, -1);
    }

  GDK_THREADS_LEAVE ();

  return return_val;
}

static gint
gtk_range_scroll (GtkRange *range,
		  gfloat    jump_perc)
{
  gfloat new_value;
  gint return_val;

  g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);

  new_value = range->adjustment->value;
  return_val = TRUE;

  switch (range->scroll_type)
    {
    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;
	}
      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;
	}
      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;
	}
      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;
	}
      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)
{
  g_return_if_fail (GTK_IS_RANGE (range));

  if (!range->timer)
    {
      range->need_timer = TRUE;
      range->timer = gtk_timeout_add (SCROLL_INITIAL_DELAY,
				      (GtkFunction) RANGE_CLASS (range)->timer,
				      (gpointer) range);
    }
}

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;
}

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);

  if (((range->old_lower != adjustment->lower) ||
       (range->old_upper != adjustment->upper) ||
       (range->old_page_size != adjustment->page_size)) &&
      (range->old_value == adjustment->value))
    {
      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");
	}
    }

  if ((range->old_value != adjustment->value) ||
      (range->old_lower != adjustment->lower) ||
      (range->old_upper != adjustment->upper) ||
      (range->old_page_size != adjustment->page_size))
    {
      gtk_range_slider_update (range);
      gtk_range_clear_background (range);

      range->old_value = adjustment->value;
      range->old_lower = adjustment->lower;
      range->old_upper = adjustment->upper;
      range->old_page_size = adjustment->page_size;
    }
}

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);

  if (range->old_value != adjustment->value)
    {
      gtk_range_slider_update (range);
      gtk_range_clear_background (range);

      range->old_value = adjustment->value;
    }
}


static void
gtk_range_trough_hdims (GtkRange *range,
			gint     *left,
			gint     *right)
{
  gint trough_width;
  gint slider_length;
  gint tmp_width;
  gint tleft;
  gint tright;

  g_return_if_fail (range != NULL);

  gdk_window_get_size (range->trough, &trough_width, NULL);
  gdk_window_get_size (range->slider, &slider_length, NULL);

  tleft = GTK_WIDGET (range)->style->xthickness;
  tright = trough_width - slider_length - GTK_WIDGET (range)->style->xthickness;

  if (range->step_back)
    {
      gdk_window_get_size (range->step_back, &tmp_width, NULL);
      tleft += (tmp_width + RANGE_CLASS (range)->stepper_slider_spacing);
    }

  if (range->step_forw)
    {
      gdk_window_get_size (range->step_forw, &tmp_width, NULL);
      tright -= (tmp_width + RANGE_CLASS (range)->stepper_slider_spacing);
    }

  if (left)
    *left = tleft;
  if (right)
    *right = tright;
}

static void
gtk_range_trough_vdims (GtkRange *range,
			gint     *top,
			gint     *bottom)
{
  gint trough_height;
  gint slider_length;
  gint tmp_height;
  gint ttop;
  gint tbottom;

  g_return_if_fail (range != NULL);

  gdk_window_get_size (range->trough, NULL, &trough_height);
  gdk_window_get_size (range->slider, NULL, &slider_length);

  ttop = GTK_WIDGET (range)->style->ythickness;
  tbottom = trough_height - slider_length - GTK_WIDGET (range)->style->ythickness;

  if (range->step_back)
    {
      gdk_window_get_size (range->step_back, NULL, &tmp_height);
      ttop += (tmp_height + RANGE_CLASS (range)->stepper_slider_spacing);
    }

  if (range->step_forw)
    {
      gdk_window_get_size (range->step_forw, NULL, &tmp_height);
      tbottom -= (tmp_height + RANGE_CLASS (range)->stepper_slider_spacing);
    }

  if (top)
    *top = ttop;
  if (bottom)
    *bottom = tbottom;
}

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);

  if (GTK_WIDGET_REALIZED (widget))
    {
      if (range->trough)
	gtk_style_set_background (widget->style, range->trough, GTK_STATE_ACTIVE);

      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.
       */

      if (range->step_forw)
	{
	  gtk_style_set_background (widget->style, range->step_forw, GTK_STATE_ACTIVE);
	  gdk_window_clear (range->step_forw);
	}

      if (range->step_back)
	{
	  gtk_style_set_background (widget->style, range->step_back, GTK_STATE_ACTIVE);
	  gdk_window_clear (range->step_back);
	}
    }
}