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