gtkrange.c 82.1 KB
Newer Older
Elliot Lee's avatar
Elliot Lee committed
1 2
/* GTK - The GIMP Toolkit
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3
 * Copyright (C) 2001 Red Hat, Inc.
Elliot Lee's avatar
Elliot Lee committed
4 5
 *
 * This library is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU Lesser General Public
Elliot Lee's avatar
Elliot Lee committed
7 8 9 10 11 12
 * 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
13
 * Lesser General Public License for more details.
Elliot Lee's avatar
Elliot Lee committed
14
 *
15
 * You should have received a copy of the GNU Lesser General Public
16 17 18
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
Elliot Lee's avatar
Elliot Lee committed
19
 */
20 21

/*
22
 * Modified by the GTK+ Team and others 1997-2004.  See the AUTHORS
23 24 25 26 27
 * 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/. 
 */

28
#include <config.h>
Elliot Lee's avatar
Elliot Lee committed
29
#include <stdio.h>
30
#include <math.h>
31
#include "gtkintl.h"
Elliot Lee's avatar
Elliot Lee committed
32
#include "gtkmain.h"
33
#include "gtkmarshalers.h"
Elliot Lee's avatar
Elliot Lee committed
34
#include "gtkrange.h"
Alexander Larsson's avatar
Alexander Larsson committed
35
#include "gtkintl.h"
36
#include "gtkscrollbar.h"
37
#include "gtkprivate.h"
38
#include "gtkalias.h"
Elliot Lee's avatar
Elliot Lee committed
39

40 41
#define SCROLL_INITIAL_DELAY 250  /* must hold button this long before ... */
#define SCROLL_LATER_DELAY   100  /* ... it starts repeating at this rate  */
42
#define UPDATE_DELAY         300  /* Delay for queued update */
Elliot Lee's avatar
Elliot Lee committed
43

44
enum {
Alexander Larsson's avatar
Alexander Larsson committed
45
  PROP_0,
46
  PROP_UPDATE_POLICY,
Havoc Pennington's avatar
Havoc Pennington committed
47
  PROP_ADJUSTMENT,
48 49 50
  PROP_INVERTED,
  PROP_LOWER_STEPPER_SENSITIVITY,
  PROP_UPPER_STEPPER_SENSITIVITY
51
};
Elliot Lee's avatar
Elliot Lee committed
52

53
enum {
54
  VALUE_CHANGED,
55
  ADJUST_BOUNDS,
56
  MOVE_SLIDER,
57
  CHANGE_VALUE,
58 59 60
  LAST_SIGNAL
};

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
typedef enum {
  MOUSE_OUTSIDE,
  MOUSE_STEPPER_A,
  MOUSE_STEPPER_B,
  MOUSE_STEPPER_C,
  MOUSE_STEPPER_D,
  MOUSE_TROUGH,
  MOUSE_SLIDER,
  MOUSE_WIDGET /* inside widget but not in any of the above GUI elements */
} MouseLocation;

struct _GtkRangeLayout
{
  /* These are in widget->window coordinates */
  GdkRectangle stepper_a;
  GdkRectangle stepper_b;
  GdkRectangle stepper_c;
  GdkRectangle stepper_d;
  /* The trough rectangle is the area the thumb can slide in, not the
   * entire range_rect
   */
  GdkRectangle trough;
  GdkRectangle slider;

  /* Layout-related state */
  
  MouseLocation mouse_location;
  /* last mouse coords we got, or -1 if mouse is outside the range */
  gint mouse_x;
  gint mouse_y;
  /* "grabbed" mouse location, OUTSIDE for no grab */
  MouseLocation grab_location;
  gint grab_button; /* 0 if none */
94 95 96 97

  /* Stepper sensitivity */
  GtkSensitivityType lower_sensitivity;
  GtkSensitivityType upper_sensitivity;
98 99
};

100

101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
static void gtk_range_class_init     (GtkRangeClass    *klass);
static void gtk_range_init           (GtkRange         *range);
static void gtk_range_set_property   (GObject          *object,
                                      guint             prop_id,
                                      const GValue     *value,
                                      GParamSpec       *pspec);
