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);
45
#undef HILDON_DISABLE_DEPRECATED
54
#include <gdk/gdkkeysyms.h>
56
#include "hildon-controlbar.h"
57
#include "hildon-controlbar-private.h"
60
dgettext("hildon-libs", string)
62
#define DEFAULT_WIDTH 234
64
#define DEFAULT_HEIGHT 30
66
#define DEFAULT_BORDER_WIDTH 2
68
#define HILDON_CONTROLBAR_STEP_INCREMENT 1
70
#define HILDON_CONTROLBAR_PAGE_INCREMENT 1
72
#define HILDON_CONTROLBAR_PAGE_SIZE 0
74
#define HILDON_CONTROLBAR_UPPER_VALUE 10
76
#define HILDON_CONTROLBAR_LOWER_VALUE 0.0
78
#define HILDON_CONTROLBAR_INITIAL_VALUE 0
80
static GtkScaleClass* parent_class;
96
static guint signals[LAST_SIGNAL] = { 0 };
99
hildon_controlbar_class_init (HildonControlbarClass *controlbar_class);
102
hildon_controlbar_init (HildonControlbar *controlbar);
105
hildon_controlbar_constructor (GType type,
106
guint n_construct_properties,
107
GObjectConstructParam *construct_properties);
110
hildon_controlbar_button_press_event (GtkWidget *widget,
111
GdkEventButton * event);
114
hildon_controlbar_button_release_event (GtkWidget *widget,
115
GdkEventButton *event);
118
hildon_controlbar_expose_event (GtkWidget *widget,
119
GdkEventExpose *event);
122
hildon_controlbar_size_request (GtkWidget *self,
123
GtkRequisition *req);
125
hildon_controlbar_paint (HildonControlbar *self,
126
GdkRectangle * area);
129
hildon_controlbar_keypress (GtkWidget *widget,
130
GdkEventKey * event);
133
hildon_controlbar_set_property (GObject *object,
139
hildon_controlbar_get_property (GObject *object,
145
hildon_controlbar_value_changed (GtkAdjustment *adj,
149
hildon_controlbar_change_value (GtkRange *range,
150
GtkScrollType scroll,
155
* hildon_controlbar_get_type:
157
* Initializes and returns the type of a hildon control bar.
159
* @Returns: GType of #HildonControlbar
162
hildon_controlbar_get_type (void)
164
static GType controlbar_type = 0;
166
if (!controlbar_type) {
167
static const GTypeInfo controlbar_info = {
168
sizeof (HildonControlbarClass),
169
NULL, /* base_init */
170
NULL, /* base_finalize */
171
(GClassInitFunc) hildon_controlbar_class_init,
172
NULL, /* class_finalize */
173
NULL, /* class_data */
174
sizeof (HildonControlbar),
176
(GInstanceInitFunc) hildon_controlbar_init,
178
controlbar_type = g_type_register_static (GTK_TYPE_SCALE,
180
&controlbar_info, 0);
183
return controlbar_type;
187
hildon_controlbar_class_init (HildonControlbarClass *controlbar_class)
189
GObjectClass *gobject_class = G_OBJECT_CLASS (controlbar_class);
190
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (controlbar_class);
192
parent_class = g_type_class_peek_parent(controlbar_class);
194
g_type_class_add_private(controlbar_class, sizeof (HildonControlbarPrivate));
196
gobject_class->get_property = hildon_controlbar_get_property;
197
gobject_class->set_property = hildon_controlbar_set_property;
198
gobject_class->constructor = hildon_controlbar_constructor;
199
widget_class->size_request = hildon_controlbar_size_request;
200
widget_class->button_press_event = hildon_controlbar_button_press_event;
201
widget_class->button_release_event = hildon_controlbar_button_release_event;
202
widget_class->expose_event = hildon_controlbar_expose_event;
203
widget_class->key_press_event = hildon_controlbar_keypress;
204
controlbar_class->end_reached = NULL;
207
* HildonControlbar:min:
209
* Controlbar minimum value.
211
g_object_class_install_property (gobject_class, PROP_MIN,
212
g_param_spec_int ("min",
214
"Smallest possible value",
216
HILDON_CONTROLBAR_LOWER_VALUE,
217
G_PARAM_READABLE | G_PARAM_WRITABLE));
220
* HildonControlbar:max:
222
* Controlbar maximum value.
224
g_object_class_install_property (gobject_class, PROP_MAX,
225
g_param_spec_int ("max",
227
"Greatest possible value",
229
HILDON_CONTROLBAR_UPPER_VALUE,
230
G_PARAM_READABLE | G_PARAM_WRITABLE));
233
* HildonControlbar:value:
235
* Controlbar current value.
237
g_object_class_install_property (gobject_class, PROP_VALUE,
238
g_param_spec_int ("value",
242
HILDON_CONTROLBAR_INITIAL_VALUE,
243
G_PARAM_READABLE | G_PARAM_WRITABLE) );
246
gtk_widget_class_install_style_property (widget_class,
247
g_param_spec_uint ("inner_border_width",
248
"Inner border width",
249
"The border spacing between the controlbar border and controlbar blocks.",
251
DEFAULT_BORDER_WIDTH,
254
signals[END_REACHED] =
255
g_signal_new("end-reached",
256
G_OBJECT_CLASS_TYPE (gobject_class),
258
G_STRUCT_OFFSET (HildonControlbarClass, end_reached),
260
g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1,
265
hildon_controlbar_init (HildonControlbar *controlbar)
268
HildonControlbarPrivate *priv;
270
/* Initialize the private property */
271
priv = HILDON_CONTROLBAR_GET_PRIVATE(controlbar);
274
priv->button_press = FALSE;
276
range = GTK_RANGE (controlbar);
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;