2 * This file is part of hildon-libs
4 * Copyright (C) 2005 Nokia Corporation.
6 * Contact: Luc Pionchon <luc.pionchon@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; either 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 * @file hildon-caption-control.c
28 * This file contains the implementation of Hildon Caption Control
34 #include <gdk/gdkkeysyms.h>
36 #include "hildon-controlbar.h"
39 #define _(string) dgettext(PACKAGE, string)
41 #define HILDON_CONTROLBAR_GET_PRIVATE(obj) \
42 (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
43 HILDON_TYPE_CONTROLBAR, HildonControlbarPrivate));
45 #define DEFAULT_WIDTH 234
46 #define DEFAULT_HEIGHT 30
47 #define DEFAULT_BORDER_WIDTH 2
49 #define HILDON_CONTROLBAR_STEP_INCREMENT 1
50 #define HILDON_CONTROLBAR_PAGE_INCREMENT 1
51 #define HILDON_CONTROLBAR_PAGE_SIZE 0
52 #define HILDON_CONTROLBAR_UPPER_VALUE 10
53 #define HILDON_CONTROLBAR_LOWER_VALUE 0.0
54 #define HILDON_CONTROLBAR_INITIAL_VALUE 0
56 static GtkScaleClass *parent_class;
58 typedef struct _HildonControlbarPrivate HildonControlbarPrivate;
68 hildon_controlbar_class_init(HildonControlbarClass * controlbar_class);
69 static void hildon_controlbar_init(HildonControlbar * controlbar);
71 hildon_controlbar_constructor(GType type, guint n_construct_properties,
72 GObjectConstructParam *construct_properties);
74 static gint hildon_controlbar_button_press_event(GtkWidget * widget,
75 GdkEventButton * event);
76 static gint hildon_controlbar_button_release_event(GtkWidget * widget,
77 GdkEventButton * event);
79 hildon_controlbar_expose_event(GtkWidget * widget, GdkEventExpose * event);
81 hildon_controlbar_size_request(GtkWidget * self, GtkRequisition * req);
83 hildon_controlbar_paint(HildonControlbar * self, GdkRectangle * area);
85 hildon_controlbar_keypress(GtkWidget * widget, GdkEventKey * event);
87 static void hildon_controlbar_set_property( GObject *object, guint param_id,
88 const GValue *value, GParamSpec *pspec );
89 static void hildon_controlbar_get_property( GObject *object, guint param_id,
90 GValue *value, GParamSpec *pspec );
93 hildon_controlbar_value_changed( GtkAdjustment *adj, GtkRange *range );
95 GType hildon_controlbar_get_type(void)
97 static GType controlbar_type = 0;
99 if (!controlbar_type) {
100 static const GTypeInfo controlbar_info = {
101 sizeof(HildonControlbarClass),
102 NULL, /* base_init */
103 NULL, /* base_finalize */
104 (GClassInitFunc) hildon_controlbar_class_init,
105 NULL, /* class_finalize */
106 NULL, /* class_data */
107 sizeof(HildonControlbar),
109 (GInstanceInitFunc) hildon_controlbar_init,
111 controlbar_type = g_type_register_static(GTK_TYPE_SCALE,
113 &controlbar_info, 0);
115 return controlbar_type;
118 struct _HildonControlbarPrivate {
119 gboolean button_press;
124 hildon_controlbar_class_init(HildonControlbarClass * controlbar_class)
126 GObjectClass *gobject_class = G_OBJECT_CLASS(controlbar_class);
127 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(controlbar_class);
129 parent_class = g_type_class_peek_parent(controlbar_class);
131 g_type_class_add_private(controlbar_class,
132 sizeof(HildonControlbarPrivate));
134 gobject_class->get_property = hildon_controlbar_get_property;
135 gobject_class->set_property = hildon_controlbar_set_property;
136 widget_class->size_request = hildon_controlbar_size_request;
137 widget_class->button_press_event = hildon_controlbar_button_press_event;
138 widget_class->button_release_event = hildon_controlbar_button_release_event;
139 widget_class->expose_event = hildon_controlbar_expose_event;
140 widget_class->key_press_event = hildon_controlbar_keypress;
141 G_OBJECT_CLASS(controlbar_class)->constructor = hildon_controlbar_constructor;
144 * HildonControlbar:min:
146 * Controlbar minimum value.
148 g_object_class_install_property( gobject_class, PROP_MIN,
149 g_param_spec_int("min",
151 "Smallest possible value",
153 HILDON_CONTROLBAR_LOWER_VALUE,
154 G_PARAM_READABLE | G_PARAM_WRITABLE) );
157 * HildonControlbar:max:
159 * Controlbar maximum value.
161 g_object_class_install_property( gobject_class, PROP_MAX,
162 g_param_spec_int("max",
164 "Greatest possible value",
166 HILDON_CONTROLBAR_UPPER_VALUE,
167 G_PARAM_READABLE | G_PARAM_WRITABLE) );
170 * HildonControlbar:value:
174 g_object_class_install_property( gobject_class, PROP_VALUE,
175 g_param_spec_int("value",
179 HILDON_CONTROLBAR_INITIAL_VALUE,
180 G_PARAM_READABLE | G_PARAM_WRITABLE) );
183 gtk_widget_class_install_style_property(widget_class,
184 g_param_spec_uint("inner_border_width",
185 "Inner border width",
186 "The border spacing between the controlbar border and controlbar blocks.",
188 DEFAULT_BORDER_WIDTH,
192 static void hildon_controlbar_init(HildonControlbar * controlbar)
195 HildonControlbarPrivate *priv;
197 priv = HILDON_CONTROLBAR_GET_PRIVATE(controlbar);
198 priv->button_press = FALSE;
200 range = GTK_RANGE(controlbar);
202 range->has_stepper_a = TRUE;
203 range->has_stepper_d = TRUE;
204 range->round_digits = -1;
206 gtk_widget_set_size_request( GTK_WIDGET(controlbar), DEFAULT_WIDTH,
210 static GObject *hildon_controlbar_constructor(GType type,
211 guint n_construct_properties, GObjectConstructParam *construct_properties)
216 obj = G_OBJECT_CLASS(parent_class)->constructor(type,
217 n_construct_properties, construct_properties);
219 gtk_scale_set_draw_value (GTK_SCALE (obj), FALSE);
221 adj = GTK_RANGE(obj)->adjustment;
222 adj->step_increment = HILDON_CONTROLBAR_STEP_INCREMENT;
223 adj->page_increment = HILDON_CONTROLBAR_PAGE_INCREMENT;
224 adj->page_size = HILDON_CONTROLBAR_PAGE_SIZE;
226 g_signal_connect( adj, "value-changed",
227 G_CALLBACK(hildon_controlbar_value_changed), obj );
231 static void hildon_controlbar_set_property (GObject *object, guint param_id,
232 const GValue *value, GParamSpec *pspec)
234 HildonControlbar *controlbar = HILDON_CONTROLBAR(object);
238 hildon_controlbar_set_min (controlbar, g_value_get_int(value));
242 hildon_controlbar_set_max (controlbar, g_value_get_int(value));
246 hildon_controlbar_set_value (controlbar, g_value_get_int(value));
250 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
255 static void hildon_controlbar_get_property( GObject *object, guint param_id,
256 GValue *value, GParamSpec *pspec )
258 HildonControlbar *controlbar = HILDON_CONTROLBAR(object);
262 g_value_set_int (value, hildon_controlbar_get_min (controlbar));
266 g_value_set_int (value, hildon_controlbar_get_max (controlbar));
270 g_value_set_int (value, hildon_controlbar_get_value (controlbar));
274 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
281 hildon_controlbar_value_changed( GtkAdjustment *adj, GtkRange *range )
283 HildonControlbarPrivate *priv = HILDON_CONTROLBAR_GET_PRIVATE(range);
285 if( ABS(ceil(range->adjustment->value) - priv->old_value) >= 1 )
287 priv->old_value = ceil(range->adjustment->value);
288 range->adjustment->value = priv->old_value;
291 g_signal_stop_emission_by_name( range->adjustment, "value-changed" );
292 gtk_adjustment_set_value( range->adjustment, priv->old_value );
296 * hildon_controlbar_new:
298 * Creates a new #HildonControlbar widget.
300 * Return value: A #GtkWidget pointer of newly created #HildonControlbar
303 GtkWidget *hildon_controlbar_new(void)
305 return GTK_WIDGET(g_object_new(HILDON_TYPE_CONTROLBAR, NULL));
309 hildon_controlbar_keypress(GtkWidget * widget, GdkEventKey * event)
311 if (event->keyval == GDK_Up || event->keyval == GDK_Down)
313 return ((GTK_WIDGET_CLASS(parent_class)->key_press_event) (widget, event));
317 hildon_controlbar_size_request(GtkWidget * self, GtkRequisition * req)
319 if (GTK_WIDGET_CLASS(parent_class)->size_request)
320 GTK_WIDGET_CLASS(parent_class)->size_request(self, req);
322 req->width = DEFAULT_WIDTH;
323 req->height = DEFAULT_HEIGHT;
327 * hildon_controlbar_set_value:
328 * @self: Pointer to #HildonControlbar
329 * @value: Value in range of >= 0 && < G_MAX_INT
331 * This method will change #HildonControlbar current value
332 * to the given value.
334 void hildon_controlbar_set_value(HildonControlbar * self, gint value)
337 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
338 adj = GTK_RANGE(self)->adjustment;
340 g_return_if_fail(value >= 0);
342 if (value >= adj->upper)
344 else if (value <= adj->lower)
348 gtk_adjustment_value_changed(adj);
350 g_object_notify (G_OBJECT(self), "value");
354 * hildon_controlbar_get_value:
355 * @self: Pointer to #HildonControlbar
357 * Accessor method for getting controlbars current value
359 * Return value: Current value as gint.
361 gint hildon_controlbar_get_value(HildonControlbar * self)
364 g_return_val_if_fail (HILDON_IS_CONTROLBAR (self), 0);
365 adj = GTK_RANGE(self)->adjustment;
367 return (gint) ceil(adj->value);
371 * hildon_controlbar_set_max:
372 * @self: Pointer to #HildonControlbar
373 * @max: Maximum value to set. The value needs to be
374 * in range of > 0 && <= G_MAX_INT.
375 * If the new maximum is smaller than current value,
376 * the value will be adjusted so that
377 * it equals the new maximum.
379 * Accessor method for setting #HildonControlbar maximum to the
382 void hildon_controlbar_set_max(HildonControlbar * self, gint max)
385 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
386 adj = GTK_RANGE(self)->adjustment;
388 if (max < adj->lower)
391 if (adj->value > max)
392 hildon_controlbar_set_value (self, max);
395 gtk_adjustment_changed(adj);
397 g_object_notify (G_OBJECT(self), "max");
401 * hildon_controlbar_set_min:
402 * @self: Pointer to #HildonControlbar
403 * @min: Minimum value to set. The value needs to be
404 * in range of => 0 && <= G_MAX_INT.
405 * If the new minimum is smaller than current value,
406 * the value will be adjusted so that
407 * it equals the new minimum.
409 * Accessor method for setting #HildonControlbar minimum to the
412 void hildon_controlbar_set_min(HildonControlbar * self, gint min)
415 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
416 adj = GTK_RANGE(self)->adjustment;
418 if (min > adj->upper)
421 if (adj->value < min)
422 hildon_controlbar_set_value (self, min);
425 gtk_adjustment_changed(adj);
426 g_object_notify (G_OBJECT(self), "min");
430 * hildon_controlbar_set_range:
431 * @self: Pointer to #HildonControlbar
432 * @max: Maximum value to set. The value needs to be
433 * in range of > 0 && <= G_MAX_INT.
434 * @min: Minimum value to set. The value needs to be
435 * in range of => 0 && <= G_MAX_INT.
436 * If the new maximum is smaller than current value,
437 * the value will be adjusted so that
438 * it equals the new maximum.
439 * If the new minimum is smaller than current value,
440 * the value will be adjusted so that
441 * it equals the new minimum.
443 * Accessor method for setting #HildonControlbar maximum to the
446 void hildon_controlbar_set_range(HildonControlbar * self, gint min,
449 g_return_if_fail (HILDON_IS_CONTROLBAR (self));
453 /* We need to set max first here, because when min is set before
454 * max is set, it would end up 0, because max can't be bigger than 0.
456 hildon_controlbar_set_max (self, max);
457 hildon_controlbar_set_min (self, min);
461 * hildon_controlbar_get_max:
462 * @self: A pointer to #HildonControlbar
464 * Accessor method for getting controlbars current maximum value
466 * Return value: Maximum value as gint
468 gint hildon_controlbar_get_max(HildonControlbar * self)
471 g_return_val_if_fail (HILDON_IS_CONTROLBAR (self), 0);
472 adj = GTK_RANGE(self)->adjustment;
474 return (gint) adj->upper;
478 * hildon_controlbar_get_min:
479 * @self: A pointer to #HildonControlbar
481 * Accessor method for getting controlbars current minimum value
483 * Return value: Minimum value as gint
485 gint hildon_controlbar_get_min(HildonControlbar * self)
487 GtkAdjustment *adj = GTK_RANGE(self)->adjustment;
488 return (gint) adj->lower;
492 * Event handler for button press
493 * Need to change button1 to button2 before passing this event to
494 * parent handler. (see specs)
495 * Also updates button_press variable so that we can draw hilites
498 static gint hildon_controlbar_button_press_event(GtkWidget * widget,
499 GdkEventButton * event)
501 HildonControlbar *self;
502 HildonControlbarPrivate *priv;
503 gboolean result = FALSE;
505 g_return_val_if_fail(widget, FALSE);
506 g_return_val_if_fail(event, FALSE);
508 self = HILDON_CONTROLBAR(widget);
509 priv = HILDON_CONTROLBAR_GET_PRIVATE(self);
511 priv->button_press = TRUE;
512 event->button = event->button == 1 ? 2 : event->button;
514 /* Ugh dirty hack. We manipulate the mouse event location to
515 compensate for centering the widget in case it is taller than the
517 if (widget->allocation.height > DEFAULT_HEIGHT) {
518 gint difference = widget->allocation.height - DEFAULT_HEIGHT;
522 difference = difference / 2;
524 event->y -= difference;
528 /* call the parent handler */
529 if (GTK_WIDGET_CLASS(parent_class)->button_press_event)
531 GTK_WIDGET_CLASS(parent_class)->button_press_event(widget, event);
537 * Event handler for button release
538 * Need to change button1 to button2 before passing this event to
539 * parent handler. (see specs)
540 * Also updates button_press variable so that we can draw hilites
543 static gint hildon_controlbar_button_release_event(GtkWidget * widget,
544 GdkEventButton * event)
546 HildonControlbar *self;
547 HildonControlbarPrivate *priv;
548 gboolean result = FALSE;
550 g_return_val_if_fail(widget, FALSE);
551 g_return_val_if_fail(event, FALSE);
553 self = HILDON_CONTROLBAR(widget);
554 priv = HILDON_CONTROLBAR_GET_PRIVATE(self);
556 priv->button_press = FALSE;
557 event->button = event->button == 1 ? 2 : event->button;
559 /* call the parent handler */
560 if (GTK_WIDGET_CLASS(parent_class)->button_release_event)
562 GTK_WIDGET_CLASS(parent_class)->button_release_event(widget, event);
567 * Event handler for expose event
569 static gint hildon_controlbar_expose_event(GtkWidget * widget,
570 GdkEventExpose * event)
572 HildonControlbar *self = NULL;
574 gboolean result = FALSE;
575 gint old_height = -1;
578 g_return_val_if_fail(widget, FALSE);
579 g_return_val_if_fail(event, FALSE);
580 g_return_val_if_fail(HILDON_IS_CONTROLBAR(widget), FALSE);
582 self = HILDON_CONTROLBAR(widget);
584 old_height = widget->allocation.height;
585 old_y = widget->allocation.y;
587 if (widget->allocation.height > DEFAULT_HEIGHT) {
588 int difference = widget->allocation.height - DEFAULT_HEIGHT;
592 difference = difference / 2;
594 widget->allocation.y += difference;
595 widget->allocation.height = DEFAULT_HEIGHT;
598 /* call the parent handler */
599 if (GTK_WIDGET_CLASS(parent_class)->expose_event)
600 result = GTK_WIDGET_CLASS(parent_class)->expose_event(widget, event);
601 hildon_controlbar_paint(self, &event->area);
603 widget->allocation.height = old_height;
604 widget->allocation.y = old_y;
611 * This is where all the work is actually done...
613 static void hildon_controlbar_paint(HildonControlbar * self,
616 HildonControlbarPrivate *priv;
617 GtkWidget *widget = GTK_WIDGET(self);
618 GtkAdjustment *ctrlbar = GTK_RANGE(self)->adjustment;
619 gint x = widget->allocation.x;
620 gint y = widget->allocation.y;
621 gint h = widget->allocation.height;
622 gint w = widget->allocation.width;
624 gint stepper_size = 0;
625 gint stepper_spacing = 0;
626 gint inner_border_width = 0;
627 gint block_area = 0, block_width = 0, block_x = 0, block_max = 0, block_height,block_y;
628 /* Number of blocks on the controlbar */
629 guint block_count = 0;
630 /* Number of displayed active blocks */
632 /* Minimum no. of blocks visible */
634 gint separatingpixels = 2;
635 gint block_remains = 0;
636 gint i, start_x, end_x, current_width;
637 GtkStateType state = GTK_STATE_NORMAL;
639 g_return_if_fail(area);
641 priv = HILDON_CONTROLBAR_GET_PRIVATE(self);
642 if (GTK_WIDGET_SENSITIVE(self) == FALSE)
643 state = GTK_STATE_INSENSITIVE;
645 gtk_widget_style_get(GTK_WIDGET(self),
646 "stepper-size", &stepper_size,
647 "stepper-spacing", &stepper_spacing,
648 "inner_border_width", &inner_border_width, NULL);
649 g_object_get(G_OBJECT(self), "minimum_visible_bars", &block_min, NULL);
651 block_area = (w - 2 * stepper_size - 2 * stepper_spacing - 2 * inner_border_width);
655 block_max = ctrlbar->upper - ctrlbar->lower + block_min;
656 block_act = priv->old_value - GTK_RANGE(self)->adjustment->lower + block_min;
658 /* We check border width and maximum value and adjust
659 * separating pixels for block width here. If the block size would
660 * become too small, we make the separators smaller. Graceful fallback.
662 max = ctrlbar->upper;
663 if( ctrlbar->upper == 0 )
665 separatingpixels = 3;
667 else if ((block_area - ((max - 1) * 3)) / max >= 4) {
668 separatingpixels = 3;
669 } else if ((block_area - ((max - 1) * 2)) / max >= 4) {
670 separatingpixels = 2;
671 } else if ((block_area - ((max - 1) * 1)) / max >= 4) {
672 separatingpixels = 1;
674 separatingpixels = 0;
678 /* If block max is 0 then we dim the whole control. */
679 state = GTK_STATE_INSENSITIVE;
680 block_width = block_area;
687 (block_area - (separatingpixels * (block_max - 1))) / block_max;
689 (block_area - (separatingpixels * (block_max - 1))) % block_max;
692 block_x = x + stepper_size + stepper_spacing + inner_border_width;
693 block_y = y + inner_border_width;
694 block_height = h - 2 * inner_border_width;
696 block_count = ctrlbar->value - ctrlbar->lower + block_min;
698 /* Without this there is vertical block corruption when block_height =
699 1. This should work from 0 up to whatever */
701 if (block_height < 2)
705 * Changed the drawing of the blocks completely,
706 * because of "do-not-resize-when-changing-max"-specs.
707 * Now the code calculates from the block_remains when
708 * it should add one pixel to the block and when not.
711 for (i = 1; i <= block_max; i++) {
713 /* Here we calculate whether we add one pixel to current_width or
715 start_x = block_width * (i - 1) + ((i - 1) * block_remains) / block_max;
716 end_x = block_width * i + (i * block_remains) / block_max;
717 current_width = end_x - start_x;
719 gtk_paint_box(widget->style, widget->window, state,
720 (i <= block_count) ? GTK_SHADOW_IN : GTK_SHADOW_OUT,
721 NULL, widget, "hildon_block",
722 block_x, block_y, current_width,
725 /* We keep the block_x separate because of the
726 'separatingpixels' */
727 block_x += current_width + separatingpixels;