1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3 * Copyright (C) 2001 Red Hat, Inc.
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
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /*
20 * Modified by the GTK+ Team and others 1997-2004. See the AUTHORS
21 * file for a list of people on the GTK+ Team. See the ChangeLog
22 * files for a list of changes. These files are distributed with
23 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
24 */
25
26 #include "config.h"
27
28 #include <stdio.h>
29 #include <math.h>
30
31 #include "gtkrange.h"
32 #include "gtkrangeprivate.h"
33
34 #include "gtkadjustmentprivate.h"
35 #include "gtkboxgadgetprivate.h"
36 #include "gtkbuiltiniconprivate.h"
37 #include "gtkcsscustomgadgetprivate.h"
38 #include "gtkcolorscaleprivate.h"
39 #include "gtkintl.h"
40 #include "gtkgesturelongpressprivate.h"
41 #include "gtkmain.h"
42 #include "gtkmarshalers.h"
43 #include "gtkorientableprivate.h"
44 #include "gtkprivate.h"
45 #include "gtkscale.h"
46 #include "gtkscrollbar.h"
47 #include "gtktypebuiltins.h"
48 #include "gtkwindow.h"
49 #include "gtkwidgetprivate.h"
50 #include "a11y/gtkrangeaccessible.h"
51 #include "gtkcssstylepropertyprivate.h"
52
53 /**
54 * SECTION:gtkrange
55 * @Short_description: Base class for widgets which visualize an adjustment
56 * @Title: GtkRange
57 *
58 * #GtkRange is the common base class for widgets which visualize an
59 * adjustment, e.g #GtkScale or #GtkScrollbar.
60 *
61 * Apart from signals for monitoring the parameters of the adjustment,
62 * #GtkRange provides properties and methods for influencing the sensitivity
63 * of the “steppers”. It also provides properties and methods for setting a
64 * “fill level” on range widgets. See gtk_range_set_fill_level().
65 */
66
67
68 #define TIMEOUT_INITIAL 500
69 #define TIMEOUT_REPEAT 250
70 #define AUTOSCROLL_FACTOR 20
71 #define SCROLL_EDGE_SIZE 15
72 #define MARK_SNAP_LENGTH 12
73
74 typedef struct _GtkRangeStepTimer GtkRangeStepTimer;
75
76 struct _GtkRangePrivate
77 {
78 GtkCssGadget *mouse_location;
79 /* last mouse coords we got, or G_MININT if mouse is outside the range */
80 gint mouse_x;
81 gint mouse_y;
82 GtkCssGadget *grab_location; /* "grabbed" mouse location, NULL for no grab */
83
84 GtkRangeStepTimer *timer;
85
86 GtkAdjustment *adjustment;
87 GtkSensitivityType lower_sensitivity;
88 GtkSensitivityType upper_sensitivity;
89
90 GdkWindow *event_window;
91
92 /* Steppers are: < > ---- < >
93 * a b c d
94 */
95 GtkCssGadget *gadget;
96 GtkCssGadget *contents_gadget;
97 GtkCssGadget *stepper_a_gadget;
98 GtkCssGadget *stepper_b_gadget;
99 GtkCssGadget *stepper_c_gadget;
100 GtkCssGadget *stepper_d_gadget;
101 GtkCssGadget *trough_gadget;
102 GtkCssGadget *fill_gadget;
103 GtkCssGadget *highlight_gadget;
104 GtkCssGadget *slider_gadget;
105
106 GtkOrientation orientation;
107
108 gdouble fill_level;
109 gdouble *marks;
110
111 gint *mark_pos;
112 gint min_slider_size;
113 gint n_marks;
114 gint round_digits; /* Round off value to this many digits, -1 for no rounding */
115 gint slide_initial_slider_position;
116 gint slide_initial_coordinate_delta;
117
118 guint flippable : 1;
119 guint inverted : 1;
120 guint slider_size_fixed : 1;
121 guint slider_use_min_size : 1;
122 guint trough_click_forward : 1; /* trough click was on the forward side of slider */
123
124 /* Stepper sensitivity */
125 guint lower_sensitive : 1;
126 guint upper_sensitive : 1;
127
128 /* The range has an origin, should be drawn differently. Used by GtkScale */
129 guint has_origin : 1;
130
131 /* Whether we're doing fine adjustment */
132 guint zoom : 1;
133
134 /* Fill level */
135 guint show_fill_level : 1;
136 guint restrict_to_fill_level : 1;
137
138 /* Whether dragging is ongoing */
139 guint in_drag : 1;
140
141 GtkGesture *long_press_gesture;
142 GtkGesture *multipress_gesture;
143 GtkGesture *drag_gesture;
144
145 GtkScrollType autoscroll_mode;
146 guint autoscroll_id;
147 };
148
149
150 enum {
151 PROP_0,
152 PROP_ADJUSTMENT,
153 PROP_INVERTED,
154 PROP_LOWER_STEPPER_SENSITIVITY,
155 PROP_UPPER_STEPPER_SENSITIVITY,
156 PROP_SHOW_FILL_LEVEL,
157 PROP_RESTRICT_TO_FILL_LEVEL,
158 PROP_FILL_LEVEL,
159 PROP_ROUND_DIGITS,
160 PROP_ORIENTATION,
161 LAST_PROP = PROP_ORIENTATION
162 };
163
164 enum {
165 VALUE_CHANGED,
166 ADJUST_BOUNDS,
167 MOVE_SLIDER,
168 CHANGE_VALUE,
169 LAST_SIGNAL
170 };
171
172 static void gtk_range_set_property (GObject *object,
173 guint prop_id,
174 const GValue *value,
175 GParamSpec *pspec);
176 static void gtk_range_get_property (GObject *object,
177 guint prop_id,
178 GValue *value,
179 GParamSpec *pspec);
180 static void gtk_range_finalize (GObject *object);
181 static void gtk_range_destroy (GtkWidget *widget);
182 static void gtk_range_get_preferred_width
183 (GtkWidget *widget,
184 gint *minimum,
185 gint *natural);
186 static void gtk_range_get_preferred_height
187 (GtkWidget *widget,
188 gint *minimum,
189 gint *natural);
190 static void gtk_range_size_allocate (GtkWidget *widget,
191 GtkAllocation *allocation);
192 static void gtk_range_realize (GtkWidget *widget);
193 static void gtk_range_unrealize (GtkWidget *widget);
194 static void gtk_range_map (GtkWidget *widget);
195 static void gtk_range_unmap (GtkWidget *widget);
196 static gboolean gtk_range_draw (GtkWidget *widget,
197 cairo_t *cr);
198
199 static void gtk_range_multipress_gesture_pressed (GtkGestureMultiPress *gesture,
200 guint n_press,
201 gdouble x,
202 gdouble y,
203 GtkRange *range);
204 static void gtk_range_multipress_gesture_released (GtkGestureMultiPress *gesture,
205 guint n_press,
206 gdouble x,
207 gdouble y,
208 GtkRange *range);
209 static void gtk_range_drag_gesture_begin (GtkGestureDrag *gesture,
210 gdouble offset_x,
211 gdouble offset_y,
212 GtkRange *range);
213 static void gtk_range_drag_gesture_update (GtkGestureDrag *gesture,
214 gdouble offset_x,
215 gdouble offset_y,
216 GtkRange *range);
217 static void gtk_range_long_press_gesture_pressed (GtkGestureLongPress *gesture,
218 gdouble x,
219 gdouble y,
220 GtkRange *range);
221
222
223 static gboolean gtk_range_scroll_event (GtkWidget *widget,
224 GdkEventScroll *event);
225 static gboolean gtk_range_event (GtkWidget *widget,
226 GdkEvent *event);
227 static void update_slider_position (GtkRange *range,
228 gint mouse_x,
229 gint mouse_y);
230 static void stop_scrolling (GtkRange *range);
231 static void add_autoscroll (GtkRange *range);
232 static void remove_autoscroll (GtkRange *range);
233
234 /* Range methods */
235
236 static void gtk_range_move_slider (GtkRange *range,
237 GtkScrollType scroll);
238
239 /* Internals */
240 static void gtk_range_compute_slider_position (GtkRange *range,
241 gdouble adjustment_value,
242 GdkRectangle *slider_rect);
243 static gboolean gtk_range_scroll (GtkRange *range,
244 GtkScrollType scroll);
245 static void gtk_range_update_mouse_location (GtkRange *range);
246 static void gtk_range_calc_slider (GtkRange *range);
247 static void gtk_range_calc_stepper_sensitivity (GtkRange *range);
248 static void gtk_range_calc_marks (GtkRange *range);
249 static void gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
250 gpointer data);
251 static void gtk_range_adjustment_changed (GtkAdjustment *adjustment,
252 gpointer data);
253 static void gtk_range_add_step_timer (GtkRange *range,
254 GtkScrollType step);
255 static void gtk_range_remove_step_timer (GtkRange *range);
256 static gboolean gtk_range_real_change_value (GtkRange *range,
257 GtkScrollType scroll,
258 gdouble value);
259 static gboolean gtk_range_key_press (GtkWidget *range,
260 GdkEventKey *event);
261 static void gtk_range_state_flags_changed (GtkWidget *widget,
262 GtkStateFlags previous_state);
263 static void gtk_range_direction_changed (GtkWidget *widget,
264 GtkTextDirection previous_direction);
265 static void gtk_range_measure_trough (GtkCssGadget *gadget,
266 GtkOrientation orientation,
267 gint for_size,
268 gint *minimum,
269 gint *natural,
270 gint *minimum_baseline,
271 gint *natural_baseline,
272 gpointer user_data);
273 static void gtk_range_allocate_trough (GtkCssGadget *gadget,
274 const GtkAllocation *allocation,
275 int baseline,
276 GtkAllocation *out_clip,
277 gpointer data);
278 static gboolean gtk_range_render_trough (GtkCssGadget *gadget,
279 cairo_t *cr,
280 int x,
281 int y,
282 int width,
283 int height,
284 gpointer user_data);
285 static void gtk_range_measure (GtkCssGadget *gadget,
286 GtkOrientation orientation,
287 gint for_size,
288 gint *minimum,
289 gint *natural,
290 gint *minimum_baseline,
291 gint *natural_baseline,
292 gpointer user_data);
293 static void gtk_range_allocate (GtkCssGadget *gadget,
294 const GtkAllocation *allocation,
295 int baseline,
296 GtkAllocation *out_clip,
297 gpointer data);
298 static gboolean gtk_range_render (GtkCssGadget *gadget,
299 cairo_t *cr,
300 int x,
301 int y,
302 int width,
303 int height,
304 gpointer user_data);
305
306 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkRange, gtk_range, GTK_TYPE_WIDGET,
307 G_ADD_PRIVATE (GtkRange)
308 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,
309 NULL))
310
311 static guint signals[LAST_SIGNAL];
312 static GParamSpec *properties[LAST_PROP];
313
314 static void
gtk_range_class_init(GtkRangeClass * class)315 gtk_range_class_init (GtkRangeClass *class)
316 {
317 GObjectClass *gobject_class;
318 GtkWidgetClass *widget_class;
319
320 gobject_class = G_OBJECT_CLASS (class);
321 widget_class = (GtkWidgetClass*) class;
322
323 gobject_class->set_property = gtk_range_set_property;
324 gobject_class->get_property = gtk_range_get_property;
325 gobject_class->finalize = gtk_range_finalize;
326
327 widget_class->destroy = gtk_range_destroy;
328 widget_class->get_preferred_width = gtk_range_get_preferred_width;
329 widget_class->get_preferred_height = gtk_range_get_preferred_height;
330 widget_class->size_allocate = gtk_range_size_allocate;
331 widget_class->realize = gtk_range_realize;
332 widget_class->unrealize = gtk_range_unrealize;
333 widget_class->map = gtk_range_map;
334 widget_class->unmap = gtk_range_unmap;
335 widget_class->draw = gtk_range_draw;
336 widget_class->event = gtk_range_event;
337 widget_class->scroll_event = gtk_range_scroll_event;
338 widget_class->key_press_event = gtk_range_key_press;
339 widget_class->state_flags_changed = gtk_range_state_flags_changed;
340 widget_class->direction_changed = gtk_range_direction_changed;
341
342 class->move_slider = gtk_range_move_slider;
343 class->change_value = gtk_range_real_change_value;
344
345 /**
346 * GtkRange::value-changed:
347 * @range: the #GtkRange that received the signal
348 *
349 * Emitted when the range value changes.
350 */
351 signals[VALUE_CHANGED] =
352 g_signal_new (I_("value-changed"),
353 G_TYPE_FROM_CLASS (gobject_class),
354 G_SIGNAL_RUN_LAST,
355 G_STRUCT_OFFSET (GtkRangeClass, value_changed),
356 NULL, NULL,
357 NULL,
358 G_TYPE_NONE, 0);
359
360 /**
361 * GtkRange::adjust-bounds:
362 * @range: the #GtkRange that received the signal
363 * @value: the value before we clamp
364 *
365 * Emitted before clamping a value, to give the application a
366 * chance to adjust the bounds.
367 */
368 signals[ADJUST_BOUNDS] =
369 g_signal_new (I_("adjust-bounds"),
370 G_TYPE_FROM_CLASS (gobject_class),
371 G_SIGNAL_RUN_LAST,
372 G_STRUCT_OFFSET (GtkRangeClass, adjust_bounds),
373 NULL, NULL,
374 NULL,
375 G_TYPE_NONE, 1,
376 G_TYPE_DOUBLE);
377
378 /**
379 * GtkRange::move-slider:
380 * @range: the #GtkRange that received the signal
381 * @step: how to move the slider
382 *
383 * Virtual function that moves the slider. Used for keybindings.
384 */
385 signals[MOVE_SLIDER] =
386 g_signal_new (I_("move-slider"),
387 G_TYPE_FROM_CLASS (gobject_class),
388 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
389 G_STRUCT_OFFSET (GtkRangeClass, move_slider),
390 NULL, NULL,
391 NULL,
392 G_TYPE_NONE, 1,
393 GTK_TYPE_SCROLL_TYPE);
394
395 /**
396 * GtkRange::change-value:
397 * @range: the #GtkRange that received the signal
398 * @scroll: the type of scroll action that was performed
399 * @value: the new value resulting from the scroll action
400 *
401 * The #GtkRange::change-value signal is emitted when a scroll action is
402 * performed on a range. It allows an application to determine the
403 * type of scroll event that occurred and the resultant new value.
404 * The application can handle the event itself and return %TRUE to
405 * prevent further processing. Or, by returning %FALSE, it can pass
406 * the event to other handlers until the default GTK+ handler is
407 * reached.
408 *
409 * The value parameter is unrounded. An application that overrides
410 * the GtkRange::change-value signal is responsible for clamping the
411 * value to the desired number of decimal digits; the default GTK+
412 * handler clamps the value based on #GtkRange:round-digits.
413 *
414 * Returns: %TRUE to prevent other handlers from being invoked for
415 * the signal, %FALSE to propagate the signal further
416 *
417 * Since: 2.6
418 */
419 signals[CHANGE_VALUE] =
420 g_signal_new (I_("change-value"),
421 G_TYPE_FROM_CLASS (gobject_class),
422 G_SIGNAL_RUN_LAST,
423 G_STRUCT_OFFSET (GtkRangeClass, change_value),
424 _gtk_boolean_handled_accumulator, NULL,
425 _gtk_marshal_BOOLEAN__ENUM_DOUBLE,
426 G_TYPE_BOOLEAN, 2,
427 GTK_TYPE_SCROLL_TYPE,
428 G_TYPE_DOUBLE);
429
430 g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation");
431
432 properties[PROP_ADJUSTMENT] =
433 g_param_spec_object ("adjustment",
434 P_("Adjustment"),
435 P_("The GtkAdjustment that contains the current value of this range object"),
436 GTK_TYPE_ADJUSTMENT,
437 GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT);
438
439 properties[PROP_INVERTED] =
440 g_param_spec_boolean ("inverted",
441 P_("Inverted"),
442 P_("Invert direction slider moves to increase range value"),
443 FALSE,
444 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
445
446 properties[PROP_LOWER_STEPPER_SENSITIVITY] =
447 g_param_spec_enum ("lower-stepper-sensitivity",
448 P_("Lower stepper sensitivity"),
449 P_("The sensitivity policy for the stepper that points to the adjustment's lower side"),
450 GTK_TYPE_SENSITIVITY_TYPE,
451 GTK_SENSITIVITY_AUTO,
452 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_DEPRECATED);
453
454 properties[PROP_UPPER_STEPPER_SENSITIVITY] =
455 g_param_spec_enum ("upper-stepper-sensitivity",
456 P_("Upper stepper sensitivity"),
457 P_("The sensitivity policy for the stepper that points to the adjustment's upper side"),
458 GTK_TYPE_SENSITIVITY_TYPE,
459 GTK_SENSITIVITY_AUTO,
460 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_DEPRECATED);
461
462 /**
463 * GtkRange:show-fill-level:
464 *
465 * The show-fill-level property controls whether fill level indicator
466 * graphics are displayed on the trough. See
467 * gtk_range_set_show_fill_level().
468 *
469 * Since: 2.12
470 **/
471 properties[PROP_SHOW_FILL_LEVEL] =
472 g_param_spec_boolean ("show-fill-level",
473 P_("Show Fill Level"),
474 P_("Whether to display a fill level indicator graphics on trough."),
475 FALSE,
476 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
477
478 /**
479 * GtkRange:restrict-to-fill-level:
480 *
481 * The restrict-to-fill-level property controls whether slider
482 * movement is restricted to an upper boundary set by the
483 * fill level. See gtk_range_set_restrict_to_fill_level().
484 *
485 * Since: 2.12
486 **/
487 properties[PROP_RESTRICT_TO_FILL_LEVEL] =
488 g_param_spec_boolean ("restrict-to-fill-level",
489 P_("Restrict to Fill Level"),
490 P_("Whether to restrict the upper boundary to the fill level."),
491 TRUE,
492 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
493
494 /**
495 * GtkRange:fill-level:
496 *
497 * The fill level (e.g. prebuffering of a network stream).
498 * See gtk_range_set_fill_level().
499 *
500 * Since: 2.12
501 **/
502 properties[PROP_FILL_LEVEL] =
503 g_param_spec_double ("fill-level",
504 P_("Fill Level"),
505 P_("The fill level."),
506 -G_MAXDOUBLE, G_MAXDOUBLE,
507 G_MAXDOUBLE,
508 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
509
510 /**
511 * GtkRange:round-digits:
512 *
513 * The number of digits to round the value to when
514 * it changes, or -1. See #GtkRange::change-value.
515 *
516 * Since: 2.24
517 */
518 properties[PROP_ROUND_DIGITS] =
519 g_param_spec_int ("round-digits",
520 P_("Round Digits"),
521 P_("The number of digits to round the value to."),
522 -1, G_MAXINT,
523 -1,
524 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
525
526 g_object_class_install_properties (gobject_class, LAST_PROP, properties);
527
528 /**
529 * GtkRange:slider-width:
530 *
531 * Width of scrollbar or scale thumb.
532 *
533 * Deprecated: 3.20: Use the min-height/min-width CSS properties on the
534 * slider element. The value of this style property is ignored.
535 */
536 gtk_widget_class_install_style_property (widget_class,
537 g_param_spec_int ("slider-width",
538 P_("Slider Width"),
539 P_("Width of scrollbar or scale thumb"),
540 0,
541 G_MAXINT,
542 14,
543 GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
544 /**
545 * GtkRange:trough-border:
546 *
547 * Spacing between thumb/steppers and outer trough bevel.
548 *
549 * Deprecated: 3.20: Use the margin/padding CSS properties on the trough and
550 * stepper elements. The value of this style property is ignored.
551 */
552 gtk_widget_class_install_style_property (widget_class,
553 g_param_spec_int ("trough-border",
554 P_("Trough Border"),
555 P_("Spacing between thumb/steppers and outer trough bevel"),
556 0,
557 G_MAXINT,
558 1,
559 GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
560 /**
561 * GtkRange:stepper-size:
562 *
563 * Length of step buttons at ends.
564 *
565 * Deprecated: 3.20: Use the min-height/min-width CSS properties on the
566 * stepper elements. The value of this style property is ignored.
567 */
568 gtk_widget_class_install_style_property (widget_class,
569 g_param_spec_int ("stepper-size",
570 P_("Stepper Size"),
571 P_("Length of step buttons at ends"),
572 0,
573 G_MAXINT,
574 14,
575 GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
576 /**
577 * GtkRange:stepper-spacing:
578 *
579 * The spacing between the stepper buttons and thumb. Note that
580 * stepper-spacing won't have any effect if there are no steppers.
581 *
582 * Deprecated: 3.20: Use the margin CSS property on the stepper elements.
583 * The value of this style property is ignored.
584 */
585 gtk_widget_class_install_style_property (widget_class,
586 g_param_spec_int ("stepper-spacing",
587 P_("Stepper Spacing"),
588 P_("Spacing between step buttons and thumb"),
589 0,
590 G_MAXINT,
591 0,
592 GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
593
594 /**
595 * GtkRange:arrow-displacement-x:
596 *
597 * How far in the x direction to move the arrow when the button is depressed.
598 *
599 * Deprecated: 3.20: The value of this style property is ignored.
600 */
601 gtk_widget_class_install_style_property (widget_class,
602 g_param_spec_int ("arrow-displacement-x",
603 P_("Arrow X Displacement"),
604 P_("How far in the x direction to move the arrow when the button is depressed"),
605 G_MININT,
606 G_MAXINT,
607 0,
608 GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
609
610 /**
611 * GtkRange:arrow-displacement-y:
612 *
613 * How far in the y direction to move the arrow when the button is depressed.
614 *
615 * Deprecated: 3.20: The value of this style property is ignored.
616 */
617 gtk_widget_class_install_style_property (widget_class,
618 g_param_spec_int ("arrow-displacement-y",
619 P_("Arrow Y Displacement"),
620 P_("How far in the y direction to move the arrow when the button is depressed"),
621 G_MININT,
622 G_MAXINT,
623 0,
624 GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
625
626 /**
627 * GtkRange:trough-under-steppers:
628 *
629 * Whether to draw the trough across the full length of the range or
630 * to exclude the steppers and their spacing.
631 *
632 * Since: 2.10
633 *
634 * Deprecated: 3.20: The value of this style property is ignored, and the
635 * widget will behave as if it was set to %TRUE.
636 */
637 gtk_widget_class_install_style_property (widget_class,
638 g_param_spec_boolean ("trough-under-steppers",
639 P_("Trough Under Steppers"),
640 P_("Whether to draw trough for full length of range or exclude the steppers and spacing"),
641 TRUE,
642 GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
643
644 /**
645 * GtkRange:arrow-scaling:
646 *
647 * The arrow size proportion relative to the scroll button size.
648 *
649 * Since: 2.14
650 *
651 * Deprecated: 3.20: Use min-width/min-height on the "button" node instead.
652 * The value of this style property is ignored.
653 */
654 gtk_widget_class_install_style_property (widget_class,
655 g_param_spec_float ("arrow-scaling",
656 P_("Arrow scaling"),
657 P_("Arrow scaling with regard to scroll button size"),
658 0.0, 1.0, 0.5,
659 GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
660
661 gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_RANGE_ACCESSIBLE);
662 }
663
664 static void
gtk_range_sync_orientation(GtkRange * range)665 gtk_range_sync_orientation (GtkRange *range)
666 {
667 GtkRangePrivate *priv = range->priv;
668 GtkOrientation orientation;
669
670 orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (range));
671 _gtk_orientable_set_style_classes (GTK_ORIENTABLE (range));
672 gtk_box_gadget_set_orientation (GTK_BOX_GADGET (priv->contents_gadget), orientation);
673 }
674
675 static void
gtk_range_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)676 gtk_range_set_property (GObject *object,
677 guint prop_id,
678 const GValue *value,
679 GParamSpec *pspec)
680 {
681 GtkRange *range = GTK_RANGE (object);
682 GtkRangePrivate *priv = range->priv;
683
684 switch (prop_id)
685 {
686 case PROP_ORIENTATION:
687 if (priv->orientation != g_value_get_enum (value))
688 {
689 priv->orientation = g_value_get_enum (value);
690 gtk_range_sync_orientation (range);
691 gtk_widget_queue_resize (GTK_WIDGET (range));
692 g_object_notify_by_pspec (object, pspec);
693 }
694 break;
695 case PROP_ADJUSTMENT:
696 gtk_range_set_adjustment (range, g_value_get_object (value));
697 break;
698 case PROP_INVERTED:
699 gtk_range_set_inverted (range, g_value_get_boolean (value));
700 break;
701 case PROP_LOWER_STEPPER_SENSITIVITY:
702 gtk_range_set_lower_stepper_sensitivity (range, g_value_get_enum (value));
703 break;
704 case PROP_UPPER_STEPPER_SENSITIVITY:
705 gtk_range_set_upper_stepper_sensitivity (range, g_value_get_enum (value));
706 break;
707 case PROP_SHOW_FILL_LEVEL:
708 gtk_range_set_show_fill_level (range, g_value_get_boolean (value));
709 break;
710 case PROP_RESTRICT_TO_FILL_LEVEL:
711 gtk_range_set_restrict_to_fill_level (range, g_value_get_boolean (value));
712 break;
713 case PROP_FILL_LEVEL:
714 gtk_range_set_fill_level (range, g_value_get_double (value));
715 break;
716 case PROP_ROUND_DIGITS:
717 gtk_range_set_round_digits (range, g_value_get_int (value));
718 break;
719 default:
720 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
721 break;
722 }
723 }
724
725 static void
gtk_range_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)726 gtk_range_get_property (GObject *object,
727 guint prop_id,
728 GValue *value,
729 GParamSpec *pspec)
730 {
731 GtkRange *range = GTK_RANGE (object);
732 GtkRangePrivate *priv = range->priv;
733
734 switch (prop_id)
735 {
736 case PROP_ORIENTATION:
737 g_value_set_enum (value, priv->orientation);
738 break;
739 case PROP_ADJUSTMENT:
740 g_value_set_object (value, priv->adjustment);
741 break;
742 case PROP_INVERTED:
743 g_value_set_boolean (value, priv->inverted);
744 break;
745 case PROP_LOWER_STEPPER_SENSITIVITY:
746 g_value_set_enum (value, gtk_range_get_lower_stepper_sensitivity (range));
747 break;
748 case PROP_UPPER_STEPPER_SENSITIVITY:
749 g_value_set_enum (value, gtk_range_get_upper_stepper_sensitivity (range));
750 break;
751 case PROP_SHOW_FILL_LEVEL:
752 g_value_set_boolean (value, gtk_range_get_show_fill_level (range));
753 break;
754 case PROP_RESTRICT_TO_FILL_LEVEL:
755 g_value_set_boolean (value, gtk_range_get_restrict_to_fill_level (range));
756 break;
757 case PROP_FILL_LEVEL:
758 g_value_set_double (value, gtk_range_get_fill_level (range));
759 break;
760 case PROP_ROUND_DIGITS:
761 g_value_set_int (value, gtk_range_get_round_digits (range));
762 break;
763 default:
764 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
765 break;
766 }
767 }
768
769 static void
gtk_range_init(GtkRange * range)770 gtk_range_init (GtkRange *range)
771 {
772 GtkRangePrivate *priv;
773 GtkCssNode *widget_node;
774
775 range->priv = gtk_range_get_instance_private (range);
776 priv = range->priv;
777
778 gtk_widget_set_has_window (GTK_WIDGET (range), FALSE);
779
780 priv->orientation = GTK_ORIENTATION_HORIZONTAL;
781 priv->adjustment = NULL;
782 priv->inverted = FALSE;
783 priv->flippable = FALSE;
784 priv->min_slider_size = 1;
785 priv->round_digits = -1;
786 priv->mouse_x = G_MININT;
787 priv->mouse_y = G_MININT;
788 priv->lower_sensitivity = GTK_SENSITIVITY_AUTO;
789 priv->upper_sensitivity = GTK_SENSITIVITY_AUTO;
790 priv->lower_sensitive = TRUE;
791 priv->upper_sensitive = TRUE;
792 priv->has_origin = FALSE;
793 priv->show_fill_level = FALSE;
794 priv->restrict_to_fill_level = TRUE;
795 priv->fill_level = G_MAXDOUBLE;
796 priv->timer = NULL;
797
798 _gtk_orientable_set_style_classes (GTK_ORIENTABLE (range));
799
800 widget_node = gtk_widget_get_css_node (GTK_WIDGET (range));
801 priv->gadget = gtk_css_custom_gadget_new_for_node (widget_node,
802 GTK_WIDGET (range),
803 gtk_range_measure,
804 gtk_range_allocate,
805 gtk_range_render,
806 NULL, NULL);
807 priv->contents_gadget = gtk_box_gadget_new ("contents",
808 GTK_WIDGET (range),
809 priv->gadget, NULL);
810 priv->trough_gadget = gtk_css_custom_gadget_new ("trough",
811 GTK_WIDGET (range),
812 NULL, NULL,
813 gtk_range_measure_trough,
814 gtk_range_allocate_trough,
815 gtk_range_render_trough,
816 NULL, NULL);
817 gtk_css_gadget_set_state (priv->trough_gadget,
818 gtk_css_node_get_state (widget_node));
819 gtk_box_gadget_insert_gadget (GTK_BOX_GADGET (priv->contents_gadget), -1, priv->trough_gadget,
820 TRUE, GTK_ALIGN_CENTER);
821
822 priv->slider_gadget = gtk_builtin_icon_new ("slider",
823 GTK_WIDGET (range),
824 priv->trough_gadget, NULL);
825 gtk_css_gadget_set_state (priv->slider_gadget,
826 gtk_css_node_get_state (widget_node));
827
828 /* Note: Order is important here.
829 * The ::drag-begin handler relies on the state set up by the
830 * multipress ::pressed handler. Gestures are handling events
831 * in the oppposite order in which they are added to their
832 * widget.
833 */
834 priv->drag_gesture = gtk_gesture_drag_new (GTK_WIDGET (range));
835 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->drag_gesture), 0);
836 g_signal_connect (priv->drag_gesture, "drag-begin",
837 G_CALLBACK (gtk_range_drag_gesture_begin), range);
838 g_signal_connect (priv->drag_gesture, "drag-update",
839 G_CALLBACK (gtk_range_drag_gesture_update), range);
840
841 priv->multipress_gesture = gtk_gesture_multi_press_new (GTK_WIDGET (range));
842 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->multipress_gesture), 0);
843 gtk_gesture_group (priv->drag_gesture, priv->multipress_gesture);
844 g_signal_connect (priv->multipress_gesture, "pressed",
845 G_CALLBACK (gtk_range_multipress_gesture_pressed), range);
846 g_signal_connect (priv->multipress_gesture, "released",
847 G_CALLBACK (gtk_range_multipress_gesture_released), range);
848
849 priv->long_press_gesture = gtk_gesture_long_press_new (GTK_WIDGET (range));
850 g_object_set (priv->long_press_gesture, "delay-factor", 2.0, NULL);
851 gtk_gesture_group (priv->drag_gesture, priv->long_press_gesture);
852 g_signal_connect (priv->long_press_gesture, "pressed",
853 G_CALLBACK (gtk_range_long_press_gesture_pressed), range);
854 }
855
856 /**
857 * gtk_range_get_adjustment:
858 * @range: a #GtkRange
859 *
860 * Get the #GtkAdjustment which is the “model” object for #GtkRange.
861 * See gtk_range_set_adjustment() for details.
862 * The return value does not have a reference added, so should not
863 * be unreferenced.
864 *
865 * Returns: (transfer none): a #GtkAdjustment
866 **/
867 GtkAdjustment*
gtk_range_get_adjustment(GtkRange * range)868 gtk_range_get_adjustment (GtkRange *range)
869 {
870 GtkRangePrivate *priv;
871
872 g_return_val_if_fail (GTK_IS_RANGE (range), NULL);
873
874 priv = range->priv;
875
876 if (!priv->adjustment)
877 gtk_range_set_adjustment (range, NULL);
878
879 return priv->adjustment;
880 }
881
882 /**
883 * gtk_range_set_adjustment:
884 * @range: a #GtkRange
885 * @adjustment: a #GtkAdjustment
886 *
887 * Sets the adjustment to be used as the “model” object for this range
888 * widget. The adjustment indicates the current range value, the
889 * minimum and maximum range values, the step/page increments used
890 * for keybindings and scrolling, and the page size. The page size
891 * is normally 0 for #GtkScale and nonzero for #GtkScrollbar, and
892 * indicates the size of the visible area of the widget being scrolled.
893 * The page size affects the size of the scrollbar slider.
894 **/
895 void
gtk_range_set_adjustment(GtkRange * range,GtkAdjustment * adjustment)896 gtk_range_set_adjustment (GtkRange *range,
897 GtkAdjustment *adjustment)
898 {
899 GtkRangePrivate *priv;
900
901 g_return_if_fail (GTK_IS_RANGE (range));
902
903 priv = range->priv;
904
905 if (!adjustment)
906 adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
907 else
908 g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
909
910 if (priv->adjustment != adjustment)
911 {
912 if (priv->adjustment)
913 {
914 g_signal_handlers_disconnect_by_func (priv->adjustment,
915 gtk_range_adjustment_changed,
916 range);
917 g_signal_handlers_disconnect_by_func (priv->adjustment,
918 gtk_range_adjustment_value_changed,
919 range);
920 g_object_unref (priv->adjustment);
921 }
922
923 priv->adjustment = adjustment;
924 g_object_ref_sink (adjustment);
925
926 g_signal_connect (adjustment, "changed",
927 G_CALLBACK (gtk_range_adjustment_changed),
928 range);
929 g_signal_connect (adjustment, "value-changed",
930 G_CALLBACK (gtk_range_adjustment_value_changed),
931 range);
932
933 gtk_range_adjustment_changed (adjustment, range);
934 g_object_notify_by_pspec (G_OBJECT (range), properties[PROP_ADJUSTMENT]);
935 }
936 }
937
938 static gboolean
should_invert(GtkRange * range)939 should_invert (GtkRange *range)
940 {
941 GtkRangePrivate *priv = range->priv;
942
943 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
944 return
945 (priv->inverted && !priv->flippable) ||
946 (priv->inverted && priv->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) ||
947 (!priv->inverted && priv->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL);
948 else
949 return priv->inverted;
950 }
951
952 static gboolean
should_invert_move(GtkRange * range,GtkOrientation move_orientation)953 should_invert_move (GtkRange *range,
954 GtkOrientation move_orientation)
955 {
956 GtkRangePrivate *priv = range->priv;
957
958 /* If the move is parallel to the range, use general check for inversion */
959 if (move_orientation == priv->orientation)
960 return should_invert (range);
961
962 /* H scale/V move: Always invert, so down/up always dec/increase the value */
963 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL && GTK_IS_SCALE (range))
964 return TRUE;
965
966 /* V range/H move: Left/right always dec/increase the value */
967 return FALSE;
968 }
969
970 static void
update_highlight_position(GtkRange * range)971 update_highlight_position (GtkRange *range)
972 {
973 GtkRangePrivate *priv = range->priv;
974
975 if (!priv->highlight_gadget)
976 return;
977
978 if (should_invert (range))
979 {
980 gtk_css_gadget_remove_class (priv->highlight_gadget, GTK_STYLE_CLASS_TOP);
981 gtk_css_gadget_add_class (priv->highlight_gadget, GTK_STYLE_CLASS_BOTTOM);
982 }
983 else
984 {
985 gtk_css_gadget_remove_class (priv->highlight_gadget, GTK_STYLE_CLASS_BOTTOM);
986 gtk_css_gadget_add_class (priv->highlight_gadget, GTK_STYLE_CLASS_TOP);
987 }
988 }
989
990 static void
update_fill_position(GtkRange * range)991 update_fill_position (GtkRange *range)
992 {
993 GtkRangePrivate *priv = range->priv;
994
995 if (!priv->fill_gadget)
996 return;
997
998 if (should_invert (range))
999 {
1000 gtk_css_gadget_remove_class (priv->fill_gadget, GTK_STYLE_CLASS_TOP);
1001 gtk_css_gadget_add_class (priv->fill_gadget, GTK_STYLE_CLASS_BOTTOM);
1002 }
1003 else
1004 {
1005 gtk_css_gadget_remove_class (priv->fill_gadget, GTK_STYLE_CLASS_BOTTOM);
1006 gtk_css_gadget_add_class (priv->fill_gadget, GTK_STYLE_CLASS_TOP);
1007 }
1008 }
1009
1010 static void
update_stepper_state(GtkRange * range,GtkCssGadget * gadget)1011 update_stepper_state (GtkRange *range,
1012 GtkCssGadget *gadget)
1013 {
1014 GtkRangePrivate *priv = range->priv;
1015 GtkStateFlags state;
1016 gboolean arrow_sensitive;
1017
1018 state = gtk_widget_get_state_flags (GTK_WIDGET (range));
1019
1020 if ((!priv->inverted &&
1021 (gadget == priv->stepper_a_gadget || gadget == priv->stepper_c_gadget)) ||
1022 (priv->inverted &&
1023 (gadget == priv->stepper_b_gadget || gadget == priv->stepper_d_gadget)))
1024 arrow_sensitive = priv->lower_sensitive;
1025 else
1026 arrow_sensitive = priv->upper_sensitive;
1027
1028 state &= ~(GTK_STATE_FLAG_ACTIVE | GTK_STATE_FLAG_PRELIGHT);
1029
1030 if ((state & GTK_STATE_FLAG_INSENSITIVE) || !arrow_sensitive)
1031 {
1032 state |= GTK_STATE_FLAG_INSENSITIVE;
1033 }
1034 else
1035 {
1036 if (priv->grab_location == gadget)
1037 state |= GTK_STATE_FLAG_ACTIVE;
1038 if (priv->mouse_location == gadget)
1039 state |= GTK_STATE_FLAG_PRELIGHT;
1040 }
1041
1042 gtk_css_gadget_set_state (gadget, state);
1043 }
1044
1045 static void
update_steppers_state(GtkRange * range)1046 update_steppers_state (GtkRange *range)
1047 {
1048 GtkRangePrivate *priv = range->priv;
1049
1050 if (priv->stepper_a_gadget)
1051 update_stepper_state (range, priv->stepper_a_gadget);
1052 if (priv->stepper_b_gadget)
1053 update_stepper_state (range, priv->stepper_b_gadget);
1054 if (priv->stepper_c_gadget)
1055 update_stepper_state (range, priv->stepper_c_gadget);
1056 if (priv->stepper_d_gadget)
1057 update_stepper_state (range, priv->stepper_d_gadget);
1058 }
1059
1060 /**
1061 * gtk_range_set_inverted:
1062 * @range: a #GtkRange
1063 * @setting: %TRUE to invert the range
1064 *
1065 * Ranges normally move from lower to higher values as the
1066 * slider moves from top to bottom or left to right. Inverted
1067 * ranges have higher values at the top or on the right rather than
1068 * on the bottom or left.
1069 **/
1070 void
gtk_range_set_inverted(GtkRange * range,gboolean setting)1071 gtk_range_set_inverted (GtkRange *range,
1072 gboolean setting)
1073 {
1074 GtkRangePrivate *priv;
1075
1076 g_return_if_fail (GTK_IS_RANGE (range));
1077
1078 priv = range->priv;
1079
1080 setting = setting != FALSE;
1081
1082 if (setting != priv->inverted)
1083 {
1084 priv->inverted = setting;
1085
1086 update_steppers_state (range);
1087 update_fill_position (range);
1088 update_highlight_position (range);
1089
1090 gtk_widget_queue_resize (GTK_WIDGET (range));
1091
1092 g_object_notify_by_pspec (G_OBJECT (range), properties[PROP_INVERTED]);
1093 }
1094 }
1095
1096 /**
1097 * gtk_range_get_inverted:
1098 * @range: a #GtkRange
1099 *
1100 * Gets the value set by gtk_range_set_inverted().
1101 *
1102 * Returns: %TRUE if the range is inverted
1103 **/
1104 gboolean
gtk_range_get_inverted(GtkRange * range)1105 gtk_range_get_inverted (GtkRange *range)
1106 {
1107 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1108
1109 return range->priv->inverted;
1110 }
1111
1112 /**
1113 * gtk_range_set_flippable:
1114 * @range: a #GtkRange
1115 * @flippable: %TRUE to make the range flippable
1116 *
1117 * If a range is flippable, it will switch its direction if it is
1118 * horizontal and its direction is %GTK_TEXT_DIR_RTL.
1119 *
1120 * See gtk_widget_get_direction().
1121 *
1122 * Since: 2.18
1123 **/
1124 void
gtk_range_set_flippable(GtkRange * range,gboolean flippable)1125 gtk_range_set_flippable (GtkRange *range,
1126 gboolean flippable)
1127 {
1128 GtkRangePrivate *priv;
1129
1130 g_return_if_fail (GTK_IS_RANGE (range));
1131
1132 priv = range->priv;
1133
1134 flippable = flippable ? TRUE : FALSE;
1135
1136 if (flippable != priv->flippable)
1137 {
1138 priv->flippable = flippable;
1139 update_fill_position (range);
1140 update_highlight_position (range);
1141
1142 gtk_widget_queue_allocate (GTK_WIDGET (range));
1143 }
1144 }
1145
1146 /**
1147 * gtk_range_get_flippable:
1148 * @range: a #GtkRange
1149 *
1150 * Gets the value set by gtk_range_set_flippable().
1151 *
1152 * Returns: %TRUE if the range is flippable
1153 *
1154 * Since: 2.18
1155 **/
1156 gboolean
gtk_range_get_flippable(GtkRange * range)1157 gtk_range_get_flippable (GtkRange *range)
1158 {
1159 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1160
1161 return range->priv->flippable;
1162 }
1163
1164 void
gtk_range_set_slider_use_min_size(GtkRange * range,gboolean use_min_size)1165 gtk_range_set_slider_use_min_size (GtkRange *range,
1166 gboolean use_min_size)
1167 {
1168 GtkRangePrivate *priv = range->priv;
1169
1170 if (use_min_size != priv->slider_use_min_size)
1171 {
1172 priv->slider_use_min_size = use_min_size;
1173 gtk_css_gadget_queue_resize (priv->slider_gadget);
1174 }
1175 }
1176
1177 /**
1178 * gtk_range_set_slider_size_fixed:
1179 * @range: a #GtkRange
1180 * @size_fixed: %TRUE to make the slider size constant
1181 *
1182 * Sets whether the range’s slider has a fixed size, or a size that
1183 * depends on its adjustment’s page size.
1184 *
1185 * This function is useful mainly for #GtkRange subclasses.
1186 *
1187 * Since: 2.20
1188 **/
1189 void
gtk_range_set_slider_size_fixed(GtkRange * range,gboolean size_fixed)1190 gtk_range_set_slider_size_fixed (GtkRange *range,
1191 gboolean size_fixed)
1192 {
1193 GtkRangePrivate *priv;
1194
1195 g_return_if_fail (GTK_IS_RANGE (range));
1196
1197 priv = range->priv;
1198
1199 if (size_fixed != priv->slider_size_fixed)
1200 {
1201 priv->slider_size_fixed = size_fixed ? TRUE : FALSE;
1202
1203 if (priv->adjustment && gtk_widget_get_mapped (GTK_WIDGET (range)))
1204 gtk_css_gadget_queue_allocate (priv->slider_gadget);
1205 }
1206 }
1207
1208 /**
1209 * gtk_range_get_slider_size_fixed:
1210 * @range: a #GtkRange
1211 *
1212 * This function is useful mainly for #GtkRange subclasses.
1213 *
1214 * See gtk_range_set_slider_size_fixed().
1215 *
1216 * Returns: whether the range’s slider has a fixed size.
1217 *
1218 * Since: 2.20
1219 **/
1220 gboolean
gtk_range_get_slider_size_fixed(GtkRange * range)1221 gtk_range_get_slider_size_fixed (GtkRange *range)
1222 {
1223 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1224
1225 return range->priv->slider_size_fixed;
1226 }
1227
1228 /**
1229 * gtk_range_set_min_slider_size:
1230 * @range: a #GtkRange
1231 * @min_size: The slider’s minimum size
1232 *
1233 * Sets the minimum size of the range’s slider.
1234 *
1235 * This function is useful mainly for #GtkRange subclasses.
1236 *
1237 * Since: 2.20
1238 *
1239 * Deprecated: 3.20: Use the min-height/min-width CSS properties on the slider
1240 * node.
1241 **/
1242 void
gtk_range_set_min_slider_size(GtkRange * range,gint min_size)1243 gtk_range_set_min_slider_size (GtkRange *range,
1244 gint min_size)
1245 {
1246 GtkRangePrivate *priv;
1247
1248 g_return_if_fail (GTK_IS_RANGE (range));
1249 g_return_if_fail (min_size > 0);
1250
1251 priv = range->priv;
1252
1253 if (min_size != priv->min_slider_size)
1254 {
1255 priv->min_slider_size = min_size;
1256
1257 gtk_widget_queue_resize (GTK_WIDGET (range));
1258 }
1259 }
1260
1261 /**
1262 * gtk_range_get_min_slider_size:
1263 * @range: a #GtkRange
1264 *
1265 * This function is useful mainly for #GtkRange subclasses.
1266 *
1267 * See gtk_range_set_min_slider_size().
1268 *
1269 * Returns: The minimum size of the range’s slider.
1270 *
1271 * Since: 2.20
1272 *
1273 * Deprecated: 3.20: Use the min-height/min-width CSS properties on the slider
1274 * node.
1275 **/
1276 gint
gtk_range_get_min_slider_size(GtkRange * range)1277 gtk_range_get_min_slider_size (GtkRange *range)
1278 {
1279 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1280
1281 return range->priv->min_slider_size;
1282 }
1283
1284 static void
measure_one_gadget(GtkCssGadget * gadget,int * width_out,int * height_out)1285 measure_one_gadget (GtkCssGadget *gadget,
1286 int *width_out,
1287 int *height_out)
1288 {
1289 gtk_css_gadget_get_preferred_size (gadget,
1290 GTK_ORIENTATION_HORIZONTAL, -1,
1291 width_out, NULL,
1292 NULL, NULL);
1293 gtk_css_gadget_get_preferred_size (gadget,
1294 GTK_ORIENTATION_VERTICAL, -1,
1295 height_out, NULL,
1296 NULL, NULL);
1297 }
1298
1299 /**
1300 * gtk_range_get_range_rect:
1301 * @range: a #GtkRange
1302 * @range_rect: (out): return location for the range rectangle
1303 *
1304 * This function returns the area that contains the range’s trough
1305 * and its steppers, in widget->window coordinates.
1306 *
1307 * This function is useful mainly for #GtkRange subclasses.
1308 *
1309 * Since: 2.20
1310 **/
1311 void
gtk_range_get_range_rect(GtkRange * range,GdkRectangle * range_rect)1312 gtk_range_get_range_rect (GtkRange *range,
1313 GdkRectangle *range_rect)
1314 {
1315 GtkRangePrivate *priv;
1316
1317 g_return_if_fail (GTK_IS_RANGE (range));
1318 g_return_if_fail (range_rect != NULL);
1319
1320 priv = range->priv;
1321
1322 gtk_css_gadget_get_margin_box (priv->contents_gadget, range_rect);
1323 }
1324
1325 /**
1326 * gtk_range_get_slider_range:
1327 * @range: a #GtkRange
1328 * @slider_start: (out) (allow-none): return location for the slider's
1329 * start, or %NULL
1330 * @slider_end: (out) (allow-none): return location for the slider's
1331 * end, or %NULL
1332 *
1333 * This function returns sliders range along the long dimension,
1334 * in widget->window coordinates.
1335 *
1336 * This function is useful mainly for #GtkRange subclasses.
1337 *
1338 * Since: 2.20
1339 **/
1340 void
gtk_range_get_slider_range(GtkRange * range,gint * slider_start,gint * slider_end)1341 gtk_range_get_slider_range (GtkRange *range,
1342 gint *slider_start,
1343 gint *slider_end)
1344 {
1345 GtkRangePrivate *priv;
1346 GtkAllocation slider_alloc;
1347
1348 g_return_if_fail (GTK_IS_RANGE (range));
1349
1350 priv = range->priv;
1351
1352 gtk_css_gadget_get_margin_box (priv->slider_gadget, &slider_alloc);
1353
1354 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
1355 {
1356 if (slider_start)
1357 *slider_start = slider_alloc.y;
1358 if (slider_end)
1359 *slider_end = slider_alloc.y + slider_alloc.height;
1360 }
1361 else
1362 {
1363 if (slider_start)
1364 *slider_start = slider_alloc.x;
1365 if (slider_end)
1366 *slider_end = slider_alloc.x + slider_alloc.width;
1367 }
1368 }
1369
1370 /**
1371 * gtk_range_set_lower_stepper_sensitivity:
1372 * @range: a #GtkRange
1373 * @sensitivity: the lower stepper’s sensitivity policy.
1374 *
1375 * Sets the sensitivity policy for the stepper that points to the
1376 * 'lower' end of the GtkRange’s adjustment.
1377 *
1378 * Since: 2.10
1379 **/
1380 void
gtk_range_set_lower_stepper_sensitivity(GtkRange * range,GtkSensitivityType sensitivity)1381 gtk_range_set_lower_stepper_sensitivity (GtkRange *range,
1382 GtkSensitivityType sensitivity)
1383 {
1384 GtkRangePrivate *priv;
1385
1386 g_return_if_fail (GTK_IS_RANGE (range));
1387
1388 priv = range->priv;
1389
1390 if (priv->lower_sensitivity != sensitivity)
1391 {
1392 priv->lower_sensitivity = sensitivity;
1393
1394 gtk_range_calc_stepper_sensitivity (range);
1395
1396 g_object_notify_by_pspec (G_OBJECT (range), properties[PROP_LOWER_STEPPER_SENSITIVITY]);
1397 }
1398 }
1399
1400 /**
1401 * gtk_range_get_lower_stepper_sensitivity:
1402 * @range: a #GtkRange
1403 *
1404 * Gets the sensitivity policy for the stepper that points to the
1405 * 'lower' end of the GtkRange’s adjustment.
1406 *
1407 * Returns: The lower stepper’s sensitivity policy.
1408 *
1409 * Since: 2.10
1410 **/
1411 GtkSensitivityType
gtk_range_get_lower_stepper_sensitivity(GtkRange * range)1412 gtk_range_get_lower_stepper_sensitivity (GtkRange *range)
1413 {
1414 g_return_val_if_fail (GTK_IS_RANGE (range), GTK_SENSITIVITY_AUTO);
1415
1416 return range->priv->lower_sensitivity;
1417 }
1418
1419 /**
1420 * gtk_range_set_upper_stepper_sensitivity:
1421 * @range: a #GtkRange
1422 * @sensitivity: the upper stepper’s sensitivity policy.
1423 *
1424 * Sets the sensitivity policy for the stepper that points to the
1425 * 'upper' end of the GtkRange’s adjustment.
1426 *
1427 * Since: 2.10
1428 **/
1429 void
gtk_range_set_upper_stepper_sensitivity(GtkRange * range,GtkSensitivityType sensitivity)1430 gtk_range_set_upper_stepper_sensitivity (GtkRange *range,
1431 GtkSensitivityType sensitivity)
1432 {
1433 GtkRangePrivate *priv;
1434
1435 g_return_if_fail (GTK_IS_RANGE (range));
1436
1437 priv = range->priv;
1438
1439 if (priv->upper_sensitivity != sensitivity)
1440 {
1441 priv->upper_sensitivity = sensitivity;
1442
1443 gtk_range_calc_stepper_sensitivity (range);
1444
1445 g_object_notify_by_pspec (G_OBJECT (range), properties[PROP_UPPER_STEPPER_SENSITIVITY]);
1446 }
1447 }
1448
1449 /**
1450 * gtk_range_get_upper_stepper_sensitivity:
1451 * @range: a #GtkRange
1452 *
1453 * Gets the sensitivity policy for the stepper that points to the
1454 * 'upper' end of the GtkRange’s adjustment.
1455 *
1456 * Returns: The upper stepper’s sensitivity policy.
1457 *
1458 * Since: 2.10
1459 **/
1460 GtkSensitivityType
gtk_range_get_upper_stepper_sensitivity(GtkRange * range)1461 gtk_range_get_upper_stepper_sensitivity (GtkRange *range)
1462 {
1463 g_return_val_if_fail (GTK_IS_RANGE (range), GTK_SENSITIVITY_AUTO);
1464
1465 return range->priv->upper_sensitivity;
1466 }
1467
1468 /**
1469 * gtk_range_set_increments:
1470 * @range: a #GtkRange
1471 * @step: step size
1472 * @page: page size
1473 *
1474 * Sets the step and page sizes for the range.
1475 * The step size is used when the user clicks the #GtkScrollbar
1476 * arrows or moves #GtkScale via arrow keys. The page size
1477 * is used for example when moving via Page Up or Page Down keys.
1478 **/
1479 void
gtk_range_set_increments(GtkRange * range,gdouble step,gdouble page)1480 gtk_range_set_increments (GtkRange *range,
1481 gdouble step,
1482 gdouble page)
1483 {
1484 GtkAdjustment *adjustment;
1485
1486 g_return_if_fail (GTK_IS_RANGE (range));
1487
1488 adjustment = range->priv->adjustment;
1489
1490 gtk_adjustment_configure (adjustment,
1491 gtk_adjustment_get_value (adjustment),
1492 gtk_adjustment_get_lower (adjustment),
1493 gtk_adjustment_get_upper (adjustment),
1494 step,
1495 page,
1496 gtk_adjustment_get_page_size (adjustment));
1497 }
1498
1499 /**
1500 * gtk_range_set_range:
1501 * @range: a #GtkRange
1502 * @min: minimum range value
1503 * @max: maximum range value
1504 *
1505 * Sets the allowable values in the #GtkRange, and clamps the range
1506 * value to be between @min and @max. (If the range has a non-zero
1507 * page size, it is clamped between @min and @max - page-size.)
1508 **/
1509 void
gtk_range_set_range(GtkRange * range,gdouble min,gdouble max)1510 gtk_range_set_range (GtkRange *range,
1511 gdouble min,
1512 gdouble max)
1513 {
1514 GtkRangePrivate *priv;
1515 GtkAdjustment *adjustment;
1516 gdouble value;
1517
1518 g_return_if_fail (GTK_IS_RANGE (range));
1519 g_return_if_fail (min <= max);
1520
1521 priv = range->priv;
1522 adjustment = priv->adjustment;
1523
1524 value = gtk_adjustment_get_value (adjustment);
1525 if (priv->restrict_to_fill_level)
1526 value = MIN (value, MAX (gtk_adjustment_get_lower (adjustment),
1527 priv->fill_level));
1528
1529 gtk_adjustment_configure (adjustment,
1530 value,
1531 min,
1532 max,
1533 gtk_adjustment_get_step_increment (adjustment),
1534 gtk_adjustment_get_page_increment (adjustment),
1535 gtk_adjustment_get_page_size (adjustment));
1536 }
1537
1538 /**
1539 * gtk_range_set_value:
1540 * @range: a #GtkRange
1541 * @value: new value of the range
1542 *
1543 * Sets the current value of the range; if the value is outside the
1544 * minimum or maximum range values, it will be clamped to fit inside
1545 * them. The range emits the #GtkRange::value-changed signal if the
1546 * value changes.
1547 **/
1548 void
gtk_range_set_value(GtkRange * range,gdouble value)1549 gtk_range_set_value (GtkRange *range,
1550 gdouble value)
1551 {
1552 GtkRangePrivate *priv;
1553
1554 g_return_if_fail (GTK_IS_RANGE (range));
1555
1556 priv = range->priv;
1557
1558 if (priv->restrict_to_fill_level)
1559 value = MIN (value, MAX (gtk_adjustment_get_lower (priv->adjustment),
1560 priv->fill_level));
1561
1562 gtk_adjustment_set_value (priv->adjustment, value);
1563 }
1564
1565 /**
1566 * gtk_range_get_value:
1567 * @range: a #GtkRange
1568 *
1569 * Gets the current value of the range.
1570 *
1571 * Returns: current value of the range.
1572 **/
1573 gdouble
gtk_range_get_value(GtkRange * range)1574 gtk_range_get_value (GtkRange *range)
1575 {
1576 g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
1577
1578 return gtk_adjustment_get_value (range->priv->adjustment);
1579 }
1580
1581 /**
1582 * gtk_range_set_show_fill_level:
1583 * @range: A #GtkRange
1584 * @show_fill_level: Whether a fill level indicator graphics is shown.
1585 *
1586 * Sets whether a graphical fill level is show on the trough. See
1587 * gtk_range_set_fill_level() for a general description of the fill
1588 * level concept.
1589 *
1590 * Since: 2.12
1591 **/
1592 void
gtk_range_set_show_fill_level(GtkRange * range,gboolean show_fill_level)1593 gtk_range_set_show_fill_level (GtkRange *range,
1594 gboolean show_fill_level)
1595 {
1596 GtkRangePrivate *priv;
1597
1598 g_return_if_fail (GTK_IS_RANGE (range));
1599
1600 priv = range->priv;
1601
1602 show_fill_level = show_fill_level ? TRUE : FALSE;
1603
1604 if (show_fill_level == priv->show_fill_level)
1605 return;
1606
1607 priv->show_fill_level = show_fill_level;
1608
1609 if (show_fill_level)
1610 {
1611 priv->fill_gadget = gtk_css_custom_gadget_new ("fill",
1612 GTK_WIDGET (range),
1613 priv->trough_gadget, NULL,
1614 NULL, NULL, NULL,
1615 NULL, NULL);
1616 gtk_css_gadget_set_state (priv->fill_gadget,
1617 gtk_css_node_get_state (gtk_css_gadget_get_node (priv->trough_gadget)));
1618
1619 update_fill_position (range);
1620 }
1621 else
1622 {
1623 gtk_css_node_set_parent (gtk_css_gadget_get_node (priv->fill_gadget), NULL);
1624 g_clear_object (&priv->fill_gadget);
1625 }
1626
1627 g_object_notify_by_pspec (G_OBJECT (range), properties[PROP_SHOW_FILL_LEVEL]);
1628 gtk_widget_queue_allocate (GTK_WIDGET (range));
1629 }
1630
1631 /**
1632 * gtk_range_get_show_fill_level:
1633 * @range: A #GtkRange
1634 *
1635 * Gets whether the range displays the fill level graphically.
1636 *
1637 * Returns: %TRUE if @range shows the fill level.
1638 *
1639 * Since: 2.12
1640 **/
1641 gboolean
gtk_range_get_show_fill_level(GtkRange * range)1642 gtk_range_get_show_fill_level (GtkRange *range)
1643 {
1644 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1645
1646 return range->priv->show_fill_level;
1647 }
1648
1649 /**
1650 * gtk_range_set_restrict_to_fill_level:
1651 * @range: A #GtkRange
1652 * @restrict_to_fill_level: Whether the fill level restricts slider movement.
1653 *
1654 * Sets whether the slider is restricted to the fill level. See
1655 * gtk_range_set_fill_level() for a general description of the fill
1656 * level concept.
1657 *
1658 * Since: 2.12
1659 **/
1660 void
gtk_range_set_restrict_to_fill_level(GtkRange * range,gboolean restrict_to_fill_level)1661 gtk_range_set_restrict_to_fill_level (GtkRange *range,
1662 gboolean restrict_to_fill_level)
1663 {
1664 GtkRangePrivate *priv;
1665
1666 g_return_if_fail (GTK_IS_RANGE (range));
1667
1668 priv = range->priv;
1669
1670 restrict_to_fill_level = restrict_to_fill_level ? TRUE : FALSE;
1671
1672 if (restrict_to_fill_level != priv->restrict_to_fill_level)
1673 {
1674 priv->restrict_to_fill_level = restrict_to_fill_level;
1675 g_object_notify_by_pspec (G_OBJECT (range), properties[PROP_RESTRICT_TO_FILL_LEVEL]);
1676
1677 gtk_range_set_value (range, gtk_range_get_value (range));
1678 }
1679 }
1680
1681 /**
1682 * gtk_range_get_restrict_to_fill_level:
1683 * @range: A #GtkRange
1684 *
1685 * Gets whether the range is restricted to the fill level.
1686 *
1687 * Returns: %TRUE if @range is restricted to the fill level.
1688 *
1689 * Since: 2.12
1690 **/
1691 gboolean
gtk_range_get_restrict_to_fill_level(GtkRange * range)1692 gtk_range_get_restrict_to_fill_level (GtkRange *range)
1693 {
1694 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1695
1696 return range->priv->restrict_to_fill_level;
1697 }
1698
1699 /**
1700 * gtk_range_set_fill_level:
1701 * @range: a #GtkRange
1702 * @fill_level: the new position of the fill level indicator
1703 *
1704 * Set the new position of the fill level indicator.
1705 *
1706 * The “fill level” is probably best described by its most prominent
1707 * use case, which is an indicator for the amount of pre-buffering in
1708 * a streaming media player. In that use case, the value of the range
1709 * would indicate the current play position, and the fill level would
1710 * be the position up to which the file/stream has been downloaded.
1711 *
1712 * This amount of prebuffering can be displayed on the range’s trough
1713 * and is themeable separately from the trough. To enable fill level
1714 * display, use gtk_range_set_show_fill_level(). The range defaults
1715 * to not showing the fill level.
1716 *
1717 * Additionally, it’s possible to restrict the range’s slider position
1718 * to values which are smaller than the fill level. This is controller
1719 * by gtk_range_set_restrict_to_fill_level() and is by default
1720 * enabled.
1721 *
1722 * Since: 2.12
1723 **/
1724 void
gtk_range_set_fill_level(GtkRange * range,gdouble fill_level)1725 gtk_range_set_fill_level (GtkRange *range,
1726 gdouble fill_level)
1727 {
1728 GtkRangePrivate *priv;
1729
1730 g_return_if_fail (GTK_IS_RANGE (range));
1731
1732 priv = range->priv;
1733
1734 if (fill_level != priv->fill_level)
1735 {
1736 priv->fill_level = fill_level;
1737 g_object_notify_by_pspec (G_OBJECT (range), properties[PROP_FILL_LEVEL]);
1738
1739 if (priv->show_fill_level)
1740 gtk_widget_queue_allocate (GTK_WIDGET (range));
1741
1742 if (priv->restrict_to_fill_level)
1743 gtk_range_set_value (range, gtk_range_get_value (range));
1744 }
1745 }
1746
1747 /**
1748 * gtk_range_get_fill_level:
1749 * @range: A #GtkRange
1750 *
1751 * Gets the current position of the fill level indicator.
1752 *
1753 * Returns: The current fill level
1754 *
1755 * Since: 2.12
1756 **/
1757 gdouble
gtk_range_get_fill_level(GtkRange * range)1758 gtk_range_get_fill_level (GtkRange *range)
1759 {
1760 g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
1761
1762 return range->priv->fill_level;
1763 }
1764
1765 static void
gtk_range_destroy(GtkWidget * widget)1766 gtk_range_destroy (GtkWidget *widget)
1767 {
1768 GtkRange *range = GTK_RANGE (widget);
1769 GtkRangePrivate *priv = range->priv;
1770
1771 gtk_range_remove_step_timer (range);
1772
1773 if (priv->adjustment)
1774 {
1775 g_signal_handlers_disconnect_by_func (priv->adjustment,
1776 gtk_range_adjustment_changed,
1777 range);
1778 g_signal_handlers_disconnect_by_func (priv->adjustment,
1779 gtk_range_adjustment_value_changed,
1780 range);
1781 g_object_unref (priv->adjustment);
1782 priv->adjustment = NULL;
1783 }
1784
1785 if (priv->n_marks)
1786 {
1787 g_free (priv->marks);
1788 priv->marks = NULL;
1789 g_free (priv->mark_pos);
1790 priv->mark_pos = NULL;
1791 priv->n_marks = 0;
1792 }
1793
1794 GTK_WIDGET_CLASS (gtk_range_parent_class)->destroy (widget);
1795 }
1796
1797 static void
gtk_range_finalize(GObject * object)1798 gtk_range_finalize (GObject *object)
1799 {
1800 GtkRange *range = GTK_RANGE (object);
1801 GtkRangePrivate *priv = range->priv;
1802
1803 g_clear_object (&priv->drag_gesture);
1804 g_clear_object (&priv->multipress_gesture);
1805 g_clear_object (&priv->long_press_gesture);
1806
1807 g_clear_object (&priv->gadget);
1808 g_clear_object (&priv->contents_gadget);
1809 g_clear_object (&priv->trough_gadget);
1810 g_clear_object (&priv->fill_gadget);
1811 g_clear_object (&priv->highlight_gadget);
1812 g_clear_object (&priv->slider_gadget);
1813 g_clear_object (&priv->stepper_a_gadget);
1814 g_clear_object (&priv->stepper_b_gadget);
1815 g_clear_object (&priv->stepper_c_gadget);
1816 g_clear_object (&priv->stepper_d_gadget);
1817
1818 G_OBJECT_CLASS (gtk_range_parent_class)->finalize (object);
1819 }
1820
1821 static void
gtk_range_measure_trough(GtkCssGadget * gadget,GtkOrientation orientation,gint for_size,gint * minimum,gint * natural,gint * minimum_baseline,gint * natural_baseline,gpointer user_data)1822 gtk_range_measure_trough (GtkCssGadget *gadget,
1823 GtkOrientation orientation,
1824 gint for_size,
1825 gint *minimum,
1826 gint *natural,
1827 gint *minimum_baseline,
1828 gint *natural_baseline,
1829 gpointer user_data)
1830 {
1831 GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
1832 GtkRange *range = GTK_RANGE (widget);
1833 GtkRangePrivate *priv = range->priv;
1834 gint min, nat;
1835
1836 gtk_css_gadget_get_preferred_size (priv->slider_gadget,
1837 orientation, -1,
1838 minimum, natural,
1839 NULL, NULL);
1840
1841 if (priv->fill_gadget)
1842 {
1843 gtk_css_gadget_get_preferred_size (priv->fill_gadget,
1844 orientation, for_size,
1845 &min, &nat,
1846 NULL, NULL);
1847 *minimum = MAX (*minimum, min);
1848 *natural = MAX (*natural, nat);
1849 }
1850
1851 if (priv->highlight_gadget)
1852 {
1853 gtk_css_gadget_get_preferred_size (priv->highlight_gadget,
1854 orientation, for_size,
1855 &min, &nat,
1856 NULL, NULL);
1857 *minimum = MAX (*minimum, min);
1858 *natural = MAX (*natural, nat);
1859 }
1860 }
1861
1862 static void
gtk_range_measure(GtkCssGadget * gadget,GtkOrientation orientation,gint for_size,gint * minimum,gint * natural,gint * minimum_baseline,gint * natural_baseline,gpointer user_data)1863 gtk_range_measure (GtkCssGadget *gadget,
1864 GtkOrientation orientation,
1865 gint for_size,
1866 gint *minimum,
1867 gint *natural,
1868 gint *minimum_baseline,
1869 gint *natural_baseline,
1870 gpointer user_data)
1871 {
1872 GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
1873 GtkRange *range = GTK_RANGE (widget);
1874 GtkRangePrivate *priv = range->priv;
1875 GtkBorder border = { 0 };
1876
1877 /* Measure the main box */
1878 gtk_css_gadget_get_preferred_size (priv->contents_gadget,
1879 orientation,
1880 -1,
1881 minimum, natural,
1882 NULL, NULL);
1883
1884 if (GTK_RANGE_GET_CLASS (range)->get_range_border)
1885 GTK_RANGE_GET_CLASS (range)->get_range_border (range, &border);
1886
1887 /* Add the border */
1888 if (orientation == GTK_ORIENTATION_HORIZONTAL)
1889 {
1890 *minimum += border.left + border.right;
1891 *natural += border.left + border.right;
1892 }
1893 else
1894 {
1895 *minimum += border.top + border.bottom;
1896 *natural += border.top + border.bottom;
1897 }
1898 }
1899
1900 static void
gtk_range_size_request(GtkWidget * widget,GtkOrientation orientation,gint * minimum,gint * natural)1901 gtk_range_size_request (GtkWidget *widget,
1902 GtkOrientation orientation,
1903 gint *minimum,
1904 gint *natural)
1905 {
1906 GtkRange *range = GTK_RANGE (widget);
1907 GtkRangePrivate *priv = range->priv;
1908
1909 gtk_css_gadget_get_preferred_size (priv->gadget, orientation, -1,
1910 minimum, natural,
1911 NULL, NULL);
1912
1913 if (GTK_RANGE_GET_CLASS (range)->get_range_size_request)
1914 {
1915 gint min, nat;
1916
1917 GTK_RANGE_GET_CLASS (range)->get_range_size_request (range, orientation,
1918 &min, &nat);
1919
1920 *minimum = MAX (*minimum, min);
1921 *natural = MAX (*natural, nat);
1922 }
1923 }
1924
1925 static void
gtk_range_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)1926 gtk_range_get_preferred_width (GtkWidget *widget,
1927 gint *minimum,
1928 gint *natural)
1929 {
1930 gtk_range_size_request (widget, GTK_ORIENTATION_HORIZONTAL,
1931 minimum, natural);
1932 }
1933
1934 static void
gtk_range_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)1935 gtk_range_get_preferred_height (GtkWidget *widget,
1936 gint *minimum,
1937 gint *natural)
1938 {
1939 gtk_range_size_request (widget, GTK_ORIENTATION_VERTICAL,
1940 minimum, natural);
1941 }
1942
1943 static void
gtk_range_allocate_trough(GtkCssGadget * gadget,const GtkAllocation * allocation,int baseline,GtkAllocation * out_clip,gpointer data)1944 gtk_range_allocate_trough (GtkCssGadget *gadget,
1945 const GtkAllocation *allocation,
1946 int baseline,
1947 GtkAllocation *out_clip,
1948 gpointer data)
1949 {
1950 GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
1951 GtkRange *range = GTK_RANGE (widget);
1952 GtkRangePrivate *priv = range->priv;
1953 GtkAllocation slider_alloc, widget_alloc;
1954
1955 /* Slider */
1956 gtk_range_calc_marks (range);
1957 gtk_range_calc_stepper_sensitivity (range);
1958
1959 gtk_widget_get_allocation (widget, &widget_alloc);
1960 gtk_range_compute_slider_position (range,
1961 gtk_adjustment_get_value (priv->adjustment),
1962 &slider_alloc);
1963 slider_alloc.x += widget_alloc.x;
1964 slider_alloc.y += widget_alloc.y;
1965
1966 gtk_css_gadget_allocate (priv->slider_gadget,
1967 &slider_alloc,
1968 gtk_widget_get_allocated_baseline (widget),
1969 out_clip);
1970
1971 if (priv->show_fill_level &&
1972 gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_page_size (priv->adjustment) -
1973 gtk_adjustment_get_lower (priv->adjustment) != 0)
1974 {
1975 gdouble level, fill;
1976 GtkAllocation fill_alloc, fill_clip;
1977
1978 fill_alloc = *allocation;
1979
1980 level = CLAMP (priv->fill_level,
1981 gtk_adjustment_get_lower (priv->adjustment),
1982 gtk_adjustment_get_upper (priv->adjustment) -
1983 gtk_adjustment_get_page_size (priv->adjustment));
1984
1985 fill = (level - gtk_adjustment_get_lower (priv->adjustment)) /
1986 (gtk_adjustment_get_upper (priv->adjustment) -
1987 gtk_adjustment_get_lower (priv->adjustment) -
1988 gtk_adjustment_get_page_size (priv->adjustment));
1989
1990 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1991 {
1992 fill_alloc.width *= fill;
1993
1994 if (should_invert (range))
1995 fill_alloc.x += allocation->width - fill_alloc.width;
1996 }
1997 else
1998 {
1999 fill_alloc.height *= fill;
2000
2001 if (should_invert (range))
2002 fill_alloc.y += allocation->height - fill_alloc.height;
2003 }
2004
2005 gtk_css_gadget_allocate (priv->fill_gadget,
2006 &fill_alloc,
2007 baseline,
2008 &fill_clip);
2009 gdk_rectangle_union (out_clip, &fill_clip, out_clip);
2010 }
2011
2012 if (priv->has_origin)
2013 {
2014 GtkAllocation highlight_alloc, highlight_clip;
2015 int min, nat;
2016
2017 gtk_css_gadget_get_preferred_size (priv->highlight_gadget,
2018 priv->orientation, -1,
2019 &min, &nat,
2020 NULL, NULL);
2021
2022 highlight_alloc = *allocation;
2023
2024 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2025 {
2026 int x = slider_alloc.x + slider_alloc.width / 2;
2027
2028 if (!should_invert (range))
2029 {
2030 highlight_alloc.x = allocation->x;
2031 highlight_alloc.width = MAX (x - allocation->x, min);
2032 }
2033 else
2034 {
2035 highlight_alloc.width = MAX (allocation->x + allocation->width - x, min);
2036 highlight_alloc.x = allocation->x + allocation->width - highlight_alloc.width;
2037 }
2038 }
2039 else
2040 {
2041 int y = slider_alloc.y + slider_alloc.height / 2;
2042
2043 if (!should_invert (range))
2044 {
2045 highlight_alloc.y = allocation->y;
2046 highlight_alloc.height = MAX (y - allocation->y, min);
2047 }
2048 else
2049 {
2050 highlight_alloc.height = MAX (allocation->y + allocation->height - y, min);
2051 highlight_alloc.y = allocation->y + allocation->height - highlight_alloc.height;
2052 }
2053 }
2054
2055 gtk_css_gadget_allocate (priv->highlight_gadget,
2056 &highlight_alloc,
2057 baseline,
2058 &highlight_clip);
2059 gdk_rectangle_union (out_clip, &highlight_clip, out_clip);
2060 }
2061 }
2062
2063 /* Clamp dimensions and border inside allocation, such that we prefer
2064 * to take space from border not dimensions in all directions, and prefer to
2065 * give space to border over dimensions in one direction.
2066 */
2067 static void
clamp_dimensions(const GtkAllocation * allocation,int * width,int * height,GtkBorder * border,gboolean border_expands_horizontally)2068 clamp_dimensions (const GtkAllocation *allocation,
2069 int *width,
2070 int *height,
2071 GtkBorder *border,
2072 gboolean border_expands_horizontally)
2073 {
2074 gint extra, shortage;
2075
2076 /* Width */
2077 extra = allocation->width - border->left - border->right - *width;
2078 if (extra > 0)
2079 {
2080 if (border_expands_horizontally)
2081 {
2082 border->left += extra / 2;
2083 border->right += extra / 2 + extra % 2;
2084 }
2085 else
2086 {
2087 *width += extra;
2088 }
2089 }
2090
2091 /* See if we can fit rect, if not kill the border */
2092 shortage = *width - allocation->width;
2093 if (shortage > 0)
2094 {
2095 *width = allocation->width;
2096 /* lose the border */
2097 border->left = 0;
2098 border->right = 0;
2099 }
2100 else
2101 {
2102 /* See if we can fit rect with borders */
2103 shortage = *width + border->left + border->right - allocation->width;
2104 if (shortage > 0)
2105 {
2106 /* Shrink borders */
2107 border->left -= shortage / 2;
2108 border->right -= shortage / 2 + shortage % 2;
2109 }
2110 }
2111
2112 /* Height */
2113 extra = allocation->height - border->top - border->bottom - *height;
2114 if (extra > 0)
2115 {
2116 if (border_expands_horizontally)
2117 {
2118 /* don't expand border vertically */
2119 *height += extra;
2120 }
2121 else
2122 {
2123 border->top += extra / 2;
2124 border->bottom += extra / 2 + extra % 2;
2125 }
2126 }
2127
2128 /* See if we can fit rect, if not kill the border */
2129 shortage = *height - allocation->height;
2130 if (shortage > 0)
2131 {
2132 *height = allocation->height;
2133 /* lose the border */
2134 border->top = 0;
2135 border->bottom = 0;
2136 }
2137 else
2138 {
2139 /* See if we can fit rect with borders */
2140 shortage = *height + border->top + border->bottom - allocation->height;
2141 if (shortage > 0)
2142 {
2143 /* Shrink borders */
2144 border->top -= shortage / 2;
2145 border->bottom -= shortage / 2 + shortage % 2;
2146 }
2147 }
2148 }
2149
2150 static void
gtk_range_allocate(GtkCssGadget * gadget,const GtkAllocation * allocation,int baseline,GtkAllocation * out_clip,gpointer data)2151 gtk_range_allocate (GtkCssGadget *gadget,
2152 const GtkAllocation *allocation,
2153 int baseline,
2154 GtkAllocation *out_clip,
2155 gpointer data)
2156 {
2157 GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
2158 GtkRange *range = GTK_RANGE (widget);
2159 GtkRangePrivate *priv = range->priv;
2160 GtkBorder border = { 0 };
2161 GtkAllocation box_alloc;
2162 int box_min_width, box_min_height;
2163
2164 if (GTK_RANGE_GET_CLASS (range)->get_range_border)
2165 GTK_RANGE_GET_CLASS (range)->get_range_border (range, &border);
2166
2167 measure_one_gadget (priv->contents_gadget, &box_min_width, &box_min_height);
2168
2169 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2170 clamp_dimensions (allocation, &box_min_width, &box_min_height, &border, TRUE);
2171 else
2172 clamp_dimensions (allocation, &box_min_width, &box_min_height, &border, FALSE);
2173
2174 box_alloc.x = border.left + allocation->x;
2175 box_alloc.y = border.top + allocation->y;
2176 box_alloc.width = box_min_width;
2177 box_alloc.height = box_min_height;
2178
2179 gtk_css_gadget_allocate (priv->contents_gadget,
2180 &box_alloc,
2181 baseline,
2182 out_clip);
2183
2184 /* TODO: we should compute a proper clip from get_range_border(),
2185 * but this will at least give us outset shadows.
2186 */
2187 gdk_rectangle_union (out_clip, allocation, out_clip);
2188 }
2189
2190 static void
gtk_range_size_allocate(GtkWidget * widget,GtkAllocation * allocation)2191 gtk_range_size_allocate (GtkWidget *widget,
2192 GtkAllocation *allocation)
2193 {
2194 GtkRange *range = GTK_RANGE (widget);
2195 GtkRangePrivate *priv = range->priv;
2196 GtkAllocation clip;
2197
2198 gtk_widget_set_allocation (widget, allocation);
2199
2200 if (gtk_widget_get_realized (widget))
2201 gdk_window_move_resize (priv->event_window,
2202 allocation->x, allocation->y,
2203 allocation->width, allocation->height);
2204
2205 gtk_css_gadget_allocate (priv->gadget,
2206 allocation,
2207 gtk_widget_get_allocated_baseline (widget),
2208 &clip);
2209 gtk_widget_set_clip (widget, &clip);
2210 }
2211
2212 static void
gtk_range_realize(GtkWidget * widget)2213 gtk_range_realize (GtkWidget *widget)
2214 {
2215 GtkAllocation allocation;
2216 GtkRange *range = GTK_RANGE (widget);
2217 GtkRangePrivate *priv = range->priv;
2218 GdkWindow *window;
2219 GdkWindowAttr attributes;
2220 gint attributes_mask;
2221
2222 gtk_widget_set_realized (widget, TRUE);
2223
2224 window = gtk_widget_get_parent_window (widget);
2225 gtk_widget_set_window (widget, window);
2226 g_object_ref (window);
2227
2228 gtk_widget_get_allocation (widget, &allocation);
2229
2230 attributes.window_type = GDK_WINDOW_CHILD;
2231 attributes.x = allocation.x;
2232 attributes.y = allocation.y;
2233 attributes.width = allocation.width;
2234 attributes.height = allocation.height;
2235 attributes.wclass = GDK_INPUT_ONLY;
2236 attributes.event_mask = gtk_widget_get_events (widget);
2237 attributes.event_mask |= GDK_BUTTON_PRESS_MASK |
2238 GDK_BUTTON_RELEASE_MASK |
2239 GDK_SCROLL_MASK |
2240 GDK_SMOOTH_SCROLL_MASK |
2241 GDK_ENTER_NOTIFY_MASK |
2242 GDK_LEAVE_NOTIFY_MASK |
2243 GDK_POINTER_MOTION_MASK;
2244
2245 attributes_mask = GDK_WA_X | GDK_WA_Y;
2246
2247 priv->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
2248 &attributes, attributes_mask);
2249 gtk_widget_register_window (widget, priv->event_window);
2250 }
2251
2252 static void
gtk_range_unrealize(GtkWidget * widget)2253 gtk_range_unrealize (GtkWidget *widget)
2254 {
2255 GtkRange *range = GTK_RANGE (widget);
2256 GtkRangePrivate *priv = range->priv;
2257
2258 gtk_range_remove_step_timer (range);
2259
2260 gtk_widget_unregister_window (widget, priv->event_window);
2261 gdk_window_destroy (priv->event_window);
2262 priv->event_window = NULL;
2263
2264 GTK_WIDGET_CLASS (gtk_range_parent_class)->unrealize (widget);
2265 }
2266
2267 static void
gtk_range_map(GtkWidget * widget)2268 gtk_range_map (GtkWidget *widget)
2269 {
2270 GtkRange *range = GTK_RANGE (widget);
2271 GtkRangePrivate *priv = range->priv;
2272
2273 gdk_window_show (priv->event_window);
2274
2275 GTK_WIDGET_CLASS (gtk_range_parent_class)->map (widget);
2276 }
2277
2278 static void
gtk_range_unmap(GtkWidget * widget)2279 gtk_range_unmap (GtkWidget *widget)
2280 {
2281 GtkRange *range = GTK_RANGE (widget);
2282 GtkRangePrivate *priv = range->priv;
2283
2284 stop_scrolling (range);
2285
2286 gdk_window_hide (priv->event_window);
2287
2288 GTK_WIDGET_CLASS (gtk_range_parent_class)->unmap (widget);
2289 }
2290
2291 static void
update_slider_state(GtkRange * range)2292 update_slider_state (GtkRange *range)
2293 {
2294 GtkRangePrivate *priv = range->priv;
2295 GtkStateFlags state;
2296
2297 state = gtk_widget_get_state_flags (GTK_WIDGET (range));
2298
2299 state &= ~(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE);
2300
2301 if (priv->mouse_location == priv->slider_gadget &&
2302 !(state & GTK_STATE_FLAG_INSENSITIVE))
2303 state |= GTK_STATE_FLAG_PRELIGHT;
2304
2305 if (priv->grab_location == priv->slider_gadget)
2306 state |= GTK_STATE_FLAG_ACTIVE;
2307
2308 gtk_css_gadget_set_state (priv->slider_gadget, state);
2309 }
2310
2311 static void
update_trough_state(GtkRange * range)2312 update_trough_state (GtkRange *range)
2313 {
2314 GtkRangePrivate *priv = range->priv;
2315 GtkStateFlags state;
2316
2317 state = gtk_widget_get_state_flags (GTK_WIDGET (range));
2318
2319 state &= ~(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE);
2320
2321 gtk_css_gadget_set_state (priv->contents_gadget, state);
2322
2323 if (priv->mouse_location == priv->trough_gadget &&
2324 !(state & GTK_STATE_FLAG_INSENSITIVE))
2325 state |= GTK_STATE_FLAG_PRELIGHT;
2326
2327 if (priv->grab_location == priv->trough_gadget)
2328 state |= GTK_STATE_FLAG_ACTIVE;
2329
2330 gtk_css_gadget_set_state (priv->trough_gadget, state);
2331 if (priv->highlight_gadget)
2332 gtk_css_gadget_set_state (priv->highlight_gadget, state);
2333 if (priv->fill_gadget)
2334 gtk_css_gadget_set_state (priv->fill_gadget, state);
2335 }
2336
2337 static void
gtk_range_direction_changed(GtkWidget * widget,GtkTextDirection previous_direction)2338 gtk_range_direction_changed (GtkWidget *widget,
2339 GtkTextDirection previous_direction)
2340 {
2341 GtkRange *range = GTK_RANGE (widget);
2342
2343 update_fill_position (range);
2344 update_highlight_position (range);
2345
2346 GTK_WIDGET_CLASS (gtk_range_parent_class)->direction_changed (widget, previous_direction);
2347 }
2348
2349 static void
gtk_range_state_flags_changed(GtkWidget * widget,GtkStateFlags previous_state)2350 gtk_range_state_flags_changed (GtkWidget *widget,
2351 GtkStateFlags previous_state)
2352 {
2353 GtkRange *range = GTK_RANGE (widget);
2354
2355 update_trough_state (range);
2356 update_slider_state (range);
2357 update_steppers_state (range);
2358
2359 GTK_WIDGET_CLASS (gtk_range_parent_class)->state_flags_changed (widget, previous_state);
2360 }
2361
2362 static gboolean
gtk_range_render_trough(GtkCssGadget * gadget,cairo_t * cr,int x,int y,int width,int height,gpointer user_data)2363 gtk_range_render_trough (GtkCssGadget *gadget,
2364 cairo_t *cr,
2365 int x,
2366 int y,
2367 int width,
2368 int height,
2369 gpointer user_data)
2370 {
2371 GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
2372 GtkRange *range = GTK_RANGE (widget);
2373 GtkRangePrivate *priv = range->priv;
2374
2375 /* HACK: GtkColorScale wants to draw its own trough
2376 * so we let it...
2377 */
2378 if (GTK_IS_COLOR_SCALE (widget))
2379 gtk_color_scale_draw_trough (GTK_COLOR_SCALE (widget), cr, x, y, width, height);
2380
2381 if (priv->show_fill_level &&
2382 gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_page_size (priv->adjustment) -
2383 gtk_adjustment_get_lower (priv->adjustment) != 0)
2384 gtk_css_gadget_draw (priv->fill_gadget, cr);
2385
2386 if (priv->has_origin)
2387 gtk_css_gadget_draw (priv->highlight_gadget, cr);
2388
2389 return gtk_widget_has_visible_focus (widget);
2390 }
2391
2392 static gboolean
gtk_range_render(GtkCssGadget * gadget,cairo_t * cr,int x,int y,int width,int height,gpointer user_data)2393 gtk_range_render (GtkCssGadget *gadget,
2394 cairo_t *cr,
2395 int x,
2396 int y,
2397 int width,
2398 int height,
2399 gpointer user_data)
2400 {
2401 GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
2402 GtkRange *range = GTK_RANGE (widget);
2403 GtkRangePrivate *priv = range->priv;
2404
2405 gtk_css_gadget_draw (priv->contents_gadget, cr);
2406
2407 /* Draw the slider last, so that e.g. the focus ring stays below it */
2408 gtk_css_gadget_draw (priv->slider_gadget, cr);
2409
2410 return FALSE;
2411 }
2412
2413 static gboolean
gtk_range_draw(GtkWidget * widget,cairo_t * cr)2414 gtk_range_draw (GtkWidget *widget,
2415 cairo_t *cr)
2416 {
2417 GtkRange *range = GTK_RANGE (widget);
2418 GtkRangePrivate *priv = range->priv;
2419
2420 gtk_css_gadget_draw (priv->gadget, cr);
2421
2422 return GDK_EVENT_PROPAGATE;
2423 }
2424
2425 static void
range_grab_add(GtkRange * range,GtkCssGadget * location)2426 range_grab_add (GtkRange *range,
2427 GtkCssGadget *location)
2428 {
2429 GtkRangePrivate *priv = range->priv;
2430 GtkStyleContext *context;
2431
2432 context = gtk_widget_get_style_context (GTK_WIDGET (range));
2433
2434 /* Don't perform any GDK/GTK+ grab here. Since a button
2435 * is down, there's an ongoing implicit grab on
2436 * priv->event_window, which pretty much guarantees this
2437 * is the only widget receiving the pointer events.
2438 */
2439 priv->grab_location = location;
2440 gtk_css_gadget_queue_allocate (location);
2441
2442 update_trough_state (range);
2443 update_slider_state (range);
2444 update_steppers_state (range);
2445
2446 gtk_style_context_add_class (context, "dragging");
2447
2448 gtk_grab_add (GTK_WIDGET (range));
2449 }
2450
2451 static void
update_zoom_state(GtkRange * range,gboolean enabled)2452 update_zoom_state (GtkRange *range,
2453 gboolean enabled)
2454 {
2455 GtkStyleContext *context;
2456
2457 context = gtk_widget_get_style_context (GTK_WIDGET (range));
2458
2459 if (enabled)
2460 gtk_style_context_add_class (context, "fine-tune");
2461 else
2462 gtk_style_context_remove_class (context, "fine-tune");
2463
2464 range->priv->zoom = enabled;
2465 }
2466
2467 static void
range_grab_remove(GtkRange * range)2468 range_grab_remove (GtkRange *range)
2469 {
2470 GtkRangePrivate *priv = range->priv;
2471 GtkStyleContext *context;
2472
2473 if (!priv->grab_location)
2474 return;
2475
2476 gtk_grab_remove (GTK_WIDGET (range));
2477 context = gtk_widget_get_style_context (GTK_WIDGET (range));
2478
2479 gtk_css_gadget_queue_allocate (priv->grab_location);
2480 priv->grab_location = NULL;
2481
2482 gtk_range_update_mouse_location (range);
2483
2484 update_slider_state (range);
2485 update_steppers_state (range);
2486 update_zoom_state (range, FALSE);
2487
2488 gtk_style_context_remove_class (context, "dragging");
2489 }
2490
2491 static GtkScrollType
range_get_scroll_for_grab(GtkRange * range)2492 range_get_scroll_for_grab (GtkRange *range)
2493 {
2494 GtkRangePrivate *priv = range->priv;
2495 guint grab_button;
2496 gboolean invert;
2497
2498 invert = should_invert (range);
2499 grab_button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (range->priv->multipress_gesture));
2500
2501 if (!priv->grab_location)
2502 return GTK_SCROLL_NONE;
2503
2504 /* Backward stepper */
2505 if (priv->grab_location == priv->stepper_a_gadget ||
2506 priv->grab_location == priv->stepper_c_gadget)
2507 {
2508 switch (grab_button)
2509 {
2510 case GDK_BUTTON_PRIMARY:
2511 return invert ? GTK_SCROLL_STEP_FORWARD : GTK_SCROLL_STEP_BACKWARD;
2512 break;
2513 case GDK_BUTTON_SECONDARY:
2514 return invert ? GTK_SCROLL_PAGE_FORWARD : GTK_SCROLL_PAGE_BACKWARD;
2515 break;
2516 case GDK_BUTTON_MIDDLE:
2517 return invert ? GTK_SCROLL_END : GTK_SCROLL_START;
2518 break;
2519 default:
2520 return GTK_SCROLL_NONE;
2521 }
2522 }
2523
2524 /* Forward stepper */
2525 if (priv->grab_location == priv->stepper_b_gadget ||
2526 priv->grab_location == priv->stepper_d_gadget)
2527 {
2528 switch (grab_button)
2529 {
2530 case GDK_BUTTON_PRIMARY:
2531 return invert ? GTK_SCROLL_STEP_BACKWARD : GTK_SCROLL_STEP_FORWARD;
2532 break;
2533 case GDK_BUTTON_SECONDARY:
2534 return invert ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_PAGE_FORWARD;
2535 break;
2536 case GDK_BUTTON_MIDDLE:
2537 return invert ? GTK_SCROLL_START : GTK_SCROLL_END;
2538 break;
2539 default:
2540 return GTK_SCROLL_NONE;
2541 }
2542 }
2543
2544 /* In the trough */
2545 if (priv->grab_location == priv->trough_gadget)
2546 {
2547 if (priv->trough_click_forward)
2548 return GTK_SCROLL_PAGE_FORWARD;
2549 else
2550 return GTK_SCROLL_PAGE_BACKWARD;
2551 }
2552
2553 return GTK_SCROLL_NONE;
2554 }
2555
2556 static gdouble
coord_to_value(GtkRange * range,gdouble coord)2557 coord_to_value (GtkRange *range,
2558 gdouble coord)
2559 {
2560 GtkRangePrivate *priv = range->priv;
2561 gdouble frac;
2562 gdouble value;
2563 gint trough_length;
2564 gint trough_start;
2565 gint slider_length;
2566 GtkAllocation slider_alloc, trough_alloc;
2567
2568 gtk_css_gadget_get_margin_box (priv->slider_gadget, &slider_alloc);
2569 gtk_css_gadget_get_content_box (priv->trough_gadget, &trough_alloc);
2570
2571 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2572 {
2573 trough_length = trough_alloc.height;
2574 trough_start = trough_alloc.y;
2575 slider_length = slider_alloc.height;
2576 }
2577 else
2578 {
2579 trough_length = trough_alloc.width;
2580 trough_start = trough_alloc.x;
2581 slider_length = slider_alloc.width;
2582 }
2583
2584 if (trough_length == slider_length)
2585 frac = 1.0;
2586 else
2587 frac = (MAX (0, coord - trough_start) /
2588 (gdouble) (trough_length - slider_length));
2589
2590 if (should_invert (range))
2591 frac = 1.0 - frac;
2592
2593 value = gtk_adjustment_get_lower (priv->adjustment) + frac * (gtk_adjustment_get_upper (priv->adjustment) -
2594 gtk_adjustment_get_lower (priv->adjustment) -
2595 gtk_adjustment_get_page_size (priv->adjustment));
2596 return value;
2597 }
2598
2599 static gboolean
gtk_range_key_press(GtkWidget * widget,GdkEventKey * event)2600 gtk_range_key_press (GtkWidget *widget,
2601 GdkEventKey *event)
2602 {
2603 GdkDevice *device;
2604 GtkRange *range = GTK_RANGE (widget);
2605 GtkRangePrivate *priv = range->priv;
2606
2607 device = gdk_event_get_device ((GdkEvent *) event);
2608 device = gdk_device_get_associated_device (device);
2609
2610 if (gtk_gesture_is_active (priv->drag_gesture) &&
2611 device == gtk_gesture_get_device (priv->drag_gesture) &&
2612 event->keyval == GDK_KEY_Escape &&
2613 priv->grab_location != NULL)
2614 {
2615 stop_scrolling (range);
2616
2617 return GDK_EVENT_STOP;
2618 }
2619 else if (priv->in_drag &&
2620 (event->keyval == GDK_KEY_Shift_L ||
2621 event->keyval == GDK_KEY_Shift_R))
2622 {
2623 GtkAllocation slider_alloc;
2624
2625 gtk_css_gadget_get_margin_box (priv->slider_gadget, &slider_alloc);
2626
2627 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2628 priv->slide_initial_slider_position = slider_alloc.y;
2629 else
2630 priv->slide_initial_slider_position = slider_alloc.x;
2631 update_zoom_state (range, !priv->zoom);
2632
2633 return GDK_EVENT_STOP;
2634 }
2635
2636 return GTK_WIDGET_CLASS (gtk_range_parent_class)->key_press_event (widget, event);
2637 }
2638
2639 static void
update_initial_slider_position(GtkRange * range,gdouble x,gdouble y,GtkAllocation * slider_alloc)2640 update_initial_slider_position (GtkRange *range,
2641 gdouble x,
2642 gdouble y,
2643 GtkAllocation *slider_alloc)
2644 {
2645 GtkRangePrivate *priv = range->priv;
2646
2647 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2648 {
2649 priv->slide_initial_slider_position = MAX (0, slider_alloc->y);
2650 priv->slide_initial_coordinate_delta = y - priv->slide_initial_slider_position;
2651 }
2652 else
2653 {
2654 priv->slide_initial_slider_position = MAX (0, slider_alloc->x);
2655 priv->slide_initial_coordinate_delta = x - priv->slide_initial_slider_position;
2656 }
2657 }
2658
2659 static void
gtk_range_long_press_gesture_pressed(GtkGestureLongPress * gesture,gdouble x,gdouble y,GtkRange * range)2660 gtk_range_long_press_gesture_pressed (GtkGestureLongPress *gesture,
2661 gdouble x,
2662 gdouble y,
2663 GtkRange *range)
2664 {
2665 GtkRangePrivate *priv = range->priv;
2666
2667 gtk_range_update_mouse_location (range);
2668
2669 if (priv->mouse_location == priv->slider_gadget && !priv->zoom)
2670 {
2671 GtkAllocation slider_alloc;
2672
2673 gtk_css_gadget_get_margin_box (priv->slider_gadget, &slider_alloc);
2674 update_initial_slider_position (range, x, y, &slider_alloc);
2675 update_zoom_state (range, TRUE);
2676 }
2677 }
2678
2679 static void
gtk_range_multipress_gesture_pressed(GtkGestureMultiPress * gesture,guint n_press,gdouble x,gdouble y,GtkRange * range)2680 gtk_range_multipress_gesture_pressed (GtkGestureMultiPress *gesture,
2681 guint n_press,
2682 gdouble x,
2683 gdouble y,
2684 GtkRange *range)
2685 {
2686 GtkWidget *widget = GTK_WIDGET (range);
2687 GtkRangePrivate *priv = range->priv;
2688 GdkDevice *source_device;
2689 GdkEventSequence *sequence;
2690 const GdkEvent *event;
2691 GdkInputSource source;
2692 gboolean primary_warps;
2693 gboolean shift_pressed;
2694 guint button;
2695 GdkModifierType state_mask;
2696 GtkAllocation slider_alloc;
2697
2698 if (!gtk_widget_has_focus (widget))
2699 gtk_widget_grab_focus (widget);
2700
2701 sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
2702 button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
2703 event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
2704 gdk_event_get_state (event, &state_mask);
2705 shift_pressed = (state_mask & GDK_SHIFT_MASK) != 0;
2706
2707 source_device = gdk_event_get_source_device ((GdkEvent *) event);
2708 source = gdk_device_get_source (source_device);
2709
2710 priv->mouse_x = x;
2711 priv->mouse_y = y;
2712
2713 gtk_range_update_mouse_location (range);
2714 gtk_css_gadget_get_margin_box (priv->slider_gadget, &slider_alloc);
2715
2716 g_object_get (gtk_widget_get_settings (widget),
2717 "gtk-primary-button-warps-slider", &primary_warps,
2718 NULL);
2719
2720 if (priv->mouse_location == priv->slider_gadget &&
2721 gdk_event_triggers_context_menu (event))
2722 {
2723 gboolean handled;
2724
2725 gtk_gesture_set_state (priv->multipress_gesture, GTK_EVENT_SEQUENCE_CLAIMED);
2726 g_signal_emit_by_name (widget, "popup-menu", &handled);
2727 return;
2728 }
2729
2730 if (priv->mouse_location == priv->slider_gadget)
2731 {
2732 /* Shift-click in the slider = fine adjustment */
2733 if (shift_pressed)
2734 update_zoom_state (range, TRUE);
2735
2736 update_initial_slider_position (range, x, y, &slider_alloc);
2737 range_grab_add (range, priv->slider_gadget);
2738
2739 gtk_widget_queue_draw (widget);
2740 }
2741 else if (priv->mouse_location == priv->stepper_a_gadget ||
2742 priv->mouse_location == priv->stepper_b_gadget ||
2743 priv->mouse_location == priv->stepper_c_gadget ||
2744 priv->mouse_location == priv->stepper_d_gadget)
2745 {
2746 GtkScrollType scroll;
2747
2748 range_grab_add (range, priv->mouse_location);
2749
2750 scroll = range_get_scroll_for_grab (range);
2751 if (scroll == GTK_SCROLL_START || scroll == GTK_SCROLL_END)
2752 gtk_range_scroll (range, scroll);
2753 else if (scroll != GTK_SCROLL_NONE)
2754 {
2755 remove_autoscroll (range);
2756 range->priv->autoscroll_mode = scroll;
2757 add_autoscroll (range);
2758 }
2759 }
2760 else if (priv->mouse_location == priv->trough_gadget &&
2761 (source == GDK_SOURCE_TOUCHSCREEN ||
2762 (primary_warps && !shift_pressed && button == GDK_BUTTON_PRIMARY) ||
2763 (!primary_warps && shift_pressed && button == GDK_BUTTON_PRIMARY) ||
2764 (!primary_warps && button == GDK_BUTTON_MIDDLE)))
2765 {
2766 /* warp to location */
2767 GdkRectangle slider;
2768 gdouble slider_low_value, slider_high_value, new_value;
2769
2770 slider_high_value =
2771 coord_to_value (range,
2772 priv->orientation == GTK_ORIENTATION_VERTICAL ?
2773 y : x);
2774 slider_low_value =
2775 coord_to_value (range,
2776 priv->orientation == GTK_ORIENTATION_VERTICAL ?
2777 y - slider_alloc.height :
2778 x - slider_alloc.width);
2779
2780 /* compute new value for warped slider */
2781 new_value = (slider_low_value + slider_high_value) / 2;
2782
2783 gtk_range_compute_slider_position (range, new_value, &slider);
2784 update_initial_slider_position (range, x, y, &slider);
2785
2786 range_grab_add (range, priv->slider_gadget);
2787
2788 gtk_widget_queue_draw (widget);
2789
2790 update_slider_position (range, x, y);
2791 }
2792 else if (priv->mouse_location == priv->trough_gadget &&
2793 ((primary_warps && shift_pressed && button == GDK_BUTTON_PRIMARY) ||
2794 (!primary_warps && !shift_pressed && button == GDK_BUTTON_PRIMARY) ||
2795 (primary_warps && button == GDK_BUTTON_MIDDLE)))
2796 {
2797 /* jump by pages */
2798 GtkScrollType scroll;
2799 gdouble click_value;
2800
2801 click_value = coord_to_value (range,
2802 priv->orientation == GTK_ORIENTATION_VERTICAL ?
2803 y : x);
2804
2805 priv->trough_click_forward = click_value > gtk_adjustment_get_value (priv->adjustment);
2806 range_grab_add (range, priv->trough_gadget);
2807
2808 scroll = range_get_scroll_for_grab (range);
2809 gtk_range_add_step_timer (range, scroll);
2810 }
2811 else if (priv->mouse_location == priv->trough_gadget &&
2812 button == GDK_BUTTON_SECONDARY)
2813 {
2814 /* autoscroll */
2815 gdouble click_value;
2816
2817 click_value = coord_to_value (range,
2818 priv->orientation == GTK_ORIENTATION_VERTICAL ?
2819 y : x);
2820
2821 priv->trough_click_forward = click_value > gtk_adjustment_get_value (priv->adjustment);
2822 range_grab_add (range, priv->trough_gadget);
2823
2824 remove_autoscroll (range);
2825 range->priv->autoscroll_mode = priv->trough_click_forward ? GTK_SCROLL_END : GTK_SCROLL_START;
2826 add_autoscroll (range);
2827 }
2828
2829 if (priv->grab_location == priv->slider_gadget);
2830 /* leave it to ::drag-begin to claim the sequence */
2831 else if (priv->grab_location != NULL)
2832 gtk_gesture_set_state (priv->multipress_gesture, GTK_EVENT_SEQUENCE_CLAIMED);
2833 }
2834
2835 static void
gtk_range_multipress_gesture_released(GtkGestureMultiPress * gesture,guint n_press,gdouble x,gdouble y,GtkRange * range)2836 gtk_range_multipress_gesture_released (GtkGestureMultiPress *gesture,
2837 guint n_press,
2838 gdouble x,
2839 gdouble y,
2840 GtkRange *range)
2841 {
2842 GtkRangePrivate *priv = range->priv;
2843
2844 priv->mouse_x = x;
2845 priv->mouse_y = y;
2846 range->priv->in_drag = FALSE;
2847 stop_scrolling (range);
2848 }
2849
2850 /* During a slide, move the slider as required given new mouse position */
2851 static void
update_slider_position(GtkRange * range,gint mouse_x,gint mouse_y)2852 update_slider_position (GtkRange *range,
2853 gint mouse_x,
2854 gint mouse_y)
2855 {
2856 GtkRangePrivate *priv = range->priv;
2857 gdouble delta;
2858 gdouble c;
2859 gdouble new_value;
2860 gboolean handled;
2861 gdouble next_value;
2862 gdouble mark_value;
2863 gdouble mark_delta;
2864 gdouble zoom;
2865 gint i;
2866
2867 if (priv->zoom)
2868 {
2869 GtkAllocation trough_alloc;
2870
2871 gtk_css_gadget_get_margin_box (priv->trough_gadget, &trough_alloc);
2872
2873 zoom = MIN(1.0, (priv->orientation == GTK_ORIENTATION_VERTICAL ?
2874 trough_alloc.height : trough_alloc.width) /
2875 (gtk_adjustment_get_upper (priv->adjustment) -
2876 gtk_adjustment_get_lower (priv->adjustment) -
2877 gtk_adjustment_get_page_size (priv->adjustment)));
2878 /* the above is ineffective for scales, so just set a zoom factor */
2879 if (zoom == 1.0)
2880 zoom = 0.25;
2881 }
2882 else
2883 zoom = 1.0;
2884
2885 /* recalculate the initial position from the current position */
2886 if (priv->slide_initial_slider_position == -1)
2887 {
2888 GtkAllocation slider_alloc;
2889
2890 gtk_css_gadget_get_margin_box (priv->slider_gadget, &slider_alloc);
2891
2892 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2893 priv->slide_initial_slider_position = (zoom * (mouse_y - priv->slide_initial_coordinate_delta) - slider_alloc.y) / (zoom - 1.0);
2894 else
2895 priv->slide_initial_slider_position = (zoom * (mouse_x - priv->slide_initial_coordinate_delta) - slider_alloc.x) / (zoom - 1.0);
2896 }
2897
2898 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2899 delta = mouse_y - (priv->slide_initial_coordinate_delta + priv->slide_initial_slider_position);
2900 else
2901 delta = mouse_x - (priv->slide_initial_coordinate_delta + priv->slide_initial_slider_position);
2902
2903 c = priv->slide_initial_slider_position + zoom * delta;
2904
2905 new_value = coord_to_value (range, c);
2906 next_value = coord_to_value (range, c + 1);
2907 mark_delta = fabs (next_value - new_value);
2908
2909 for (i = 0; i < priv->n_marks; i++)
2910 {
2911 mark_value = priv->marks[i];
2912
2913 if (fabs (gtk_adjustment_get_value (priv->adjustment) - mark_value) < 3 * mark_delta)
2914 {
2915 if (fabs (new_value - mark_value) < MARK_SNAP_LENGTH * mark_delta)
2916 {
2917 new_value = mark_value;
2918 break;
2919 }
2920 }
2921 }
2922
2923 g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_JUMP, new_value, &handled);
2924 }
2925
2926 static void
remove_autoscroll(GtkRange * range)2927 remove_autoscroll (GtkRange *range)
2928 {
2929 if (range->priv->autoscroll_id)
2930 {
2931 gtk_widget_remove_tick_callback (GTK_WIDGET (range),
2932 range->priv->autoscroll_id);
2933 range->priv->autoscroll_id = 0;
2934 }
2935
2936 /* unset initial position so it can be calculated */
2937 range->priv->slide_initial_slider_position = -1;
2938
2939 range->priv->autoscroll_mode = GTK_SCROLL_NONE;
2940 }
2941
2942 static gboolean
autoscroll_cb(GtkWidget * widget,GdkFrameClock * frame_clock,gpointer data)2943 autoscroll_cb (GtkWidget *widget,
2944 GdkFrameClock *frame_clock,
2945 gpointer data)
2946 {
2947 GtkRange *range = GTK_RANGE (data);
2948 GtkRangePrivate *priv = range->priv;
2949 GtkAdjustment *adj = priv->adjustment;
2950 gdouble increment;
2951 gdouble value;
2952 gboolean handled;
2953 gdouble step, page;
2954
2955 step = gtk_adjustment_get_step_increment (adj);
2956 page = gtk_adjustment_get_page_increment (adj);
2957
2958 switch (priv->autoscroll_mode)
2959 {
2960 case GTK_SCROLL_STEP_FORWARD:
2961 increment = step / AUTOSCROLL_FACTOR;
2962 break;
2963 case GTK_SCROLL_PAGE_FORWARD:
2964 increment = page / AUTOSCROLL_FACTOR;
2965 break;
2966 case GTK_SCROLL_STEP_BACKWARD:
2967 increment = - step / AUTOSCROLL_FACTOR;
2968 break;
2969 case GTK_SCROLL_PAGE_BACKWARD:
2970 increment = - page / AUTOSCROLL_FACTOR;
2971 break;
2972 case GTK_SCROLL_START:
2973 case GTK_SCROLL_END:
2974 {
2975 gdouble x, y;
2976 gdouble distance, t;
2977
2978 /* Vary scrolling speed from slow (ie step) to fast (2 * page),
2979 * based on the distance of the pointer from the widget. We start
2980 * speeding up if the pointer moves at least 20 pixels away, and
2981 * we reach maximum speed when it is 220 pixels away.
2982 */
2983 if (!gtk_gesture_drag_get_offset (GTK_GESTURE_DRAG (priv->drag_gesture), &x, &y))
2984 {
2985 x = 0.0;
2986 y = 0.0;
2987 }
2988 if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL)
2989 distance = fabs (y);
2990 else
2991 distance = fabs (x);
2992 distance = CLAMP (distance - 20, 0.0, 200);
2993 t = distance / 100.0;
2994 step = (1 - t) * step + t * page;
2995 if (priv->autoscroll_mode == GTK_SCROLL_END)
2996 increment = step / AUTOSCROLL_FACTOR;
2997 else
2998 increment = - step / AUTOSCROLL_FACTOR;
2999 }
3000 break;
3001 default:
3002 g_assert_not_reached ();
3003 break;
3004 }
3005
3006 value = gtk_adjustment_get_value (adj);
3007 value += increment;
3008
3009 g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_JUMP, value, &handled);
3010
3011 return G_SOURCE_CONTINUE;
3012 }
3013
3014 static void
add_autoscroll(GtkRange * range)3015 add_autoscroll (GtkRange *range)
3016 {
3017 GtkRangePrivate *priv = range->priv;
3018
3019 if (priv->autoscroll_id != 0 ||
3020 priv->autoscroll_mode == GTK_SCROLL_NONE)
3021 return;
3022
3023 priv->autoscroll_id = gtk_widget_add_tick_callback (GTK_WIDGET (range),
3024 autoscroll_cb, range, NULL);
3025 }
3026
3027 static void
stop_scrolling(GtkRange * range)3028 stop_scrolling (GtkRange *range)
3029 {
3030 range_grab_remove (range);
3031 gtk_range_remove_step_timer (range);
3032 remove_autoscroll (range);
3033 }
3034
3035 /**
3036 * _gtk_range_get_wheel_delta:
3037 * @range: a #GtkRange
3038 * @event: A #GdkEventScroll
3039 *
3040 * Returns a good step value for the mouse wheel.
3041 *
3042 * Returns: A good step value for the mouse wheel.
3043 *
3044 * Since: 2.4
3045 **/
3046 gdouble
_gtk_range_get_wheel_delta(GtkRange * range,GdkEventScroll * event)3047 _gtk_range_get_wheel_delta (GtkRange *range,
3048 GdkEventScroll *event)
3049 {
3050 GtkRangePrivate *priv = range->priv;
3051 GtkAdjustment *adjustment = priv->adjustment;
3052 gdouble dx, dy;
3053 gdouble delta = 0;
3054 gdouble page_size;
3055 gdouble page_increment;
3056 gdouble scroll_unit;
3057 GdkScrollDirection direction;
3058 GtkOrientation move_orientation;
3059
3060 page_size = gtk_adjustment_get_page_size (adjustment);
3061 page_increment = gtk_adjustment_get_page_increment (adjustment);
3062
3063 if (GTK_IS_SCROLLBAR (range))
3064 {
3065 gdouble pow_unit = pow (page_size, 2.0 / 3.0);
3066
3067 /* for very small page sizes of < 1.0, the effect of pow() is
3068 * the opposite of what's intended and the scroll steps become
3069 * unusably large, make sure we never get a scroll_unit larger
3070 * than page_size / 2.0, which used to be the default before the
3071 * pow() magic was introduced.
3072 */
3073 scroll_unit = MIN (pow_unit, page_size / 2.0);
3074 }
3075 else
3076 scroll_unit = page_increment;
3077
3078 if (gdk_event_get_scroll_deltas ((GdkEvent *) event, &dx, &dy))
3079 {
3080 #ifdef GDK_WINDOWING_QUARTZ
3081 scroll_unit = 1;
3082 #endif
3083
3084 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL && dx != 0)
3085 {
3086 move_orientation = GTK_ORIENTATION_HORIZONTAL;
3087 delta = dx * scroll_unit;
3088 }
3089 else
3090 {
3091 move_orientation = GTK_ORIENTATION_VERTICAL;
3092 delta = dy * scroll_unit;
3093 }
3094 }
3095 else if (gdk_event_get_scroll_direction ((GdkEvent *) event, &direction))
3096 {
3097 if (direction == GDK_SCROLL_LEFT || direction == GDK_SCROLL_RIGHT)
3098 move_orientation = GTK_ORIENTATION_HORIZONTAL;
3099 else
3100 move_orientation = GTK_ORIENTATION_VERTICAL;
3101
3102 if (direction == GDK_SCROLL_LEFT || direction == GDK_SCROLL_UP)
3103 delta = - scroll_unit;
3104 else
3105 delta = scroll_unit;
3106 }
3107
3108 if (delta != 0 && should_invert_move (range, move_orientation))
3109 delta = - delta;
3110
3111 return delta;
3112 }
3113
3114 static gboolean
gtk_range_scroll_event(GtkWidget * widget,GdkEventScroll * event)3115 gtk_range_scroll_event (GtkWidget *widget,
3116 GdkEventScroll *event)
3117 {
3118 GtkRange *range = GTK_RANGE (widget);
3119 GtkRangePrivate *priv = range->priv;
3120 double delta = _gtk_range_get_wheel_delta (range, event);
3121 gboolean handled;
3122
3123 g_signal_emit (range, signals[CHANGE_VALUE], 0,
3124 GTK_SCROLL_JUMP, gtk_adjustment_get_value (priv->adjustment) + delta,
3125 &handled);
3126
3127 return GDK_EVENT_STOP;
3128 }
3129
3130 static void
update_autoscroll_mode(GtkRange * range)3131 update_autoscroll_mode (GtkRange *range)
3132 {
3133 GtkScrollType mode = GTK_SCROLL_NONE;
3134
3135 if (range->priv->zoom)
3136 {
3137 GtkAllocation allocation;
3138 gint size, pos;
3139
3140 gtk_widget_get_allocation (GTK_WIDGET (range), &allocation);
3141
3142 if (range->priv->orientation == GTK_ORIENTATION_VERTICAL)
3143 {
3144 size = allocation.height;
3145 pos = range->priv->mouse_y;
3146 }
3147 else
3148 {
3149 size = allocation.width;
3150 pos = range->priv->mouse_x;
3151 }
3152
3153 if (pos < SCROLL_EDGE_SIZE)
3154 mode = range->priv->inverted ? GTK_SCROLL_STEP_FORWARD : GTK_SCROLL_STEP_BACKWARD;
3155 else if (pos > (size - SCROLL_EDGE_SIZE))
3156 mode = range->priv->inverted ? GTK_SCROLL_STEP_BACKWARD : GTK_SCROLL_STEP_FORWARD;
3157 }
3158
3159 if (mode != range->priv->autoscroll_mode)
3160 {
3161 remove_autoscroll (range);
3162 range->priv->autoscroll_mode = mode;
3163 add_autoscroll (range);
3164 }
3165 }
3166
3167 static void
gtk_range_drag_gesture_update(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,GtkRange * range)3168 gtk_range_drag_gesture_update (GtkGestureDrag *gesture,
3169 gdouble offset_x,
3170 gdouble offset_y,
3171 GtkRange *range)
3172 {
3173 GtkRangePrivate *priv = range->priv;
3174 gdouble start_x, start_y;
3175
3176 if (priv->grab_location == priv->slider_gadget)
3177 {
3178 gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
3179 priv->mouse_x = start_x + offset_x;
3180 priv->mouse_y = start_y + offset_y;
3181 priv->in_drag = TRUE;
3182 update_autoscroll_mode (range);
3183
3184 if (priv->autoscroll_mode == GTK_SCROLL_NONE)
3185 update_slider_position (range, priv->mouse_x, priv->mouse_y);
3186 }
3187 }
3188
3189 static void
gtk_range_drag_gesture_begin(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,GtkRange * range)3190 gtk_range_drag_gesture_begin (GtkGestureDrag *gesture,
3191 gdouble offset_x,
3192 gdouble offset_y,
3193 GtkRange *range)
3194 {
3195 GtkRangePrivate *priv = range->priv;
3196
3197 if (priv->grab_location == priv->slider_gadget)
3198 gtk_gesture_set_state (priv->drag_gesture, GTK_EVENT_SEQUENCE_CLAIMED);
3199 }
3200
3201 static gboolean
gtk_range_event(GtkWidget * widget,GdkEvent * event)3202 gtk_range_event (GtkWidget *widget,
3203 GdkEvent *event)
3204 {
3205 GtkRange *range = GTK_RANGE (widget);
3206 GtkRangePrivate *priv = range->priv;
3207 gdouble x, y;
3208
3209 if (event->type == GDK_LEAVE_NOTIFY)
3210 {
3211 priv->mouse_x = G_MININT;
3212 priv->mouse_y = G_MININT;
3213 }
3214 else if (gdk_event_get_coords (event, &x, &y))
3215 {
3216 priv->mouse_x = x;
3217 priv->mouse_y = y;
3218 }
3219
3220 gtk_range_update_mouse_location (range);
3221
3222 return GDK_EVENT_PROPAGATE;
3223 }
3224
3225 static void
gtk_range_adjustment_changed(GtkAdjustment * adjustment,gpointer data)3226 gtk_range_adjustment_changed (GtkAdjustment *adjustment,
3227 gpointer data)
3228 {
3229 GtkRange *range = GTK_RANGE (data);
3230
3231 gtk_range_calc_slider (range);
3232 gtk_range_calc_stepper_sensitivity (range);
3233
3234 /* Note that we don't round off to priv->round_digits here.
3235 * that's because it's really broken to change a value
3236 * in response to a change signal on that value; round_digits
3237 * is therefore defined to be a filter on what the GtkRange
3238 * can input into the adjustment, not a filter that the GtkRange
3239 * will enforce on the adjustment.
3240 */
3241 }
3242
3243 static void
gtk_range_adjustment_value_changed(GtkAdjustment * adjustment,gpointer data)3244 gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
3245 gpointer data)
3246 {
3247 GtkRange *range = GTK_RANGE (data);
3248
3249 gtk_range_calc_slider (range);
3250 gtk_range_calc_stepper_sensitivity (range);
3251
3252 /* now check whether the layout changed */
3253 if (GTK_IS_SCALE (range) && gtk_scale_get_draw_value (GTK_SCALE (range)))
3254 {
3255 gtk_widget_queue_draw (GTK_WIDGET (range));
3256 }
3257
3258 /* Note that we don't round off to priv->round_digits here.
3259 * that's because it's really broken to change a value
3260 * in response to a change signal on that value; round_digits
3261 * is therefore defined to be a filter on what the GtkRange
3262 * can input into the adjustment, not a filter that the GtkRange
3263 * will enforce on the adjustment.
3264 */
3265
3266 g_signal_emit (range, signals[VALUE_CHANGED], 0);
3267 }
3268
3269 static void
apply_marks(GtkRange * range,gdouble oldval,gdouble * newval)3270 apply_marks (GtkRange *range,
3271 gdouble oldval,
3272 gdouble *newval)
3273 {
3274 GtkRangePrivate *priv = range->priv;
3275 gint i;
3276 gdouble mark;
3277
3278 for (i = 0; i < priv->n_marks; i++)
3279 {
3280 mark = priv->marks[i];
3281 if ((oldval < mark && mark < *newval) ||
3282 (oldval > mark && mark > *newval))
3283 {
3284 *newval = mark;
3285 return;
3286 }
3287 }
3288 }
3289
3290 static void
step_back(GtkRange * range)3291 step_back (GtkRange *range)
3292 {
3293 GtkRangePrivate *priv = range->priv;
3294 gdouble newval;
3295 gboolean handled;
3296
3297 newval = gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_step_increment (priv->adjustment);
3298 apply_marks (range, gtk_adjustment_get_value (priv->adjustment), &newval);
3299 g_signal_emit (range, signals[CHANGE_VALUE], 0,
3300 GTK_SCROLL_STEP_BACKWARD, newval, &handled);
3301 }
3302
3303 static void
step_forward(GtkRange * range)3304 step_forward (GtkRange *range)
3305 {
3306 GtkRangePrivate *priv = range->priv;
3307 gdouble newval;
3308 gboolean handled;
3309
3310 newval = gtk_adjustment_get_value (priv->adjustment) + gtk_adjustment_get_step_increment (priv->adjustment);
3311 apply_marks (range, gtk_adjustment_get_value (priv->adjustment), &newval);
3312 g_signal_emit (range, signals[CHANGE_VALUE], 0,
3313 GTK_SCROLL_STEP_FORWARD, newval, &handled);
3314 }
3315
3316
3317 static void
page_back(GtkRange * range)3318 page_back (GtkRange *range)
3319 {
3320 GtkRangePrivate *priv = range->priv;
3321 gdouble newval;
3322 gboolean handled;
3323
3324 newval = gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_page_increment (priv->adjustment);
3325 apply_marks (range, gtk_adjustment_get_value (priv->adjustment), &newval);
3326 g_signal_emit (range, signals[CHANGE_VALUE], 0,
3327 GTK_SCROLL_PAGE_BACKWARD, newval, &handled);
3328 }
3329
3330 static void
page_forward(GtkRange * range)3331 page_forward (GtkRange *range)
3332 {
3333 GtkRangePrivate *priv = range->priv;
3334 gdouble newval;
3335 gboolean handled;
3336
3337 newval = gtk_adjustment_get_value (priv->adjustment) + gtk_adjustment_get_page_increment (priv->adjustment);
3338 apply_marks (range, gtk_adjustment_get_value (priv->adjustment), &newval);
3339 g_signal_emit (range, signals[CHANGE_VALUE], 0,
3340 GTK_SCROLL_PAGE_FORWARD, newval, &handled);
3341 }
3342
3343 static void
scroll_begin(GtkRange * range)3344 scroll_begin (GtkRange *range)
3345 {
3346 GtkRangePrivate *priv = range->priv;
3347 gboolean handled;
3348
3349 g_signal_emit (range, signals[CHANGE_VALUE], 0,
3350 GTK_SCROLL_START, gtk_adjustment_get_lower (priv->adjustment),
3351 &handled);
3352 }
3353
3354 static void
scroll_end(GtkRange * range)3355 scroll_end (GtkRange *range)
3356 {
3357 GtkRangePrivate *priv = range->priv;
3358 gdouble newval;
3359 gboolean handled;
3360
3361 newval = gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_page_size (priv->adjustment);
3362 g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_END, newval,
3363 &handled);
3364 }
3365
3366 static gboolean
gtk_range_scroll(GtkRange * range,GtkScrollType scroll)3367 gtk_range_scroll (GtkRange *range,
3368 GtkScrollType scroll)
3369 {
3370 GtkRangePrivate *priv = range->priv;
3371 gdouble old_value = gtk_adjustment_get_value (priv->adjustment);
3372
3373 switch (scroll)
3374 {
3375 case GTK_SCROLL_STEP_LEFT:
3376 if (should_invert (range))
3377 step_forward (range);
3378 else
3379 step_back (range);
3380 break;
3381
3382 case GTK_SCROLL_STEP_UP:
3383 if (should_invert (range))
3384 step_forward (range);
3385 else
3386 step_back (range);
3387 break;
3388
3389 case GTK_SCROLL_STEP_RIGHT:
3390 if (should_invert (range))
3391 step_back (range);
3392 else
3393 step_forward (range);
3394 break;
3395
3396 case GTK_SCROLL_STEP_DOWN:
3397 if (should_invert (range))
3398 step_back (range);
3399 else
3400 step_forward (range);
3401 break;
3402
3403 case GTK_SCROLL_STEP_BACKWARD:
3404 step_back (range);
3405 break;
3406
3407 case GTK_SCROLL_STEP_FORWARD:
3408 step_forward (range);
3409 break;
3410
3411 case GTK_SCROLL_PAGE_LEFT:
3412 if (should_invert (range))
3413 page_forward (range);
3414 else
3415 page_back (range);
3416 break;
3417
3418 case GTK_SCROLL_PAGE_UP:
3419 if (should_invert (range))
3420 page_forward (range);
3421 else
3422 page_back (range);
3423 break;
3424
3425 case GTK_SCROLL_PAGE_RIGHT:
3426 if (should_invert (range))
3427 page_back (range);
3428 else
3429 page_forward (range);
3430 break;
3431
3432 case GTK_SCROLL_PAGE_DOWN:
3433 if (should_invert (range))
3434 page_back (range);
3435 else
3436 page_forward (range);
3437 break;
3438
3439 case GTK_SCROLL_PAGE_BACKWARD:
3440 page_back (range);
3441 break;
3442
3443 case GTK_SCROLL_PAGE_FORWARD:
3444 page_forward (range);
3445 break;
3446
3447 case GTK_SCROLL_START:
3448 scroll_begin (range);
3449 break;
3450
3451 case GTK_SCROLL_END:
3452 scroll_end (range);
3453 break;
3454
3455 case GTK_SCROLL_JUMP:
3456 /* Used by CList, range doesn't use it. */
3457 break;
3458
3459 case GTK_SCROLL_NONE:
3460 break;
3461 }
3462
3463 return gtk_adjustment_get_value (priv->adjustment) != old_value;
3464 }
3465
3466 static void
gtk_range_move_slider(GtkRange * range,GtkScrollType scroll)3467 gtk_range_move_slider (GtkRange *range,
3468 GtkScrollType scroll)
3469 {
3470 if (! gtk_range_scroll (range, scroll))
3471 gtk_widget_error_bell (GTK_WIDGET (range));
3472 }
3473
3474 static gboolean
rectangle_contains_point(GdkRectangle * rect,gint x,gint y)3475 rectangle_contains_point (GdkRectangle *rect,
3476 gint x,
3477 gint y)
3478 {
3479 return (x >= rect->x) && (x < rect->x + rect->width) &&
3480 (y >= rect->y) && (y < rect->y + rect->height);
3481 }
3482
3483 /* Update mouse location, return TRUE if it changes */
3484 static void
gtk_range_update_mouse_location(GtkRange * range)3485 gtk_range_update_mouse_location (GtkRange *range)
3486 {
3487 GtkRangePrivate *priv = range->priv;
3488 gint x, y;
3489 GtkCssGadget *old_location;
3490 GtkWidget *widget = GTK_WIDGET (range);
3491 GdkRectangle trough_alloc, slider_alloc, slider_trace;
3492
3493 old_location = priv->mouse_location;
3494
3495 x = priv->mouse_x;
3496 y = priv->mouse_y;
3497
3498 gtk_css_gadget_get_border_box (priv->trough_gadget, &trough_alloc);
3499 gtk_css_gadget_get_border_box (priv->slider_gadget, &slider_alloc);
3500 gdk_rectangle_union (&slider_alloc, &trough_alloc, &slider_trace);
3501
3502 if (priv->grab_location != NULL)
3503 priv->mouse_location = priv->grab_location;
3504 else if (priv->stepper_a_gadget &&
3505 gtk_css_gadget_border_box_contains_point (priv->stepper_a_gadget, x, y))
3506 priv->mouse_location = priv->stepper_a_gadget;
3507 else if (priv->stepper_b_gadget &&
3508 gtk_css_gadget_border_box_contains_point (priv->stepper_b_gadget, x, y))
3509 priv->mouse_location = priv->stepper_b_gadget;
3510 else if (priv->stepper_c_gadget &&
3511 gtk_css_gadget_border_box_contains_point (priv->stepper_c_gadget, x, y))
3512 priv->mouse_location = priv->stepper_c_gadget;
3513 else if (priv->stepper_d_gadget &&
3514 gtk_css_gadget_border_box_contains_point (priv->stepper_d_gadget, x, y))
3515 priv->mouse_location = priv->stepper_d_gadget;
3516 else if (gtk_css_gadget_border_box_contains_point (priv->slider_gadget, x, y))
3517 priv->mouse_location = priv->slider_gadget;
3518 else if (rectangle_contains_point (&slider_trace, x, y))
3519 priv->mouse_location = priv->trough_gadget;
3520 else if (gtk_css_gadget_margin_box_contains_point (priv->gadget, x, y))
3521 priv->mouse_location = priv->gadget;
3522 else
3523 priv->mouse_location = NULL;
3524
3525 if (old_location != priv->mouse_location)
3526 {
3527 if (old_location != NULL)
3528 gtk_css_gadget_queue_allocate (old_location);
3529
3530 if (priv->mouse_location != NULL)
3531 {
3532 gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_PRELIGHT, FALSE);
3533 gtk_css_gadget_queue_allocate (priv->mouse_location);
3534 }
3535 else
3536 {
3537 gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_PRELIGHT);
3538 }
3539
3540 update_trough_state (range);
3541 update_slider_state (range);
3542 update_steppers_state (range);
3543 }
3544 }
3545
3546 static void
gtk_range_compute_slider_position(GtkRange * range,gdouble adjustment_value,GdkRectangle * slider_rect)3547 gtk_range_compute_slider_position (GtkRange *range,
3548 gdouble adjustment_value,
3549 GdkRectangle *slider_rect)
3550 {
3551 GtkRangePrivate *priv = range->priv;
3552 GtkAllocation trough_content_alloc;
3553 int slider_width, slider_height, min_slider_size;
3554
3555 measure_one_gadget (priv->slider_gadget, &slider_width, &slider_height);
3556 gtk_css_gadget_get_content_box (priv->trough_gadget, &trough_content_alloc);
3557
3558 min_slider_size = priv->min_slider_size;
3559
3560 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
3561 {
3562 gint y, bottom, top, height;
3563
3564 /* Slider fits into the trough, with stepper_spacing on either side,
3565 * and the size/position based on the adjustment or fixed, depending.
3566 */
3567 slider_rect->x = trough_content_alloc.x + (int) floor ((trough_content_alloc.width - slider_width) / 2);
3568 slider_rect->width = slider_width;
3569
3570 if (priv->slider_use_min_size)
3571 min_slider_size = slider_height;
3572
3573 /* Compute slider position/length */
3574 top = trough_content_alloc.y;
3575 bottom = top + trough_content_alloc.height;
3576
3577 /* Scale slider half extends over the trough edge */
3578 if (GTK_IS_SCALE (range))
3579 {
3580 top -= min_slider_size / 2;
3581 bottom += min_slider_size / 2;
3582 }
3583
3584 /* slider height is the fraction (page_size /
3585 * total_adjustment_range) times the trough height in pixels
3586 */
3587
3588 if (gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment) != 0)
3589 height = ((bottom - top) * (gtk_adjustment_get_page_size (priv->adjustment) /
3590 (gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment))));
3591 else
3592 height = min_slider_size;
3593
3594 if (height < min_slider_size ||
3595 priv->slider_size_fixed)
3596 height = min_slider_size;
3597
3598 height = MIN (height, trough_content_alloc.height);
3599
3600 y = top;
3601
3602 if (gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment) - gtk_adjustment_get_page_size (priv->adjustment) != 0)
3603 y += (bottom - top - height) * ((adjustment_value - gtk_adjustment_get_lower (priv->adjustment)) /
3604 (gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment) - gtk_adjustment_get_page_size (priv->adjustment)));
3605
3606 y = CLAMP (y, top, bottom);
3607
3608 if (should_invert (range))
3609 y = bottom - (y - top + height);
3610
3611 slider_rect->y = y;
3612 slider_rect->height = height;
3613 }
3614 else
3615 {
3616 gint x, left, right, width;
3617
3618 /* Slider fits into the trough, with stepper_spacing on either side,
3619 * and the size/position based on the adjustment or fixed, depending.
3620 */
3621 slider_rect->y = trough_content_alloc.y + (int) floor ((trough_content_alloc.height - slider_height) / 2);
3622 slider_rect->height = slider_height;
3623
3624 if (priv->slider_use_min_size)
3625 min_slider_size = slider_width;
3626
3627 /* Compute slider position/length */
3628 left = trough_content_alloc.x;
3629 right = left + trough_content_alloc.width;
3630
3631 /* Scale slider half extends over the trough edge */
3632 if (GTK_IS_SCALE (range))
3633 {
3634 left -= min_slider_size / 2;
3635 right += min_slider_size / 2;
3636 }
3637
3638 /* slider width is the fraction (page_size /
3639 * total_adjustment_range) times the trough width in pixels
3640 */
3641
3642 if (gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment) != 0)
3643 width = ((right - left) * (gtk_adjustment_get_page_size (priv->adjustment) /
3644 (gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment))));
3645 else
3646 width = min_slider_size;
3647
3648 if (width < min_slider_size ||
3649 priv->slider_size_fixed)
3650 width = min_slider_size;
3651
3652 width = MIN (width, trough_content_alloc.width);
3653
3654 x = left;
3655
3656 if (gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment) - gtk_adjustment_get_page_size (priv->adjustment) != 0)
3657 x += (right - left - width) * ((adjustment_value - gtk_adjustment_get_lower (priv->adjustment)) /
3658 (gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_lower (priv->adjustment) - gtk_adjustment_get_page_size (priv->adjustment)));
3659
3660 x = CLAMP (x, left, right);
3661
3662 if (should_invert (range))
3663 x = right - (x - left + width);
3664
3665 slider_rect->x = x;
3666 slider_rect->width = width;
3667 }
3668 }
3669
3670 static void
gtk_range_calc_slider(GtkRange * range)3671 gtk_range_calc_slider (GtkRange *range)
3672 {
3673 GtkRangePrivate *priv = range->priv;
3674 gboolean visible;
3675
3676 if (GTK_IS_SCALE (range) &&
3677 gtk_adjustment_get_upper (priv->adjustment) == gtk_adjustment_get_lower (priv->adjustment))
3678 visible = FALSE;
3679 else
3680 visible = TRUE;
3681
3682 gtk_css_gadget_set_visible (priv->slider_gadget, visible);
3683
3684 gtk_css_gadget_queue_resize (priv->slider_gadget);
3685
3686 if (priv->has_origin)
3687 gtk_css_gadget_queue_allocate (priv->trough_gadget);
3688
3689 gtk_range_update_mouse_location (range);
3690 }
3691
3692 static void
gtk_range_calc_stepper_sensitivity(GtkRange * range)3693 gtk_range_calc_stepper_sensitivity (GtkRange *range)
3694 {
3695 GtkRangePrivate *priv = range->priv;
3696 gboolean was_upper_sensitive, was_lower_sensitive;
3697
3698 was_upper_sensitive = priv->upper_sensitive;
3699 switch (priv->upper_sensitivity)
3700 {
3701 case GTK_SENSITIVITY_AUTO:
3702 priv->upper_sensitive =
3703 (gtk_adjustment_get_value (priv->adjustment) <
3704 (gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_page_size (priv->adjustment)));
3705 break;
3706
3707 case GTK_SENSITIVITY_ON:
3708 priv->upper_sensitive = TRUE;
3709 break;
3710
3711 case GTK_SENSITIVITY_OFF:
3712 priv->upper_sensitive = FALSE;
3713 break;
3714 }
3715
3716 was_lower_sensitive = priv->lower_sensitive;
3717 switch (priv->lower_sensitivity)
3718 {
3719 case GTK_SENSITIVITY_AUTO:
3720 priv->lower_sensitive =
3721 (gtk_adjustment_get_value (priv->adjustment) > gtk_adjustment_get_lower (priv->adjustment));
3722 break;
3723
3724 case GTK_SENSITIVITY_ON:
3725 priv->lower_sensitive = TRUE;
3726 break;
3727
3728 case GTK_SENSITIVITY_OFF:
3729 priv->lower_sensitive = FALSE;
3730 break;
3731 }
3732
3733 /* Too many side effects can influence which stepper reacts to wat condition.
3734 * So we just invalidate them all.
3735 */
3736 if (was_upper_sensitive != priv->upper_sensitive ||
3737 was_lower_sensitive != priv->lower_sensitive)
3738 {
3739 update_steppers_state (range);
3740
3741 if (priv->stepper_a_gadget)
3742 gtk_css_gadget_queue_allocate (priv->stepper_a_gadget);
3743 if (priv->stepper_b_gadget)
3744 gtk_css_gadget_queue_allocate (priv->stepper_b_gadget);
3745 if (priv->stepper_c_gadget)
3746 gtk_css_gadget_queue_allocate (priv->stepper_c_gadget);
3747 if (priv->stepper_d_gadget)
3748 gtk_css_gadget_queue_allocate (priv->stepper_d_gadget);
3749 }
3750 }
3751
3752 static void
gtk_range_calc_marks(GtkRange * range)3753 gtk_range_calc_marks (GtkRange *range)
3754 {
3755 GtkRangePrivate *priv = range->priv;
3756 GdkRectangle slider;
3757 gint i;
3758
3759 for (i = 0; i < priv->n_marks; i++)
3760 {
3761 gtk_range_compute_slider_position (range, priv->marks[i], &slider);
3762
3763 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
3764 priv->mark_pos[i] = slider.x + slider.width / 2;
3765 else
3766 priv->mark_pos[i] = slider.y + slider.height / 2;
3767 }
3768 }
3769
3770 static gboolean
gtk_range_real_change_value(GtkRange * range,GtkScrollType scroll,gdouble value)3771 gtk_range_real_change_value (GtkRange *range,
3772 GtkScrollType scroll,
3773 gdouble value)
3774 {
3775 GtkRangePrivate *priv = range->priv;
3776
3777 /* potentially adjust the bounds _before_ we clamp */
3778 g_signal_emit (range, signals[ADJUST_BOUNDS], 0, value);
3779
3780 if (priv->restrict_to_fill_level)
3781 value = MIN (value, MAX (gtk_adjustment_get_lower (priv->adjustment),
3782 priv->fill_level));
3783
3784 value = CLAMP (value, gtk_adjustment_get_lower (priv->adjustment),
3785 (gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_page_size (priv->adjustment)));
3786
3787 if (priv->round_digits >= 0)
3788 {
3789 gdouble power;
3790 gint i;
3791
3792 i = priv->round_digits;
3793 power = 1;
3794 while (i--)
3795 power *= 10;
3796
3797 value = floor ((value * power) + 0.5) / power;
3798 }
3799
3800 if (priv->in_drag || priv->autoscroll_id)
3801 gtk_adjustment_set_value (priv->adjustment, value);
3802 else
3803 gtk_adjustment_animate_to_value (priv->adjustment, value);
3804
3805 return FALSE;
3806 }
3807
3808 struct _GtkRangeStepTimer
3809 {
3810 guint timeout_id;
3811 GtkScrollType step;
3812 };
3813
3814 static gboolean
second_timeout(gpointer data)3815 second_timeout (gpointer data)
3816 {
3817 GtkRange *range = GTK_RANGE (data);
3818 GtkRangePrivate *priv = range->priv;
3819
3820 gtk_range_scroll (range, priv->timer->step);
3821
3822 return G_SOURCE_CONTINUE;
3823 }
3824
3825 static gboolean
initial_timeout(gpointer data)3826 initial_timeout (gpointer data)
3827 {
3828 GtkRange *range = GTK_RANGE (data);
3829 GtkRangePrivate *priv = range->priv;
3830
3831 priv->timer->timeout_id = gdk_threads_add_timeout (TIMEOUT_REPEAT,
3832 second_timeout,
3833 range);
3834 g_source_set_name_by_id (priv->timer->timeout_id, "[gtk+] second_timeout");
3835 return G_SOURCE_REMOVE;
3836 }
3837
3838 static void
gtk_range_add_step_timer(GtkRange * range,GtkScrollType step)3839 gtk_range_add_step_timer (GtkRange *range,
3840 GtkScrollType step)
3841 {
3842 GtkRangePrivate *priv = range->priv;
3843
3844 g_return_if_fail (priv->timer == NULL);
3845 g_return_if_fail (step != GTK_SCROLL_NONE);
3846
3847 priv->timer = g_new (GtkRangeStepTimer, 1);
3848
3849 priv->timer->timeout_id = gdk_threads_add_timeout (TIMEOUT_INITIAL,
3850 initial_timeout,
3851 range);
3852 g_source_set_name_by_id (priv->timer->timeout_id, "[gtk+] initial_timeout");
3853 priv->timer->step = step;
3854
3855 gtk_range_scroll (range, priv->timer->step);
3856 }
3857
3858 static void
gtk_range_remove_step_timer(GtkRange * range)3859 gtk_range_remove_step_timer (GtkRange *range)
3860 {
3861 GtkRangePrivate *priv = range->priv;
3862
3863 if (priv->timer)
3864 {
3865 if (priv->timer->timeout_id != 0)
3866 g_source_remove (priv->timer->timeout_id);
3867
3868 g_free (priv->timer);
3869
3870 priv->timer = NULL;
3871 }
3872 }
3873
3874 void
_gtk_range_set_has_origin(GtkRange * range,gboolean has_origin)3875 _gtk_range_set_has_origin (GtkRange *range,
3876 gboolean has_origin)
3877 {
3878 GtkRangePrivate *priv = range->priv;
3879
3880 range->priv->has_origin = has_origin;
3881
3882 if (has_origin)
3883 {
3884 priv->highlight_gadget = gtk_css_custom_gadget_new ("highlight",
3885 GTK_WIDGET (range),
3886 priv->trough_gadget, NULL,
3887 NULL, NULL, NULL,
3888 NULL, NULL);
3889 gtk_css_gadget_set_state (priv->highlight_gadget,
3890 gtk_css_node_get_state (gtk_css_gadget_get_node (priv->trough_gadget)));
3891
3892 update_highlight_position (range);
3893 }
3894 else
3895 {
3896 gtk_css_node_set_parent (gtk_css_gadget_get_node (priv->highlight_gadget), NULL);
3897 g_clear_object (&priv->highlight_gadget);
3898 }
3899 }
3900
3901 gboolean
_gtk_range_get_has_origin(GtkRange * range)3902 _gtk_range_get_has_origin (GtkRange *range)
3903 {
3904 return range->priv->has_origin;
3905 }
3906
3907 void
_gtk_range_set_stop_values(GtkRange * range,gdouble * values,gint n_values)3908 _gtk_range_set_stop_values (GtkRange *range,
3909 gdouble *values,
3910 gint n_values)
3911 {
3912 GtkRangePrivate *priv = range->priv;
3913 gint i;
3914
3915 g_free (priv->marks);
3916 priv->marks = g_new (gdouble, n_values);
3917
3918 g_free (priv->mark_pos);
3919 priv->mark_pos = g_new (gint, n_values);
3920
3921 priv->n_marks = n_values;
3922
3923 for (i = 0; i < n_values; i++)
3924 priv->marks[i] = values[i];
3925
3926 gtk_range_calc_marks (range);
3927 }
3928
3929 gint
_gtk_range_get_stop_positions(GtkRange * range,gint ** values)3930 _gtk_range_get_stop_positions (GtkRange *range,
3931 gint **values)
3932 {
3933 GtkRangePrivate *priv = range->priv;
3934
3935 gtk_range_calc_marks (range);
3936
3937 if (values)
3938 *values = g_memdup (priv->mark_pos, priv->n_marks * sizeof (gint));
3939
3940 return priv->n_marks;
3941 }
3942
3943 /**
3944 * gtk_range_set_round_digits:
3945 * @range: a #GtkRange
3946 * @round_digits: the precision in digits, or -1
3947 *
3948 * Sets the number of digits to round the value to when
3949 * it changes. See #GtkRange::change-value.
3950 *
3951 * Since: 2.24
3952 */
3953 void
gtk_range_set_round_digits(GtkRange * range,gint round_digits)3954 gtk_range_set_round_digits (GtkRange *range,
3955 gint round_digits)
3956 {
3957 g_return_if_fail (GTK_IS_RANGE (range));
3958 g_return_if_fail (round_digits >= -1);
3959
3960 if (range->priv->round_digits != round_digits)
3961 {
3962 range->priv->round_digits = round_digits;
3963 g_object_notify_by_pspec (G_OBJECT (range), properties[PROP_ROUND_DIGITS]);
3964 }
3965 }
3966
3967 /**
3968 * gtk_range_get_round_digits:
3969 * @range: a #GtkRange
3970 *
3971 * Gets the number of digits to round the value to when
3972 * it changes. See #GtkRange::change-value.
3973 *
3974 * Returns: the number of digits to round to
3975 *
3976 * Since: 2.24
3977 */
3978 gint
gtk_range_get_round_digits(GtkRange * range)3979 gtk_range_get_round_digits (GtkRange *range)
3980 {
3981 g_return_val_if_fail (GTK_IS_RANGE (range), -1);
3982
3983 return range->priv->round_digits;
3984 }
3985
3986 static void
sync_stepper_gadget(GtkRange * range,gboolean should_have_stepper,GtkCssGadget ** gadget_ptr,const gchar * class,GtkCssImageBuiltinType image_type,GtkCssGadget * prev_sibling)3987 sync_stepper_gadget (GtkRange *range,
3988 gboolean should_have_stepper,
3989 GtkCssGadget **gadget_ptr,
3990 const gchar *class,
3991 GtkCssImageBuiltinType image_type,
3992 GtkCssGadget *prev_sibling)
3993 {
3994 GtkWidget *widget;
3995 GtkCssGadget *gadget;
3996 GtkCssNode *widget_node;
3997 gboolean has_stepper;
3998 GtkRangePrivate *priv = range->priv;
3999
4000 has_stepper = (*gadget_ptr != NULL);
4001 if (has_stepper == should_have_stepper)
4002 return;
4003
4004 if (!should_have_stepper)
4005 {
4006 if (*gadget_ptr != NULL)
4007 {
4008 if (*gadget_ptr == priv->grab_location)
4009 stop_scrolling (range);
4010 if (*gadget_ptr == priv->mouse_location)
4011 priv->mouse_location = NULL;
4012 gtk_css_node_set_parent (gtk_css_gadget_get_node (*gadget_ptr), NULL);
4013 gtk_box_gadget_remove_gadget (GTK_BOX_GADGET (priv->contents_gadget), *gadget_ptr);
4014 }
4015 g_clear_object (gadget_ptr);
4016 return;
4017 }
4018
4019 widget = GTK_WIDGET (range);
4020 widget_node = gtk_widget_get_css_node (widget);
4021 gadget = gtk_builtin_icon_new ("button",
4022 widget,
4023 NULL, NULL);
4024 gtk_builtin_icon_set_image (GTK_BUILTIN_ICON (gadget), image_type);
4025 gtk_css_gadget_add_class (gadget, class);
4026 gtk_css_gadget_set_state (gadget, gtk_css_node_get_state (widget_node));
4027
4028 gtk_box_gadget_insert_gadget_after (GTK_BOX_GADGET (priv->contents_gadget), prev_sibling,
4029 gadget, FALSE, GTK_ALIGN_FILL);
4030 *gadget_ptr = gadget;
4031 }
4032
4033 void
_gtk_range_set_steppers(GtkRange * range,gboolean has_a,gboolean has_b,gboolean has_c,gboolean has_d)4034 _gtk_range_set_steppers (GtkRange *range,
4035 gboolean has_a,
4036 gboolean has_b,
4037 gboolean has_c,
4038 gboolean has_d)
4039 {
4040 GtkRangePrivate *priv = range->priv;
4041
4042 sync_stepper_gadget (range,
4043 has_a, &priv->stepper_a_gadget,
4044 "up",
4045 priv->orientation == GTK_ORIENTATION_VERTICAL ?
4046 GTK_CSS_IMAGE_BUILTIN_ARROW_UP : GTK_CSS_IMAGE_BUILTIN_ARROW_LEFT,
4047 NULL);
4048
4049 sync_stepper_gadget (range,
4050 has_b, &priv->stepper_b_gadget,
4051 "down",
4052 priv->orientation == GTK_ORIENTATION_VERTICAL ?
4053 GTK_CSS_IMAGE_BUILTIN_ARROW_DOWN : GTK_CSS_IMAGE_BUILTIN_ARROW_RIGHT,
4054 priv->stepper_a_gadget);
4055
4056 sync_stepper_gadget (range,
4057 has_c, &priv->stepper_c_gadget,
4058 "up",
4059 priv->orientation == GTK_ORIENTATION_VERTICAL ?
4060 GTK_CSS_IMAGE_BUILTIN_ARROW_UP : GTK_CSS_IMAGE_BUILTIN_ARROW_LEFT,
4061 priv->trough_gadget);
4062
4063 sync_stepper_gadget (range,
4064 has_d, &priv->stepper_d_gadget,
4065 "down",
4066 priv->orientation == GTK_ORIENTATION_VERTICAL ?
4067 GTK_CSS_IMAGE_BUILTIN_ARROW_DOWN : GTK_CSS_IMAGE_BUILTIN_ARROW_RIGHT,
4068 priv->stepper_c_gadget ? priv->stepper_c_gadget : priv->trough_gadget);
4069
4070 gtk_widget_queue_resize (GTK_WIDGET (range));
4071 }
4072
4073 GtkCssGadget *
gtk_range_get_slider_gadget(GtkRange * range)4074 gtk_range_get_slider_gadget (GtkRange *range)
4075 {
4076 return range->priv->slider_gadget;
4077 }
4078
4079 GtkCssGadget *
gtk_range_get_gadget(GtkRange * range)4080 gtk_range_get_gadget (GtkRange *range)
4081 {
4082 return range->priv->gadget;
4083 }
4084