static void gtk_range_get_property   (GObject          *object,
                                      guint             prop_id,
                                      GValue           *value,
                                      GParamSpec       *pspec);
static void gtk_range_destroy        (GtkObject        *object);
static void gtk_range_finalize       (GObject          *object);
static void gtk_range_size_request   (GtkWidget        *widget,
                                      GtkRequisition   *requisition);
static void gtk_range_size_allocate  (GtkWidget        *widget,
                                      GtkAllocation    *allocation);
static void gtk_range_realize        (GtkWidget        *widget);
static void gtk_range_unrealize      (GtkWidget        *widget);
119 120
static void gtk_range_map            (GtkWidget        *widget);
static void gtk_range_unmap          (GtkWidget        *widget);
121 122 123 124 125 126 127 128 129 130 131 132
static gint gtk_range_expose         (GtkWidget        *widget,
                                      GdkEventExpose   *event);
static gint gtk_range_button_press   (GtkWidget        *widget,
                                      GdkEventButton   *event);
static gint gtk_range_button_release (GtkWidget        *widget,
                                      GdkEventButton   *event);
static gint gtk_range_motion_notify  (GtkWidget        *widget,
                                      GdkEventMotion   *event);
static gint gtk_range_enter_notify   (GtkWidget        *widget,
                                      GdkEventCrossing *event);
static gint gtk_range_leave_notify   (GtkWidget        *widget,
                                      GdkEventCrossing *event);
Matthias Clasen's avatar
Matthias Clasen committed
133 134
static gboolean gtk_range_grab_broken (GtkWidget          *widget,
				       GdkEventGrabBroken *event);
Matthias Clasen's avatar
Matthias Clasen committed
135 136 137 138
static void gtk_range_grab_notify    (GtkWidget          *widget,
				      gboolean            was_grabbed);
static void gtk_range_state_changed  (GtkWidget          *widget,
				      GtkStateType        previous_state);
139 140 141 142
static gint gtk_range_scroll_event   (GtkWidget        *widget,
                                      GdkEventScroll   *event);
static void gtk_range_style_set      (GtkWidget        *widget,
                                      GtkStyle         *previous_style);
143 144 145
static void update_slider_position   (GtkRange	       *range,
				      gint              mouse_x,
				      gint              mouse_y);
146
static void stop_scrolling           (GtkRange         *range);
147 148

/* Range methods */
149

150
static void gtk_range_move_slider              (GtkRange         *range,
151 152 153 154 155 156
                                                GtkScrollType     scroll);

/* Internals */
static void          gtk_range_scroll                   (GtkRange      *range,
                                                         GtkScrollType  scroll);
static gboolean      gtk_range_update_mouse_location    (GtkRange      *range);
157 158
static void          gtk_range_calc_layout              (GtkRange      *range,
							 gdouble	adjustment_value);
159 160 161 162
static void          gtk_range_get_props                (GtkRange      *range,
                                                         gint          *slider_width,
                                                         gint          *stepper_size,
                                                         gint          *trough_border,
163 164 165
                                                         gint          *stepper_spacing,
							 gint          *arrow_displacement_x,
							 gint	       *arrow_displacement_y);
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
static void          gtk_range_calc_request             (GtkRange      *range,
                                                         gint           slider_width,
                                                         gint           stepper_size,
                                                         gint           trough_border,
                                                         gint           stepper_spacing,
                                                         GdkRectangle  *range_rect,
                                                         GtkBorder     *border,
                                                         gint          *n_steppers_p,
                                                         gint          *slider_length_p);
static void          gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
                                                         gpointer       data);
static void          gtk_range_adjustment_changed       (GtkAdjustment *adjustment,
                                                         gpointer       data);
static void          gtk_range_add_step_timer           (GtkRange      *range,
                                                         GtkScrollType  step);
static void          gtk_range_remove_step_timer        (GtkRange      *range);
static void          gtk_range_reset_update_timer       (GtkRange      *range);
static void          gtk_range_remove_update_timer      (GtkRange      *range);
static GdkRectangle* get_area                           (GtkRange      *range,
                                                         MouseLocation  location);
