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
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.
39 #include "hildon-controlbar.h"
42 #include <gdk/gdkkeysyms.h>
45 #include "hildon-controlbar-private.h"
48 dgettext("hildon-libs", string)
50 #define DEFAULT_WIDTH 234
52 #define DEFAULT_HEIGHT 30
54 #define DEFAULT_BORDER_WIDTH 2
56 #define HILDON_CONTROLBAR_STEP_INCREMENT 1
58 #define HILDON_CONTROLBAR_PAGE_INCREMENT 1
60 #define HILDON_CONTROLBAR_PAGE_SIZE 0
62 #define HILDON_CONTROLBAR_UPPER_VALUE 10
64 #define HILDON_CONTROLBAR_LOWER_VALUE 0.0
66 #define HILDON_CONTROLBAR_INITIAL_VALUE 0
68 static GtkScaleClass* parent_class;
84 static guint signals[LAST_SIGNAL] = { 0 };
87 hildon_controlbar_class_init (HildonControlbarClass *controlbar_class);
90 hildon_controlbar_init (HildonControlbar *controlbar);
93 hildon_controlbar_constructor (GType type,
94 guint n_construct_properties,
95 GObjectConstructParam *construct_properties);
98 hildon_controlbar_button_press_event (GtkWidget *widget,
99 GdkEventButton * event);
102 hildon_controlbar_button_release_event (GtkWidget *widget,
103 GdkEventButton *event);
106 hildon_controlbar_expose_event (GtkWidget *widget,
107 GdkEventExpose *event);
110 hildon_controlbar_size_request (GtkWidget *self,
111 GtkRequisition *req);
113 hildon_controlbar_paint (HildonControlbar *self,
114 GdkRectangle * area);
117 hildon_controlbar_keypress (GtkWidget *widget,
118 GdkEventKey * event);
121 hildon_controlbar_set_property (GObject *object,
127 hildon_controlbar_get_property (GObject *object,
133 hildon_controlbar_value_changed (GtkAdjustment *adj,
137 hildon_controlbar_change_value (GtkRange *range,
138 GtkScrollType scroll,
143 hildon_controlbar_get_type (void)
145 static GType controlbar_type = 0;
147 if (!controlbar_type) {
148 static const GTypeInfo controlbar_info = {
149 sizeof (HildonControlbarClass),
150 NULL, /* base_init */
151 NULL, /* base_finalize */
152 (GClassInitFunc) hildon_controlbar_class_init,
153 NULL, /* class_finalize */
154 NULL, /* class_data */
155 sizeof (HildonControlbar),
157 (GInstanceInitFunc) hildon_controlbar_init,
159 controlbar_type = g_type_register_static (GTK_TYPE_SCALE,
161 &controlbar_info, 0);
164 return controlbar_type;
168 hildon_controlbar_class_init (HildonControlbarClass *controlbar_class)
170 GObjectClass *gobject_class = G_OBJECT_CLASS (controlbar_class);
171 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (controlbar_class);
173 parent_class = g_type_class_peek_parent(controlbar_class);
175 g_type_class_add_private(controlbar_class, sizeof (HildonControlbarPrivate));
177 gobject_class->get_property = hildon_controlbar_get_property;
178 gobject_class->set_property = hildon_controlbar_set_property;
179 gobject_class->constructor = hildon_controlbar_constructor;
180 widget_class->size_request = hildon_controlbar_size_request;
181 widget_class->button_press_event = hildon_controlbar_button_press_event;
182 widget_class->button_release_event = hildon_controlbar_button_release_event;
183 widget_class->expose_event = hildon_controlbar_expose_event;
184 widget_class->key_press_event = hildon_controlbar_keypress;
185 controlbar_class->end_reached = NULL;
188 * HildonControlbar:min:
190 * Controlbar minimum value.
192 g_object_class_install_property (gobject_class, PROP_MIN,
193 g_param_spec_int ("min",
195 "Smallest possible value",
197 HILDON_CONTROLBAR_LOWER_VALUE,
198 G_PARAM_READABLE | G_PARAM_WRITABLE));
201 * HildonControlbar:max:
203 * Controlbar maximum value.
205 g_object_class_install_property (gobject_class, PROP_MAX,
206 g_param_spec_int ("max",
208 "Greatest possible value",
210 HILDON_CONTROLBAR_UPPER_VALUE,
211 G_PARAM_READABLE | G_PARAM_WRITABLE));
214 * HildonControlbar:value:
218 g_object_class_install_property (gobject_class, PROP_VALUE,
219 g_param_spec_int ("value",
223 HILDON_CONTROLBAR_INITIAL_VALUE,
224 G_PARAM_READABLE | G_PARAM_WRITABLE) );
227 gtk_widget_class_install_style_property (widget_class,
228 g_param_spec_uint ("inner_border_width",
229 "Inner border width",
230 "The border spacing between the controlbar border and controlbar blocks.",
232 DEFAULT_BORDER_WIDTH,
235 signals[END_REACHED] =
236 g_signal_new("end-reached",
237 G_OBJECT_CLASS_TYPE (gobject_class),
239 G_STRUCT_OFFSET (HildonControlbarClass, end_reached),
241 g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1,
246 hildon_controlbar_init (HildonControlbar *controlbar)
249 HildonControlbarPrivate *priv;
251 /* Initialize the private property */
252 priv = HILDON_CONTROLBAR_GET_PRIVATE(controlbar);
255 priv->button_press = FALSE;
257 range = GTK_RANGE (controlbar);
259 range->has_stepper_a = TRUE;
260 range->has_stepper_d = TRUE;
261 range->round_digits = -1;
263 gtk_widget_set_size_request (GTK_WIDGET (controlbar),
267 g_signal_connect (range, "change-value",
268 G_CALLBACK (hildon_controlbar_change_value), NULL );
272 hildon_controlbar_constructor (GType type,
273 guint n_construct_properties,
274 GObjectConstructParam *construct_properties)
279 obj = G_OBJECT_CLASS (parent_class)->constructor (type,
280 n_construct_properties, construct_properties);
282 gtk_scale_set_draw_value (GTK_SCALE (obj), FALSE);
284 /* Initialize the GtkAdjustment of the controlbar*/
285 adj = GTK_RANGE (obj)->adjustment;
286 adj->step_increment = HILDON_CONTROLBAR_STEP_INCREMENT;
287 adj->page_increment = HILDON_CONTROLBAR_PAGE_INCREMENT;
288 adj->page_size = HILDON_CONTROLBAR_PAGE_SIZE;
290 g_signal_connect (adj, "value-changed",
291 G_CALLBACK (hildon_controlbar_value_changed), obj);
296 hildon_controlbar_set_property (GObject *object,
301 HildonControlbar *controlbar = HILDON_CONTROLBAR (object);
306 hildon_controlbar_set_min (controlbar, g_value_get_int(value));
310 hildon_controlbar_set_max (controlbar, g_value_get_int(value));
314 hildon_controlbar_set_value (controlbar, g_value_get_int(value));
318 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
323 static void hildon_controlbar_get_property (GObject *object,
328 HildonControlbar *controlbar = HILDON_CONTROLBAR(object);
333 g_value_set_int (value, hildon_controlbar_get_min (controlbar));
337 g_value_set_int (value, hildon_controlbar_get_max (controlbar));
341 g_value_set_int (value, hildon_controlbar_get_value (controlbar));
345 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
352 hildon_controlbar_value_changed (GtkAdjustment *adj,
355 HildonControlbarPrivate *priv = HILDON_CONTROLBAR_GET_PRIVATE(range);
358 /* Change the controlbar value if the adjusted value is large enough
359 * otherwise, keep the old value
361 if (ABS(ceil (adj->value) - priv->old_value) >= 1)
363 priv->old_value = ceil (adj->value);
364 adj->value = priv->old_value;
367 g_signal_stop_emission_by_name (adj, "value-changed");
369 gtk_adjustment_set_value (adj, priv->old_value);
373 * hildon_controlbar_new:
375 * Creates a new #HildonControlbar widget.
377 * Returns: a #GtkWidget pointer of newly created control bar
381 hildon_controlbar_new (void)
383 return GTK_WIDGET (g_object_new (HILDON_TYPE_CONTROLBAR, NULL));
386 /* This function prevents Up and Down keys from changing the
387 * widget's value (like Left and Right).
388 * Instead they are used for changing focus to other widgtes.
391 hildon_controlbar_keypress (GtkWidget *widget,
394 if (event->keyval == GDK_Up || event->keyval == GDK_Down)
397 return ((GTK_WIDGET_CLASS (parent_class)->key_press_event) (widget, event));
401 hildon_controlbar_size_request (GtkWidget *self,
404 if (GTK_WIDGET_CLASS (parent_class)->size_request)
405 GTK_WIDGET_CLASS (parent_class)->size_request(self, req);
407 req->width = DEFAULT_WIDTH;
408 req->height = DEFAULT_HEIGHT;
412 * hildon_controlbar_set_value:
413 * @self: pointer to #HildonControlbar
414 * @value: value in range of >= 0 && < G_MAX_INT
416 * Change the current value of the control bar to the specified value.
419 hildon_controlbar_set_value (HildonControlbar * self,
423 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
424 adj = GTK_RANGE (self)->adjustment;
426 g_return_if_fail (value >= 0);
428 if (value >= adj->upper)
430 else if (value <= adj->lower)
434 gtk_adjustment_value_changed (adj);
436 g_object_notify (G_OBJECT(self), "value");
440 * hildon_controlbar_get_value:
441 * @self: pointer to #HildonControlbar
443 * Returns: current value as gint
446 hildon_controlbar_get_value (HildonControlbar * self)
449 g_return_val_if_fail (HILDON_IS_CONTROLBAR (self), 0);
450 adj = GTK_RANGE(self)->adjustment;
452 return (gint) ceil(adj->value);
456 * hildon_controlbar_set_max:
457 * @self: pointer to #HildonControlbar
458 * @max: maximum value to set. The value needs to be greater than 0.
460 * Set the control bar's maximum to the given value.
462 * If the new maximum is smaller than current value, the value will be
463 * adjusted so that it equals the new maximum.
466 hildon_controlbar_set_max (HildonControlbar * self,
470 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
471 adj = GTK_RANGE (self)->adjustment;
473 if (max < adj->lower)
476 if (adj->value > max)
477 hildon_controlbar_set_value (self, max);
480 gtk_adjustment_changed (adj);
482 g_object_notify (G_OBJECT(self), "max");
486 * hildon_controlbar_set_min:
487 * @self: pointer to #HildonControlbar
488 * @min: minimum value to set. The value needs to be greater than or
491 * Set the control bar's minimum to the given value.
493 * If the new minimum is smaller than current value, the value will be
494 * adjusted so that it equals the new minimum.
497 hildon_controlbar_set_min (HildonControlbar *self,
501 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
502 adj = GTK_RANGE (self)->adjustment;
504 if (min > adj->upper)
507 if (adj->value < min)
508 hildon_controlbar_set_value (self, min);
511 gtk_adjustment_changed (adj);
512 g_object_notify (G_OBJECT(self), "min");
516 * hildon_controlbar_set_range:
517 * @self: pointer to #HildonControlbar
518 * @max: maximum value to set. The value needs to be greater than 0.
519 * @min: Minimum value to set. The value needs to be greater than or
522 * Set the controlbars range to the given value
524 * If the new maximum is smaller than current value, the value will be
525 * adjusted so that it equals the new maximum.
527 * If the new minimum is smaller than current value, the value will be
528 * adjusted so that it equals the new minimum.
531 hildon_controlbar_set_range (HildonControlbar *self,
535 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
540 /* We need to set max first here, because when min is set before
541 * max is set, it would end up 0, because max can't be bigger than 0.
543 hildon_controlbar_set_max (self, max);
544 hildon_controlbar_set_min (self, min);
548 * hildon_controlbar_get_max:
549 * @self: a pointer to #HildonControlbar
551 * Returns: maximum value of control bar
553 gint hildon_controlbar_get_max (HildonControlbar *self)
556 g_return_val_if_fail (HILDON_IS_CONTROLBAR (self), 0);
557 adj = GTK_RANGE (self)->adjustment;
559 return (gint) adj->upper;
563 * hildon_controlbar_get_min:
564 * @self: a pointer to #HildonControlbar
566 * Returns: minimum value of controlbar
569 hildon_controlbar_get_min (HildonControlbar *self)
571 GtkAdjustment *adj = GTK_RANGE (self)->adjustment;
572 return (gint) adj->lower;
576 * Event handler for button press
577 * Need to change button1 to button2 before passing this event to
578 * parent handler. (see specs)
579 * Also updates button_press variable so that we can draw hilites
583 hildon_controlbar_button_press_event (GtkWidget *widget,
584 GdkEventButton *event)
586 HildonControlbar *self;
587 HildonControlbarPrivate *priv;
588 gboolean result = FALSE;
590 g_return_val_if_fail (widget, FALSE);
591 g_return_val_if_fail (event, FALSE);
593 self = HILDON_CONTROLBAR (widget);
594 priv = HILDON_CONTROLBAR_GET_PRIVATE (self);
597 priv->button_press = TRUE;
598 event->button = event->button == 1 ? 2 : event->button;
600 /* Ugh dirty hack. We manipulate the mouse event location to
601 compensate for centering the widget in case it is taller than the
603 if (widget->allocation.height > DEFAULT_HEIGHT) {
604 gint difference = widget->allocation.height - DEFAULT_HEIGHT;
608 difference = difference / 2;
610 event->y -= difference;
614 /* call the parent handler */
615 if (GTK_WIDGET_CLASS (parent_class)->button_press_event)
616 result = GTK_WIDGET_CLASS (parent_class)->button_press_event(widget, event);
622 * Purpose of this function is to prevent Up and Down keys from
623 * changing the widget's value (like Left and Right). Instead they
624 * are used for changing focus to other widgtes.
627 hildon_controlbar_change_value (GtkRange *range,
628 GtkScrollType scroll,
632 HildonControlbarPrivate *priv;
633 GtkAdjustment *adj = range->adjustment;
635 priv = HILDON_CONTROLBAR_GET_PRIVATE(range);
638 /* Emit a signal when upper or lower limit is reached */
641 case GTK_SCROLL_STEP_FORWARD :
642 case GTK_SCROLL_PAGE_FORWARD :
643 if( adj->value == priv->old_value )
644 if( adj->value == adj->upper )
645 g_signal_emit( G_OBJECT(range), signals[END_REACHED], 0, TRUE );
648 case GTK_SCROLL_STEP_BACKWARD :
649 case GTK_SCROLL_PAGE_BACKWARD :
650 if( adj->value == priv->old_value )
651 if( adj->value == adj->lower )
652 g_signal_emit( G_OBJECT(range), signals[END_REACHED], 0, FALSE );
662 * Event handler for button release
663 * Need to change button1 to button2 before passing this event to
664 * parent handler. (see specs)
665 * Also updates button_press variable so that we can draw hilites
669 hildon_controlbar_button_release_event (GtkWidget *widget,
670 GdkEventButton *event)
672 HildonControlbar *self;
673 HildonControlbarPrivate *priv;
674 gboolean result = FALSE;
676 g_return_val_if_fail (widget, FALSE);
677 g_return_val_if_fail (event, FALSE);
679 self = HILDON_CONTROLBAR (widget);
680 priv = HILDON_CONTROLBAR_GET_PRIVATE (self);
683 priv->button_press = FALSE;
684 event->button = event->button == 1 ? 2 : event->button;
686 /* call the parent handler */
687 if (GTK_WIDGET_CLASS (parent_class)->button_release_event)
688 result = GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event);
694 * Event handler for expose event
697 hildon_controlbar_expose_event (GtkWidget *widget,
698 GdkEventExpose * event)
700 HildonControlbar *self = NULL;
702 gboolean result = FALSE;
703 gint old_height = -1;
706 g_return_val_if_fail (event, FALSE);
707 g_return_val_if_fail (HILDON_IS_CONTROLBAR(widget), FALSE);
709 self = HILDON_CONTROLBAR(widget);
711 old_height = widget->allocation.height;
712 old_y = widget->allocation.y;
714 if (widget->allocation.height > DEFAULT_HEIGHT) {
715 int difference = widget->allocation.height - DEFAULT_HEIGHT;
720 difference = difference / 2;
722 widget->allocation.y += difference;
723 widget->allocation.height = DEFAULT_HEIGHT;
726 /* call the parent handler */
727 if (GTK_WIDGET_CLASS (parent_class)->expose_event)
728 result = GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
730 hildon_controlbar_paint (self, &event->area);
732 widget->allocation.height = old_height;
733 widget->allocation.y = old_y;
740 * This is where all the work is actually done...
743 hildon_controlbar_paint (HildonControlbar *self,
746 HildonControlbarPrivate *priv;
747 GtkWidget *widget = GTK_WIDGET(self);
748 GtkAdjustment *ctrlbar = GTK_RANGE(self)->adjustment;
749 gint x = widget->allocation.x;
750 gint y = widget->allocation.y;
751 gint h = widget->allocation.height;
752 gint w = widget->allocation.width;
754 gint stepper_size = 0;
755 gint stepper_spacing = 0;
756 gint inner_border_width = 0;
757 gint block_area = 0, block_width = 0, block_x = 0, block_max = 0, block_height,block_y;
758 /* Number of blocks on the controlbar */
759 guint block_count = 0;
760 /* Number of displayed active blocks */
762 /* Minimum no. of blocks visible */
764 gint separatingpixels = 2;
765 gint block_remains = 0;
766 gint i, start_x, end_x, current_width;
767 GtkStateType state = GTK_STATE_NORMAL;
769 g_return_if_fail(area);
771 priv = HILDON_CONTROLBAR_GET_PRIVATE(self);
774 if (GTK_WIDGET_SENSITIVE (self) == FALSE)
775 state = GTK_STATE_INSENSITIVE;
777 gtk_widget_style_get (GTK_WIDGET (self),
778 "stepper-size", &stepper_size,
779 "stepper-spacing", &stepper_spacing,
780 "inner_border_width", &inner_border_width, NULL);
782 g_object_get (G_OBJECT (self), "minimum_visible_bars", &block_min, NULL);
784 block_area = (w - 2 * stepper_size - 2 * stepper_spacing - 2 * inner_border_width);
789 block_max = ctrlbar->upper - ctrlbar->lower + block_min;
790 block_act = priv->old_value - GTK_RANGE (self)->adjustment->lower + block_min;
792 /* We check border width and maximum value and adjust
793 * separating pixels for block width here. If the block size would
794 * become too small, we make the separators smaller. Graceful fallback.
796 max = ctrlbar->upper;
797 if(ctrlbar->upper == 0)
798 separatingpixels = 3;
799 else if ((block_area - ((max - 1) * 3)) / max >= 4)
800 separatingpixels = 3;
801 else if ((block_area - ((max - 1) * 2)) / max >= 4)
802 separatingpixels = 2;
803 else if ((block_area - ((max - 1) * 1)) / max >= 4)
804 separatingpixels = 1;
806 separatingpixels = 0;
810 /* If block max is 0 then we dim the whole control. */
811 state = GTK_STATE_INSENSITIVE;
812 block_width = block_area;
819 (block_area - (separatingpixels * (block_max - 1))) / block_max;
821 (block_area - (separatingpixels * (block_max - 1))) % block_max;
824 block_x = x + stepper_size + stepper_spacing + inner_border_width;
825 block_y = y + inner_border_width;
826 block_height = h - 2 * inner_border_width;
828 block_count = ctrlbar->value - ctrlbar->lower + block_min;
830 /* Without this there is vertical block corruption when block_height =
831 1. This should work from 0 up to whatever */
833 if (block_height < 2)
837 * Changed the drawing of the blocks completely,
838 * because of "do-not-resize-when-changing-max"-specs.
839 * Now the code calculates from the block_remains when
840 * it should add one pixel to the block and when not.
843 for (i = 1; i <= block_max; i++) {
845 /* Here we calculate whether we add one pixel to current_width or
847 start_x = block_width * (i - 1) + ((i - 1) * block_remains) / block_max;
848 end_x = block_width * i + (i * block_remains) / block_max;
849 current_width = end_x - start_x;
851 gtk_paint_box (widget->style, widget->window, state,
852 (i <= block_count) ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
853 NULL, widget, "hildon_block",
854 block_x, block_y, current_width,
857 /* We keep the block_x separate because of the
858 'separatingpixels' */
859 block_x += current_width + separatingpixels;