2caf1648c0ea5bc03695ab2e6a0fccd4fed5efff
[hildon] / src / hildon-number-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.
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-number-editor
27  * @short_description: A widget used to enter a number within a pre-defined range
28  *
29  * HildonNumberEditor is used to enter a number from a specific range. 
30  * There are two buttons to scroll the value in number field. 
31  * Manual input is also possible.
32  */
33
34 #include <gdk/gdkkeysyms.h>
35 #include <gtk/gtk.h>
36
37 #include <string.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40
41 #include "hildon-number-editor.h"
42 #include "hildon-marshalers.h"
43 #include <hildon-widgets/gtk-infoprint.h>
44 #include "hildon-composite-widget.h"
45 #include <hildon-widgets/hildon-input-mode-hint.h>
46 #include <hildon-widgets/hildon-defines.h>
47 #include "hildon-enum-types.h"
48 #include "hildon-banner.h"
49
50 #ifdef HAVE_CONFIG_H
51 #include <config.h>
52 #endif
53
54 #include <libintl.h>
55 #define _(String) dgettext(PACKAGE, String)
56
57 /*Pixel spec defines*/
58 #define NUMBER_EDITOR_HEIGHT 30
59
60 /* Size of plus and minus buttons */
61 #define BUTTON_HEIGHT 30
62 #define BUTTON_WIDTH 30
63
64 #define HILDON_NUMBER_EDITOR_GET_PRIVATE(obj) \
65         (G_TYPE_INSTANCE_GET_PRIVATE ((obj), HILDON_TYPE_NUMBER_EDITOR, \
66         HildonNumberEditorPrivate));
67
68 typedef struct _HildonNumberEditorPrivate HildonNumberEditorPrivate;
69
70 static void
71 hildon_number_editor_class_init (HildonNumberEditorClass *editor_class);
72
73 static void
74 hildon_number_editor_init (HildonNumberEditor *editor);
75
76 static gboolean
77 hildon_number_editor_entry_focusout (GtkWidget *widget, GdkEventFocus *event,
78                                      gpointer data);
79
80 static void
81 hildon_number_editor_entry_changed (GtkWidget *widget, gpointer data);
82
83 static void
84 hildon_number_editor_size_request (GtkWidget *widget,
85                                    GtkRequisition *requisition);
86
87 static void
88 set_widget_allocation (GtkWidget *widget, GtkAllocation *alloc,
89                        const GtkAllocation *allocation);
90
91 static void
92 hildon_number_editor_size_allocate (GtkWidget *widget,
93                                     GtkAllocation *allocation);
94
95 static gboolean
96 hildon_number_editor_entry_keypress (GtkWidget *widget, GdkEventKey *event,
97                                      gpointer data);
98
99 static gboolean
100 hildon_number_editor_button_pressed (GtkWidget *widget, GdkEventButton *event,
101                                      gpointer data);
102
103 static gboolean
104 hildon_number_editor_entry_button_released (GtkWidget *widget,
105                                            GdkEventButton *event,
106                                            gpointer data);
107 static gboolean
108 hildon_number_editor_button_released (GtkWidget *widget,
109                                       GdkEvent *event,
110                                       HildonNumberEditor *editor);
111 static gboolean
112 do_mouse_timeout (HildonNumberEditor *editor);
113
114 static void
115 change_numbers (HildonNumberEditor *editor, gint update);
116
117 static void
118 hildon_number_editor_forall (GtkContainer *container, gboolean include_internals,
119                      GtkCallback callback, gpointer callback_data);
120
121 static void
122 hildon_number_editor_destroy (GtkObject *self);
123
124 static gboolean
125 hildon_number_editor_start_timer (HildonNumberEditor *editor);
126
127 static void
128 hildon_number_editor_finalize (GObject *self);
129
130 static gboolean
131 hildon_number_editor_range_error(HildonNumberEditor *editor,
132                                    HildonNumberEditorErrorType type);
133
134 static gboolean
135 hildon_number_editor_select_all (HildonNumberEditorPrivate *priv);
136
137 static void
138 hildon_number_editor_validate_value(HildonNumberEditor *editor, gboolean allow_intermediate);
139     
140 static void hildon_number_editor_set_property(GObject * object,
141                                      guint prop_id,
142                                      const GValue * value,
143                                      GParamSpec * pspec);
144
145 static void hildon_number_editor_get_property(GObject * object,
146                                      guint prop_id,
147                                      GValue * value, GParamSpec * pspec);
148
149 /* Signal indices */
150 enum
151 {
152   RANGE_ERROR,
153   LAST_SIGNAL
154 };
155
156 /* Property indices */
157 enum {
158     PROP_0,
159     PROP_VALUE
160 };
161
162 static GtkContainerClass *parent_class;
163
164 static guint HildonNumberEditor_signal[LAST_SIGNAL] = {0};
165
166 struct _HildonNumberEditorPrivate
167 {
168     /* Child widgets */
169     GtkWidget *num_entry;
170     GtkWidget *plus;
171     GtkWidget *minus;
172
173     gint start; /* Minimum */
174     gint end;   /* Maximum */
175     gint default_val;
176     gint button_type; /* Type of button pressed: 1 = plus, -1 = minus */
177
178     /* Timer IDs */
179     guint button_event_id; /* Repeat change when button is held */
180     guint select_all_idle_id; /* Selection repaint hack
181                                  see hildon_number_editor_select_all */
182 };
183
184
185 GType hildon_number_editor_get_type(void)
186 {
187     static GType editor_type = 0;
188
189     if (!editor_type)
190       {
191         static const GTypeInfo editor_info =
192           {
193             sizeof(HildonNumberEditorClass),
194             NULL,       /* base_init */
195             NULL,       /* base_finalize */
196             (GClassInitFunc) hildon_number_editor_class_init,
197             NULL,       /* class_finalize */
198             NULL,       /* class_data */
199             sizeof(HildonNumberEditor),
200             0,  /* n_preallocs */
201             (GInstanceInitFunc) hildon_number_editor_init,
202           };
203         editor_type = g_type_register_static(GTK_TYPE_CONTAINER,
204                                              "HildonNumberEditor",
205                                              &editor_info, 0);
206       }
207     return editor_type;
208 }
209
210 static void
211 hildon_number_editor_class_init(HildonNumberEditorClass * editor_class)
212 {
213     GtkContainerClass *container_class = GTK_CONTAINER_CLASS(editor_class);
214     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(editor_class);
215     GObjectClass *gobject_class = G_OBJECT_CLASS(editor_class);
216
217     g_type_class_add_private(editor_class,
218                              sizeof(HildonNumberEditorPrivate));
219
220     parent_class = g_type_class_peek_parent(editor_class);
221
222     widget_class->size_request = hildon_number_editor_size_request;
223     widget_class->size_allocate = hildon_number_editor_size_allocate;
224     widget_class->focus = hildon_composite_widget_focus;
225
226     editor_class->range_error = hildon_number_editor_range_error;
227
228     /* Because we derived our widget from GtkContainer, we should override 
229        forall method */
230     container_class->forall = hildon_number_editor_forall;
231     GTK_OBJECT_CLASS(editor_class)->destroy = hildon_number_editor_destroy;
232     gobject_class->finalize = hildon_number_editor_finalize;
233     gobject_class->set_property = hildon_number_editor_set_property;
234     gobject_class->get_property = hildon_number_editor_get_property;
235
236     g_object_class_install_property(gobject_class, PROP_VALUE,
237         g_param_spec_int("value",
238                          "Value",
239                          "The current value of number editor",
240                          G_MININT,
241                          G_MAXINT,
242                          0, G_PARAM_READWRITE));
243
244     HildonNumberEditor_signal[RANGE_ERROR] =
245       g_signal_new("range_error", HILDON_TYPE_NUMBER_EDITOR,
246                    G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET
247                    (HildonNumberEditorClass, range_error),
248                    g_signal_accumulator_true_handled, NULL,
249                    _hildon_marshal_BOOLEAN__ENUM,
250                    G_TYPE_BOOLEAN, 1, HILDON_TYPE_NUMBER_EDITOR_ERROR_TYPE);
251 }
252
253 static void
254 hildon_number_editor_forall(GtkContainer *container, gboolean include_internals,
255                             GtkCallback callback, gpointer callback_data)
256 {
257     HildonNumberEditorPrivate *priv =
258         HILDON_NUMBER_EDITOR_GET_PRIVATE(container);
259
260     g_assert(callback != NULL);              
261
262     if (!include_internals)
263         return;
264
265     /* Enumerate child widgets */
266     (*callback) (priv->minus, callback_data);
267     (*callback) (priv->num_entry, callback_data);
268     (*callback) (priv->plus, callback_data);
269 }
270
271 static void
272 hildon_number_editor_destroy(GtkObject *self)
273 {
274     HildonNumberEditorPrivate *priv;
275
276     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(self);
277
278     /* Free child widgets */
279     if (priv->minus)
280       {
281         gtk_widget_unparent(priv->minus);
282         priv->minus = NULL;
283       }
284     if (priv->num_entry)
285       {
286         gtk_widget_unparent(priv->num_entry);
287         priv->num_entry = NULL;
288       }
289     if (priv->plus)
290       {
291         gtk_widget_unparent(priv->plus);
292         priv->plus = NULL;
293       }
294
295     if (GTK_OBJECT_CLASS(parent_class)->destroy)
296         GTK_OBJECT_CLASS(parent_class)->destroy(self);
297 }
298
299 static void
300 hildon_number_editor_stop_repeat_timer(HildonNumberEditorPrivate *priv)
301 {
302     if (priv->button_event_id)
303     {
304         g_source_remove(priv->button_event_id);
305         priv->button_event_id = 0;
306     }
307 }
308
309 static void
310 hildon_number_editor_finalize (GObject *self)
311 {
312    HildonNumberEditorPrivate *priv;
313
314    priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(self);
315
316    /* Free timers */
317    hildon_number_editor_stop_repeat_timer(priv);
318
319    if (priv->select_all_idle_id)
320      g_source_remove (priv->select_all_idle_id);
321
322     /* Call parent class finalize, if have one */
323     if (G_OBJECT_CLASS (parent_class)->finalize)
324         G_OBJECT_CLASS (parent_class)->finalize(self);
325 }
326
327 static void
328 hildon_number_editor_init (HildonNumberEditor *editor)
329 {
330     HildonNumberEditorPrivate *priv;
331
332     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
333     GTK_WIDGET_SET_FLAGS(GTK_WIDGET(editor), GTK_NO_WINDOW);
334
335     /* Create child widgets */
336     priv->num_entry = gtk_entry_new();
337     priv->minus = gtk_button_new();
338     priv->plus = gtk_button_new();
339
340     gtk_widget_set_name( priv->minus, "ne-minus-button" );
341     gtk_widget_set_name( priv->plus, "ne-plus-button" );
342     gtk_widget_set_size_request( priv->minus, BUTTON_WIDTH, BUTTON_HEIGHT );
343     gtk_widget_set_size_request( priv->plus, BUTTON_WIDTH, BUTTON_HEIGHT );
344     gtk_entry_set_alignment (GTK_ENTRY(priv->num_entry), 1);
345     
346     GTK_WIDGET_UNSET_FLAGS( priv->minus, GTK_CAN_FOCUS );
347     GTK_WIDGET_UNSET_FLAGS( priv->plus, GTK_CAN_FOCUS );
348     
349     priv->button_event_id = 0;
350     priv->select_all_idle_id = 0;
351
352     gtk_widget_set_parent(priv->minus, GTK_WIDGET(editor));
353     gtk_widget_set_parent(priv->num_entry, GTK_WIDGET(editor));
354     gtk_widget_set_parent(priv->plus, GTK_WIDGET(editor));
355
356     /* Connect child widget signals */
357     g_signal_connect(GTK_OBJECT(priv->num_entry), "changed",
358                      G_CALLBACK(hildon_number_editor_entry_changed),
359                      editor);
360
361     g_signal_connect(GTK_OBJECT(priv->num_entry), "focus-out-event",
362                      G_CALLBACK(hildon_number_editor_entry_focusout),
363                      editor);
364
365     g_signal_connect(GTK_OBJECT(priv->num_entry), "key-press-event",
366                      G_CALLBACK(hildon_number_editor_entry_keypress),
367                      editor);
368
369     g_signal_connect(GTK_OBJECT(priv->num_entry), "button-release-event",
370                      G_CALLBACK(hildon_number_editor_entry_button_released),
371                      NULL);
372
373     g_signal_connect(GTK_OBJECT(priv->minus), "button-press-event",
374                      G_CALLBACK(hildon_number_editor_button_pressed),
375                      editor);
376
377     g_signal_connect(GTK_OBJECT(priv->plus), "button-press-event",
378                      G_CALLBACK(hildon_number_editor_button_pressed),
379                      editor);
380                      
381     g_signal_connect(GTK_OBJECT(priv->minus), "button-release-event",
382                      G_CALLBACK(hildon_number_editor_button_released),
383                      editor);
384
385     g_signal_connect(GTK_OBJECT(priv->plus), "button-release-event",
386                      G_CALLBACK(hildon_number_editor_button_released),
387                      editor);
388
389     g_signal_connect(GTK_OBJECT(priv->minus), "leave-notify-event",
390                      G_CALLBACK(hildon_number_editor_button_released),
391                      editor);
392
393     g_signal_connect(GTK_OBJECT(priv->plus), "leave-notify-event",
394                      G_CALLBACK(hildon_number_editor_button_released),
395                      editor);
396
397     g_object_set( G_OBJECT(priv->num_entry),
398                   "input-mode", HILDON_INPUT_MODE_HINT_NUMERIC, NULL );
399     
400     gtk_widget_show(priv->num_entry);
401     gtk_widget_show(priv->minus);
402     gtk_widget_show(priv->plus);
403
404     hildon_number_editor_set_range(editor, G_MININT, G_MAXINT);
405 }
406
407 static gboolean
408 hildon_number_editor_entry_button_released (GtkWidget *widget,
409                                            GdkEventButton *event,
410                                            gpointer data)
411 {
412   gtk_editable_select_region(GTK_EDITABLE(widget), 0, -1);
413   return FALSE;
414 }
415
416 static gboolean
417 hildon_number_editor_button_released (GtkWidget *widget, GdkEvent *event,
418                                       HildonNumberEditor *editor)
419 {
420   HildonNumberEditorPrivate *priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);  
421   hildon_number_editor_stop_repeat_timer(priv);
422   return FALSE;
423 }
424
425 /* Format given number to editor field, no checks performed, all signals
426    are sent normally. */
427 static void
428 hildon_number_editor_real_set_value (HildonNumberEditorPrivate *priv, gint value)
429 {
430     gchar buffer[32];
431   
432     /* Update text in entry to new value */
433     g_snprintf(buffer, sizeof(buffer), "%d", value);
434     gtk_entry_set_text(GTK_ENTRY(priv->num_entry), buffer);
435 }
436
437 static gboolean
438 hildon_number_editor_button_pressed (GtkWidget *widget, GdkEventButton *event,
439                                      gpointer data)
440 {
441     /* FIXME: XXX Why aren't we using hildon_number_editor_start_timer here? XXX */
442     /* Need to fetch current value from entry and increment or decrement
443        it */
444     HildonNumberEditor *editor;
445     HildonNumberEditorPrivate *priv;
446     GtkSettings *settings;
447     guint timeout;
448
449     g_assert(HILDON_IS_NUMBER_EDITOR(data));
450
451     editor = HILDON_NUMBER_EDITOR(data);
452     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
453     settings = gtk_settings_get_default();
454     g_object_get(settings, "gtk-initial-timeout", &timeout, NULL);
455
456     /* Save type of button pressed */
457     if (GTK_BUTTON(widget) == GTK_BUTTON(priv->plus))
458         priv->button_type = 1;
459     else
460         priv->button_type = -1;
461
462     /* Start repetition timer */
463     if (!priv->button_event_id)
464       {
465         do_mouse_timeout(editor);
466         priv->button_event_id = g_timeout_add (timeout,
467                                 (GSourceFunc) hildon_number_editor_start_timer,
468                                 editor);
469       }
470
471     return FALSE;
472 }
473
474 static gboolean
475 hildon_number_editor_start_timer (HildonNumberEditor *editor)
476 {
477     HildonNumberEditorPrivate *priv;
478     GtkSettings *settings;
479     guint timeout;
480
481     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
482     settings = gtk_settings_get_default();
483     g_object_get(settings, "gtk-update-timeout", &timeout, NULL);
484
485     priv->button_event_id = g_timeout_add(timeout,
486                                           (GSourceFunc) do_mouse_timeout,
487                                           editor);
488     return FALSE;
489 }
490
491 static gboolean
492 do_mouse_timeout (HildonNumberEditor *editor)
493 {
494     HildonNumberEditorPrivate *priv;
495
496     GDK_THREADS_ENTER ();
497     
498     g_assert(HILDON_IS_NUMBER_EDITOR(editor));
499
500     /* Update value based on button held */
501     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
502     change_numbers(editor, priv->button_type);
503
504     GDK_THREADS_LEAVE ();
505
506     return TRUE;
507 }
508
509 /* Changes the current number value by the amount of update
510    and verifies the result. */
511 static void
512 change_numbers (HildonNumberEditor *editor, gint update)
513 {
514     HildonNumberEditorPrivate *priv;
515     gint current_value;
516
517     g_assert(HILDON_IS_NUMBER_EDITOR(editor));
518
519     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
520     current_value = hildon_number_editor_get_value(editor);
521
522     /* We need to rerun validation by hand, since validation
523        done in "changed" callback allows intermediate values */
524     hildon_number_editor_real_set_value(priv, current_value + update);
525     hildon_number_editor_validate_value(editor, FALSE);
526     g_object_notify (G_OBJECT(editor), "value");
527 }
528
529 static void
530 add_select_all_idle (HildonNumberEditorPrivate *priv)
531 {
532   if (!priv->select_all_idle_id)
533     {
534       priv->select_all_idle_id =
535         g_idle_add((GSourceFunc) hildon_number_editor_select_all, priv);
536     }    
537 }
538
539 static void
540 hildon_number_editor_validate_value(HildonNumberEditor *editor, gboolean allow_intermediate)
541 {
542     HildonNumberEditorPrivate *priv;
543     gint error_code, fixup_value;
544     const gchar *text;
545     long value;
546     gchar *tail;
547     gboolean r;
548
549     g_assert(HILDON_IS_NUMBER_EDITOR(editor));
550
551     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
552     text = gtk_entry_get_text(GTK_ENTRY(priv->num_entry));
553     error_code = -1;
554     fixup_value = priv->default_val;
555
556     if (text && text[0])
557     { 
558       /* Try to convert entry text to number */
559       value = strtol(text, &tail, 10);
560
561       /* Check if conversion succeeded */
562       if (tail[0] == 0)
563       {    
564         /* Check if value is in allowed range. This is tricky in those
565            cases when user is editing a value. 
566            For example: Range = [100, 500] and user have just inputted "4".
567            This should not lead into error message. Otherwise value is
568            resetted back to "100" and next "4" press will reset it back
569            and so on. */
570         if (allow_intermediate)
571         {
572             /* We now have the following error cases:
573                 * If inputted value as above maximum and
574                   maximum is either positive or then maximum
575                   negative and value is positive.
576                 * If inputted value is below minimum and minimum
577                   is negative or minumum positive and value
578                   negative or zero.
579                In all other cases situation can be fixed just by
580                adding new numbers to the string.
581              */
582             if (value > priv->end && (priv->end >= 0 || (priv->end < 0 && value >= 0)))
583             {
584                 error_code = MAXIMUM_VALUE_EXCEED;
585                 fixup_value = priv->end;
586             }
587             else if (value < priv->start && (priv->start < 0 || (priv->start >= 0 && value <= 0)))
588             {
589                 error_code = MINIMUM_VALUE_EXCEED;
590                 fixup_value = priv->start;
591             }
592         }
593         else
594         {
595             if (value > priv->end) {
596                 error_code = MAXIMUM_VALUE_EXCEED;
597                 fixup_value = priv->end;
598             }
599             else if (value < priv->start) {
600                 error_code = MINIMUM_VALUE_EXCEED;
601                 fixup_value = priv->start;
602             }
603         }
604           }
605       /* The only valid case when conversion can fail is when we
606          have plain '-', intermediate forms are allowed AND
607          minimum bound is negative */
608       else if (!allow_intermediate || strcmp(text, "-") != 0 || priv->start >= 0)
609         error_code = ERRONEOUS_VALUE;
610         }
611     else if (!allow_intermediate)
612         error_code = ERRONEOUS_VALUE;
613
614     if (error_code != -1)
615     {
616         /* If entry is empty and intermediate forms are nor allowed, 
617            emit error signal */
618         /* Change to default value */
619         hildon_number_editor_set_value(editor, fixup_value);
620         g_signal_emit(editor, HildonNumberEditor_signal[RANGE_ERROR], 
621                                   0, error_code, &r);
622         add_select_all_idle(priv);
623     }
624 }
625
626 static void 
627 hildon_number_editor_entry_changed(GtkWidget *widget, gpointer data)
628 {
629     g_assert(HILDON_IS_NUMBER_EDITOR(data));
630     hildon_number_editor_validate_value(HILDON_NUMBER_EDITOR(data), TRUE);
631     g_object_notify (G_OBJECT(data), "value");
632 }
633
634 static void
635 hildon_number_editor_size_request (GtkWidget *widget,
636                                   GtkRequisition *requisition)
637 {
638     HildonNumberEditor *editor;
639     HildonNumberEditorPrivate *priv;
640     GtkRequisition req;
641
642     editor = HILDON_NUMBER_EDITOR(widget);
643     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
644
645     /* Requested size is size of all child widgets plus border space */
646     gtk_widget_size_request(priv->minus, &req);
647     requisition->width = req.width;
648
649     gtk_widget_size_request(priv->num_entry, &req);
650     requisition->width += req.width;
651
652     gtk_widget_size_request(priv->plus, &req);
653     requisition->width += req.width;
654
655     requisition->width += HILDON_MARGIN_DEFAULT * 2;
656
657     /* FIXME: XXX Height is fixed */
658     requisition->height = NUMBER_EDITOR_HEIGHT;
659 }
660
661 /* Update @alloc->width so widget fits, update @alloc->x to point to free space */
662 static void
663 set_widget_allocation (GtkWidget *widget, GtkAllocation *alloc,
664                        const GtkAllocation *allocation)
665 {
666     GtkRequisition child_requisition;
667
668     gtk_widget_get_child_requisition(widget, &child_requisition);
669
670     /* Fit to widget width */
671     if (allocation->width + allocation->x >
672         alloc->x + child_requisition.width)
673         alloc->width = child_requisition.width;
674     else
675       {
676         alloc->width = allocation->width - (alloc->x - allocation->x);
677         if (alloc->width < 0)
678             alloc->width = 0;
679       }
680
681     gtk_widget_size_allocate(widget, alloc);
682     /* Update x position */
683     alloc->x += alloc->width;
684 }
685
686 static void
687 hildon_number_editor_size_allocate (GtkWidget *widget,
688                                     GtkAllocation *allocation)
689 {
690   HildonNumberEditor *editor;
691   HildonNumberEditorPrivate *priv;
692   GtkAllocation alloc;
693
694   editor = HILDON_NUMBER_EDITOR(widget);
695   priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
696
697   widget->allocation = *allocation;
698
699   /* Add upper border */
700   alloc.y = widget->allocation.y + widget->style->ythickness;
701
702   /* Fix height */
703   if (widget->allocation.height > NUMBER_EDITOR_HEIGHT)
704     {
705       alloc.height = NUMBER_EDITOR_HEIGHT - widget->style->ythickness * 2;
706       alloc.y += (widget->allocation.height - NUMBER_EDITOR_HEIGHT) / 2;
707     }
708   else
709       alloc.height = widget->allocation.height - widget->style->ythickness * 2;
710   
711   if (alloc.height < 0)
712     alloc.height = 0;
713
714   /* Add left border */
715   alloc.x = allocation->x + widget->style->xthickness;
716
717   /* Allocate positions for widgets (left-to-right) */
718   set_widget_allocation(priv->minus, &alloc, &widget->allocation);
719   alloc.x += HILDON_MARGIN_DEFAULT;
720
721   set_widget_allocation(priv->num_entry, &alloc, &widget->allocation);
722   alloc.x += HILDON_MARGIN_DEFAULT;
723
724   set_widget_allocation(priv->plus, &alloc, &widget->allocation);
725 }
726
727 static gboolean
728 hildon_number_editor_entry_focusout (GtkWidget *widget, GdkEventFocus *event,
729                                      gpointer data)
730 {
731     g_assert(HILDON_IS_NUMBER_EDITOR(data));
732     hildon_number_editor_validate_value(HILDON_NUMBER_EDITOR(data), FALSE);
733     return FALSE;
734 }
735
736 static gboolean
737 hildon_number_editor_entry_keypress (GtkWidget *widget, GdkEventKey *event,
738                                      gpointer data)
739 {
740     GtkEditable *editable;
741     gint cursor_pos;
742
743     g_assert(HILDON_IS_NUMBER_EDITOR(data));
744
745     editable = GTK_EDITABLE(widget);
746     cursor_pos = gtk_editable_get_position(editable);
747     
748     switch (event->keyval)
749     {
750         case GDK_Left:
751             /* If the cursor is on the left, try to decrement */
752             if (cursor_pos == 0) {
753                 change_numbers(HILDON_NUMBER_EDITOR(data), -1);
754                 return TRUE;
755             }
756             break;
757
758         case GDK_Right:
759             /* If the cursor is on the right, try to increment */
760             if (cursor_pos >= g_utf8_strlen(gtk_entry_get_text(GTK_ENTRY(widget)), -1))
761             {
762                 change_numbers(HILDON_NUMBER_EDITOR(data), 1);
763                 gtk_editable_set_position(editable, cursor_pos);
764                 return TRUE;
765             }
766             break;
767
768         default:
769             break;
770     };
771
772     return FALSE;
773 }
774
775 static gboolean
776 hildon_number_editor_range_error(HildonNumberEditor *editor,
777                                    HildonNumberEditorErrorType type)
778 {
779
780   gint min, max;
781   gchar *err_msg = NULL;
782   HildonNumberEditorPrivate *priv;
783
784   priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
785   min = priv->start;
786   max = priv->end;
787
788   /* Construct error message */
789   switch (type)
790     {
791     case MAXIMUM_VALUE_EXCEED:
792       err_msg = g_strdup_printf(_("ckct_ib_maximum_value"), max, max);
793       break;
794     case MINIMUM_VALUE_EXCEED:
795       err_msg = g_strdup_printf(_("ckct_ib_minimum_value"), min, min);
796       break;
797     case ERRONEOUS_VALUE:
798       err_msg =
799         g_strdup_printf(_("ckct_ib_set_a_value_within_range"), min, max);
800       break;
801     }
802   
803   /* Infoprint error */
804   if (err_msg)
805     {
806       hildon_banner_show_information(GTK_WIDGET (GTK_WINDOW(gtk_widget_get_ancestor(GTK_WIDGET(editor),
807                                      GTK_TYPE_WINDOW))), NULL, err_msg);
808       g_free(err_msg);
809     }
810
811   return TRUE;
812 }
813
814
815 /**
816  * hildon_number_editor_new:
817  * @min: minimum accepted value
818  * @max: maximum accepted value
819  * 
820  * Creates new number editor
821  *
822  * Returns: a new #HildonNumberEditor widget
823  */
824 GtkWidget *
825 hildon_number_editor_new (gint min, gint max)
826 {
827     HildonNumberEditor *editor =
828         g_object_new(HILDON_TYPE_NUMBER_EDITOR, NULL);
829
830     /* Set user inputted range to editor */
831     hildon_number_editor_set_range(editor, min, max);
832
833     return GTK_WIDGET(editor);
834 }
835
836 /**
837  * hildon_number_editor_set_range:
838  * @editor: a #HildonNumberEditor widget
839  * @min: minimum accepted value
840  * @max: maximum accepted value
841  *
842  * Sets accepted number range for editor
843  */
844 void
845 hildon_number_editor_set_range (HildonNumberEditor *editor, gint min, gint max)
846 {
847     HildonNumberEditorPrivate *priv;
848     gchar buffer_min[32], buffer_max[32];
849     gint a, b;
850
851     g_return_if_fail(HILDON_IS_NUMBER_EDITOR(editor));
852
853     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
854
855     /* Set preferences */
856     priv->start = MIN(min, max);
857     priv->end = MAX(min, max);
858
859     /* Find maximum allowed length of value */
860     g_snprintf(buffer_min, sizeof(buffer_min), "%d", min);
861     g_snprintf(buffer_max, sizeof(buffer_max), "%d", max);
862     a = strlen(buffer_min);
863     b = strlen(buffer_max);
864
865     /* Set maximum size of entry */
866     gtk_entry_set_width_chars(GTK_ENTRY(priv->num_entry), MAX(a, b));
867     hildon_number_editor_set_value(editor, priv->start);
868 }
869
870 /**
871  * hildon_number_editor_get_value:
872  * @editor: pointer to #HildonNumberEditor
873  *
874  * Returns: current NumberEditor value
875  */
876 gint
877 hildon_number_editor_get_value (HildonNumberEditor *editor)
878 {
879     HildonNumberEditorPrivate *priv;
880
881     g_return_val_if_fail(HILDON_IS_NUMBER_EDITOR(editor), 0);
882
883     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
884     return atoi(gtk_entry_get_text(GTK_ENTRY(priv->num_entry)));
885 }
886
887 /**
888  * hildon_number_editor_set_value:
889  * @editor: pointer to #HildonNumberEditor
890  * @value: numeric value for number editor
891  *
892  * Sets numeric value for number editor
893  */
894 void
895 hildon_number_editor_set_value (HildonNumberEditor *editor, gint value)
896 {
897     HildonNumberEditorPrivate *priv;
898
899     g_return_if_fail(HILDON_IS_NUMBER_EDITOR(editor));
900
901     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
902
903     g_return_if_fail(value <= priv->end);
904     g_return_if_fail(value >= priv->start);
905
906     priv->default_val = value;
907     hildon_number_editor_real_set_value(priv, value);
908     g_object_notify (G_OBJECT(editor), "value");
909 }
910
911 /* When calling gtk_entry_set_text, the entry widget does things that can
912  * cause the whole widget to redraw. This redrawing is delayed and if any
913  * selections are made right after calling the gtk_entry_set_text the
914  * setting of the selection might seem to have no effect.
915  *
916  * If the selection is delayed with a lower priority than the redrawing,
917  * the selection should stick. Calling this function with g_idle_add should
918  * do it.
919  */
920 static gboolean
921 hildon_number_editor_select_all (HildonNumberEditorPrivate *priv)
922 {   
923     GDK_THREADS_ENTER ();
924     gtk_editable_select_region(GTK_EDITABLE(priv->num_entry), 0, -1);
925     priv->select_all_idle_id = 0;
926     GDK_THREADS_LEAVE ();
927     return FALSE;
928
929
930 static void
931 hildon_number_editor_set_property(GObject * object,
932                          guint prop_id,
933                          const GValue * value, GParamSpec * pspec)
934 {
935     HildonNumberEditor *editor;
936
937     editor = HILDON_NUMBER_EDITOR(object);
938
939     switch (prop_id) {
940     case PROP_VALUE:
941         hildon_number_editor_set_value(editor, g_value_get_int(value));
942         break;
943     default:
944         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
945         break;
946     }
947 }
948
949 static void
950 hildon_number_editor_get_property(GObject * object,
951                          guint prop_id, GValue * value, GParamSpec * pspec)
952 {
953     HildonNumberEditor *editor;
954
955     editor = HILDON_NUMBER_EDITOR(object);
956
957     switch (prop_id) {
958     case PROP_VALUE:
959         g_value_set_int(value, hildon_number_editor_get_value(editor));
960         break;
961     default:
962         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
963         break;
964     }
965 }