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-controlbar
27 * @short_description: A widget that allows increasing or decreasing
28 * a value within a pre-defined range.
30 * #HildonControlbar is a horizontally positioned range widget that is
31 * visually divided into blocks and supports setting a minimum and
32 * maximum value for the range.
36 * #HildonControlbar has been deprecated since Hildon 2.2
37 * See <link linkend="hildon-migrating-control-bar">Migrating Control Bars</link>
38 * section to know how to migrate this deprecated widget.
43 * <title>HildonControlbar example</title>
45 * GtkWidget *cbar = hildon_controlbar_new();
46 * hildon_controlbar_set_max (HILDON_CONTROLBAR (cbar), 12);
47 * hildon_controlbar_set_value (HILDON_CONTROLBAR (cbar), 6);
53 #undef HILDON_DISABLE_DEPRECATED
62 #include <gdk/gdkkeysyms.h>
64 #include "hildon-controlbar.h"
65 #include "hildon-controlbar-private.h"
68 dgettext("hildon-libs", string)
70 #define DEFAULT_WIDTH 234
72 #define DEFAULT_HEIGHT 60
74 #define DEFAULT_BORDER_WIDTH 0
76 #define HILDON_CONTROLBAR_STEP_INCREMENT 1
78 #define HILDON_CONTROLBAR_PAGE_INCREMENT 1
80 #define HILDON_CONTROLBAR_PAGE_SIZE 0
82 #define HILDON_CONTROLBAR_UPPER_VALUE 10
84 #define HILDON_CONTROLBAR_LOWER_VALUE 0.0
86 #define HILDON_CONTROLBAR_INITIAL_VALUE 0
88 static GtkScaleClass* parent_class;
104 static guint signals[LAST_SIGNAL] = { 0 };
107 hildon_controlbar_class_init (HildonControlbarClass *controlbar_class);
110 hildon_controlbar_init (HildonControlbar *controlbar);
113 hildon_controlbar_constructor (GType type,
114 guint n_construct_properties,
115 GObjectConstructParam *construct_properties);
118 hildon_controlbar_button_press_event (GtkWidget *widget,
119 GdkEventButton * event);
122 hildon_controlbar_button_release_event (GtkWidget *widget,
123 GdkEventButton *event);
126 hildon_controlbar_expose_event (GtkWidget *widget,
127 GdkEventExpose *event);
130 hildon_controlbar_size_request (GtkWidget *self,
131 GtkRequisition *req);
133 hildon_controlbar_paint (HildonControlbar *self,
134 GdkRectangle * area);
137 hildon_controlbar_keypress (GtkWidget *widget,
138 GdkEventKey * event);
141 hildon_controlbar_set_property (GObject *object,
147 hildon_controlbar_get_property (GObject *object,
153 hildon_controlbar_value_changed (GtkAdjustment *adj,
157 hildon_controlbar_change_value (GtkRange *range,
158 GtkScrollType scroll,
163 * hildon_controlbar_get_type:
165 * Initializes and returns the type of a hildon control bar.
167 * Returns: GType of #HildonControlbar
170 hildon_controlbar_get_type (void)
172 static GType controlbar_type = 0;
174 if (!controlbar_type) {
175 static const GTypeInfo controlbar_info = {
176 sizeof (HildonControlbarClass),
177 NULL, /* base_init */
178 NULL, /* base_finalize */
179 (GClassInitFunc) hildon_controlbar_class_init,
180 NULL, /* class_finalize */
181 NULL, /* class_data */
182 sizeof (HildonControlbar),
184 (GInstanceInitFunc) hildon_controlbar_init,
186 controlbar_type = g_type_register_static (GTK_TYPE_SCALE,
188 &controlbar_info, 0);
191 return controlbar_type;
195 hildon_controlbar_class_init (HildonControlbarClass *controlbar_class)
197 GObjectClass *gobject_class = G_OBJECT_CLASS (controlbar_class);
198 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (controlbar_class);
200 parent_class = g_type_class_peek_parent(controlbar_class);
202 g_type_class_add_private(controlbar_class, sizeof (HildonControlbarPrivate));
204 gobject_class->get_property = hildon_controlbar_get_property;
205 gobject_class->set_property = hildon_controlbar_set_property;
206 gobject_class->constructor = hildon_controlbar_constructor;
207 widget_class->size_request = hildon_controlbar_size_request;
208 widget_class->button_press_event = hildon_controlbar_button_press_event;
209 widget_class->button_release_event = hildon_controlbar_button_release_event;
210 widget_class->expose_event = hildon_controlbar_expose_event;
211 widget_class->key_press_event = hildon_controlbar_keypress;
212 controlbar_class->end_reached = NULL;
215 * HildonControlbar:min:
217 * Controlbar minimum value.
219 g_object_class_install_property (gobject_class, PROP_MIN,
220 g_param_spec_int ("min",
222 "Smallest possible value",
224 HILDON_CONTROLBAR_LOWER_VALUE,
225 G_PARAM_READABLE | G_PARAM_WRITABLE));
228 * HildonControlbar:max:
230 * Controlbar maximum value.
232 g_object_class_install_property (gobject_class, PROP_MAX,
233 g_param_spec_int ("max",
235 "Greatest possible value",
237 HILDON_CONTROLBAR_UPPER_VALUE,
238 G_PARAM_READABLE | G_PARAM_WRITABLE));
241 * HildonControlbar:value:
243 * Controlbar current value.
245 g_object_class_install_property (gobject_class, PROP_VALUE,
246 g_param_spec_int ("value",
250 HILDON_CONTROLBAR_INITIAL_VALUE,
251 G_PARAM_READABLE | G_PARAM_WRITABLE) );
254 gtk_widget_class_install_style_property (widget_class,
255 g_param_spec_uint ("inner_border_width",
256 "Inner border width",
257 "The border spacing between the controlbar border and controlbar blocks.",
259 DEFAULT_BORDER_WIDTH,
262 signals[END_REACHED] =
263 g_signal_new("end-reached",
264 G_OBJECT_CLASS_TYPE (gobject_class),
266 G_STRUCT_OFFSET (HildonControlbarClass, end_reached),
268 g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1,
273 hildon_controlbar_init (HildonControlbar *controlbar)
276 HildonControlbarPrivate *priv;
278 /* Initialize the private property */
279 priv = HILDON_CONTROLBAR_GET_PRIVATE(controlbar);
282 priv->button_press = FALSE;
284 range = GTK_RANGE (controlbar);
286 range->round_digits = -1;
288 gtk_widget_set_size_request (GTK_WIDGET (controlbar),
292 g_signal_connect (range, "change-value",
293 G_CALLBACK (hildon_controlbar_change_value), NULL);
297 hildon_controlbar_constructor (GType type,
298 guint n_construct_properties,
299 GObjectConstructParam *construct_properties)
304 obj = G_OBJECT_CLASS (parent_class)->constructor (type,
305 n_construct_properties, construct_properties);
307 gtk_scale_set_draw_value (GTK_SCALE (obj), FALSE);
309 /* Initialize the GtkAdjustment of the controlbar*/
310 adj = GTK_RANGE (obj)->adjustment;
311 adj->step_increment = HILDON_CONTROLBAR_STEP_INCREMENT;
312 adj->page_increment = HILDON_CONTROLBAR_PAGE_INCREMENT;
313 adj->page_size = HILDON_CONTROLBAR_PAGE_SIZE;
315 g_signal_connect (adj, "value-changed",
316 G_CALLBACK (hildon_controlbar_value_changed), obj);
321 hildon_controlbar_set_property (GObject *object,
326 HildonControlbar *controlbar = HILDON_CONTROLBAR (object);
331 hildon_controlbar_set_min (controlbar, g_value_get_int(value));
335 hildon_controlbar_set_max (controlbar, g_value_get_int(value));
339 hildon_controlbar_set_value (controlbar, g_value_get_int(value));
343 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
348 static void hildon_controlbar_get_property (GObject *object,
353 HildonControlbar *controlbar = HILDON_CONTROLBAR(object);
358 g_value_set_int (value, hildon_controlbar_get_min (controlbar));
362 g_value_set_int (value, hildon_controlbar_get_max (controlbar));
366 g_value_set_int (value, hildon_controlbar_get_value (controlbar));
370 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
377 hildon_controlbar_value_changed (GtkAdjustment *adj,
380 HildonControlbarPrivate *priv = HILDON_CONTROLBAR_GET_PRIVATE(range);
383 /* Change the controlbar value if the adjusted value is large enough
384 * otherwise, keep the old value
386 if (ABS(ceil (adj->value) - priv->old_value) >= 1)
388 priv->old_value = ceil (adj->value);
389 adj->value = priv->old_value;
392 g_signal_stop_emission_by_name (adj, "value-changed");
394 gtk_adjustment_set_value (adj, priv->old_value);
398 * hildon_controlbar_new:
400 * Creates a new #HildonControlbar widget.
402 * Returns: a #GtkWidget pointer of newly created control bar
406 hildon_controlbar_new (void)
408 return GTK_WIDGET (g_object_new (HILDON_TYPE_CONTROLBAR, NULL));
411 /* This function prevents Up and Down keys from changing the
412 * widget's value (like Left and Right).
413 * Instead they are used for changing focus to other widgtes.
416 hildon_controlbar_keypress (GtkWidget *widget,
419 if (event->keyval == GDK_Up || event->keyval == GDK_Down)
422 return ((GTK_WIDGET_CLASS (parent_class)->key_press_event) (widget, event));
426 hildon_controlbar_size_request (GtkWidget *self,
429 if (GTK_WIDGET_CLASS (parent_class)->size_request)
430 GTK_WIDGET_CLASS (parent_class)->size_request(self, req);
432 req->width = DEFAULT_WIDTH;
433 req->height = DEFAULT_HEIGHT;
437 * hildon_controlbar_set_value:
438 * @self: pointer to #HildonControlbar
439 * @value: value in range of >= 0 && < G_MAX_INT
441 * Set the current value of the control bar to the specified value.
444 hildon_controlbar_set_value (HildonControlbar * self,
448 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
449 adj = GTK_RANGE (self)->adjustment;
451 g_return_if_fail (value >= 0);
453 if (value >= adj->upper)
455 else if (value <= adj->lower)
459 gtk_adjustment_value_changed (adj);
461 g_object_notify (G_OBJECT(self), "value");
465 * hildon_controlbar_get_value:
466 * @self: pointer to #HildonControlbar
468 * Returns: current value as gint
471 hildon_controlbar_get_value (HildonControlbar * self)
474 g_return_val_if_fail (HILDON_IS_CONTROLBAR (self), 0);
475 adj = GTK_RANGE(self)->adjustment;
477 return (gint) ceil(adj->value);
481 * hildon_controlbar_set_max:
482 * @self: pointer to #HildonControlbar
483 * @max: maximum value to set. The value needs to be greater than 0.
485 * Set the control bar's maximum to the given value.
487 * If the new maximum is smaller than current value, the value will be
488 * adjusted so that it equals the new maximum.
491 hildon_controlbar_set_max (HildonControlbar * self,
495 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
496 adj = GTK_RANGE (self)->adjustment;
498 if (max < adj->lower)
501 if (adj->value > max)
502 hildon_controlbar_set_value (self, max);
505 gtk_adjustment_changed (adj);
507 g_object_notify (G_OBJECT(self), "max");
511 * hildon_controlbar_set_min:
512 * @self: pointer to #HildonControlbar
513 * @min: minimum value to set. The value needs to be greater than or
516 * Set the control bar's minimum to the given value.
518 * If the new minimum is smaller than current value, the value will be
519 * adjusted so that it equals the new minimum.
522 hildon_controlbar_set_min (HildonControlbar *self,
526 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
527 adj = GTK_RANGE (self)->adjustment;
529 if (min > adj->upper)
532 if (adj->value < min)
533 hildon_controlbar_set_value (self, min);
536 gtk_adjustment_changed (adj);
537 g_object_notify (G_OBJECT(self), "min");
541 * hildon_controlbar_set_range:
542 * @self: pointer to #HildonControlbar
543 * @max: maximum value to set. The value needs to be greater than 0.
544 * @min: Minimum value to set. The value needs to be greater than or
547 * Set the controlbars range to the given value
549 * If the new maximum is smaller than current value, the value will be
550 * adjusted so that it equals the new maximum.
552 * If the new minimum is smaller than current value, the value will be
553 * adjusted so that it equals the new minimum.
556 hildon_controlbar_set_range (HildonControlbar *self,
560 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
565 /* We need to set max first here, because when min is set before
566 * max is set, it would end up 0, because max can't be bigger than 0.
568 hildon_controlbar_set_max (self, max);
569 hildon_controlbar_set_min (self, min);
573 * hildon_controlbar_get_max:
574 * @self: a pointer to #HildonControlbar
576 * Returns: maximum value of control bar
578 gint hildon_controlbar_get_max (HildonControlbar *self)
581 g_return_val_if_fail (HILDON_IS_CONTROLBAR (self), 0);
582 adj = GTK_RANGE (self)->adjustment;
584 return (gint) adj->upper;
588 * hildon_controlbar_get_min:
589 * @self: a pointer to #HildonControlbar
591 * Returns: minimum value of controlbar
594 hildon_controlbar_get_min (HildonControlbar *self)
596 GtkAdjustment *adj = GTK_RANGE (self)->adjustment;
597 return (gint) adj->lower;
601 * Event handler for button press
602 * Need to change button1 to button2 before passing this event to
603 * parent handler. (see specs)
604 * Also updates button_press variable so that we can draw highlights
608 hildon_controlbar_button_press_event (GtkWidget *widget,
609 GdkEventButton *event)
611 HildonControlbar *self;
612 HildonControlbarPrivate *priv;
613 gboolean result = FALSE;
615 g_return_val_if_fail (widget, FALSE);
616 g_return_val_if_fail (event, FALSE);
618 self = HILDON_CONTROLBAR (widget);
619 priv = HILDON_CONTROLBAR_GET_PRIVATE (self);
622 priv->button_press = TRUE;
623 event->button = event->button == 1 ? 2 : event->button;
625 /* Ugh dirty hack. We manipulate the mouse event location to
626 compensate for centering the widget in case it is taller than the
628 if (widget->allocation.height > DEFAULT_HEIGHT) {
629 gint difference = widget->allocation.height - DEFAULT_HEIGHT;
633 difference = difference / 2;
635 event->y -= difference;
639 /* call the parent handler */
640 if (GTK_WIDGET_CLASS (parent_class)->button_press_event)
641 result = GTK_WIDGET_CLASS (parent_class)->button_press_event(widget, event);
647 * Purpose of this function is to prevent Up and Down keys from
648 * changing the widget's value (like Left and Right). Instead they
649 * are used for changing focus to other widgtes.
652 hildon_controlbar_change_value (GtkRange *range,
653 GtkScrollType scroll,
657 HildonControlbarPrivate *priv;
658 GtkAdjustment *adj = range->adjustment;
659 gdouble vv = adj->upper - adj->lower;
660 gint calc = ((new_value - adj->lower) / vv) * (vv + 1.0) + adj->lower;
662 priv = HILDON_CONTROLBAR_GET_PRIVATE(range);
665 /* Emit a signal when upper or lower limit is reached */
668 case GTK_SCROLL_STEP_FORWARD :
669 case GTK_SCROLL_PAGE_FORWARD :
670 if( adj->value == priv->old_value )
671 if( adj->value == adj->upper )
672 g_signal_emit( G_OBJECT(range), signals[END_REACHED], 0, TRUE );
675 case GTK_SCROLL_STEP_BACKWARD :
676 case GTK_SCROLL_PAGE_BACKWARD :
677 if( adj->value == priv->old_value )
678 if( adj->value == adj->lower )
679 g_signal_emit( G_OBJECT(range), signals[END_REACHED], 0, FALSE );
686 GTK_RANGE_CLASS (parent_class)->change_value (range, scroll, calc);
692 * Event handler for button release
693 * Need to change button1 to button2 before passing this event to
694 * parent handler. (see specs)
695 * Also updates button_press variable so that we can draw hilites
699 hildon_controlbar_button_release_event (GtkWidget *widget,
700 GdkEventButton *event)
702 HildonControlbar *self;
703 HildonControlbarPrivate *priv;
704 gboolean result = FALSE;
706 g_return_val_if_fail (widget, FALSE);
707 g_return_val_if_fail (event, FALSE);
709 self = HILDON_CONTROLBAR (widget);
710 priv = HILDON_CONTROLBAR_GET_PRIVATE (self);
713 priv->button_press = FALSE;
714 event->button = event->button == 1 ? 2 : event->button;
716 /* call the parent handler */
717 if (GTK_WIDGET_CLASS (parent_class)->button_release_event)
718 result = GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event);
724 * Event handler for expose event
727 hildon_controlbar_expose_event (GtkWidget *widget,
728 GdkEventExpose * event)
730 HildonControlbar *self = NULL;
732 gboolean result = FALSE;
733 gint old_height = -1;
736 g_return_val_if_fail (event, FALSE);
737 g_return_val_if_fail (HILDON_IS_CONTROLBAR(widget), FALSE);
739 self = HILDON_CONTROLBAR(widget);
741 old_height = widget->allocation.height;
742 old_y = widget->allocation.y;
744 if (widget->allocation.height > DEFAULT_HEIGHT) {
745 int difference = widget->allocation.height - DEFAULT_HEIGHT;
750 difference = difference / 2;
752 widget->allocation.y += difference;
753 widget->allocation.height = DEFAULT_HEIGHT;
756 /* call the parent handler */
757 if (GTK_WIDGET_CLASS (parent_class)->expose_event)
758 result = GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
760 hildon_controlbar_paint (self, &event->area);
762 widget->allocation.height = old_height;
763 widget->allocation.y = old_y;
770 * This is where all the work is actually done...
773 hildon_controlbar_paint (HildonControlbar *self,
776 HildonControlbarPrivate *priv;
777 GtkWidget *widget = GTK_WIDGET(self);
778 GtkAdjustment *ctrlbar = GTK_RANGE(self)->adjustment;
779 gint x = widget->allocation.x;
780 gint y = widget->allocation.y;
781 gint h = widget->allocation.height;
782 gint w = widget->allocation.width;
784 gint stepper_size = 0;
785 gint stepper_spacing = 0;
786 gint inner_border_width = 0;
787 gint block_area = 0, block_width = 0, block_x = 0, block_max = 0, block_height,block_y;
788 /* Number of blocks on the controlbar */
789 guint block_count = 0;
790 /* Number of displayed active blocks */
792 /* Minimum no. of blocks visible */
794 gint separatingpixels = 2;
795 gint block_remains = 0;
796 gint i, start_x, end_x, current_width;
797 GtkStateType state = GTK_STATE_NORMAL;
799 g_return_if_fail(area);
801 priv = HILDON_CONTROLBAR_GET_PRIVATE(self);
804 if (GTK_WIDGET_SENSITIVE (self) == FALSE)
805 state = GTK_STATE_INSENSITIVE;
807 gtk_widget_style_get (GTK_WIDGET (self),
808 "stepper-size", &stepper_size,
809 "stepper-spacing", &stepper_spacing,
810 "inner_border_width", &inner_border_width, NULL);
812 block_area = (w - 2 * stepper_size - 2 * stepper_spacing - 2 * inner_border_width);
818 block_max = ctrlbar->upper - ctrlbar->lower + block_min;
819 block_act = priv->old_value - GTK_RANGE (self)->adjustment->lower + block_min;
821 /* We check border width and maximum value and adjust
822 * separating pixels for block width here. If the block size would
823 * become too small, we make the separators smaller. Graceful fallback.
825 max = ctrlbar->upper;
826 if(ctrlbar->upper == 0)
827 separatingpixels = 3;
828 else if ((block_area - ((max - 1) * 3)) / max >= 4)
829 separatingpixels = 3;
830 else if ((block_area - ((max - 1) * 2)) / max >= 4)
831 separatingpixels = 2;
832 else if ((block_area - ((max - 1) * 1)) / max >= 4)
833 separatingpixels = 1;
835 separatingpixels = 0;
839 /* If block max is 0 then we dim the whole control. */
840 state = GTK_STATE_INSENSITIVE;
841 block_width = block_area;
848 (block_area - (separatingpixels * (block_max - 1))) / block_max;
850 (block_area - (separatingpixels * (block_max - 1))) % block_max;
853 block_x = x + stepper_size + stepper_spacing + inner_border_width;
854 block_y = y + inner_border_width;
855 block_height = h - 2 * inner_border_width;
857 block_count = ctrlbar->value - ctrlbar->lower + block_min;
859 if (block_count == 0)
861 /* Without this there is vertical block corruption when block_height =
862 1. This should work from 0 up to whatever */
864 if (block_height < 2)
868 * Changed the drawing of the blocks completely,
869 * because of "do-not-resize-when-changing-max"-specs.
870 * Now the code calculates from the block_remains when
871 * it should add one pixel to the block and when not.
874 for (i = 1; i <= block_max; i++) {
876 /* Here we calculate whether we add one pixel to current_width or
878 start_x = block_width * (i - 1) + ((i - 1) * block_remains) / block_max;
879 end_x = block_width * i + (i * block_remains) / block_max;
880 current_width = end_x - start_x;
882 gtk_paint_box (widget->style, widget->window, state,
883 (i <= block_count) ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
884 NULL, widget, "hildon_block",
885 block_x, block_y, current_width,
888 /* We keep the block_x separate because of the
889 'separatingpixels' */
890 block_x += current_width + separatingpixels;