Updated Finnish translation
[rhythmbox.git] / widgets / bacon-volume.c
blob5e78338c12c1340604ec41eb46c3ffc7514061f3
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.
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 "bacon-volume.h"
32 #define SCALE_SIZE 100
33 #define CLICK_TIMEOUT 250
35 enum {
36 SIGNAL_VALUE_CHANGED,
37 NUM_SIGNALS
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,
50 gpointer data);
52 static gboolean cb_button_press (GtkWidget * widget,
53 GdkEventButton * event,
54 gpointer data);
55 static gboolean cb_button_release (GtkWidget * widget,
56 GdkEventButton * event,
57 gpointer data);
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,
62 float min, float max,
63 float step);
65 static GtkButtonClass *parent_class = NULL;
66 static guint signals[NUM_SIGNALS] = { 0 };
68 GType
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),
76 NULL,
77 NULL,
78 (GClassInitFunc) bacon_volume_button_class_init,
79 NULL,
80 NULL,
81 sizeof (BaconVolumeButton),
83 (GInstanceInitFunc) bacon_volume_button_init,
84 NULL
87 bacon_volume_button_type =
88 g_type_register_static (GTK_TYPE_BUTTON,
89 "BaconVolumeButton",
90 &bacon_volume_button_info, 0);
93 return bacon_volume_button_type;
96 static void
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);
104 /* events */
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;
109 /* signals */
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);
116 static void
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 ();
124 #endif
127 static void
128 bacon_volume_button_dispose (GObject *object)
130 BaconVolumeButton *button = BACON_VOLUME_BUTTON (object);
132 if (button->dock) {
133 gtk_widget_destroy (button->dock);
134 button->dock = NULL;
137 if (button->theme) {
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);
151 * public API.
154 GtkWidget *
155 bacon_volume_button_new (GtkIconSize size,
156 float min, float max,
157 float step)
159 BaconVolumeButton *button;
160 GtkWidget *frame, *box;
162 button = g_object_new (BACON_TYPE_VOLUME_BUTTON, NULL);
163 button->size = size;
164 gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
166 #ifndef HAVE_GTK_ONLY
167 /* image */
168 button->image = gtk_image_new ();
169 gtk_container_add (GTK_CONTAINER (button), button->image);
170 gtk_widget_show_all (button->image);
171 #endif
173 /* window */
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);
179 /* frame */
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);
186 /* + */
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);
195 /* scale */
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);
202 /* - */
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);
217 float
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));
225 void
226 bacon_volume_button_set_value (BaconVolumeButton * button,
227 float value)
229 g_return_if_fail (button != NULL);
231 gtk_range_set_value (GTK_RANGE (button->scale), value);
235 * button callbacks.
238 static gboolean
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));
244 float d;
246 if (event->type != GDK_SCROLL)
247 return FALSE;
249 d = bacon_volume_button_get_value (button);
250 if (event->direction == GDK_SCROLL_UP) {
251 d += adj->step_increment;
252 if (d > adj->upper)
253 d = adj->upper;
254 } else {
255 d -= adj->step_increment;
256 if (d < adj->lower)
257 d = adj->lower;
259 bacon_volume_button_set_value (button, d);
261 return TRUE;
264 static gboolean
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;
271 float v;
272 GdkEventButton *e;
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;
284 ystartoff = sy - dy;
285 mouse_y = event->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;
291 y -= ystartoff;
292 y -= GTK_RANGE (button->scale)->min_slider_size / 2;
293 m = button->scale->allocation.height -
294 GTK_RANGE (button->scale)->min_slider_size;
295 y -= m * (1.0 - v);
296 y += mouse_y;
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);
302 /* grab focus */
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;
328 return TRUE;
332 * +/- button callbacks.
335 static gboolean
336 button_timeout (BaconVolumeButton *button)
338 GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (button->scale));
339 float val;
340 gboolean res = TRUE;
342 if (button->click_id == 0)
343 return FALSE;
345 val = bacon_volume_button_get_value (button);
346 val += button->direction;
347 if (val <= adj->lower) {
348 res = FALSE;
349 val = adj->lower;
350 } else if (val > adj->upper) {
351 res = FALSE;
352 val = adj->upper;
354 bacon_volume_button_set_value (button, val);
356 if (!res) {
357 g_source_remove (button->click_id);
358 button->click_id = 0;
361 return res;
364 static gboolean
365 cb_button_timeout (gpointer data)
367 BaconVolumeButton *button = BACON_VOLUME_BUTTON (data);
368 gboolean r;
370 GDK_THREADS_ENTER ();
371 r = button_timeout (button);
372 GDK_THREADS_LEAVE ();
374 return r;
378 static gboolean
379 cb_button_press (GtkWidget * widget,
380 GdkEventButton * event,
381 gpointer data)
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);
394 return TRUE;
397 static gboolean
398 cb_button_release (GtkWidget * widget,
399 GdkEventButton * event,
400 gpointer data)
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;
409 return TRUE;
413 * Scale callbacks.
416 static void
417 bacon_volume_release_grab (BaconVolumeButton *button,
418 GdkEventButton * event)
420 GdkEventButton *e;
422 /* ungrab focus */
423 gdk_keyboard_ungrab (GDK_CURRENT_TIME);
424 gdk_pointer_ungrab (GDK_CURRENT_TIME);
425 gtk_grab_remove (button->dock);
427 /* hide again */
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);
439 static gboolean
440 cb_dock_press (GtkWidget * widget,
441 GdkEventButton * event,
442 gpointer data)
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);
449 return TRUE;
452 return FALSE;
456 * Scale stuff.
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, \
463 BaconVolumeScale))
465 typedef struct _BaconVolumeScale {
466 GtkVScale parent;
467 BaconVolumeButton *button;
468 } BaconVolumeScale;
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;
481 static GType
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),
489 NULL,
490 NULL,
491 (GClassInitFunc) bacon_volume_scale_class_init,
492 NULL,
493 NULL,
494 sizeof (BaconVolumeScale),
496 NULL,
497 NULL
500 bacon_volume_scale_type =
501 g_type_register_static (GTK_TYPE_VSCALE,
502 "BaconVolumeScale",
503 &bacon_volume_scale_info, 0);
506 return bacon_volume_scale_type;
509 static void
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;
522 static GtkWidget *
523 bacon_volume_scale_new (BaconVolumeButton * button,
524 float min, float max,
525 float step)
527 BaconVolumeScale *scale = g_object_new (BACON_TYPE_VOLUME_SCALE, NULL);
528 GtkObject *adj;
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);
537 static gboolean
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);
551 static gboolean
552 bacon_volume_scale_release (GtkWidget * widget,
553 GdkEventButton * event)
555 BaconVolumeScale *scale = BACON_VOLUME_SCALE (widget);
556 BaconVolumeButton *button = scale->button;
557 gboolean res;
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);
564 return TRUE;
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);
578 return res;
581 static void
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);
589 gint w, h;
590 #ifdef HAVE_GTK_ONLY
591 char *s;
593 /* update label */
594 s = g_strdup_printf ("%d", lrintf (val));
595 gtk_button_set_label (GTK_BUTTON (button), s);
596 g_free (s);
597 #else
598 const char *s;
599 GdkPixbuf *buf;
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";
607 else
608 s = "stock_volume-max";
610 /* update image */
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);
614 if (buf != NULL) {
615 g_object_unref (buf);
617 #endif
619 /* signal */
620 g_signal_emit (button, signals[SIGNAL_VALUE_CHANGED], 0);