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