186 187 188
static gboolean      gtk_range_real_change_value        (GtkRange      *range,
                                                         GtkScrollType  scroll,
                                                         gdouble        value);
189 190
static void          gtk_range_update_value             (GtkRange      *range);

Elliot Lee's avatar
Elliot Lee committed
191 192

static GtkWidgetClass *parent_class = NULL;
193
static guint signals[LAST_SIGNAL];
Elliot Lee's avatar
Elliot Lee committed
194 195


Manish Singh's avatar
Manish Singh committed
196
GType
197
gtk_range_get_type (void)
Elliot Lee's avatar
Elliot Lee committed
198
{
Manish Singh's avatar
Manish Singh committed
199
  static GType range_type = 0;
Elliot Lee's avatar
Elliot Lee committed
200 201 202

  if (!range_type)
    {
203
      static const GTypeInfo range_info =
Elliot Lee's avatar
Elliot Lee committed
204 205
      {
	sizeof (GtkRangeClass),
Manish Singh's avatar
Manish Singh committed
206 207
	NULL,		/* base_init */
	NULL,		/* base_finalize */
208
	(GClassInitFunc) gtk_range_class_init,
Manish Singh's avatar
Manish Singh committed
209 210
	NULL,		/* class_finalize */
	NULL,		/* class_data */
211
	sizeof (GtkRange),
Manish Singh's avatar
Manish Singh committed
212
	0,		/* n_preallocs */
213
	(GInstanceInitFunc) gtk_range_init,
Manish Singh's avatar
Manish Singh committed
214
	NULL,		/* value_table */
Elliot Lee's avatar
Elliot Lee committed
215 216
      };

Matthias Clasen's avatar
Matthias Clasen committed
217
      range_type = g_type_register_static (GTK_TYPE_WIDGET, I_("GtkRange"),
218
					   &range_info, G_TYPE_FLAG_ABSTRACT);
Elliot Lee's avatar
Elliot Lee committed
219 220 221 222 223 224 225 226
    }

  return range_type;
}

static void
gtk_range_class_init (GtkRangeClass *class)
{
Alexander Larsson's avatar
Alexander Larsson committed
227
  GObjectClass   *gobject_class;
Elliot Lee's avatar
Elliot Lee committed
228 229 230
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;

Alexander Larsson's avatar
Alexander Larsson committed
231
  gobject_class = G_OBJECT_CLASS (class);
Elliot Lee's avatar
Elliot Lee committed
232 233 234
  object_class = (GtkObjectClass*) class;
  widget_class = (GtkWidgetClass*) class;

235
  parent_class = g_type_class_peek_parent (class);
Elliot Lee's avatar
Elliot Lee committed
236

Alexander Larsson's avatar
Alexander Larsson committed
237 238
  gobject_class->set_property = gtk_range_set_property;
  gobject_class->get_property = gtk_range_get_property;
239
  gobject_class->finalize = gtk_range_finalize;
240
  object_class->destroy = gtk_range_destroy;
Elliot Lee's avatar
Elliot Lee committed
241

242 243 244 245
  widget_class->size_request = gtk_range_size_request;
  widget_class->size_allocate = gtk_range_size_allocate;
  widget_class->realize = gtk_range_realize;
  widget_class->unrealize = gtk_range_unrealize;  
246 247
  widget_class->map = gtk_range_map;
  widget_class->unmap = gtk_range_unmap;
Elliot Lee's avatar
Elliot Lee committed
248 249 250 251
  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;
252
  widget_class->scroll_event = gtk_range_scroll_event;
Elliot Lee's avatar
Elliot Lee committed
253 254
  widget_class->enter_notify_event = gtk_range_enter_notify;
  widget_class->leave_notify_event = gtk_range_leave_notify;
Matthias Clasen's avatar
Matthias Clasen committed
255
  widget_class->grab_broken_event = gtk_range_grab_broken;
Matthias Clasen's avatar
Matthias Clasen committed
256 257
  widget_class->grab_notify = gtk_range_grab_notify;
  widget_class->state_changed = gtk_range_state_changed;
258
  widget_class->style_set = gtk_range_style_set;
259 260

  class->move_slider = gtk_range_move_slider;
261
  class->change_value = gtk_range_real_change_value;
262

263 264
  class->slider_detail = "slider";
  class->stepper_detail = "stepper";
265 266

  signals[VALUE_CHANGED] =
Matthias Clasen's avatar
Matthias Clasen committed
267
    g_signal_new (I_("value_changed"),
Manish Singh's avatar
Manish Singh committed
268
                  G_TYPE_FROM_CLASS (gobject_class),
269 270 271
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkRangeClass, value_changed),
                  NULL, NULL,
272
                  _gtk_marshal_NONE__NONE,
273
                  G_TYPE_NONE, 0);
