1/* GTK - The GIMP Toolkit
2 * Copyright (C) 2005 Ronald S. Bultje
3 * Copyright (C) 2006, 2007 Christian Persch
4 * Copyright (C) 2006 Jan Arne Petersen
5 * Copyright (C) 2005-2007 Red Hat, Inc.
6 * Copyright (C) 2014 Red Hat, Inc.
7 *
8 * Authors:
9 * - Ronald S. Bultje <rbultje@ronald.bitfreak.net>
10 * - Bastien Nocera <bnocera@redhat.com>
11 * - Jan Arne Petersen <jpetersen@jpetersen.org>
12 * - Christian Persch <chpe@svn.gnome.org>
13 *
14 * This library is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU Lesser General Public
16 * License as published by the Free Software Foundation; either
17 * version 2 of the License, or (at your option) any later version.
18 *
19 * This library is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 * Lesser General Public License for more details.
23 *
24 * You should have received a copy of the GNU Lesser General Public
25 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
26 */
27
28/*
29 * Modified by the GTK+ Team and others 2007. See the AUTHORS
30 * file for a list of people on the GTK+ Team. See the ChangeLog
31 * files for a list of changes. These files are distributed with
32 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
33 */
34
35#include "config.h"
36
37#include "gtkscalebutton.h"
38
39#include "gtkadjustment.h"
40#include "gtkbox.h"
41#include "gtkbuttonprivate.h"
42#include "gtktogglebutton.h"
43#include "gtkeventcontrollerscroll.h"
44#include "gtkframe.h"
45#include "gtkgesture.h"
46#include "gtkintl.h"
47#include "gtkmain.h"
48#include "gtkmarshalers.h"
49#include "gtkorientable.h"
50#include "gtkpopover.h"
51#include "gtkprivate.h"
52#include "gtkrangeprivate.h"
53#include "gtkscale.h"
54#include "gtktypebuiltins.h"
55#include "gtkwidgetprivate.h"
56#include "gtkwindowprivate.h"
57#include "gtknative.h"
58
59#include <math.h>
60#include <stdlib.h>
61#include <string.h>
62
63/**
64 * GtkScaleButton:
65 *
66 * `GtkScaleButton` provides a button which pops up a scale widget.
67 *
68 * This kind of widget is commonly used for volume controls in multimedia
69 * applications, and GTK provides a [class@Gtk.VolumeButton] subclass that
70 * is tailored for this use case.
71 *
72 * # CSS nodes
73 *
74 * `GtkScaleButton` has a single CSS node with name button. To differentiate
75 * it from a plain `GtkButton`, it gets the .scale style class.
76 */
77
78
79#define SCALE_SIZE 100
80
81enum
82{
83 VALUE_CHANGED,
84 POPUP,
85 POPDOWN,
86
87 LAST_SIGNAL
88};
89
90enum
91{
92 PROP_0,
93
94 PROP_ORIENTATION,
95 PROP_VALUE,
96 PROP_SIZE,
97 PROP_ADJUSTMENT,
98 PROP_ICONS
99};
100
101typedef struct
102{
103 GtkWidget *button;
104
105 GtkWidget *plus_button;
106 GtkWidget *minus_button;
107 GtkWidget *dock;
108 GtkWidget *box;
109 GtkWidget *scale;
110 GtkWidget *active_button;
111
112 GtkOrientation orientation;
113 GtkOrientation applied_orientation;
114
115 guint autoscroll_timeout;
116 GtkScrollType autoscroll_step;
117 gboolean autoscrolling;
118
119 char **icon_list;
120
121 GtkAdjustment *adjustment; /* needed because it must be settable in init() */
122} GtkScaleButtonPrivate;
123
124static void gtk_scale_button_constructed (GObject *object);
125static void gtk_scale_button_dispose (GObject *object);
126static void gtk_scale_button_finalize (GObject *object);
127static void gtk_scale_button_set_property (GObject *object,
128 guint prop_id,
129 const GValue *value,
130 GParamSpec *pspec);
131static void gtk_scale_button_get_property (GObject *object,
132 guint prop_id,
133 GValue *value,
134 GParamSpec *pspec);
135static void gtk_scale_button_size_allocate (GtkWidget *widget,
136 int width,
137 int height,
138 int baseline);
139static void gtk_scale_button_measure (GtkWidget *widget,
140 GtkOrientation orientation,
141 int for_size,
142 int *minimum,
143 int *natural,
144 int *minimum_baseline,
145 int *natural_baseline);
146static void gtk_scale_button_set_orientation_private (GtkScaleButton *button,
147 GtkOrientation orientation);
148static void gtk_scale_button_popup (GtkWidget *widget);
149static void gtk_scale_button_popdown (GtkWidget *widget);
150static void cb_button_clicked (GtkWidget *button,
151 gpointer user_data);
152static void gtk_scale_button_update_icon (GtkScaleButton *button);
153static void cb_scale_value_changed (GtkRange *range,
154 gpointer user_data);
155static void cb_popup_mapped (GtkWidget *popup,
156 gpointer user_data);
157
158static gboolean gtk_scale_button_scroll_controller_scroll (GtkEventControllerScroll *scroll,
159 double dx,
160 double dy,
161 GtkScaleButton *button);
162
163G_DEFINE_TYPE_WITH_CODE (GtkScaleButton, gtk_scale_button, GTK_TYPE_WIDGET,
164 G_ADD_PRIVATE (GtkScaleButton)
165 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
166
167static guint signals[LAST_SIGNAL] = { 0, };
168
169static void
170gtk_scale_button_class_init (GtkScaleButtonClass *klass)
171{
172 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
173 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
174
175 gobject_class->constructed = gtk_scale_button_constructed;
176 gobject_class->finalize = gtk_scale_button_finalize;
177 gobject_class->dispose = gtk_scale_button_dispose;
178 gobject_class->set_property = gtk_scale_button_set_property;
179 gobject_class->get_property = gtk_scale_button_get_property;
180
181 widget_class->measure = gtk_scale_button_measure;
182 widget_class->size_allocate = gtk_scale_button_size_allocate;
183 widget_class->focus = gtk_widget_focus_child;
184 widget_class->grab_focus = gtk_widget_grab_focus_child;
185
186
187 g_object_class_override_property (oclass: gobject_class, property_id: PROP_ORIENTATION, name: "orientation");
188
189 /**
190 * GtkScaleButton:value: (attributes org.gtk.Property.get=gtk_scale_button_get_value org.gtk.Property.set=gtk_scale_button_set_value)
191 *
192 * The value of the scale.
193 */
194 g_object_class_install_property (oclass: gobject_class,
195 property_id: PROP_VALUE,
196 pspec: g_param_spec_double (name: "value",
197 P_("Value"),
198 P_("The value of the scale"),
199 minimum: -G_MAXDOUBLE,
200 G_MAXDOUBLE,
201 default_value: 0,
202 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
203
204 /**
205 * GtkScaleButton:adjustment: (attributes org.gtk.Property.get=gtk_scale_button_get_adjustment org.gtk.Property.set=gtk_scale_button_set_adjustment)
206 *
207 * The `GtkAdjustment` that is used as the model.
208 */
209 g_object_class_install_property (oclass: gobject_class,
210 property_id: PROP_ADJUSTMENT,
211 pspec: g_param_spec_object (name: "adjustment",
212 P_("Adjustment"),
213 P_("The GtkAdjustment that contains the current value of this scale button object"),
214 GTK_TYPE_ADJUSTMENT,
215 GTK_PARAM_READWRITE));
216
217 /**
218 * GtkScaleButton:icons: (attributes org.gtk.Property.set=gtk_scale_button_set_icons)
219 *
220 * The names of the icons to be used by the scale button.
221 *
222 * The first item in the array will be used in the button
223 * when the current value is the lowest value, the second
224 * item for the highest value. All the subsequent icons will
225 * be used for all the other values, spread evenly over the
226 * range of values.
227 *
228 * If there's only one icon name in the @icons array, it will
229 * be used for all the values. If only two icon names are in
230 * the @icons array, the first one will be used for the bottom
231 * 50% of the scale, and the second one for the top 50%.
232 *
233 * It is recommended to use at least 3 icons so that the
234 * `GtkScaleButton` reflects the current value of the scale
235 * better for the users.
236 */
237 g_object_class_install_property (oclass: gobject_class,
238 property_id: PROP_ICONS,
239 pspec: g_param_spec_boxed (name: "icons",
240 P_("Icons"),
241 P_("List of icon names"),
242 G_TYPE_STRV,
243 GTK_PARAM_READWRITE));
244
245 /**
246 * GtkScaleButton::value-changed:
247 * @button: the object which received the signal
248 * @value: the new value
249 *
250 * Emitted when the value field has changed.
251 */
252 signals[VALUE_CHANGED] =
253 g_signal_new (I_("value-changed"),
254 G_TYPE_FROM_CLASS (klass),
255 signal_flags: G_SIGNAL_RUN_LAST,
256 G_STRUCT_OFFSET (GtkScaleButtonClass, value_changed),
257 NULL, NULL,
258 NULL,
259 G_TYPE_NONE, n_params: 1, G_TYPE_DOUBLE);
260
261 /**
262 * GtkScaleButton::popup:
263 * @button: the object which received the signal
264 *
265 * Emitted to popup the scale widget.
266 *
267 * This is a [keybinding signal](class.SignalAction.html).
268 *
269 * The default bindings for this signal are <kbd>Space</kbd>,
270 * <kbd>Enter</kbd> and <kbd>Return</kbd>.
271 */
272 signals[POPUP] =
273 g_signal_new_class_handler (I_("popup"),
274 G_OBJECT_CLASS_TYPE (klass),
275 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
276 G_CALLBACK (gtk_scale_button_popup),
277 NULL, NULL,
278 NULL,
279 G_TYPE_NONE, n_params: 0);
280
281 /**
282 * GtkScaleButton::popdown:
283 * @button: the object which received the signal
284 *
285 * Emitted to dismiss the popup.
286 *
287 * This is a [keybinding signal](class.SignalAction.html).
288 *
289 * The default binding for this signal is <kbd>Escape</kbd>.
290 */
291 signals[POPDOWN] =
292 g_signal_new_class_handler (I_("popdown"),
293 G_OBJECT_CLASS_TYPE (klass),
294 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
295 G_CALLBACK (gtk_scale_button_popdown),
296 NULL, NULL,
297 NULL,
298 G_TYPE_NONE, n_params: 0);
299
300 /* Key bindings */
301 gtk_widget_class_add_binding_signal (widget_class,
302 GDK_KEY_space, mods: 0,
303 signal: "popup",
304 NULL);
305 gtk_widget_class_add_binding_signal (widget_class,
306 GDK_KEY_KP_Space, mods: 0,
307 signal: "popup",
308 NULL);
309 gtk_widget_class_add_binding_signal (widget_class,
310 GDK_KEY_Return, mods: 0,
311 signal: "popup",
312 NULL);
313 gtk_widget_class_add_binding_signal (widget_class,
314 GDK_KEY_ISO_Enter, mods: 0,
315 signal: "popup",
316 NULL);
317 gtk_widget_class_add_binding_signal (widget_class,
318 GDK_KEY_KP_Enter, mods: 0,
319 signal: "popup",
320 NULL);
321 gtk_widget_class_add_binding_signal (widget_class,
322 GDK_KEY_Escape, mods: 0,
323 signal: "popdown",
324 NULL);
325
326 /* Bind class to template
327 */
328 gtk_widget_class_set_template_from_resource (widget_class,
329 resource_name: "/org/gtk/libgtk/ui/gtkscalebutton.ui");
330
331 gtk_widget_class_bind_template_child_private (widget_class, GtkScaleButton, button);
332 gtk_widget_class_bind_template_child_internal_private (widget_class, GtkScaleButton, plus_button);
333 gtk_widget_class_bind_template_child_internal_private (widget_class, GtkScaleButton, minus_button);
334 gtk_widget_class_bind_template_child_private (widget_class, GtkScaleButton, dock);
335 gtk_widget_class_bind_template_child_private (widget_class, GtkScaleButton, box);
336 gtk_widget_class_bind_template_child_private (widget_class, GtkScaleButton, scale);
337
338 gtk_widget_class_bind_template_callback (widget_class, cb_button_clicked);
339 gtk_widget_class_bind_template_callback (widget_class, cb_scale_value_changed);
340 gtk_widget_class_bind_template_callback (widget_class, cb_popup_mapped);
341
342 gtk_widget_class_set_css_name (widget_class, I_("scalebutton"));
343}
344
345static gboolean
346start_autoscroll (gpointer data)
347{
348 GtkScaleButton *button = data;
349 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
350
351 gtk_range_start_autoscroll (GTK_RANGE (priv->scale), scroll_type: priv->autoscroll_step);
352
353 priv->autoscrolling = TRUE;
354 priv->autoscroll_timeout = 0;
355
356 return G_SOURCE_REMOVE;
357}
358
359static void
360button_pressed_cb (GtkGesture *gesture,
361 int n_press,
362 double x,
363 double y,
364 GtkScaleButton *button)
365{
366 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
367 GtkWidget *widget;
368
369 widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
370 if (widget == priv->plus_button)
371 priv->autoscroll_step = GTK_SCROLL_PAGE_FORWARD;
372 else
373 priv->autoscroll_step = GTK_SCROLL_PAGE_BACKWARD;
374 priv->autoscroll_timeout = g_timeout_add (interval: 200, function: start_autoscroll, data: button);
375}
376
377static void
378gtk_scale_button_toggled (GtkScaleButton *button)
379{
380 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
381 gboolean active;
382
383 active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->button));
384
385 if (active)
386 gtk_popover_popup (GTK_POPOVER (priv->dock));
387 else
388 gtk_popover_popdown (GTK_POPOVER (priv->dock));
389}
390
391static void
392gtk_scale_button_closed (GtkScaleButton *button)
393{
394 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
395
396 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), FALSE);
397}
398
399static void
400gtk_scale_button_init (GtkScaleButton *button)
401{
402 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
403 GtkEventController *controller;
404
405 priv->orientation = GTK_ORIENTATION_VERTICAL;
406 priv->applied_orientation = GTK_ORIENTATION_VERTICAL;
407
408 gtk_widget_init_template (GTK_WIDGET (button));
409 gtk_widget_set_parent (widget: priv->dock, GTK_WIDGET (button));
410
411 /* Need a local reference to the adjustment */
412 priv->adjustment = gtk_adjustment_new (value: 0, lower: 0, upper: 100, step_increment: 2, page_increment: 20, page_size: 0);
413 g_object_ref_sink (priv->adjustment);
414 gtk_range_set_adjustment (GTK_RANGE (priv->scale), adjustment: priv->adjustment);
415
416 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: button),
417 first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, gtk_adjustment_get_upper (adjustment: priv->adjustment),
418 GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, gtk_adjustment_get_lower (adjustment: priv->adjustment),
419 GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (adjustment: priv->adjustment),
420 -1);
421
422 gtk_widget_add_css_class (GTK_WIDGET (button), css_class: "scale");
423
424 controller = gtk_event_controller_scroll_new (flags: GTK_EVENT_CONTROLLER_SCROLL_VERTICAL);
425 g_signal_connect (controller, "scroll",
426 G_CALLBACK (gtk_scale_button_scroll_controller_scroll),
427 button);
428 gtk_widget_add_controller (GTK_WIDGET (button), controller);
429
430 g_signal_connect_swapped (priv->dock, "closed",
431 G_CALLBACK (gtk_scale_button_closed), button);
432 g_signal_connect_swapped (priv->button, "toggled",
433 G_CALLBACK (gtk_scale_button_toggled), button);
434
435 g_signal_connect (gtk_button_get_gesture (GTK_BUTTON (priv->plus_button)),
436 "pressed", G_CALLBACK (button_pressed_cb), button);
437 g_signal_connect (gtk_button_get_gesture (GTK_BUTTON (priv->minus_button)),
438 "pressed", G_CALLBACK (button_pressed_cb), button);
439}
440
441static void
442gtk_scale_button_constructed (GObject *object)
443{
444 GtkScaleButton *button = GTK_SCALE_BUTTON (object);
445
446 G_OBJECT_CLASS (gtk_scale_button_parent_class)->constructed (object);
447
448 /* set button text and size */
449 gtk_scale_button_update_icon (button);
450}
451
452static void
453gtk_scale_button_set_property (GObject *object,
454 guint prop_id,
455 const GValue *value,
456 GParamSpec *pspec)
457{
458 GtkScaleButton *button = GTK_SCALE_BUTTON (object);
459
460 switch (prop_id)
461 {
462 case PROP_ORIENTATION:
463 gtk_scale_button_set_orientation_private (button, orientation: g_value_get_enum (value));
464 break;
465 case PROP_VALUE:
466 gtk_scale_button_set_value (button, value: g_value_get_double (value));
467 break;
468 case PROP_ADJUSTMENT:
469 gtk_scale_button_set_adjustment (button, adjustment: g_value_get_object (value));
470 break;
471 case PROP_ICONS:
472 gtk_scale_button_set_icons (button,
473 icons: (const char **)g_value_get_boxed (value));
474 break;
475 default:
476 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
477 break;
478 }
479}
480
481static void
482gtk_scale_button_get_property (GObject *object,
483 guint prop_id,
484 GValue *value,
485 GParamSpec *pspec)
486{
487 GtkScaleButton *button = GTK_SCALE_BUTTON (object);
488 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
489
490 switch (prop_id)
491 {
492 case PROP_ORIENTATION:
493 g_value_set_enum (value, v_enum: priv->orientation);
494 break;
495 case PROP_VALUE:
496 g_value_set_double (value, v_double: gtk_scale_button_get_value (button));
497 break;
498 case PROP_ADJUSTMENT:
499 g_value_set_object (value, v_object: gtk_scale_button_get_adjustment (button));
500 break;
501 case PROP_ICONS:
502 g_value_set_boxed (value, v_boxed: priv->icon_list);
503 break;
504 default:
505 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
506 break;
507 }
508}
509
510static void
511gtk_scale_button_finalize (GObject *object)
512{
513 GtkScaleButton *button = GTK_SCALE_BUTTON (object);
514 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
515
516 g_clear_pointer (&priv->icon_list, g_strfreev);
517 g_clear_object (&priv->adjustment);
518 g_clear_handle_id (&priv->autoscroll_timeout, g_source_remove);
519
520 G_OBJECT_CLASS (gtk_scale_button_parent_class)->finalize (object);
521}
522
523static void
524gtk_scale_button_dispose (GObject *object)
525{
526 GtkScaleButton *button = GTK_SCALE_BUTTON (object);
527 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
528
529 g_clear_pointer (&priv->dock, gtk_widget_unparent);
530 g_clear_pointer (&priv->button, gtk_widget_unparent);
531
532 G_OBJECT_CLASS (gtk_scale_button_parent_class)->dispose (object);
533}
534
535/**
536 * gtk_scale_button_new:
537 * @min: the minimum value of the scale (usually 0)
538 * @max: the maximum value of the scale (usually 100)
539 * @step: the stepping of value when a scroll-wheel event,
540 * or up/down arrow event occurs (usually 2)
541 * @icons: (nullable) (array zero-terminated=1): a %NULL-terminated
542 * array of icon names, or %NULL if you want to set the list
543 * later with gtk_scale_button_set_icons()
544 *
545 * Creates a `GtkScaleButton`.
546 *
547 * The new scale button has a range between @min and @max,
548 * with a stepping of @step.
549 *
550 * Returns: a new `GtkScaleButton`
551 */
552GtkWidget *
553gtk_scale_button_new (double min,
554 double max,
555 double step,
556 const char **icons)
557{
558 GtkScaleButton *button;
559 GtkAdjustment *adjustment;
560
561 adjustment = gtk_adjustment_new (value: min, lower: min, upper: max, step_increment: step, page_increment: 10 * step, page_size: 0);
562
563 button = g_object_new (GTK_TYPE_SCALE_BUTTON,
564 first_property_name: "adjustment", adjustment,
565 "icons", icons,
566 NULL);
567
568 return GTK_WIDGET (button);
569}
570
571/**
572 * gtk_scale_button_get_value: (attributes org.gtk.Method.get_property=value)
573 * @button: a `GtkScaleButton`
574 *
575 * Gets the current value of the scale button.
576 *
577 * Returns: current value of the scale button
578 */
579double
580gtk_scale_button_get_value (GtkScaleButton * button)
581{
582 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
583
584 g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), 0);
585
586 return gtk_adjustment_get_value (adjustment: priv->adjustment);
587}
588
589/**
590 * gtk_scale_button_set_value: (attributes org.gtk.Method.set_property=value)
591 * @button: a `GtkScaleButton`
592 * @value: new value of the scale button
593 *
594 * Sets the current value of the scale.
595 *
596 * If the value is outside the minimum or maximum range values,
597 * it will be clamped to fit inside them.
598 *
599 * The scale button emits the [signal@Gtk.ScaleButton::value-changed]
600 * signal if the value changes.
601 */
602void
603gtk_scale_button_set_value (GtkScaleButton *button,
604 double value)
605{
606 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
607
608 g_return_if_fail (GTK_IS_SCALE_BUTTON (button));
609
610 gtk_range_set_value (GTK_RANGE (priv->scale), value);
611 g_object_notify (G_OBJECT (button), property_name: "value");
612}
613
614/**
615 * gtk_scale_button_set_icons: (attributes org.gtk.Method.set_property=icons)
616 * @button: a `GtkScaleButton`
617 * @icons: (array zero-terminated=1): a %NULL-terminated array of icon names
618 *
619 * Sets the icons to be used by the scale button.
620 */
621void
622gtk_scale_button_set_icons (GtkScaleButton *button,
623 const char **icons)
624{
625 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
626 char **tmp;
627
628 g_return_if_fail (GTK_IS_SCALE_BUTTON (button));
629
630 tmp = priv->icon_list;
631 priv->icon_list = g_strdupv (str_array: (char **) icons);
632 g_strfreev (str_array: tmp);
633 gtk_scale_button_update_icon (button);
634
635 g_object_notify (G_OBJECT (button), property_name: "icons");
636}
637
638/**
639 * gtk_scale_button_get_adjustment: (attributes org.gtk.Method.get_property=adjustment)
640 * @button: a `GtkScaleButton`
641 *
642 * Gets the `GtkAdjustment` associated with the `GtkScaleButton`’s scale.
643 *
644 * See [method@Gtk.Range.get_adjustment] for details.
645 *
646 * Returns: (transfer none): the adjustment associated with the scale
647 */
648GtkAdjustment*
649gtk_scale_button_get_adjustment (GtkScaleButton *button)
650{
651 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
652
653 g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
654
655 return priv->adjustment;
656}
657
658/**
659 * gtk_scale_button_set_adjustment: (attributes org.gtk.Method.set_property=adjustment)
660 * @button: a `GtkScaleButton`
661 * @adjustment: a `GtkAdjustment`
662 *
663 * Sets the `GtkAdjustment` to be used as a model
664 * for the `GtkScaleButton`’s scale.
665 *
666 * See [method@Gtk.Range.set_adjustment] for details.
667 */
668void
669gtk_scale_button_set_adjustment (GtkScaleButton *button,
670 GtkAdjustment *adjustment)
671{
672 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
673
674 g_return_if_fail (GTK_IS_SCALE_BUTTON (button));
675
676 if (!adjustment)
677 adjustment = gtk_adjustment_new (value: 0.0, lower: 0.0, upper: 0.0, step_increment: 0.0, page_increment: 0.0, page_size: 0.0);
678 else
679 g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
680
681 if (priv->adjustment != adjustment)
682 {
683 if (priv->adjustment)
684 g_object_unref (object: priv->adjustment);
685 priv->adjustment = g_object_ref_sink (adjustment);
686
687 if (priv->scale)
688 gtk_range_set_adjustment (GTK_RANGE (priv->scale), adjustment);
689
690 g_object_notify (G_OBJECT (button), property_name: "adjustment");
691
692 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: button),
693 first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, gtk_adjustment_get_upper (adjustment),
694 GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, gtk_adjustment_get_lower (adjustment),
695 GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (adjustment),
696 -1);
697
698 }
699}
700
701/**
702 * gtk_scale_button_get_plus_button:
703 * @button: a `GtkScaleButton`
704 *
705 * Retrieves the plus button of the `GtkScaleButton.`
706 *
707 * Returns: (transfer none) (type Gtk.Button): the plus button
708 * of the `GtkScaleButton`
709 */
710GtkWidget *
711gtk_scale_button_get_plus_button (GtkScaleButton *button)
712{
713 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
714
715 g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
716
717 return priv->plus_button;
718}
719
720/**
721 * gtk_scale_button_get_minus_button:
722 * @button: a `GtkScaleButton`
723 *
724 * Retrieves the minus button of the `GtkScaleButton`.
725 *
726 * Returns: (transfer none) (type Gtk.Button): the minus button
727 * of the `GtkScaleButton`
728 */
729GtkWidget *
730gtk_scale_button_get_minus_button (GtkScaleButton *button)
731{
732 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
733
734 g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
735
736 return priv->minus_button;
737}
738
739/**
740 * gtk_scale_button_get_popup:
741 * @button: a `GtkScaleButton`
742 *
743 * Retrieves the popup of the `GtkScaleButton`.
744 *
745 * Returns: (transfer none): the popup of the `GtkScaleButton`
746 */
747GtkWidget *
748gtk_scale_button_get_popup (GtkScaleButton *button)
749{
750 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
751
752 g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
753
754 return priv->dock;
755}
756
757static void
758gtk_scale_button_set_orientation_private (GtkScaleButton *button,
759 GtkOrientation orientation)
760{
761 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
762
763 if (priv->orientation != orientation)
764 {
765 priv->orientation = orientation;
766 g_object_notify (G_OBJECT (button), property_name: "orientation");
767 }
768}
769
770static gboolean
771gtk_scale_button_scroll_controller_scroll (GtkEventControllerScroll *scroll,
772 double dx,
773 double dy,
774 GtkScaleButton *button)
775{
776 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
777 GtkAdjustment *adjustment;
778 double d;
779
780 adjustment = priv->adjustment;
781
782 d = CLAMP (gtk_scale_button_get_value (button) -
783 (dy * gtk_adjustment_get_step_increment (adjustment)),
784 gtk_adjustment_get_lower (adjustment),
785 gtk_adjustment_get_upper (adjustment));
786
787 gtk_scale_button_set_value (button, value: d);
788
789 return GDK_EVENT_STOP;
790}
791
792/*
793 * button callbacks.
794 */
795
796static void
797gtk_scale_popup (GtkWidget *widget)
798{
799 GtkScaleButton *button = GTK_SCALE_BUTTON (widget);
800 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
801
802 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), TRUE);
803}
804
805static void
806gtk_scale_button_popdown (GtkWidget *widget)
807{
808 GtkScaleButton *button = GTK_SCALE_BUTTON (widget);
809 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
810
811 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), FALSE);
812}
813
814static void
815gtk_scale_button_popup (GtkWidget *widget)
816{
817 gtk_scale_popup (widget);
818}
819
820/*
821 * +/- button callbacks.
822 */
823static gboolean
824button_click (GtkScaleButton *button,
825 GtkWidget *active)
826{
827 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
828 GtkAdjustment *adjustment = priv->adjustment;
829 gboolean can_continue = TRUE;
830 double val;
831
832 val = gtk_scale_button_get_value (button);
833
834 if (active == priv->plus_button)
835 val += gtk_adjustment_get_page_increment (adjustment);
836 else
837 val -= gtk_adjustment_get_page_increment (adjustment);
838
839 if (val <= gtk_adjustment_get_lower (adjustment))
840 {
841 can_continue = FALSE;
842 val = gtk_adjustment_get_lower (adjustment);
843 }
844 else if (val > gtk_adjustment_get_upper (adjustment))
845 {
846 can_continue = FALSE;
847 val = gtk_adjustment_get_upper (adjustment);
848 }
849
850 gtk_scale_button_set_value (button, value: val);
851
852 return can_continue;
853}
854
855static void
856cb_button_clicked (GtkWidget *widget,
857 gpointer user_data)
858{
859 GtkScaleButton *button = GTK_SCALE_BUTTON (user_data);
860 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
861
862 if (priv->autoscroll_timeout)
863 {
864 g_source_remove (tag: priv->autoscroll_timeout);
865 priv->autoscroll_timeout = 0;
866 }
867
868 if (priv->autoscrolling)
869 {
870 gtk_range_stop_autoscroll (GTK_RANGE (priv->scale));
871 priv->autoscrolling = FALSE;
872 return;
873 }
874
875 button_click (button, active: widget);
876}
877
878static void
879gtk_scale_button_update_icon (GtkScaleButton *button)
880{
881 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
882 GtkAdjustment *adjustment;
883 double value;
884 const char *name;
885 guint num_icons;
886
887 if (!priv->icon_list || !priv->icon_list[0] || priv->icon_list[0][0] == '\0')
888 {
889 gtk_button_set_icon_name (GTK_BUTTON (priv->button), icon_name: "image-missing");
890 return;
891 }
892
893 num_icons = g_strv_length (str_array: priv->icon_list);
894
895 /* The 1-icon special case */
896 if (num_icons == 1)
897 {
898 gtk_button_set_icon_name (GTK_BUTTON (priv->button), icon_name: priv->icon_list[0]);
899 return;
900 }
901
902 adjustment = priv->adjustment;
903 value = gtk_scale_button_get_value (button);
904
905 /* The 2-icons special case */
906 if (num_icons == 2)
907 {
908 double limit;
909
910 limit = (gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_lower (adjustment)) / 2 + gtk_adjustment_get_lower (adjustment);
911 if (value < limit)
912 name = priv->icon_list[0];
913 else
914 name = priv->icon_list[1];
915
916 gtk_button_set_icon_name (GTK_BUTTON (priv->button), icon_name: name);
917 return;
918 }
919
920 /* With 3 or more icons */
921 if (value == gtk_adjustment_get_lower (adjustment))
922 {
923 name = priv->icon_list[0];
924 }
925 else if (value == gtk_adjustment_get_upper (adjustment))
926 {
927 name = priv->icon_list[1];
928 }
929 else
930 {
931 double step;
932 guint i;
933
934 step = (gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_lower (adjustment)) / (num_icons - 2); i = (guint) ((value - gtk_adjustment_get_lower (adjustment)) / step) + 2;
935 g_assert (i < num_icons);
936 name = priv->icon_list[i];
937 }
938
939 gtk_button_set_icon_name (GTK_BUTTON (priv->button), icon_name: name);
940}
941
942static void
943cb_scale_value_changed (GtkRange *range,
944 gpointer user_data)
945{
946 GtkScaleButton *button = user_data;
947 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
948 double value;
949 double upper, lower;
950
951 value = gtk_range_get_value (range);
952 upper = gtk_adjustment_get_upper (adjustment: priv->adjustment);
953 lower = gtk_adjustment_get_lower (adjustment: priv->adjustment);
954
955 gtk_scale_button_update_icon (button);
956
957 gtk_widget_set_sensitive (widget: priv->plus_button, sensitive: value < upper);
958 gtk_widget_set_sensitive (widget: priv->minus_button, sensitive: lower < value);
959
960 g_signal_emit (instance: button, signal_id: signals[VALUE_CHANGED], detail: 0, value);
961 g_object_notify (G_OBJECT (button), property_name: "value");
962
963 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: button),
964 first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, value,
965 -1);
966}
967
968static void
969cb_popup_mapped (GtkWidget *popup,
970 gpointer user_data)
971{
972 GtkScaleButton *button = user_data;
973 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
974
975 gtk_widget_grab_focus (widget: priv->scale);
976}
977
978static void
979gtk_scale_button_measure (GtkWidget *widget,
980 GtkOrientation orientation,
981 int for_size,
982 int *minimum,
983 int *natural,
984 int *minimum_baseline,
985 int *natural_baseline)
986{
987 GtkScaleButton *button = GTK_SCALE_BUTTON (widget);
988 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
989
990 gtk_widget_measure (widget: priv->button,
991 orientation,
992 for_size,
993 minimum, natural,
994 minimum_baseline, natural_baseline);
995
996}
997
998static void
999gtk_scale_button_size_allocate (GtkWidget *widget,
1000 int width,
1001 int height,
1002 int baseline)
1003{
1004 GtkScaleButton *button = GTK_SCALE_BUTTON (widget);
1005 GtkScaleButtonPrivate *priv = gtk_scale_button_get_instance_private (self: button);
1006
1007 gtk_widget_size_allocate (widget: priv->button,
1008 allocation: &(GtkAllocation) { 0, 0, width, height },
1009 baseline);
1010
1011 gtk_popover_present (GTK_POPOVER (priv->dock));
1012}
1013

source code of gtk/gtk/gtkscalebutton.c