2006-09-12 Michael Dominic Kostrzewa <michael.kostrzewa@nokia.com>
[hildon] / hildon-widgets / hildon-volumebar.c
1 /*
2  * This file is part of hildon-libs
3  *
4  * Copyright (C) 2005, 2006 Nokia Corporation, all rights reserved.
5  *
6  * Contact: Michael Dominic Kostrzewa <michael.kostrzewa@nokia.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * as published by the Free Software Foundation; version 2.1 of
11  * the License or any later version.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA
22  *
23  */
24
25 /**
26  * SECTION:hildon-volumebar
27  * @short_description: Base class for widgets that display a volume bar
28  * @see_also: #HildonHVolumebar, #HildonVVolumebar
29  *
30  * #HildonVolumebar is a base class for widgets that display a volume bar that
31  * allows increasing or decreasing volume within a predefined range, and muting
32  * the volume when users click the mute icon.
33  */
34
35 #include <gtk/gtkwindow.h>
36 #include <gtk/gtksignal.h>
37 #include <gdk/gdkkeysyms.h>
38
39 #include "hildon-volumebar.h"
40 #include "hildon-volumebar-range.h"
41 #include "hildon-volumebar-private.h"
42
43 static GtkContainerClass *parent_class;
44
45 static void
46 hildon_volumebar_class_init(HildonVolumebarClass * volumebar_class);       
47 static void 
48 hildon_volumebar_init(HildonVolumebar * volumebar);
49
50 static void 
51 hildon_child_forall(GtkContainer * container,
52                     gboolean include_internals,
53                     GtkCallback callback,
54                     gpointer callback_data);
55 static void 
56 hildon_volumebar_destroy(GtkObject * self);
57
58 static void hildon_volumebar_set_property(GObject * object,
59                                            guint prop_id,
60                                            const GValue * value,
61                                            GParamSpec * pspec);
62 static void hildon_volumebar_get_property(GObject * object,
63                                            guint prop_id,
64                                            GValue * value, 
65                                            GParamSpec * pspec);
66
67 static void mute_toggled (HildonVolumebar *self);
68
69 static gboolean
70 hildon_volumebar_key_press(GtkWidget * widget,
71                            GdkEventKey * event);
72
73
74 enum 
75 {
76     MUTE_TOGGLED_SIGNAL,
77     LEVEL_CHANGED_SIGNAL,
78     LAST_SIGNAL
79 };
80
81 enum {
82     PROP_NONE = 0,
83     PROP_HILDON_HAS_MUTE,
84     PROP_HILDON_FOCUSABLE,
85     PROP_HILDON_LEVEL,
86     PROP_HILDON_MUTE
87 };
88
89 static guint signals[LAST_SIGNAL] = { 0 };
90
91 GType 
92 hildon_volumebar_get_type(void)
93 {
94     static GType volumebar_type = 0;
95
96     if (!volumebar_type) {
97         static const GTypeInfo volumebar_info = {
98             sizeof(HildonVolumebarClass),
99             NULL,       /* base_init */
100             NULL,       /* base_finalize */
101             (GClassInitFunc) hildon_volumebar_class_init,
102             NULL,       /* class_finalize */
103             NULL,       /* class_data */
104             sizeof(HildonVolumebar),
105             0,  /* n_preallocs */
106             (GInstanceInitFunc) hildon_volumebar_init,
107         };
108         volumebar_type = g_type_register_static(GTK_TYPE_CONTAINER,
109                                                 "HildonVolumebar",
110                                                 &volumebar_info, 0);
111     }
112     return volumebar_type;
113 }
114
115 static void 
116 hildon_volumebar_class_init(HildonVolumebarClass *volumebar_class)
117 {
118     GObjectClass      *gobject_class   = G_OBJECT_CLASS  (volumebar_class);
119     GtkObjectClass    *object_class    = GTK_OBJECT_CLASS(volumebar_class);
120     GtkWidgetClass    *widget_class    = GTK_WIDGET_CLASS(volumebar_class);
121     GtkContainerClass *container_class = GTK_CONTAINER_CLASS(volumebar_class);
122
123     parent_class = g_type_class_peek_parent(volumebar_class);
124
125     g_type_class_add_private(volumebar_class,
126                              sizeof(HildonVolumebarPrivate));
127
128     /* Because we derived our widget from GtkContainer, we should also
129        override forall method */
130     volumebar_class->mute_toggled = mute_toggled;
131     container_class->forall = hildon_child_forall;
132     widget_class->key_press_event = hildon_volumebar_key_press;
133     object_class->destroy = hildon_volumebar_destroy;
134
135     signals[MUTE_TOGGLED_SIGNAL] = g_signal_new("mute_toggled",
136                                                 G_OBJECT_CLASS_TYPE
137                                                 (object_class),
138                                                 G_SIGNAL_RUN_LAST |
139                                                 G_SIGNAL_ACTION,
140                                                 G_STRUCT_OFFSET
141                                                 (HildonVolumebarClass,
142                                                  mute_toggled), NULL, NULL,
143                                                 gtk_marshal_VOID__VOID,
144                                                 G_TYPE_NONE, 0);
145
146     signals[LEVEL_CHANGED_SIGNAL] = g_signal_new("level_changed",
147                                                  G_OBJECT_CLASS_TYPE
148                                                  (object_class),
149                                                  G_SIGNAL_RUN_LAST |
150                                                  G_SIGNAL_ACTION,
151                                                  G_STRUCT_OFFSET
152                                                  (HildonVolumebarClass,
153                                                   level_changed), NULL,
154                                                  NULL,
155                                                  gtk_marshal_VOID__VOID,
156                                                  G_TYPE_NONE, 0);
157     
158     gobject_class->set_property = hildon_volumebar_set_property;
159     gobject_class->get_property = hildon_volumebar_get_property; 
160
161     /*This kind of property could be usefull in the gtkcontainer*/
162     g_object_class_install_property(gobject_class,
163                                     PROP_HILDON_FOCUSABLE, 
164                                     g_param_spec_boolean("can-focus",
165                                     "The widget focusablility",
166                                 "The widget focusablility. TRUE is focusable",
167                                     TRUE,
168                                     G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
169
170     g_object_class_install_property(gobject_class,
171                                     PROP_HILDON_HAS_MUTE, 
172                                     g_param_spec_boolean("has_mute",
173                                     "Show/Hide the mute button",
174                "Whether the mute button is visible. Default value: TRUE",
175                                     TRUE,
176                                     G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
177
178     g_object_class_install_property(gobject_class,
179                                     PROP_HILDON_LEVEL,
180                                     g_param_spec_double("level",
181                                                         "Level",
182                                                         "Current volume level",
183                                                         0.0,
184                                                         100.0,
185                                                         50.0,
186                                                         G_PARAM_READWRITE));
187
188     g_object_class_install_property(gobject_class,
189                                     PROP_HILDON_MUTE,
190                                     g_param_spec_boolean("mute",
191                                                          "Mute",
192                                                          "Whether volume is muted",
193                                                          FALSE,
194                                                          G_PARAM_READWRITE));
195 }
196
197 static void 
198 hildon_volumebar_init(HildonVolumebar * volumebar)
199 {
200     HildonVolumebarPrivate *priv;
201
202     priv = HILDON_VOLUMEBAR_GET_PRIVATE(volumebar);
203
204     /* Should set GTK_NO_WINDOW flag, because widget is derived from
205        GtkContainer */
206     GTK_WIDGET_SET_FLAGS(GTK_WIDGET(volumebar), GTK_NO_WINDOW);
207     GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(volumebar), GTK_CAN_FOCUS);
208
209     /* Initialize mute button */
210     priv->tbutton = GTK_TOGGLE_BUTTON(gtk_toggle_button_new());
211     g_object_set (G_OBJECT (priv->tbutton), "can-focus", FALSE, NULL);
212 }
213
214 static void
215 hildon_child_forall(GtkContainer * container,
216                     gboolean include_internals,
217                     GtkCallback callback, gpointer callback_data)
218 {
219     HildonVolumebarPrivate *priv;
220
221     g_assert(HILDON_IS_VOLUMEBAR(container));
222     g_assert(callback != NULL);
223
224     priv = HILDON_VOLUMEBAR_GET_PRIVATE(container);
225
226     /* No external children */
227     if (!include_internals)
228         return;
229
230     /* Execute callback for both internals */
231     (*callback) (GTK_WIDGET(priv->tbutton), callback_data);
232     (*callback) (GTK_WIDGET(priv->volumebar), callback_data);
233 }
234
235 static void 
236 hildon_volumebar_destroy(GtkObject * self)
237 {
238     HildonVolumebarPrivate *priv;
239
240     priv = HILDON_VOLUMEBAR_GET_PRIVATE(self);
241
242     if (priv->tbutton) {
243         gtk_widget_unparent(GTK_WIDGET(priv->tbutton));
244         priv->tbutton = NULL;
245     }
246     if (priv->volumebar) {
247         gtk_widget_unparent(GTK_WIDGET(priv->volumebar));
248         priv->volumebar = NULL;
249     }
250
251     if (GTK_OBJECT_CLASS(parent_class)->destroy)
252         GTK_OBJECT_CLASS(parent_class)->destroy(self);
253 }
254
255 static void
256 hildon_volumebar_set_property(GObject * object,
257                               guint prop_id,
258                               const GValue * value, 
259                               GParamSpec * pspec)
260 {  
261     HildonVolumebarPrivate *priv;
262
263     priv = HILDON_VOLUMEBAR_GET_PRIVATE(object);
264
265     switch (prop_id) {
266     case PROP_HILDON_HAS_MUTE:
267         /* Mute button always exists, but might be hidden */
268         if (g_value_get_boolean(value))
269             gtk_widget_show(GTK_WIDGET(priv->tbutton));
270         else
271             gtk_widget_hide(GTK_WIDGET(priv->tbutton));
272         break;
273     case PROP_HILDON_FOCUSABLE:
274         g_object_set( G_OBJECT(priv->volumebar), "can-focus", 
275                       g_value_get_boolean(value), NULL );
276         break;
277     case PROP_HILDON_LEVEL:
278         hildon_volumebar_set_level(HILDON_VOLUMEBAR(priv->volumebar),
279                                    g_value_get_double(value));
280         break;
281     case PROP_HILDON_MUTE:
282         hildon_volumebar_set_mute(HILDON_VOLUMEBAR(priv->volumebar),
283                                   g_value_get_boolean(value));
284         break;
285     default:
286         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
287
288         break;
289     }
290 }
291
292 static void
293 hildon_volumebar_get_property(GObject * object,
294                               guint prop_id, GValue * value, 
295                               GParamSpec * pspec)
296 {
297     HildonVolumebar *vb = HILDON_VOLUMEBAR(object);
298     HildonVolumebarPrivate *priv = HILDON_VOLUMEBAR_GET_PRIVATE(vb);
299
300     switch (prop_id) {
301     case PROP_HILDON_HAS_MUTE:
302         g_value_set_boolean(value, GTK_WIDGET_VISIBLE(priv->tbutton));
303         break;
304     case PROP_HILDON_FOCUSABLE:
305         g_value_set_boolean(value, GTK_WIDGET_CAN_FOCUS(priv->volumebar));
306         break;
307     case PROP_HILDON_LEVEL:
308         g_value_set_double(value, hildon_volumebar_get_level(vb));
309         break;
310     case PROP_HILDON_MUTE:
311         g_value_set_boolean(value, hildon_volumebar_get_mute(vb));
312         break;
313     default:
314         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
315         break;
316     }
317 }
318
319 /**
320  * hildon_volumebar_level_change:
321  * @self: a #HildonVolumebar widget
322  *
323  * Emits "level_changed" signal to the given volume bar. This function
324  * is mainly used by derived classes.
325  */
326 void 
327 hildon_volumebar_level_change(HildonVolumebar * self)
328 {
329     g_return_if_fail(HILDON_IS_VOLUMEBAR(self));
330     g_signal_emit_by_name(GTK_WIDGET(self), "level_changed");
331 }
332
333 /**
334  * hildon_volumebar_set_level:
335  * @self: volume bar to change level on
336  * @level: new level
337  *
338  * Sets new volume level for this #HildonVolumebar.
339  */
340 void 
341 hildon_volumebar_set_level(HildonVolumebar * self, gdouble level)
342 {
343     HildonVolumebarPrivate *priv;
344
345     g_return_if_fail(HILDON_IS_VOLUMEBAR(self));
346
347     priv = HILDON_VOLUMEBAR_GET_PRIVATE(self);
348    
349     hildon_volumebar_range_set_level(priv->volumebar, level);
350 }
351
352 /**
353  * hildon_volumebar_get_level:
354  * @self: volume bar to query level on
355  *
356  * Gets the volume level of this #HildonVolumebar.
357  *
358  * Returns: volume level or -1 on error
359  */
360 gdouble 
361 hildon_volumebar_get_level(HildonVolumebar * self)
362 {
363     HildonVolumebarPrivate *priv;
364
365     g_return_val_if_fail(HILDON_IS_VOLUMEBAR(self), -1);
366
367     priv = HILDON_VOLUMEBAR_GET_PRIVATE(self);
368
369     return hildon_volumebar_range_get_level(priv->volumebar);
370 }
371
372 /**
373  * hildon_volumebar_set_mute:
374  * @self: volume bar to work on
375  * @mute: mute ON/OFF
376  *
377  * Sets mute status for this #HildonVolumebar.
378  */
379 void 
380 hildon_volumebar_set_mute(HildonVolumebar * self, gboolean mute)
381 {
382     HildonVolumebarPrivate *priv;
383     gboolean focusable = TRUE;
384     
385     g_return_if_fail(HILDON_IS_VOLUMEBAR(self));
386
387     priv = HILDON_VOLUMEBAR_GET_PRIVATE(self);
388
389     /* Slider should be insensitive when mute is on */
390     gtk_widget_set_sensitive(GTK_WIDGET(priv->volumebar), !mute);
391     
392     focusable = GTK_WIDGET_CAN_FOCUS (GTK_WIDGET (priv->volumebar));
393     
394     if (mute){   
395         if (focusable){
396             /* Make mute button focusable since the slider isn't anymore */
397             g_object_set (G_OBJECT (priv->tbutton), "can-focus", TRUE, NULL);
398             gtk_widget_grab_focus (GTK_WIDGET(priv->tbutton));
399         }
400     }
401     else
402     {
403         g_object_set (G_OBJECT (priv->tbutton), "can-focus", FALSE, NULL);
404         
405         /* Mute off grabs focus */
406         if (focusable){
407             gtk_widget_grab_focus (GTK_WIDGET (self));
408         }
409         else{
410             /* If volumebar is not focusable, focus the parent window instead */
411             GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (self), 
412                                                       GTK_TYPE_WINDOW);
413             gtk_window_set_focus (GTK_WINDOW (win), NULL);
414         }
415     }
416
417     /* Update mute button state and redraw */
418     gtk_toggle_button_set_active(priv->tbutton, mute);
419
420     gtk_widget_queue_draw (GTK_WIDGET (self));
421 }
422
423 /**
424  * hildon_volumebar_get_mute:
425  * @self: volume bar to query mute status
426  *
427  * Gets mute status of this #HildonVolumebar (ON/OFF).
428  *
429  * Returns: Mute status as #gboolean value.
430  */
431 gboolean 
432 hildon_volumebar_get_mute(HildonVolumebar * self)
433 {
434     HildonVolumebarPrivate *priv;
435
436     g_return_val_if_fail(HILDON_IS_VOLUMEBAR(self), TRUE);
437
438     priv = HILDON_VOLUMEBAR_GET_PRIVATE(self);
439
440     return gtk_toggle_button_get_active(priv->tbutton);
441 }
442
443 /**
444  * hildon_volumebar_get_adjustment
445  * @self : a #HildonVolumebar
446  * 
447  * Gets the GtkAdjustment used in volume bar. This can be handy
448  * to give to hildon_appview_set_connected_adjustment which
449  * will allow changing the volume with increase / decrease
450  * hardware buttons.
451  *
452  * This is a temporary solution until volume bar is restructured to
453  * be a child class of GtkRange.
454  * 
455  * Returns: a #GtkAdjustment used by volume bar.
456  */
457 GtkAdjustment * 
458 hildon_volumebar_get_adjustment (HildonVolumebar * self)
459 {
460     HildonVolumebarPrivate *priv;
461
462     g_return_val_if_fail(HILDON_IS_VOLUMEBAR(self), NULL);
463
464     priv = HILDON_VOLUMEBAR_GET_PRIVATE(self);
465
466     return gtk_range_get_adjustment (GTK_RANGE (priv->volumebar));
467 }
468
469 static void
470 mute_toggled (HildonVolumebar *self)
471 {
472   /* This looks like no-op, but it still does something meaningfull!
473      set_mute also updates the ui to match new state that
474      is already reported by get_mute */
475   hildon_volumebar_set_mute (self, hildon_volumebar_get_mute(self));
476 }
477
478 static gboolean
479 hildon_volumebar_key_press (GtkWidget * widget,
480                             GdkEventKey * event)
481 {
482     HildonVolumebarPrivate *priv;
483     
484     priv = HILDON_VOLUMEBAR_GET_PRIVATE(widget);
485
486     /* Enter key toggles mute button (unless it is hidden) */
487     if (event->keyval == GDK_Return && GTK_WIDGET_VISIBLE(priv->tbutton)) {
488         gtk_toggle_button_set_active(priv->tbutton, 
489                 !hildon_volumebar_get_mute(HILDON_VOLUMEBAR(widget)));
490         return TRUE;
491     }
492
493     return GTK_WIDGET_CLASS(parent_class)->key_press_event(widget, event);
494 }
495
496 /* Sends mute-toggled signal to widget, used as a callback in derived classes
497    Just keep this "protected" in order to avoid introducing new API. */
498 void
499 _hildon_volumebar_mute_toggled(HildonVolumebar * self)
500 {
501     g_return_if_fail(HILDON_IS_VOLUMEBAR(self));
502     g_signal_emit_by_name(self, "mute_toggled");
503 }