274
  
275
  signals[ADJUST_BOUNDS] =
Matthias Clasen's avatar
Matthias Clasen committed
276
    g_signal_new (I_("adjust_bounds"),
Manish Singh's avatar
Manish Singh committed
277
                  G_TYPE_FROM_CLASS (gobject_class),
278 279 280 281 282 283 284
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkRangeClass, adjust_bounds),
                  NULL, NULL,
                  _gtk_marshal_VOID__DOUBLE,
                  G_TYPE_NONE, 1,
                  G_TYPE_DOUBLE);
  
285
  signals[MOVE_SLIDER] =
Matthias Clasen's avatar
Matthias Clasen committed
286
    g_signal_new (I_("move_slider"),
Manish Singh's avatar
Manish Singh committed
287
                  G_TYPE_FROM_CLASS (gobject_class),
288 289 290
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkRangeClass, move_slider),
                  NULL, NULL,
291
                  _gtk_marshal_VOID__ENUM,
292 293
                  G_TYPE_NONE, 1,
                  GTK_TYPE_SCROLL_TYPE);
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312

  /**
   * 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
313 314
   * the desired number of decimal digits; the default GTK+ handler 
   * clamps the value based on @range->round_digits.
315 316 317 318 319 320 321
   *
   * It is not possible to use delayed update policies in an overridden
   * ::change-value handler.
   *
   * Since: 2.6
   */
  signals[CHANGE_VALUE] =
Matthias Clasen's avatar
Matthias Clasen committed
322
    g_signal_new (I_("change_value"),
323 324 325 326 327 328 329 330
                  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);
331
  
Alexander Larsson's avatar
Alexander Larsson committed
332 333
  g_object_class_install_property (gobject_class,
                                   PROP_UPDATE_POLICY,
334
                                   g_param_spec_enum ("update-policy",
335 336
						      P_("Update policy"),
						      P_("How the range should be updated on the screen"),
Alexander Larsson's avatar
Alexander Larsson committed
337 338
						      GTK_TYPE_UPDATE_TYPE,
						      GTK_UPDATE_CONTINUOUS,
339
						      GTK_PARAM_READWRITE));
340
  
