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