latest update
[hildon] / hildon-widgets / hildon-seekbar.c
1 /*
2  * This file is part of hildon-libs
3  *
4  * Copyright (C) 2005 Nokia Corporation.
5  *
6  * Contact: Luc Pionchon <luc.pionchon@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; either version 2.1 of
11  * the License, or (at your option) 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  * @file hildon-seekbar.c
27  *
28  * This file contains the API implementation for Hildon Seekbar widget.
29  * Based on seekbar.gob rev 1.10
30  */
31
32 #ifdef HAVE_CONFIG_H
33 #include <config.h>
34 #endif
35
36 #include <libintl.h>
37 #include <stdio.h>
38 #include <math.h>
39
40 #include <gtk/gtklabel.h>
41 #include <gtk/gtkframe.h>
42 #include <gtk/gtkalignment.h>
43 #include <gtk/gtkadjustment.h>
44 #include <gtk/gtktoolbar.h>
45 #include <gdk/gdkkeysyms.h>
46
47 #include "hildon-seekbar.h"
48
49 #define _(String) dgettext(PACKAGE, String)
50
51 #define HILDON_SEEKBAR_GET_PRIVATE(obj) \
52         (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
53          HILDON_TYPE_SEEKBAR, HildonSeekbarPrivate));
54
55 typedef struct _HildonSeekbarPrivate HildonSeekbarPrivate;
56
57 /* our parent class */
58 static GtkScaleClass *parent_class = NULL;
59
60 /* Init functions */
61 static void hildon_seekbar_class_init(HildonSeekbarClass * seekbar_class);
62 static void hildon_seekbar_init(HildonSeekbar * seekbar);
63 static void hildon_seekbar_finalize(GObject * self);
64
65 /* property functions */
66 static void hildon_seekbar_set_property(GObject * object, guint prop_id,
67                                         const GValue * value,
68                                         GParamSpec * pspec);
69
70 static void hildon_seekbar_get_property(GObject * object, guint prop_id,
71                                         GValue * value,
72                                         GParamSpec * pspec);
73
74 /* virtual functions */
75 static void hildon_seekbar_size_request(GtkWidget * widget,
76                                         GtkRequisition * event);
77 static void hildon_seekbar_size_allocate(GtkWidget * widget,
78                                          GtkAllocation * allocation);
79 static gboolean hildon_seekbar_expose(GtkWidget * widget,
80                                       GdkEventExpose * event);
81 static gboolean hildon_seekbar_button_press_event(GtkWidget * widget,
82                                                   GdkEventButton * event);
83 static gboolean hildon_seekbar_button_release_event(GtkWidget * widget,
84                                                     GdkEventButton * event);
85 static gboolean hildon_seekbar_keypress(GtkWidget * widget,
86                                         GdkEventKey * event);
87
88 /* private stuff */
89
90 /*
91  * Some constants
92  * These should be named so that they are quite self
93  * explanatory
94  */
95 #define MINIMUM_WIDTH 115
96 #define DEFAULT_HEIGHT 58
97 /* Toolbar width and height defines */
98 #define TOOL_MINIMUM_WIDTH 75
99 #define TOOL_DEFAULT_HEIGHT 40
100
101 #define DEFAULT_DISPLAYC_BORDER 10
102 #define BUFFER_SIZE 32
103 #define EXTRA_SIDE_BORDER 20
104 #define TOOL_EXTRA_SIDE_BORDER 0
105
106 /* the number of steps it takes to move from left to right */
107 #define SECONDS_PER_MINUTE 60
108 #define NUM_STEPS 20
109
110 /* the number of digits precision for the internal range.
111  * note, this needs to be enough so that the step size for
112  * small total_times doesn't get rounded off. Currently set to 3
113  * this is because for the smallest total time ( i.e 1 ) and the current
114  * num steps ( 20 ) is: 1/20 = 0.05.  0.05 is 2 digits, and we
115  * add one for safety */
116 #define MAX_ROUND_DIGITS 3
117
118 /* enum for properties */
119 enum {
120     PROP_TOTAL_TIME = 1,
121     PROP_POSITION
122 };
123
124 /* private variables */
125 struct _HildonSeekbarPrivate {
126     GtkWidget *label;
127     gboolean draw_value;
128     gboolean is_toolbar;
129     guint fraction; /* This is the amount of time that has progressed from
130                        the beginning. It should be an integer between the
131                        minimum and maximum values of the corresponding
132                        adjustment, ie. adjument->lower and ->upper.. */
133 };
134
135 /**
136  * Initialises, and returns the type of a hildon seekbar.
137  */
138 GType hildon_seekbar_get_type(void)
139 {
140     static GType seekbar_type = 0;
141
142     if (!seekbar_type) {
143         static const GTypeInfo seekbar_info = {
144             sizeof(HildonSeekbarClass),
145             NULL,       /* base_init */
146             NULL,       /* base_finalize */
147             (GClassInitFunc) hildon_seekbar_class_init,
148             NULL,       /* class_finalize */
149             NULL,       /* class_data */
150             sizeof(HildonSeekbar),
151             0,  /* n_preallocs */
152             (GInstanceInitFunc) hildon_seekbar_init,
153         };
154         seekbar_type = g_type_register_static(GTK_TYPE_SCALE,
155                                               "HildonSeekbar",
156                                               &seekbar_info, 0);
157     }
158     return seekbar_type;
159 }
160
161 /**
162  * Initialises the seekbar class.
163  */
164 static void hildon_seekbar_class_init(HildonSeekbarClass * seekbar_class)
165 {
166     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(seekbar_class);
167     GObjectClass *object_class = G_OBJECT_CLASS(seekbar_class);
168
169     /* set the global parent_class */
170     parent_class = g_type_class_peek_parent(seekbar_class);
171
172     g_type_class_add_private(seekbar_class, sizeof(HildonSeekbarPrivate));
173
174     /* setup our widgets v-table */
175     widget_class->size_request = hildon_seekbar_size_request;
176     widget_class->size_allocate = hildon_seekbar_size_allocate;
177     widget_class->expose_event = hildon_seekbar_expose;
178     widget_class->button_press_event = hildon_seekbar_button_press_event;
179     widget_class->button_release_event =
180         hildon_seekbar_button_release_event;
181     widget_class->key_press_event = hildon_seekbar_keypress;
182
183     /* now the object stuff */
184     object_class->set_property = hildon_seekbar_set_property;
185     object_class->get_property = hildon_seekbar_get_property;
186     object_class->finalize = hildon_seekbar_finalize;
187
188     /* install the properties */
189     g_object_class_install_property(object_class, PROP_TOTAL_TIME,
190         g_param_spec_double("total_time",
191                             "total time",
192                             "Total playing time of this media file",
193                             0,           /* min value */
194                             G_MAXDOUBLE, /* max value */
195                             0,           /* default */
196                             G_PARAM_READABLE | G_PARAM_WRITABLE));
197
198     g_object_class_install_property(object_class, PROP_POSITION,
199         g_param_spec_double("position",
200                             "position",
201                             "Current position in this media file",
202                             0,           /* min value */
203                             G_MAXDOUBLE, /* max value */
204                             0,           /* default */
205                             G_PARAM_READABLE | G_PARAM_WRITABLE));
206     /* readable and writable */
207 }
208
209
210 static void hildon_seekbar_init(HildonSeekbar * seekbar)
211 {
212     HildonSeekbarPrivate *priv;
213     GtkRange *range = GTK_RANGE(seekbar);
214
215     priv = HILDON_SEEKBAR_GET_PRIVATE(seekbar);
216
217     range->orientation = GTK_ORIENTATION_HORIZONTAL;
218     range->flippable = TRUE;
219     range->has_stepper_a = TRUE;
220     range->has_stepper_d = TRUE;
221     range->round_digits = MAX_ROUND_DIGITS;
222
223     gtk_scale_set_draw_value (GTK_SCALE (seekbar), FALSE);
224 }
225
226 /* is this even necessary? */
227 static void hildon_seekbar_finalize( GObject *obj_self )        
228 {       
229     HildonSeekbar *self;        
230     HildonSeekbarPrivate *priv; 
231
232     self = HILDON_SEEKBAR ( obj_self ); 
233     priv = HILDON_SEEKBAR_GET_PRIVATE (self);   
234
235     if( G_OBJECT_CLASS( parent_class )->finalize )      
236         G_OBJECT_CLASS( parent_class )->finalize( obj_self );   
237 }
238
239
240 /* handle keypress events */
241 static gboolean hildon_seekbar_keypress(GtkWidget * widget,
242                                         GdkEventKey * event)
243 {
244     if (event->keyval == GDK_Up || event->keyval == GDK_Down)
245         return FALSE;
246     return ((GTK_WIDGET_CLASS(parent_class)->key_press_event) (widget,
247                                                                event));
248 }
249
250 /* handle setting of seekbar properties */
251 static void
252 hildon_seekbar_set_property(GObject * object, guint prop_id,
253                             const GValue * value, GParamSpec * pspec)
254 {
255     HildonSeekbar *seekbar = HILDON_SEEKBAR(object);
256
257     switch (prop_id) {
258     case PROP_TOTAL_TIME:
259         hildon_seekbar_set_total_time(seekbar, g_value_get_double(value));
260         break;
261     case PROP_POSITION:
262         hildon_seekbar_set_position(seekbar, g_value_get_double(value));
263         break;
264     default:
265         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
266         break;
267     }
268 }
269
270 /* handle getting of seekbar properties */
271 static void
272 hildon_seekbar_get_property(GObject * object, guint prop_id,
273                             GValue * value, GParamSpec * pspec)
274 {
275     GtkRange *range = GTK_RANGE(object);
276
277     switch (prop_id) {
278     case PROP_TOTAL_TIME:
279         g_value_set_double(value, range->adjustment->upper);
280         break;
281     case PROP_POSITION:
282         g_value_set_double(value, range->adjustment->value);
283         break;
284     default:
285         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
286         break;
287     }
288 }
289
290 /**
291  * hildon_seekbar_new:
292  *
293  * Creates a new #HildonSeekbar widget.
294  * 
295  * Return value: A #GtkWidget pointer of #HildonSeekbar widget.
296  */
297 GtkWidget *hildon_seekbar_new(void)
298 {
299     /* return a new object */
300     return g_object_new(HILDON_TYPE_SEEKBAR, NULL);
301 }
302
303 /**
304  * hildon_seekbar_get_total_time:
305  * @seekbar: Pointer to #HildonSeekbar widget.
306  *
307  * Accessor method for getting total playing time of stream
308  * in seconds.
309  *
310  * Returns: Total time as gint.
311  **/
312 gint hildon_seekbar_get_total_time(HildonSeekbar *seekbar)
313 {
314     GtkWidget *widget;
315     widget = GTK_WIDGET (seekbar);
316     g_return_val_if_fail(HILDON_IS_SEEKBAR(seekbar), 0);
317     g_return_val_if_fail(GTK_RANGE(widget)->adjustment, 0);
318     return GTK_RANGE(widget)->adjustment->upper;
319 }
320
321 /**
322  * hildon_seekbar_set_total_time:
323  * @seekbar: Pointer to #HildonSeekbar widget.
324  * @time: Time within range of > 0 && < G_MAXINT
325  *
326  * Accessor method for setting total playing time of stream
327  * in seconds.
328  *
329  **/
330 void hildon_seekbar_set_total_time(HildonSeekbar *seekbar, gint time)
331 {
332     GtkAdjustment *adj;
333     GtkWidget *widget;
334     gboolean value_changed = FALSE;
335
336     g_return_if_fail(HILDON_IS_SEEKBAR(seekbar));
337     widget = GTK_WIDGET (seekbar);
338
339     if (time <= 0) {
340         return;
341     }
342
343     g_return_if_fail(GTK_RANGE(widget)->adjustment);
344
345     adj = GTK_RANGE(widget)->adjustment;
346     adj->upper = time;
347
348     if (adj->value > time) {
349         adj->value = time;
350         value_changed = TRUE;
351     }
352
353     adj->step_increment = adj->upper / NUM_STEPS;
354     adj->page_increment = adj->step_increment;
355
356     gtk_adjustment_changed(adj);
357
358     if (value_changed) {
359         gtk_adjustment_value_changed(adj);
360         hildon_seekbar_set_fraction(seekbar,
361                                     MIN(hildon_seekbar_get_fraction(seekbar),
362                                         time));
363
364         g_object_freeze_notify (G_OBJECT(seekbar));
365
366         hildon_seekbar_set_position(seekbar,
367                                     MIN(hildon_seekbar_get_position(seekbar),
368                                         time));
369
370         g_object_notify(G_OBJECT (seekbar), "total-time");
371
372         g_object_thaw_notify (G_OBJECT(seekbar));
373     }
374 }
375
376 /**
377  * hildon_seekbar_get_fraction:
378  * @seekbar: Pointer to #HildonSeekbar widget.
379  *
380  * Accessor method for getting current fraction related to the progress indicator.
381  * It should be between min and max of seekbar range.
382  *
383  * Returns: Current fraction.
384  **/
385 guint hildon_seekbar_get_fraction( HildonSeekbar *seekbar )
386 {
387   g_return_val_if_fail( HILDON_IS_SEEKBAR( seekbar ), 0 );
388
389   return osso_gtk_range_get_stream_position (GTK_RANGE(seekbar));
390 }
391
392 /**
393  * hildon_seekbar_set_fraction:
394  * @seekbar: Pointer to #HildonSeekbar widget.
395  * @fraction: The new position of the progress indicator.
396  *
397  * Method for setting current value related to the progress indicator.
398  * It should be between the minimal and maximal values of the range in seekbar.
399  **/
400 void hildon_seekbar_set_fraction( HildonSeekbar *seekbar, guint fraction )
401 {
402   GtkRange *range = NULL;
403   g_return_if_fail( HILDON_IS_SEEKBAR( seekbar ) );
404
405   range = GTK_RANGE(GTK_WIDGET(seekbar));
406   
407   g_return_if_fail(fraction <= range->adjustment->upper &&
408                    fraction >= range->adjustment->lower);
409   
410   /* Set to show stream indicator. */
411   g_object_set (G_OBJECT (seekbar), "stream_indicator", TRUE, NULL);
412
413   fraction = CLAMP(fraction, range->adjustment->lower,
414                    range->adjustment->upper);
415   
416   osso_gtk_range_set_stream_position( range, fraction );
417   
418   if (fraction < hildon_seekbar_get_position(seekbar))
419     hildon_seekbar_set_position(seekbar, fraction);
420 }
421
422 /**
423  * hildon_seekbar_get_position:
424  * @seekbar: Pointer to #HildonSeekbar widget.
425  *
426  * Accessor method for getting current position in stream
427  * in seconds.
428  *
429  *
430  * Returns: Current position in stream in seconds.
431  **/
432 gint hildon_seekbar_get_position(HildonSeekbar *seekbar)
433 {
434   g_return_val_if_fail(HILDON_IS_SEEKBAR(seekbar), 0);
435   g_return_val_if_fail(GTK_RANGE(seekbar)->adjustment, 0);
436
437   return GTK_RANGE(seekbar)->adjustment->value;
438 }
439
440 /**
441  * hildon_seekbar_set_position:
442  * @seekbar: Pointer to #HildonSeekbar widget.
443  * @time: Time within range of >= 0 && < G_MAXINT
444  *
445  * Accessor method for setting current position in stream
446  * in seconds.
447  **/
448 void hildon_seekbar_set_position(HildonSeekbar *seekbar, gint time)
449 {
450     GtkRange *range;
451     GtkAdjustment *adj;
452     gint value;
453
454     g_return_if_fail(time >= 0);
455     g_return_if_fail(HILDON_IS_SEEKBAR(seekbar));
456     range = GTK_RANGE(seekbar);
457     adj = range->adjustment;
458     g_return_if_fail(adj);
459     
460     /* only change value if it is a different int. this allows us to have
461        smooth scrolls for small total_times */
462     value = floor(adj->value);
463     if (time != value) {
464       value = (time < adj->upper) ? time : adj->upper;
465       if (value <= osso_gtk_range_get_stream_position (range)) {
466         adj->value = value;
467         gtk_adjustment_value_changed(adj);
468
469         g_object_notify(G_OBJECT(seekbar), "position");
470       }
471     }
472 }
473
474 static void hildon_seekbar_size_request(GtkWidget * widget,
475                                         GtkRequisition * req)
476 {
477     HildonSeekbar *self = NULL;
478     HildonSeekbarPrivate *priv = NULL;
479     GtkWidget *parent = NULL;
480
481     self = HILDON_SEEKBAR(widget);
482     priv = HILDON_SEEKBAR_GET_PRIVATE(self)
483
484     parent =
485         gtk_widget_get_ancestor(GTK_WIDGET(self), GTK_TYPE_TOOLBAR);
486
487     priv->is_toolbar = parent ? TRUE : FALSE;
488
489     if (GTK_WIDGET_CLASS(parent_class)->size_request)
490         GTK_WIDGET_CLASS(parent_class)->size_request(widget, req);
491
492     req->width = priv->is_toolbar ? TOOL_MINIMUM_WIDTH : MINIMUM_WIDTH;
493     req->height = priv->is_toolbar ? TOOL_DEFAULT_HEIGHT : DEFAULT_HEIGHT;
494 }
495
496 static void hildon_seekbar_size_allocate(GtkWidget * widget,
497                                          GtkAllocation * allocation)
498 {
499     HildonSeekbarPrivate *priv;
500
501     priv = HILDON_SEEKBAR_GET_PRIVATE(HILDON_SEEKBAR(widget));
502
503     if (priv->is_toolbar == TRUE)
504       {
505         if (allocation->height > TOOL_DEFAULT_HEIGHT) {
506             allocation->y +=
507               (allocation->height - TOOL_DEFAULT_HEIGHT) / 2;
508             allocation->height = TOOL_DEFAULT_HEIGHT;
509           }
510       }
511     else
512       {
513         if (allocation->height > DEFAULT_HEIGHT) {
514             allocation->y +=
515               (allocation->height - DEFAULT_HEIGHT) / 2;
516             allocation->height = DEFAULT_HEIGHT;
517           }
518       }
519
520     if (priv->is_toolbar == TRUE)
521       {
522         allocation->x += TOOL_EXTRA_SIDE_BORDER;
523         allocation->width -= 2 * TOOL_EXTRA_SIDE_BORDER;
524       }
525     else
526       {
527         allocation->x += EXTRA_SIDE_BORDER;
528         allocation->width -= 2 * EXTRA_SIDE_BORDER;
529       }
530    
531     if (GTK_WIDGET_CLASS(parent_class)->size_allocate)
532         GTK_WIDGET_CLASS(parent_class)->size_allocate(widget, allocation);
533 }
534
535 static gboolean hildon_seekbar_expose(GtkWidget * widget,
536                                       GdkEventExpose * event)
537 {
538     HildonSeekbarPrivate *priv;
539     gint extra_side_borders = 0;
540
541     priv = HILDON_SEEKBAR_GET_PRIVATE(HILDON_SEEKBAR(widget));
542    
543     extra_side_borders = priv->is_toolbar ? TOOL_EXTRA_SIDE_BORDER :
544                                             EXTRA_SIDE_BORDER;
545
546     if (GTK_WIDGET_DRAWABLE(widget)) {
547         gtk_paint_box(widget->style, widget->window,
548                       GTK_WIDGET_STATE(widget), GTK_SHADOW_OUT,
549                       NULL, widget, "seekbar",
550                       widget->allocation.x - extra_side_borders,
551                       widget->allocation.y,
552                       widget->allocation.width + 2 * extra_side_borders,
553                       widget->allocation.height);
554
555         (*GTK_WIDGET_CLASS(parent_class)->expose_event) (widget, event);
556     }
557
558     return FALSE;
559 }
560
561 /*
562  * Event handler for button_press_event
563  */
564 static gboolean
565 hildon_seekbar_button_press_event(GtkWidget * widget,
566                                   GdkEventButton * event)
567 {
568     HildonSeekbar *self;
569     HildonSeekbarPrivate *priv;
570
571     gint result = FALSE;
572
573     self = HILDON_SEEKBAR(widget);
574     priv = HILDON_SEEKBAR_GET_PRIVATE(self);
575
576     event->button = event->button == 1 ? 2 : event->button;
577
578     /* call the parent handler */
579     if (GTK_WIDGET_CLASS(parent_class)->button_press_event)
580         result = GTK_WIDGET_CLASS(parent_class)->button_press_event(widget,
581                                                                     event);
582
583     return result;
584 }
585
586 /*
587  * Event handler for button_release_event
588  */
589 static gboolean
590 hildon_seekbar_button_release_event(GtkWidget * widget,
591                                     GdkEventButton * event)
592 {
593     HildonSeekbar *self;
594     HildonSeekbarPrivate *priv;
595     gboolean result = FALSE;
596
597     self = HILDON_SEEKBAR(widget);
598     priv = HILDON_SEEKBAR_GET_PRIVATE(self);
599
600     event->button = event->button == 1 ? 2 : event->button;
601
602     /* call the parent handler */
603     if (GTK_WIDGET_CLASS(parent_class)->button_release_event)
604         result =
605             GTK_WIDGET_CLASS(parent_class)->button_release_event(widget,
606                                                                  event);
607     return result;
608 }