341 342 343
  g_object_class_install_property (gobject_class,
                                   PROP_ADJUSTMENT,
                                   g_param_spec_object ("adjustment",
344 345
							P_("Adjustment"),
							P_("The GtkAdjustment that contains the current value of this range object"),
346
                                                        GTK_TYPE_ADJUSTMENT,
347
                                                        GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
348

Havoc Pennington's avatar
Havoc Pennington committed
349 350 351
  g_object_class_install_property (gobject_class,
                                   PROP_INVERTED,
                                   g_param_spec_boolean ("inverted",
352 353
							P_("Inverted"),
							P_("Invert direction slider moves to increase range value"),
Havoc Pennington's avatar
Havoc Pennington committed
354
                                                         FALSE,
355
                                                         GTK_PARAM_READWRITE));
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374

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

375
  gtk_widget_class_install_style_property (widget_class,
376
					   g_param_spec_int ("slider-width",
377 378
							     P_("Slider Width"),
							     P_("Width of scrollbar or scale thumb"),
379 380
							     0,
							     G_MAXINT,
381
							     14,
382
							     GTK_PARAM_READABLE));
383
  gtk_widget_class_install_style_property (widget_class,
384
					   g_param_spec_int ("trough-border",
385 386
                                                             P_("Trough Border"),
                                                             P_("Spacing between thumb/steppers and outer trough bevel"),
387 388 389
                                                             0,
                                                             G_MAXINT,
                                                             1,
390
                                                             GTK_PARAM_READABLE));
391
  gtk_widget_class_install_style_property (widget_class,
392
					   g_param_spec_int ("stepper-size",
393 394
							     P_("Stepper Size"),
							     P_("Length of step buttons at ends"),
395 396
							     0,
							     G_MAXINT,
397
							     14,
398
							     GTK_PARAM_READABLE));
399
  gtk_widget_class_install_style_property (widget_class,
400
					   g_param_spec_int ("stepper-spacing",
401 402
							     P_("Stepper Spacing"),
							     P_("Spacing between step buttons and thumb"),
403
                                                             0,
404
							     G_MAXINT,
405
							     0,
406
							     GTK_PARAM_READABLE));
407
  gtk_widget_class_install_style_property (widget_class,
408
					   g_param_spec_int ("arrow-displacement-x",
409 410
							     P_("Arrow X Displacement"),
							     P_("How far in the x direction to move the arrow when the button is depressed"),
411 412 413
							     G_MININT,
							     G_MAXINT,
							     0,
414
							     GTK_PARAM_READABLE));
415
  gtk_widget_class_install_style_property (widget_class,
416
					   g_param_spec_int ("arrow-displacement-y",
417 418
							     P_("Arrow Y Displacement"),
							     P_("How far in the y direction to move the arrow when the button is depressed"),
419 420 421
							     G_MININT,
							     G_MAXINT,
							     0,
422
							     GTK_PARAM_READABLE));
Elliot Lee's avatar
Elliot Lee committed
423 424
}

425
static void
Alexander Larsson's avatar
Alexander Larsson committed
426 427 428 429
gtk_range_set_property (GObject      *object,
			guint         prop_id,
			const GValue *value,
			GParamSpec   *pspec)
430 431 432 433 434
{
  GtkRange *range;

  range = GTK_RANGE (object);

Alexander Larsson's avatar
Alexander Larsson committed
435
  switch (prop_id)
436
    {
Alexander Larsson's avatar
Alexander Larsson committed
437 438
    case PROP_UPDATE_POLICY:
      gtk_range_set_update_policy (range, g_value_get_enum (value));
439
      break;
440 441 442
    case PROP_ADJUSTMENT:
      gtk_range_set_adjustment (range, g_value_get_object (value));
      break;
Havoc Pennington's avatar
Havoc Pennington committed
443 444 445
    case PROP_INVERTED:
      gtk_range_set_inverted (range, g_value_get_boolean (value));
      break;
446 447 448 449 450 451
    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;
452
    default:
Alexander Larsson's avatar
Alexander Larsson committed
453
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
454 455 456 457 458
      break;
    }
}

static void
Alexander Larsson's avatar
Alexander Larsson committed
459 460 461 462
gtk_range_get_property (GObject      *object,
			guint         prop_id,
			GValue       *value,
			GParamSpec   *pspec)
463 464 465 466 467
{
  GtkRange *range;

  range = GTK_RANGE (object);

Alexander Larsson's avatar
Alexander Larsson committed
468
  switch (prop_id)
469
    {
Alexander Larsson's avatar
Alexander Larsson committed
470
    case PROP_UPDATE_POLICY:
471
      g_value_set_enum (value, range->update_policy);
472
      break;
473
    case PROP_ADJUSTMENT:
474
      g_value_set_object (value, range->adjustment);
475
      break;
Havoc Pennington's avatar
Havoc Pennington committed
476 477 478
    case PROP_INVERTED:
      g_value_set_boolean (value, range->inverted);
      break;
479 480 481 482 483 484
    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;
485
    default:
Alexander Larsson's avatar
Alexander Larsson committed
486
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
487 488 489 490
      break;
    }
}

Elliot Lee's avatar
Elliot Lee committed
491 492 493
static void
gtk_range_init (GtkRange *range)
{
494 495
  GTK_WIDGET_SET_FLAGS (range, GTK_NO_WINDOW);

Elliot Lee's avatar
Elliot Lee committed
496
  range->adjustment = NULL;
497 498 499 500 501 502 503 504 505 506 507
  range->update_policy = GTK_UPDATE_CONTINUOUS;
  range->inverted = FALSE;
  range->flippable = FALSE;
  range->min_slider_size = 1;
  range->has_stepper_a = FALSE;
  range->has_stepper_b = FALSE;
  range->has_stepper_c = FALSE;
  range->has_stepper_d = FALSE;
  range->need_recalc = TRUE;
  range->round_digits = -1;
  range->layout = g_new0 (GtkRangeLayout, 1);
508 509 510
  range->layout->mouse_location = MOUSE_OUTSIDE;
  range->layout->mouse_x = -1;
  range->layout->mouse_y = -1;
511 512
  range->layout->grab_location = MOUSE_OUTSIDE;
  range->layout->grab_button = 0;
513 514
  range->layout->lower_sensitivity = GTK_SENSITIVITY_AUTO;
  range->layout->upper_sensitivity = GTK_SENSITIVITY_AUTO;
515
  range->timer = NULL;  
Elliot Lee's avatar
Elliot Lee committed
516 517
}

518 519 520 521 522 523 524 525 526 527 528
/**
 * 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
 **/
Elliot Lee's avatar
Elliot Lee committed
529 530 531 532 533
GtkAdjustment*
gtk_range_get_adjustment (GtkRange *range)
{
  g_return_val_if_fail (GTK_IS_RANGE (range), NULL);

534 535 536
  if (!range->adjustment)
    gtk_range_set_adjustment (range, NULL);

Elliot Lee's avatar
Elliot Lee committed
537 538 539
  return range->adjustment;
}

540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
/**
 * 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.
 * 
 **/
Elliot Lee's avatar
Elliot Lee committed
555 556 557 558 559 560
void
gtk_range_set_update_policy (GtkRange      *range,
			     GtkUpdateType  policy)
{
  g_return_if_fail (GTK_IS_RANGE (range));

561
  if (range->update_policy != policy)
Alexander Larsson's avatar
Alexander Larsson committed
562
    {
563
      range->update_policy = policy;
564
      g_object_notify (G_OBJECT (range), "update-policy");
Alexander Larsson's avatar
Alexander Larsson committed
565
    }
Elliot Lee's avatar
Elliot Lee committed
566 567
}

568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
/**
 * 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;
}

584 585 586 587 588 589 590 591 592 593 594 595 596 597
/**
 * 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.
 * 
 **/
Elliot Lee's avatar
Elliot Lee committed
598 599 600 601 602
void
gtk_range_set_adjustment (GtkRange      *range,
			  GtkAdjustment *adjustment)
{
  g_return_if_fail (GTK_IS_RANGE (range));
603 604 605 606 607
  
  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));
