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 "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
cb_button_timeout (gpointer data)
338
BaconVolumeButton *button = BACON_VOLUME_BUTTON (data);
339
GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
343
if (button->click_id == 0)
346
val = bacon_volume_button_get_value (button);
347
val += button->direction;
348
if (val <= adj->lower) {
351
} else if (val > adj->upper) {
355
bacon_volume_button_set_value (button, val);
358
g_source_remove (button->click_id);
359
button->click_id = 0;
366
cb_button_press (GtkWidget * widget,
367
GdkEventButton * event,
370
BaconVolumeButton *button = BACON_VOLUME_BUTTON (data);
371
GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
373
if (button->click_id != 0)
374
g_source_remove (button->click_id);
375
button->direction = (widget == button->plus) ?
376
fabs (adj->page_increment) : - fabs (adj->page_increment);
377
button->click_id = g_timeout_add (CLICK_TIMEOUT,
378
(GSourceFunc) cb_button_timeout, button);
379
cb_button_timeout (button);
385
cb_button_release (GtkWidget * widget,
386
GdkEventButton * event,
389
BaconVolumeButton *button = BACON_VOLUME_BUTTON (data);
391
if (button->click_id != 0) {
392
g_source_remove (button->click_id);
393
button->click_id = 0;
404
bacon_volume_release_grab (BaconVolumeButton *button,
405
GdkEventButton * event)
410
gdk_keyboard_ungrab (GDK_CURRENT_TIME);
411
gdk_pointer_ungrab (GDK_CURRENT_TIME);
412
gtk_grab_remove (button->dock);
415
gtk_widget_hide (button->dock);
416
button->timeout = FALSE;
418
e = (GdkEventButton *) gdk_event_copy ((GdkEvent *) event);
419
e->window = GTK_WIDGET (button)->window;
420
e->type = GDK_BUTTON_RELEASE;
421
gtk_widget_event (GTK_WIDGET (button), (GdkEvent *) e);
422
e->window = event->window;
423
gdk_event_free ((GdkEvent *) e);
427
cb_dock_press (GtkWidget * widget,
428
GdkEventButton * event,
431
//GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *) event);
432
BaconVolumeButton *button = BACON_VOLUME_BUTTON (data);
434
if (/*ewidget == button->dock &&*/ event->type == GDK_BUTTON_PRESS) {
435
bacon_volume_release_grab (button, event);
446
#define BACON_TYPE_VOLUME_SCALE \
447
(bacon_volume_scale_get_type ())
448
#define BACON_VOLUME_SCALE(obj) \
449
(G_TYPE_CHECK_INSTANCE_CAST ((obj), BACON_TYPE_VOLUME_SCALE, \
452
typedef struct _BaconVolumeScale {
454
BaconVolumeButton *button;
457
static GType bacon_volume_scale_get_type (void);
459
static void bacon_volume_scale_class_init (GtkVScaleClass * klass);
461
static gboolean bacon_volume_scale_press (GtkWidget * widget,
462
GdkEventButton * event);
463
static gboolean bacon_volume_scale_release (GtkWidget * widget,
464
GdkEventButton * event);
466
static GtkVScaleClass *scale_parent_class = NULL;
469
bacon_volume_scale_get_type (void)
471
static GType bacon_volume_scale_type = 0;
473
if (!bacon_volume_scale_type) {
474
static const GTypeInfo bacon_volume_scale_info = {
475
sizeof (GtkVScaleClass),
478
(GClassInitFunc) bacon_volume_scale_class_init,
481
sizeof (BaconVolumeScale),
487
bacon_volume_scale_type =
488
g_type_register_static (GTK_TYPE_VSCALE,
490
&bacon_volume_scale_info, 0);
493
return bacon_volume_scale_type;
497
bacon_volume_scale_class_init (GtkVScaleClass * klass)
499
GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS (klass);
500
GtkRangeClass *gtkrange_class = GTK_RANGE_CLASS (klass);
502
scale_parent_class = g_type_class_ref (GTK_TYPE_VSCALE);
504
gtkwidget_class->button_press_event = bacon_volume_scale_press;
505
gtkwidget_class->button_release_event = bacon_volume_scale_release;
506
gtkrange_class->value_changed = bacon_volume_scale_value_changed;
510
bacon_volume_scale_new (BaconVolumeButton * button,
511
float min, float max,
514
BaconVolumeScale *scale = g_object_new (BACON_TYPE_VOLUME_SCALE, NULL);
517
adj = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
518
gtk_range_set_adjustment (GTK_RANGE (scale), GTK_ADJUSTMENT (adj));
519
scale->button = button;
521
return GTK_WIDGET (scale);
525
bacon_volume_scale_press (GtkWidget * widget,
526
GdkEventButton * event)
528
BaconVolumeScale *scale = BACON_VOLUME_SCALE (widget);
529
BaconVolumeButton *button = scale->button;
531
/* the scale will grab input; if we have input grabbed, all goes
532
* horribly wrong, so let's not do that. */
533
gtk_grab_remove (button->dock);
535
return GTK_WIDGET_CLASS (scale_parent_class)->button_press_event (widget, event);
539
bacon_volume_scale_release (GtkWidget * widget,
540
GdkEventButton * event)
542
BaconVolumeScale *scale = BACON_VOLUME_SCALE (widget);
543
BaconVolumeButton *button = scale->button;
546
if (button->timeout) {
547
/* if we did a quick click, leave the window open; else, hide it */
548
if (event->time > button->pop_time + CLICK_TIMEOUT) {
549
bacon_volume_release_grab (button, event);
550
GTK_WIDGET_CLASS (scale_parent_class)->button_release_event (widget, event);
553
button->timeout = FALSE;
556
res = GTK_WIDGET_CLASS (scale_parent_class)->button_release_event (widget, event);
558
/* the scale will release input; right after that, we *have to* grab
559
* it back so we can catch out-of-scale clicks and hide the popup,
560
* so I basically want a g_signal_connect_after_always(), but I can't
561
* find that, so we do this complex 'first-call-parent-then-do-actual-
562
* action' thingy... */
563
gtk_grab_add (button->dock);
569
bacon_volume_scale_value_changed (GtkRange * range)
571
BaconVolumeScale *scale = BACON_VOLUME_SCALE (range);
572
BaconVolumeButton *button = scale->button;
573
GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
574
float step = (adj->upper - adj->lower) / 4;
575
float val = gtk_range_get_value (range);
581
s = g_strdup_printf ("%d", lrintf (val));
582
gtk_button_set_label (GTK_BUTTON (button), s);
588
if (val == adj->lower)
589
s = "stock_volume-mute";
590
else if (val > adj->lower && val <= adj->lower + step)
591
s = "stock_volume-0";
592
else if (val > adj->lower + step && val <= adj->lower + step * 2)
593
s = "stock_volume-min";
594
else if (val > adj->lower + step * 2 && val <= adj->lower + step * 3)
595
s = "stock_volume-med";
597
s = "stock_volume-max";
600
gtk_icon_size_lookup (button->size, &w, &h);
601
buf = gtk_icon_theme_load_icon (button->theme, s, w, 0, NULL);
602
gtk_image_set_from_pixbuf (GTK_IMAGE (button->image), buf);
606
g_signal_emit (button, signals[SIGNAL_VALUE_CHANGED], 0);