fix a memory leak
[rhythmbox.git] / widgets / bacon-volume.c
blobacb785458c39f7f2cb7c8ef6a3522592f1309d32
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., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
24 #define _GNU_SOURCE
25 #include <math.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <glib/gi18n.h>
29 #include <gtk/gtk.h>
30 #include <gdk/gdkkeysyms.h>
31 #include "bacon-volume.h"
33 #define SCALE_SIZE 100
34 #define CLICK_TIMEOUT 250
36 enum {
37 SIGNAL_VALUE_CHANGED,
38 NUM_SIGNALS
41 static void bacon_volume_button_class_init (BaconVolumeButtonClass * klass);
42 static void bacon_volume_button_init (BaconVolumeButton * button);
43 static void bacon_volume_button_dispose (GObject * object);
45 static gboolean bacon_volume_button_scroll (GtkWidget * widget,
46 GdkEventScroll * event);
47 static gboolean bacon_volume_button_press (GtkWidget * widget,
48 GdkEventButton * event);
49 static gboolean bacon_volume_key_release (GtkWidget * widget,
50 GdkEventKey * event);
51 static void bacon_volume_button_style_set (GtkWidget *widget,
52 GtkStyle *previous_style);
53 static gboolean cb_dock_button_press (GtkWidget * widget,
54 GdkEventButton * event,
55 gpointer data);
56 static gboolean cb_dock_key_release (GtkWidget * widget,
57 GdkEventKey * event,
58 gpointer data);
59 static gboolean cb_dock_key_press (GtkWidget * widget,
60 GdkEventKey * event,
61 gpointer data);
63 static gboolean cb_button_press (GtkWidget * widget,
64 GdkEventButton * event,
65 gpointer data);
66 static gboolean cb_button_release (GtkWidget * widget,
67 GdkEventButton * event,
68 gpointer data);
69 static void bacon_volume_button_update_icon (BaconVolumeButton *button);
70 static void bacon_volume_scale_value_changed(GtkRange * range);
71 static void bacon_volume_button_load_icons (GtkWidget * widget);
73 /* see below for scale definitions */
74 static GtkWidget *bacon_volume_scale_new (BaconVolumeButton * button,
75 float min, float max,
76 float step);
78 static GtkButtonClass *parent_class = NULL;
79 static guint signals[NUM_SIGNALS] = { 0 };
81 GType
82 bacon_volume_button_get_type (void)
84 static GType bacon_volume_button_type = 0;
86 if (G_UNLIKELY (bacon_volume_button_type == 0)) {
87 const GTypeInfo bacon_volume_button_info = {
88 sizeof (BaconVolumeButtonClass),
89 NULL,
90 NULL,
91 (GClassInitFunc) bacon_volume_button_class_init,
92 NULL,
93 NULL,
94 sizeof (BaconVolumeButton),
96 (GInstanceInitFunc) bacon_volume_button_init,
97 NULL
100 bacon_volume_button_type =
101 g_type_register_static (GTK_TYPE_BUTTON,
102 "BaconVolumeButton",
103 &bacon_volume_button_info, 0);
106 return bacon_volume_button_type;
109 static void
110 bacon_volume_button_class_init (BaconVolumeButtonClass *klass)
112 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
113 GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS (klass);
115 parent_class = g_type_class_peek_parent (klass);
117 /* events */
118 gobject_class->dispose = bacon_volume_button_dispose;
119 gtkwidget_class->button_press_event = bacon_volume_button_press;
120 gtkwidget_class->key_release_event = bacon_volume_key_release;
121 gtkwidget_class->scroll_event = bacon_volume_button_scroll;
122 gtkwidget_class->style_set = bacon_volume_button_style_set;
124 /* signals */
125 signals[SIGNAL_VALUE_CHANGED] = g_signal_new ("value-changed",
126 G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
127 G_STRUCT_OFFSET (BaconVolumeButtonClass, value_changed),
128 NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
131 static void
132 bacon_volume_button_init (BaconVolumeButton *button)
134 button->timeout = FALSE;
135 button->click_id = 0;
136 button->dock = button->scale = NULL;
139 static void
140 bacon_volume_button_dispose (GObject *object)
142 BaconVolumeButton *button = BACON_VOLUME_BUTTON (object);
143 guint i;
145 if (button->dock) {
146 gtk_widget_destroy (button->dock);
147 button->dock = NULL;
150 if (button->click_id != 0) {
151 g_source_remove (button->click_id);
152 button->click_id = 0;
154 for (i = 0; i < 4; i++) {
155 if (button->icon[i] != NULL) {
156 g_object_unref (button->icon[i]);
157 button->icon[i] = NULL;
161 G_OBJECT_CLASS (parent_class)->dispose (object);
165 * public API.
168 GtkWidget *
169 bacon_volume_button_new (GtkIconSize size,
170 float min, float max,
171 float step)
173 BaconVolumeButton *button;
174 GtkWidget *frame, *box;
176 button = g_object_new (BACON_TYPE_VOLUME_BUTTON, NULL);
177 atk_object_set_name (gtk_widget_get_accessible (GTK_WIDGET (button)),
178 _("Volume"));
179 button->size = size;
180 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
181 gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
183 /* image */
184 button->image = gtk_image_new ();
185 gtk_container_add (GTK_CONTAINER (button), button->image);
186 gtk_widget_show_all (button->image);
188 /* window */
189 button->dock = gtk_window_new (GTK_WINDOW_POPUP);
190 g_signal_connect (button->dock, "button-press-event",
191 G_CALLBACK (cb_dock_button_press), button);
192 g_signal_connect (button->dock, "key-release-event",
193 G_CALLBACK (cb_dock_key_release), button);
194 g_signal_connect (button->dock, "key-press-event",
195 G_CALLBACK (cb_dock_key_press), button);
196 gtk_window_set_decorated (GTK_WINDOW (button->dock), FALSE);
198 /* frame */
199 frame = gtk_frame_new (NULL);
200 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
201 gtk_container_add (GTK_CONTAINER (button->dock), frame);
202 box = gtk_vbox_new (FALSE, 0);
203 gtk_container_add (GTK_CONTAINER (frame), box);
205 /* + */
206 button->plus = gtk_button_new_with_label (_("+"));
207 atk_object_set_name (gtk_widget_get_accessible (button->plus),
208 _("Volume Down"));
209 gtk_button_set_relief (GTK_BUTTON (button->plus), GTK_RELIEF_NONE);
210 g_signal_connect (button->plus, "button-press-event",
211 G_CALLBACK (cb_button_press), button);
212 g_signal_connect (button->plus, "button-release-event",
213 G_CALLBACK (cb_button_release), button);
214 gtk_box_pack_start (GTK_BOX (box), button->plus, TRUE, FALSE, 0);
216 /* scale */
217 button->scale = bacon_volume_scale_new (button, min, max, step);
218 gtk_widget_set_size_request (button->scale, -1, SCALE_SIZE);
219 gtk_scale_set_draw_value (GTK_SCALE (button->scale), FALSE);
220 gtk_range_set_inverted (GTK_RANGE (button->scale), TRUE);
221 gtk_box_pack_start (GTK_BOX (box), button->scale, TRUE, FALSE, 0);
223 /* - */
224 button->min = gtk_button_new_with_label (_("-"));
225 atk_object_set_name (gtk_widget_get_accessible (button->min),
226 _("Volume Up"));
227 gtk_button_set_relief (GTK_BUTTON (button->min), GTK_RELIEF_NONE);
228 g_signal_connect (button->min, "button-press-event",
229 G_CALLBACK (cb_button_press), button);
230 g_signal_connect (button->min, "button-release-event",
231 G_CALLBACK (cb_button_release), button);
232 gtk_box_pack_start (GTK_BOX (box), button->min, TRUE, FALSE, 0);
234 /* set button text */
235 bacon_volume_button_update_icon (button);
237 return GTK_WIDGET (button);
240 float
241 bacon_volume_button_get_value (BaconVolumeButton * button)
243 g_return_val_if_fail (button != NULL, 0);
245 return gtk_range_get_value (GTK_RANGE (button->scale));
248 void
249 bacon_volume_button_set_value (BaconVolumeButton * button,
250 float value)
252 g_return_if_fail (button != NULL);
254 gtk_range_set_value (GTK_RANGE (button->scale), value);
258 * button callbacks.
261 static void
262 bacon_volume_button_style_set (GtkWidget *widget,
263 GtkStyle *previous_style)
265 GTK_WIDGET_CLASS (parent_class)->style_set (widget, previous_style);
267 bacon_volume_button_load_icons (widget);
270 static gboolean
271 bacon_volume_button_scroll (GtkWidget * widget,
272 GdkEventScroll * event)
274 BaconVolumeButton *button = BACON_VOLUME_BUTTON (widget);
275 GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
276 float d;
278 if (event->type != GDK_SCROLL)
279 return FALSE;
281 d = bacon_volume_button_get_value (button);
282 if (event->direction == GDK_SCROLL_UP) {
283 d += adj->step_increment;
284 if (d > adj->upper)
285 d = adj->upper;
286 } else {
287 d -= adj->step_increment;
288 if (d < adj->lower)
289 d = adj->lower;
291 bacon_volume_button_set_value (button, d);
293 return TRUE;
296 static gboolean
297 bacon_volume_button_press (GtkWidget * widget,
298 GdkEventButton * event)
300 BaconVolumeButton *button = BACON_VOLUME_BUTTON (widget);
301 GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
302 gint x, y, m, dx, dy, sx, sy, ystartoff, mouse_y;
303 float v;
304 GdkEventButton *e;
305 GdkDisplay *display;
306 GdkScreen *screen;
308 display = gtk_widget_get_display (widget);
309 screen = gtk_widget_get_screen (widget);
311 /* position roughly */
312 gtk_window_set_screen (GTK_WINDOW (button->dock), screen);
314 gdk_window_get_origin (widget->window, &x, &y);
315 x += widget->allocation.x;
316 y += widget->allocation.y;
317 gtk_window_move (GTK_WINDOW (button->dock), x, y - (SCALE_SIZE / 2));
318 gtk_widget_show_all (button->dock);
319 gdk_window_get_origin (button->dock->window, &dx, &dy);
320 dy += button->dock->allocation.y;
321 gdk_window_get_origin (button->scale->window, &sx, &sy);
322 sy += button->scale->allocation.y;
323 ystartoff = sy - dy;
324 mouse_y = event->y;
325 button->timeout = TRUE;
327 /* position (needs widget to be shown already) */
328 v = bacon_volume_button_get_value (button) / (adj->upper - adj->lower);
329 x += (widget->allocation.width - button->dock->allocation.width) / 2;
330 y -= ystartoff;
331 y -= GTK_RANGE (button->scale)->min_slider_size / 2;
332 m = button->scale->allocation.height -
333 GTK_RANGE (button->scale)->min_slider_size;
334 y -= m * (1.0 - v);
335 y += mouse_y;
336 gtk_window_move (GTK_WINDOW (button->dock), x, y);
337 gdk_window_get_origin (button->scale->window, &sx, &sy);
339 GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event);
341 /* grab focus */
342 gtk_grab_add (button->dock);
344 if (gdk_pointer_grab (button->dock->window, TRUE,
345 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
346 GDK_POINTER_MOTION_MASK, NULL, NULL, event->time)
347 != GDK_GRAB_SUCCESS) {
348 gtk_grab_remove (button->dock);
349 gtk_widget_hide (button->dock);
350 return FALSE;
353 if (gdk_keyboard_grab (button->dock->window, TRUE, event->time) != GDK_GRAB_SUCCESS) {
354 gdk_display_pointer_ungrab (display, event->time);
355 gtk_grab_remove (button->dock);
356 gtk_widget_hide (button->dock);
357 return FALSE;
360 gtk_widget_grab_focus (button->dock);
362 /* forward event to the slider */
363 e = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
364 e->window = button->scale->window;
366 /* position: the X position isn't relevant, halfway will work just fine.
367 * The vertical position should be *exactly* in the middle of the slider
368 * of the scale; if we don't do that correctly, it'll move from its current
369 * position, which means a position change on-click, which is bad. */
370 e->x = button->scale->allocation.width / 2;
371 m = button->scale->allocation.height -
372 GTK_RANGE (button->scale)->min_slider_size;
373 e->y = ((1.0 - v) * m) + GTK_RANGE (button->scale)->min_slider_size / 2;
374 gtk_widget_event (button->scale, (GdkEvent *) e);
375 e->window = event->window;
376 gdk_event_free ((GdkEvent *) e);
378 button->pop_time = event->time;
380 return TRUE;
383 static gboolean
384 bacon_volume_key_release (GtkWidget * widget,
385 GdkEventKey * event)
387 BaconVolumeButton *button = BACON_VOLUME_BUTTON (widget);
388 GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
389 gint x, y, m, dx, dy, sx, sy, ystartoff;
390 float v;
391 GdkDisplay *display;
392 GdkScreen *screen;
394 if (event->keyval != GDK_space && event->keyval != GDK_Return)
395 return FALSE;
397 display = gtk_widget_get_display (widget);
398 screen = gtk_widget_get_screen (widget);
400 /* position roughly */
401 gtk_window_set_screen (GTK_WINDOW (button->dock), screen);
403 gdk_window_get_origin (widget->window, &x, &y);
404 x += widget->allocation.x;
405 y += widget->allocation.y;
406 gtk_window_move (GTK_WINDOW (button->dock), x, y - (SCALE_SIZE / 2));
407 gtk_widget_show_all (button->dock);
408 gdk_window_get_origin (button->dock->window, &dx, &dy);
409 dy += button->dock->allocation.y;
410 gdk_window_get_origin (button->scale->window, &sx, &sy);
411 sy += button->scale->allocation.y;
412 ystartoff = sy - dy;
413 button->timeout = TRUE;
415 /* position (needs widget to be shown already) */
416 v = bacon_volume_button_get_value (button) / (adj->upper - adj->lower);
417 x += (widget->allocation.width - button->dock->allocation.width) / 2;
418 y -= ystartoff;
419 y -= GTK_RANGE (button->scale)->min_slider_size / 2;
420 m = button->scale->allocation.height -
421 GTK_RANGE (button->scale)->min_slider_size;
422 y -= m * (1.0 - v);
423 gtk_window_move (GTK_WINDOW (button->dock), x, y);
424 gdk_window_get_origin (button->scale->window, &sx, &sy);
426 /* grab focus */
427 gtk_grab_add (button->dock);
429 if (gdk_pointer_grab (button->dock->window, TRUE,
430 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
431 NULL, NULL, event->time)
432 != GDK_GRAB_SUCCESS) {
433 gtk_grab_remove (button->dock);
434 gtk_widget_hide (button->dock);
435 return FALSE;
438 if (gdk_keyboard_grab (button->dock->window, TRUE, event->time) != GDK_GRAB_SUCCESS) {
439 gdk_display_pointer_ungrab (display, event->time);
440 gtk_grab_remove (button->dock);
441 gtk_widget_hide (button->dock);
442 return FALSE;
445 gtk_widget_grab_focus (button->scale);
447 button->pop_time = event->time;
449 return TRUE;
453 * +/- button callbacks.
456 static gboolean
457 cb_button_timeout (gpointer data)
459 BaconVolumeButton *button = BACON_VOLUME_BUTTON (data);
460 GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
461 float val;
462 gboolean res = TRUE;
464 if (button->click_id == 0)
465 return FALSE;
467 val = bacon_volume_button_get_value (button);
468 val += button->direction;
469 if (val <= adj->lower) {
470 res = FALSE;
471 val = adj->lower;
472 } else if (val > adj->upper) {
473 res = FALSE;
474 val = adj->upper;
476 bacon_volume_button_set_value (button, val);
478 if (!res) {
479 g_source_remove (button->click_id);
480 button->click_id = 0;
483 return res;
486 static gboolean
487 cb_button_press (GtkWidget * widget,
488 GdkEventButton * event,
489 gpointer data)
491 BaconVolumeButton *button = BACON_VOLUME_BUTTON (data);
492 GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
494 if (button->click_id != 0)
495 g_source_remove (button->click_id);
496 button->direction = (widget == button->plus) ?
497 fabs (adj->page_increment) : - fabs (adj->page_increment);
498 button->click_id = g_timeout_add (CLICK_TIMEOUT,
499 (GSourceFunc) cb_button_timeout, button);
500 cb_button_timeout (button);
502 return TRUE;
505 static gboolean
506 cb_button_release (GtkWidget * widget,
507 GdkEventButton * event,
508 gpointer data)
510 BaconVolumeButton *button = BACON_VOLUME_BUTTON (data);
512 if (button->click_id != 0) {
513 g_source_remove (button->click_id);
514 button->click_id = 0;
517 return TRUE;
521 * Scale callbacks.
524 static void
525 bacon_volume_release_grab (BaconVolumeButton *button,
526 GdkEventButton * event)
528 GdkEventButton *e;
529 GdkDisplay *display;
531 /* ungrab focus */
532 display = gtk_widget_get_display (GTK_WIDGET (button));
533 gdk_display_keyboard_ungrab (display, event->time);
534 gdk_display_pointer_ungrab (display, event->time);
535 gtk_grab_remove (button->dock);
537 /* hide again */
538 gtk_widget_hide (button->dock);
539 button->timeout = FALSE;
541 e = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
542 e->window = GTK_WIDGET (button)->window;
543 e->type = GDK_BUTTON_RELEASE;
544 gtk_widget_event (GTK_WIDGET (button), (GdkEvent *) e);
545 e->window = event->window;
546 gdk_event_free ((GdkEvent *) e);
549 static gboolean
550 cb_dock_button_press (GtkWidget * widget,
551 GdkEventButton * event,
552 gpointer data)
554 //GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *) event);
555 BaconVolumeButton *button = BACON_VOLUME_BUTTON (data);
557 if (/*ewidget == button->dock &&*/ event->type == GDK_BUTTON_PRESS) {
558 bacon_volume_release_grab (button, event);
559 return TRUE;
562 return FALSE;
565 static gboolean
566 cb_dock_key_release (GtkWidget * widget,
567 GdkEventKey * event,
568 gpointer data)
570 BaconVolumeButton *button = BACON_VOLUME_BUTTON (data);
572 if (event->keyval == GDK_Escape) {
573 GdkDisplay *display;
575 /* ungrab focus */
576 display = gtk_widget_get_display (widget);
577 gdk_display_keyboard_ungrab (display, event->time);
578 gdk_display_pointer_ungrab (display, event->time);
580 /* hide again */
581 gtk_widget_hide (button->dock);
582 button->timeout = FALSE;
584 return TRUE;
586 return FALSE;
589 static gboolean
590 cb_dock_key_press (GtkWidget * widget,
591 GdkEventKey * event,
592 gpointer data)
594 if (event->keyval == GDK_Escape) {
595 return TRUE;
597 return FALSE;
601 * Scale stuff.
604 #define BACON_TYPE_VOLUME_SCALE \
605 (bacon_volume_scale_get_type ())
606 #define BACON_VOLUME_SCALE(obj) \
607 (G_TYPE_CHECK_INSTANCE_CAST ((obj), BACON_TYPE_VOLUME_SCALE, \
608 BaconVolumeScale))
610 typedef struct _BaconVolumeScale {
611 GtkVScale parent;
612 BaconVolumeButton *button;
613 } BaconVolumeScale;
615 static GType bacon_volume_scale_get_type (void);
617 static void bacon_volume_scale_class_init (GtkVScaleClass * klass);
619 static gboolean bacon_volume_scale_press (GtkWidget * widget,
620 GdkEventButton * event);
621 static gboolean bacon_volume_scale_release (GtkWidget * widget,
622 GdkEventButton * event);
624 static GtkVScaleClass *scale_parent_class = NULL;
626 static GType
627 bacon_volume_scale_get_type (void)
629 static GType bacon_volume_scale_type = 0;
631 if (G_UNLIKELY (bacon_volume_scale_type == 0)) {
632 const GTypeInfo bacon_volume_scale_info = {
633 sizeof (GtkVScaleClass),
634 NULL,
635 NULL,
636 (GClassInitFunc) bacon_volume_scale_class_init,
637 NULL,
638 NULL,
639 sizeof (BaconVolumeScale),
641 NULL,
642 NULL
645 bacon_volume_scale_type =
646 g_type_register_static (GTK_TYPE_VSCALE,
647 "BaconVolumeScale",
648 &bacon_volume_scale_info, 0);
651 return bacon_volume_scale_type;
654 static void
655 bacon_volume_scale_class_init (GtkVScaleClass * klass)
657 GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS (klass);
658 GtkRangeClass *gtkrange_class = GTK_RANGE_CLASS (klass);
660 scale_parent_class = g_type_class_peek_parent (klass);
662 gtkwidget_class->button_press_event = bacon_volume_scale_press;
663 gtkwidget_class->button_release_event = bacon_volume_scale_release;
664 gtkrange_class->value_changed = bacon_volume_scale_value_changed;
667 static GtkWidget *
668 bacon_volume_scale_new (BaconVolumeButton * button,
669 float min, float max,
670 float step)
672 BaconVolumeScale *scale = g_object_new (BACON_TYPE_VOLUME_SCALE, NULL);
673 GtkObject *adj;
675 adj = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
676 gtk_range_set_adjustment (GTK_RANGE (scale), GTK_ADJUSTMENT (adj));
677 scale->button = button;
679 return GTK_WIDGET (scale);
682 static gboolean
683 bacon_volume_scale_press (GtkWidget * widget,
684 GdkEventButton * event)
686 BaconVolumeScale *scale = BACON_VOLUME_SCALE (widget);
687 BaconVolumeButton *button = scale->button;
689 /* the scale will grab input; if we have input grabbed, all goes
690 * horribly wrong, so let's not do that. */
691 gtk_grab_remove (button->dock);
693 return GTK_WIDGET_CLASS (scale_parent_class)->button_press_event (widget, event);
696 static gboolean
697 bacon_volume_scale_release (GtkWidget * widget,
698 GdkEventButton * event)
700 BaconVolumeScale *scale = BACON_VOLUME_SCALE (widget);
701 BaconVolumeButton *button = scale->button;
702 gboolean res;
704 if (button->timeout) {
705 /* if we did a quick click, leave the window open; else, hide it */
706 if (event->time > button->pop_time + CLICK_TIMEOUT) {
707 bacon_volume_release_grab (button, event);
708 GTK_WIDGET_CLASS (scale_parent_class)->button_release_event (widget, event);
709 return TRUE;
711 button->timeout = FALSE;
714 res = GTK_WIDGET_CLASS (scale_parent_class)->button_release_event (widget, event);
716 /* the scale will release input; right after that, we *have to* grab
717 * it back so we can catch out-of-scale clicks and hide the popup,
718 * so I basically want a g_signal_connect_after_always(), but I can't
719 * find that, so we do this complex 'first-call-parent-then-do-actual-
720 * action' thingy... */
721 gtk_grab_add (button->dock);
723 return res;
726 static void
727 bacon_volume_button_update_icon (BaconVolumeButton *button)
729 GtkRange *range = GTK_RANGE (button->scale);
730 float val = gtk_range_get_value (range);
731 GtkAdjustment *adj;
732 GdkPixbuf *pixbuf;
733 float step;
735 adj = gtk_range_get_adjustment (range);
736 step = (adj->upper - adj->lower) / 4;
738 if (val == adj->lower)
739 pixbuf = button->icon[0];
740 else if (val > adj->lower && val <= adj->lower + step)
741 pixbuf = button->icon[1];
742 else if (val > adj->lower + step && val <= adj->lower + step * 2)
743 pixbuf = button->icon[2];
744 else
745 pixbuf = button->icon[3];
747 gtk_image_set_from_pixbuf (GTK_IMAGE (button->image), pixbuf);
750 static void
751 bacon_volume_scale_value_changed (GtkRange * range)
753 BaconVolumeScale *scale = BACON_VOLUME_SCALE (range);
754 BaconVolumeButton *button = scale->button;
756 bacon_volume_button_update_icon (button);
758 /* signal */
759 g_signal_emit (button, signals[SIGNAL_VALUE_CHANGED], 0);
762 static void
763 bacon_volume_button_load_icons (GtkWidget *widget)
765 BaconVolumeButton *button = BACON_VOLUME_BUTTON (widget);
766 guint i;
767 gint w, h;
768 GdkScreen *screen;
769 GtkIconTheme *theme;
770 const char *icon_name[] = {"audio-volume-muted", "audio-volume-low",
771 "audio-volume-medium", "audio-volume-high"};
772 const char *fallback_icon_name[] = {"stock_volume-0", "stock_volume-min",
773 "stock_volume-med", "stock_volume-max"};
775 screen = gtk_widget_get_screen (widget);
776 theme = gtk_icon_theme_get_for_screen (screen);
778 gtk_icon_size_lookup (button->size, &w, &h);
780 for (i = 0; i < 4; i++) {
781 GError *error = NULL;
782 if (button->icon[i] != NULL) {
783 g_object_unref (button->icon[i]);
784 button->icon[i] = NULL;
786 button->icon[i] = gtk_icon_theme_load_icon (theme, icon_name[i], w, 0, &error);
787 if (error) {
788 g_print ("Couldn't load themed icon '%s': %s\n", icon_name[i], error->message);
789 g_clear_error (&error);
791 button->icon[i] = gtk_icon_theme_load_icon (theme, fallback_icon_name[i], w, 0, &error);
792 if (error) {
793 g_print ("Couldn't load themed icon '%s': %s\n", icon_name[i], error->message);
794 g_clear_error (&error);
799 /* Apply the new icons */
800 bacon_volume_button_update_icon (button);
804 * vim: sw=2 ts=8 cindent noai bs=2