Adding an example test program to check icon-lookup functionality. Doc updates.
[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-input-mode-hint.h"
56 #include                                        "hildon-banner.h"
57 #include                                        <libintl.h>
58 #include                                        "hildon-range-editor-private.h"
59
60 #define                                         _(string) \
61                                                 dgettext("hildon-libs", string)
62
63 /* Alignment in entry box ( 0 = left, 1 = right ) */
64
65 #define                                         DEFAULT_ALIGNMENT 1
66
67 /* Amount of padding to add to each side of the separator */
68
69 #define                                         DEFAULT_PADDING 3
70
71 #define                                         DEFAULT_START -999
72
73 #define                                         DEFAULT_END    999
74
75 #define                                         DEFAULT_LENGTH 4
76
77 static GtkContainerClass                        *parent_class = NULL;
78
79 static void
80 hildon_range_editor_class_init                  (HildonRangeEditorClass *editor_class);
81
82 static void
83 hildon_range_editor_init                        (HildonRangeEditor *editor);
84
85 static void
86 hildon_range_editor_forall                      (GtkContainer *container,
87                                                  gboolean include_internals, 
88                                                  GtkCallback callback,
89                                                  gpointer callback_data);
90
91 static void
92 hildon_range_editor_destroy                     (GtkObject *self);
93
94 static void
95 hildon_range_editor_size_request                (GtkWidget *widget,
96                                                  GtkRequisition *requisition);
97
98 static void
99 hildon_range_editor_size_allocate               (GtkWidget *widget,
100                                                  GtkAllocation *allocation);
101
102 static gboolean
103 hildon_range_editor_entry_focus_in              (GtkEditable *editable,
104                                                  GdkEventFocus *event,
105                                                  HildonRangeEditor *editor);
106
107 static gboolean
108 hildon_range_editor_entry_focus_out             (GtkEditable *editable,
109                                                  GdkEventFocus *event,
110                                                  HildonRangeEditor *editor);
111
112 static gboolean
113 hildon_range_editor_entry_keypress              (GtkWidget *widget, 
114                                                  GdkEventKey *event,
115                                                  HildonRangeEditor *editor);
116
117 static gboolean
118 hildon_range_editor_released                    (GtkEditable *editable, 
119                                                  GdkEventButton *event,
120                                                  HildonRangeEditor *editor);
121
122 static gboolean
123 hildon_range_editor_press                       (GtkEditable *editable, 
124                                                  GdkEventButton *event,
125                                                  HildonRangeEditor *editor);
126
127 static void 
128 hildon_range_editor_set_property                (GObject *object, 
129                                                  guint param_id,
130                                                  const GValue *value, 
131                                                  GParamSpec *pspec);
132
133 static void
134 hildon_range_editor_get_property                (GObject *object, 
135                                                  guint param_id,
136                                                  GValue *value, 
137                                                  GParamSpec *pspec);
138
139 static void
140 hildon_range_editor_entry_changed               (GtkWidget *widget, 
141                                                  HildonRangeEditor *editor);
142
143 enum
144 {
145     PROP_0,
146     PROP_LOWER = 1,
147     PROP_HIGHER,
148     PROP_MIN,
149     PROP_MAX,
150     PROP_SEPARATOR
151 };
152
153 static void
154 hildon_range_editor_class_init                  (HildonRangeEditorClass *editor_class)
155 {
156     GObjectClass *gobject_class = G_OBJECT_CLASS (editor_class);
157     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (editor_class);
158     GtkContainerClass *container_class = GTK_CONTAINER_CLASS (editor_class);
159
160     parent_class = g_type_class_peek_parent (editor_class);
161
162     g_type_class_add_private (editor_class, sizeof (HildonRangeEditorPrivate));
163
164     gobject_class->set_property = hildon_range_editor_set_property;
165     gobject_class->get_property = hildon_range_editor_get_property;
166     widget_class->size_request  = hildon_range_editor_size_request;
167     widget_class->size_allocate = hildon_range_editor_size_allocate;
168
169     container_class->forall = hildon_range_editor_forall;
170     GTK_OBJECT_CLASS (editor_class)->destroy = hildon_range_editor_destroy;
171
172     gtk_widget_class_install_style_property (widget_class,
173             g_param_spec_int ("hildon_range_editor_entry_alignment",
174                 "Hildon RangeEditor entry alignment",
175                 "Hildon RangeEditor entry alignment", 0, 1,
176                 DEFAULT_ALIGNMENT,
177                 G_PARAM_READABLE));
178
179     gtk_widget_class_install_style_property (widget_class,
180             g_param_spec_int ("hildon_range_editor_separator_padding",
181                 "Hildon RangeEditor separator padding",
182                 "Hildon RangeEditor separaror padding",
183                 G_MININT, G_MAXINT,
184                 DEFAULT_PADDING,
185                 G_PARAM_READABLE));
186
187     /**
188      * HildonRangeEditor:min:
189      *
190      * Minimum value in a range.
191      * Default: -999
192      */
193     g_object_class_install_property (gobject_class, PROP_MIN,
194             g_param_spec_int ("min",
195                 "Minimum value",
196                 "Minimum value in a range",
197                 G_MININT, G_MAXINT,
198                 DEFAULT_START, G_PARAM_CONSTRUCT | 
199                 G_PARAM_READABLE | G_PARAM_WRITABLE));
200
201     /**
202      * HildonRangeEditor:max:
203      *
204      * Maximum value in a range.
205      * Default: 999
206      */
207     g_object_class_install_property (gobject_class, PROP_MAX,
208             g_param_spec_int ("max",
209                 "Maximum value",
210                 "Maximum value in a range",
211                 G_MININT, G_MAXINT,
212                 DEFAULT_END, G_PARAM_CONSTRUCT | 
213                 G_PARAM_READABLE | G_PARAM_WRITABLE));
214
215     /**
216      * HildonRangeEditor:lower:
217      *
218      * Current value in the entry presenting lower end of selected range.
219      * Default: -999
220      */
221     g_object_class_install_property (gobject_class, PROP_LOWER,
222             g_param_spec_int ("lower",
223                 "Current lower value",
224                 "Current value in the entry presenting lower end of selected range",
225                 G_MININT, G_MAXINT,
226                 DEFAULT_START, G_PARAM_CONSTRUCT | 
227                 G_PARAM_READABLE | G_PARAM_WRITABLE));
228
229     /**
230      * HildonRangeEditor:higher:
231      *
232      * Current value in the entry presenting higher end of selected range.
233      * Default: 999
234      */
235     g_object_class_install_property (gobject_class, PROP_HIGHER,
236             g_param_spec_int ("higher",
237                 "Current higher value",
238                 "Current value in the entry presenting higher end of selected range",
239                 G_MININT, G_MAXINT,
240                 DEFAULT_END, G_PARAM_CONSTRUCT | 
241                 G_PARAM_READABLE | G_PARAM_WRITABLE));
242
243     /**
244      * HildonRangeEditor:separator:
245      *
246      * Separator string to separate range editor entries.
247      * Default: "-"
248      */
249     g_object_class_install_property (gobject_class, PROP_SEPARATOR,
250             g_param_spec_string ("separator",
251                 "Separator",
252                 "Separator string to separate entries",
253                 _("ckct_wi_range_separator"),
254                 G_PARAM_CONSTRUCT | 
255                 G_PARAM_READABLE | G_PARAM_WRITABLE));
256 }
257
258 static void
259 hildon_range_editor_init                        (HildonRangeEditor *editor)
260 {
261     HildonRangeEditorPrivate *priv;
262
263     gint range_editor_entry_alignment;
264     gint range_editor_separator_padding;
265
266     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
267     g_assert (priv);
268
269     GTK_WIDGET_SET_FLAGS (editor, GTK_NO_WINDOW);
270
271     gtk_widget_push_composite_child ();
272
273     priv->start_entry = gtk_entry_new ();
274     priv->end_entry = gtk_entry_new ();
275     priv->label = gtk_label_new (_("ckct_wi_range_separator"));
276     priv->bp = FALSE;
277
278     /* Get values from gtkrc (or use defaults) */
279     /* FIXME: This is broken, styles are not yet attached */
280     gtk_widget_style_get (GTK_WIDGET (editor),
281             "hildon_range_editor_entry_alignment",
282             &range_editor_entry_alignment,
283             "hildon_range_editor_separator_padding",
284             &range_editor_separator_padding, NULL);
285
286     /* Add padding to separator */
287     gtk_misc_set_padding (GTK_MISC (priv->label),
288             range_editor_separator_padding, 0);
289
290     /* Align the text to right in entry box */
291     gtk_entry_set_alignment (GTK_ENTRY (priv->start_entry),
292             range_editor_entry_alignment);
293     gtk_entry_set_alignment (GTK_ENTRY (priv->end_entry),
294             range_editor_entry_alignment);
295
296     gtk_widget_set_composite_name (priv->start_entry, "start_entry");
297     gtk_widget_set_composite_name (priv->end_entry, "end_entry");
298     gtk_widget_set_composite_name (priv->label, "separator_label");
299     gtk_widget_set_parent (priv->start_entry, GTK_WIDGET (editor));
300     gtk_widget_set_parent (priv->end_entry, GTK_WIDGET (editor));
301     gtk_widget_set_parent (priv->label, GTK_WIDGET (editor));
302
303     g_signal_connect (G_OBJECT (priv->start_entry), "button-release-event",
304             G_CALLBACK (hildon_range_editor_released), editor);
305     g_signal_connect (G_OBJECT (priv->end_entry), "button-release-event",
306             G_CALLBACK (hildon_range_editor_released), editor);
307
308     g_signal_connect (G_OBJECT (priv->start_entry), "button-press-event",
309             G_CALLBACK (hildon_range_editor_press), editor);
310     g_signal_connect (G_OBJECT (priv->end_entry), "button-press-event",
311             G_CALLBACK (hildon_range_editor_press), editor);
312
313     g_signal_connect (G_OBJECT (priv->start_entry), "key-press-event",
314             G_CALLBACK (hildon_range_editor_entry_keypress), editor);
315     g_signal_connect (G_OBJECT (priv->end_entry), "key-press-event",
316             G_CALLBACK (hildon_range_editor_entry_keypress), editor);
317
318     g_signal_connect (G_OBJECT (priv->start_entry), "focus-in-event",
319             G_CALLBACK (hildon_range_editor_entry_focus_in), editor);
320     g_signal_connect (G_OBJECT (priv->end_entry), "focus-in-event",
321             G_CALLBACK (hildon_range_editor_entry_focus_in), editor);
322
323     g_signal_connect (G_OBJECT (priv->start_entry), "focus-out-event",
324             G_CALLBACK (hildon_range_editor_entry_focus_out), editor);
325     g_signal_connect (G_OBJECT (priv->end_entry), "focus-out-event",
326             G_CALLBACK (hildon_range_editor_entry_focus_out), editor);
327     g_signal_connect (priv->start_entry, "changed", 
328             G_CALLBACK (hildon_range_editor_entry_changed), editor);
329     g_signal_connect (priv->end_entry, "changed", 
330             G_CALLBACK (hildon_range_editor_entry_changed), editor);
331
332     g_object_set (G_OBJECT (priv->start_entry),
333             "input-mode", HILDON_INPUT_MODE_HINT_NUMERIC, NULL);
334
335     g_object_set( G_OBJECT (priv->end_entry),
336             "input-mode", HILDON_INPUT_MODE_HINT_NUMERIC, NULL);
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 usefull 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, child2_allocation, child3_allocation;
673
674     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (widget);
675     g_assert (priv);
676
677     widget->allocation = *allocation;
678
679     /* Allocate entries, left-to-right */
680     if (priv->start_entry && GTK_WIDGET_VISIBLE (priv->start_entry))
681     {
682         GtkRequisition child_requisition;
683
684         gtk_widget_get_child_requisition (priv->start_entry,
685                 &child_requisition);
686
687         child1_allocation.x = allocation->x;
688         child1_allocation.y = allocation->y;
689
690         child1_allocation.width = child_requisition.width;
691         child1_allocation.height = allocation->height;
692
693         gtk_widget_size_allocate (priv->start_entry, &child1_allocation);
694     }
695
696     if (priv->label && GTK_WIDGET_VISIBLE (priv->label))
697     {
698         GtkRequisition child_requisition;
699
700         gtk_widget_get_child_requisition (priv->label, &child_requisition);
701
702         child2_allocation.x = child1_allocation.x + child1_allocation.width;
703         child2_allocation.y = allocation->y;
704         /* Add spacing */
705         child2_allocation.width = child_requisition.width + 4;
706         child2_allocation.height = allocation->height;
707
708         gtk_widget_size_allocate (priv->label, &child2_allocation);
709     }
710
711     if (priv->end_entry && GTK_WIDGET_VISIBLE (priv->end_entry))
712     {
713         GtkRequisition child_requisition;
714
715         gtk_widget_get_child_requisition (priv->end_entry, &child_requisition);
716
717         child3_allocation.x = child2_allocation.x + child2_allocation.width;
718         child3_allocation.y = allocation->y;
719
720         child3_allocation.width = child_requisition.width;
721         child3_allocation.height = allocation->height;
722
723         gtk_widget_size_allocate (priv->end_entry, &child3_allocation);
724     }
725 }
726
727 /* Button released inside entries */
728 static gboolean
729 hildon_range_editor_released                    (GtkEditable *editable, 
730                                                  GdkEventButton *event,
731                                                  HildonRangeEditor *editor)
732 {
733     HildonRangeEditorPrivate *priv = HILDON_RANGE_EDITOR_GET_PRIVATE(editor);
734     g_assert (priv);
735
736     if (GTK_WIDGET (editable) == priv->start_entry)
737         gtk_editable_select_region(editable, -1, 0);
738     else
739         gtk_editable_select_region(editable, 0, -1); 
740
741     return FALSE;
742 }
743
744 static gboolean
745 hildon_range_editor_entry_keypress              (GtkWidget *widget, 
746                                                  GdkEventKey *event,
747                                                  HildonRangeEditor *editor)
748 {
749     const gchar *text;
750     gint cursor_pos;
751
752     g_assert(HILDON_IS_RANGE_EDITOR (editor));
753
754     text = gtk_entry_get_text (GTK_ENTRY (widget));
755     cursor_pos = gtk_editable_get_position (GTK_EDITABLE (widget));
756
757     switch (event->keyval)
758     {
759         case GDK_Left:
760             /* If we are on the first character and press left, 
761                try to move to previous field */
762             if (cursor_pos == 0) {
763                 (void) gtk_widget_child_focus (GTK_WIDGET (editor), GTK_DIR_LEFT);
764                 return TRUE;
765             }
766             break;
767
768         case GDK_Right:
769             /* If the cursor is on the right, try to move to the next field */
770             if (cursor_pos >= g_utf8_strlen (text, -1)) {
771                 (void) gtk_widget_child_focus (GTK_WIDGET (editor), GTK_DIR_RIGHT);
772                 return TRUE;
773             }
774             break;
775
776         default:
777             break;
778     };
779
780     return FALSE;
781 }
782
783 static void 
784 hildon_range_editor_refresh_widths              (HildonRangeEditorPrivate *priv)
785 {
786     gchar start_range[32], end_range[32];
787     gint length;
788
789     /* Calculate length of entry so extremes would fit */
790     g_snprintf (start_range, sizeof (start_range), "%d", priv->range_limits_start);
791     g_snprintf (end_range, sizeof (end_range), "%d", priv->range_limits_end);
792     length = MAX (g_utf8_strlen (start_range, -1), g_utf8_strlen (end_range, -1));
793
794     gtk_entry_set_width_chars (GTK_ENTRY (priv->start_entry), length);
795     gtk_entry_set_max_length (GTK_ENTRY (priv->start_entry), length);
796     gtk_entry_set_width_chars (GTK_ENTRY (priv->end_entry), length);
797     gtk_entry_set_max_length (GTK_ENTRY (priv->end_entry), length);
798 }
799
800 /**
801  * hildon_range_editor_get_type:
802  * 
803  * Initializes, and returns the type of a hildon range editor.
804  * 
805  * @Returns : GType of #HildonRangeEditor
806  * 
807  */
808 GType G_GNUC_CONST
809 hildon_range_editor_get_type                    (void)
810 {
811     static GType editor_type = 0;
812
813     if (! editor_type)
814     {
815         static const GTypeInfo editor_info =
816         {
817             sizeof (HildonRangeEditorClass),
818             NULL,       /* base_init */
819             NULL,       /* base_finalize */
820             (GClassInitFunc) hildon_range_editor_class_init,
821             NULL,       /* class_finalize */
822             NULL,       /* class_data */
823             sizeof (HildonRangeEditor),
824             0,  /* n_preallocs */
825             (GInstanceInitFunc) hildon_range_editor_init,
826         };
827         editor_type = g_type_register_static (GTK_TYPE_CONTAINER,
828                 "HildonRangeEditor",
829                 &editor_info, 0);
830     }
831     return editor_type;
832 }
833
834 /**
835  * hildon_range_editor_new:
836  *
837  * HildonRangeEditor contains two GtkEntrys that accept numbers and minus. 
838  *
839  * Returns: pointer to a new @HildonRangeEditor widget
840  */
841 GtkWidget*
842 hildon_range_editor_new                         (void)
843 {
844     return GTK_WIDGET (g_object_new (HILDON_TYPE_RANGE_EDITOR, NULL));
845 }
846
847 /**
848  * hildon_range_editor_new_with_separator:
849  * @separator: a string that is shown between the numbers
850  *
851  * HildonRangeEditor contains two Gtk entries that accept numbers. 
852  * A separator is displayed between two entries. 
853  * CHECKME: Use '-' as a separator in the case of null separator?
854  * 
855  * Returns: pointer to a new @HildonRangeEditor widget
856  */
857 GtkWidget*
858 hildon_range_editor_new_with_separator          (const gchar *separator)
859 {
860     return GTK_WIDGET (g_object_new (HILDON_TYPE_RANGE_EDITOR,
861                 "separator", separator, NULL));
862 }
863
864 /**
865  * hildon_range_editor_set_range:
866  * @editor: the #HildonRangeEditor widget
867  * @start: range's start value 
868  * @end: range's end value
869  *
870  * Sets a range to the editor. (The current value)
871  *
872  * Sets the range of the @HildonRangeEditor widget.
873  */
874 void
875 hildon_range_editor_set_range                   (HildonRangeEditor *editor, 
876                                                  gint start,
877                                                  gint end)
878 {
879     g_return_if_fail (HILDON_IS_RANGE_EDITOR (editor));
880
881     /* Make sure that the start/end appear in the correct order */
882     hildon_range_editor_set_lower (editor, MIN (start, end));
883     hildon_range_editor_set_higher (editor, MAX (start, end));
884 }
885
886 /**
887  * hildon_range_editor_get_range:
888  * @editor: the #HildonRangeEditor widget
889  * @start: ranges start value
890  * @end: ranges end value
891  *
892  * Gets the range of the @HildonRangeEditor widget.
893  */
894 void
895 hildon_range_editor_get_range                   (HildonRangeEditor *editor, 
896                                                  gint *start,
897                                                  gint *end)
898 {
899     HildonRangeEditorPrivate *priv;
900
901     g_return_if_fail (HILDON_IS_RANGE_EDITOR (editor) && start && end);
902     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
903
904     *start = hildon_range_editor_get_lower (editor);
905     *end = hildon_range_editor_get_higher (editor);
906 }
907
908 /**
909  * hildon_range_editor_set_limits:
910  * @editor: the #HildonRangeEditor widget
911  * @start: minimum acceptable value (default: no limit)
912  * @end:   maximum acceptable value (default: no limit)
913  *
914  * Sets the range of the @HildonRangeEditor widget.
915  */
916 void
917 hildon_range_editor_set_limits                  (HildonRangeEditor *editor, 
918                                                  gint start,
919                                                  gint end)
920 {
921     /* FIXME: Setting start/end as separate steps can modify
922        the inputted range unneedlesly */
923     hildon_range_editor_set_min (editor, start);
924     hildon_range_editor_set_max (editor, end);
925 }
926
927 void
928 hildon_range_editor_set_lower                   (HildonRangeEditor *editor, 
929                                                  gint value)
930 {
931     HildonRangeEditorPrivate *priv;
932     gchar buffer[32];
933
934     g_return_if_fail (HILDON_IS_RANGE_EDITOR (editor));
935     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
936
937     g_snprintf(buffer, sizeof (buffer), "%d", 
938             CLAMP (value, priv->range_limits_start, priv->range_limits_end));
939
940     /* Update entry text with new value */
941     gtk_entry_set_text (GTK_ENTRY (priv->start_entry), buffer);
942     g_object_notify (G_OBJECT (editor), "lower");
943 }
944
945 void
946 hildon_range_editor_set_higher                  (HildonRangeEditor *editor, 
947                                                  gint value)
948 {
949     HildonRangeEditorPrivate *priv;
950     gchar buffer[32];
951
952     g_return_if_fail (HILDON_IS_RANGE_EDITOR (editor));
953     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
954
955     g_snprintf(buffer, sizeof(buffer), "%d", 
956             CLAMP(value, priv->range_limits_start, priv->range_limits_end));
957
958     /* Update entry text with new value */
959     gtk_entry_set_text (GTK_ENTRY (priv->end_entry), buffer);
960     g_object_notify (G_OBJECT (editor), "higher");
961 }
962
963 gint
964 hildon_range_editor_get_lower                   (HildonRangeEditor *editor)
965 {
966     HildonRangeEditorPrivate *priv;
967     g_return_val_if_fail (HILDON_IS_RANGE_EDITOR (editor), 0);
968     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
969     return atoi(gtk_entry_get_text(GTK_ENTRY(priv->start_entry)));
970 }
971
972 gint
973 hildon_range_editor_get_higher                  (HildonRangeEditor *editor)
974 {
975     HildonRangeEditorPrivate *priv;
976     g_return_val_if_fail (HILDON_IS_RANGE_EDITOR (editor), 0);
977     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
978     return atoi (gtk_entry_get_text(GTK_ENTRY (priv->end_entry)));
979 }
980
981 void
982 hildon_range_editor_set_min                     (HildonRangeEditor *editor, 
983                                                  gint value)
984 {
985     HildonRangeEditorPrivate *priv;
986
987     g_return_if_fail (HILDON_IS_RANGE_EDITOR (editor));
988
989     /* We can cause many properties to change */
990     g_object_freeze_notify (G_OBJECT(editor));
991     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
992     priv->range_limits_start = value;
993
994     if (priv->range_limits_end < value)
995         hildon_range_editor_set_max (editor, value);
996     /* Setting maximum applies widths and range in this case */
997     else {
998         hildon_range_editor_refresh_widths (priv);
999         hildon_range_editor_apply_current_range (editor);
1000     }
1001
1002     g_object_notify (G_OBJECT (editor), "min");
1003     g_object_thaw_notify (G_OBJECT (editor));
1004 }
1005
1006 void
1007 hildon_range_editor_set_max                     (HildonRangeEditor *editor, 
1008                                                  gint value)
1009 {
1010     HildonRangeEditorPrivate *priv;
1011
1012     g_return_if_fail (HILDON_IS_RANGE_EDITOR (editor));
1013
1014     /* We can cause many properties to change */
1015     g_object_freeze_notify (G_OBJECT (editor));
1016     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
1017     priv->range_limits_end = value;
1018
1019     if (priv->range_limits_start > value)
1020         hildon_range_editor_set_min (editor, value);
1021     /* Setting minimum applies widths and range in this case */
1022     else {
1023         hildon_range_editor_refresh_widths (priv);
1024         hildon_range_editor_apply_current_range (editor);
1025     }
1026
1027     g_object_notify (G_OBJECT (editor), "max");
1028     g_object_thaw_notify (G_OBJECT (editor));
1029 }
1030
1031 gint
1032 hildon_range_editor_get_min                     (HildonRangeEditor *editor)
1033 {
1034     HildonRangeEditorPrivate *priv;
1035     g_return_val_if_fail (HILDON_IS_RANGE_EDITOR (editor), 0);
1036     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
1037
1038     return priv->range_limits_start;
1039 }
1040
1041 gint
1042 hildon_range_editor_get_max                     (HildonRangeEditor *editor)
1043 {
1044     HildonRangeEditorPrivate *priv;
1045     g_return_val_if_fail (HILDON_IS_RANGE_EDITOR (editor), 0);
1046     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
1047
1048     return priv->range_limits_end;
1049 }
1050
1051 void
1052 hildon_range_editor_set_separator               (HildonRangeEditor *editor,
1053                                                  const gchar *separator)
1054 {
1055     HildonRangeEditorPrivate *priv;
1056     g_return_if_fail (HILDON_IS_RANGE_EDITOR (editor));
1057     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
1058
1059     gtk_label_set_text (GTK_LABEL (priv->label), separator);
1060     g_object_notify (G_OBJECT(editor), "separator");
1061 }
1062
1063 const gchar*
1064 hildon_range_editor_get_separator               (HildonRangeEditor *editor)
1065 {
1066     HildonRangeEditorPrivate *priv;
1067     g_return_val_if_fail (HILDON_IS_RANGE_EDITOR (editor), NULL);
1068     priv = HILDON_RANGE_EDITOR_GET_PRIVATE (editor);
1069
1070     return gtk_label_get_text (GTK_LABEL (priv->label));
1071 }