Adding the HildonCalendar based on Gtk Calendar. This effectively changes the license...
[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.
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     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
337     gtk_widget_show (priv->start_entry);
338     gtk_widget_show (priv->end_entry);
339     gtk_widget_show (priv->label);
340
341     gtk_widget_pop_composite_child();
342 }
343
344 static void 
345 hildon_range_editor_set_property                (GObject *object, 
346                                                  guint param_id,
347                                                  const GValue *value, 
348                                                  GParamSpec *pspec)
349 {
350     HildonRangeEditor *editor = HILDON_RANGE_EDITOR(object);
351
352     switch (param_id)
353     {
354         case PROP_LOWER:
355             hildon_range_editor_set_lower (editor, g_value_get_int (value));
356             break;
357
358         case PROP_HIGHER:
359             hildon_range_editor_set_higher (editor, g_value_get_int (value));
360             break;
361
362         case PROP_MIN:
363             hildon_range_editor_set_min (editor, g_value_get_int (value));
364             break;
365
366         case PROP_MAX:
367             hildon_range_editor_set_max (editor, g_value_get_int (value));
368             break;
369
370         case PROP_SEPARATOR:
371             hildon_range_editor_set_separator (editor,
372                     g_value_get_string (value));
373             break;
374
375         default:
376             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
377             break;
378     }
379 }
380
381 static void
382 hildon_range_editor_get_property                (GObject *object, 
383                                                  guint param_id,
384                                                  GValue *value, 
385                                                  GParamSpec *pspec)
386 {
387     HildonRangeEditor *editor = HILDON_RANGE_EDITOR(object);
388
389     switch (param_id)
390     {
391         case PROP_LOWER:
392             g_value_set_int (value, hildon_range_editor_get_lower (editor));
393             break;
394
395         case PROP_HIGHER:
396             g_value_set_int (value, hildon_range_editor_get_higher (editor));
397             break;
398
399         case PROP_MIN:
400             g_value_set_int (value, hildon_range_editor_get_min (editor));
401             break;
402
403         case PROP_MAX:
404             g_value_set_int (value, hildon_range_editor_get_max (editor));
405             break;
406
407         case PROP_SEPARATOR:
408             g_value_set_string (value, hildon_range_editor_get_separator (editor));
409             break;
410
411         default:
412             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
413             break;
414     }
415 }
416
417 static void
418 hildon_range_editor_entry_validate              (HildonRangeEditor *editor, 
419                                                  GtkWidget *edited_entry, 
420                                                  gboolean allow_intermediate)
421 {
422     HildonRangeEditorPrivate *priv;
423     const gchar *text;
424     long value;
425     gint min, max, fixup;
426     gchar *tail;
427     gchar buffer[256];
428     gboolean error = FALSE;
429
430     g_assert(HILDON_IS_RANGE_EDITOR(editor));
431     g_assert(GTK_IS_ENTRY(edited_entry));
432
433     priv = HILDON_RANGE_EDITOR_GET_PRIVATE(editor);
434     g_assert (priv);
435
436     /* Find the valid range for the modified component */
437     if (edited_entry == priv->start_entry) {
438         min = hildon_range_editor_get_min (editor);
439         max = hildon_range_editor_get_higher (editor);
440     } else {
441         min = hildon_range_editor_get_lower (editor);
442         max = hildon_range_editor_get_max (editor);
443     }
444
445     text = gtk_entry_get_text (GTK_ENTRY (edited_entry));
446
447     if (text && text [0])
448     { 
449         /* Try to convert entry text to number */
450         value = strtol(text, &tail, 10);
451
452         /* Check if conversion succeeded */
453         if (tail [0] == 0)
454         {    
455             /* Check if value is in allowed range. This is tricky in those
456                cases when user is editing a value. 
457                For example: Range = [100, 500] and user have just inputted "4".
458                This should not lead into error message. Otherwise value is
459                resetted back to "100" and next "4" press will reset it back
460                and so on. */
461             if (allow_intermediate)
462             {
463                 /* We now have the following error cases:
464                  * If inputted value as above maximum and
465                  maximum is either positive or then maximum
466                  negative and value is positive.
467                  * If inputted value is below minimum and minimum
468                  is negative or minumum positive and value
469                  negative.
470                  In all other cases situation can be fixed just by
471                  adding new numbers to the string.
472                  */
473                 if (value > max && (max >= 0 || (max < 0 && value >= 0)))
474                 {
475                     error = TRUE;
476                     fixup = max;
477                     g_snprintf (buffer, sizeof (buffer), _("ckct_ib_maximum_value"), max);
478                 }
479                 else if (value < min && (min < 0 || (min >= 0 && value < 0)))
480                 {
481                     error = TRUE;
482                     fixup = min;
483                     g_snprintf (buffer, sizeof (buffer), _("ckct_ib_minimum_value"), min);
484                 }
485             }
486             else
487             {
488                 if (value > max) {
489                     error = TRUE;
490                     fixup = max;
491                     g_snprintf (buffer, sizeof (buffer), _("ckct_ib_maximum_value"), max);
492                 }
493                 else if (value < min) {
494                     error = TRUE;
495                     fixup = min;
496                     g_snprintf (buffer, sizeof (buffer), _("ckct_ib_minimum_value"), min);
497                 }
498             }
499
500             if (error) {
501                 if (edited_entry == priv->start_entry)
502                     hildon_range_editor_set_lower (editor, fixup);
503                 else
504                     hildon_range_editor_set_higher (editor, fixup);
505             }
506         }
507         /* The only valid case when conversion can fail is when we
508            have plain '-', intermediate forms are allowed AND
509            minimum bound is negative */
510         else if (!allow_intermediate || strcmp(text, "-") != 0 || min >= 0) {
511             error = TRUE;
512             g_snprintf (buffer, sizeof (buffer), _("ckct_ib_set_a_value_within_range"), min, max);
513         }
514     }
515     else if (! allow_intermediate) {
516         error = TRUE;
517         g_snprintf (buffer, sizeof (buffer), _("ckct_ib_set_a_value_within_range"), min, max);
518     }
519
520     if (error)
521     {
522         hildon_banner_show_information (edited_entry, NULL, buffer);
523         gtk_widget_grab_focus (edited_entry);
524     }
525 }
526
527 static gboolean
528 hildon_range_editor_entry_focus_in              (GtkEditable *editable,
529                                                  GdkEventFocus *event,
530                                                  HildonRangeEditor *editor)
531 {
532     HildonRangeEditorPrivate *priv = HILDON_RANGE_EDITOR_GET_PRIVATE(editor);
533     g_assert (priv);
534
535     if (priv->bp)
536     {
537         priv->bp = FALSE;
538         return FALSE;
539     }
540
541     if (GTK_WIDGET (editable) == priv->start_entry)
542         gtk_editable_select_region (editable, -1, 0);
543     else
544         gtk_editable_select_region (editable, 0, -1);
545     return FALSE;
546 }
547
548 /* Gets and sets the current range. This has two usefull side effects:
549  * Values are now sorted to the correct order
550  * Out of range values are clamped to range */
551 static void 
552 hildon_range_editor_apply_current_range         (HildonRangeEditor *editor)
553 {
554     g_assert (HILDON_IS_RANGE_EDITOR (editor));
555
556     hildon_range_editor_set_range (editor,
557             hildon_range_editor_get_lower (editor),
558             hildon_range_editor_get_higher (editor));
559 }
560
561 static void 
562 hildon_range_editor_entry_changed               (GtkWidget *widget, 
563                                                  HildonRangeEditor *editor)
564 {
565     g_assert (HILDON_IS_RANGE_EDITOR (editor));
566
567     hildon_range_editor_entry_validate (editor, widget, TRUE);
568 }
569
570 static gboolean
571 hildon_range_editor_entry_focus_out             (GtkEditable *editable,
572                                                  GdkEventFocus *event,
573                                                  HildonRangeEditor *editor)
574 {
575     g_assert (HILDON_IS_RANGE_EDITOR(editor));
576
577     hildon_range_editor_entry_validate (editor, GTK_WIDGET (editable), FALSE);  
578     return FALSE;
579 }
580
581 static gboolean
582 hildon_range_editor_press                       (GtkEditable *editable, 
583                                                  GdkEventButton *event,
584                                                  HildonRangeEditor *editor)
585 {
586     HildonRangeEditorPrivate *priv = HILDON_RANGE_EDITOR_GET_PRIVATE(editor);
587     g_assert (priv);
588
589     priv->bp = TRUE;
590     return FALSE;
591 }
592
593 static void
594 hildon_range_editor_forall                      (GtkContainer *container,
595                                                  gboolean include_internals,
596                                                  GtkCallback callback, 
597                                                  gpointer callback_data)
598 {
599     HildonRangeEditorPrivate *priv;
600
601     g_assert (HILDON_IS_RANGE_EDITOR (container));
602     g_assert (callback != NULL);
603
604     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (container);
605     g_assert (priv);
606
607     if (! include_internals)
608         return;
609
610     (*callback) (priv->start_entry, callback_data);
611     (*callback) (priv->end_entry, callback_data);
612     (*callback) (priv->label, callback_data);
613 }
614
615 static void
616 hildon_range_editor_destroy                     (GtkObject *self)
617 {
618     HildonRangeEditorPrivate *priv = HILDON_RANGE_EDITOR_GET_PRIVATE (self);
619
620     if (priv->start_entry)
621     {
622         gtk_widget_unparent (priv->start_entry);
623         priv->start_entry = NULL;
624     }
625     if (priv->end_entry)
626     {
627         gtk_widget_unparent (priv->end_entry);
628         priv->end_entry = NULL;
629     }
630     if (priv->label)
631     {
632         gtk_widget_unparent (priv->label);
633         priv->label = NULL;
634     }
635
636     if (GTK_OBJECT_CLASS (parent_class)->destroy)
637         GTK_OBJECT_CLASS (parent_class)->destroy (self);
638 }
639
640
641 static void
642 hildon_range_editor_size_request                (GtkWidget *widget,
643                                                  GtkRequisition *requisition)
644 {
645     HildonRangeEditorPrivate *priv = NULL;
646     GtkRequisition lab_req, mreq;
647
648     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (widget);
649     g_assert (priv);
650
651     gtk_entry_get_width_chars (GTK_ENTRY (priv->end_entry));
652
653     gtk_widget_size_request (priv->start_entry, &mreq);
654     gtk_widget_size_request (priv->end_entry, &mreq);
655     gtk_widget_size_request (priv->label, &lab_req);
656
657     /* Width for entries and separator label and border */
658     requisition->width = mreq.width * 2 + lab_req.width +
659         widget->style->xthickness * 2;
660     /* Add vertical border */
661     requisition->height = mreq.height + widget->style->ythickness * 2;
662     /* Fit label height */
663     requisition->height = MAX (requisition->height, lab_req.height);
664 }
665
666 static void
667 hildon_range_editor_size_allocate               (GtkWidget *widget,
668                                                  GtkAllocation *allocation)
669 {
670     HildonRangeEditorPrivate *priv;
671     GtkAllocation child1_allocation, child2_allocation, child3_allocation;
672
673     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (widget);
674     g_assert (priv);
675
676     widget->allocation = *allocation;
677
678     /* Allocate entries, left-to-right */
679     if (priv->start_entry && GTK_WIDGET_VISIBLE (priv->start_entry))
680     {
681         GtkRequisition child_requisition;
682
683         gtk_widget_get_child_requisition (priv->start_entry,
684                 &child_requisition);
685
686         child1_allocation.x = allocation->x;
687         child1_allocation.y = allocation->y;
688
689         child1_allocation.width = child_requisition.width;
690         child1_allocation.height = allocation->height;
691
692         gtk_widget_size_allocate (priv->start_entry, &child1_allocation);
693     }
694
695     if (priv->label && GTK_WIDGET_VISIBLE (priv->label))
696     {
697         GtkRequisition child_requisition;
698
699         gtk_widget_get_child_requisition (priv->label, &child_requisition);
700
701         child2_allocation.x = child1_allocation.x + child1_allocation.width;
702         child2_allocation.y = allocation->y;
703         /* Add spacing */
704         child2_allocation.width = child_requisition.width + 4;
705         child2_allocation.height = allocation->height;
706
707         gtk_widget_size_allocate (priv->label, &child2_allocation);
708     }
709
710     if (priv->end_entry && GTK_WIDGET_VISIBLE (priv->end_entry))
711     {
712         GtkRequisition child_requisition;
713
714         gtk_widget_get_child_requisition (priv->end_entry, &child_requisition);
715
716         child3_allocation.x = child2_allocation.x + child2_allocation.width;
717         child3_allocation.y = allocation->y;
718
719         child3_allocation.width = child_requisition.width;
720         child3_allocation.height = allocation->height;
721
722         gtk_widget_size_allocate (priv->end_entry, &child3_allocation);
723     }
724 }
725
726 /* Button released inside entries */
727 static gboolean
728 hildon_range_editor_released                    (GtkEditable *editable, 
729                                                  GdkEventButton *event,
730                                                  HildonRangeEditor *editor)
731 {
732     HildonRangeEditorPrivate *priv = HILDON_RANGE_EDITOR_GET_PRIVATE(editor);
733     g_assert (priv);
734
735     if (GTK_WIDGET (editable) == priv->start_entry)
736         gtk_editable_select_region(editable, -1, 0);
737     else
738         gtk_editable_select_region(editable, 0, -1); 
739
740     return FALSE;
741 }
742
743 static gboolean
744 hildon_range_editor_entry_keypress              (GtkWidget *widget, 
745                                                  GdkEventKey *event,
746                                                  HildonRangeEditor *editor)
747 {
748     const gchar *text;
749     gint cursor_pos;
750
751     g_assert(HILDON_IS_RANGE_EDITOR (editor));
752
753     text = gtk_entry_get_text (GTK_ENTRY (widget));
754     cursor_pos = gtk_editable_get_position (GTK_EDITABLE (widget));
755
756     switch (event->keyval)
757     {
758         case GDK_Left:
759             /* If we are on the first character and press left, 
760                try to move to previous field */
761             if (cursor_pos == 0) {
762                 (void) gtk_widget_child_focus (GTK_WIDGET (editor), GTK_DIR_LEFT);
763                 return TRUE;
764             }
765             break;
766
767         case GDK_Right:
768             /* If the cursor is on the right, try to move to the next field */
769             if (cursor_pos >= g_utf8_strlen (text, -1)) {
770                 (void) gtk_widget_child_focus (GTK_WIDGET (editor), GTK_DIR_RIGHT);
771                 return TRUE;
772             }
773             break;
774
775         default:
776             break;
777     };
778
779     return FALSE;
780 }
781
782 static void 
783 hildon_range_editor_refresh_widths              (HildonRangeEditorPrivate *priv)
784 {
785     gchar start_range[32], end_range[32];
786     gint length;
787
788     /* Calculate length of entry so extremes would fit */
789     g_snprintf (start_range, sizeof (start_range), "%d", priv->range_limits_start);
790     g_snprintf (end_range, sizeof (end_range), "%d", priv->range_limits_end);
791     length = MAX (g_utf8_strlen (start_range, -1), g_utf8_strlen (end_range, -1));
792
793     gtk_entry_set_width_chars (GTK_ENTRY (priv->start_entry), length);
794     gtk_entry_set_max_length (GTK_ENTRY (priv->start_entry), length);
795     gtk_entry_set_width_chars (GTK_ENTRY (priv->end_entry), length);
796     gtk_entry_set_max_length (GTK_ENTRY (priv->end_entry), length);
797 }
798
799 /**
800  * hildon_range_editor_get_type:
801  * 
802  * Initializes, and returns the type of a hildon range editor.
803  * 
804  * @Returns : GType of #HildonRangeEditor
805  * 
806  */
807 GType G_GNUC_CONST
808 hildon_range_editor_get_type                    (void)
809 {
810     static GType editor_type = 0;
811
812     if (! editor_type)
813     {
814         static const GTypeInfo editor_info =
815         {
816             sizeof (HildonRangeEditorClass),
817             NULL,       /* base_init */
818             NULL,       /* base_finalize */
819             (GClassInitFunc) hildon_range_editor_class_init,
820             NULL,       /* class_finalize */
821             NULL,       /* class_data */
822             sizeof (HildonRangeEditor),
823             0,  /* n_preallocs */
824             (GInstanceInitFunc) hildon_range_editor_init,
825         };
826         editor_type = g_type_register_static (GTK_TYPE_CONTAINER,
827                 "HildonRangeEditor",
828                 &editor_info, 0);
829     }
830     return editor_type;
831 }
832
833 /**
834  * hildon_range_editor_new:
835  *
836  * HildonRangeEditor contains two GtkEntrys that accept numbers and minus. 
837  *
838  * Returns: pointer to a new @HildonRangeEditor widget
839  */
840 GtkWidget*
841 hildon_range_editor_new                         (void)
842 {
843     return GTK_WIDGET (g_object_new (HILDON_TYPE_RANGE_EDITOR, NULL));
844 }
845
846 /**
847  * hildon_range_editor_new_with_separator:
848  * @separator: a string that is shown between the numbers
849  *
850  * HildonRangeEditor contains two Gtk entries that accept numbers. 
851  * A separator is displayed between two entries. 
852  * CHECKME: Use '-' as a separator in the case of null separator?
853  * 
854  * Returns: pointer to a new @HildonRangeEditor widget
855  */
856 GtkWidget*
857 hildon_range_editor_new_with_separator          (const gchar *separator)
858 {
859     return GTK_WIDGET (g_object_new (HILDON_TYPE_RANGE_EDITOR,
860                 "separator", separator, NULL));
861 }
862
863 /**
864  * hildon_range_editor_set_range:
865  * @editor: the #HildonRangeEditor widget
866  * @start: range's start value 
867  * @end: range's end value
868  *
869  * Sets a range to the editor. (The current value)
870  *
871  * Sets the range of the @HildonRangeEditor widget.
872  */
873 void
874 hildon_range_editor_set_range                   (HildonRangeEditor *editor, 
875                                                  gint start,
876                                                  gint end)
877 {
878     g_return_if_fail (HILDON_IS_RANGE_EDITOR (editor));
879
880     /* Make sure that the start/end appear in the correct order */
881     hildon_range_editor_set_lower (editor, MIN (start, end));
882     hildon_range_editor_set_higher (editor, MAX (start, end));
883 }
884
885 /**
886  * hildon_range_editor_get_range:
887  * @editor: the #HildonRangeEditor widget
888  * @start: ranges start value
889  * @end: ranges end value
890  *
891  * Gets the range of the @HildonRangeEditor widget.
892  */
893 void
894 hildon_range_editor_get_range                   (HildonRangeEditor *editor, 
895                                                  gint *start,
896                                                  gint *end)
897 {
898     HildonRangeEditorPrivate *priv;
899
900     g_return_if_fail (HILDON_IS_RANGE_EDITOR (editor) && start && end);
901     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
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 }