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