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.
28
#include <glib/gi18n.h>
30
#include <gdk/gdkkeysyms.h>
31
#include "bacon-volume.h"
33
#define SCALE_SIZE 100
34
#define CLICK_TIMEOUT 250
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,
51
static gboolean cb_dock_button_press (GtkWidget * widget,
52
GdkEventButton * event,
54
static gboolean cb_dock_key_release (GtkWidget * widget,
57
static gboolean cb_dock_key_press (GtkWidget * widget,
61
static gboolean cb_button_press (GtkWidget * widget,
62
GdkEventButton * event,
64
static gboolean cb_button_release (GtkWidget * widget,
65
GdkEventButton * event,
67
static void bacon_volume_scale_value_changed(GtkRange * range);
69
/* see below for scale definitions */
70
static GtkWidget *bacon_volume_scale_new (BaconVolumeButton * button,
74
static GtkButtonClass *parent_class = NULL;
75
static guint signals[NUM_SIGNALS] = { 0 };
78
bacon_volume_button_get_type (void)
80
static GType bacon_volume_button_type = 0;
82
if (!bacon_volume_button_type) {
83
static const GTypeInfo bacon_volume_button_info = {
84
sizeof (BaconVolumeButtonClass),
87
(GClassInitFunc) bacon_volume_button_class_init,
90
sizeof (BaconVolumeButton),
92
(GInstanceInitFunc) bacon_volume_button_init,
96
bacon_volume_button_type =
97
g_type_register_static (GTK_TYPE_BUTTON,
99
&bacon_volume_button_info, 0);
102
return bacon_volume_button_type;
106
bacon_volume_button_class_init (BaconVolumeButtonClass *klass)
108
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
109
GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS (klass);
111
parent_class = g_type_class_ref (GTK_TYPE_BUTTON);
114
gobject_class->dispose = bacon_volume_button_dispose;
115
gtkwidget_class->button_press_event = bacon_volume_button_press;
116
gtkwidget_class->key_release_event = bacon_volume_key_release;
117
gtkwidget_class->scroll_event = bacon_volume_button_scroll;
120
signals[SIGNAL_VALUE_CHANGED] = g_signal_new ("value-changed",
121
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
122
G_STRUCT_OFFSET (BaconVolumeButtonClass, value_changed),
123
NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
127
bacon_volume_button_init (BaconVolumeButton *button)
129
button->timeout = FALSE;
130
button->click_id = 0;
131
button->dock = button->scale = NULL;
132
#ifndef HAVE_GTK_ONLY
133
button->theme = gtk_icon_theme_get_default ();
138
bacon_volume_button_dispose (GObject *object)
140
BaconVolumeButton *button = BACON_VOLUME_BUTTON (object);
143
gtk_widget_destroy (button->dock);
148
g_object_unref (G_OBJECT (button->theme));
149
button->theme = NULL;
152
if (button->click_id != 0) {
153
g_source_remove (button->click_id);
154
button->click_id = 0;
157
G_OBJECT_CLASS (parent_class)->dispose (object);
165
bacon_volume_button_new (GtkIconSize size,
166
float min, float max,
169
BaconVolumeButton *button;
170
GtkWidget *frame, *box;
172
button = g_object_new (BACON_TYPE_VOLUME_BUTTON, NULL);
174
gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
176
#ifndef HAVE_GTK_ONLY
178
button->image = gtk_image_new ();
179
gtk_container_add (GTK_CONTAINER (button), button->image);
180
gtk_widget_show_all (button->image);
184
button->dock = gtk_window_new (GTK_WINDOW_POPUP);
185
g_signal_connect (button->dock, "button-press-event",
186
G_CALLBACK (cb_dock_button_press), button);
187
g_signal_connect (button->dock, "key-release-event",
188
G_CALLBACK (cb_dock_key_release), button);
189
g_signal_connect (button->dock, "key-press-event",
190
G_CALLBACK (cb_dock_key_press), button);
191
gtk_window_set_decorated (GTK_WINDOW (button->dock), FALSE);
194
frame = gtk_frame_new (NULL);
195
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
196
gtk_container_add (GTK_CONTAINER (button->dock), frame);
197
box = gtk_vbox_new (FALSE, 0);
198
gtk_container_add (GTK_CONTAINER (frame), box);
201
button->plus = gtk_button_new_with_label (_("+"));
202
gtk_button_set_relief (GTK_BUTTON (button->plus), GTK_RELIEF_NONE);
203
g_signal_connect (button->plus, "button-press-event",
204
G_CALLBACK (cb_button_press), button);
205
g_signal_connect (button->plus, "button-release-event",
206
G_CALLBACK (cb_button_release), button);
207
gtk_box_pack_start (GTK_BOX (box), button->plus, TRUE, FALSE, 0);
210
button->scale = bacon_volume_scale_new (button, min, max, step);
211
gtk_widget_set_size_request (button->scale, -1, SCALE_SIZE);
212
gtk_scale_set_draw_value (GTK_SCALE (button->scale), FALSE);
213
gtk_range_set_inverted (GTK_RANGE (button->scale), TRUE);
214
gtk_box_pack_start (GTK_BOX (box), button->scale, TRUE, FALSE, 0);
217
button->min = gtk_button_new_with_label (_("-"));
218
gtk_button_set_relief (GTK_BUTTON (button->min), GTK_RELIEF_NONE);
219
g_signal_connect (button->min, "button-press-event",
220
G_CALLBACK (cb_button_press), button);
221
g_signal_connect (button->min, "button-release-event",
222
G_CALLBACK (cb_button_release), button);
223
gtk_box_pack_start (GTK_BOX (box), button->min, TRUE, FALSE, 0);
225
/* call callback once so original icon is drawn */
226
bacon_volume_scale_value_changed (GTK_RANGE (button->scale));
228
return GTK_WIDGET (button);
232
bacon_volume_button_get_value (BaconVolumeButton * button)
234
g_return_val_if_fail (button != NULL, 0);
236
return gtk_range_get_value (GTK_RANGE (button->scale));
240
bacon_volume_button_set_value (BaconVolumeButton * button,
243
g_return_if_fail (button != NULL);
245
gtk_range_set_value (GTK_RANGE (button->scale), value);
253
bacon_volume_button_scroll (GtkWidget * widget,
254
GdkEventScroll * event)
256
BaconVolumeButton *button = BACON_VOLUME_BUTTON (widget);
257
GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
260
if (event->type != GDK_SCROLL)
263
d = bacon_volume_button_get_value (button);
264
if (event->direction == GDK_SCROLL_UP) {
265
d += adj->step_increment;
269
d -= adj->step_increment;
273
bacon_volume_button_set_value (button, d);
279
bacon_volume_button_press (GtkWidget * widget,
280
GdkEventButton * event)
282
BaconVolumeButton *button = BACON_VOLUME_BUTTON (widget);
283
GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
284
gint x, y, m, dx, dy, sx, sy, ystartoff, mouse_y;
288
/* position roughly */
289
gdk_window_get_origin (widget->window, &x, &y);
290
x += widget->allocation.x;
291
y += widget->allocation.y;
292
gtk_window_move (GTK_WINDOW (button->dock), x, y - (SCALE_SIZE / 2));
293
gtk_widget_show_all (button->dock);
294
gdk_window_get_origin (button->dock->window, &dx, &dy);
295
dy += button->dock->allocation.y;
296
gdk_window_get_origin (button->scale->window, &sx, &sy);
297
sy += button->scale->allocation.y;
300
button->timeout = TRUE;
302
/* position (needs widget to be shown already) */
303
v = bacon_volume_button_get_value (button) / (adj->upper - adj->lower);
304
x += (widget->allocation.width - button->dock->allocation.width) / 2;
306
y -= GTK_RANGE (button->scale)->min_slider_size / 2;
307
m = button->scale->allocation.height -
308
GTK_RANGE (button->scale)->min_slider_size;
311
gtk_window_move (GTK_WINDOW (button->dock), x, y);
312
gdk_window_get_origin (button->scale->window, &sx, &sy);
314
GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event);
317
gtk_widget_grab_focus (button->dock);
318
gtk_grab_add (button->dock);
319
gdk_pointer_grab (button->dock->window, TRUE,
320
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
321
GDK_POINTER_MOTION_MASK, NULL, NULL, GDK_CURRENT_TIME);
322
gdk_keyboard_grab (button->dock->window, TRUE, GDK_CURRENT_TIME);
324
/* forward event to the slider */
325
e = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
326
e->window = button->scale->window;
328
/* position: the X position isn't relevant, halfway will work just fine.
329
* The vertical position should be *exactly* in the middle of the slider
330
* of the scale; if we don't do that correctly, it'll move from its current
331
* position, which means a position change on-click, which is bad. */
332
e->x = button->scale->allocation.width / 2;
333
m = button->scale->allocation.height -
334
GTK_RANGE (button->scale)->min_slider_size;
335
e->y = ((1.0 - v) * m) + GTK_RANGE (button->scale)->min_slider_size / 2;
336
gtk_widget_event (button->scale, (GdkEvent *) e);
337
e->window = event->window;
338
gdk_event_free ((GdkEvent *) e);
340
button->pop_time = event->time;
346
bacon_volume_key_release (GtkWidget * widget,
349
BaconVolumeButton *button = BACON_VOLUME_BUTTON (widget);
350
GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
351
gint x, y, m, dx, dy, sx, sy, ystartoff;
354
if (event->keyval != GDK_space && event->keyval != GDK_Return)
357
/* position roughly */
358
gdk_window_get_origin (widget->window, &x, &y);
359
x += widget->allocation.x;
360
y += widget->allocation.y;
361
gtk_window_move (GTK_WINDOW (button->dock), x, y - (SCALE_SIZE / 2));
362
gtk_widget_show_all (button->dock);
363
gdk_window_get_origin (button->dock->window, &dx, &dy);
364
dy += button->dock->allocation.y;
365
gdk_window_get_origin (button->scale->window, &sx, &sy);
366
sy += button->scale->allocation.y;
368
button->timeout = TRUE;
370
/* position (needs widget to be shown already) */
371
v = bacon_volume_button_get_value (button) / (adj->upper - adj->lower);
372
x += (widget->allocation.width - button->dock->allocation.width) / 2;
374
y -= GTK_RANGE (button->scale)->min_slider_size / 2;
375
m = button->scale->allocation.height -
376
GTK_RANGE (button->scale)->min_slider_size;
378
gtk_window_move (GTK_WINDOW (button->dock), x, y);
379
gdk_window_get_origin (button->scale->window, &sx, &sy);
382
gtk_widget_grab_focus (button->dock);
383
gtk_grab_add (button->dock);
384
gdk_pointer_grab (button->dock->window, TRUE,
385
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
386
NULL, NULL, GDK_CURRENT_TIME);
387
gdk_keyboard_grab (button->dock->window, TRUE, GDK_CURRENT_TIME);
389
gtk_widget_grab_focus (button->scale);
391
button->pop_time = event->time;
397
* +/- button callbacks.
401
cb_button_timeout (gpointer data)
403
BaconVolumeButton *button = BACON_VOLUME_BUTTON (data);
404
GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
408
if (button->click_id == 0)
411
val = bacon_volume_button_get_value (button);
412
val += button->direction;
413
if (val <= adj->lower) {
416
} else if (val > adj->upper) {
420
bacon_volume_button_set_value (button, val);
423
g_source_remove (button->click_id);
424
button->click_id = 0;
431
cb_button_press (GtkWidget * widget,
432
GdkEventButton * event,
435
BaconVolumeButton *button = BACON_VOLUME_BUTTON (data);
436
GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
438
if (button->click_id != 0)
439
g_source_remove (button->click_id);
440
button->direction = (widget == button->plus) ?
441
fabs (adj->page_increment) : - fabs (adj->page_increment);
442
button->click_id = g_timeout_add (CLICK_TIMEOUT,
443
(GSourceFunc) cb_button_timeout, button);
444
cb_button_timeout (button);
450
cb_button_release (GtkWidget * widget,
451
GdkEventButton * event,
454
BaconVolumeButton *button = BACON_VOLUME_BUTTON (data);
456
if (button->click_id != 0) {
457
g_source_remove (button->click_id);
458
button->click_id = 0;
469
bacon_volume_release_grab (BaconVolumeButton *button,
470
GdkEventButton * event)
475
gdk_keyboard_ungrab (GDK_CURRENT_TIME);
476
gdk_pointer_ungrab (GDK_CURRENT_TIME);
477
gtk_grab_remove (button->dock);
480
gtk_widget_hide (button->dock);
481
button->timeout = FALSE;
483
e = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
484
e->window = GTK_WIDGET (button)->window;
485
e->type = GDK_BUTTON_RELEASE;
486
gtk_widget_event (GTK_WIDGET (button), (GdkEvent *) e);
487
e->window = event->window;
488
gdk_event_free ((GdkEvent *) e);
492
cb_dock_button_press (GtkWidget * widget,
493
GdkEventButton * event,
496
//GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *) event);
497
BaconVolumeButton *button = BACON_VOLUME_BUTTON (data);
499
if (/*ewidget == button->dock &&*/ event->type == GDK_BUTTON_PRESS) {
500
bacon_volume_release_grab (button, event);
508
cb_dock_key_release (GtkWidget * widget,
512
BaconVolumeButton *button = BACON_VOLUME_BUTTON (data);
514
if (event->keyval == GDK_Escape) {
516
gdk_keyboard_ungrab (GDK_CURRENT_TIME);
517
gdk_pointer_ungrab (GDK_CURRENT_TIME);
518
gtk_grab_remove (button->dock);
521
gtk_widget_hide (button->dock);
522
button->timeout = FALSE;
530
cb_dock_key_press (GtkWidget * widget,
534
if (event->keyval == GDK_Escape) {
544
#define BACON_TYPE_VOLUME_SCALE \
545
(bacon_volume_scale_get_type ())
546
#define BACON_VOLUME_SCALE(obj) \
547
(G_TYPE_CHECK_INSTANCE_CAST ((obj), BACON_TYPE_VOLUME_SCALE, \
550
typedef struct _BaconVolumeScale {
552
BaconVolumeButton *button;
555
static GType bacon_volume_scale_get_type (void);
557
static void bacon_volume_scale_class_init (GtkVScaleClass * klass);
559
static gboolean bacon_volume_scale_press (GtkWidget * widget,
560
GdkEventButton * event);
561
static gboolean bacon_volume_scale_release (GtkWidget * widget,
562
GdkEventButton * event);
564
static GtkVScaleClass *scale_parent_class = NULL;
567
bacon_volume_scale_get_type (void)
569
static GType bacon_volume_scale_type = 0;
571
if (!bacon_volume_scale_type) {
572
static const GTypeInfo bacon_volume_scale_info = {
573
sizeof (GtkVScaleClass),
576
(GClassInitFunc) bacon_volume_scale_class_init,
579
sizeof (BaconVolumeScale),
585
bacon_volume_scale_type =
586
g_type_register_static (GTK_TYPE_VSCALE,
588
&bacon_volume_scale_info, 0);
591
return bacon_volume_scale_type;
595
bacon_volume_scale_class_init (GtkVScaleClass * klass)
597
GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS (klass);
598
GtkRangeClass *gtkrange_class = GTK_RANGE_CLASS (klass);
600
scale_parent_class = g_type_class_ref (GTK_TYPE_VSCALE);
602
gtkwidget_class->button_press_event = bacon_volume_scale_press;
603
gtkwidget_class->button_release_event = bacon_volume_scale_release;
604
gtkrange_class->value_changed = bacon_volume_scale_value_changed;
608
bacon_volume_scale_new (BaconVolumeButton * button,
609
float min, float max,
612
BaconVolumeScale *scale = g_object_new (BACON_TYPE_VOLUME_SCALE, NULL);
615
adj = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
616
gtk_range_set_adjustment (GTK_RANGE (scale), GTK_ADJUSTMENT (adj));
617
scale->button = button;
619
return GTK_WIDGET (scale);
623
bacon_volume_scale_press (GtkWidget * widget,
624
GdkEventButton * event)
626
BaconVolumeScale *scale = BACON_VOLUME_SCALE (widget);
627
BaconVolumeButton *button = scale->button;
629
/* the scale will grab input; if we have input grabbed, all goes
630
* horribly wrong, so let's not do that. */
631
gtk_grab_remove (button->dock);
633
return GTK_WIDGET_CLASS (scale_parent_class)->button_press_event (widget, event);
637
bacon_volume_scale_release (GtkWidget * widget,
638
GdkEventButton * event)
640
BaconVolumeScale *scale = BACON_VOLUME_SCALE (widget);
641
BaconVolumeButton *button = scale->button;
644
if (button->timeout) {
645
/* if we did a quick click, leave the window open; else, hide it */
646
if (event->time > button->pop_time + CLICK_TIMEOUT) {
647
bacon_volume_release_grab (button, event);
648
GTK_WIDGET_CLASS (scale_parent_class)->button_release_event (widget, event);
651
button->timeout = FALSE;
654
res = GTK_WIDGET_CLASS (scale_parent_class)->button_release_event (widget, event);
656
/* the scale will release input; right after that, we *have to* grab
657
* it back so we can catch out-of-scale clicks and hide the popup,
658
* so I basically want a g_signal_connect_after_always(), but I can't
659
* find that, so we do this complex 'first-call-parent-then-do-actual-
660
* action' thingy... */
661
gtk_grab_add (button->dock);
667
bacon_volume_scale_value_changed (GtkRange * range)
669
BaconVolumeScale *scale = BACON_VOLUME_SCALE (range);
670
BaconVolumeButton *button = scale->button;
671
GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
672
float step = (adj->upper - adj->lower) / 4;
673
float val = gtk_range_get_value (range);
679
s = g_strdup_printf ("%d", lrintf (val));
680
gtk_button_set_label (GTK_BUTTON (button), s);
686
if (val == adj->lower)
687
s = "stock_volume-0";
688
else if (val > adj->lower && val <= adj->lower + step)
689
s = "stock_volume-min";
690
else if (val > adj->lower + step && val <= adj->lower + step * 2)
691
s = "stock_volume-med";
693
s = "stock_volume-max";
696
gtk_icon_size_lookup (button->size, &w, &h);
697
buf = gtk_icon_theme_load_icon (button->theme, s, w, 0, NULL);
698
gtk_image_set_from_pixbuf (GTK_IMAGE (button->image), buf);
702
g_signal_emit (button, signals[SIGNAL_VALUE_CHANGED], 0);
706
* vim: sw=2 ts=8 cindent noai bs=2