1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
3
* arch-tag: Implementation of Rhythmbox volume control button
5
* Copyright (C) 2003 Colin Walters <walters@rhythmbox.org>
8
* (C) Copyright 2001, Richard Hult
10
* Author: Richard Hult <rhult@codefactory.se>
12
* Loosely based on the mixer applet:
14
* GNOME audio mixer module
15
* (C) 1998 The Free Software Foundation
17
* Author: Michael Fulbright <msf@redhat.com>:
21
* GNOME time/date display module.
22
* (C) 1997 The Free Software Foundation
24
* Authors: Miguel de Icaza
27
* This program is free software; you can redistribute it and/or modify
28
* it under the terms of the GNU General Public License as published by
29
* the Free Software Foundation; either version 2 of the License, or
30
* (at your option) any later version.
32
* This program is distributed in the hope that it will be useful,
33
* but WITHOUT ANY WARRANTY; without even the implied warranty of
34
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35
* GNU General Public License for more details.
37
* You should have received a copy of the GNU General Public License
38
* along with this program; if not, write to the Free Software
39
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
44
#include <gdk/gdkkeysyms.h>
46
#include <libgnome/gnome-i18n.h>
47
#include <libgnome/gnome-url.h>
49
#include "rb-volume.h"
51
#include "rb-stock-icons.h"
52
#include "eel-gconf-extensions.h"
53
#include "rb-preferences.h"
56
static void rb_volume_class_init (RBVolumeClass *klass);
57
static void rb_volume_init (RBVolume *link);
58
static void rb_volume_finalize (GObject *object);
59
static void rb_volume_sync_volume (RBVolume *volume);
60
static void clicked_cb (GtkButton *button, RBVolume *volume);
61
static gboolean scroll_cb (GtkWidget *widget, GdkEvent *event, gpointer unused);
62
static gboolean scale_button_release_event_cb (GtkWidget *widget,
63
GdkEventButton *event, RBVolume *volume);
64
static gboolean scale_button_event_cb (GtkWidget *widget, GdkEventButton *event,
66
static gboolean scale_key_press_event_cb (GtkWidget *widget, GdkEventKey *event,
68
static void mixer_value_changed_cb (GtkAdjustment *adj, RBVolume *volume);
69
static void volume_changed_cb (GConfClient *client, guint cnxn_id,
70
GConfEntry *entry, RBVolume *volume);
72
#define VOLUME_MAX 1.0
74
struct RBVolumePrivate
84
GtkWidget *medium_image;
86
GtkWidget *zero_image;
94
static GtkEventBoxClass *parent_class = NULL;
97
rb_volume_get_type (void)
99
static GType rb_volume_type = 0;
101
if (rb_volume_type == 0)
103
static const GTypeInfo our_info =
105
sizeof (RBVolumeClass),
108
(GClassInitFunc) rb_volume_class_init,
113
(GInstanceInitFunc) rb_volume_init
116
rb_volume_type = g_type_register_static (GTK_TYPE_EVENT_BOX,
121
return rb_volume_type;
125
rb_volume_class_init (RBVolumeClass *klass)
127
GObjectClass *object_class = G_OBJECT_CLASS (klass);
129
parent_class = g_type_class_peek_parent (klass);
131
object_class->finalize = rb_volume_finalize;
135
rb_volume_init (RBVolume *volume)
138
GtkWidget *inner_frame;
139
GtkWidget *pluslabel, *minuslabel;
143
volume->priv = g_new0 (RBVolumePrivate, 1);
145
volume->priv->button = gtk_button_new ();
147
gtk_container_add (GTK_CONTAINER (volume), volume->priv->button);
149
volume->priv->max_image = rb_image_new_from_stock (RB_STOCK_VOLUME_MAX,
150
GTK_ICON_SIZE_LARGE_TOOLBAR);
151
g_object_ref (G_OBJECT (volume->priv->max_image));
152
volume->priv->medium_image = rb_image_new_from_stock (RB_STOCK_VOLUME_MEDIUM,
153
GTK_ICON_SIZE_LARGE_TOOLBAR);
154
g_object_ref (G_OBJECT (volume->priv->medium_image));
155
volume->priv->min_image = rb_image_new_from_stock (RB_STOCK_VOLUME_MIN,
156
GTK_ICON_SIZE_LARGE_TOOLBAR);
157
g_object_ref (G_OBJECT (volume->priv->min_image));
158
volume->priv->zero_image = rb_image_new_from_stock (RB_STOCK_VOLUME_ZERO,
159
GTK_ICON_SIZE_LARGE_TOOLBAR);
160
g_object_ref (G_OBJECT (volume->priv->zero_image));
162
gtk_container_add (GTK_CONTAINER (volume->priv->button), volume->priv->max_image);
164
g_signal_connect_object (G_OBJECT (volume->priv->button), "clicked",
165
G_CALLBACK (clicked_cb), volume, 0);
166
g_signal_connect_object (G_OBJECT (volume->priv->button), "scroll_event",
167
G_CALLBACK (scroll_cb),
169
gtk_widget_show_all (GTK_WIDGET (volume));
171
volume->priv->window = gtk_window_new (GTK_WINDOW_POPUP);
173
volume->priv->adj = GTK_ADJUSTMENT (gtk_adjustment_new (50,
179
g_signal_connect_object (volume->priv->adj,
181
(GCallback) mixer_value_changed_cb,
184
frame = gtk_frame_new (NULL);
185
gtk_container_set_border_width (GTK_CONTAINER (frame), 0);
186
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
188
inner_frame = gtk_frame_new (NULL);
189
gtk_container_set_border_width (GTK_CONTAINER (inner_frame), 0);
190
gtk_frame_set_shadow_type (GTK_FRAME (inner_frame), GTK_SHADOW_NONE);
192
event = gtk_event_box_new ();
193
/* This signal is to not let button press close the popup when the press is
195
g_signal_connect_after (event, "button_press_event",
196
G_CALLBACK (scale_button_event_cb), volume);
198
box = gtk_vbox_new (FALSE, 0);
199
volume->priv->scale = gtk_vscale_new (volume->priv->adj);
200
gtk_range_set_inverted (GTK_RANGE (volume->priv->scale), TRUE);
201
gtk_widget_set_size_request (volume->priv->scale, -1, 100);
203
g_signal_connect_object (G_OBJECT (volume->priv->window), "scroll_event",
204
G_CALLBACK (scroll_cb),
207
g_signal_connect_object (G_OBJECT (volume->priv->window),
208
"button-press-event",
209
(GCallback) scale_button_release_event_cb,
212
/* button event on the scale widget are not catched by its parent window
213
** so we must connect to this widget as well */
214
g_signal_connect_object (G_OBJECT (volume->priv->scale),
215
"button-release-event",
216
(GCallback) scale_button_release_event_cb,
219
g_signal_connect_object (G_OBJECT (volume->priv->scale),
221
(GCallback) scale_key_press_event_cb,
224
gtk_scale_set_draw_value (GTK_SCALE (volume->priv->scale), FALSE);
226
gtk_range_set_update_policy (GTK_RANGE (volume->priv->scale),
227
GTK_UPDATE_CONTINUOUS);
229
gtk_container_add (GTK_CONTAINER (volume->priv->window), frame);
231
gtk_container_add (GTK_CONTAINER (frame), inner_frame);
233
/* Translators - The + and - refer to increasing and decreasing the volume.
234
** I don't know if there are sensible alternatives in other languages */
235
pluslabel = gtk_label_new (_("+"));
236
minuslabel = gtk_label_new (_("-"));
238
gtk_box_pack_start (GTK_BOX (box), pluslabel, FALSE, FALSE, 0);
239
gtk_box_pack_end (GTK_BOX (box), minuslabel, FALSE, FALSE, 0);
240
gtk_box_pack_start (GTK_BOX (box), volume->priv->scale, TRUE, TRUE, 0);
242
gtk_container_add (GTK_CONTAINER (event), box);
243
gtk_container_add (GTK_CONTAINER (inner_frame), event);
245
eel_gconf_notification_add (CONF_STATE_VOLUME,
246
(GConfClientNotifyFunc) volume_changed_cb,
248
rb_volume_sync_volume (volume);
252
rb_volume_finalize (GObject *object)
256
g_return_if_fail (object != NULL);
257
g_return_if_fail (RB_IS_VOLUME (object));
259
volume = RB_VOLUME (object);
261
g_return_if_fail (volume->priv != NULL);
263
g_free (volume->priv);
265
G_OBJECT_CLASS (parent_class)->finalize (object);
273
volume = RB_VOLUME (g_object_new (RB_TYPE_VOLUME, NULL));
275
g_return_val_if_fail (volume->priv != NULL, NULL);
281
rb_volume_sync_volume (RBVolume *volume)
285
vol = eel_gconf_get_float (CONF_STATE_VOLUME);
286
rb_debug ("current volume is %f", vol);
287
rb_volume_set_volume (volume, vol);
291
rb_volume_set_volume (RBVolume *volume, float vol)
295
gtk_container_remove (GTK_CONTAINER (volume->priv->button),
296
gtk_bin_get_child (GTK_BIN (volume->priv->button)));
299
image = volume->priv->zero_image;
300
else if (vol <= (VOLUME_MAX / 3.0))
301
image = volume->priv->min_image;
302
else if (vol <= 2.0 * (VOLUME_MAX / 3.0))
303
image = volume->priv->medium_image;
305
image = volume->priv->max_image;
307
gtk_widget_show (image);
308
gtk_container_add (GTK_CONTAINER (volume->priv->button), image);
310
gtk_adjustment_set_value (volume->priv->adj, vol);
314
clicked_cb (GtkButton *button, RBVolume *volume)
317
GdkGrabStatus pointer, keyboard;
319
gint button_width, button_height;
320
gint window_width, window_height;
324
gint volume_slider_x;
325
gint volume_slider_y;
327
rb_debug ("volume clicked");
329
/* if (GTK_WIDGET_VISIBLE (GTK_WIDGET (volume->priv->window))) */
333
* Position the popup right next to the button.
336
max_y = gdk_screen_height ();
338
gtk_widget_size_request (GTK_WIDGET (volume->priv->window), &req);
340
gdk_window_get_origin (gtk_widget_get_parent_window (GTK_BIN (volume->priv->button)->child), &x, &y);
341
gdk_drawable_get_size (gtk_widget_get_parent_window (GTK_BIN (volume->priv->button)->child), &button_width, &button_height);
342
rb_debug ("window origin: %d %d; size: %d %d", x, y, button_width, button_height);
346
gtk_widget_show_all (volume->priv->window);
347
gdk_drawable_get_size (gtk_widget_get_parent_window (GTK_BIN (volume->priv->window)->child), &window_width, &window_height);
349
volume_slider_x = x + (button_width - window_width) / 2;
351
if (y + button_width + window_height + spacing < max_y) {
352
/* if volume slider will fit on the screen, display it under
355
volume_slider_y = y + button_width + spacing;
357
/* otherwise display it above the volume button */
358
volume_slider_y = y - window_height - spacing;
361
gtk_window_move (GTK_WINDOW (volume->priv->window), volume_slider_x, volume_slider_y);
364
* Grab focus and pointer.
366
rb_debug ("grabbing focus");
367
gtk_widget_grab_focus (volume->priv->window);
368
gtk_grab_add (volume->priv->window);
370
pointer = gdk_pointer_grab (volume->priv->window->window,
372
(GDK_BUTTON_PRESS_MASK |
373
GDK_BUTTON_RELEASE_MASK |
374
GDK_POINTER_MOTION_MASK |
376
NULL, NULL, GDK_CURRENT_TIME);
378
keyboard = gdk_keyboard_grab (volume->priv->window->window,
382
if (pointer != GDK_GRAB_SUCCESS || keyboard != GDK_GRAB_SUCCESS) {
383
/* We could not grab. */
384
rb_debug ("grab failed");
385
gtk_grab_remove (volume->priv->window);
386
gtk_widget_hide (volume->priv->window);
388
if (pointer == GDK_GRAB_SUCCESS) {
389
gdk_keyboard_ungrab (GDK_CURRENT_TIME);
391
if (keyboard == GDK_GRAB_SUCCESS) {
392
gdk_pointer_ungrab (GDK_CURRENT_TIME);
395
g_warning ("Could not grab X server!");
399
/* gtk_frame_set_shadow_type (GTK_FRAME (volume->priv->frame), GTK_SHADOW_IN); */
403
scroll_cb (GtkWidget *widget, GdkEvent *event, gpointer unused)
405
float volume = eel_gconf_get_float (CONF_STATE_VOLUME);
407
switch(event->scroll.direction) {
413
case GDK_SCROLL_DOWN:
418
case GDK_SCROLL_LEFT:
419
case GDK_SCROLL_RIGHT:
423
rb_debug ("got scroll, setting volume to %f", volume);
424
eel_gconf_set_float (CONF_STATE_VOLUME, volume);
431
rb_volume_popup_hide (RBVolume *volume)
433
rb_debug ("hiding popup");
434
gtk_grab_remove (volume->priv->window);
435
gdk_pointer_ungrab (GDK_CURRENT_TIME);
436
gdk_keyboard_ungrab (GDK_CURRENT_TIME);
438
gtk_widget_hide (GTK_WIDGET (volume->priv->window));
440
/* gtk_frame_set_shadow_type (GTK_FRAME (data->frame), GTK_SHADOW_NONE); */
444
scale_button_release_event_cb (GtkWidget *widget, GdkEventButton *event, RBVolume *volume)
446
rb_debug ("scale release");
447
rb_volume_popup_hide (volume);
452
scale_button_event_cb (GtkWidget *widget, GdkEventButton *event, RBVolume *volume)
459
scale_key_press_event_cb (GtkWidget *widget, GdkEventKey *event, RBVolume *volume)
461
rb_debug ("got key press");
462
switch (event->keyval) {
469
rb_volume_popup_hide (volume);
479
mixer_value_changed_cb (GtkAdjustment *adj, RBVolume *volume)
481
float vol = gtk_adjustment_get_value (volume->priv->adj);
483
rb_debug ("setting volume to %f", vol);
485
eel_gconf_set_float (CONF_STATE_VOLUME, vol);
488
static void volume_changed_cb (GConfClient *client, guint cnxn_id,
489
GConfEntry *entry, RBVolume *volume)
491
rb_debug ("volume changed");
493
rb_volume_sync_volume (volume);