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