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