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