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