]> Pileus Git - ~andy/gtk/blob - gtk/gtkscalebutton.c
f994d6e92f07ebe207c9f897d584b333e2c7a4a8
[~andy/gtk] / gtk / gtkscalebutton.c
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  *
7  * Authors:
8  * - Ronald S. Bultje <rbultje@ronald.bitfreak.net>
9  * - Bastien Nocera <bnocera@redhat.com>
10  * - Jan Arne Petersen <jpetersen@jpetersen.org>
11  * - Christian Persch <chpe@svn.gnome.org>
12  *
13  * This library is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU Lesser General Public
15  * License as published by the Free Software Foundation; either
16  * version 2 of the License, or (at your option) any later version.
17  *
18  * This library is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21  * Lesser General Public License for more details.
22  *
23  * You should have received a copy of the GNU Lesser General Public
24  * License along with this library; if not, write to the
25  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
26  * Boston, MA 02111-1307, USA.
27  */
28
29 /*
30  * Modified by the GTK+ Team and others 2007.  See the AUTHORS
31  * file for a list of people on the GTK+ Team.  See the ChangeLog
32  * files for a list of changes.  These files are distributed with
33  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
34  */
35
36 #include "config.h"
37
38 #ifndef _WIN32
39 #define _GNU_SOURCE
40 #endif
41 #include <math.h>
42 #include <stdlib.h>
43 #include <string.h>
44
45 #include <gdk-pixbuf/gdk-pixbuf.h>
46 #include <gdk/gdkkeysyms.h>
47
48 #include "gtkbindings.h"
49 #include "gtkframe.h"
50 #include "gtkmain.h"
51 #include "gtkmarshalers.h"
52 #include "gtkorientable.h"
53 #include "gtkprivate.h"
54 #include "gtkscale.h"
55 #include "gtkscalebutton.h"
56 #include "gtkstock.h"
57 #include "gtkvbox.h"
58 #include "gtkwindow.h"
59
60 #include "gtkintl.h"
61 #include "gtkalias.h"
62
63
64 /**
65  * SECTION:gtkscalebutton
66  * @Short_description: A button which pops up a scale
67  * @Title: GtkScaleButton
68  *
69  * #GtkScaleButton provides a button which pops up a scale widget.
70  * This kind of widget is commonly used for volume controls in multimedia
71  * applications, and GTK+ provides a #GtkVolumeButton subclass that
72  * is tailored for this use case.
73  */
74
75
76 #define SCALE_SIZE 100
77 #define CLICK_TIMEOUT 250
78
79 enum
80 {
81   VALUE_CHANGED,
82   POPUP,
83   POPDOWN,
84
85   LAST_SIGNAL
86 };
87
88 enum
89 {
90   PROP_0,
91
92   PROP_ORIENTATION,
93   PROP_VALUE,
94   PROP_SIZE,
95   PROP_ADJUSTMENT,
96   PROP_ICONS
97 };
98
99 #define GET_PRIVATE(obj)        (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_SCALE_BUTTON, GtkScaleButtonPrivate))
100
101 struct _GtkScaleButtonPrivate
102 {
103   GtkWidget *dock;
104   GtkWidget *box;
105   GtkWidget *scale;
106   GtkWidget *image;
107
108   GtkIconSize size;
109   GtkOrientation orientation;
110
111   guint click_id;
112   gint click_timeout;
113   guint timeout : 1;
114   gdouble direction;
115   guint32 pop_time;
116
117   gchar **icon_list;
118
119   GtkAdjustment *adjustment; /* needed because it must be settable in init() */
120 };
121
122 static GObject* gtk_scale_button_constructor    (GType                  type,
123                                                  guint                  n_construct_properties,
124                                                  GObjectConstructParam *construct_params);
125 static void     gtk_scale_button_dispose        (GObject             *object);
126 static void     gtk_scale_button_finalize       (GObject             *object);
127 static void     gtk_scale_button_set_property   (GObject             *object,
128                                                  guint                prop_id,
129                                                  const GValue        *value,
130                                                  GParamSpec          *pspec);
131 static void     gtk_scale_button_get_property   (GObject             *object,
132                                                  guint                prop_id,
133                                                  GValue              *value,
134                                                  GParamSpec          *pspec);
135 static void gtk_scale_button_set_orientation_private (GtkScaleButton *button,
136                                                       GtkOrientation  orientation);
137 static gboolean gtk_scale_button_scroll         (GtkWidget           *widget,
138                                                  GdkEventScroll      *event);
139 static void gtk_scale_button_screen_changed     (GtkWidget           *widget,
140                                                  GdkScreen           *previous_screen);
141 static gboolean gtk_scale_button_press          (GtkWidget           *widget,
142                                                  GdkEventButton      *event);
143 static gboolean gtk_scale_button_key_release    (GtkWidget           *widget,
144                                                  GdkEventKey         *event);
145 static void     gtk_scale_button_popup          (GtkWidget           *widget);
146 static void     gtk_scale_button_popdown        (GtkWidget           *widget);
147 static gboolean cb_dock_button_press            (GtkWidget           *widget,
148                                                  GdkEventButton      *event,
149                                                  gpointer             user_data);
150 static gboolean cb_dock_key_release             (GtkWidget           *widget,
151                                                  GdkEventKey         *event,
152                                                  gpointer             user_data);
153 static gboolean cb_button_press                 (GtkWidget           *widget,
154                                                  GdkEventButton      *event,
155                                                  gpointer             user_data);
156 static gboolean cb_button_release               (GtkWidget           *widget,
157                                                  GdkEventButton      *event,
158                                                  gpointer             user_data);
159 static void cb_dock_grab_notify                 (GtkWidget           *widget,
160                                                  gboolean             was_grabbed,
161                                                  gpointer             user_data);
162 static gboolean cb_dock_grab_broken_event       (GtkWidget           *widget,
163                                                  gboolean             was_grabbed,
164                                                  gpointer             user_data);
165 static void cb_scale_grab_notify                (GtkWidget           *widget,
166                                                  gboolean             was_grabbed,
167                                                  gpointer             user_data);
168 static void gtk_scale_button_update_icon        (GtkScaleButton      *button);
169 static void gtk_scale_button_scale_value_changed(GtkRange            *range);
170
171 /* see below for scale definitions */
172 static GtkWidget *gtk_scale_button_scale_new    (GtkScaleButton      *button);
173
174 G_DEFINE_TYPE_WITH_CODE (GtkScaleButton, gtk_scale_button, GTK_TYPE_BUTTON,
175                          G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,
176                                                 NULL))
177
178 static guint signals[LAST_SIGNAL] = { 0, };
179
180 static void
181 gtk_scale_button_class_init (GtkScaleButtonClass *klass)
182 {
183   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
184   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
185   GtkBindingSet *binding_set;
186
187   g_type_class_add_private (klass, sizeof (GtkScaleButtonPrivate));
188
189   gobject_class->constructor = gtk_scale_button_constructor;
190   gobject_class->finalize = gtk_scale_button_finalize;
191   gobject_class->dispose = gtk_scale_button_dispose;
192   gobject_class->set_property = gtk_scale_button_set_property;
193   gobject_class->get_property = gtk_scale_button_get_property;
194
195   widget_class->button_press_event = gtk_scale_button_press;
196   widget_class->key_release_event = gtk_scale_button_key_release;
197   widget_class->scroll_event = gtk_scale_button_scroll;
198   widget_class->screen_changed = gtk_scale_button_screen_changed;
199
200   /**
201    * GtkScaleButton:orientation:
202    *
203    * The orientation of the #GtkScaleButton's popup window.
204    *
205    * Note that since GTK+ 2.16, #GtkScaleButton implements the
206    * #GtkOrientable interface which has its own @orientation
207    * property. However we redefine the property here in order to
208    * override its default horizontal orientation.
209    *
210    * Since: 2.14
211    **/
212   g_object_class_override_property (gobject_class,
213                                     PROP_ORIENTATION,
214                                     "orientation");
215
216   g_object_class_install_property (gobject_class,
217                                    PROP_VALUE,
218                                    g_param_spec_double ("value",
219                                                         P_("Value"),
220                                                         P_("The value of the scale"),
221                                                         -G_MAXDOUBLE,
222                                                         G_MAXDOUBLE,
223                                                         0,
224                                                         GTK_PARAM_READWRITE));
225
226   g_object_class_install_property (gobject_class,
227                                    PROP_SIZE,
228                                    g_param_spec_enum ("size",
229                                                       P_("Icon size"),
230                                                       P_("The icon size"),
231                                                       GTK_TYPE_ICON_SIZE,
232                                                       GTK_ICON_SIZE_SMALL_TOOLBAR,
233                                                       GTK_PARAM_READWRITE));
234
235   g_object_class_install_property (gobject_class,
236                                    PROP_ADJUSTMENT,
237                                    g_param_spec_object ("adjustment",
238                                                         P_("Adjustment"),
239                                                         P_("The GtkAdjustment that contains the current value of this scale button object"),
240                                                         GTK_TYPE_ADJUSTMENT,
241                                                         GTK_PARAM_READWRITE));
242
243   /**
244    * GtkScaleButton:icons:
245    *
246    * The names of the icons to be used by the scale button.
247    * The first item in the array will be used in the button
248    * when the current value is the lowest value, the second
249    * item for the highest value. All the subsequent icons will
250    * be used for all the other values, spread evenly over the
251    * range of values.
252    *
253    * If there's only one icon name in the @icons array, it will
254    * be used for all the values. If only two icon names are in
255    * the @icons array, the first one will be used for the bottom
256    * 50% of the scale, and the second one for the top 50%.
257    *
258    * It is recommended to use at least 3 icons so that the
259    * #GtkScaleButton reflects the current value of the scale
260    * better for the users.
261    *
262    * Since: 2.12
263    */
264   g_object_class_install_property (gobject_class,
265                                    PROP_ICONS,
266                                    g_param_spec_boxed ("icons",
267                                                        P_("Icons"),
268                                                        P_("List of icon names"),
269                                                        G_TYPE_STRV,
270                                                        GTK_PARAM_READWRITE));
271
272   /**
273    * GtkScaleButton::value-changed:
274    * @button: the object which received the signal
275    * @value: the new value
276    *
277    * The ::value-changed signal is emitted when the value field has
278    * changed.
279    *
280    * Since: 2.12
281    */
282   signals[VALUE_CHANGED] =
283     g_signal_new (I_("value-changed"),
284                   G_TYPE_FROM_CLASS (klass),
285                   G_SIGNAL_RUN_LAST,
286                   G_STRUCT_OFFSET (GtkScaleButtonClass, value_changed),
287                   NULL, NULL,
288                   _gtk_marshal_VOID__DOUBLE,
289                   G_TYPE_NONE, 1, G_TYPE_DOUBLE);
290
291   /**
292    * GtkScaleButton::popup:
293    * @button: the object which received the signal
294    *
295    * The ::popup signal is a
296    * <link linkend="keybinding-signals">keybinding signal</link>
297    * which gets emitted to popup the scale widget.
298    *
299    * The default bindings for this signal are Space, Enter and Return.
300    *
301    * Since: 2.12
302    */
303   signals[POPUP] =
304     g_signal_new_class_handler (I_("popup"),
305                                 G_OBJECT_CLASS_TYPE (klass),
306                                 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
307                                 G_CALLBACK (gtk_scale_button_popup),
308                                 NULL, NULL,
309                                 g_cclosure_marshal_VOID__VOID,
310                                 G_TYPE_NONE, 0);
311
312   /**
313    * GtkScaleButton::popdown:
314    * @button: the object which received the signal
315    *
316    * The ::popdown signal is a
317    * <link linkend="keybinding-signals">keybinding signal</link>
318    * which gets emitted to popdown the scale widget.
319    *
320    * The default binding for this signal is Escape.
321    *
322    * Since: 2.12
323    */
324   signals[POPDOWN] =
325     g_signal_new_class_handler (I_("popdown"),
326                                 G_OBJECT_CLASS_TYPE (klass),
327                                 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
328                                 G_CALLBACK (gtk_scale_button_popdown),
329                                 NULL, NULL,
330                                 g_cclosure_marshal_VOID__VOID,
331                                 G_TYPE_NONE, 0);
332
333   /* Key bindings */
334   binding_set = gtk_binding_set_by_class (widget_class);
335
336   gtk_binding_entry_add_signal (binding_set, GDK_space, 0,
337                                 "popup", 0);
338   gtk_binding_entry_add_signal (binding_set, GDK_KP_Space, 0,
339                                 "popup", 0);
340   gtk_binding_entry_add_signal (binding_set, GDK_Return, 0,
341                                 "popup", 0);
342   gtk_binding_entry_add_signal (binding_set, GDK_ISO_Enter, 0,
343                                 "popup", 0);
344   gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0,
345                                 "popup", 0);
346   gtk_binding_entry_add_signal (binding_set, GDK_Escape, 0,
347                                 "popdown", 0);
348 }
349
350 static void
351 gtk_scale_button_init (GtkScaleButton *button)
352 {
353   GtkScaleButtonPrivate *priv;
354   GtkWidget *frame;
355
356   button->priv = priv = GET_PRIVATE (button);
357
358   priv->timeout = FALSE;
359   priv->click_id = 0;
360   priv->click_timeout = CLICK_TIMEOUT;
361   priv->orientation = GTK_ORIENTATION_VERTICAL;
362
363   gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
364   gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
365
366   /* image */
367   priv->image = gtk_image_new ();
368   gtk_container_add (GTK_CONTAINER (button), priv->image);
369   gtk_widget_show_all (priv->image);
370
371   /* window */
372   priv->dock = gtk_window_new (GTK_WINDOW_POPUP);
373   gtk_widget_set_name (priv->dock, "gtk-scalebutton-popup-window");
374   g_signal_connect (priv->dock, "button-press-event",
375                     G_CALLBACK (cb_dock_button_press), button);
376   g_signal_connect (priv->dock, "key-release-event",
377                     G_CALLBACK (cb_dock_key_release), button);
378   g_signal_connect (priv->dock, "grab-notify",
379                     G_CALLBACK (cb_dock_grab_notify), button);
380   g_signal_connect (priv->dock, "grab-broken-event",
381                     G_CALLBACK (cb_dock_grab_broken_event), button);
382   gtk_window_set_decorated (GTK_WINDOW (priv->dock), FALSE);
383
384   /* frame */
385   frame = gtk_frame_new (NULL);
386   gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
387   gtk_container_add (GTK_CONTAINER (priv->dock), frame);
388
389   /* box for scale and +/- buttons */
390   priv->box = gtk_vbox_new (FALSE, 0);
391   gtk_container_add (GTK_CONTAINER (frame), priv->box);
392
393   /* + */
394   button->plus_button = gtk_button_new_with_label ("+");
395   gtk_button_set_relief (GTK_BUTTON (button->plus_button), GTK_RELIEF_NONE);
396   g_signal_connect (button->plus_button, "button-press-event",
397                     G_CALLBACK (cb_button_press), button);
398   g_signal_connect (button->plus_button, "button-release-event",
399                     G_CALLBACK (cb_button_release), button);
400   gtk_box_pack_start (GTK_BOX (priv->box), button->plus_button, FALSE, FALSE, 0);
401
402   /* - */
403   button->minus_button = gtk_button_new_with_label ("-");
404   gtk_button_set_relief (GTK_BUTTON (button->minus_button), GTK_RELIEF_NONE);
405   g_signal_connect (button->minus_button, "button-press-event",
406                    G_CALLBACK (cb_button_press), button);
407   g_signal_connect (button->minus_button, "button-release-event",
408                     G_CALLBACK (cb_button_release), button);
409   gtk_box_pack_end (GTK_BOX (priv->box), button->minus_button, FALSE, FALSE, 0);
410
411   priv->adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 100.0, 2, 20, 0));
412   g_object_ref_sink (priv->adjustment);
413
414   /* the scale */
415   priv->scale = gtk_scale_button_scale_new (button);
416   gtk_container_add (GTK_CONTAINER (priv->box), priv->scale);
417 }
418
419 static GObject *
420 gtk_scale_button_constructor (GType                  type,
421                               guint                  n_construct_properties,
422                               GObjectConstructParam *construct_params)
423 {
424   GObject *object;
425   GtkScaleButton *button;
426   GtkScaleButtonPrivate *priv;
427
428   object = G_OBJECT_CLASS (gtk_scale_button_parent_class)->constructor (type, n_construct_properties, construct_params);
429
430   button = GTK_SCALE_BUTTON (object);
431
432   priv = button->priv;
433
434   /* set button text and size */
435   priv->size = GTK_ICON_SIZE_SMALL_TOOLBAR;
436   gtk_scale_button_update_icon (button);
437
438   return object;
439 }
440
441 static void
442 gtk_scale_button_set_property (GObject       *object,
443                                guint          prop_id,
444                                const GValue  *value,
445                                GParamSpec    *pspec)
446 {
447   GtkScaleButton *button = GTK_SCALE_BUTTON (object);
448
449   switch (prop_id)
450     {
451     case PROP_ORIENTATION:
452       gtk_scale_button_set_orientation_private (button, g_value_get_enum (value));
453       break;
454     case PROP_VALUE:
455       gtk_scale_button_set_value (button, g_value_get_double (value));
456       break;
457     case PROP_SIZE:
458       {
459         GtkIconSize size;
460         size = g_value_get_enum (value);
461         if (button->priv->size != size)
462           {
463             button->priv->size = size;
464             gtk_scale_button_update_icon (button);
465           }
466       }
467       break;
468     case PROP_ADJUSTMENT:
469       gtk_scale_button_set_adjustment (button, g_value_get_object (value));
470       break;
471     case PROP_ICONS:
472       gtk_scale_button_set_icons (button,
473                                   (const gchar **)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
481 static void
482 gtk_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 = button->priv;
489
490   switch (prop_id)
491     {
492     case PROP_ORIENTATION:
493       g_value_set_enum (value, priv->orientation);
494       break;
495     case PROP_VALUE:
496       g_value_set_double (value, gtk_scale_button_get_value (button));
497       break;
498     case PROP_SIZE:
499       g_value_set_enum (value, priv->size);
500       break;
501     case PROP_ADJUSTMENT:
502       g_value_set_object (value, gtk_scale_button_get_adjustment (button));
503       break;
504     case PROP_ICONS:
505       g_value_set_boxed (value, priv->icon_list);
506       break;
507     default:
508       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
509       break;
510     }
511 }
512
513 static void
514 gtk_scale_button_finalize (GObject *object)
515 {
516   GtkScaleButton *button = GTK_SCALE_BUTTON (object);
517   GtkScaleButtonPrivate *priv = button->priv;
518
519   if (priv->icon_list)
520     {
521       g_strfreev (priv->icon_list);
522       priv->icon_list = NULL;
523     }
524
525   if (priv->adjustment)
526     {
527       g_object_unref (priv->adjustment);
528       priv->adjustment = NULL;
529     }
530
531   G_OBJECT_CLASS (gtk_scale_button_parent_class)->finalize (object);
532 }
533
534 static void
535 gtk_scale_button_dispose (GObject *object)
536 {
537   GtkScaleButton *button = GTK_SCALE_BUTTON (object);
538   GtkScaleButtonPrivate *priv = button->priv;
539
540   if (priv->dock)
541     {
542       gtk_widget_destroy (priv->dock);
543       priv->dock = NULL;
544     }
545
546   if (priv->click_id != 0)
547     {
548       g_source_remove (priv->click_id);
549       priv->click_id = 0;
550     }
551
552   G_OBJECT_CLASS (gtk_scale_button_parent_class)->dispose (object);
553 }
554
555 /**
556  * gtk_scale_button_new:
557  * @size: (int): a stock icon size
558  * @min: the minimum value of the scale (usually 0)
559  * @max: the maximum value of the scale (usually 100)
560  * @step: the stepping of value when a scroll-wheel event,
561  *        or up/down arrow event occurs (usually 2)
562  * @icons: (allow-none): a %NULL-terminated array of icon names, or %NULL if
563  *         you want to set the list later with gtk_scale_button_set_icons()
564  *
565  * Creates a #GtkScaleButton, with a range between @min and @max, with
566  * a stepping of @step.
567  *
568  * Return value: a new #GtkScaleButton
569  *
570  * Since: 2.12
571  */
572 GtkWidget *
573 gtk_scale_button_new (GtkIconSize   size,
574                       gdouble       min,
575                       gdouble       max,
576                       gdouble       step,
577                       const gchar **icons)
578 {
579   GtkScaleButton *button;
580   GtkObject *adj;
581
582   adj = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
583
584   button = g_object_new (GTK_TYPE_SCALE_BUTTON,
585                          "adjustment", adj,
586                          "icons", icons,
587                          "size", size,
588                          NULL);
589
590   return GTK_WIDGET (button);
591 }
592
593 /**
594  * gtk_scale_button_get_value:
595  * @button: a #GtkScaleButton
596  *
597  * Gets the current value of the scale button.
598  *
599  * Return value: current value of the scale button
600  *
601  * Since: 2.12
602  */
603 gdouble
604 gtk_scale_button_get_value (GtkScaleButton * button)
605 {
606   GtkScaleButtonPrivate *priv;
607
608   g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), 0);
609
610   priv = button->priv;
611
612   return gtk_adjustment_get_value (priv->adjustment);
613 }
614
615 /**
616  * gtk_scale_button_set_value:
617  * @button: a #GtkScaleButton
618  * @value: new value of the scale button
619  *
620  * Sets the current value of the scale; if the value is outside
621  * the minimum or maximum range values, it will be clamped to fit
622  * inside them. The scale button emits the #GtkScaleButton::value-changed
623  * signal if the value changes.
624  *
625  * Since: 2.12
626  */
627 void
628 gtk_scale_button_set_value (GtkScaleButton *button,
629                             gdouble         value)
630 {
631   GtkScaleButtonPrivate *priv;
632
633   g_return_if_fail (GTK_IS_SCALE_BUTTON (button));
634
635   priv = button->priv;
636
637   gtk_range_set_value (GTK_RANGE (priv->scale), value);
638 }
639
640 /**
641  * gtk_scale_button_set_icons:
642  * @button: a #GtkScaleButton
643  * @icons: a %NULL-terminated array of icon names
644  *
645  * Sets the icons to be used by the scale button.
646  * For details, see the #GtkScaleButton:icons property.
647  *
648  * Since: 2.12
649  */
650 void
651 gtk_scale_button_set_icons (GtkScaleButton  *button,
652                             const gchar    **icons)
653 {
654   GtkScaleButtonPrivate *priv;
655   gchar **tmp;
656
657   g_return_if_fail (GTK_IS_SCALE_BUTTON (button));
658
659   priv = button->priv;
660
661   tmp = priv->icon_list;
662   priv->icon_list = g_strdupv ((gchar **) icons);
663   g_strfreev (tmp);
664   gtk_scale_button_update_icon (button);
665
666   g_object_notify (G_OBJECT (button), "icons");
667 }
668
669 /**
670  * gtk_scale_button_get_adjustment:
671  * @button: a #GtkScaleButton
672  *
673  * Gets the #GtkAdjustment associated with the #GtkScaleButton's scale.
674  * See gtk_range_get_adjustment() for details.
675  *
676  * Returns: the adjustment associated with the scale
677  *
678  * Since: 2.12
679  */
680 GtkAdjustment*
681 gtk_scale_button_get_adjustment (GtkScaleButton *button)
682 {
683   g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
684
685   return button->priv->adjustment;
686 }
687
688 /**
689  * gtk_scale_button_set_adjustment:
690  * @button: a #GtkScaleButton
691  * @adjustment: a #GtkAdjustment
692  *
693  * Sets the #GtkAdjustment to be used as a model
694  * for the #GtkScaleButton's scale.
695  * See gtk_range_set_adjustment() for details.
696  *
697  * Since: 2.12
698  */
699 void
700 gtk_scale_button_set_adjustment (GtkScaleButton *button,
701                                  GtkAdjustment  *adjustment)
702 {
703   g_return_if_fail (GTK_IS_SCALE_BUTTON (button));
704   if (!adjustment)
705     adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
706   else
707     g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
708
709   if (button->priv->adjustment != adjustment)
710     {
711       if (button->priv->adjustment)
712         g_object_unref (button->priv->adjustment);
713       button->priv->adjustment = g_object_ref_sink (adjustment);
714
715       if (button->priv->scale)
716         gtk_range_set_adjustment (GTK_RANGE (button->priv->scale), adjustment);
717
718       g_object_notify (G_OBJECT (button), "adjustment");
719     }
720 }
721
722 /**
723  * gtk_scale_button_get_plus_button:
724  * @button: a #GtkScaleButton
725  *
726  * Retrieves the plus button of the #GtkScaleButton.
727  *
728  * Returns: the plus button of the #GtkScaleButton.
729  *
730  * Since: 2.14
731  */
732 GtkWidget *
733 gtk_scale_button_get_plus_button (GtkScaleButton *button)
734 {
735   g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
736
737   return button->plus_button;
738 }
739
740 /**
741  * gtk_scale_button_get_minus_button:
742  * @button: a #GtkScaleButton
743  *
744  * Retrieves the minus button of the #GtkScaleButton.
745  *
746  * Returns: the minus button of the #GtkScaleButton.
747  *
748  * Since: 2.14
749  */
750 GtkWidget *
751 gtk_scale_button_get_minus_button (GtkScaleButton *button)
752 {
753   g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
754
755   return button->minus_button;
756 }
757
758 /**
759  * gtk_scale_button_get_popup:
760  * @button: a #GtkScaleButton
761  *
762  * Retrieves the popup of the #GtkScaleButton.
763  *
764  * Returns: the popup of the #GtkScaleButton
765  *
766  * Since: 2.14
767  */
768 GtkWidget *
769 gtk_scale_button_get_popup (GtkScaleButton *button)
770 {
771   g_return_val_if_fail (GTK_IS_SCALE_BUTTON (button), NULL);
772
773   return button->priv->dock;
774 }
775
776 static void
777 gtk_scale_button_set_orientation_private (GtkScaleButton *button,
778                                           GtkOrientation  orientation)
779 {
780   GtkScaleButtonPrivate *priv = button->priv;
781
782   if (orientation != priv->orientation)
783     {
784       priv->orientation = orientation;
785
786       gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->box),
787                                       orientation);
788       gtk_container_child_set (GTK_CONTAINER (priv->box),
789                                button->plus_button,
790                                "pack-type",
791                                orientation == GTK_ORIENTATION_VERTICAL ?
792                                GTK_PACK_START : GTK_PACK_END,
793                                NULL);
794       gtk_container_child_set (GTK_CONTAINER (priv->box),
795                                button->minus_button,
796                                "pack-type",
797                                orientation == GTK_ORIENTATION_VERTICAL ?
798                                GTK_PACK_END : GTK_PACK_START,
799                                NULL);
800
801       gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->scale),
802                                       orientation);
803
804       if (orientation == GTK_ORIENTATION_VERTICAL)
805         {
806           gtk_widget_set_size_request (GTK_WIDGET (priv->scale),
807                                        -1, SCALE_SIZE);
808           gtk_range_set_inverted (GTK_RANGE (priv->scale), TRUE);
809         }
810       else
811         {
812           gtk_widget_set_size_request (GTK_WIDGET (priv->scale),
813                                        SCALE_SIZE, -1);
814           gtk_range_set_inverted (GTK_RANGE (priv->scale), FALSE);
815         }
816
817       /* FIXME: without this, the popup window appears as a square
818        * after changing the orientation
819        */
820       gtk_window_resize (GTK_WINDOW (priv->dock), 1, 1);
821
822       g_object_notify (G_OBJECT (button), "orientation");
823     }
824 }
825
826 /*
827  * button callbacks.
828  */
829
830 static gboolean
831 gtk_scale_button_scroll (GtkWidget      *widget,
832                          GdkEventScroll *event)
833 {
834   GtkScaleButton *button;
835   GtkScaleButtonPrivate *priv;
836   GtkAdjustment *adj;
837   gdouble d;
838
839   button = GTK_SCALE_BUTTON (widget);
840   priv = button->priv;
841   adj = priv->adjustment;
842
843   if (event->type != GDK_SCROLL)
844     return FALSE;
845
846   d = gtk_scale_button_get_value (button);
847   if (event->direction == GDK_SCROLL_UP)
848     {
849       d += adj->step_increment;
850       if (d > adj->upper)
851         d = adj->upper;
852     }
853   else
854     {
855       d -= adj->step_increment;
856       if (d < adj->lower)
857         d = adj->lower;
858     }
859   gtk_scale_button_set_value (button, d);
860
861   return TRUE;
862 }
863
864 static void
865 gtk_scale_button_screen_changed (GtkWidget *widget,
866                                  GdkScreen *previous_screen)
867 {
868   GtkScaleButton *button = (GtkScaleButton *) widget;
869   GtkScaleButtonPrivate *priv;
870   GdkScreen *screen;
871   GValue value = { 0, };
872
873   if (gtk_widget_has_screen (widget) == FALSE)
874     return;
875
876   priv = button->priv;
877
878   screen = gtk_widget_get_screen (widget);
879   g_value_init (&value, G_TYPE_INT);
880   if (gdk_screen_get_setting (screen,
881                               "gtk-double-click-time",
882                               &value) == FALSE)
883     {
884       priv->click_timeout = CLICK_TIMEOUT;
885       return;
886     }
887
888   priv->click_timeout = g_value_get_int (&value);
889 }
890
891 static gboolean
892 gtk_scale_popup (GtkWidget *widget,
893                  GdkEvent  *event,
894                  guint32    time)
895 {
896   GtkScaleButton *button;
897   GtkScaleButtonPrivate *priv;
898   GtkAdjustment *adj;
899   gint x, y, m, dx, dy, sx, sy, startoff;
900   gdouble v;
901   GdkDisplay *display;
902   GdkScreen *screen;
903   gboolean is_moved;
904
905   is_moved = FALSE;
906   button = GTK_SCALE_BUTTON (widget);
907   priv = button->priv;
908   adj = priv->adjustment;
909
910   display = gtk_widget_get_display (widget);
911   screen = gtk_widget_get_screen (widget);
912
913   /* position roughly */
914   gtk_window_set_screen (GTK_WINDOW (priv->dock), screen);
915
916   gdk_window_get_origin (widget->window, &x, &y);
917   x += widget->allocation.x;
918   y += widget->allocation.y;
919
920   if (priv->orientation == GTK_ORIENTATION_VERTICAL)
921     gtk_window_move (GTK_WINDOW (priv->dock), x, y - (SCALE_SIZE / 2));
922   else
923     gtk_window_move (GTK_WINDOW (priv->dock), x - (SCALE_SIZE / 2), y);
924
925   gtk_widget_show_all (priv->dock);
926
927   gdk_window_get_origin (priv->dock->window, &dx, &dy);
928   dx += priv->dock->allocation.x;
929   dy += priv->dock->allocation.y;
930
931   gdk_window_get_origin (priv->scale->window, &sx, &sy);
932   sx += priv->scale->allocation.x;
933   sy += priv->scale->allocation.y;
934
935   priv->timeout = TRUE;
936
937   /* position (needs widget to be shown already) */
938   v = gtk_scale_button_get_value (button) / (adj->upper - adj->lower);
939
940   if (priv->orientation == GTK_ORIENTATION_VERTICAL)
941     {
942       startoff = sy - dy;
943
944       x += (widget->allocation.width - priv->dock->allocation.width) / 2;
945       y -= startoff;
946       y -= GTK_RANGE (priv->scale)->min_slider_size / 2;
947       m = priv->scale->allocation.height -
948           GTK_RANGE (priv->scale)->min_slider_size;
949       y -= m * (1.0 - v);
950     }
951   else
952     {
953       startoff = sx - dx;
954
955       x -= startoff;
956       y += (widget->allocation.height - priv->dock->allocation.height) / 2;
957       x -= GTK_RANGE (priv->scale)->min_slider_size / 2;
958       m = priv->scale->allocation.width -
959           GTK_RANGE (priv->scale)->min_slider_size;
960       x -= m * v;
961     }
962
963   /* Make sure the dock stays inside the monitor */
964   if (event->type == GDK_BUTTON_PRESS)
965     {
966       int monitor;
967       GdkEventButton *button_event = (GdkEventButton *) event;
968       GdkRectangle rect;
969       GtkWidget *d;
970
971       d = GTK_WIDGET (priv->dock);
972       monitor = gdk_screen_get_monitor_at_point (screen,
973                                                  button_event->x_root,
974                                                  button_event->y_root);
975       gdk_screen_get_monitor_geometry (screen, monitor, &rect);
976
977       if (priv->orientation == GTK_ORIENTATION_VERTICAL)
978         y += button_event->y;
979       else
980         x += button_event->x;
981
982       /* Move the dock, but set is_moved so we
983        * don't forward the first click later on,
984        * as it could make the scale go to the bottom */
985       if (y < rect.y) {
986         y = rect.y;
987         is_moved = TRUE;
988       } else if (y + d->allocation.height > rect.height + rect.y) {
989         y = rect.y + rect.height - d->allocation.height;
990         is_moved = TRUE;
991       }
992
993       if (x < rect.x) {
994         x = rect.x;
995         is_moved = TRUE;
996       } else if (x + d->allocation.width > rect.width + rect.x) {
997         x = rect.x + rect.width - d->allocation.width;
998         is_moved = TRUE;
999       }
1000     }
1001
1002   gtk_window_move (GTK_WINDOW (priv->dock), x, y);
1003
1004   if (event->type == GDK_BUTTON_PRESS)
1005     GTK_WIDGET_CLASS (gtk_scale_button_parent_class)->button_press_event (widget, (GdkEventButton *) event);
1006
1007   /* grab focus */
1008   gtk_grab_add (priv->dock);
1009
1010   if (gdk_pointer_grab (priv->dock->window, TRUE,
1011                         GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
1012                         GDK_POINTER_MOTION_MASK, NULL, NULL, time)
1013       != GDK_GRAB_SUCCESS)
1014     {
1015       gtk_grab_remove (priv->dock);
1016       gtk_widget_hide (priv->dock);
1017       return FALSE;
1018     }
1019
1020   if (gdk_keyboard_grab (priv->dock->window, TRUE, time) != GDK_GRAB_SUCCESS)
1021     {
1022       gdk_display_pointer_ungrab (display, time);
1023       gtk_grab_remove (priv->dock);
1024       gtk_widget_hide (priv->dock);
1025       return FALSE;
1026     }
1027
1028   gtk_widget_grab_focus (priv->dock);
1029
1030   if (event->type == GDK_BUTTON_PRESS && !is_moved)
1031     {
1032       GdkEventButton *e;
1033       GdkEventButton *button_event = (GdkEventButton *) event;
1034
1035       /* forward event to the slider */
1036       e = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
1037       e->window = priv->scale->window;
1038
1039       /* position: the X position isn't relevant, halfway will work just fine.
1040        * The vertical position should be *exactly* in the middle of the slider
1041        * of the scale; if we don't do that correctly, it'll move from its current
1042        * position, which means a position change on-click, which is bad.
1043        */
1044       if (priv->orientation == GTK_ORIENTATION_VERTICAL)
1045         {
1046           e->x = priv->scale->allocation.width / 2;
1047           m = priv->scale->allocation.height -
1048               GTK_RANGE (priv->scale)->min_slider_size;
1049           e->y = ((1.0 - v) * m) + GTK_RANGE (priv->scale)->min_slider_size / 2;
1050         }
1051       else
1052         {
1053           e->y = priv->scale->allocation.height / 2;
1054           m = priv->scale->allocation.width -
1055               GTK_RANGE (priv->scale)->min_slider_size;
1056           e->x = (v * m) + GTK_RANGE (priv->scale)->min_slider_size / 2;
1057         }
1058
1059       gtk_widget_event (priv->scale, (GdkEvent *) e);
1060       e->window = button_event->window;
1061       gdk_event_free ((GdkEvent *) e);
1062     }
1063
1064   gtk_widget_grab_focus (priv->scale);
1065
1066   priv->pop_time = time;
1067
1068   return TRUE;
1069 }
1070
1071 static gboolean
1072 gtk_scale_button_press (GtkWidget      *widget,
1073                         GdkEventButton *event)
1074 {
1075   return gtk_scale_popup (widget, (GdkEvent *) event, event->time);
1076 }
1077
1078 static void
1079 gtk_scale_button_popup (GtkWidget *widget)
1080 {
1081   GdkEvent *ev;
1082
1083   ev = gdk_event_new (GDK_KEY_RELEASE);
1084   gtk_scale_popup (widget, ev, GDK_CURRENT_TIME);
1085   gdk_event_free (ev);
1086 }
1087
1088 static gboolean
1089 gtk_scale_button_key_release (GtkWidget   *widget,
1090                               GdkEventKey *event)
1091 {
1092   return gtk_bindings_activate_event (GTK_OBJECT (widget), event);
1093 }
1094
1095 /* This is called when the grab is broken for
1096  * either the dock, or the scale itself */
1097 static void
1098 gtk_scale_button_grab_notify (GtkScaleButton *button,
1099                               gboolean        was_grabbed)
1100 {
1101   GdkDisplay *display;
1102   GtkScaleButtonPrivate *priv;
1103
1104   if (was_grabbed != FALSE)
1105     return;
1106
1107   priv = button->priv;
1108
1109   if (!gtk_widget_has_grab (priv->dock))
1110     return;
1111
1112   if (gtk_widget_is_ancestor (gtk_grab_get_current (), priv->dock))
1113     return;
1114
1115   display = gtk_widget_get_display (priv->dock);
1116   gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME);
1117   gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME);
1118   gtk_grab_remove (priv->dock);
1119
1120   /* hide again */
1121   gtk_widget_hide (priv->dock);
1122   priv->timeout = FALSE;
1123 }
1124
1125 /*
1126  * +/- button callbacks.
1127  */
1128
1129 static gboolean
1130 cb_button_timeout (gpointer user_data)
1131 {
1132   GtkScaleButton *button;
1133   GtkScaleButtonPrivate *priv;
1134   GtkAdjustment *adj;
1135   gdouble val;
1136   gboolean res = TRUE;
1137
1138   button = GTK_SCALE_BUTTON (user_data);
1139   priv = button->priv;
1140
1141   if (priv->click_id == 0)
1142     return FALSE;
1143
1144   adj = priv->adjustment;
1145
1146   val = gtk_scale_button_get_value (button);
1147   val += priv->direction;
1148   if (val <= adj->lower)
1149     {
1150       res = FALSE;
1151       val = adj->lower;
1152     }
1153   else if (val > adj->upper)
1154     {
1155       res = FALSE;
1156       val = adj->upper;
1157     }
1158   gtk_scale_button_set_value (button, val);
1159
1160   if (!res)
1161     {
1162       g_source_remove (priv->click_id);
1163       priv->click_id = 0;
1164     }
1165
1166   return res;
1167 }
1168
1169 static gboolean
1170 cb_button_press (GtkWidget      *widget,
1171                  GdkEventButton *event,
1172                  gpointer        user_data)
1173 {
1174   GtkScaleButton *button;
1175   GtkScaleButtonPrivate *priv;
1176   GtkAdjustment *adj;
1177
1178   button = GTK_SCALE_BUTTON (user_data);
1179   priv = button->priv;
1180   adj = priv->adjustment;
1181
1182   if (priv->click_id != 0)
1183     g_source_remove (priv->click_id);
1184
1185   if (widget == button->plus_button)
1186     priv->direction = fabs (adj->page_increment);
1187   else
1188     priv->direction = - fabs (adj->page_increment);
1189
1190   priv->click_id = gdk_threads_add_timeout (priv->click_timeout,
1191                                             cb_button_timeout,
1192                                             button);
1193   cb_button_timeout (button);
1194
1195   return TRUE;
1196 }
1197
1198 static gboolean
1199 cb_button_release (GtkWidget      *widget,
1200                    GdkEventButton *event,
1201                    gpointer        user_data)
1202 {
1203   GtkScaleButton *button;
1204   GtkScaleButtonPrivate *priv;
1205
1206   button = GTK_SCALE_BUTTON (user_data);
1207   priv = button->priv;
1208
1209   if (priv->click_id != 0)
1210     {
1211       g_source_remove (priv->click_id);
1212       priv->click_id = 0;
1213     }
1214
1215   return TRUE;
1216 }
1217
1218 static void
1219 cb_dock_grab_notify (GtkWidget *widget,
1220                      gboolean   was_grabbed,
1221                      gpointer   user_data)
1222 {
1223   GtkScaleButton *button = (GtkScaleButton *) user_data;
1224
1225   gtk_scale_button_grab_notify (button, was_grabbed);
1226 }
1227
1228 static gboolean
1229 cb_dock_grab_broken_event (GtkWidget *widget,
1230                            gboolean   was_grabbed,
1231                            gpointer   user_data)
1232 {
1233   GtkScaleButton *button = (GtkScaleButton *) user_data;
1234
1235   gtk_scale_button_grab_notify (button, FALSE);
1236
1237   return FALSE;
1238 }
1239
1240 /*
1241  * Scale callbacks.
1242  */
1243
1244 static void
1245 gtk_scale_button_release_grab (GtkScaleButton *button,
1246                                GdkEventButton *event)
1247 {
1248   GdkEventButton *e;
1249   GdkDisplay *display;
1250   GtkScaleButtonPrivate *priv;
1251
1252   priv = button->priv;
1253
1254   /* ungrab focus */
1255   display = gtk_widget_get_display (GTK_WIDGET (button));
1256   gdk_display_keyboard_ungrab (display, event->time);
1257   gdk_display_pointer_ungrab (display, event->time);
1258   gtk_grab_remove (priv->dock);
1259
1260   /* hide again */
1261   gtk_widget_hide (priv->dock);
1262   priv->timeout = FALSE;
1263
1264   e = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
1265   e->window = GTK_WIDGET (button)->window;
1266   e->type = GDK_BUTTON_RELEASE;
1267   gtk_widget_event (GTK_WIDGET (button), (GdkEvent *) e);
1268   e->window = event->window;
1269   gdk_event_free ((GdkEvent *) e);
1270 }
1271
1272 static gboolean
1273 cb_dock_button_press (GtkWidget      *widget,
1274                       GdkEventButton *event,
1275                       gpointer        user_data)
1276 {
1277   GtkScaleButton *button = GTK_SCALE_BUTTON (user_data);
1278
1279   if (event->type == GDK_BUTTON_PRESS)
1280     {
1281       gtk_scale_button_release_grab (button, event);
1282       return TRUE;
1283     }
1284
1285   return FALSE;
1286 }
1287
1288 static void
1289 gtk_scale_button_popdown (GtkWidget *widget)
1290 {
1291   GtkScaleButton *button;
1292   GtkScaleButtonPrivate *priv;
1293   GdkDisplay *display;
1294
1295   button = GTK_SCALE_BUTTON (widget);
1296   priv = button->priv;
1297
1298   /* ungrab focus */
1299   display = gtk_widget_get_display (widget);
1300   gdk_display_keyboard_ungrab (display, GDK_CURRENT_TIME);
1301   gdk_display_pointer_ungrab (display, GDK_CURRENT_TIME);
1302   gtk_grab_remove (priv->dock);
1303
1304   /* hide again */
1305   gtk_widget_hide (priv->dock);
1306   priv->timeout = FALSE;
1307 }
1308
1309 static gboolean
1310 cb_dock_key_release (GtkWidget   *widget,
1311                      GdkEventKey *event,
1312                      gpointer     user_data)
1313 {
1314   if (event->keyval == GDK_Escape)
1315     {
1316       gtk_scale_button_popdown (GTK_WIDGET (user_data));
1317       return TRUE;
1318     }
1319
1320   if (!gtk_bindings_activate_event (GTK_OBJECT (widget), event))
1321     {
1322       /* The popup hasn't managed the event, pass onto the button */
1323       gtk_bindings_activate_event (GTK_OBJECT (user_data), event);
1324     }
1325
1326   return TRUE;
1327 }
1328
1329 static void
1330 cb_scale_grab_notify (GtkWidget *widget,
1331                       gboolean   was_grabbed,
1332                       gpointer   user_data)
1333 {
1334   GtkScaleButton *button = (GtkScaleButton *) user_data;
1335
1336   gtk_scale_button_grab_notify (button, was_grabbed);
1337 }
1338
1339 /*
1340  * Scale stuff.
1341  */
1342
1343 #define GTK_TYPE_SCALE_BUTTON_SCALE    (_gtk_scale_button_scale_get_type ())
1344 #define GTK_SCALE_BUTTON_SCALE(obj)    (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SCALE_BUTTON_SCALE, GtkScaleButtonScale))
1345 #define GTK_IS_SCALE_BUTTON_SCALE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SCALE_BUTTON_SCALE))
1346
1347 typedef struct _GtkScaleButtonScale
1348 {
1349   GtkScale parent_instance;
1350   GtkScaleButton *button;
1351 } GtkScaleButtonScale;
1352
1353 typedef struct _GtkScaleButtonScaleClass
1354 {
1355   GtkScaleClass parent_class;
1356 } GtkScaleButtonScaleClass;
1357
1358 static gboolean gtk_scale_button_scale_press   (GtkWidget      *widget,
1359                                                 GdkEventButton *event);
1360 static gboolean gtk_scale_button_scale_release (GtkWidget      *widget,
1361                                                 GdkEventButton *event);
1362
1363 G_DEFINE_TYPE (GtkScaleButtonScale, _gtk_scale_button_scale, GTK_TYPE_SCALE)
1364
1365 static void
1366 _gtk_scale_button_scale_class_init (GtkScaleButtonScaleClass *klass)
1367 {
1368   GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS (klass);
1369   GtkRangeClass *gtkrange_class = GTK_RANGE_CLASS (klass);
1370
1371   gtkwidget_class->button_press_event = gtk_scale_button_scale_press;
1372   gtkwidget_class->button_release_event = gtk_scale_button_scale_release;
1373   gtkrange_class->value_changed = gtk_scale_button_scale_value_changed;
1374 }
1375
1376 static void
1377 _gtk_scale_button_scale_init (GtkScaleButtonScale *scale)
1378 {
1379 }
1380
1381 static GtkWidget *
1382 gtk_scale_button_scale_new (GtkScaleButton *button)
1383 {
1384   GtkScaleButtonPrivate *priv = button->priv;
1385   GtkScaleButtonScale *scale;
1386
1387   scale = g_object_new (GTK_TYPE_SCALE_BUTTON_SCALE,
1388                         "orientation", priv->orientation,
1389                         "adjustment",  priv->adjustment,
1390                         "draw-value",  FALSE,
1391                         NULL);
1392
1393   scale->button = button;
1394
1395   g_signal_connect (scale, "grab-notify",
1396                     G_CALLBACK (cb_scale_grab_notify), button);
1397
1398   if (priv->orientation == GTK_ORIENTATION_VERTICAL)
1399     {
1400       gtk_widget_set_size_request (GTK_WIDGET (scale), -1, SCALE_SIZE);
1401       gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
1402     }
1403   else
1404     {
1405       gtk_widget_set_size_request (GTK_WIDGET (scale), SCALE_SIZE, -1);
1406       gtk_range_set_inverted (GTK_RANGE (scale), FALSE);
1407     }
1408
1409   return GTK_WIDGET (scale);
1410 }
1411
1412 static gboolean
1413 gtk_scale_button_scale_press (GtkWidget      *widget,
1414                               GdkEventButton *event)
1415 {
1416   GtkScaleButtonPrivate *priv = GTK_SCALE_BUTTON_SCALE (widget)->button->priv;
1417
1418   /* the scale will grab input; if we have input grabbed, all goes
1419    * horribly wrong, so let's not do that.
1420    */
1421   gtk_grab_remove (priv->dock);
1422
1423   return GTK_WIDGET_CLASS (_gtk_scale_button_scale_parent_class)->button_press_event (widget, event);
1424 }
1425
1426 static gboolean
1427 gtk_scale_button_scale_release (GtkWidget      *widget,
1428                                 GdkEventButton *event)
1429 {
1430   GtkScaleButton *button = GTK_SCALE_BUTTON_SCALE (widget)->button;
1431   gboolean res;
1432
1433   if (button->priv->timeout)
1434     {
1435       /* if we did a quick click, leave the window open; else, hide it */
1436       if (event->time > button->priv->pop_time + button->priv->click_timeout)
1437         {
1438
1439           gtk_scale_button_release_grab (button, event);
1440           GTK_WIDGET_CLASS (_gtk_scale_button_scale_parent_class)->button_release_event (widget, event);
1441
1442           return TRUE;
1443         }
1444
1445       button->priv->timeout = FALSE;
1446     }
1447
1448   res = GTK_WIDGET_CLASS (_gtk_scale_button_scale_parent_class)->button_release_event (widget, event);
1449
1450   /* the scale will release input; right after that, we *have to* grab
1451    * it back so we can catch out-of-scale clicks and hide the popup,
1452    * so I basically want a g_signal_connect_after_always(), but I can't
1453    * find that, so we do this complex 'first-call-parent-then-do-actual-
1454    * action' thingy...
1455    */
1456   gtk_grab_add (button->priv->dock);
1457
1458   return res;
1459 }
1460
1461 static void
1462 gtk_scale_button_update_icon (GtkScaleButton *button)
1463 {
1464   GtkScaleButtonPrivate *priv;
1465   GtkRange *range;
1466   GtkAdjustment *adj;
1467   gdouble value;
1468   const gchar *name;
1469   guint num_icons;
1470
1471   priv = button->priv;
1472
1473   if (!priv->icon_list || priv->icon_list[0] == '\0')
1474     {
1475       gtk_image_set_from_stock (GTK_IMAGE (priv->image),
1476                                 GTK_STOCK_MISSING_IMAGE,
1477                                 priv->size);
1478       return;
1479     }
1480
1481   num_icons = g_strv_length (priv->icon_list);
1482
1483   /* The 1-icon special case */
1484   if (num_icons == 1)
1485     {
1486       gtk_image_set_from_icon_name (GTK_IMAGE (priv->image),
1487                                     priv->icon_list[0],
1488                                     priv->size);
1489       return;
1490     }
1491
1492   range = GTK_RANGE (priv->scale);
1493   adj = priv->adjustment;
1494   value = gtk_scale_button_get_value (button);
1495
1496   /* The 2-icons special case */
1497   if (num_icons == 2)
1498     {
1499       gdouble limit;
1500       limit = (adj->upper - adj->lower) / 2 + adj->lower;
1501       if (value < limit)
1502         name = priv->icon_list[0];
1503       else
1504         name = priv->icon_list[1];
1505
1506       gtk_image_set_from_icon_name (GTK_IMAGE (priv->image),
1507                                     name,
1508                                     priv->size);
1509       return;
1510     }
1511
1512   /* With 3 or more icons */
1513   if (value == adj->lower)
1514     {
1515       name = priv->icon_list[0];
1516     }
1517   else if (value == adj->upper)
1518     {
1519       name = priv->icon_list[1];
1520     }
1521   else
1522     {
1523       gdouble step;
1524       guint i;
1525
1526       step = (adj->upper - adj->lower) / (num_icons - 2);
1527       i = (guint) ((value - adj->lower) / step) + 2;
1528       g_assert (i < num_icons);
1529       name = priv->icon_list[i];
1530     }
1531
1532   gtk_image_set_from_icon_name (GTK_IMAGE (priv->image),
1533                                 name,
1534                                 priv->size);
1535 }
1536
1537 static void
1538 gtk_scale_button_scale_value_changed (GtkRange *range)
1539 {
1540   GtkScaleButton *button = GTK_SCALE_BUTTON_SCALE (range)->button;
1541   gdouble value;
1542
1543   value = gtk_range_get_value (range);
1544
1545   gtk_scale_button_update_icon (button);
1546
1547   g_signal_emit (button, signals[VALUE_CHANGED], 0, value);
1548   g_object_notify (G_OBJECT (button), "value");
1549 }
1550
1551 #define __GTK_SCALE_BUTTON_C__
1552 #include "gtkaliasdef.c"