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:
145 * Initializes and returns the type of a hildon control bar.
147 * @Returns: GType of #HildonControlbar
150 hildon_controlbar_get_type (void)
152 static GType controlbar_type = 0;
154 if (!controlbar_type) {
155 static const GTypeInfo controlbar_info = {
156 sizeof (HildonControlbarClass),
157 NULL, /* base_init */
158 NULL, /* base_finalize */
159 (GClassInitFunc) hildon_controlbar_class_init,
160 NULL, /* class_finalize */
161 NULL, /* class_data */
162 sizeof (HildonControlbar),
164 (GInstanceInitFunc) hildon_controlbar_init,
166 controlbar_type = g_type_register_static (GTK_TYPE_SCALE,
168 &controlbar_info, 0);
171 return controlbar_type;
175 hildon_controlbar_class_init (HildonControlbarClass *controlbar_class)
177 GObjectClass *gobject_class = G_OBJECT_CLASS (controlbar_class);
178 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (controlbar_class);
180 parent_class = g_type_class_peek_parent(controlbar_class);
182 g_type_class_add_private(controlbar_class, sizeof (HildonControlbarPrivate));
184 gobject_class->get_property = hildon_controlbar_get_property;
185 gobject_class->set_property = hildon_controlbar_set_property;
186 gobject_class->constructor = hildon_controlbar_constructor;
187 widget_class->size_request = hildon_controlbar_size_request;
188 widget_class->button_press_event = hildon_controlbar_button_press_event;
189 widget_class->button_release_event = hildon_controlbar_button_release_event;
190 widget_class->expose_event = hildon_controlbar_expose_event;
191 widget_class->key_press_event = hildon_controlbar_keypress;
192 controlbar_class->end_reached = NULL;
195 * HildonControlbar:min:
197 * Controlbar minimum value.
199 g_object_class_install_property (gobject_class, PROP_MIN,
200 g_param_spec_int ("min",
202 "Smallest possible value",
204 HILDON_CONTROLBAR_LOWER_VALUE,
205 G_PARAM_READABLE | G_PARAM_WRITABLE));
208 * HildonControlbar:max:
210 * Controlbar maximum value.
212 g_object_class_install_property (gobject_class, PROP_MAX,
213 g_param_spec_int ("max",
215 "Greatest possible value",
217 HILDON_CONTROLBAR_UPPER_VALUE,
218 G_PARAM_READABLE | G_PARAM_WRITABLE));
221 * HildonControlbar:value:
223 * Controlbar current value.
225 g_object_class_install_property (gobject_class, PROP_VALUE,
226 g_param_spec_int ("value",
230 HILDON_CONTROLBAR_INITIAL_VALUE,
231 G_PARAM_READABLE | G_PARAM_WRITABLE) );
234 gtk_widget_class_install_style_property (widget_class,
235 g_param_spec_uint ("inner_border_width",
236 "Inner border width",
237 "The border spacing between the controlbar border and controlbar blocks.",
239 DEFAULT_BORDER_WIDTH,
242 signals[END_REACHED] =
243 g_signal_new("end-reached",
244 G_OBJECT_CLASS_TYPE (gobject_class),
246 G_STRUCT_OFFSET (HildonControlbarClass, end_reached),
248 g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1,
253 hildon_controlbar_init (HildonControlbar *controlbar)
256 HildonControlbarPrivate *priv;
258 /* Initialize the private property */
259 priv = HILDON_CONTROLBAR_GET_PRIVATE(controlbar);
262 priv->button_press = FALSE;
264 range = GTK_RANGE (controlbar);
266 range->has_stepper_a = TRUE;
267 range->has_stepper_d = TRUE;
268 range->round_digits = -1;
270 gtk_widget_set_size_request (GTK_WIDGET (controlbar),
274 g_signal_connect (range, "change-value",
275 G_CALLBACK (hildon_controlbar_change_value), NULL );
279 hildon_controlbar_constructor (GType type,
280 guint n_construct_properties,
281 GObjectConstructParam *construct_properties)
286 obj = G_OBJECT_CLASS (parent_class)->constructor (type,
287 n_construct_properties, construct_properties);
289 gtk_scale_set_draw_value (GTK_SCALE (obj), FALSE);
291 /* Initialize the GtkAdjustment of the controlbar*/
292 adj = GTK_RANGE (obj)->adjustment;
293 adj->step_increment = HILDON_CONTROLBAR_STEP_INCREMENT;
294 adj->page_increment = HILDON_CONTROLBAR_PAGE_INCREMENT;
295 adj->page_size = HILDON_CONTROLBAR_PAGE_SIZE;
297 g_signal_connect (adj, "value-changed",
298 G_CALLBACK (hildon_controlbar_value_changed), obj);
303 hildon_controlbar_set_property (GObject *object,
308 HildonControlbar *controlbar = HILDON_CONTROLBAR (object);
313 hildon_controlbar_set_min (controlbar, g_value_get_int(value));
317 hildon_controlbar_set_max (controlbar, g_value_get_int(value));
321 hildon_controlbar_set_value (controlbar, g_value_get_int(value));
325 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
330 static void hildon_controlbar_get_property (GObject *object,
335 HildonControlbar *controlbar = HILDON_CONTROLBAR(object);
340 g_value_set_int (value, hildon_controlbar_get_min (controlbar));
344 g_value_set_int (value, hildon_controlbar_get_max (controlbar));
348 g_value_set_int (value, hildon_controlbar_get_value (controlbar));
352 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
359 hildon_controlbar_value_changed (GtkAdjustment *adj,
362 HildonControlbarPrivate *priv = HILDON_CONTROLBAR_GET_PRIVATE(range);
365 /* Change the controlbar value if the adjusted value is large enough
366 * otherwise, keep the old value
368 if (ABS(ceil (adj->value) - priv->old_value) >= 1)
370 priv->old_value = ceil (adj->value);
371 adj->value = priv->old_value;
374 g_signal_stop_emission_by_name (adj, "value-changed");
376 gtk_adjustment_set_value (adj, priv->old_value);
380 * hildon_controlbar_new:
382 * Creates a new #HildonControlbar widget.
384 * Returns: a #GtkWidget pointer of newly created control bar
388 hildon_controlbar_new (void)
390 return GTK_WIDGET (g_object_new (HILDON_TYPE_CONTROLBAR, NULL));
393 /* This function prevents Up and Down keys from changing the
394 * widget's value (like Left and Right).
395 * Instead they are used for changing focus to other widgtes.
398 hildon_controlbar_keypress (GtkWidget *widget,
401 if (event->keyval == GDK_Up || event->keyval == GDK_Down)
404 return ((GTK_WIDGET_CLASS (parent_class)->key_press_event) (widget, event));
408 hildon_controlbar_size_request (GtkWidget *self,
411 if (GTK_WIDGET_CLASS (parent_class)->size_request)
412 GTK_WIDGET_CLASS (parent_class)->size_request(self, req);
414 req->width = DEFAULT_WIDTH;
415 req->height = DEFAULT_HEIGHT;
419 * hildon_controlbar_set_value:
420 * @self: pointer to #HildonControlbar
421 * @value: value in range of >= 0 && < G_MAX_INT
423 * Set the current value of the control bar to the specified value.
426 hildon_controlbar_set_value (HildonControlbar * self,
430 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
431 adj = GTK_RANGE (self)->adjustment;
433 g_return_if_fail (value >= 0);
435 if (value >= adj->upper)
437 else if (value <= adj->lower)
441 gtk_adjustment_value_changed (adj);
443 g_object_notify (G_OBJECT(self), "value");
447 * hildon_controlbar_get_value:
448 * @self: pointer to #HildonControlbar
450 * Returns: current value as gint
453 hildon_controlbar_get_value (HildonControlbar * self)
456 g_return_val_if_fail (HILDON_IS_CONTROLBAR (self), 0);
457 adj = GTK_RANGE(self)->adjustment;
459 return (gint) ceil(adj->value);
463 * hildon_controlbar_set_max:
464 * @self: pointer to #HildonControlbar
465 * @max: maximum value to set. The value needs to be greater than 0.
467 * Set the control bar's maximum to the given value.
469 * If the new maximum is smaller than current value, the value will be
470 * adjusted so that it equals the new maximum.
473 hildon_controlbar_set_max (HildonControlbar * self,
477 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
478 adj = GTK_RANGE (self)->adjustment;
480 if (max < adj->lower)
483 if (adj->value > max)
484 hildon_controlbar_set_value (self, max);
487 gtk_adjustment_changed (adj);
489 g_object_notify (G_OBJECT(self), "max");
493 * hildon_controlbar_set_min:
494 * @self: pointer to #HildonControlbar
495 * @min: minimum value to set. The value needs to be greater than or
498 * Set the control bar's minimum to the given value.
500 * If the new minimum is smaller than current value, the value will be
501 * adjusted so that it equals the new minimum.
504 hildon_controlbar_set_min (HildonControlbar *self,
508 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
509 adj = GTK_RANGE (self)->adjustment;
511 if (min > adj->upper)
514 if (adj->value < min)
515 hildon_controlbar_set_value (self, min);
518 gtk_adjustment_changed (adj);
519 g_object_notify (G_OBJECT(self), "min");
523 * hildon_controlbar_set_range:
524 * @self: pointer to #HildonControlbar
525 * @max: maximum value to set. The value needs to be greater than 0.
526 * @min: Minimum value to set. The value needs to be greater than or
529 * Set the controlbars range to the given value
531 * If the new maximum is smaller than current value, the value will be
532 * adjusted so that it equals the new maximum.
534 * If the new minimum is smaller than current value, the value will be
535 * adjusted so that it equals the new minimum.
538 hildon_controlbar_set_range (HildonControlbar *self,
542 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
547 /* We need to set max first here, because when min is set before
548 * max is set, it would end up 0, because max can't be bigger than 0.
550 hildon_controlbar_set_max (self, max);
551 hildon_controlbar_set_min (self, min);
555 * hildon_controlbar_get_max:
556 * @self: a pointer to #HildonControlbar
558 * Returns: maximum value of control bar
560 gint hildon_controlbar_get_max (HildonControlbar *self)
563 g_return_val_if_fail (HILDON_IS_CONTROLBAR (self), 0);
564 adj = GTK_RANGE (self)->adjustment;
566 return (gint) adj->upper;
570 * hildon_controlbar_get_min:
571 * @self: a pointer to #HildonControlbar
573 * Returns: minimum value of controlbar
576 hildon_controlbar_get_min (HildonControlbar *self)
578 GtkAdjustment *adj = GTK_RANGE (self)->adjustment;
579 return (gint) adj->lower;
583 * Event handler for button press
584 * Need to change button1 to button2 before passing this event to
585 * parent handler. (see specs)
586 * Also updates button_press variable so that we can draw highlights
590 hildon_controlbar_button_press_event (GtkWidget *widget,
591 GdkEventButton *event)
593 HildonControlbar *self;
594 HildonControlbarPrivate *priv;
595 gboolean result = FALSE;
597 g_return_val_if_fail (widget, FALSE);
598 g_return_val_if_fail (event, FALSE);
600 self = HILDON_CONTROLBAR (widget);
601 priv = HILDON_CONTROLBAR_GET_PRIVATE (self);
604 priv->button_press = TRUE;
605 event->button = event->button == 1 ? 2 : event->button;
607 /* Ugh dirty hack. We manipulate the mouse event location to
608 compensate for centering the widget in case it is taller than the
610 if (widget->allocation.height > DEFAULT_HEIGHT) {
611 gint difference = widget->allocation.height - DEFAULT_HEIGHT;
615 difference = difference / 2;
617 event->y -= difference;
621 /* call the parent handler */
622 if (GTK_WIDGET_CLASS (parent_class)->button_press_event)
623 result = GTK_WIDGET_CLASS (parent_class)->button_press_event(widget, event);
629 * Purpose of this function is to prevent Up and Down keys from
630 * changing the widget's value (like Left and Right). Instead they
631 * are used for changing focus to other widgtes.
634 hildon_controlbar_change_value (GtkRange *range,
635 GtkScrollType scroll,
639 HildonControlbarPrivate *priv;
640 GtkAdjustment *adj = range->adjustment;
642 priv = HILDON_CONTROLBAR_GET_PRIVATE(range);
645 /* Emit a signal when upper or lower limit is reached */
648 case GTK_SCROLL_STEP_FORWARD :
649 case GTK_SCROLL_PAGE_FORWARD :
650 if( adj->value == priv->old_value )
651 if( adj->value == adj->upper )
652 g_signal_emit( G_OBJECT(range), signals[END_REACHED], 0, TRUE );
655 case GTK_SCROLL_STEP_BACKWARD :
656 case GTK_SCROLL_PAGE_BACKWARD :
657 if( adj->value == priv->old_value )
658 if( adj->value == adj->lower )
659 g_signal_emit( G_OBJECT(range), signals[END_REACHED], 0, FALSE );
669 * Event handler for button release
670 * Need to change button1 to button2 before passing this event to
671 * parent handler. (see specs)
672 * Also updates button_press variable so that we can draw hilites
676 hildon_controlbar_button_release_event (GtkWidget *widget,
677 GdkEventButton *event)
679 HildonControlbar *self;
680 HildonControlbarPrivate *priv;
681 gboolean result = FALSE;
683 g_return_val_if_fail (widget, FALSE);
684 g_return_val_if_fail (event, FALSE);
686 self = HILDON_CONTROLBAR (widget);
687 priv = HILDON_CONTROLBAR_GET_PRIVATE (self);
690 priv->button_press = FALSE;
691 event->button = event->button == 1 ? 2 : event->button;
693 /* call the parent handler */
694 if (GTK_WIDGET_CLASS (parent_class)->button_release_event)
695 result = GTK_WIDGET_CLASS (parent_class)->button_release_event (widget, event);
701 * Event handler for expose event
704 hildon_controlbar_expose_event (GtkWidget *widget,
705 GdkEventExpose * event)
707 HildonControlbar *self = NULL;
709 gboolean result = FALSE;
710 gint old_height = -1;
713 g_return_val_if_fail (event, FALSE);
714 g_return_val_if_fail (HILDON_IS_CONTROLBAR(widget), FALSE);
716 self = HILDON_CONTROLBAR(widget);
718 old_height = widget->allocation.height;
719 old_y = widget->allocation.y;
721 if (widget->allocation.height > DEFAULT_HEIGHT) {
722 int difference = widget->allocation.height - DEFAULT_HEIGHT;
727 difference = difference / 2;
729 widget->allocation.y += difference;
730 widget->allocation.height = DEFAULT_HEIGHT;
733 /* call the parent handler */
734 if (GTK_WIDGET_CLASS (parent_class)->expose_event)
735 result = GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
737 hildon_controlbar_paint (self, &event->area);
739 widget->allocation.height = old_height;
740 widget->allocation.y = old_y;
747 * This is where all the work is actually done...
750 hildon_controlbar_paint (HildonControlbar *self,
753 HildonControlbarPrivate *priv;
754 GtkWidget *widget = GTK_WIDGET(self);
755 GtkAdjustment *ctrlbar = GTK_RANGE(self)->adjustment;
756 gint x = widget->allocation.x;
757 gint y = widget->allocation.y;
758 gint h = widget->allocation.height;
759 gint w = widget->allocation.width;
761 gint stepper_size = 0;
762 gint stepper_spacing = 0;
763 gint inner_border_width = 0;
764 gint block_area = 0, block_width = 0, block_x = 0, block_max = 0, block_height,block_y;
765 /* Number of blocks on the controlbar */
766 guint block_count = 0;
767 /* Number of displayed active blocks */
769 /* Minimum no. of blocks visible */
771 gint separatingpixels = 2;
772 gint block_remains = 0;
773 gint i, start_x, end_x, current_width;
774 GtkStateType state = GTK_STATE_NORMAL;
776 g_return_if_fail(area);
778 priv = HILDON_CONTROLBAR_GET_PRIVATE(self);
781 if (GTK_WIDGET_SENSITIVE (self) == FALSE)
782 state = GTK_STATE_INSENSITIVE;
784 gtk_widget_style_get (GTK_WIDGET (self),
785 "stepper-size", &stepper_size,
786 "stepper-spacing", &stepper_spacing,
787 "inner_border_width", &inner_border_width, NULL);
789 g_object_get (G_OBJECT (self), "minimum_visible_bars", &block_min, NULL);
791 block_area = (w - 2 * stepper_size - 2 * stepper_spacing - 2 * inner_border_width);
796 block_max = ctrlbar->upper - ctrlbar->lower + block_min;
797 block_act = priv->old_value - GTK_RANGE (self)->adjustment->lower + block_min;
799 /* We check border width and maximum value and adjust
800 * separating pixels for block width here. If the block size would
801 * become too small, we make the separators smaller. Graceful fallback.
803 max = ctrlbar->upper;
804 if(ctrlbar->upper == 0)
805 separatingpixels = 3;
806 else if ((block_area - ((max - 1) * 3)) / max >= 4)
807 separatingpixels = 3;
808 else if ((block_area - ((max - 1) * 2)) / max >= 4)
809 separatingpixels = 2;
810 else if ((block_area - ((max - 1) * 1)) / max >= 4)
811 separatingpixels = 1;
813 separatingpixels = 0;
817 /* If block max is 0 then we dim the whole control. */
818 state = GTK_STATE_INSENSITIVE;
819 block_width = block_area;
826 (block_area - (separatingpixels * (block_max - 1))) / block_max;
828 (block_area - (separatingpixels * (block_max - 1))) % block_max;
831 block_x = x + stepper_size + stepper_spacing + inner_border_width;
832 block_y = y + inner_border_width;
833 block_height = h - 2 * inner_border_width;
835 block_count = ctrlbar->value - ctrlbar->lower + block_min;
837 /* Without this there is vertical block corruption when block_height =
838 1. This should work from 0 up to whatever */
840 if (block_height < 2)
844 * Changed the drawing of the blocks completely,
845 * because of "do-not-resize-when-changing-max"-specs.
846 * Now the code calculates from the block_remains when
847 * it should add one pixel to the block and when not.
850 for (i = 1; i <= block_max; i++) {
852 /* Here we calculate whether we add one pixel to current_width or
854 start_x = block_width * (i - 1) + ((i - 1) * block_remains) / block_max;
855 end_x = block_width * i + (i * block_remains) / block_max;
856 current_width = end_x - start_x;
858 gtk_paint_box (widget->style, widget->window, state,
859 (i <= block_count) ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
860 NULL, widget, "hildon_block",
861 block_x, block_y, current_width,
864 /* We keep the block_x separate because of the
865 'separatingpixels' */
866 block_x += current_width + separatingpixels;