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 <math.h>
40 #include <stdlib.h>
41 #include <string.h>
42
43 #include "gtkadjustment.h"
44 #include "gtkbindings.h"
45 #include "gtkframe.h"
46 #include "gtkmain.h"
47 #include "gtkmarshalers.h"
48 #include "gtkorientable.h"
49 #include "gtkpopover.h"
50 #include "gtkprivate.h"
51 #include "gtkscale.h"
52 #include "gtkbox.h"
53 #include "gtkwindow.h"
54 #include "gtkwindowprivate.h"
55 #include "gtktypebuiltins.h"
56 #include "gtkintl.h"
57 #include "a11y/gtkscalebuttonaccessible.h"
58
59 /**
60 * SECTION:gtkscalebutton
61 * @Short_description: A button which pops up a scale
62 * @Title: GtkScaleButton
63 *
64 * #GtkScaleButton provides a button which pops up a scale widget.
65 * This kind of widget is commonly used for volume controls in multimedia
66 * applications, and GTK+ provides a #GtkVolumeButton subclass that
67 * is tailored for this use case.
68 *
69 * # CSS nodes
70 *
71 * GtkScaleButton has a single CSS node with name button. To differentiate
72 * it from a plain #GtkButton, it gets the .scale style class.
73 *
74 * The popup widget that contains the scale has a .scale-popup style class.
75 */
76
77
78 #define SCALE_SIZE 100
79 #define CLICK_TIMEOUT 250
80
81 enum
82 {
83 VALUE_CHANGED,
84 POPUP,
85 POPDOWN,
86
87 LAST_SIGNAL
88 };
89
90 enum
91 {
92 PROP_0,
93
94 PROP_ORIENTATION,
95 PROP_VALUE,
96 PROP_SIZE,
97 PROP_ADJUSTMENT,
98 PROP_ICONS
99 };
100
101 struct _GtkScaleButtonPrivate
102 {
103 GtkWidget *plus_button;
104 GtkWidget *minus_button;
105 GtkWidget *dock;
106 GtkWidget *box;
107 GtkWidget *scale;
108 GtkWidget *image;
109 GtkWidget *active_button;
110
111 GtkIconSize size;
112 GtkOrientation orientation;
113 GtkOrientation applied_orientation;
114
115 guint click_id;
116
117 gchar **icon_list;
118
119 GtkAdjustment *adjustment; /* needed because it must be settable in init() */
120 };
121
122 static void gtk_scale_button_constructed (GObject *object);
123 static void gtk_scale_button_dispose (GObject *object);
124 static void gtk_scale_button_finalize (GObject *object);
125 static void gtk_scale_button_set_property (GObject *object,
126 guint prop_id,
127 const GValue *value,
128 GParamSpec *pspec);
129 static void gtk_scale_button_get_property (GObject *object,
130 guint prop_id,
131 GValue *value,
132 GParamSpec *pspec);
133 static void gtk_scale_button_set_orientation_private (GtkScaleButton *button,
134 GtkOrientation orientation);
135 static gboolean gtk_scale_button_scroll (GtkWidget *widget,
136 GdkEventScroll *event);
137 static void gtk_scale_button_clicked (GtkButton *button);
138 static void gtk_scale_button_popup (GtkWidget *widget);
139 static void gtk_scale_button_popdown (GtkWidget *widget);
140 static gboolean cb_button_press (GtkWidget *widget,
141 GdkEventButton *event,
142 gpointer user_data);
143 static gboolean cb_button_release (GtkWidget *widget,
144 GdkEventButton *event,
145 gpointer user_data);
146 static void cb_button_clicked (GtkWidget *button,
147 gpointer user_data);
148 static void gtk_scale_button_update_icon (GtkScaleButton *button);
149 static void cb_scale_value_changed (GtkRange *range,
150 gpointer user_data);
151 static void cb_popup_mapped (GtkWidget *popup,
152 gpointer user_data);
153
154 G_DEFINE_TYPE_WITH_CODE (GtkScaleButton, gtk_scale_button, GTK_TYPE_BUTTON,
155 G_ADD_PRIVATE (GtkScaleButton)
156 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,
157 NULL))
158
159 static guint signals[LAST_SIGNAL] = { 0, };
160
161 static void
gtk_scale_button_class_init(GtkScaleButtonClass * klass)162 gtk_scale_button_class_init (GtkScaleButtonClass *klass)
163 {
164 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
165 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
166 GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass);
167 GtkBindingSet *binding_set;
168
169 gobject_class->constructed = gtk_scale_button_constructed;
170 gobject_class->finalize = gtk_scale_button_finalize;
171 gobject_class->dispose = gtk_scale_button_dispose;
172 gobject_class->set_property = gtk_scale_button_set_property;
173 gobject_class->get_property = gtk_scale_button_get_property;
174
175 widget_class->scroll_event = gtk_scale_button_scroll;
176
177 button_class->clicked = gtk_scale_button_clicked;
178
179 /**
180 * GtkScaleButton:orientation:
181 *
182 * The orientation of the #GtkScaleButton's popup window.
183 *
184 * Note that since GTK+ 2.16, #GtkScaleButton implements the
185 * #GtkOrientable interface which has its own @orientation
186 * property. However we redefine the property here in order to
187 * override its default horizontal orientation.
188 *
189 * Since: 2.14
190 **/
191 g_object_class_override_property (gobject_class,
192 PROP_ORIENTATION,
193 "orientation");
194
195 g_object_class_install_property (gobject_class,
196 PROP_VALUE,
197 g_param_spec_double ("value",
198 P_("Value"),
199 P_("The value of the scale"),
200 -G_MAXDOUBLE,
201 G_MAXDOUBLE,
202 0,
203 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
204
205 g_object_class_install_property (gobject_class,
206 PROP_SIZE,
207 g_param_spec_enum ("size",
208 P_("Icon size"),
209 P_("The icon size"),
210 GTK_TYPE_ICON_SIZE,
211 GTK_ICON_SIZE_SMALL_TOOLBAR,
212 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY));
213
214 g_object_class_install_property (gobject_class,
215 PROP_ADJUSTMENT,
216 g_param_spec_object ("adjustment",
217 P_("Adjustment"),
218 P_("The GtkAdjustment that contains the current value of this scale button object"),
219 GTK_TYPE_ADJUSTMENT,
220 GTK_PARAM_READWRITE));
221
222 /**
223 * GtkScaleButton:icons:
224 *
225 * The names of the icons to be used by the scale button.
226 * The first item in the array will be used in the button
227 * when the current value is the lowest value, the second
228 * item for the highest value. All the subsequent icons will
229 * be used for all the other values, spread evenly over the
230 * range of values.
231 *
232 * If there's only one icon name in the @icons array, it will
233 * be used for all the values. If only two icon names are in
234 * the @icons array, the first one will be used for the bottom
235 * 50% of the scale, and the second one for the top 50%.
236 *
237 * It is recommended to use at least 3 icons so that the
238 * #GtkScaleButton reflects the current value of the scale
239 * better for the users.
240 *
241 * Since: 2.12
242 */
243 g_object_class_install_property (gobject_class,
244 PROP_ICONS,
245 g_param_spec_boxed ("icons",
246 P_("Icons"),
247 P_("List of icon names"),
248 G_TYPE_STRV,
249 GTK_PARAM_READWRITE));
250
251 /**
252 * GtkScaleButton::value-changed:
253 * @button: the object which received the signal
254 * @value: the new value
255 *
256 * The ::value-changed signal is emitted when the value field has
257 * changed.
258 *
259 * Since: 2.12
260 */
261 signals[VALUE_CHANGED] =
262 g_signal_new (I_("value-changed"),
263 G_TYPE_FROM_CLASS (klass),
264 G_SIGNAL_RUN_LAST,
265 G_STRUCT_OFFSET (GtkScaleButtonClass, value_changed),
266 NULL, NULL,
267 NULL,
268 G_TYPE_NONE, 1, G_TYPE_DOUBLE);
269
270 /**
271 * GtkScaleButton::popup:
272 * @button: the object which received the signal
273 *
274 * The ::popup signal is a
275 * [keybinding signal][GtkBindingSignal]
276 * which gets emitted to popup the scale widget.
277 *
278 * The default bindings for this signal are Space, Enter and Return.
279 *
280 * Since: 2.12
281 */
282 signals[POPUP] =
283 g_signal_new_class_handler (I_("popup"),
284 G_OBJECT_CLASS_TYPE (klass),
285 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
286 G_CALLBACK (gtk_scale_button_popup),
287 NULL, NULL,
288 NULL,
289 G_TYPE_NONE, 0);
290
291 /**
292 * GtkScaleButton::popdown:
293 * @button: the object which received the signal
294 *
295 * The ::popdown signal is a
296 * [keybinding signal][GtkBindingSignal]
297 * which gets emitted to popdown the scale widget.
298 *
299 * The default binding for this signal is Escape.
300 *
301 * Since: 2.12
302 */
303 signals[POPDOWN] =
304 g_signal_new_class_handler (I_("popdown"),
305 G_OBJECT_CLASS_TYPE (klass),
306 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
307 G_CALLBACK (gtk_scale_button_popdown),
308 NULL, NULL,
309 NULL,
310 G_TYPE_NONE, 0);
311
312 /* Key bindings */
313 binding_set = gtk_binding_set_by_class (widget_class);
314
315 gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, 0,
316 "popup", 0);
317 gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Space, 0,
318 "popup", 0);
319 gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0,
320 "popup", 0);
321 gtk_binding_entry_add_signal (binding_set, GDK_KEY_ISO_Enter, 0,
322 "popup", 0);
323 gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0,
324 "popup", 0);
325 gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0,
326 "popdown", 0);
327
328 /* Bind class to template
329 */
330 gtk_widget_class_set_template_from_resource (widget_class,
331 "/org/gtk/libgtk/ui/gtkscalebutton.ui");
332
333 gtk_widget_class_bind_template_child_internal_private (widget_class, GtkScaleButton, plus_button);
334 gtk_widget_class_bind_template_child_internal_private (widget_class, GtkScaleButton, minus_button);
335 gtk_widget_class_bind_template_child_private (widget_class, GtkScaleButton, dock);
336 gtk_widget_class_bind_template_child_private (widget_class, GtkScaleButton, box);
337 gtk_widget_class_bind_template_child_private (widget_class, GtkScaleButton, scale);
338 gtk_widget_class_bind_template_child_private (widget_class, GtkScaleButton, image);
339
340 gtk_widget_class_bind_template_callback (widget_class, cb_button_press);
341 gtk_widget_class_bind_template_callback (widget_class, cb_button_release);
342 gtk_widget_class_bind_template_callback (widget_class, cb_button_clicked);
343 gtk_widget_class_bind_template_callback (widget_class, cb_scale_value_changed);
344 gtk_widget_class_bind_template_callback (widget_class, cb_popup_mapped);
345
346 gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SCALE_BUTTON_ACCESSIBLE);
347 gtk_widget_class_set_css_name (widget_class, "button");
348 }
349
350 static void
gtk_scale_button_init(GtkScaleButton * button)351 gtk_scale_button_init (GtkScaleButton *button)
352 {
353 GtkScaleButtonPrivate *priv;
354 GtkStyleContext *context;
355
356 button->priv = priv = gtk_scale_button_get_instance_private (button);
357
358 priv->click_id = 0;
359 priv->orientation = GTK_ORIENTATION_VERTICAL;
360 priv->applied_orientation = GTK_ORIENTATION_VERTICAL;
361
362 gtk_widget_init_template (GTK_WIDGET (button));
363 gtk_popover_set_relative_to (GTK_POPOVER (priv->dock), GTK_WIDGET (button));
364
365 /* Need a local reference to the adjustment */
366 priv->adjustment = gtk_adjustment_new (0, 0, 100, 2, 20, 0);
367 g_object_ref_sink (priv->adjustment);
368 gtk_range_set_adjustment (GTK_RANGE (priv->scale), priv->adjustment);
369
370 gtk_widget_add_events (GTK_WIDGET (button), GDK_SMOOTH_SCROLL_MASK);
371
372 context = gtk_widget_get_style_context (GTK_WIDGET (button));
373 gtk_style_context_add_class (context, "scale");
374 }
375
376 static void
gtk_scale_button_constructed(GObject * object)377 gtk_scale_button_constructed (GObject *object)
378 {
379 GtkScaleButton *button = GTK_SCALE_BUTTON (object);
380 GtkScaleButtonPrivate *priv = button->priv;
381
382 G_OBJECT_CLASS (gtk_scale_button_parent_class)->constructed (object);
383
384 /* set button text and size */
385 priv->size = GTK_ICON_SIZE_SMALL_TOOLBAR;
386 gtk_scale_button_update_icon (button);
387 }
388
389 static void
gtk_scale_button_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)390 gtk_scale_button_set_property (GObject *object,
391 guint prop_id,
392 const GValue *value,
393 GParamSpec *pspec)
394 {
395 GtkScaleButton *button = GTK_SCALE_BUTTON (object);
396
397 switch (prop_id)
398 {
399 case PROP_ORIENTATION:
400 gtk_scale_button_set_orientation_private (button, g_value_get_enum (value));
401 break;
402 case PROP_VALUE:
403 gtk_scale_button_set_value (button, g_value_get_double (value));
404 break;
405 case PROP_SIZE:
406 if (button->priv->size != g_value_get_enum (value))
407 {
408 button->priv->size = g_value_get_enum (value);
409 gtk_scale_button_update_icon (button);
410 g_object_notify_by_pspec (object, pspec);
411 }
412 break;
413 case PROP_ADJUSTMENT:
414 gtk_scale_button_set_adjustment (button, g_value_get_object (value));
415 break;
416 case PROP_ICONS:
417 gtk_scale_button_set_icons (button,
418 (const gchar **)g_value_get_boxed (value));
419 break;
420 default:
421 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
422 break;
423 }
424 }
425
426 static void
gtk_scale_button_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)427 gtk_scale_button_get_property (GObject *object,
428 guint prop_id,
429 GValue *value,
430 GParamSpec *pspec)
431 {
432 GtkScaleButton *button = GTK_SCALE_BUTTON (object);
433 GtkScaleButtonPrivate *priv = button->priv;
434
435 switch (prop_id)
436 {
437 case PROP_ORIENTATION:
438 g_value_set_enum (value, priv->orientation);
439 break;
440 case PROP_VALUE:
441 g_value_set_double (value, gtk_scale_button_get_value (button));
442 break;
443 case PROP_SIZE:
444 g_value_set_enum (value, priv->size);
445 break;
446 case PROP_ADJUSTMENT:
447 g_value_set_object (value, gtk_scale_button_get_adjustment (button));
448 break;
449 case PROP_ICONS:
450 g_value_set_boxed (value, priv->icon_list);
451 break;
452 default:
453 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
454 break;
455 }
456 }
457
458 static void
gtk_scale_button_finalize(GObject * object)459 gtk_scale_button_finalize (GObject *object)
460 {
461 GtkScaleButton *button = GTK_SCALE_BUTTON (object);
462 GtkScaleButtonPrivate *priv = button->priv;
463
464 if (priv->icon_list)
465 {
466 g_strfreev (priv->icon_list);
467 priv->icon_list = NULL;
468 }
469
470 if (priv->adjustment)
471 {
472 g_object_unref (priv->adjustment);
473 priv->adjustment = NULL;
474 }
475
476 G_OBJECT_CLASS (gtk_scale_button_parent_class)->finalize (object);
477 }
478
479 static void
gtk_scale_button_dispose(GObject * object)480 gtk_scale_button_dispose (GObject *object)
481 {
482 GtkScaleButton *button = GTK_SCALE_BUTTON (object);
483 GtkScaleButtonPrivate *priv = button->priv;
484
485 if (priv->dock)
486 {
487 gtk_widget_destroy (priv->dock);
488 priv->dock = NULL;
489 }
490
491 if (priv->click_id != 0)
492 {
493 g_source_remove (priv->click_id);
494 priv->click_id = 0;
495 }
496
497 G_OBJECT_CLASS (gtk_scale_button_parent_class)->dispose (object);
498 }
499
500 /**
501 * gtk_scale_button_new:
502 * @size: (type int): a stock icon size (#GtkIconSize)
503 * @min: the minimum value of the scale (usually 0)
504 * @max: the maximum value of the scale (usually 100)
505 * @step: the stepping of value when a scroll-wheel event,
506 * or up/down arrow event occurs (usually 2)
507 * @icons: (allow-none) (array zero-terminated=1): a %NULL-terminated
508 * array of icon names, or %NULL if you want to set the list
509 * later with gtk_scale_button_set_icons()
510 *
511 * Creates a #GtkScaleButton, with a range between @min and @max, with
512 * a stepping of @step.
513 *
514 * Returns: a new #GtkScaleButton
515 *
516 * Since: 2.12
517 */
518 GtkWidget *
gtk_scale_button_new(GtkIconSize size,gdouble min,gdouble max,gdouble step,const gchar ** icons)519 gtk_scale_button_new (GtkIconSize size,
520 gdouble min,
521 gdouble max,
522 gdouble step,
523 const gchar **icons)
524 {
525 GtkScaleButton *button;
526 GtkAdjustment *adjustment;
527
528 adjustment = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
529
530 button = g_object_new (GTK_TYPE_SCALE_BUTTON,
531 "adjustment", adjustment,
532 "icons", icons,
533 "size", size,
534 NULL);
535
536 return GTK_WIDGET (button);
537 }
538
539 /**
540 * gtk_scale_button_get_value:
541 * @button: a #GtkScaleButton
542 *
543 * Gets the current value of the scale button.
544 *
545 * Returns: current value of the scale button
546 *
547 * Since: 2.12
548 */
549 gdouble
gtk_scale_button_get_value(GtkScaleButton * button)550 gtk_scale_button_get_value (GtkScaleButton * button)
551 {
552 GtkScaleButtonPrivate *priv;
553
554 g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), 0);
555
556 priv = button->priv;
557
558 return gtk_adjustment_get_value (priv->adjustment);
559 }
560
561 /**
562 * gtk_scale_button_set_value:
563 * @button: a #GtkScaleButton
564 * @value: new value of the scale button
565 *
566 * Sets the current value of the scale; if the value is outside
567 * the minimum or maximum range values, it will be clamped to fit
568 * inside them. The scale button emits the #GtkScaleButton::value-changed
569 * signal if the value changes.
570 *
571 * Since: 2.12
572 */
573 void
gtk_scale_button_set_value(GtkScaleButton * button,gdouble value)574 gtk_scale_button_set_value (GtkScaleButton *button,
575 gdouble value)
576 {
577 GtkScaleButtonPrivate *priv;
578
579 g_return_if_fail (GTK_IS_SCALE_BUTTON (button));
580
581 priv = button->priv;
582
583 gtk_range_set_value (GTK_RANGE (priv->scale), value);
584 g_object_notify (G_OBJECT (button), "value");
585 }
586
587 /**
588 * gtk_scale_button_set_icons:
589 * @button: a #GtkScaleButton
590 * @icons: (array zero-terminated=1): a %NULL-terminated array of icon names
591 *
592 * Sets the icons to be used by the scale button.
593 * For details, see the #GtkScaleButton:icons property.
594 *
595 * Since: 2.12
596 */
597 void
gtk_scale_button_set_icons(GtkScaleButton * button,const gchar ** icons)598 gtk_scale_button_set_icons (GtkScaleButton *button,
599 const gchar **icons)
600 {
601 GtkScaleButtonPrivate *priv;
602 gchar **tmp;
603
604 g_return_if_fail (GTK_IS_SCALE_BUTTON (button));
605
606 priv = button->priv;
607
608 tmp = priv->icon_list;
609 priv->icon_list = g_strdupv ((gchar **) icons);
610 g_strfreev (tmp);
611 gtk_scale_button_update_icon (button);
612
613 g_object_notify (G_OBJECT (button), "icons");
614 }
615
616 /**
617 * gtk_scale_button_get_adjustment:
618 * @button: a #GtkScaleButton
619 *
620 * Gets the #GtkAdjustment associated with the #GtkScaleButton’s scale.
621 * See gtk_range_get_adjustment() for details.
622 *
623 * Returns: (transfer none): the adjustment associated with the scale
624 *
625 * Since: 2.12
626 */
627 GtkAdjustment*
gtk_scale_button_get_adjustment(GtkScaleButton * button)628 gtk_scale_button_get_adjustment (GtkScaleButton *button)
629 {
630 g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
631
632 return button->priv->adjustment;
633 }
634
635 /**
636 * gtk_scale_button_set_adjustment:
637 * @button: a #GtkScaleButton
638 * @adjustment: a #GtkAdjustment
639 *
640 * Sets the #GtkAdjustment to be used as a model
641 * for the #GtkScaleButton’s scale.
642 * See gtk_range_set_adjustment() for details.
643 *
644 * Since: 2.12
645 */
646 void
gtk_scale_button_set_adjustment(GtkScaleButton * button,GtkAdjustment * adjustment)647 gtk_scale_button_set_adjustment (GtkScaleButton *button,
648 GtkAdjustment *adjustment)
649 {
650 g_return_if_fail (GTK_IS_SCALE_BUTTON (button));
651
652 if (!adjustment)
653 adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
654 else
655 g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
656
657 if (button->priv->adjustment != adjustment)
658 {
659 if (button->priv->adjustment)
660 g_object_unref (button->priv->adjustment);
661 button->priv->adjustment = g_object_ref_sink (adjustment);
662
663 if (button->priv->scale)
664 gtk_range_set_adjustment (GTK_RANGE (button->priv->scale), adjustment);
665
666 g_object_notify (G_OBJECT (button), "adjustment");
667 }
668 }
669
670 /**
671 * gtk_scale_button_get_plus_button:
672 * @button: a #GtkScaleButton
673 *
674 * Retrieves the plus button of the #GtkScaleButton.
675 *
676 * Returns: (transfer none) (type Gtk.Button): the plus button of the #GtkScaleButton as a #GtkButton
677 *
678 * Since: 2.14
679 */
680 GtkWidget *
gtk_scale_button_get_plus_button(GtkScaleButton * button)681 gtk_scale_button_get_plus_button (GtkScaleButton *button)
682 {
683 g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
684
685 return button->priv->plus_button;
686 }
687
688 /**
689 * gtk_scale_button_get_minus_button:
690 * @button: a #GtkScaleButton
691 *
692 * Retrieves the minus button of the #GtkScaleButton.
693 *
694 * Returns: (transfer none) (type Gtk.Button): the minus button of the #GtkScaleButton as a #GtkButton
695 *
696 * Since: 2.14
697 */
698 GtkWidget *
gtk_scale_button_get_minus_button(GtkScaleButton * button)699 gtk_scale_button_get_minus_button (GtkScaleButton *button)
700 {
701 g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
702
703 return button->priv->minus_button;
704 }
705
706 /**
707 * gtk_scale_button_get_popup:
708 * @button: a #GtkScaleButton
709 *
710 * Retrieves the popup of the #GtkScaleButton.
711 *
712 * Returns: (transfer none): the popup of the #GtkScaleButton
713 *
714 * Since: 2.14
715 */
716 GtkWidget *
gtk_scale_button_get_popup(GtkScaleButton * button)717 gtk_scale_button_get_popup (GtkScaleButton *button)
718 {
719 g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
720
721 return button->priv->dock;
722 }
723
724 static void
apply_orientation(GtkScaleButton * button,GtkOrientation orientation)725 apply_orientation (GtkScaleButton *button,
726 GtkOrientation orientation)
727 {
728 GtkScaleButtonPrivate *priv = button->priv;
729
730 if (priv->applied_orientation != orientation)
731 {
732 priv->applied_orientation = orientation;
733 gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->box), orientation);
734 gtk_container_child_set (GTK_CONTAINER (priv->box),
735 priv->plus_button,
736 "pack-type",
737 orientation == GTK_ORIENTATION_VERTICAL ?
738 GTK_PACK_START : GTK_PACK_END,
739 NULL);
740 gtk_container_child_set (GTK_CONTAINER (priv->box),
741 priv->minus_button,
742 "pack-type",
743 orientation == GTK_ORIENTATION_VERTICAL ?
744 GTK_PACK_END : GTK_PACK_START,
745 NULL);
746
747 gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->scale), orientation);
748
749 if (orientation == GTK_ORIENTATION_VERTICAL)
750 {
751 gtk_widget_set_size_request (GTK_WIDGET (priv->scale), -1, SCALE_SIZE);
752 gtk_range_set_inverted (GTK_RANGE (priv->scale), TRUE);
753 }
754 else
755 {
756 gtk_widget_set_size_request (GTK_WIDGET (priv->scale), SCALE_SIZE, -1);
757 gtk_range_set_inverted (GTK_RANGE (priv->scale), FALSE);
758 }
759 }
760 }
761
762 static void
gtk_scale_button_set_orientation_private(GtkScaleButton * button,GtkOrientation orientation)763 gtk_scale_button_set_orientation_private (GtkScaleButton *button,
764 GtkOrientation orientation)
765 {
766 GtkScaleButtonPrivate *priv = button->priv;
767
768 if (priv->orientation != orientation)
769 {
770 priv->orientation = orientation;
771 g_object_notify (G_OBJECT (button), "orientation");
772 }
773 }
774
775 /*
776 * button callbacks.
777 */
778
779 static gboolean
gtk_scale_button_scroll(GtkWidget * widget,GdkEventScroll * event)780 gtk_scale_button_scroll (GtkWidget *widget,
781 GdkEventScroll *event)
782 {
783 GtkScaleButton *button;
784 GtkScaleButtonPrivate *priv;
785 GtkAdjustment *adjustment;
786 gdouble d;
787
788 button = GTK_SCALE_BUTTON (widget);
789 priv = button->priv;
790 adjustment = priv->adjustment;
791
792 if (event->type != GDK_SCROLL)
793 return FALSE;
794
795 d = gtk_scale_button_get_value (button);
796 if (event->direction == GDK_SCROLL_UP)
797 {
798 d += gtk_adjustment_get_step_increment (adjustment);
799 if (d > gtk_adjustment_get_upper (adjustment))
800 d = gtk_adjustment_get_upper (adjustment);
801 }
802 else if (event->direction == GDK_SCROLL_DOWN)
803 {
804 d -= gtk_adjustment_get_step_increment (adjustment);
805 if (d < gtk_adjustment_get_lower (adjustment))
806 d = gtk_adjustment_get_lower (adjustment);
807 }
808 else if (event->direction == GDK_SCROLL_SMOOTH)
809 {
810 d -= event->delta_y * gtk_adjustment_get_step_increment (adjustment);
811 d = CLAMP (d, gtk_adjustment_get_lower (adjustment),
812 gtk_adjustment_get_upper (adjustment));
813 }
814 gtk_scale_button_set_value (button, d);
815
816 return TRUE;
817 }
818
819 static gboolean
gtk_scale_popup(GtkWidget * widget)820 gtk_scale_popup (GtkWidget *widget)
821 {
822 GtkScaleButton *button = GTK_SCALE_BUTTON (widget);
823 GtkScaleButtonPrivate *priv = button->priv;
824 GtkWidget *toplevel;
825 GtkBorder border;
826 GtkRequisition req;
827 gint w, h;
828 gint size;
829
830 gtk_popover_popup (GTK_POPOVER (priv->dock));
831
832 toplevel = gtk_widget_get_toplevel (widget);
833 _gtk_window_get_shadow_width (GTK_WINDOW (toplevel), &border);
834 w = gtk_widget_get_allocated_width (toplevel) - border.left - border.right;
835 h = gtk_widget_get_allocated_height (toplevel) - border.top - border.bottom;
836 gtk_widget_get_preferred_size (priv->dock, NULL, &req);
837 size = MAX (req.width, req.height);
838
839 if (size > w)
840 apply_orientation (button, GTK_ORIENTATION_VERTICAL);
841 else if (size > h)
842 apply_orientation (button, GTK_ORIENTATION_HORIZONTAL);
843 else
844 apply_orientation (button, priv->orientation);
845
846 return TRUE;
847 }
848
849 static void
gtk_scale_button_popdown(GtkWidget * widget)850 gtk_scale_button_popdown (GtkWidget *widget)
851 {
852 GtkScaleButton *button = GTK_SCALE_BUTTON (widget);
853 GtkScaleButtonPrivate *priv = button->priv;
854
855 gtk_popover_popdown (GTK_POPOVER (priv->dock));
856 }
857
858 static void
gtk_scale_button_clicked(GtkButton * button)859 gtk_scale_button_clicked (GtkButton *button)
860 {
861 gtk_scale_popup (GTK_WIDGET (button));
862 }
863
864 static void
gtk_scale_button_popup(GtkWidget * widget)865 gtk_scale_button_popup (GtkWidget *widget)
866 {
867 gtk_scale_popup (widget);
868 }
869
870 /*
871 * +/- button callbacks.
872 */
873 static gboolean
button_click(GtkScaleButton * button,GtkWidget * active)874 button_click (GtkScaleButton *button,
875 GtkWidget *active)
876 {
877 GtkScaleButtonPrivate *priv = button->priv;
878 GtkAdjustment *adjustment = priv->adjustment;
879 gboolean can_continue = TRUE;
880 gdouble val;
881
882 val = gtk_scale_button_get_value (button);
883
884 if (active == priv->plus_button)
885 val += gtk_adjustment_get_page_increment (adjustment);
886 else
887 val -= gtk_adjustment_get_page_increment (adjustment);
888
889 if (val <= gtk_adjustment_get_lower (adjustment))
890 {
891 can_continue = FALSE;
892 val = gtk_adjustment_get_lower (adjustment);
893 }
894 else if (val > gtk_adjustment_get_upper (adjustment))
895 {
896 can_continue = FALSE;
897 val = gtk_adjustment_get_upper (adjustment);
898 }
899
900 gtk_scale_button_set_value (button, val);
901
902 return can_continue;
903 }
904
905 static gboolean
cb_button_timeout(gpointer user_data)906 cb_button_timeout (gpointer user_data)
907 {
908 GtkScaleButton *button = GTK_SCALE_BUTTON (user_data);
909 GtkScaleButtonPrivate *priv = button->priv;
910 gboolean res;
911
912 if (priv->click_id == 0)
913 return G_SOURCE_REMOVE;
914
915 res = button_click (button, priv->active_button);
916 if (!res)
917 {
918 g_source_remove (priv->click_id);
919 priv->click_id = 0;
920 }
921
922 return res;
923 }
924
925 static gboolean
cb_button_press(GtkWidget * widget,GdkEventButton * event,gpointer user_data)926 cb_button_press (GtkWidget *widget,
927 GdkEventButton *event,
928 gpointer user_data)
929 {
930 GtkScaleButton *button = GTK_SCALE_BUTTON (user_data);
931 GtkScaleButtonPrivate *priv = button->priv;
932 gint double_click_time;
933
934 if (priv->click_id != 0)
935 g_source_remove (priv->click_id);
936
937 priv->active_button = widget;
938
939 g_object_get (gtk_widget_get_settings (widget),
940 "gtk-double-click-time", &double_click_time,
941 NULL);
942 priv->click_id = gdk_threads_add_timeout (double_click_time,
943 cb_button_timeout,
944 button);
945 g_source_set_name_by_id (priv->click_id, "[gtk+] cb_button_timeout");
946 cb_button_timeout (button);
947
948 return TRUE;
949 }
950
951 static gboolean
cb_button_release(GtkWidget * widget,GdkEventButton * event,gpointer user_data)952 cb_button_release (GtkWidget *widget,
953 GdkEventButton *event,
954 gpointer user_data)
955 {
956 GtkScaleButton *button = GTK_SCALE_BUTTON (user_data);
957 GtkScaleButtonPrivate *priv = button->priv;
958
959 if (priv->click_id != 0)
960 {
961 g_source_remove (priv->click_id);
962 priv->click_id = 0;
963 }
964
965 return TRUE;
966 }
967
968 static void
cb_button_clicked(GtkWidget * widget,gpointer user_data)969 cb_button_clicked (GtkWidget *widget,
970 gpointer user_data)
971 {
972 GtkScaleButton *button = GTK_SCALE_BUTTON (user_data);
973 GtkScaleButtonPrivate *priv = button->priv;
974
975 if (priv->click_id != 0)
976 return;
977
978 button_click (button, widget);
979 }
980
981 static void
gtk_scale_button_update_icon(GtkScaleButton * button)982 gtk_scale_button_update_icon (GtkScaleButton *button)
983 {
984 GtkScaleButtonPrivate *priv = button->priv;
985 GtkAdjustment *adjustment;
986 gdouble value;
987 const gchar *name;
988 guint num_icons;
989
990 if (!priv->icon_list || priv->icon_list[0][0] == '\0')
991 {
992 gtk_image_set_from_icon_name (GTK_IMAGE (priv->image),
993 "image-missing",
994 priv->size);
995 return;
996 }
997
998 num_icons = g_strv_length (priv->icon_list);
999
1000 /* The 1-icon special case */
1001 if (num_icons == 1)
1002 {
1003 gtk_image_set_from_icon_name (GTK_IMAGE (priv->image),
1004 priv->icon_list[0],
1005 priv->size);
1006 return;
1007 }
1008
1009 adjustment = priv->adjustment;
1010 value = gtk_scale_button_get_value (button);
1011
1012 /* The 2-icons special case */
1013 if (num_icons == 2)
1014 {
1015 gdouble limit;
1016
1017 limit = (gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_lower (adjustment)) / 2 + gtk_adjustment_get_lower (adjustment);
1018 if (value < limit)
1019 name = priv->icon_list[0];
1020 else
1021 name = priv->icon_list[1];
1022
1023 gtk_image_set_from_icon_name (GTK_IMAGE (priv->image),
1024 name,
1025 priv->size);
1026 return;
1027 }
1028
1029 /* With 3 or more icons */
1030 if (value == gtk_adjustment_get_lower (adjustment))
1031 {
1032 name = priv->icon_list[0];
1033 }
1034 else if (value == gtk_adjustment_get_upper (adjustment))
1035 {
1036 name = priv->icon_list[1];
1037 }
1038 else
1039 {
1040 gdouble step;
1041 guint i;
1042
1043 step = (gtk_adjustment_get_upper (adjustment) - gtk_adjustment_get_lower (adjustment)) / (num_icons - 2); i = (guint) ((value - gtk_adjustment_get_lower (adjustment)) / step) + 2;
1044 g_assert (i < num_icons);
1045 name = priv->icon_list[i];
1046 }
1047
1048 gtk_image_set_from_icon_name (GTK_IMAGE (priv->image),
1049 name,
1050 priv->size);
1051 }
1052
1053 static void
cb_scale_value_changed(GtkRange * range,gpointer user_data)1054 cb_scale_value_changed (GtkRange *range,
1055 gpointer user_data)
1056 {
1057 GtkScaleButton *button = user_data;
1058 gdouble value;
1059 gdouble upper, lower;
1060
1061 value = gtk_range_get_value (range);
1062 upper = gtk_adjustment_get_upper (button->priv->adjustment);
1063 lower = gtk_adjustment_get_lower (button->priv->adjustment);
1064
1065 gtk_scale_button_update_icon (button);
1066
1067 gtk_widget_set_sensitive (button->priv->plus_button, value < upper);
1068 gtk_widget_set_sensitive (button->priv->minus_button, lower < value);
1069
1070 g_signal_emit (button, signals[VALUE_CHANGED], 0, value);
1071 g_object_notify (G_OBJECT (button), "value");
1072 }
1073
1074 static void
cb_popup_mapped(GtkWidget * popup,gpointer user_data)1075 cb_popup_mapped (GtkWidget *popup,
1076 gpointer user_data)
1077 {
1078 GtkScaleButton *button = user_data;
1079 GtkScaleButtonPrivate *priv = button->priv;
1080
1081 gtk_widget_grab_focus (priv->scale);
1082 }
1083