Elliot Lee's avatar
Elliot Lee committed
608

609
  if (range->adjustment != adjustment)
Elliot Lee's avatar
Elliot Lee committed
610
    {
611 612
      if (range->adjustment)
	{
Manish Singh's avatar
Manish Singh committed
613 614 615 616 617 618 619
	  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);
620
	}
621

622
      range->adjustment = adjustment;
Manish Singh's avatar
Manish Singh committed
623
      g_object_ref (adjustment);
624 625
      gtk_object_sink (GTK_OBJECT (adjustment));
      
Manish Singh's avatar
Manish Singh committed
626 627 628 629 630 631
      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);
632
      
633
      gtk_range_adjustment_changed (adjustment, range);
634
      g_object_notify (G_OBJECT (range), "adjustment");
Elliot Lee's avatar
Elliot Lee committed
635 636 637
    }
}

638 639 640 641 642 643 644 645 646 647 648
/**
 * 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.
 * 
 **/
649 650 651 652 653 654 655 656 657 658 659
void
gtk_range_set_inverted (GtkRange *range,
                        gboolean  setting)
{
  g_return_if_fail (GTK_IS_RANGE (range));
  
  setting = setting != FALSE;

  if (setting != range->inverted)
    {
      range->inverted = setting;
Havoc Pennington's avatar
Havoc Pennington committed
660
      g_object_notify (G_OBJECT (range), "inverted");
661 662 663 664
      gtk_widget_queue_resize (GTK_WIDGET (range));
    }
}

