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