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