665 666 667 668 669 670 671 672
/**
 * gtk_range_get_inverted:
 * @range: a #GtkRange
 * 
 * Gets the value set by gtk_range_set_inverted().
 * 
 * Return value: %TRUE if the range is inverted
 **/
673 674 675 676 677 678 679 680
gboolean
gtk_range_get_inverted (GtkRange *range)
{
  g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);

  return range->inverted;
}

681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766
/**
 * 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;
}

767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798
/**
 * 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
799 800
 * 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.)
801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817
 **/
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));
818 819 820

  gtk_adjustment_set_value (range->adjustment, value);
  gtk_adjustment_changed (range->adjustment);
821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861
}

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

862 863 864 865 866 867 868 869 870 871
static gboolean
should_invert (GtkRange *range)
{  
  if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
    return
      (range->inverted && !range->flippable) ||
      (range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) ||
      (!range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL);
  else
    return range->inverted;
Elliot Lee's avatar
Elliot Lee committed
872 873
}

874 875
static void
gtk_range_finalize (GObject *object)
876
{
877
  GtkRange *range = GTK_RANGE (object);
Elliot Lee's avatar
Elliot Lee committed
878

879 880 881
  g_free (range->layout);

  (* G_OBJECT_CLASS (parent_class)->finalize) (object);
Elliot Lee's avatar
Elliot Lee committed
882 883
}

884 885
static void
gtk_range_destroy (GtkObject *object)
Elliot Lee's avatar
Elliot Lee committed
886
{
887
  GtkRange *range = GTK_RANGE (object);
888 889 890 891 892 893

  gtk_range_remove_step_timer (range);
  gtk_range_remove_update_timer (range);
  
  if (range->adjustment)
    {
Manish Singh's avatar
Manish Singh committed
894 895 896 897 898 899 900
      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);
901 902
      range->adjustment = NULL;
    }
Elliot Lee's avatar
Elliot Lee committed
903

904
  (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
Elliot Lee's avatar
Elliot Lee committed
905 906
}

907 908 909
static void
gtk_range_size_request (GtkWidget      *widget,
                        GtkRequisition *requisition)
Elliot Lee's avatar
Elliot Lee committed
910
{
911 912 913 914 915 916 917 918
  GtkRange *range;
  gint slider_width, stepper_size, trough_border, stepper_spacing;
  GdkRectangle range_rect;
  GtkBorder border;
  
  range = GTK_RANGE (widget);
  
  gtk_range_get_props (range,
919 920
                       &slider_width, &stepper_size, &trough_border, &stepper_spacing,
		       NULL, NULL);
921 922 923 924

  gtk_range_calc_request (range, 
                          slider_width, stepper_size, trough_border, stepper_spacing,
                          &range_rect, &border, NULL, NULL);
Elliot Lee's avatar
Elliot Lee committed
925

926 927
  requisition->width = range_rect.width + border.left + border.right;
  requisition->height = range_rect.height + border.top + border.bottom;
Elliot Lee's avatar
Elliot Lee committed
928 929
}

930 931 932
static void
gtk_range_size_allocate (GtkWidget     *widget,
                         GtkAllocation *allocation)
