2 * This file is part of hildon-libs
4 * Copyright (C) 2005, 2006 Nokia Corporation, all rights reserved.
6 * Contact: Michael Dominic Kostrzewa <michael.kostrzewa@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
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
41 #include "hildon-seekbar.h"
45 #include <gtk/gtklabel.h>
46 #include <gtk/gtkframe.h>
47 #include <gtk/gtkalignment.h>
48 #include <gtk/gtkadjustment.h>
49 #include <gtk/gtktoolbar.h>
50 #include <gdk/gdkkeysyms.h>
51 #include "hildon-seekbar-private.h"
53 static GtkScaleClass* parent_class = NULL;
56 hildon_seekbar_class_init (HildonSeekbarClass *seekbar_class);
59 hildon_seekbar_init (HildonSeekbar *seekbar);
62 hildon_seekbar_set_property (GObject *object,
68 hildon_seekbar_get_property (GObject *object,
74 hildon_seekbar_size_request (GtkWidget *widget,
75 GtkRequisition *event);
78 hildon_seekbar_size_allocate (GtkWidget *widget,
79 GtkAllocation *allocation);
82 hildon_seekbar_expose (GtkWidget *widget,
83 GdkEventExpose *event);
86 hildon_seekbar_button_press_event (GtkWidget *widget,
87 GdkEventButton *event);
90 hildon_seekbar_button_release_event (GtkWidget *widget,
91 GdkEventButton *event);
94 hildon_seekbar_keypress (GtkWidget *widget,
97 #define MINIMUM_WIDTH 115
99 #define DEFAULT_HEIGHT 58
101 #define TOOL_MINIMUM_WIDTH 75
103 #define TOOL_DEFAULT_HEIGHT 40
105 #define DEFAULT_DISPLAYC_BORDER 10
107 #define BUFFER_SIZE 32
109 #define EXTRA_SIDE_BORDER 20
111 #define TOOL_EXTRA_SIDE_BORDER 0
115 #define SECONDS_PER_MINUTE 60
117 /* the number of digits precision for the internal range.
118 * note, this needs to be enough so that the step size for
119 * small total_times doesn't get rounded off. Currently set to 3
120 * this is because for the smallest total time ( i.e 1 ) and the current
121 * num steps ( 20 ) is: 1/20 = 0.05. 0.05 is 2 digits, and we
122 * add one for safety */
123 #define MAX_ROUND_DIGITS 3
134 * Initialises, and returns the type of a hildon seekbar.
137 hildon_seekbar_get_type (void)
139 static GType seekbar_type = 0;
142 static const GTypeInfo seekbar_info = {
143 sizeof (HildonSeekbarClass),
144 NULL, /* base_init */
145 NULL, /* base_finalize */
146 (GClassInitFunc) hildon_seekbar_class_init,
147 NULL, /* class_finalize */
148 NULL, /* class_data */
149 sizeof (HildonSeekbar),
151 (GInstanceInitFunc) hildon_seekbar_init,
153 seekbar_type = g_type_register_static(GTK_TYPE_SCALE,
162 * Initialises the seekbar class.
165 hildon_seekbar_class_init (HildonSeekbarClass *seekbar_class)
167 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (seekbar_class);
168 GObjectClass *object_class = G_OBJECT_CLASS (seekbar_class);
170 parent_class = g_type_class_peek_parent (seekbar_class);
172 g_type_class_add_private (seekbar_class, sizeof (HildonSeekbarPrivate));
174 widget_class->size_request = hildon_seekbar_size_request;
175 widget_class->size_allocate = hildon_seekbar_size_allocate;
176 widget_class->expose_event = hildon_seekbar_expose;
177 widget_class->button_press_event = hildon_seekbar_button_press_event;
178 widget_class->button_release_event = hildon_seekbar_button_release_event;
179 widget_class->key_press_event = hildon_seekbar_keypress;
181 object_class->set_property = hildon_seekbar_set_property;
182 object_class->get_property = hildon_seekbar_get_property;
184 g_object_class_install_property (object_class, PROP_TOTAL_TIME,
185 g_param_spec_double ("total_time",
187 "Total playing time of this media file",
189 G_MAXDOUBLE, /* max value */
193 g_object_class_install_property (object_class, PROP_POSITION,
194 g_param_spec_double ("position",
196 "Current position in this media file",
198 G_MAXDOUBLE, /* max value */
202 g_object_class_install_property (object_class, PROP_FRACTION,
203 g_param_spec_double ("fraction",
205 "current fraction related to the"
206 "progress indicator",
208 G_MAXDOUBLE, /* max value */
215 hildon_seekbar_init (HildonSeekbar *seekbar)
217 HildonSeekbarPrivate *priv;
218 GtkRange *range = GTK_RANGE(seekbar);
220 priv = HILDON_SEEKBAR_GET_PRIVATE (seekbar);
223 /* Initialize range widget */
224 range->orientation = GTK_ORIENTATION_HORIZONTAL;
225 range->flippable = TRUE;
226 range->has_stepper_a = TRUE;
227 range->has_stepper_d = TRUE;
228 range->round_digits = MAX_ROUND_DIGITS;
230 gtk_scale_set_draw_value (GTK_SCALE (seekbar), FALSE);
234 * Purpose of this function is to prevent Up and Down keys from
235 * changing the widget's value (like Left and Right). Instead they
236 * are used for changing focus to other widgtes.
239 hildon_seekbar_keypress (GtkWidget *widget,
242 if (event->keyval == GDK_Up || event->keyval == GDK_Down)
245 return ((GTK_WIDGET_CLASS (parent_class)->key_press_event) (widget, event));
249 hildon_seekbar_set_property (GObject *object,
254 HildonSeekbar *seekbar = HILDON_SEEKBAR (object);
258 case PROP_TOTAL_TIME:
259 hildon_seekbar_set_total_time (seekbar, g_value_get_double (value));
263 hildon_seekbar_set_position (seekbar, g_value_get_double (value));
267 hildon_seekbar_set_fraction (seekbar, g_value_get_double (value));
271 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
276 /* handle getting of seekbar properties */
278 hildon_seekbar_get_property (GObject *object,
283 GtkRange *range = GTK_RANGE (object);
287 case PROP_TOTAL_TIME:
288 g_value_set_double (value, range->adjustment->upper);
292 g_value_set_double (value, range->adjustment->value);
296 g_value_set_double (value,
297 hildon_seekbar_get_fraction (HILDON_SEEKBAR(object)));
301 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
307 * hildon_seekbar_new:
309 * Create a new #HildonSeekbar widget.
311 * Returns: a #GtkWidget pointer of #HildonSeekbar widget
314 hildon_seekbar_new (void)
316 return g_object_new (HILDON_TYPE_SEEKBAR, NULL);
320 * hildon_seekbar_get_total_time:
321 * @seekbar: pointer to #HildonSeekbar widget
323 * Returns: total playing time of media in seconds.
326 hildon_seekbar_get_total_time (HildonSeekbar *seekbar)
329 widget = GTK_WIDGET (seekbar);
330 g_return_val_if_fail (HILDON_IS_SEEKBAR (seekbar), 0);
331 g_return_val_if_fail (GTK_RANGE (widget)->adjustment, 0);
332 return GTK_RANGE (widget)->adjustment->upper;
336 * hildon_seekbar_set_total_time:
337 * @seekbar: pointer to #HildonSeekbar widget
338 * @time: integer greater than zero
340 * Set total playing time of media in seconds.
343 hildon_seekbar_set_total_time (HildonSeekbar *seekbar,
348 gboolean value_changed = FALSE;
350 g_return_if_fail (HILDON_IS_SEEKBAR (seekbar));
351 widget = GTK_WIDGET (seekbar);
357 g_return_if_fail (GTK_RANGE (widget)->adjustment);
359 adj = GTK_RANGE (widget)->adjustment;
362 /* Clamp position to total time */
363 if (adj->value > time) {
365 value_changed = TRUE;
368 /* Calculate new step value */
369 adj->step_increment = adj->upper / NUM_STEPS;
370 adj->page_increment = adj->step_increment;
372 gtk_adjustment_changed (adj);
374 /* Update range widget position/fraction */
376 gtk_adjustment_value_changed (adj);
377 hildon_seekbar_set_fraction(seekbar,
378 MIN (hildon_seekbar_get_fraction (seekbar),
381 g_object_freeze_notify (G_OBJECT (seekbar));
383 hildon_seekbar_set_position (seekbar,
384 MIN (hildon_seekbar_get_position (seekbar),
387 g_object_notify(G_OBJECT (seekbar), "total-time");
389 g_object_thaw_notify (G_OBJECT (seekbar));
394 * hildon_seekbar_get_fraction:
395 * @seekbar: pointer to #HildonSeekbar widget
397 * Get current fraction value of the rage.
399 * Returns: current fraction
402 hildon_seekbar_get_fraction (HildonSeekbar *seekbar)
404 g_return_val_if_fail (HILDON_IS_SEEKBAR (seekbar), 0);
406 return osso_gtk_range_get_stream_position (GTK_RANGE(seekbar));
410 * hildon_seekbar_set_fraction:
411 * @seekbar: pointer to #HildonSeekbar widget
412 * @fraction: the new position of the progress indicator
414 * Set current fraction value of the range.
415 * It should be between the minimal and maximal values of the range in seekbar.
418 hildon_seekbar_set_fraction (HildonSeekbar *seekbar,
421 GtkRange *range = NULL;
422 g_return_if_fail (HILDON_IS_SEEKBAR (seekbar));
424 range = GTK_RANGE(GTK_WIDGET(seekbar));
426 g_return_if_fail (fraction <= range->adjustment->upper &&
427 fraction >= range->adjustment->lower);
429 /* Set to show stream indicator. */
430 g_object_set (G_OBJECT (seekbar), "stream_indicator", TRUE, NULL);
432 fraction = CLAMP (fraction, range->adjustment->lower,
433 range->adjustment->upper);
435 /* Update stream position of range widget */
436 osso_gtk_range_set_stream_position (range, fraction);
438 if (fraction < hildon_seekbar_get_position(seekbar))
439 hildon_seekbar_set_position(seekbar, fraction);
441 g_object_notify (G_OBJECT (seekbar), "fraction");
445 * hildon_seekbar_get_position:
446 * @seekbar: pointer to #HildonSeekbar widget
448 * Get current position in stream in seconds.
450 * Returns: current position in stream in seconds
453 hildon_seekbar_get_position (HildonSeekbar *seekbar)
455 g_return_val_if_fail (HILDON_IS_SEEKBAR(seekbar), 0);
456 g_return_val_if_fail (GTK_RANGE(seekbar)->adjustment, 0);
458 return GTK_RANGE (seekbar)->adjustment->value;
462 * hildon_seekbar_set_position:
463 * @seekbar: pointer to #HildonSeekbar widget
464 * @time: time within range of >= 0 && < G_MAXINT
466 * Set current position in stream in seconds.
469 hildon_seekbar_set_position (HildonSeekbar *seekbar,
476 g_return_if_fail (time >= 0);
477 g_return_if_fail (HILDON_IS_SEEKBAR(seekbar));
478 range = GTK_RANGE (seekbar);
479 adj = range->adjustment;
480 g_return_if_fail (adj);
482 /* only change value if it is a different int. this allows us to have
483 smooth scrolls for small total_times */
484 value = floor (adj->value);
486 value = (time < adj->upper) ? time : adj->upper;
487 if (value <= osso_gtk_range_get_stream_position (range)) {
489 gtk_adjustment_value_changed (adj);
491 g_object_notify (G_OBJECT (seekbar), "position");
497 hildon_seekbar_size_request (GtkWidget *widget,
500 HildonSeekbar *self = NULL;
501 HildonSeekbarPrivate *priv = NULL;
502 GtkWidget *parent = NULL;
504 self = HILDON_SEEKBAR (widget);
505 priv = HILDON_SEEKBAR_GET_PRIVATE (self);
508 parent = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_TOOLBAR);
510 priv->is_toolbar = parent ? TRUE : FALSE;
512 if (GTK_WIDGET_CLASS (parent_class)->size_request)
513 GTK_WIDGET_CLASS (parent_class)->size_request (widget, req);
515 /* Request minimum size, depending on whether the widget is in a
517 req->width = priv->is_toolbar ? TOOL_MINIMUM_WIDTH : MINIMUM_WIDTH;
518 req->height = priv->is_toolbar ? TOOL_DEFAULT_HEIGHT : DEFAULT_HEIGHT;
522 hildon_seekbar_size_allocate (GtkWidget *widget,
523 GtkAllocation *allocation)
525 HildonSeekbarPrivate *priv;
527 priv = HILDON_SEEKBAR_GET_PRIVATE (widget);
530 if (priv->is_toolbar == TRUE)
532 /* Center vertically */
533 if (allocation->height > TOOL_DEFAULT_HEIGHT)
536 (allocation->height - TOOL_DEFAULT_HEIGHT) / 2;
537 allocation->height = TOOL_DEFAULT_HEIGHT;
539 /* Add space for border */
540 allocation->x += TOOL_EXTRA_SIDE_BORDER;
541 allocation->width -= 2 * TOOL_EXTRA_SIDE_BORDER;
545 /* Center vertically */
546 if (allocation->height > DEFAULT_HEIGHT)
548 allocation->y += (allocation->height - DEFAULT_HEIGHT) / 2;
549 allocation->height = DEFAULT_HEIGHT;
552 /* Add space for border */
553 allocation->x += EXTRA_SIDE_BORDER;
554 allocation->width -= 2 * EXTRA_SIDE_BORDER;
557 if (GTK_WIDGET_CLASS (parent_class)->size_allocate)
558 GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
562 hildon_seekbar_expose (GtkWidget *widget,
563 GdkEventExpose *event)
565 HildonSeekbarPrivate *priv;
566 gint extra_side_borders = 0;
568 priv = HILDON_SEEKBAR_GET_PRIVATE(widget);
571 extra_side_borders = priv->is_toolbar ? TOOL_EXTRA_SIDE_BORDER :
574 if (GTK_WIDGET_DRAWABLE (widget)) {
576 gtk_paint_box(widget->style, widget->window,
577 GTK_WIDGET_STATE(widget), GTK_SHADOW_OUT,
578 NULL, widget, "seekbar",
579 widget->allocation.x - extra_side_borders,
580 widget->allocation.y,
581 widget->allocation.width + 2 * extra_side_borders,
582 widget->allocation.height);
584 (*GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
591 * Event handler for button press. Changes button1 to button2.
594 hildon_seekbar_button_press_event (GtkWidget *widget,
595 GdkEventButton *event)
599 /* We change here the button id because we want to use button2
600 * functionality for button1: jump to mouse position
601 * instead of slowly incrementing to it */
602 if (event->button == 1) event->button = 2;
604 /* call the parent handler */
605 if (GTK_WIDGET_CLASS (parent_class)->button_press_event)
606 result = GTK_WIDGET_CLASS (parent_class)->button_press_event (widget,
612 * Event handler for button release. Changes button1 to button2.
615 hildon_seekbar_button_release_event (GtkWidget *widget,
616 GdkEventButton *event)
618 gboolean result = FALSE;
620 /* We change here the button id because we want to use button2
621 * functionality for button1: jump to mouse position
622 * instead of slowly incrementing to it */
623 event->button = event->button == 1 ? 2 : event->button;
625 /* call the parent handler */
626 if (GTK_WIDGET_CLASS (parent_class)->button_release_event)
627 result = GTK_WIDGET_CLASS (parent_class)->button_release_event (widget,