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
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 #include "hildon-controlbar.h"
39 #include <gdk/gdkkeysyms.h>
43 #define _(string) dgettext(PACKAGE, string)
45 #define HILDON_CONTROLBAR_GET_PRIVATE(obj) \
46 (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
47 HILDON_TYPE_CONTROLBAR, HildonControlbarPrivate));
49 #define DEFAULT_WIDTH 234
50 #define DEFAULT_HEIGHT 30
51 #define DEFAULT_BORDER_WIDTH 2
53 #define HILDON_CONTROLBAR_STEP_INCREMENT 1
54 #define HILDON_CONTROLBAR_PAGE_INCREMENT 1
55 #define HILDON_CONTROLBAR_PAGE_SIZE 0
56 #define HILDON_CONTROLBAR_UPPER_VALUE 10
57 #define HILDON_CONTROLBAR_LOWER_VALUE 0.0
58 #define HILDON_CONTROLBAR_INITIAL_VALUE 0
60 static GtkScaleClass *parent_class;
62 typedef struct _HildonControlbarPrivate HildonControlbarPrivate;
77 static guint signals[LAST_SIGNAL] = { 0 };
80 hildon_controlbar_class_init(HildonControlbarClass * controlbar_class);
81 static void hildon_controlbar_init(HildonControlbar * controlbar);
83 hildon_controlbar_constructor(GType type, guint n_construct_properties,
84 GObjectConstructParam *construct_properties);
86 static gint hildon_controlbar_button_press_event(GtkWidget * widget,
87 GdkEventButton * event);
88 static gint hildon_controlbar_button_release_event(GtkWidget * widget,
89 GdkEventButton * event);
91 hildon_controlbar_expose_event(GtkWidget * widget, GdkEventExpose * event);
93 hildon_controlbar_size_request(GtkWidget * self, GtkRequisition * req);
95 hildon_controlbar_paint(HildonControlbar * self, GdkRectangle * area);
97 hildon_controlbar_keypress(GtkWidget * widget, GdkEventKey * event);
99 static void hildon_controlbar_set_property( GObject *object, guint param_id,
100 const GValue *value, GParamSpec *pspec );
101 static void hildon_controlbar_get_property( GObject *object, guint param_id,
102 GValue *value, GParamSpec *pspec );
105 hildon_controlbar_value_changed( GtkAdjustment *adj, GtkRange *range );
108 * Purpose of this function is to prevent Up and Down keys from
109 * changing the widget's value (like Left and Right). Instead they
110 * are used for changing focus to other widgtes.
113 hildon_controlbar_change_value( GtkRange *range, GtkScrollType scroll,
114 gdouble new_value, gpointer data );
116 GType hildon_controlbar_get_type(void)
118 static GType controlbar_type = 0;
120 if (!controlbar_type) {
121 static const GTypeInfo controlbar_info = {
122 sizeof(HildonControlbarClass),
123 NULL, /* base_init */
124 NULL, /* base_finalize */
125 (GClassInitFunc) hildon_controlbar_class_init,
126 NULL, /* class_finalize */
127 NULL, /* class_data */
128 sizeof(HildonControlbar),
130 (GInstanceInitFunc) hildon_controlbar_init,
132 controlbar_type = g_type_register_static(GTK_TYPE_SCALE,
134 &controlbar_info, 0);
136 return controlbar_type;
139 struct _HildonControlbarPrivate {
140 gboolean button_press;
145 hildon_controlbar_class_init(HildonControlbarClass * controlbar_class)
147 GObjectClass *gobject_class = G_OBJECT_CLASS(controlbar_class);
148 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(controlbar_class);
150 parent_class = g_type_class_peek_parent(controlbar_class);
152 g_type_class_add_private(controlbar_class,
153 sizeof(HildonControlbarPrivate));
155 gobject_class->get_property = hildon_controlbar_get_property;
156 gobject_class->set_property = hildon_controlbar_set_property;
157 widget_class->size_request = hildon_controlbar_size_request;
158 widget_class->button_press_event = hildon_controlbar_button_press_event;
159 widget_class->button_release_event = hildon_controlbar_button_release_event;
160 widget_class->expose_event = hildon_controlbar_expose_event;
161 widget_class->key_press_event = hildon_controlbar_keypress;
162 G_OBJECT_CLASS(controlbar_class)->constructor = hildon_controlbar_constructor;
163 controlbar_class->end_reached = NULL;
166 * HildonControlbar:min:
168 * Controlbar minimum value.
170 g_object_class_install_property( gobject_class, PROP_MIN,
171 g_param_spec_int("min",
173 "Smallest possible value",
175 HILDON_CONTROLBAR_LOWER_VALUE,
176 G_PARAM_READABLE | G_PARAM_WRITABLE) );
179 * HildonControlbar:max:
181 * Controlbar maximum value.
183 g_object_class_install_property( gobject_class, PROP_MAX,
184 g_param_spec_int("max",
186 "Greatest possible value",
188 HILDON_CONTROLBAR_UPPER_VALUE,
189 G_PARAM_READABLE | G_PARAM_WRITABLE) );
192 * HildonControlbar:value:
196 g_object_class_install_property( gobject_class, PROP_VALUE,
197 g_param_spec_int("value",
201 HILDON_CONTROLBAR_INITIAL_VALUE,
202 G_PARAM_READABLE | G_PARAM_WRITABLE) );
205 gtk_widget_class_install_style_property(widget_class,
206 g_param_spec_uint("inner_border_width",
207 "Inner border width",
208 "The border spacing between the controlbar border and controlbar blocks.",
210 DEFAULT_BORDER_WIDTH,
213 signals[END_REACHED] =
214 g_signal_new("end-reached",
215 G_OBJECT_CLASS_TYPE(gobject_class),
217 G_STRUCT_OFFSET(HildonControlbarClass, end_reached),
219 g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1,
223 static void hildon_controlbar_init(HildonControlbar * controlbar)
226 HildonControlbarPrivate *priv;
228 /* Initialize the private property */
229 priv = HILDON_CONTROLBAR_GET_PRIVATE(controlbar);
230 priv->button_press = FALSE;
232 range = GTK_RANGE(controlbar);
234 range->has_stepper_a = TRUE;
235 range->has_stepper_d = TRUE;
236 range->round_digits = -1;
238 gtk_widget_set_size_request( GTK_WIDGET(controlbar), DEFAULT_WIDTH,
240 g_signal_connect( range, "change-value",
241 G_CALLBACK(hildon_controlbar_change_value), NULL );
244 static GObject *hildon_controlbar_constructor(GType type,
245 guint n_construct_properties, GObjectConstructParam *construct_properties)
250 obj = G_OBJECT_CLASS(parent_class)->constructor(type,
251 n_construct_properties, construct_properties);
253 gtk_scale_set_draw_value (GTK_SCALE (obj), FALSE);
255 /* Initialize the GtkAdjustment of the controlbar*/
256 adj = GTK_RANGE(obj)->adjustment;
257 adj->step_increment = HILDON_CONTROLBAR_STEP_INCREMENT;
258 adj->page_increment = HILDON_CONTROLBAR_PAGE_INCREMENT;
259 adj->page_size = HILDON_CONTROLBAR_PAGE_SIZE;
261 g_signal_connect( adj, "value-changed",
262 G_CALLBACK(hildon_controlbar_value_changed), obj );
266 static void hildon_controlbar_set_property (GObject *object, guint param_id,
267 const GValue *value, GParamSpec *pspec)
269 HildonControlbar *controlbar = HILDON_CONTROLBAR(object);
273 hildon_controlbar_set_min (controlbar, g_value_get_int(value));
277 hildon_controlbar_set_max (controlbar, g_value_get_int(value));
281 hildon_controlbar_set_value (controlbar, g_value_get_int(value));
285 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
290 static void hildon_controlbar_get_property( GObject *object, guint param_id,
291 GValue *value, GParamSpec *pspec )
293 HildonControlbar *controlbar = HILDON_CONTROLBAR(object);
297 g_value_set_int (value, hildon_controlbar_get_min (controlbar));
301 g_value_set_int (value, hildon_controlbar_get_max (controlbar));
305 g_value_set_int (value, hildon_controlbar_get_value (controlbar));
309 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
316 hildon_controlbar_value_changed( GtkAdjustment *adj, GtkRange *range )
318 HildonControlbarPrivate *priv = HILDON_CONTROLBAR_GET_PRIVATE(range);
320 /* Change the controlbar value if the adjusted value is large enough
321 * otherwise, keep the old value
323 if( ABS(ceil(adj->value) - priv->old_value) >= 1 )
325 priv->old_value = ceil(adj->value);
326 adj->value = priv->old_value;
329 g_signal_stop_emission_by_name( adj, "value-changed" );
330 gtk_adjustment_set_value( adj, priv->old_value );
334 * hildon_controlbar_new:
336 * Creates a new #HildonControlbar widget.
338 * Returns: a #GtkWidget pointer of newly created control bar
341 GtkWidget *hildon_controlbar_new(void)
343 return GTK_WIDGET(g_object_new(HILDON_TYPE_CONTROLBAR, NULL));
346 /* This function prevents Up and Down keys from changing the
347 * widget's value (like Left and Right).
348 * Instead they are used for changing focus to other widgtes.
351 hildon_controlbar_keypress(GtkWidget * widget, GdkEventKey * event)
353 if (event->keyval == GDK_Up || event->keyval == GDK_Down)
355 return ((GTK_WIDGET_CLASS(parent_class)->key_press_event) (widget, event));
359 hildon_controlbar_size_request(GtkWidget * self, GtkRequisition * req)
361 if (GTK_WIDGET_CLASS(parent_class)->size_request)
362 GTK_WIDGET_CLASS(parent_class)->size_request(self, req);
364 req->width = DEFAULT_WIDTH;
365 req->height = DEFAULT_HEIGHT;
369 * hildon_controlbar_set_value:
370 * @self: pointer to #HildonControlbar
371 * @value: value in range of >= 0 && < G_MAX_INT
373 * Change the current value of the control bar to the specified value.
375 void hildon_controlbar_set_value(HildonControlbar * self, gint value)
378 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
379 adj = GTK_RANGE(self)->adjustment;
381 g_return_if_fail(value >= 0);
383 if (value >= adj->upper)
385 else if (value <= adj->lower)
389 gtk_adjustment_value_changed(adj);
391 g_object_notify (G_OBJECT(self), "value");
395 * hildon_controlbar_get_value:
396 * @self: pointer to #HildonControlbar
398 * Returns: current value as gint
400 gint hildon_controlbar_get_value(HildonControlbar * self)
403 g_return_val_if_fail (HILDON_IS_CONTROLBAR (self), 0);
404 adj = GTK_RANGE(self)->adjustment;
406 return (gint) ceil(adj->value);
410 * hildon_controlbar_set_max:
411 * @self: pointer to #HildonControlbar
412 * @max: maximum value to set. The value needs to be greater than 0.
414 * Set the control bar's maximum to the given value.
416 * If the new maximum is smaller than current value, the value will be
417 * adjusted so that it equals the new maximum.
419 void hildon_controlbar_set_max(HildonControlbar * self, gint max)
422 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
423 adj = GTK_RANGE(self)->adjustment;
425 if (max < adj->lower)
428 if (adj->value > max)
429 hildon_controlbar_set_value (self, max);
432 gtk_adjustment_changed(adj);
434 g_object_notify (G_OBJECT(self), "max");
438 * hildon_controlbar_set_min:
439 * @self: pointer to #HildonControlbar
440 * @min: minimum value to set. The value needs to be greater than or
443 * Set the control bar's minimum to the given value.
445 * If the new minimum is smaller than current value, the value will be
446 * adjusted so that it equals the new minimum.
448 void hildon_controlbar_set_min(HildonControlbar * self, gint min)
451 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
452 adj = GTK_RANGE(self)->adjustment;
454 if (min > adj->upper)
457 if (adj->value < min)
458 hildon_controlbar_set_value (self, min);
461 gtk_adjustment_changed(adj);
462 g_object_notify (G_OBJECT(self), "min");
466 * hildon_controlbar_set_range:
467 * @self: pointer to #HildonControlbar
468 * @max: maximum value to set. The value needs to be greater than 0.
469 * @min: Minimum value to set. The value needs to be greater than or
472 * Set the controlbars range to the given value
474 * If the new maximum is smaller than current value, the value will be
475 * adjusted so that it equals the new maximum.
477 * If the new minimum is smaller than current value, the value will be
478 * adjusted so that it equals the new minimum.
480 void hildon_controlbar_set_range(HildonControlbar * self, gint min,
483 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
487 /* We need to set max first here, because when min is set before
488 * max is set, it would end up 0, because max can't be bigger than 0.
490 hildon_controlbar_set_max (self, max);
491 hildon_controlbar_set_min (self, min);
495 * hildon_controlbar_get_max:
496 * @self: a pointer to #HildonControlbar
498 * Returns: maximum value of control bar
500 gint hildon_controlbar_get_max(HildonControlbar * self)
503 g_return_val_if_fail (HILDON_IS_CONTROLBAR (self), 0);
504 adj = GTK_RANGE(self)->adjustment;
506 return (gint) adj->upper;
510 * hildon_controlbar_get_min:
511 * @self: a pointer to #HildonControlbar
513 * Returns: minimum value of controlbar
515 gint hildon_controlbar_get_min(HildonControlbar * self)
517 GtkAdjustment *adj = GTK_RANGE(self)->adjustment;
518 return (gint) adj->lower;
522 * Event handler for button press
523 * Need to change button1 to button2 before passing this event to
524 * parent handler. (see specs)
525 * Also updates button_press variable so that we can draw hilites
528 static gint hildon_controlbar_button_press_event(GtkWidget * widget,
529 GdkEventButton * event)
531 HildonControlbar *self;
532 HildonControlbarPrivate *priv;
533 gboolean result = FALSE;
535 g_return_val_if_fail(widget, FALSE);
536 g_return_val_if_fail(event, FALSE);
538 self = HILDON_CONTROLBAR(widget);
539 priv = HILDON_CONTROLBAR_GET_PRIVATE(self);
541 priv->button_press = TRUE;
542 event->button = event->button == 1 ? 2 : event->button;
544 /* Ugh dirty hack. We manipulate the mouse event location to
545 compensate for centering the widget in case it is taller than the
547 if (widget->allocation.height > DEFAULT_HEIGHT) {
548 gint difference = widget->allocation.height - DEFAULT_HEIGHT;
552 difference = difference / 2;
554 event->y -= difference;
558 /* call the parent handler */
559 if (GTK_WIDGET_CLASS(parent_class)->button_press_event)
561 GTK_WIDGET_CLASS(parent_class)->button_press_event(widget, event);
567 * Purpose of this function is to prevent Up and Down keys from
568 * changing the widget's value (like Left and Right). Instead they
569 * are used for changing focus to other widgtes.
572 hildon_controlbar_change_value( GtkRange *range,
573 GtkScrollType scroll,
577 HildonControlbarPrivate *priv;
578 GtkAdjustment *adj = range->adjustment;
580 priv = HILDON_CONTROLBAR_GET_PRIVATE(range);
582 /* Emit a signal when upper or lower limit is reached */
585 case GTK_SCROLL_STEP_FORWARD :
586 case GTK_SCROLL_PAGE_FORWARD :
587 if( adj->value == priv->old_value )
588 if( adj->value == adj->upper )
589 g_signal_emit( G_OBJECT(range), signals[END_REACHED], 0, TRUE );
592 case GTK_SCROLL_STEP_BACKWARD :
593 case GTK_SCROLL_PAGE_BACKWARD :
594 if( adj->value == priv->old_value )
595 if( adj->value == adj->lower )
596 g_signal_emit( G_OBJECT(range), signals[END_REACHED], 0, FALSE );
606 * Event handler for button release
607 * Need to change button1 to button2 before passing this event to
608 * parent handler. (see specs)
609 * Also updates button_press variable so that we can draw hilites
612 static gint hildon_controlbar_button_release_event(GtkWidget * widget,
613 GdkEventButton * event)
615 HildonControlbar *self;
616 HildonControlbarPrivate *priv;
617 gboolean result = FALSE;
619 g_return_val_if_fail(widget, FALSE);
620 g_return_val_if_fail(event, FALSE);
622 self = HILDON_CONTROLBAR(widget);
623 priv = HILDON_CONTROLBAR_GET_PRIVATE(self);
625 priv->button_press = FALSE;
626 event->button = event->button == 1 ? 2 : event->button;
628 /* call the parent handler */
629 if (GTK_WIDGET_CLASS(parent_class)->button_release_event)
631 GTK_WIDGET_CLASS(parent_class)->button_release_event(widget, event);
636 * Event handler for expose event
638 static gint hildon_controlbar_expose_event(GtkWidget * widget,
639 GdkEventExpose * event)
641 HildonControlbar *self = NULL;
643 gboolean result = FALSE;
644 gint old_height = -1;
647 g_return_val_if_fail(widget, FALSE);
648 g_return_val_if_fail(event, FALSE);
649 g_return_val_if_fail(HILDON_IS_CONTROLBAR(widget), FALSE);
651 self = HILDON_CONTROLBAR(widget);
653 old_height = widget->allocation.height;
654 old_y = widget->allocation.y;
656 if (widget->allocation.height > DEFAULT_HEIGHT) {
657 int difference = widget->allocation.height - DEFAULT_HEIGHT;
661 difference = difference / 2;
663 widget->allocation.y += difference;
664 widget->allocation.height = DEFAULT_HEIGHT;
667 /* call the parent handler */
668 if (GTK_WIDGET_CLASS(parent_class)->expose_event)
669 result = GTK_WIDGET_CLASS(parent_class)->expose_event(widget, event);
670 hildon_controlbar_paint(self, &event->area);
672 widget->allocation.height = old_height;
673 widget->allocation.y = old_y;
680 * This is where all the work is actually done...
682 static void hildon_controlbar_paint(HildonControlbar * self,
685 HildonControlbarPrivate *priv;
686 GtkWidget *widget = GTK_WIDGET(self);
687 GtkAdjustment *ctrlbar = GTK_RANGE(self)->adjustment;
688 gint x = widget->allocation.x;
689 gint y = widget->allocation.y;
690 gint h = widget->allocation.height;
691 gint w = widget->allocation.width;
693 gint stepper_size = 0;
694 gint stepper_spacing = 0;
695 gint inner_border_width = 0;
696 gint block_area = 0, block_width = 0, block_x = 0, block_max = 0, block_height,block_y;
697 /* Number of blocks on the controlbar */
698 guint block_count = 0;
699 /* Number of displayed active blocks */
701 /* Minimum no. of blocks visible */
703 gint separatingpixels = 2;
704 gint block_remains = 0;
705 gint i, start_x, end_x, current_width;
706 GtkStateType state = GTK_STATE_NORMAL;
708 g_return_if_fail(area);
710 priv = HILDON_CONTROLBAR_GET_PRIVATE(self);
711 if (GTK_WIDGET_SENSITIVE(self) == FALSE)
712 state = GTK_STATE_INSENSITIVE;
714 gtk_widget_style_get(GTK_WIDGET(self),
715 "stepper-size", &stepper_size,
716 "stepper-spacing", &stepper_spacing,
717 "inner_border_width", &inner_border_width, NULL);
718 g_object_get(G_OBJECT(self), "minimum_visible_bars", &block_min, NULL);
720 block_area = (w - 2 * stepper_size - 2 * stepper_spacing - 2 * inner_border_width);
724 block_max = ctrlbar->upper - ctrlbar->lower + block_min;
725 block_act = priv->old_value - GTK_RANGE(self)->adjustment->lower + block_min;
727 /* We check border width and maximum value and adjust
728 * separating pixels for block width here. If the block size would
729 * become too small, we make the separators smaller. Graceful fallback.
731 max = ctrlbar->upper;
732 if( ctrlbar->upper == 0 )
734 separatingpixels = 3;
736 else if ((block_area - ((max - 1) * 3)) / max >= 4) {
737 separatingpixels = 3;
738 } else if ((block_area - ((max - 1) * 2)) / max >= 4) {
739 separatingpixels = 2;
740 } else if ((block_area - ((max - 1) * 1)) / max >= 4) {
741 separatingpixels = 1;
743 separatingpixels = 0;
747 /* If block max is 0 then we dim the whole control. */
748 state = GTK_STATE_INSENSITIVE;
749 block_width = block_area;
756 (block_area - (separatingpixels * (block_max - 1))) / block_max;
758 (block_area - (separatingpixels * (block_max - 1))) % block_max;
761 block_x = x + stepper_size + stepper_spacing + inner_border_width;
762 block_y = y + inner_border_width;
763 block_height = h - 2 * inner_border_width;
765 block_count = ctrlbar->value - ctrlbar->lower + block_min;
767 /* Without this there is vertical block corruption when block_height =
768 1. This should work from 0 up to whatever */
770 if (block_height < 2)
774 * Changed the drawing of the blocks completely,
775 * because of "do-not-resize-when-changing-max"-specs.
776 * Now the code calculates from the block_remains when
777 * it should add one pixel to the block and when not.
780 for (i = 1; i <= block_max; i++) {
782 /* Here we calculate whether we add one pixel to current_width or
784 start_x = block_width * (i - 1) + ((i - 1) * block_remains) / block_max;
785 end_x = block_width * i + (i * block_remains) / block_max;
786 current_width = end_x - start_x;
788 gtk_paint_box(widget->style, widget->window, state,
789 (i <= block_count) ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
790 NULL, widget, "hildon_block",
791 block_x, block_y, current_width,
794 /* We keep the block_x separate because of the
795 'separatingpixels' */
796 block_x += current_width + separatingpixels;