7e9625e3f04b1402ca2318e9c885aa2317c4a611
[hildon] / hildon-widgets / hildon-number-editor.c
1 /*
2  * This file is part of hildon-libs
3  *
4  * Copyright (C) 2005 Nokia Corporation.
5  *
6  * Contact: Luc Pionchon <luc.pionchon@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; either version 2.1 of
11  * the License, or (at your option) 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-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     g_assert(HILDON_IS_NUMBER_EDITOR(editor));
496
497     /* Update value based on button held */
498     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
499     change_numbers(editor, priv->button_type);
500
501     return TRUE;
502 }
503
504 /* Changes the current number value by the amount of update
505    and verifies the result. */
506 static void
507 change_numbers (HildonNumberEditor *editor, gint update)
508 {
509     HildonNumberEditorPrivate *priv;
510     gint current_value;
511
512     g_assert(HILDON_IS_NUMBER_EDITOR(editor));
513
514     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
515     current_value = hildon_number_editor_get_value(editor);
516
517     /* We need to rerun validation by hand, since validation
518        done in "changed" callback allows intermediate values */
519     hildon_number_editor_real_set_value(priv, current_value + update);
520     hildon_number_editor_validate_value(editor, FALSE);
521 }
522
523 static void
524 add_select_all_idle (HildonNumberEditorPrivate *priv)
525 {
526   if (!priv->select_all_idle_id)
527     {
528       priv->select_all_idle_id =
529         g_idle_add((GSourceFunc) hildon_number_editor_select_all, priv);
530     }    
531 }
532
533 static void
534 hildon_number_editor_validate_value(HildonNumberEditor *editor, gboolean allow_intermediate)
535 {
536     HildonNumberEditorPrivate *priv;
537     gint error_code, fixup_value;
538     const gchar *text;
539     long value;
540     gchar *tail;
541     gboolean r;
542
543     g_assert(HILDON_IS_NUMBER_EDITOR(editor));
544
545     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
546     text = gtk_entry_get_text(GTK_ENTRY(priv->num_entry));
547     error_code = -1;
548     fixup_value = priv->default_val;
549
550     if (text && text[0])
551     { 
552       /* Try to convert entry text to number */
553       value = strtol(text, &tail, 10);
554
555       /* Check if conversion succeeded */
556       if (tail[0] == 0)
557       {    
558         /* Check if value is in allowed range. This is tricky in those
559            cases when user is editing a value. 
560            For example: Range = [100, 500] and user have just inputted "4".
561            This should not lead into error message. Otherwise value is
562            resetted back to "100" and next "4" press will reset it back
563            and so on. */
564         if (allow_intermediate)
565         {
566             /* We now have the following error cases:
567                 * If inputted value as above maximum and
568                   maximum is either positive or then maximum
569                   negative and value is positive.
570                 * If inputted value is below minimum and minimum
571                   is negative or minumum positive and value
572                   negative.
573                In all other cases situation can be fixed just by
574                adding new numbers to the string.
575              */
576             if (value > priv->end && (priv->end >= 0 || (priv->end < 0 && value >= 0)))
577             {
578                 error_code = MAXIMUM_VALUE_EXCEED;
579                 fixup_value = priv->end;
580             }
581             else if (value < priv->start && (priv->start < 0 || (priv->start >= 0 && value < 0)))
582             {
583                 error_code = MINIMUM_VALUE_EXCEED;
584                 fixup_value = priv->start;
585             }
586         }
587         else
588         {
589             if (value > priv->end) {
590                 error_code = MAXIMUM_VALUE_EXCEED;
591                 fixup_value = priv->end;
592             }
593             else if (value < priv->start) {
594                 error_code = MINIMUM_VALUE_EXCEED;
595                 fixup_value = priv->start;
596             }
597         }
598           }
599       /* The only valid case when conversion can fail is when we
600          have plain '-', intermediate forms are allowed AND
601          minimum bound is negative */
602       else if (!allow_intermediate || strcmp(text, "-") != 0 || priv->start >= 0)
603         error_code = ERRONEOUS_VALUE;
604         }
605     else if (!allow_intermediate)
606         error_code = ERRONEOUS_VALUE;
607
608     if (error_code != -1)
609     {
610         /* If entry is empty and intermediate forms are nor allowed, 
611            emit error signal */
612         g_signal_emit(editor, HildonNumberEditor_signal[RANGE_ERROR], 
613                       0, error_code, &r);
614         /* Change to default value */
615         hildon_number_editor_set_value(editor, fixup_value);
616         add_select_all_idle(priv);
617     }
618 }
619
620 static void 
621 hildon_number_editor_entry_changed(GtkWidget *widget, gpointer data)
622 {
623     g_assert(HILDON_IS_NUMBER_EDITOR(data));
624     hildon_number_editor_validate_value(HILDON_NUMBER_EDITOR(data), TRUE);
625 }
626
627 static void
628 hildon_number_editor_size_request (GtkWidget *widget,
629                                   GtkRequisition *requisition)
630 {
631     HildonNumberEditor *editor;
632     HildonNumberEditorPrivate *priv;
633     GtkRequisition req;
634
635     editor = HILDON_NUMBER_EDITOR(widget);
636     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
637
638     /* Requested size is size of all child widgets plus border space */
639     gtk_widget_size_request(priv->minus, &req);
640     requisition->width = req.width;
641
642     gtk_widget_size_request(priv->num_entry, &req);
643     requisition->width += req.width;
644
645     gtk_widget_size_request(priv->plus, &req);
646     requisition->width += req.width;
647
648     requisition->width += HILDON_MARGIN_DEFAULT * 2;
649
650     /* FIXME: XXX Height is fixed */
651     requisition->height = NUMBER_EDITOR_HEIGHT;
652 }
653
654 /* Update @alloc->width so widget fits, update @alloc->x to point to free space */
655 static void
656 set_widget_allocation (GtkWidget *widget, GtkAllocation *alloc,
657                        const GtkAllocation *allocation)
658 {
659     GtkRequisition child_requisition;
660
661     gtk_widget_get_child_requisition(widget, &child_requisition);
662
663     /* Fit to widget width */
664     if (allocation->width + allocation->x >
665         alloc->x + child_requisition.width)
666         alloc->width = child_requisition.width;
667     else
668       {
669         alloc->width = allocation->width - (alloc->x - allocation->x);
670         if (alloc->width < 0)
671             alloc->width = 0;
672       }
673
674     gtk_widget_size_allocate(widget, alloc);
675     /* Update x position */
676     alloc->x += alloc->width;
677 }
678
679 static void
680 hildon_number_editor_size_allocate (GtkWidget *widget,
681                                     GtkAllocation *allocation)
682 {
683   HildonNumberEditor *editor;
684   HildonNumberEditorPrivate *priv;
685   GtkAllocation alloc;
686
687   editor = HILDON_NUMBER_EDITOR(widget);
688   priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
689
690   widget->allocation = *allocation;
691
692   /* Add upper border */
693   alloc.y = widget->allocation.y + widget->style->ythickness;
694
695   /* Fix height */
696   if (widget->allocation.height > NUMBER_EDITOR_HEIGHT)
697     {
698       alloc.height = NUMBER_EDITOR_HEIGHT - widget->style->ythickness * 2;
699       alloc.y += (widget->allocation.height - NUMBER_EDITOR_HEIGHT) / 2;
700     }
701   else
702       alloc.height = widget->allocation.height - widget->style->ythickness * 2;
703   
704   if (alloc.height < 0)
705     alloc.height = 0;
706
707   /* Add left border */
708   alloc.x = allocation->x + widget->style->xthickness;
709
710   /* Allocate positions for widgets (left-to-right) */
711   set_widget_allocation(priv->minus, &alloc, &widget->allocation);
712   alloc.x += HILDON_MARGIN_DEFAULT;
713
714   set_widget_allocation(priv->num_entry, &alloc, &widget->allocation);
715   alloc.x += HILDON_MARGIN_DEFAULT;
716
717   set_widget_allocation(priv->plus, &alloc, &widget->allocation);
718 }
719
720 static gboolean
721 hildon_number_editor_entry_focusout (GtkWidget *widget, GdkEventFocus *event,
722                                      gpointer data)
723 {
724     g_assert(HILDON_IS_NUMBER_EDITOR(data));
725     hildon_number_editor_validate_value(HILDON_NUMBER_EDITOR(data), FALSE);
726     return FALSE;
727 }
728
729 static gboolean
730 hildon_number_editor_entry_keypress (GtkWidget *widget, GdkEventKey *event,
731                                      gpointer data)
732 {
733     GtkEditable *editable;
734     gint cursor_pos;
735
736     g_assert(HILDON_IS_NUMBER_EDITOR(data));
737
738     editable = GTK_EDITABLE(widget);
739     cursor_pos = gtk_editable_get_position(editable);
740     
741     switch (event->keyval)
742     {
743         case GDK_Left:
744             /* If the cursor is on the left, try to decrement */
745             if (cursor_pos == 0) {
746                 change_numbers(HILDON_NUMBER_EDITOR(data), -1);
747                 return TRUE;
748             }
749             break;
750
751         case GDK_Right:
752             /* If the cursor is on the right, try to increment */
753             if (cursor_pos >= g_utf8_strlen(gtk_entry_get_text(GTK_ENTRY(widget)), -1))
754             {
755                 change_numbers(HILDON_NUMBER_EDITOR(data), 1);
756                 gtk_editable_set_position(editable, cursor_pos);
757                 return TRUE;
758             }
759             break;
760
761         default:
762             break;
763     };
764
765     return FALSE;
766 }
767
768 static gboolean
769 hildon_number_editor_range_error(HildonNumberEditor *editor,
770                                    HildonNumberEditorErrorType type)
771 {
772
773   gint min, max;
774   gchar *err_msg = NULL;
775   HildonNumberEditorPrivate *priv;
776
777   priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
778   min = priv->start;
779   max = priv->end;
780
781   /* Construct error message */
782   switch (type)
783     {
784     case MAXIMUM_VALUE_EXCEED:
785       err_msg = g_strdup_printf(_("ckct_ib_maximum_value"), max, max);
786       break;
787     case MINIMUM_VALUE_EXCEED:
788       err_msg = g_strdup_printf(_("ckct_ib_minimum_value"), min, min);
789       break;
790     case ERRONEOUS_VALUE:
791       err_msg =
792         g_strdup_printf(_("ckct_ib_set_a_value_within_range"), min, max);
793       break;
794     }
795   
796   /* Infoprint error */
797   if (err_msg)
798     {
799       gtk_infoprint(GTK_WINDOW(gtk_widget_get_ancestor(GTK_WIDGET(editor),
800                                                        GTK_TYPE_WINDOW)), err_msg);
801       g_free(err_msg);
802     }
803
804   return TRUE;
805 }
806
807
808 /**
809  * hildon_number_editor_new:
810  * @min: minimum accepted value
811  * @max: maximum accepted value
812  * 
813  * Creates new number editor
814  *
815  * Returns: a new #HildonNumberEditor widget
816  */
817 GtkWidget *
818 hildon_number_editor_new (gint min, gint max)
819 {
820     HildonNumberEditor *editor =
821         g_object_new(HILDON_TYPE_NUMBER_EDITOR, NULL);
822
823     /* Set user inputted range to editor */
824     hildon_number_editor_set_range(editor, min, max);
825
826     return GTK_WIDGET(editor);
827 }
828
829 /**
830  * hildon_number_editor_set_range:
831  * @editor: a #HildonNumberEditor widget
832  * @min: minimum accepted value
833  * @max: maximum accepted value
834  *
835  * Sets accepted number range for editor
836  */
837 void
838 hildon_number_editor_set_range (HildonNumberEditor *editor, gint min, gint max)
839 {
840     HildonNumberEditorPrivate *priv;
841     gchar buffer_min[32], buffer_max[32];
842     gint a, b;
843
844     g_return_if_fail(HILDON_IS_NUMBER_EDITOR(editor));
845
846     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
847
848     /* Set preferences */
849     priv->start = MIN(min, max);
850     priv->end = MAX(min, max);
851
852     /* Find maximum allowed length of value */
853     g_snprintf(buffer_min, sizeof(buffer_min), "%d", min);
854     g_snprintf(buffer_max, sizeof(buffer_max), "%d", max);
855     a = strlen(buffer_min);
856     b = strlen(buffer_max);
857
858     /* Set maximum size of entry */
859     gtk_entry_set_width_chars(GTK_ENTRY(priv->num_entry), MAX(a, b));
860     hildon_number_editor_set_value(editor, priv->start);
861 }
862
863 /**
864  * hildon_number_editor_get_value:
865  * @editor: pointer to #HildonNumberEditor
866  *
867  * Returns: current NumberEditor value
868  */
869 gint
870 hildon_number_editor_get_value (HildonNumberEditor *editor)
871 {
872     HildonNumberEditorPrivate *priv;
873
874     g_return_val_if_fail(HILDON_IS_NUMBER_EDITOR(editor), 0);
875
876     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
877     return atoi(gtk_entry_get_text(GTK_ENTRY(priv->num_entry)));
878 }
879
880 /**
881  * hildon_number_editor_set_value:
882  * @editor: pointer to #HildonNumberEditor
883  * @value: numeric value for number editor
884  *
885  * Sets numeric value for number editor
886  */
887 void
888 hildon_number_editor_set_value (HildonNumberEditor *editor, gint value)
889 {
890     HildonNumberEditorPrivate *priv;
891
892     g_return_if_fail(HILDON_IS_NUMBER_EDITOR(editor));
893
894     priv = HILDON_NUMBER_EDITOR_GET_PRIVATE(editor);
895
896     g_return_if_fail(value <= priv->end);
897     g_return_if_fail(value >= priv->start);
898
899     priv->default_val = value;
900     hildon_number_editor_real_set_value(priv, value);
901     g_object_notify (G_OBJECT(editor), "value");
902 }
903
904 /* When calling gtk_entry_set_text, the entry widget does things that can
905  * cause the whole widget to redraw. This redrawing is delayed and if any
906  * selections are made right after calling the gtk_entry_set_text the
907  * setting of the selection might seem to have no effect.
908  *
909  * If the selection is delayed with a lower priority than the redrawing,
910  * the selection should stick. Calling this function with g_idle_add should
911  * do it.
912  */
913 static gboolean
914 hildon_number_editor_select_all (HildonNumberEditorPrivate *priv)
915 {   
916     gtk_editable_select_region(GTK_EDITABLE(priv->num_entry), 0, -1);
917     priv->select_all_idle_id = 0;
918     return FALSE;
919
920
921 static void
922 hildon_number_editor_set_property(GObject * object,
923                          guint prop_id,
924                          const GValue * value, GParamSpec * pspec)
925 {
926     HildonNumberEditor *editor;
927
928     editor = HILDON_NUMBER_EDITOR(object);
929
930     switch (prop_id) {
931     case PROP_VALUE:
932         hildon_number_editor_set_value(editor, g_value_get_int(value));
933         break;
934     default:
935         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
936         break;
937     }
938 }
939
940 static void
941 hildon_number_editor_get_property(GObject * object,
942                          guint prop_id, GValue * value, GParamSpec * pspec)
943 {
944     HildonNumberEditor *editor;
945
946     editor = HILDON_NUMBER_EDITOR(object);
947
948     switch (prop_id) {
949     case PROP_VALUE:
950         g_value_set_int(value, hildon_number_editor_get_value(editor));
951         break;
952     default:
953         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
954         break;
955     }
956 }