1 /* Volume Button / popup widget
2 * (c) copyright 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301 USA.
28 #include <glib/gi18n.h>
30 #include "bacon-volume.h"
32 #define SCALE_SIZE 100
33 #define CLICK_TIMEOUT 250
40 static void bacon_volume_button_class_init (BaconVolumeButtonClass
* klass
);
41 static void bacon_volume_button_init (BaconVolumeButton
* button
);
42 static void bacon_volume_button_dispose (GObject
* object
);
44 static gboolean
bacon_volume_button_scroll (GtkWidget
* widget
,
45 GdkEventScroll
* event
);
46 static gboolean
bacon_volume_button_press (GtkWidget
* widget
,
47 GdkEventButton
* event
);
48 static gboolean
cb_dock_press (GtkWidget
* widget
,
49 GdkEventButton
* event
,
52 static gboolean
cb_button_press (GtkWidget
* widget
,
53 GdkEventButton
* event
,
55 static gboolean
cb_button_release (GtkWidget
* widget
,
56 GdkEventButton
* event
,
58 static void bacon_volume_scale_value_changed(GtkRange
* range
);
60 /* see below for scale definitions */
61 static GtkWidget
*bacon_volume_scale_new (BaconVolumeButton
* button
,
65 static GtkButtonClass
*parent_class
= NULL
;
66 static guint signals
[NUM_SIGNALS
] = { 0 };
69 bacon_volume_button_get_type (void)
71 static GType bacon_volume_button_type
= 0;
73 if (!bacon_volume_button_type
) {
74 static const GTypeInfo bacon_volume_button_info
= {
75 sizeof (BaconVolumeButtonClass
),
78 (GClassInitFunc
) bacon_volume_button_class_init
,
81 sizeof (BaconVolumeButton
),
83 (GInstanceInitFunc
) bacon_volume_button_init
,
87 bacon_volume_button_type
=
88 g_type_register_static (GTK_TYPE_BUTTON
,
90 &bacon_volume_button_info
, 0);
93 return bacon_volume_button_type
;
97 bacon_volume_button_class_init (BaconVolumeButtonClass
*klass
)
99 GObjectClass
*gobject_class
= G_OBJECT_CLASS (klass
);
100 GtkWidgetClass
*gtkwidget_class
= GTK_WIDGET_CLASS (klass
);
102 parent_class
= g_type_class_ref (GTK_TYPE_BUTTON
);
105 gobject_class
->dispose
= bacon_volume_button_dispose
;
106 gtkwidget_class
->button_press_event
= bacon_volume_button_press
;
107 gtkwidget_class
->scroll_event
= bacon_volume_button_scroll
;
110 signals
[SIGNAL_VALUE_CHANGED
] = g_signal_new ("value-changed",
111 G_TYPE_FROM_CLASS (klass
), G_SIGNAL_RUN_LAST
,
112 G_STRUCT_OFFSET (BaconVolumeButtonClass
, value_changed
),
113 NULL
, NULL
, g_cclosure_marshal_VOID__VOID
, G_TYPE_NONE
, 0);
117 bacon_volume_button_init (BaconVolumeButton
*button
)
119 button
->timeout
= FALSE
;
120 button
->click_id
= 0;
121 button
->dock
= button
->scale
= NULL
;
122 #ifndef HAVE_GTK_ONLY
123 button
->theme
= gtk_icon_theme_get_default ();
128 bacon_volume_button_dispose (GObject
*object
)
130 BaconVolumeButton
*button
= BACON_VOLUME_BUTTON (object
);
133 gtk_widget_destroy (button
->dock
);
138 g_object_unref (G_OBJECT (button
->theme
));
139 button
->theme
= NULL
;
142 if (button
->click_id
!= 0) {
143 g_source_remove (button
->click_id
);
144 button
->click_id
= 0;
147 G_OBJECT_CLASS (parent_class
)->dispose (object
);
155 bacon_volume_button_new (GtkIconSize size
,
156 float min
, float max
,
159 BaconVolumeButton
*button
;
160 GtkWidget
*frame
, *box
;
162 button
= g_object_new (BACON_TYPE_VOLUME_BUTTON
, NULL
);
164 gtk_button_set_relief (GTK_BUTTON (button
), GTK_RELIEF_NONE
);
166 #ifndef HAVE_GTK_ONLY
168 button
->image
= gtk_image_new ();
169 gtk_container_add (GTK_CONTAINER (button
), button
->image
);
170 gtk_widget_show_all (button
->image
);
174 button
->dock
= gtk_window_new (GTK_WINDOW_POPUP
);
175 g_signal_connect (button
->dock
, "button-press-event",
176 G_CALLBACK (cb_dock_press
), button
);
177 gtk_window_set_decorated (GTK_WINDOW (button
->dock
), FALSE
);
180 frame
= gtk_frame_new (NULL
);
181 gtk_frame_set_shadow_type (GTK_FRAME (frame
), GTK_SHADOW_OUT
);
182 gtk_container_add (GTK_CONTAINER (button
->dock
), frame
);
183 box
= gtk_vbox_new (FALSE
, 0);
184 gtk_container_add (GTK_CONTAINER (frame
), box
);
187 button
->plus
= gtk_button_new_with_label (_("+"));
188 gtk_button_set_relief (GTK_BUTTON (button
->plus
), GTK_RELIEF_NONE
);
189 g_signal_connect (button
->plus
, "button-press-event",
190 G_CALLBACK (cb_button_press
), button
);
191 g_signal_connect (button
->plus
, "button-release-event",
192 G_CALLBACK (cb_button_release
), button
);
193 gtk_box_pack_start (GTK_BOX (box
), button
->plus
, TRUE
, FALSE
, 0);
196 button
->scale
= bacon_volume_scale_new (button
, min
, max
, step
);
197 gtk_widget_set_size_request (button
->scale
, -1, SCALE_SIZE
);
198 gtk_scale_set_draw_value (GTK_SCALE (button
->scale
), FALSE
);
199 gtk_range_set_inverted (GTK_RANGE (button
->scale
), TRUE
);
200 gtk_box_pack_start (GTK_BOX (box
), button
->scale
, TRUE
, FALSE
, 0);
203 button
->min
= gtk_button_new_with_label (_("-"));
204 gtk_button_set_relief (GTK_BUTTON (button
->min
), GTK_RELIEF_NONE
);
205 g_signal_connect (button
->min
, "button-press-event",
206 G_CALLBACK (cb_button_press
), button
);
207 g_signal_connect (button
->min
, "button-release-event",
208 G_CALLBACK (cb_button_release
), button
);
209 gtk_box_pack_start (GTK_BOX (box
), button
->min
, TRUE
, FALSE
, 0);
211 /* call callback once so original icon is drawn */
212 bacon_volume_scale_value_changed (GTK_RANGE (button
->scale
));
214 return GTK_WIDGET (button
);
218 bacon_volume_button_get_value (BaconVolumeButton
* button
)
220 g_return_val_if_fail (button
!= NULL
, 0);
222 return gtk_range_get_value (GTK_RANGE (button
->scale
));
226 bacon_volume_button_set_value (BaconVolumeButton
* button
,
229 g_return_if_fail (button
!= NULL
);
231 gtk_range_set_value (GTK_RANGE (button
->scale
), value
);
239 bacon_volume_button_scroll (GtkWidget
* widget
,
240 GdkEventScroll
* event
)
242 BaconVolumeButton
*button
= BACON_VOLUME_BUTTON (widget
);
243 GtkAdjustment
*adj
= gtk_range_get_adjustment (GTK_RANGE (button
->scale
));
246 if (event
->type
!= GDK_SCROLL
)
249 d
= bacon_volume_button_get_value (button
);
250 if (event
->direction
== GDK_SCROLL_UP
) {
251 d
+= adj
->step_increment
;
255 d
-= adj
->step_increment
;
259 bacon_volume_button_set_value (button
, d
);
265 bacon_volume_button_press (GtkWidget
* widget
,
266 GdkEventButton
* event
)
268 BaconVolumeButton
*button
= BACON_VOLUME_BUTTON (widget
);
269 GtkAdjustment
*adj
= gtk_range_get_adjustment (GTK_RANGE (button
->scale
));
270 gint x
, y
, m
, dx
, dy
, sx
, sy
, ystartoff
, mouse_y
;
274 /* position roughly */
275 gdk_window_get_origin (widget
->window
, &x
, &y
);
276 x
+= widget
->allocation
.x
;
277 y
+= widget
->allocation
.y
;
278 gtk_window_move (GTK_WINDOW (button
->dock
), x
, y
- (SCALE_SIZE
/ 2));
279 gtk_widget_show_all (button
->dock
);
280 gdk_window_get_origin (button
->dock
->window
, &dx
, &dy
);
281 dy
+= button
->dock
->allocation
.y
;
282 gdk_window_get_origin (button
->scale
->window
, &sx
, &sy
);
283 sy
+= button
->scale
->allocation
.y
;
286 button
->timeout
= TRUE
;
288 /* position (needs widget to be shown already) */
289 v
= bacon_volume_button_get_value (button
) / (adj
->upper
- adj
->lower
);
290 x
+= (widget
->allocation
.width
- button
->dock
->allocation
.width
) / 2;
292 y
-= GTK_RANGE (button
->scale
)->min_slider_size
/ 2;
293 m
= button
->scale
->allocation
.height
-
294 GTK_RANGE (button
->scale
)->min_slider_size
;
297 gtk_window_move (GTK_WINDOW (button
->dock
), x
, y
);
298 gdk_window_get_origin (button
->scale
->window
, &sx
, &sy
);
300 GTK_WIDGET_CLASS (parent_class
)->button_press_event (widget
, event
);
303 gtk_widget_grab_focus (button
->dock
);
304 gtk_grab_add (button
->dock
);
305 gdk_pointer_grab (button
->dock
->window
, TRUE
,
306 GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
|
307 GDK_POINTER_MOTION_MASK
, NULL
, NULL
, GDK_CURRENT_TIME
);
308 gdk_keyboard_grab (button
->dock
->window
, TRUE
, GDK_CURRENT_TIME
);
310 /* forward event to the slider */
311 e
= (GdkEventButton
*) gdk_event_copy ((GdkEvent
*) event
);
312 e
->window
= button
->scale
->window
;
314 /* position: the X position isn't relevant, halfway will work just fine.
315 * The vertical position should be *exactly* in the middle of the slider
316 * of the scale; if we don't do that correctly, it'll move from its current
317 * position, which means a position change on-click, which is bad. */
318 e
->x
= button
->scale
->allocation
.width
/ 2;
319 m
= button
->scale
->allocation
.height
-
320 GTK_RANGE (button
->scale
)->min_slider_size
;
321 e
->y
= ((1.0 - v
) * m
) + GTK_RANGE (button
->scale
)->min_slider_size
/ 2;
322 gtk_widget_event (button
->scale
, (GdkEvent
*) e
);
323 e
->window
= event
->window
;
324 gdk_event_free ((GdkEvent
*) e
);
326 button
->pop_time
= event
->time
;
332 * +/- button callbacks.
336 button_timeout (BaconVolumeButton
*button
)
338 GtkAdjustment
*adj
= gtk_range_get_adjustment (GTK_RANGE (button
->scale
));
342 if (button
->click_id
== 0)
345 val
= bacon_volume_button_get_value (button
);
346 val
+= button
->direction
;
347 if (val
<= adj
->lower
) {
350 } else if (val
> adj
->upper
) {
354 bacon_volume_button_set_value (button
, val
);
357 g_source_remove (button
->click_id
);
358 button
->click_id
= 0;
365 cb_button_timeout (gpointer data
)
367 BaconVolumeButton
*button
= BACON_VOLUME_BUTTON (data
);
370 GDK_THREADS_ENTER ();
371 r
= button_timeout (button
);
372 GDK_THREADS_LEAVE ();
379 cb_button_press (GtkWidget
* widget
,
380 GdkEventButton
* event
,
383 BaconVolumeButton
*button
= BACON_VOLUME_BUTTON (data
);
384 GtkAdjustment
*adj
= gtk_range_get_adjustment (GTK_RANGE (button
->scale
));
386 if (button
->click_id
!= 0)
387 g_source_remove (button
->click_id
);
388 button
->direction
= (widget
== button
->plus
) ?
389 fabs (adj
->page_increment
) : - fabs (adj
->page_increment
);
390 button
->click_id
= g_timeout_add (CLICK_TIMEOUT
,
391 (GSourceFunc
) cb_button_timeout
, button
);
392 button_timeout (button
);
398 cb_button_release (GtkWidget
* widget
,
399 GdkEventButton
* event
,
402 BaconVolumeButton
*button
= BACON_VOLUME_BUTTON (data
);
404 if (button
->click_id
!= 0) {
405 g_source_remove (button
->click_id
);
406 button
->click_id
= 0;
417 bacon_volume_release_grab (BaconVolumeButton
*button
,
418 GdkEventButton
* event
)
423 gdk_keyboard_ungrab (GDK_CURRENT_TIME
);
424 gdk_pointer_ungrab (GDK_CURRENT_TIME
);
425 gtk_grab_remove (button
->dock
);
428 gtk_widget_hide (button
->dock
);
429 button
->timeout
= FALSE
;
431 e
= (GdkEventButton
*) gdk_event_copy ((GdkEvent
*) event
);
432 e
->window
= GTK_WIDGET (button
)->window
;
433 e
->type
= GDK_BUTTON_RELEASE
;
434 gtk_widget_event (GTK_WIDGET (button
), (GdkEvent
*) e
);
435 e
->window
= event
->window
;
436 gdk_event_free ((GdkEvent
*) e
);
440 cb_dock_press (GtkWidget
* widget
,
441 GdkEventButton
* event
,
444 //GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *) event);
445 BaconVolumeButton
*button
= BACON_VOLUME_BUTTON (data
);
447 if (/*ewidget == button->dock &&*/ event
->type
== GDK_BUTTON_PRESS
) {
448 bacon_volume_release_grab (button
, event
);
459 #define BACON_TYPE_VOLUME_SCALE \
460 (bacon_volume_scale_get_type ())
461 #define BACON_VOLUME_SCALE(obj) \
462 (G_TYPE_CHECK_INSTANCE_CAST ((obj), BACON_TYPE_VOLUME_SCALE, \
465 typedef struct _BaconVolumeScale
{
467 BaconVolumeButton
*button
;
470 static GType
bacon_volume_scale_get_type (void);
472 static void bacon_volume_scale_class_init (GtkVScaleClass
* klass
);
474 static gboolean
bacon_volume_scale_press (GtkWidget
* widget
,
475 GdkEventButton
* event
);
476 static gboolean
bacon_volume_scale_release (GtkWidget
* widget
,
477 GdkEventButton
* event
);
479 static GtkVScaleClass
*scale_parent_class
= NULL
;
482 bacon_volume_scale_get_type (void)
484 static GType bacon_volume_scale_type
= 0;
486 if (!bacon_volume_scale_type
) {
487 static const GTypeInfo bacon_volume_scale_info
= {
488 sizeof (GtkVScaleClass
),
491 (GClassInitFunc
) bacon_volume_scale_class_init
,
494 sizeof (BaconVolumeScale
),
500 bacon_volume_scale_type
=
501 g_type_register_static (GTK_TYPE_VSCALE
,
503 &bacon_volume_scale_info
, 0);
506 return bacon_volume_scale_type
;
510 bacon_volume_scale_class_init (GtkVScaleClass
* klass
)
512 GtkWidgetClass
*gtkwidget_class
= GTK_WIDGET_CLASS (klass
);
513 GtkRangeClass
*gtkrange_class
= GTK_RANGE_CLASS (klass
);
515 scale_parent_class
= g_type_class_ref (GTK_TYPE_VSCALE
);
517 gtkwidget_class
->button_press_event
= bacon_volume_scale_press
;
518 gtkwidget_class
->button_release_event
= bacon_volume_scale_release
;
519 gtkrange_class
->value_changed
= bacon_volume_scale_value_changed
;
523 bacon_volume_scale_new (BaconVolumeButton
* button
,
524 float min
, float max
,
527 BaconVolumeScale
*scale
= g_object_new (BACON_TYPE_VOLUME_SCALE
, NULL
);
530 adj
= gtk_adjustment_new (min
, min
, max
, step
, 10 * step
, 0);
531 gtk_range_set_adjustment (GTK_RANGE (scale
), GTK_ADJUSTMENT (adj
));
532 scale
->button
= button
;
534 return GTK_WIDGET (scale
);
538 bacon_volume_scale_press (GtkWidget
* widget
,
539 GdkEventButton
* event
)
541 BaconVolumeScale
*scale
= BACON_VOLUME_SCALE (widget
);
542 BaconVolumeButton
*button
= scale
->button
;
544 /* the scale will grab input; if we have input grabbed, all goes
545 * horribly wrong, so let's not do that. */
546 gtk_grab_remove (button
->dock
);
548 return GTK_WIDGET_CLASS (scale_parent_class
)->button_press_event (widget
, event
);
552 bacon_volume_scale_release (GtkWidget
* widget
,
553 GdkEventButton
* event
)
555 BaconVolumeScale
*scale
= BACON_VOLUME_SCALE (widget
);
556 BaconVolumeButton
*button
= scale
->button
;
559 if (button
->timeout
) {
560 /* if we did a quick click, leave the window open; else, hide it */
561 if (event
->time
> button
->pop_time
+ CLICK_TIMEOUT
) {
562 bacon_volume_release_grab (button
, event
);
563 GTK_WIDGET_CLASS (scale_parent_class
)->button_release_event (widget
, event
);
566 button
->timeout
= FALSE
;
569 res
= GTK_WIDGET_CLASS (scale_parent_class
)->button_release_event (widget
, event
);
571 /* the scale will release input; right after that, we *have to* grab
572 * it back so we can catch out-of-scale clicks and hide the popup,
573 * so I basically want a g_signal_connect_after_always(), but I can't
574 * find that, so we do this complex 'first-call-parent-then-do-actual-
575 * action' thingy... */
576 gtk_grab_add (button
->dock
);
582 bacon_volume_scale_value_changed (GtkRange
* range
)
584 BaconVolumeScale
*scale
= BACON_VOLUME_SCALE (range
);
585 BaconVolumeButton
*button
= scale
->button
;
586 GtkAdjustment
*adj
= gtk_range_get_adjustment (GTK_RANGE (button
->scale
));
587 float step
= (adj
->upper
- adj
->lower
) / 4;
588 float val
= gtk_range_get_value (range
);
594 s
= g_strdup_printf ("%d", lrintf (val
));
595 gtk_button_set_label (GTK_BUTTON (button
), s
);
601 if (val
== adj
->lower
)
602 s
= "stock_volume-0";
603 else if (val
> adj
->lower
&& val
<= adj
->lower
+ step
)
604 s
= "stock_volume-min";
605 else if (val
> adj
->lower
+ step
&& val
<= adj
->lower
+ step
* 2)
606 s
= "stock_volume-med";
608 s
= "stock_volume-max";
611 gtk_icon_size_lookup (button
->size
, &w
, &h
);
612 buf
= gtk_icon_theme_load_icon (button
->theme
, s
, w
, 0, NULL
);
613 gtk_image_set_from_pixbuf (GTK_IMAGE (button
->image
), buf
);
615 g_object_unref (buf
);
620 g_signal_emit (button
, signals
[SIGNAL_VALUE_CHANGED
], 0);