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