2 * This file is a part of hildon
4 * Copyright (C) 2005, 2006 Nokia Corporation, all rights reserved.
6 * Contact: Rodrigo Novo <rodrigo.novo@nokia.com>
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.
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.
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
26 * SECTION:hildon-seekbar
27 * @short_description: A widget used to identify a place from a content.
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
37 #undef HILDON_DISABLE_DEPRECATED
46 #include <gdk/gdkkeysyms.h>
48 #include "hildon-seekbar.h"
49 #include "hildon-seekbar-private.h"
51 static GtkScaleClass* parent_class = NULL;
54 hildon_seekbar_class_init (HildonSeekbarClass *seekbar_class);
57 hildon_seekbar_init (HildonSeekbar *seekbar);
60 hildon_seekbar_set_property (GObject *object,
66 hildon_seekbar_get_property (GObject *object,
72 hildon_seekbar_size_request (GtkWidget *widget,
73 GtkRequisition *event);
76 hildon_seekbar_size_allocate (GtkWidget *widget,
77 GtkAllocation *allocation);
80 hildon_seekbar_button_press_event (GtkWidget *widget,
81 GdkEventButton *event);
84 hildon_seekbar_button_release_event (GtkWidget *widget,
85 GdkEventButton *event);
88 hildon_seekbar_keypress (GtkWidget *widget,
91 #define MINIMUM_WIDTH 115
93 #define DEFAULT_HEIGHT 58
95 #define TOOL_MINIMUM_WIDTH 75
97 #define TOOL_DEFAULT_HEIGHT 40
99 #define DEFAULT_DISPLAYC_BORDER 10
101 #define BUFFER_SIZE 32
103 #define EXTRA_SIDE_BORDER 20
105 #define TOOL_EXTRA_SIDE_BORDER 0
109 #define SECONDS_PER_MINUTE 60
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
128 * hildon_seekbar_get_type:
130 * Initializes, and returns the type of a hildon seekbar.
132 * Returns: GType of #HildonSeekbar
136 hildon_seekbar_get_type (void)
138 static GType seekbar_type = 0;
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),
150 (GInstanceInitFunc) hildon_seekbar_init,
152 seekbar_type = g_type_register_static(GTK_TYPE_SCALE,
161 * Initialises the seekbar class.
164 hildon_seekbar_class_init (HildonSeekbarClass *seekbar_class)
166 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (seekbar_class);
167 GObjectClass *object_class = G_OBJECT_CLASS (seekbar_class);
169 parent_class = g_type_class_peek_parent (seekbar_class);
171 g_type_class_add_private (seekbar_class, sizeof (HildonSeekbarPrivate));
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;
179 object_class->set_property = hildon_seekbar_set_property;
180 object_class->get_property = hildon_seekbar_get_property;
183 * HildonSeekbar:total-time:
185 * Total playing time of this media file.
187 g_object_class_install_property (object_class, PROP_TOTAL_TIME,
188 g_param_spec_double ("total-time",
190 "Total playing time of this media file",
192 G_MAXDOUBLE, /* max value */
197 * HildonSeekbar:position:
199 * Current position in this media file.
201 g_object_class_install_property (object_class, PROP_POSITION,
202 g_param_spec_double ("position",
204 "Current position in this media file",
206 G_MAXDOUBLE, /* max value */
211 * HildonSeekbar:fraction:
213 * Current fraction related to the progress indicator.
215 g_object_class_install_property (object_class, PROP_FRACTION,
216 g_param_spec_double ("fraction",
218 "current fraction related to the"
219 "progress indicator",
221 G_MAXDOUBLE, /* max value */
228 hildon_seekbar_init (HildonSeekbar *seekbar)
230 HildonSeekbarPrivate *priv;
231 GtkRange *range = GTK_RANGE(seekbar);
233 priv = HILDON_SEEKBAR_GET_PRIVATE (seekbar);
236 /* Initialize range widget */
237 range->orientation = GTK_ORIENTATION_HORIZONTAL;
238 range->flippable = TRUE;
239 range->round_digits = MAX_ROUND_DIGITS;
241 gtk_scale_set_draw_value (GTK_SCALE (seekbar), FALSE);
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.
250 hildon_seekbar_keypress (GtkWidget *widget,
253 if (event->keyval == GDK_Up || event->keyval == GDK_Down)
256 return ((GTK_WIDGET_CLASS (parent_class)->key_press_event) (widget, event));
260 hildon_seekbar_set_property (GObject *object,
265 HildonSeekbar *seekbar = HILDON_SEEKBAR (object);
269 case PROP_TOTAL_TIME:
270 hildon_seekbar_set_total_time (seekbar, g_value_get_double (value));
274 hildon_seekbar_set_position (seekbar, g_value_get_double (value));
278 hildon_seekbar_set_fraction (seekbar, g_value_get_double (value));
282 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
287 /* handle getting of seekbar properties */
289 hildon_seekbar_get_property (GObject *object,
294 GtkRange *range = GTK_RANGE (object);
298 case PROP_TOTAL_TIME:
299 g_value_set_double (value, range->adjustment->upper);
303 g_value_set_double (value, range->adjustment->value);
307 g_value_set_double (value,
308 hildon_seekbar_get_fraction (HILDON_SEEKBAR(object)));
312 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
318 * hildon_seekbar_new:
320 * Create a new #HildonSeekbar widget.
322 * Returns: a #GtkWidget pointer of #HildonSeekbar widget
325 hildon_seekbar_new (void)
327 return g_object_new (HILDON_TYPE_SEEKBAR, NULL);
331 * hildon_seekbar_get_total_time:
332 * @seekbar: pointer to #HildonSeekbar widget
334 * Returns: total playing time of media in seconds.
337 hildon_seekbar_get_total_time (HildonSeekbar *seekbar)
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;
347 * hildon_seekbar_set_total_time:
348 * @seekbar: pointer to #HildonSeekbar widget
349 * @time: integer greater than zero
351 * Set total playing time of media in seconds.
354 hildon_seekbar_set_total_time (HildonSeekbar *seekbar,
359 gboolean value_changed = FALSE;
361 g_return_if_fail (HILDON_IS_SEEKBAR (seekbar));
362 widget = GTK_WIDGET (seekbar);
368 g_return_if_fail (GTK_RANGE (widget)->adjustment);
370 adj = GTK_RANGE (widget)->adjustment;
373 /* Clamp position to total time */
374 if (adj->value > time) {
376 value_changed = TRUE;
379 /* Calculate new step value */
380 adj->step_increment = adj->upper / NUM_STEPS;
381 adj->page_increment = adj->step_increment;
383 gtk_adjustment_changed (adj);
385 /* Update range widget position/fraction */
387 gtk_adjustment_value_changed (adj);
388 hildon_seekbar_set_fraction(seekbar,
389 MIN (hildon_seekbar_get_fraction (seekbar),
392 g_object_freeze_notify (G_OBJECT (seekbar));
394 hildon_seekbar_set_position (seekbar,
395 MIN (hildon_seekbar_get_position (seekbar),
398 g_object_notify(G_OBJECT (seekbar), "total-time");
400 g_object_thaw_notify (G_OBJECT (seekbar));
405 * hildon_seekbar_get_fraction:
406 * @seekbar: pointer to #HildonSeekbar widget
408 * Get current fraction value of the rage.
410 * Returns: current fraction
413 hildon_seekbar_get_fraction (HildonSeekbar *seekbar)
415 g_return_val_if_fail (HILDON_IS_SEEKBAR (seekbar), 0);
417 #if defined(MAEMO_GTK) || GTK_CHECK_VERSION(2,11,0)
418 return gtk_range_get_fill_level (GTK_RANGE (seekbar));
425 * hildon_seekbar_set_fraction:
426 * @seekbar: pointer to #HildonSeekbar widget
427 * @fraction: the new position of the progress indicator
429 * Set current fraction value of the range.
430 * It should be between the minimal and maximal values of the range in seekbar.
433 hildon_seekbar_set_fraction (HildonSeekbar *seekbar,
436 GtkRange *range = NULL;
437 g_return_if_fail (HILDON_IS_SEEKBAR (seekbar));
439 range = GTK_RANGE(GTK_WIDGET(seekbar));
441 g_return_if_fail (fraction <= range->adjustment->upper &&
442 fraction >= range->adjustment->lower);
444 /* Set to show stream indicator. */
445 g_object_set (G_OBJECT (seekbar), "show-fill-level", TRUE, NULL);
447 fraction = CLAMP (fraction, range->adjustment->lower,
448 range->adjustment->upper);
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);
455 if (fraction < hildon_seekbar_get_position(seekbar))
456 hildon_seekbar_set_position(seekbar, fraction);
458 g_object_notify (G_OBJECT (seekbar), "fraction");
462 * hildon_seekbar_get_position:
463 * @seekbar: pointer to #HildonSeekbar widget
465 * Get current position in stream in seconds.
467 * Returns: current position in stream in seconds
470 hildon_seekbar_get_position (HildonSeekbar *seekbar)
472 g_return_val_if_fail (HILDON_IS_SEEKBAR(seekbar), 0);
473 g_return_val_if_fail (GTK_RANGE(seekbar)->adjustment, 0);
475 return GTK_RANGE (seekbar)->adjustment->value;
479 * hildon_seekbar_set_position:
480 * @seekbar: pointer to #HildonSeekbar widget
481 * @time: time within range of >= 0 && < G_MAXINT
483 * Set current position in stream in seconds.
486 hildon_seekbar_set_position (HildonSeekbar *seekbar,
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);
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);
503 value = (time < adj->upper) ? time : adj->upper;
505 #if defined(MAEMO_GTK) || GTK_CHECK_VERSION(2,11,0)
506 if (value <= gtk_range_get_fill_level (range)) {
511 gtk_adjustment_value_changed (adj);
513 g_object_notify (G_OBJECT (seekbar), "position");
519 hildon_seekbar_size_request (GtkWidget *widget,
522 HildonSeekbar *self = NULL;
523 HildonSeekbarPrivate *priv = NULL;
524 GtkWidget *parent = NULL;
526 self = HILDON_SEEKBAR (widget);
527 priv = HILDON_SEEKBAR_GET_PRIVATE (self);
530 parent = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_TOOLBAR);
532 priv->is_toolbar = parent ? TRUE : FALSE;
534 if (GTK_WIDGET_CLASS (parent_class)->size_request)
535 GTK_WIDGET_CLASS (parent_class)->size_request (widget, req);
537 /* Request minimum size, depending on whether the widget is in a
539 req->width = priv->is_toolbar ? TOOL_MINIMUM_WIDTH : MINIMUM_WIDTH;
540 req->height = priv->is_toolbar ? TOOL_DEFAULT_HEIGHT : DEFAULT_HEIGHT;
544 hildon_seekbar_size_allocate (GtkWidget *widget,
545 GtkAllocation *allocation)
547 HildonSeekbarPrivate *priv;
549 priv = HILDON_SEEKBAR_GET_PRIVATE (widget);
552 if (priv->is_toolbar == TRUE)
554 /* Center vertically */
555 if (allocation->height > TOOL_DEFAULT_HEIGHT)
558 (allocation->height - TOOL_DEFAULT_HEIGHT) / 2;
559 allocation->height = TOOL_DEFAULT_HEIGHT;
561 /* Add space for border */
562 allocation->x += TOOL_EXTRA_SIDE_BORDER;
563 allocation->width -= 2 * TOOL_EXTRA_SIDE_BORDER;
567 /* Center vertically */
568 if (allocation->height > DEFAULT_HEIGHT)
570 allocation->y += (allocation->height - DEFAULT_HEIGHT) / 2;
571 allocation->height = DEFAULT_HEIGHT;
574 /* Add space for border */
575 allocation->x += EXTRA_SIDE_BORDER;
576 allocation->width -= 2 * EXTRA_SIDE_BORDER;
579 if (GTK_WIDGET_CLASS (parent_class)->size_allocate)
580 GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
584 * Event handler for button press. Changes button1 to button2.
587 hildon_seekbar_button_press_event (GtkWidget *widget,
588 GdkEventButton *event)
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;
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,
605 * Event handler for button release. Changes button1 to button2.
608 hildon_seekbar_button_release_event (GtkWidget *widget,
609 GdkEventButton *event)
611 gboolean result = FALSE;
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;
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,