2 * This file is a part of hildon
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 (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.
35 * <title>HildonControlbar example</title>
37 * GtkWidget *cbar = hildon_controlbar_new();
38 * hildon_controlbar_set_max (HILDON_CONTROLBAR (cbar), 12);
39 * hildon_controlbar_set_value (HILDON_CONTROLBAR (cbar), 6);
49 #include "hildon-controlbar.h"
52 #include <gdk/gdkkeysyms.h>
55 #include "hildon-controlbar-private.h"
58 dgettext("hildon-libs", string)
60 #define DEFAULT_WIDTH 234
62 #define DEFAULT_HEIGHT 30
64 #define DEFAULT_BORDER_WIDTH 2
66 #define HILDON_CONTROLBAR_STEP_INCREMENT 1
68 #define HILDON_CONTROLBAR_PAGE_INCREMENT 1
70 #define HILDON_CONTROLBAR_PAGE_SIZE 0
72 #define HILDON_CONTROLBAR_UPPER_VALUE 10
74 #define HILDON_CONTROLBAR_LOWER_VALUE 0.0
76 #define HILDON_CONTROLBAR_INITIAL_VALUE 0
78 static GtkScaleClass* parent_class;
94 static guint signals[LAST_SIGNAL] = { 0 };
97 hildon_controlbar_class_init (HildonControlbarClass *controlbar_class);
100 hildon_controlbar_init (HildonControlbar *controlbar);
103 hildon_controlbar_constructor (GType type,
104 guint n_construct_properties,
105 GObjectConstructParam *construct_properties);
108 hildon_controlbar_button_press_event (GtkWidget *widget,
109 GdkEventButton * event);
112 hildon_controlbar_button_release_event (GtkWidget *widget,
113 GdkEventButton *event);
116 hildon_controlbar_expose_event (GtkWidget *widget,
117 GdkEventExpose *event);
120 hildon_controlbar_size_request (GtkWidget *self,
121 GtkRequisition *req);
123 hildon_controlbar_paint (HildonControlbar *self,
124 GdkRectangle * area);
127 hildon_controlbar_keypress (GtkWidget *widget,
128 GdkEventKey * event);
131 hildon_controlbar_set_property (GObject *object,
137 hildon_controlbar_get_property (GObject *object,
143 hildon_controlbar_value_changed (GtkAdjustment *adj,
147 hildon_controlbar_change_value (GtkRange *range,
148 GtkScrollType scroll,
153 * hildon_controlbar_get_type:
155 * Initializes and returns the type of a hildon control bar.
157 * @Returns: GType of #HildonControlbar
160 hildon_controlbar_get_type (void)
162 static GType controlbar_type = 0;
164 if (!controlbar_type) {
165 static const GTypeInfo controlbar_info = {
166 sizeof (HildonControlbarClass),
167 NULL, /* base_init */
168 NULL, /* base_finalize */
169 (GClassInitFunc) hildon_controlbar_class_init,
170 NULL, /* class_finalize */
171 NULL, /* class_data */
172 sizeof (HildonControlbar),
174 (GInstanceInitFunc) hildon_controlbar_init,
176 controlbar_type = g_type_register_static (GTK_TYPE_SCALE,
178 &controlbar_info, 0);
181 return controlbar_type;
185 hildon_controlbar_class_init (HildonControlbarClass *controlbar_class)
187 GObjectClass *gobject_class = G_OBJECT_CLASS (controlbar_class);
188 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (controlbar_class);
190 parent_class = g_type_class_peek_parent(controlbar_class);
192 g_type_class_add_private(controlbar_class, sizeof (HildonControlbarPrivate));
194 gobject_class->get_property = hildon_controlbar_get_property;
195 gobject_class->set_property = hildon_controlbar_set_property;
196 gobject_class->constructor = hildon_controlbar_constructor;
197 widget_class->size_request = hildon_controlbar_size_request;
198 widget_class->button_press_event = hildon_controlbar_button_press_event;
199 widget_class->button_release_event = hildon_controlbar_button_release_event;
200 widget_class->expose_event = hildon_controlbar_expose_event;
201 widget_class->key_press_event = hildon_controlbar_keypress;
202 controlbar_class->end_reached = NULL;
205 * HildonControlbar:min:
207 * Controlbar minimum value.
209 g_object_class_install_property (gobject_class, PROP_MIN,
210 g_param_spec_int ("min",
212 "Smallest possible value",
214 HILDON_CONTROLBAR_LOWER_VALUE,
215 G_PARAM_READABLE | G_PARAM_WRITABLE));
218 * HildonControlbar:max:
220 * Controlbar maximum value.
222 g_object_class_install_property (gobject_class, PROP_MAX,
223 g_param_spec_int ("max",
225 "Greatest possible value",
227 HILDON_CONTROLBAR_UPPER_VALUE,
228 G_PARAM_READABLE | G_PARAM_WRITABLE));
231 * HildonControlbar:value:
233 * Controlbar current value.
235 g_object_class_install_property (gobject_class, PROP_VALUE,
236 g_param_spec_int ("value",
240 HILDON_CONTROLBAR_INITIAL_VALUE,
241 G_PARAM_READABLE | G_PARAM_WRITABLE) );
244 gtk_widget_class_install_style_property (widget_class,
245 g_param_spec_uint ("inner_border_width",
246 "Inner border width",
247 "The border spacing between the controlbar border and controlbar blocks.",
249 DEFAULT_BORDER_WIDTH,
252 signals[END_REACHED] =
253 g_signal_new("end-reached",
254 G_OBJECT_CLASS_TYPE (gobject_class),
256 G_STRUCT_OFFSET (HildonControlbarClass, end_reached),
258 g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1,
263 hildon_controlbar_init (HildonControlbar *controlbar)
266 HildonControlbarPrivate *priv;
268 /* Initialize the private property */
269 priv = HILDON_CONTROLBAR_GET_PRIVATE(controlbar);
272 priv->button_press = FALSE;
274 range = GTK_RANGE (controlbar);
276 range->has_stepper_a = TRUE;
277 range->has_stepper_d = TRUE;
278 range->round_digits = -1;
280 gtk_widget_set_size_request (GTK_WIDGET (controlbar),
284 g_signal_connect (range, "change-value",
285 G_CALLBACK (hildon_controlbar_change_value), NULL);
289 hildon_controlbar_constructor (GType type,
290 guint n_construct_properties,
291 GObjectConstructParam *construct_properties)
296 obj = G_OBJECT_CLASS (parent_class)->constructor (type,
297 n_construct_properties, construct_properties);
299 gtk_scale_set_draw_value (GTK_SCALE (obj), FALSE);
301 /* Initialize the GtkAdjustment of the controlbar*/
302 adj = GTK_RANGE (obj)->adjustment;
303 adj->step_increment = HILDON_CONTROLBAR_STEP_INCREMENT;
304 adj->page_increment = HILDON_CONTROLBAR_PAGE_INCREMENT;
305 adj->page_size = HILDON_CONTROLBAR_PAGE_SIZE;
307 g_signal_connect (adj, "value-changed",
308 G_CALLBACK (hildon_controlbar_value_changed), obj);
313 hildon_controlbar_set_property (GObject *object,
318 HildonControlbar *controlbar = HILDON_CONTROLBAR (object);
323 hildon_controlbar_set_min (controlbar, g_value_get_int(value));
327 hildon_controlbar_set_max (controlbar, g_value_get_int(value));
331 hildon_controlbar_set_value (controlbar, g_value_get_int(value));
335 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
340 static void hildon_controlbar_get_property (GObject *object,
345 HildonControlbar *controlbar = HILDON_CONTROLBAR(object);
350 g_value_set_int (value, hildon_controlbar_get_min (controlbar));
354 g_value_set_int (value, hildon_controlbar_get_max (controlbar));
358 g_value_set_int (value, hildon_controlbar_get_value (controlbar));
362 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
369 hildon_controlbar_value_changed (GtkAdjustment *adj,
372 HildonControlbarPrivate *priv = HILDON_CONTROLBAR_GET_PRIVATE(range);
375 /* Change the controlbar value if the adjusted value is large enough
376 * otherwise, keep the old value
378 if (ABS(ceil (adj->value) - priv->old_value) >= 1)
380 priv->old_value = ceil (adj->value);
381 adj->value = priv->old_value;
384 g_signal_stop_emission_by_name (adj, "value-changed");
386 gtk_adjustment_set_value (adj, priv->old_value);
390 * hildon_controlbar_new:
392 * Creates a new #HildonControlbar widget.
394 * Returns: a #GtkWidget pointer of newly created control bar
398 hildon_controlbar_new (void)
400 return GTK_WIDGET (g_object_new (HILDON_TYPE_CONTROLBAR, NULL));
403 /* This function prevents Up and Down keys from changing the
404 * widget's value (like Left and Right).
405 * Instead they are used for changing focus to other widgtes.
408 hildon_controlbar_keypress (GtkWidget *widget,
411 if (event->keyval == GDK_Up || event->keyval == GDK_Down)
414 return ((GTK_WIDGET_CLASS (parent_class)->key_press_event) (widget, event));
418 hildon_controlbar_size_request (GtkWidget *self,
421 if (GTK_WIDGET_CLASS (parent_class)->size_request)
422 GTK_WIDGET_CLASS (parent_class)->size_request(self, req);
424 req->width = DEFAULT_WIDTH;
425 req->height = DEFAULT_HEIGHT;
429 * hildon_controlbar_set_value:
430 * @self: pointer to #HildonControlbar
431 * @value: value in range of >= 0 && < G_MAX_INT
433 * Set the current value of the control bar to the specified value.
436 hildon_controlbar_set_value (HildonControlbar * self,
440 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
441 adj = GTK_RANGE (self)->adjustment;
443 g_return_if_fail (value >= 0);
445 if (value >= adj->upper)
447 else if (value <= adj->lower)
451 gtk_adjustment_value_changed (adj);
453 g_object_notify (G_OBJECT(self), "value");
457 * hildon_controlbar_get_value:
458 * @self: pointer to #HildonControlbar
460 * Returns: current value as gint
463 hildon_controlbar_get_value (HildonControlbar * self)
466 g_return_val_if_fail (HILDON_IS_CONTROLBAR (self), 0);
467 adj = GTK_RANGE(self)->adjustment;
469 return (gint) ceil(adj->value);
473 * hildon_controlbar_set_max:
474 * @self: pointer to #HildonControlbar
475 * @max: maximum value to set. The value needs to be greater than 0.
477 * Set the control bar's maximum to the given value.
479 * If the new maximum is smaller than current value, the value will be
480 * adjusted so that it equals the new maximum.
483 hildon_controlbar_set_max (HildonControlbar * self,
487 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
488 adj = GTK_RANGE (self)->adjustment;
490 if (max < adj->lower)
493 if (adj->value > max)
494 hildon_controlbar_set_value (self, max);
497 gtk_adjustment_changed (adj);
499 g_object_notify (G_OBJECT(self), "max");
503 * hildon_controlbar_set_min:
504 * @self: pointer to #HildonControlbar
505 * @min: minimum value to set. The value needs to be greater than or
508 * Set the control bar's minimum to the given value.
510 * If the new minimum is smaller than current value, the value will be
511 * adjusted so that it equals the new minimum.
514 hildon_controlbar_set_min (HildonControlbar *self,
518 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
519 adj = GTK_RANGE (self)->adjustment;
521 if (min > adj->upper)
524 if (adj->value < min)
525 hildon_controlbar_set_value (self, min);
528 gtk_adjustment_changed (adj);
529 g_object_notify (G_OBJECT(self), "min");
533 * hildon_controlbar_set_range:
534 * @self: pointer to #HildonControlbar
535 * @max: maximum value to set. The value needs to be greater than 0.
536 * @min: Minimum value to set. The value needs to be greater than or
539 * Set the controlbars range to the given value
541 * If the new maximum is smaller than current value, the value will be
542 * adjusted so that it equals the new maximum.
544 * If the new minimum is smaller than current value, the value will be
545 * adjusted so that it equals the new minimum.
548 hildon_controlbar_set_range (HildonControlbar *self,
552 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
557 /* We need to set max first here, because when min is set before
558 * max is set, it would end up 0, because max can't be bigger than 0.
560 hildon_controlbar_set_max (self, max);
561 hildon_controlbar_set_min (self, min);
565 * hildon_controlbar_get_max:
566 * @self: a pointer to #HildonControlbar
568 * Returns: maximum value of control bar
570 gint hildon_controlbar_get_max (HildonControlbar *self)
573 g_return_val_if_fail (HILDON_IS_CONTROLBAR (self), 0);
574 adj = GTK_RANGE (self)->adjustment;
576 return (gint) adj->upper;
580 * hildon_controlbar_get_min:
581 * @self: a pointer to #HildonControlbar
583 * Returns: minimum value of controlbar
586 hildon_controlbar_get_min (HildonControlbar *self)
588 GtkAdjustment *adj = GTK_RANGE (self)->adjustment;
589 return (gint) adj->lower;
593 * Event handler for button press
594 * Need to change button1 to button2 before passing this event to
595 * parent handler. (see specs)
596 * Also updates button_press variable so that we can draw highlights
600 hildon_controlbar_button_press_event (GtkWidget *widget,
601 GdkEventButton *event)
603 HildonControlbar *self;
604 HildonControlbarPrivate *priv;
605 gboolean result = FALSE;
607 g_return_val_if_fail (widget, FALSE);
608 g_return_val_if_fail (event, FALSE);
610 self = HILDON_CONTROLBAR (widget);
611 priv = HILDON_CONTROLBAR_GET_PRIVATE (self);
614 priv->button_press = TRUE;
615 event->button = event->button == 1 ? 2 : event->button;
617 /* Ugh dirty hack. We manipulate the mouse event location to
618 compensate for centering the widget in case it is taller than the
620 if (widget->allocation.height > DEFAULT_HEIGHT) {
621 gint difference = widget->allocation.height - DEFAULT_HEIGHT;
625 difference = difference / 2;
627 event->y -= difference;
631 /* call the parent handler */
632 if (GTK_WIDGET_CLASS (parent_class)->button_press_event)
633 result = GTK_WIDGET_CLASS (parent_class)->button_press_event(widget, event);
639 * Purpose of this function is to prevent Up and Down keys from
640 * changing the widget's value (like Left and Right). Instead they
641 * are used for changing focus to other widgtes.
644 hildon_controlbar_change_value (GtkRange *range,
645 GtkScrollType scroll,
649 HildonControlbarPrivate *priv;
650 GtkAdjustment *adj = range->adjustment;
651 gdouble vv = adj->upper - adj->lower;
652 gint calc = ((new_value - adj->lower) / vv) * (vv + 1.0) + adj->lower;
654 priv = HILDON_CONTROLBAR_GET_PRIVATE(range);
657 /* Emit a signal when upper or lower limit is reached */
660 case GTK_SCROLL_STEP_FORWARD :
661 case GTK_SCROLL_PAGE_FORWARD :
662 if( adj->value == priv->old_value )
663 if( adj->value == adj->upper )
664 g_signal_emit( G_OBJECT(range), signals[END_REACHED], 0, TRUE );
667 case GTK_SCROLL_STEP_BACKWARD :
668 case GTK_SCROLL_PAGE_BACKWARD :
669 if( adj->value == priv->old_value )
670 if( adj->value == adj->lower )
671 g_signal_emit( G_OBJECT(range), signals[END_REACHED], 0, FALSE );
678 GTK_RANGE_CLASS (parent_class)->change_value (range, scroll, calc);
684 * Event handler for button release
685 * Need to change button1 to button2 before passing this event to
686 * parent handler. (see specs)
687 * Also updates button_press variable so that we can draw hilites
691 hildon_controlbar_button_release_event (GtkWidget *widget,
692 GdkEventButton *event)
694 HildonControlbar *self;
695 HildonControlbarPrivate *priv;
696 gboolean result = FALSE;
698 g_return_val_if_fail (widget, FALSE);
699 g_return_val_if_fail (event, FALSE);
701 self = HILDON_CONTROLBAR (widget);
702 priv = HILDON_CONTROLBAR_GET_PRIVATE (self);
705 priv->button_press = FALSE;
706 event->button = event->button == 1 ? 2 : event->button;
708 /* call the parent handler */
709 if (GTK_WIDGET_CLASS (parent_class)->button_release_event)
710 result = GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event);
716 * Event handler for expose event
719 hildon_controlbar_expose_event (GtkWidget *widget,
720 GdkEventExpose * event)
722 HildonControlbar *self = NULL;
724 gboolean result = FALSE;
725 gint old_height = -1;
728 g_return_val_if_fail (event, FALSE);
729 g_return_val_if_fail (HILDON_IS_CONTROLBAR(widget), FALSE);
731 self = HILDON_CONTROLBAR(widget);
733 old_height = widget->allocation.height;
734 old_y = widget->allocation.y;
736 if (widget->allocation.height > DEFAULT_HEIGHT) {
737 int difference = widget->allocation.height - DEFAULT_HEIGHT;
742 difference = difference / 2;
744 widget->allocation.y += difference;
745 widget->allocation.height = DEFAULT_HEIGHT;
748 /* call the parent handler */
749 if (GTK_WIDGET_CLASS (parent_class)->expose_event)
750 result = GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
752 hildon_controlbar_paint (self, &event->area);
754 widget->allocation.height = old_height;
755 widget->allocation.y = old_y;
762 * This is where all the work is actually done...
765 hildon_controlbar_paint (HildonControlbar *self,
768 HildonControlbarPrivate *priv;
769 GtkWidget *widget = GTK_WIDGET(self);
770 GtkAdjustment *ctrlbar = GTK_RANGE(self)->adjustment;
771 gint x = widget->allocation.x;
772 gint y = widget->allocation.y;
773 gint h = widget->allocation.height;
774 gint w = widget->allocation.width;
776 gint stepper_size = 0;
777 gint stepper_spacing = 0;
778 gint inner_border_width = 0;
779 gint block_area = 0, block_width = 0, block_x = 0, block_max = 0, block_height,block_y;
780 /* Number of blocks on the controlbar */
781 guint block_count = 0;
782 /* Number of displayed active blocks */
784 /* Minimum no. of blocks visible */
786 gint separatingpixels = 2;
787 gint block_remains = 0;
788 gint i, start_x, end_x, current_width;
789 GtkStateType state = GTK_STATE_NORMAL;
791 g_return_if_fail(area);
793 priv = HILDON_CONTROLBAR_GET_PRIVATE(self);
796 if (GTK_WIDGET_SENSITIVE (self) == FALSE)
797 state = GTK_STATE_INSENSITIVE;
799 gtk_widget_style_get (GTK_WIDGET (self),
800 "stepper-size", &stepper_size,
801 "stepper-spacing", &stepper_spacing,
802 "inner_border_width", &inner_border_width, NULL);
804 block_area = (w - 2 * stepper_size - 2 * stepper_spacing - 2 * inner_border_width);
810 block_max = ctrlbar->upper - ctrlbar->lower + block_min;
811 block_act = priv->old_value - GTK_RANGE (self)->adjustment->lower + block_min;
813 /* We check border width and maximum value and adjust
814 * separating pixels for block width here. If the block size would
815 * become too small, we make the separators smaller. Graceful fallback.
817 max = ctrlbar->upper;
818 if(ctrlbar->upper == 0)
819 separatingpixels = 3;
820 else if ((block_area - ((max - 1) * 3)) / max >= 4)
821 separatingpixels = 3;
822 else if ((block_area - ((max - 1) * 2)) / max >= 4)
823 separatingpixels = 2;
824 else if ((block_area - ((max - 1) * 1)) / max >= 4)
825 separatingpixels = 1;
827 separatingpixels = 0;
831 /* If block max is 0 then we dim the whole control. */
832 state = GTK_STATE_INSENSITIVE;
833 block_width = block_area;
840 (block_area - (separatingpixels * (block_max - 1))) / block_max;
842 (block_area - (separatingpixels * (block_max - 1))) % block_max;
845 block_x = x + stepper_size + stepper_spacing + inner_border_width;
846 block_y = y + inner_border_width;
847 block_height = h - 2 * inner_border_width;
849 block_count = ctrlbar->value - ctrlbar->lower + block_min;
851 if (block_count == 0)
853 /* Without this there is vertical block corruption when block_height =
854 1. This should work from 0 up to whatever */
856 if (block_height < 2)
860 * Changed the drawing of the blocks completely,
861 * because of "do-not-resize-when-changing-max"-specs.
862 * Now the code calculates from the block_remains when
863 * it should add one pixel to the block and when not.
866 for (i = 1; i <= block_max; i++) {
868 /* Here we calculate whether we add one pixel to current_width or
870 start_x = block_width * (i - 1) + ((i - 1) * block_remains) / block_max;
871 end_x = block_width * i + (i * block_remains) / block_max;
872 current_width = end_x - start_x;
874 gtk_paint_box (widget->style, widget->window, state,
875 (i <= block_count) ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
876 NULL, widget, "hildon_block",
877 block_x, block_y, current_width,
880 /* We keep the block_x separate because of the
881 'separatingpixels' */
882 block_x += current_width + separatingpixels;