2008-03-03 Sven Herzberg <sven@imendio.com>
[hildon] / src / hildon-range-editor.c
1 /*
2  * This file is a part of hildon
3  *
4  * Copyright (C) 2005, 2006 Nokia Corporation, all rights reserved.
5  *
6  * Contact: Michael Dominic Kostrzewa <michael.kostrzewa@nokia.com>
7  *
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.
12  *
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.
17  *
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
21  * 02110-1301 USA
22  *
23  */
24
25 /**
26  * SECTION:hildon-range-editor
27  * @short_description: A widget is used to ask bounds of a range.
28  *
29  * HidlonRangeEditor allows entering a pair of integers, e.g. the lower
30  * and higher bounds of a range. A minimum and maximum can also be set
31  * for the bounds.
32  * 
33  * <example>
34  * <programlisting>
35  *      range_editor = hildon_range_editor_new ();
36  *      hildon_range_editor_set_limits (editor, start, end );
37  *      hildon_range_editor_get_range (editor, &amp;start, &amp;end);
38  * </programlisting>
39  * </example>
40  */
41
42 #ifdef                                          HAVE_CONFIG_H
43 #include                                        <config.h>
44 #endif
45
46 #include                                        "hildon-range-editor.h"
47 #include                                        <gtk/gtkbox.h>
48 #include                                        <gtk/gtklabel.h>
49 #include                                        <gtk/gtksignal.h>
50 #include                                        <gtk/gtkentry.h>
51 #include                                        <gdk/gdkkeysyms.h>
52 #include                                        <glib/gprintf.h>
53 #include                                        <string.h>
54 #include                                        <stdlib.h>
55 #include                                        "hildon-banner.h"
56 #include                                        <libintl.h>
57 #include                                        "hildon-range-editor-private.h"
58
59 #define                                         _(string) \
60                                                 dgettext("hildon-libs", string)
61
62 /* Alignment in entry box ( 0 = left, 1 = right ) */
63
64 #define                                         DEFAULT_ALIGNMENT 1
65
66 /* Amount of padding to add to each side of the separator */
67
68 #define                                         DEFAULT_PADDING 3
69
70 #define                                         DEFAULT_START -999
71
72 #define                                         DEFAULT_END    999
73
74 #define                                         DEFAULT_LENGTH 4
75
76 static GtkContainerClass                        *parent_class = NULL;
77
78 static void
79 hildon_range_editor_class_init                  (HildonRangeEditorClass *editor_class);
80
81 static void
82 hildon_range_editor_init                        (HildonRangeEditor *editor);
83
84 static void
85 hildon_range_editor_forall                      (GtkContainer *container,
86                                                  gboolean include_internals, 
87                                                  GtkCallback callback,
88                                                  gpointer callback_data);
89
90 static void
91 hildon_range_editor_destroy                     (GtkObject *self);
92
93 static void
94 hildon_range_editor_size_request                (GtkWidget *widget,
95                                                  GtkRequisition *requisition);
96
97 static void
98 hildon_range_editor_size_allocate               (GtkWidget *widget,
99                                                  GtkAllocation *allocation);
100
101 static gboolean
102 hildon_range_editor_entry_focus_in              (GtkEditable *editable,
103                                                  GdkEventFocus *event,
104                                                  HildonRangeEditor *editor);
105
106 static gboolean
107 hildon_range_editor_entry_focus_out             (GtkEditable *editable,
108                                                  GdkEventFocus *event,
109                                                  HildonRangeEditor *editor);
110
111 static gboolean
112 hildon_range_editor_entry_keypress              (GtkWidget *widget, 
113                                                  GdkEventKey *event,
114                                                  HildonRangeEditor *editor);
115
116 static gboolean
117 hildon_range_editor_released                    (GtkEditable *editable, 
118                                                  GdkEventButton *event,
119                                                  HildonRangeEditor *editor);
120
121 static gboolean
122 hildon_range_editor_press                       (GtkEditable *editable, 
123                                                  GdkEventButton *event,
124                                                  HildonRangeEditor *editor);
125
126 static void 
127 hildon_range_editor_set_property                (GObject *object, 
128                                                  guint param_id,
129                                                  const GValue *value, 
130                                                  GParamSpec *pspec);
131
132 static void
133 hildon_range_editor_get_property                (GObject *object, 
134                                                  guint param_id,
135                                                  GValue *value, 
136                                                  GParamSpec *pspec);
137
138 static void
139 hildon_range_editor_entry_changed               (GtkWidget *widget, 
140                                                  HildonRangeEditor *editor);
141
142 enum
143 {
144     PROP_0,
145     PROP_LOWER = 1,
146     PROP_HIGHER,
147     PROP_MIN,
148     PROP_MAX,
149     PROP_SEPARATOR
150 };
151
152 static void
153 hildon_range_editor_class_init                  (HildonRangeEditorClass *editor_class)
154 {
155     GObjectClass *gobject_class = G_OBJECT_CLASS (editor_class);
156     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (editor_class);
157     GtkContainerClass *container_class = GTK_CONTAINER_CLASS (editor_class);
158
159     parent_class = g_type_class_peek_parent (editor_class);
160
161     g_type_class_add_private (editor_class, sizeof (HildonRangeEditorPrivate));
162
163     gobject_class->set_property = hildon_range_editor_set_property;
164     gobject_class->get_property = hildon_range_editor_get_property;
165     widget_class->size_request  = hildon_range_editor_size_request;
166     widget_class->size_allocate = hildon_range_editor_size_allocate;
167
168     container_class->forall = hildon_range_editor_forall;
169     GTK_OBJECT_CLASS (editor_class)->destroy = hildon_range_editor_destroy;
170
171     gtk_widget_class_install_style_property (widget_class,
172             g_param_spec_int ("hildon_range_editor_entry_alignment",
173                 "Hildon RangeEditor entry alignment",
174                 "Hildon RangeEditor entry alignment", 0, 1,
175                 DEFAULT_ALIGNMENT,
176                 G_PARAM_READABLE));
177
178     gtk_widget_class_install_style_property (widget_class,
179             g_param_spec_int ("hildon_range_editor_separator_padding",
180                 "Hildon RangeEditor separator padding",
181                 "Hildon RangeEditor separaror padding",
182                 G_MININT, G_MAXINT,
183                 DEFAULT_PADDING,
184                 G_PARAM_READABLE));
185
186     /**
187      * HildonRangeEditor:min:
188      *
189      * Minimum value in a range.
190      * Default: -999
191      */
192     g_object_class_install_property (gobject_class, PROP_MIN,
193             g_param_spec_int ("min",
194                 "Minimum value",
195                 "Minimum value in a range",
196                 G_MININT, G_MAXINT,
197                 DEFAULT_START, G_PARAM_CONSTRUCT | 
198                 G_PARAM_READABLE | G_PARAM_WRITABLE));
199
200     /**
201      * HildonRangeEditor:max:
202      *
203      * Maximum value in a range.
204      * Default: 999
205      */
206     g_object_class_install_property (gobject_class, PROP_MAX,
207             g_param_spec_int ("max",
208                 "Maximum value",
209                 "Maximum value in a range",
210                 G_MININT, G_MAXINT,
211                 DEFAULT_END, G_PARAM_CONSTRUCT | 
212                 G_PARAM_READABLE | G_PARAM_WRITABLE));
213
214     /**
215      * HildonRangeEditor:lower:
216      *
217      * Current value in the entry presenting lower end of selected range.
218      * Default: -999
219      */
220     g_object_class_install_property (gobject_class, PROP_LOWER,
221             g_param_spec_int ("lower",
222                 "Current lower value",
223                 "Current value in the entry presenting lower end of selected range",
224                 G_MININT, G_MAXINT,
225                 DEFAULT_START, G_PARAM_CONSTRUCT | 
226                 G_PARAM_READABLE | G_PARAM_WRITABLE));
227
228     /**
229      * HildonRangeEditor:higher:
230      *
231      * Current value in the entry presenting higher end of selected range.
232      * Default: 999
233      */
234     g_object_class_install_property (gobject_class, PROP_HIGHER,
235             g_param_spec_int ("higher",
236                 "Current higher value",
237                 "Current value in the entry presenting higher end of selected range",
238                 G_MININT, G_MAXINT,
239                 DEFAULT_END, G_PARAM_CONSTRUCT | 
240                 G_PARAM_READABLE | G_PARAM_WRITABLE));
241
242     /**
243      * HildonRangeEditor:separator:
244      *
245      * Separator string to separate range editor entries.
246      * Default: "-"
247      */
248     g_object_class_install_property (gobject_class, PROP_SEPARATOR,
249             g_param_spec_string ("separator",
250                 "Separator",
251                 "Separator string to separate entries",
252                 _("ckct_wi_range_separator"),
253                 G_PARAM_CONSTRUCT | 
254                 G_PARAM_READABLE | G_PARAM_WRITABLE));
255 }
256
257 static void
258 hildon_range_editor_init                        (HildonRangeEditor *editor)
259 {
260     HildonRangeEditorPrivate *priv;
261
262     gint range_editor_entry_alignment;
263     gint range_editor_separator_padding;
264
265     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
266     g_assert (priv);
267
268     GTK_WIDGET_SET_FLAGS (editor, GTK_NO_WINDOW);
269
270     gtk_widget_push_composite_child ();
271
272     priv->start_entry = gtk_entry_new ();
273     priv->end_entry = gtk_entry_new ();
274     priv->label = gtk_label_new (_("ckct_wi_range_separator"));
275     priv->bp = FALSE;
276
277     /* Get values from gtkrc (or use defaults) */
278     /* FIXME: This is broken, styles are not yet attached */
279     gtk_widget_style_get (GTK_WIDGET (editor),
280             "hildon_range_editor_entry_alignment",
281             &range_editor_entry_alignment,
282             "hildon_range_editor_separator_padding",
283             &range_editor_separator_padding, NULL);
284
285     /* Add padding to separator */
286     gtk_misc_set_padding (GTK_MISC (priv->label),
287             range_editor_separator_padding, 0);
288
289     /* Align the text to right in entry box */
290     gtk_entry_set_alignment (GTK_ENTRY (priv->start_entry),
291             range_editor_entry_alignment);
292     gtk_entry_set_alignment (GTK_ENTRY (priv->end_entry),
293             range_editor_entry_alignment);
294
295     gtk_widget_set_composite_name (priv->start_entry, "start_entry");
296     gtk_widget_set_composite_name (priv->end_entry, "end_entry");
297     gtk_widget_set_composite_name (priv->label, "separator_label");
298     gtk_widget_set_parent (priv->start_entry, GTK_WIDGET (editor));
299     gtk_widget_set_parent (priv->end_entry, GTK_WIDGET (editor));
300     gtk_widget_set_parent (priv->label, GTK_WIDGET (editor));
301
302     g_signal_connect (G_OBJECT (priv->start_entry), "button-release-event",
303             G_CALLBACK (hildon_range_editor_released), editor);
304     g_signal_connect (G_OBJECT (priv->end_entry), "button-release-event",
305             G_CALLBACK (hildon_range_editor_released), editor);
306
307     g_signal_connect (G_OBJECT (priv->start_entry), "button-press-event",
308             G_CALLBACK (hildon_range_editor_press), editor);
309     g_signal_connect (G_OBJECT (priv->end_entry), "button-press-event",
310             G_CALLBACK (hildon_range_editor_press), editor);
311
312     g_signal_connect (G_OBJECT (priv->start_entry), "key-press-event",
313             G_CALLBACK (hildon_range_editor_entry_keypress), editor);
314     g_signal_connect (G_OBJECT (priv->end_entry), "key-press-event",
315             G_CALLBACK (hildon_range_editor_entry_keypress), editor);
316
317     g_signal_connect (G_OBJECT (priv->start_entry), "focus-in-event",
318             G_CALLBACK (hildon_range_editor_entry_focus_in), editor);
319     g_signal_connect (G_OBJECT (priv->end_entry), "focus-in-event",
320             G_CALLBACK (hildon_range_editor_entry_focus_in), editor);
321
322     g_signal_connect (G_OBJECT (priv->start_entry), "focus-out-event",
323             G_CALLBACK (hildon_range_editor_entry_focus_out), editor);
324     g_signal_connect (G_OBJECT (priv->end_entry), "focus-out-event",
325             G_CALLBACK (hildon_range_editor_entry_focus_out), editor);
326     g_signal_connect (priv->start_entry, "changed", 
327             G_CALLBACK (hildon_range_editor_entry_changed), editor);
328     g_signal_connect (priv->end_entry, "changed", 
329             G_CALLBACK (hildon_range_editor_entry_changed), editor);
330
331 #ifdef MAEMO_GTK 
332     g_object_set (G_OBJECT (priv->start_entry),
333             "hildon-input-mode", HILDON_GTK_INPUT_MODE_NUMERIC, NULL);
334
335     g_object_set( G_OBJECT (priv->end_entry),
336             "hildon-input-mode", HILDON_GTK_INPUT_MODE_NUMERIC, NULL);
337 #endif 
338
339     gtk_widget_show (priv->start_entry);
340     gtk_widget_show (priv->end_entry);
341     gtk_widget_show (priv->label);
342
343     gtk_widget_pop_composite_child();
344 }
345
346 static void 
347 hildon_range_editor_set_property                (GObject *object, 
348                                                  guint param_id,
349                                                  const GValue *value, 
350                                                  GParamSpec *pspec)
351 {
352     HildonRangeEditor *editor = HILDON_RANGE_EDITOR(object);
353
354     switch (param_id)
355     {
356         case PROP_LOWER:
357             hildon_range_editor_set_lower (editor, g_value_get_int (value));
358             break;
359
360         case PROP_HIGHER:
361             hildon_range_editor_set_higher (editor, g_value_get_int (value));
362             break;
363
364         case PROP_MIN:
365             hildon_range_editor_set_min (editor, g_value_get_int (value));
366             break;
367
368         case PROP_MAX:
369             hildon_range_editor_set_max (editor, g_value_get_int (value));
370             break;
371
372         case PROP_SEPARATOR:
373             hildon_range_editor_set_separator (editor,
374                     g_value_get_string (value));
375             break;
376
377         default:
378             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
379             break;
380     }
381 }
382
383 static void
384 hildon_range_editor_get_property                (GObject *object, 
385                                                  guint param_id,
386                                                  GValue *value, 
387                                                  GParamSpec *pspec)
388 {
389     HildonRangeEditor *editor = HILDON_RANGE_EDITOR(object);
390
391     switch (param_id)
392     {
393         case PROP_LOWER:
394             g_value_set_int (value, hildon_range_editor_get_lower (editor));
395             break;
396
397         case PROP_HIGHER:
398             g_value_set_int (value, hildon_range_editor_get_higher (editor));
399             break;
400
401         case PROP_MIN:
402             g_value_set_int (value, hildon_range_editor_get_min (editor));
403             break;
404
405         case PROP_MAX:
406             g_value_set_int (value, hildon_range_editor_get_max (editor));
407             break;
408
409         case PROP_SEPARATOR:
410             g_value_set_string (value, hildon_range_editor_get_separator (editor));
411             break;
412
413         default:
414             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
415             break;
416     }
417 }
418
419 static void
420 hildon_range_editor_entry_validate              (HildonRangeEditor *editor, 
421                                                  GtkWidget *edited_entry, 
422                                                  gboolean allow_intermediate)
423 {
424     HildonRangeEditorPrivate *priv;
425     const gchar *text;
426     long value;
427     gint min, max, fixup;
428     gchar *tail;
429     gchar buffer[256];
430     gboolean error = FALSE;
431
432     g_assert(HILDON_IS_RANGE_EDITOR(editor));
433     g_assert(GTK_IS_ENTRY(edited_entry));
434
435     priv = HILDON_RANGE_EDITOR_GET_PRIVATE(editor);
436     g_assert (priv);
437
438     /* Find the valid range for the modified component */
439     if (edited_entry == priv->start_entry) {
440         min = hildon_range_editor_get_min (editor);
441         max = hildon_range_editor_get_higher (editor);
442     } else {
443         min = hildon_range_editor_get_lower (editor);
444         max = hildon_range_editor_get_max (editor);
445     }
446
447     text = gtk_entry_get_text (GTK_ENTRY (edited_entry));
448
449     if (text && text [0])
450     { 
451         /* Try to convert entry text to number */
452         value = strtol(text, &tail, 10);
453
454         /* Check if conversion succeeded */
455         if (tail [0] == 0)
456         {    
457             /* Check if value is in allowed range. This is tricky in those
458                cases when user is editing a value. 
459                For example: Range = [100, 500] and user have just inputted "4".
460                This should not lead into error message. Otherwise value is
461                resetted back to "100" and next "4" press will reset it back
462                and so on. */
463             if (allow_intermediate)
464             {
465                 /* We now have the following error cases:
466                  * If inputted value as above maximum and
467                  maximum is either positive or then maximum
468                  negative and value is positive.
469                  * If inputted value is below minimum and minimum
470                  is negative or minumum positive and value
471                  negative.
472                  In all other cases situation can be fixed just by
473                  adding new numbers to the string.
474                  */
475                 if (value > max && (max >= 0 || (max < 0 && value >= 0)))
476                 {
477                     error = TRUE;
478                     fixup = max;
479                     g_snprintf (buffer, sizeof (buffer), _("ckct_ib_maximum_value"), max);
480                 }
481                 else if (value < min && (min < 0 || (min >= 0 && value < 0)))
482                 {
483                     error = TRUE;
484                     fixup = min;
485                     g_snprintf (buffer, sizeof (buffer), _("ckct_ib_minimum_value"), min);
486                 }
487             }
488             else
489             {
490                 if (value > max) {
491                     error = TRUE;
492                     fixup = max;
493                     g_snprintf (buffer, sizeof (buffer), _("ckct_ib_maximum_value"), max);
494                 }
495                 else if (value < min) {
496                     error = TRUE;
497                     fixup = min;
498                     g_snprintf (buffer, sizeof (buffer), _("ckct_ib_minimum_value"), min);
499                 }
500             }
501
502             if (error) {
503                 if (edited_entry == priv->start_entry)
504                     hildon_range_editor_set_lower (editor, fixup);
505                 else
506                     hildon_range_editor_set_higher (editor, fixup);
507             }
508         }
509         /* The only valid case when conversion can fail is when we
510            have plain '-', intermediate forms are allowed AND
511            minimum bound is negative */
512         else if (!allow_intermediate || strcmp(text, "-") != 0 || min >= 0) {
513             error = TRUE;
514             g_snprintf (buffer, sizeof (buffer), _("ckct_ib_set_a_value_within_range"), min, max);
515         }
516     }
517     else if (! allow_intermediate) {
518         error = TRUE;
519         g_snprintf (buffer, sizeof (buffer), _("ckct_ib_set_a_value_within_range"), min, max);
520     }
521
522     if (error)
523     {
524         hildon_banner_show_information (edited_entry, NULL, buffer);
525         gtk_widget_grab_focus (edited_entry);
526     }
527 }
528
529 static gboolean
530 hildon_range_editor_entry_focus_in              (GtkEditable *editable,
531                                                  GdkEventFocus *event,
532                                                  HildonRangeEditor *editor)
533 {
534     HildonRangeEditorPrivate *priv = HILDON_RANGE_EDITOR_GET_PRIVATE(editor);
535     g_assert (priv);
536
537     if (priv->bp)
538     {
539         priv->bp = FALSE;
540         return FALSE;
541     }
542
543     if (GTK_WIDGET (editable) == priv->start_entry)
544         gtk_editable_select_region (editable, -1, 0);
545     else
546         gtk_editable_select_region (editable, 0, -1);
547     return FALSE;
548 }
549
550 /* Gets and sets the current range. This has two usefull side effects:
551  * Values are now sorted to the correct order
552  * Out of range values are clamped to range */
553 static void 
554 hildon_range_editor_apply_current_range         (HildonRangeEditor *editor)
555 {
556     g_assert (HILDON_IS_RANGE_EDITOR (editor));
557
558     hildon_range_editor_set_range (editor,
559             hildon_range_editor_get_lower (editor),
560             hildon_range_editor_get_higher (editor));
561 }
562
563 static void 
564 hildon_range_editor_entry_changed               (GtkWidget *widget, 
565                                                  HildonRangeEditor *editor)
566 {
567     g_assert (HILDON_IS_RANGE_EDITOR (editor));
568
569     hildon_range_editor_entry_validate (editor, widget, TRUE);
570 }
571
572 static gboolean
573 hildon_range_editor_entry_focus_out             (GtkEditable *editable,
574                                                  GdkEventFocus *event,
575                                                  HildonRangeEditor *editor)
576 {
577     g_assert (HILDON_IS_RANGE_EDITOR(editor));
578
579     hildon_range_editor_entry_validate (editor, GTK_WIDGET (editable), FALSE);  
580     return FALSE;
581 }
582
583 static gboolean
584 hildon_range_editor_press                       (GtkEditable *editable, 
585                                                  GdkEventButton *event,
586                                                  HildonRangeEditor *editor)
587 {
588     HildonRangeEditorPrivate *priv = HILDON_RANGE_EDITOR_GET_PRIVATE(editor);
589     g_assert (priv);
590
591     priv->bp = TRUE;
592     return FALSE;
593 }
594
595 static void
596 hildon_range_editor_forall                      (GtkContainer *container,
597                                                  gboolean include_internals,
598                                                  GtkCallback callback, 
599                                                  gpointer callback_data)
600 {
601     HildonRangeEditorPrivate *priv;
602
603     g_assert (HILDON_IS_RANGE_EDITOR (container));
604     g_assert (callback != NULL);
605
606     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (container);
607     g_assert (priv);
608
609     if (! include_internals)
610         return;
611
612     (*callback) (priv->start_entry, callback_data);
613     (*callback) (priv->end_entry, callback_data);
614     (*callback) (priv->label, callback_data);
615 }
616
617 static void
618 hildon_range_editor_destroy                     (GtkObject *self)
619 {
620     HildonRangeEditorPrivate *priv = HILDON_RANGE_EDITOR_GET_PRIVATE (self);
621
622     if (priv->start_entry)
623     {
624         gtk_widget_unparent (priv->start_entry);
625         priv->start_entry = NULL;
626     }
627     if (priv->end_entry)
628     {
629         gtk_widget_unparent (priv->end_entry);
630         priv->end_entry = NULL;
631     }
632     if (priv->label)
633     {
634         gtk_widget_unparent (priv->label);
635         priv->label = NULL;
636     }
637
638     if (GTK_OBJECT_CLASS (parent_class)->destroy)
639         GTK_OBJECT_CLASS (parent_class)->destroy (self);
640 }
641
642
643 static void
644 hildon_range_editor_size_request                (GtkWidget *widget,
645                                                  GtkRequisition *requisition)
646 {
647     HildonRangeEditorPrivate *priv = NULL;
648     GtkRequisition lab_req, mreq;
649
650     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (widget);
651     g_assert (priv);
652
653     gtk_entry_get_width_chars (GTK_ENTRY (priv->end_entry));
654
655     gtk_widget_size_request (priv->start_entry, &mreq);
656     gtk_widget_size_request (priv->end_entry, &mreq);
657     gtk_widget_size_request (priv->label, &lab_req);
658
659     /* Width for entries and separator label and border */
660     requisition->width = mreq.width * 2 + lab_req.width +
661         widget->style->xthickness * 2;
662     /* Add vertical border */
663     requisition->height = mreq.height + widget->style->ythickness * 2;
664     /* Fit label height */
665     requisition->height = MAX (requisition->height, lab_req.height);
666 }
667
668 static void
669 hildon_range_editor_size_allocate               (GtkWidget *widget,
670                                                  GtkAllocation *allocation)
671 {
672     HildonRangeEditorPrivate *priv;
673     GtkAllocation child1_allocation, child2_allocation, child3_allocation;
674
675     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (widget);
676     g_assert (priv);
677
678     widget->allocation = *allocation;
679
680     /* Allocate entries, left-to-right */
681     if (priv->start_entry && GTK_WIDGET_VISIBLE (priv->start_entry))
682     {
683         GtkRequisition child_requisition;
684
685         gtk_widget_get_child_requisition (priv->start_entry,
686                 &child_requisition);
687
688         child1_allocation.x = allocation->x;
689         child1_allocation.y = allocation->y;
690
691         child1_allocation.width = child_requisition.width;
692         child1_allocation.height = allocation->height;
693
694         gtk_widget_size_allocate (priv->start_entry, &child1_allocation);
695     }
696
697     if (priv->label && GTK_WIDGET_VISIBLE (priv->label))
698     {
699         GtkRequisition child_requisition;
700
701         gtk_widget_get_child_requisition (priv->label, &child_requisition);
702
703         child2_allocation.x = child1_allocation.x + child1_allocation.width;
704         child2_allocation.y = allocation->y;
705         /* Add spacing */
706         child2_allocation.width = child_requisition.width + 4;
707         child2_allocation.height = allocation->height;
708
709         gtk_widget_size_allocate (priv->label, &child2_allocation);
710     }
711
712     if (priv->end_entry && GTK_WIDGET_VISIBLE (priv->end_entry))
713     {
714         GtkRequisition child_requisition;
715
716         gtk_widget_get_child_requisition (priv->end_entry, &child_requisition);
717
718         child3_allocation.x = child2_allocation.x + child2_allocation.width;
719         child3_allocation.y = allocation->y;
720
721         child3_allocation.width = child_requisition.width;
722         child3_allocation.height = allocation->height;
723
724         gtk_widget_size_allocate (priv->end_entry, &child3_allocation);
725     }
726 }
727
728 /* Button released inside entries */
729 static gboolean
730 hildon_range_editor_released                    (GtkEditable *editable, 
731                                                  GdkEventButton *event,
732                                                  HildonRangeEditor *editor)
733 {
734     HildonRangeEditorPrivate *priv = HILDON_RANGE_EDITOR_GET_PRIVATE(editor);
735     g_assert (priv);
736
737     if (GTK_WIDGET (editable) == priv->start_entry)
738         gtk_editable_select_region(editable, -1, 0);
739     else
740         gtk_editable_select_region(editable, 0, -1); 
741
742     return FALSE;
743 }
744
745 static gboolean
746 hildon_range_editor_entry_keypress              (GtkWidget *widget, 
747                                                  GdkEventKey *event,
748                                                  HildonRangeEditor *editor)
749 {
750     const gchar *text;
751     gint cursor_pos;
752
753     g_assert(HILDON_IS_RANGE_EDITOR (editor));
754
755     text = gtk_entry_get_text (GTK_ENTRY (widget));
756     cursor_pos = gtk_editable_get_position (GTK_EDITABLE (widget));
757
758     switch (event->keyval)
759     {
760         case GDK_Left:
761             /* If we are on the first character and press left, 
762                try to move to previous field */
763             if (cursor_pos == 0) {
764                 (void) gtk_widget_child_focus (GTK_WIDGET (editor), GTK_DIR_LEFT);
765                 return TRUE;
766             }
767             break;
768
769         case GDK_Right:
770             /* If the cursor is on the right, try to move to the next field */
771             if (cursor_pos >= g_utf8_strlen (text, -1)) {
772                 (void) gtk_widget_child_focus (GTK_WIDGET (editor), GTK_DIR_RIGHT);
773                 return TRUE;
774             }
775             break;
776
777         default:
778             break;
779     };
780
781     return FALSE;
782 }
783
784 static void 
785 hildon_range_editor_refresh_widths              (HildonRangeEditorPrivate *priv)
786 {
787     gchar start_range[32], end_range[32];
788     gint length;
789
790     /* Calculate length of entry so extremes would fit */
791     g_snprintf (start_range, sizeof (start_range), "%d", priv->range_limits_start);
792     g_snprintf (end_range, sizeof (end_range), "%d", priv->range_limits_end);
793     length = MAX (g_utf8_strlen (start_range, -1), g_utf8_strlen (end_range, -1));
794
795     gtk_entry_set_width_chars (GTK_ENTRY (priv->start_entry), length);
796     gtk_entry_set_max_length (GTK_ENTRY (priv->start_entry), length);
797     gtk_entry_set_width_chars (GTK_ENTRY (priv->end_entry), length);
798     gtk_entry_set_max_length (GTK_ENTRY (priv->end_entry), length);
799 }
800
801 /**
802  * hildon_range_editor_get_type:
803  * 
804  * Initializes, and returns the type of a hildon range editor.
805  * 
806  * @Returns : GType of #HildonRangeEditor
807  * 
808  */
809 GType G_GNUC_CONST
810 hildon_range_editor_get_type                    (void)
811 {
812     static GType editor_type = 0;
813
814     if (! editor_type)
815     {
816         static const GTypeInfo editor_info =
817         {
818             sizeof (HildonRangeEditorClass),
819             NULL,       /* base_init */
820             NULL,       /* base_finalize */
821             (GClassInitFunc) hildon_range_editor_class_init,
822             NULL,       /* class_finalize */
823             NULL,       /* class_data */
824             sizeof (HildonRangeEditor),
825             0,  /* n_preallocs */
826             (GInstanceInitFunc) hildon_range_editor_init,
827         };
828         editor_type = g_type_register_static (GTK_TYPE_CONTAINER,
829                 "HildonRangeEditor",
830                 &editor_info, 0);
831     }
832     return editor_type;
833 }
834
835 /**
836  * hildon_range_editor_new:
837  *
838  * HildonRangeEditor contains two GtkEntrys that accept numbers and minus. 
839  *
840  * Returns: pointer to a new @HildonRangeEditor widget
841  */
842 GtkWidget*
843 hildon_range_editor_new                         (void)
844 {
845     return GTK_WIDGET (g_object_new (HILDON_TYPE_RANGE_EDITOR, NULL));
846 }
847
848 /**
849  * hildon_range_editor_new_with_separator:
850  * @separator: a string that is shown between the numbers
851  *
852  * HildonRangeEditor contains two Gtk entries that accept numbers. 
853  * A separator is displayed between two entries. 
854  * CHECKME: Use '-' as a separator in the case of null separator?
855  * 
856  * Returns: pointer to a new @HildonRangeEditor widget
857  */
858 GtkWidget*
859 hildon_range_editor_new_with_separator          (const gchar *separator)
860 {
861     return GTK_WIDGET (g_object_new (HILDON_TYPE_RANGE_EDITOR,
862                 "separator", separator, NULL));
863 }
864
865 /**
866  * hildon_range_editor_set_range:
867  * @editor: the #HildonRangeEditor widget
868  * @start: range's start value 
869  * @end: range's end value
870  *
871  * Sets a range to the editor. (The current value)
872  *
873  * Sets the range of the @HildonRangeEditor widget.
874  */
875 void
876 hildon_range_editor_set_range                   (HildonRangeEditor *editor, 
877                                                  gint start,
878                                                  gint end)
879 {
880     g_return_if_fail (HILDON_IS_RANGE_EDITOR (editor));
881
882     /* Make sure that the start/end appear in the correct order */
883     hildon_range_editor_set_lower (editor, MIN (start, end));
884     hildon_range_editor_set_higher (editor, MAX (start, end));
885 }
886
887 /**
888  * hildon_range_editor_get_range:
889  * @editor: the #HildonRangeEditor widget
890  * @start: ranges start value
891  * @end: ranges end value
892  *
893  * Gets the range of the @HildonRangeEditor widget.
894  */
895 void
896 hildon_range_editor_get_range                   (HildonRangeEditor *editor, 
897                                                  gint *start,
898                                                  gint *end)
899 {
900     HildonRangeEditorPrivate *priv;
901
902     g_return_if_fail (HILDON_IS_RANGE_EDITOR (editor) && start && end);
903     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
904
905     *start = hildon_range_editor_get_lower (editor);
906     *end = hildon_range_editor_get_higher (editor);
907 }
908
909 /**
910  * hildon_range_editor_set_limits:
911  * @editor: the #HildonRangeEditor widget
912  * @start: minimum acceptable value (default: no limit)
913  * @end:   maximum acceptable value (default: no limit)
914  *
915  * Sets the range of the @HildonRangeEditor widget.
916  */
917 void
918 hildon_range_editor_set_limits                  (HildonRangeEditor *editor, 
919                                                  gint start,
920                                                  gint end)
921 {
922     /* FIXME: Setting start/end as separate steps can modify
923        the inputted range unneedlesly */
924     hildon_range_editor_set_min (editor, start);
925     hildon_range_editor_set_max (editor, end);
926 }
927
928 void
929 hildon_range_editor_set_lower                   (HildonRangeEditor *editor, 
930                                                  gint value)
931 {
932     HildonRangeEditorPrivate *priv;
933     gchar buffer[32];
934
935     g_return_if_fail (HILDON_IS_RANGE_EDITOR (editor));
936     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
937
938     g_snprintf(buffer, sizeof (buffer), "%d", 
939             CLAMP (value, priv->range_limits_start, priv->range_limits_end));
940
941     /* Update entry text with new value */
942     gtk_entry_set_text (GTK_ENTRY (priv->start_entry), buffer);
943     g_object_notify (G_OBJECT (editor), "lower");
944 }
945
946 void
947 hildon_range_editor_set_higher                  (HildonRangeEditor *editor, 
948                                                  gint value)
949 {
950     HildonRangeEditorPrivate *priv;
951     gchar buffer[32];
952
953     g_return_if_fail (HILDON_IS_RANGE_EDITOR (editor));
954     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
955
956     g_snprintf(buffer, sizeof(buffer), "%d", 
957             CLAMP(value, priv->range_limits_start, priv->range_limits_end));
958
959     /* Update entry text with new value */
960     gtk_entry_set_text (GTK_ENTRY (priv->end_entry), buffer);
961     g_object_notify (G_OBJECT (editor), "higher");
962 }
963
964 gint
965 hildon_range_editor_get_lower                   (HildonRangeEditor *editor)
966 {
967     HildonRangeEditorPrivate *priv;
968     g_return_val_if_fail (HILDON_IS_RANGE_EDITOR (editor), 0);
969     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
970     return atoi(gtk_entry_get_text(GTK_ENTRY(priv->start_entry)));
971 }
972
973 gint
974 hildon_range_editor_get_higher                  (HildonRangeEditor *editor)
975 {
976     HildonRangeEditorPrivate *priv;
977     g_return_val_if_fail (HILDON_IS_RANGE_EDITOR (editor), 0);
978     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
979     return atoi (gtk_entry_get_text(GTK_ENTRY (priv->end_entry)));
980 }
981
982 void
983 hildon_range_editor_set_min                     (HildonRangeEditor *editor, 
984                                                  gint value)
985 {
986     HildonRangeEditorPrivate *priv;
987
988     g_return_if_fail (HILDON_IS_RANGE_EDITOR (editor));
989
990     /* We can cause many properties to change */
991     g_object_freeze_notify (G_OBJECT(editor));
992     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
993     priv->range_limits_start = value;
994
995     if (priv->range_limits_end < value)
996         hildon_range_editor_set_max (editor, value);
997     /* Setting maximum applies widths and range in this case */
998     else {
999         hildon_range_editor_refresh_widths (priv);
1000         hildon_range_editor_apply_current_range (editor);
1001     }
1002
1003     g_object_notify (G_OBJECT (editor), "min");
1004     g_object_thaw_notify (G_OBJECT (editor));
1005 }
1006
1007 void
1008 hildon_range_editor_set_max                     (HildonRangeEditor *editor, 
1009                                                  gint value)
1010 {
1011     HildonRangeEditorPrivate *priv;
1012
1013     g_return_if_fail (HILDON_IS_RANGE_EDITOR (editor));
1014
1015     /* We can cause many properties to change */
1016     g_object_freeze_notify (G_OBJECT (editor));
1017     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
1018     priv->range_limits_end = value;
1019
1020     if (priv->range_limits_start > value)
1021         hildon_range_editor_set_min (editor, value);
1022     /* Setting minimum applies widths and range in this case */
1023     else {
1024         hildon_range_editor_refresh_widths (priv);
1025         hildon_range_editor_apply_current_range (editor);
1026     }
1027
1028     g_object_notify (G_OBJECT (editor), "max");
1029     g_object_thaw_notify (G_OBJECT (editor));
1030 }
1031
1032 gint
1033 hildon_range_editor_get_min                     (HildonRangeEditor *editor)
1034 {
1035     HildonRangeEditorPrivate *priv;
1036     g_return_val_if_fail (HILDON_IS_RANGE_EDITOR (editor), 0);
1037     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
1038
1039     return priv->range_limits_start;
1040 }
1041
1042 gint
1043 hildon_range_editor_get_max                     (HildonRangeEditor *editor)
1044 {
1045     HildonRangeEditorPrivate *priv;
1046     g_return_val_if_fail (HILDON_IS_RANGE_EDITOR (editor), 0);
1047     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
1048
1049     return priv->range_limits_end;
1050 }
1051
1052 void
1053 hildon_range_editor_set_separator               (HildonRangeEditor *editor,
1054                                                  const gchar *separator)
1055 {
1056     HildonRangeEditorPrivate *priv;
1057     g_return_if_fail (HILDON_IS_RANGE_EDITOR (editor));
1058     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
1059
1060     gtk_label_set_text (GTK_LABEL (priv->label), separator);
1061     g_object_notify (G_OBJECT(editor), "separator");
1062 }
1063
1064 const gchar*
1065 hildon_range_editor_get_separator               (HildonRangeEditor *editor)
1066 {
1067     HildonRangeEditorPrivate *priv;
1068     g_return_val_if_fail (HILDON_IS_RANGE_EDITOR (editor), NULL);
1069     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
1070
1071     return gtk_label_get_text (GTK_LABEL (priv->label));
1072 }