Elliot Lee's avatar
Elliot Lee committed
933
{
934 935 936 937
  GtkRange *range;

  range = GTK_RANGE (widget);

938 939
  widget->allocation = *allocation;
  
940
  range->need_recalc = TRUE;
941
  gtk_range_calc_layout (range, range->adjustment->value);
Elliot Lee's avatar
Elliot Lee committed
942

943 944 945 946 947 948
  if (GTK_WIDGET_REALIZED (range))
    gdk_window_move_resize (range->event_window,
			    widget->allocation.x,
			    widget->allocation.y,
			    widget->allocation.width,
			    widget->allocation.height);
Elliot Lee's avatar
Elliot Lee committed
949 950
}

951 952
static void
gtk_range_realize (GtkWidget *widget)
Elliot Lee's avatar
Elliot Lee committed
953
{
954 955 956
  GtkRange *range;
  GdkWindowAttr attributes;
  gint attributes_mask;  
Elliot Lee's avatar
Elliot Lee committed
957

958
  range = GTK_RANGE (widget);
Elliot Lee's avatar
Elliot Lee committed
959

960
  gtk_range_calc_layout (range, range->adjustment->value);
961 962 963
  
  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);

964
  widget->window = gtk_widget_get_parent_window (widget);
Manish Singh's avatar
Manish Singh committed
965
  g_object_ref (widget->window);
966
  
967 968 969 970 971
  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;
972
  attributes.wclass = GDK_INPUT_ONLY;
973
  attributes.event_mask = gtk_widget_get_events (widget);
974
  attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
975 976 977 978 979 980
			    GDK_BUTTON_RELEASE_MASK |
			    GDK_ENTER_NOTIFY_MASK |
			    GDK_LEAVE_NOTIFY_MASK |
                            GDK_POINTER_MOTION_MASK |
                            GDK_POINTER_MOTION_HINT_MASK);

981
  attributes_mask = GDK_WA_X | GDK_WA_Y;
982

983 984 985
  range->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
					&attributes, attributes_mask);
  gdk_window_set_user_data (range->event_window, range);
986 987

  widget->style = gtk_style_attach (widget->style, widget->window);
Elliot Lee's avatar
Elliot Lee committed
988 989
}

990 991
static void
gtk_range_unrealize (GtkWidget *widget)
992
{
993
  GtkRange *range = GTK_RANGE (widget);
994

995 996 997
  gtk_range_remove_step_timer (range);
  gtk_range_remove_update_timer (range);
  
998 999 1000 1001
  gdk_window_set_user_data (range->event_window, NULL);
  gdk_window_destroy (range->event_window);
  range->event_window = NULL;
  
1002 1003
  if (GTK_WIDGET_CLASS (parent_class)->unrealize)
    (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
1004 1005
}

1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
static void
gtk_range_map (GtkWidget *widget)
{
  GtkRange *range = GTK_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);
    
1021 1022
  stop_scrolling (range);

1023 1024 1025 1026 1027
  gdk_window_hide (range->event_window);

  GTK_WIDGET_CLASS (parent_class)->unmap (widget);
}

1028
static void
1029 1030 1031 1032 1033 1034
draw_stepper (GtkRange     *range,
              GdkRectangle *rect,
              GtkArrowType  arrow_type,
              gboolean      clicked,
              gboolean      prelighted,
              GdkRectangle *area)
1035
{
1036 1037 1038
  GtkStateType state_type;
  GtkShadowType shadow_type;
  GdkRectangle intersection;
1039
  GtkWidget *widget = GTK_WIDGET (range);
1040

1041 1042 1043 1044 1045
  gint arrow_x;
  gint arrow_y;
  gint arrow_width;
  gint arrow_height;

1046
  gboolean arrow_sensitive = TRUE;
1047

1048 1049 1050
  /* More to get the right clip region than for efficiency */
  if (!gdk_rectangle_intersect (area, rect, &intersection))
    return;
1051 1052 1053

  intersection.x += widget->allocation.x;
  intersection.y += widget->allocation.y;
1054

1055 1056 1057 1058
  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)))
1059
    {
1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075
      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;
        }
1076
    }
1077
  else
1078
    {
1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093
      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;
        }
1094 1095
    }

1096
  if (!GTK_WIDGET_IS_SENSITIVE (range) || !arrow_sensitive)