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
11 * the License or 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
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>
52 #include "hildon-seekbar.h"
54 #define HILDON_SEEKBAR_GET_PRIVATE(obj) \
55 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
56 HILDON_TYPE_SEEKBAR, HildonSeekbarPrivate));
58 typedef struct _HildonSeekbarPrivate HildonSeekbarPrivate;
60 /* our parent class */
61 static GtkScaleClass *parent_class = NULL;
64 static void hildon_seekbar_class_init(HildonSeekbarClass * seekbar_class);
65 static void hildon_seekbar_init(HildonSeekbar * seekbar);
67 /* property functions */
68 static void hildon_seekbar_set_property(GObject * object, guint prop_id,
72 static void hildon_seekbar_get_property(GObject * object, guint prop_id,
76 /* virtual functions */
77 static void hildon_seekbar_size_request(GtkWidget * widget,
78 GtkRequisition * event);
79 static void hildon_seekbar_size_allocate(GtkWidget * widget,
80 GtkAllocation * allocation);
81 static gboolean hildon_seekbar_expose(GtkWidget * widget,
82 GdkEventExpose * event);
83 static gboolean hildon_seekbar_button_press_event(GtkWidget * widget,
84 GdkEventButton * event);
85 static gboolean hildon_seekbar_button_release_event(GtkWidget * widget,
86 GdkEventButton * event);
87 static gboolean hildon_seekbar_keypress(GtkWidget * widget,
91 #define MINIMUM_WIDTH 115
92 #define DEFAULT_HEIGHT 58
94 /* Toolbar width and height defines */
95 #define TOOL_MINIMUM_WIDTH 75
96 #define TOOL_DEFAULT_HEIGHT 40
98 #define DEFAULT_DISPLAYC_BORDER 10
99 #define BUFFER_SIZE 32
100 #define EXTRA_SIDE_BORDER 20
101 #define TOOL_EXTRA_SIDE_BORDER 0
103 /* the number of steps it takes to move from left to right */
106 #define SECONDS_PER_MINUTE 60
108 /* the number of digits precision for the internal range.
109 * note, this needs to be enough so that the step size for
110 * small total_times doesn't get rounded off. Currently set to 3
111 * this is because for the smallest total time ( i.e 1 ) and the current
112 * num steps ( 20 ) is: 1/20 = 0.05. 0.05 is 2 digits, and we
113 * add one for safety */
114 #define MAX_ROUND_DIGITS 3
117 * FIXME HildonSeekbar introduced major changes in GtkRange mostly related
118 * to stream_indicator. These changes should be minimized.
121 /* Property indices */
128 /* private variables */
129 struct _HildonSeekbarPrivate {
130 gboolean is_toolbar; /* TRUE if this widget is inside a toolbar */
131 guint fraction; /* This is the amount of time that has progressed from
132 the beginning. It should be an integer between the
133 minimum and maximum values of the corresponding
134 adjustment, ie. adjument->lower and ->upper.. */
138 * Initialises, and returns the type of a hildon seekbar.
140 GType hildon_seekbar_get_type(void)
142 static GType seekbar_type = 0;
145 static const GTypeInfo seekbar_info = {
146 sizeof(HildonSeekbarClass),
147 NULL, /* base_init */
148 NULL, /* base_finalize */
149 (GClassInitFunc) hildon_seekbar_class_init,
150 NULL, /* class_finalize */
151 NULL, /* class_data */
152 sizeof(HildonSeekbar),
154 (GInstanceInitFunc) hildon_seekbar_init,
156 seekbar_type = g_type_register_static(GTK_TYPE_SCALE,
164 * Initialises the seekbar class.
166 static void hildon_seekbar_class_init(HildonSeekbarClass * seekbar_class)
168 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(seekbar_class);
169 GObjectClass *object_class = G_OBJECT_CLASS(seekbar_class);
171 parent_class = g_type_class_peek_parent(seekbar_class);
173 g_type_class_add_private(seekbar_class, sizeof(HildonSeekbarPrivate));
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;
183 object_class->set_property = hildon_seekbar_set_property;
184 object_class->get_property = hildon_seekbar_get_property;
186 g_object_class_install_property(object_class, PROP_TOTAL_TIME,
187 g_param_spec_double("total_time",
189 "Total playing time of this media file",
191 G_MAXDOUBLE, /* max value */
195 g_object_class_install_property(object_class, PROP_POSITION,
196 g_param_spec_double("position",
198 "Current position in this media file",
200 G_MAXDOUBLE, /* max value */
204 g_object_class_install_property(object_class, PROP_FRACTION,
205 g_param_spec_double("fraction",
207 "current fraction related to the"
208 "progress indicator",
210 G_MAXDOUBLE, /* max value */
216 static void hildon_seekbar_init(HildonSeekbar * seekbar)
218 HildonSeekbarPrivate *priv;
219 GtkRange *range = GTK_RANGE(seekbar);
221 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.
238 static gboolean hildon_seekbar_keypress(GtkWidget * widget,
241 if (event->keyval == GDK_Up || event->keyval == GDK_Down)
243 return ((GTK_WIDGET_CLASS(parent_class)->key_press_event) (widget,
248 hildon_seekbar_set_property(GObject * object, guint prop_id,
249 const GValue * value, GParamSpec * pspec)
251 HildonSeekbar *seekbar = HILDON_SEEKBAR(object);
254 case PROP_TOTAL_TIME:
255 hildon_seekbar_set_total_time(seekbar, g_value_get_double(value));
258 hildon_seekbar_set_position(seekbar, g_value_get_double(value));
261 hildon_seekbar_set_fraction(seekbar, g_value_get_double(value));
264 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
269 /* handle getting of seekbar properties */
271 hildon_seekbar_get_property(GObject * object, guint prop_id,
272 GValue * value, GParamSpec * pspec)
274 GtkRange *range = GTK_RANGE(object);
277 case PROP_TOTAL_TIME:
278 g_value_set_double(value, range->adjustment->upper);
281 g_value_set_double(value, range->adjustment->value);
284 g_value_set_double(value,
285 hildon_seekbar_get_fraction(HILDON_SEEKBAR(object)));
288 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
294 * hildon_seekbar_new:
296 * Create a new #HildonSeekbar widget.
298 * Returns: a #GtkWidget pointer of #HildonSeekbar widget
300 GtkWidget *hildon_seekbar_new(void)
302 return g_object_new(HILDON_TYPE_SEEKBAR, NULL);
306 * hildon_seekbar_get_total_time:
307 * @seekbar: pointer to #HildonSeekbar widget
309 * Returns: total playing time of media in seconds.
311 gint hildon_seekbar_get_total_time(HildonSeekbar *seekbar)
314 widget = GTK_WIDGET (seekbar);
315 g_return_val_if_fail(HILDON_IS_SEEKBAR(seekbar), 0);
316 g_return_val_if_fail(GTK_RANGE(widget)->adjustment, 0);
317 return GTK_RANGE(widget)->adjustment->upper;
321 * hildon_seekbar_set_total_time:
322 * @seekbar: pointer to #HildonSeekbar widget
323 * @time: integer greater than zero
325 * Set total playing time of media in seconds.
327 void hildon_seekbar_set_total_time(HildonSeekbar *seekbar, gint time)
331 gboolean value_changed = FALSE;
333 g_return_if_fail(HILDON_IS_SEEKBAR(seekbar));
334 widget = GTK_WIDGET (seekbar);
340 g_return_if_fail(GTK_RANGE(widget)->adjustment);
342 adj = GTK_RANGE(widget)->adjustment;
345 /* Clamp position to total time */
346 if (adj->value > time) {
348 value_changed = TRUE;
351 /* Calculate new step value */
352 adj->step_increment = adj->upper / NUM_STEPS;
353 adj->page_increment = adj->step_increment;
355 gtk_adjustment_changed(adj);
357 /* Update range widget position/fraction */
359 gtk_adjustment_value_changed(adj);
360 hildon_seekbar_set_fraction(seekbar,
361 MIN(hildon_seekbar_get_fraction(seekbar),
364 g_object_freeze_notify (G_OBJECT(seekbar));
366 hildon_seekbar_set_position(seekbar,
367 MIN(hildon_seekbar_get_position(seekbar),
370 g_object_notify(G_OBJECT (seekbar), "total-time");
372 g_object_thaw_notify (G_OBJECT(seekbar));
377 * hildon_seekbar_get_fraction:
378 * @seekbar: pointer to #HildonSeekbar widget
380 * Get current fraction value of the rage.
382 * Returns: current fraction
384 guint hildon_seekbar_get_fraction( HildonSeekbar *seekbar )
386 g_return_val_if_fail( HILDON_IS_SEEKBAR( seekbar ), 0 );
388 return osso_gtk_range_get_stream_position (GTK_RANGE(seekbar));
392 * hildon_seekbar_set_fraction:
393 * @seekbar: pointer to #HildonSeekbar widget
394 * @fraction: the new position of the progress indicator
396 * Set current fraction value of the range.
397 * It should be between the minimal and maximal values of the range in seekbar.
399 void hildon_seekbar_set_fraction( HildonSeekbar *seekbar, guint fraction )
401 GtkRange *range = NULL;
402 g_return_if_fail( HILDON_IS_SEEKBAR( seekbar ) );
404 range = GTK_RANGE(GTK_WIDGET(seekbar));
406 g_return_if_fail(fraction <= range->adjustment->upper &&
407 fraction >= range->adjustment->lower);
409 /* Set to show stream indicator. */
410 g_object_set (G_OBJECT (seekbar), "stream_indicator", TRUE, NULL);
412 fraction = CLAMP(fraction, range->adjustment->lower,
413 range->adjustment->upper);
415 /* Update stream position of range widget */
416 osso_gtk_range_set_stream_position( range, fraction );
418 if (fraction < hildon_seekbar_get_position(seekbar))
419 hildon_seekbar_set_position(seekbar, fraction);
421 g_object_notify (G_OBJECT (seekbar), "fraction");
425 * hildon_seekbar_get_position:
426 * @seekbar: pointer to #HildonSeekbar widget
428 * Get current position in stream in seconds.
430 * Returns: current position in stream in seconds
432 gint hildon_seekbar_get_position(HildonSeekbar *seekbar)
434 g_return_val_if_fail(HILDON_IS_SEEKBAR(seekbar), 0);
435 g_return_val_if_fail(GTK_RANGE(seekbar)->adjustment, 0);
437 return GTK_RANGE(seekbar)->adjustment->value;
441 * hildon_seekbar_set_position:
442 * @seekbar: pointer to #HildonSeekbar widget
443 * @time: time within range of >= 0 && < G_MAXINT
445 * Set current position in stream in seconds.
447 void hildon_seekbar_set_position(HildonSeekbar *seekbar, gint time)
453 g_return_if_fail(time >= 0);
454 g_return_if_fail(HILDON_IS_SEEKBAR(seekbar));
455 range = GTK_RANGE(seekbar);
456 adj = range->adjustment;
457 g_return_if_fail(adj);
459 /* only change value if it is a different int. this allows us to have
460 smooth scrolls for small total_times */
461 value = floor(adj->value);
463 value = (time < adj->upper) ? time : adj->upper;
464 if (value <= osso_gtk_range_get_stream_position (range)) {
466 gtk_adjustment_value_changed(adj);
468 g_object_notify(G_OBJECT(seekbar), "position");
473 static void hildon_seekbar_size_request(GtkWidget * widget,
474 GtkRequisition * req)
476 HildonSeekbar *self = NULL;
477 HildonSeekbarPrivate *priv = NULL;
478 GtkWidget *parent = NULL;
480 self = HILDON_SEEKBAR(widget);
481 priv = HILDON_SEEKBAR_GET_PRIVATE(self);
483 parent = gtk_widget_get_ancestor(GTK_WIDGET(self), GTK_TYPE_TOOLBAR);
485 priv->is_toolbar = parent ? TRUE : FALSE;
487 if (GTK_WIDGET_CLASS(parent_class)->size_request)
488 GTK_WIDGET_CLASS(parent_class)->size_request(widget, req);
490 /* Request minimum size, depending on whether the widget is in a
492 req->width = priv->is_toolbar ? TOOL_MINIMUM_WIDTH : MINIMUM_WIDTH;
493 req->height = priv->is_toolbar ? TOOL_DEFAULT_HEIGHT : DEFAULT_HEIGHT;
496 static void hildon_seekbar_size_allocate(GtkWidget * widget,
497 GtkAllocation * allocation)
499 HildonSeekbarPrivate *priv;
501 priv = HILDON_SEEKBAR_GET_PRIVATE(HILDON_SEEKBAR(widget));
503 if (priv->is_toolbar == TRUE)
505 /* Center vertically */
506 if (allocation->height > TOOL_DEFAULT_HEIGHT)
509 (allocation->height - TOOL_DEFAULT_HEIGHT) / 2;
510 allocation->height = TOOL_DEFAULT_HEIGHT;
512 /* Add space for border */
513 allocation->x += TOOL_EXTRA_SIDE_BORDER;
514 allocation->width -= 2 * TOOL_EXTRA_SIDE_BORDER;
518 /* Center vertically */
519 if (allocation->height > DEFAULT_HEIGHT)
521 allocation->y += (allocation->height - DEFAULT_HEIGHT) / 2;
522 allocation->height = DEFAULT_HEIGHT;
525 /* Add space for border */
526 allocation->x += EXTRA_SIDE_BORDER;
527 allocation->width -= 2 * EXTRA_SIDE_BORDER;
530 if (GTK_WIDGET_CLASS(parent_class)->size_allocate)
531 GTK_WIDGET_CLASS(parent_class)->size_allocate(widget, allocation);
534 static gboolean hildon_seekbar_expose(GtkWidget * widget,
535 GdkEventExpose * event)
537 HildonSeekbarPrivate *priv;
538 gint extra_side_borders = 0;
540 priv = HILDON_SEEKBAR_GET_PRIVATE(HILDON_SEEKBAR(widget));
542 extra_side_borders = priv->is_toolbar ? TOOL_EXTRA_SIDE_BORDER :
545 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);
555 (*GTK_WIDGET_CLASS(parent_class)->expose_event) (widget, event);
562 * Event handler for button press. Changes button1 to button2.
565 hildon_seekbar_button_press_event(GtkWidget * widget,
566 GdkEventButton * event)
570 /* We change here the button id because we want to use button2
571 * functionality for button1: jump to mouse position
572 * instead of slowly incrementing to it */
573 if (event->button == 1) event->button = 2;
575 /* call the parent handler */
576 if (GTK_WIDGET_CLASS(parent_class)->button_press_event)
577 result = GTK_WIDGET_CLASS(parent_class)->button_press_event(widget,
583 * Event handler for button release. Changes button1 to button2.
586 hildon_seekbar_button_release_event(GtkWidget * widget,
587 GdkEventButton * event)
589 gboolean result = FALSE;
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 event->button = event->button == 1 ? 2 : event->button;
596 /* call the parent handler */
597 if (GTK_WIDGET_CLASS(parent_class)->button_release_event)
598 result = GTK_WIDGET_CLASS(parent_class)->button_release_event(widget,