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