Use hildon API to set numeric mode
[modest] / src / hildon2 / modest-number-editor.c
1 /*
2  * This file is a part of modest
3  *
4  * Copyright (C) 2005, 2006, 2008 Nokia Corporation, all rights reserved.
5  *
6  */
7
8 /**
9  * SECTION:modest-number-editor
10  * @short_description: A widget used to enter a number within a pre-defined range.
11  *
12  * ModestNumberEditor is used to enter a number from a specific range. 
13  * There are two buttons to scroll the value in number field. 
14  * Manual input is also possible.
15  *
16  * <example>
17  * <title>ModestNumberEditor example</title>
18  * <programlisting>
19  * number_editor = modest_number_editor_new (-250, 500);
20  * modest_number_editor_set_range (number_editor, 0, 100);
21  * </programlisting>
22  * </example>
23  */
24
25 #undef                                          MODEST_DISABLE_DEPRECATED
26
27 #ifdef                                          HAVE_CONFIG_H
28 #include                                        <config.h>
29 #endif
30
31 #include                                        <string.h>
32 #include                                        <stdio.h>
33 #include                                        <stdlib.h>
34 #include                                        <libintl.h>
35 #include                                        <gdk/gdkkeysyms.h>
36
37 #include                                        "modest-number-editor.h"
38 #include                                        "modest-marshal.h"
39 #include                                        <hildon/hildon-banner.h>
40 #include                                        "modest-text-utils.h"
41
42 #define                                         _(String) dgettext("modest-libs", String)
43
44 typedef struct                                  _ModestNumberEditorPrivate ModestNumberEditorPrivate;
45
46 #define                                         MODEST_NUMBER_EDITOR_GET_PRIVATE(obj) \
47                                                 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), MODEST_TYPE_NUMBER_EDITOR, \
48                                                 ModestNumberEditorPrivate));
49
50 struct                                          _ModestNumberEditorPrivate
51 {
52     gint start; /* Minimum */
53     gint end;   /* Maximum */
54     gint default_val;
55
56     /* Timer IDs */
57     guint select_all_idle_id; /* Selection repaint hack
58                                  see modest_number_editor_select_all */
59 };
60
61
62 static void
63 modest_number_editor_class_init                 (ModestNumberEditorClass *editor_class);
64
65 static void
66 modest_number_editor_init                       (ModestNumberEditor *editor);
67
68 static gboolean
69 modest_number_editor_entry_focusout             (GtkWidget *widget, 
70                                                  GdkEventFocus *event,
71                                                  gpointer data);
72
73 static void
74 modest_number_editor_entry_changed              (GtkWidget *widget, 
75                                                  gpointer data);
76
77 static void
78 modest_number_editor_finalize                   (GObject *self);
79
80 static gboolean
81 modest_number_editor_range_error                (ModestNumberEditor *editor,
82                                                  ModestNumberEditorErrorType type);
83
84 static gboolean
85 modest_number_editor_select_all                 (ModestNumberEditor *editor);
86
87 static void
88 modest_number_editor_validate_value             (ModestNumberEditor *editor, 
89                                                  gboolean allow_intermediate);
90     
91 static void 
92 modest_number_editor_set_property               (GObject * object,
93                                                  guint prop_id,
94                                                  const GValue * value,
95                                                  GParamSpec * pspec);
96
97 static void
98 modest_number_editor_get_property               (GObject *object,
99                                                  guint prop_id,
100                                                  GValue *value, 
101                                                  GParamSpec * pspec);
102
103 enum
104 {
105     RANGE_ERROR,
106     LAST_SIGNAL
107 };
108
109 enum {
110     PROP_0,
111     PROP_VALUE
112 };
113
114 static GtkContainerClass*                       parent_class;
115
116 static guint                                    ModestNumberEditor_signal[LAST_SIGNAL] = {0};
117
118 /**
119  * modest_number_editor_get_type:
120  *
121  * Returns GType for ModestNumberEditor.
122  *
123  * Returns: ModestNumberEditor type
124  */
125 GType G_GNUC_CONST
126 modest_number_editor_get_type                   (void)
127 {
128     static GType editor_type = 0;
129
130     if (!editor_type)
131     {
132         static const GTypeInfo editor_info =
133         {
134             sizeof (ModestNumberEditorClass),
135             NULL,       /* base_init */
136             NULL,       /* base_finalize */
137             (GClassInitFunc) modest_number_editor_class_init,
138             NULL,       /* class_finalize */
139             NULL,       /* class_data */
140             sizeof (ModestNumberEditor),
141             0,  /* n_preallocs */
142             (GInstanceInitFunc) modest_number_editor_init,
143         };
144         editor_type = g_type_register_static (HILDON_TYPE_ENTRY,
145                 "ModestNumberEditor",
146                 &editor_info, 0);
147     }
148     return editor_type;
149 }
150
151 static void
152 modest_number_editor_class_init                 (ModestNumberEditorClass *editor_class)
153 {
154     GObjectClass *gobject_class = G_OBJECT_CLASS (editor_class);
155
156     g_type_class_add_private (editor_class,
157             sizeof (ModestNumberEditorPrivate));
158
159     parent_class = g_type_class_peek_parent (editor_class);
160
161     editor_class->range_error = modest_number_editor_range_error;
162
163     gobject_class->finalize                 = modest_number_editor_finalize;
164     gobject_class->set_property             = modest_number_editor_set_property;
165     gobject_class->get_property             = modest_number_editor_get_property;
166
167     /**
168      * ModestNumberEditor:value:
169      *
170      * The current value of the number editor.
171      */
172     g_object_class_install_property (gobject_class, PROP_VALUE,
173             g_param_spec_int ("value",
174                 "Value",
175                 "The current value of number editor",
176                 G_MININT,
177                 G_MAXINT,
178                 0, G_PARAM_READWRITE));
179
180     ModestNumberEditor_signal[RANGE_ERROR] =
181         g_signal_new ("range_error", MODEST_TYPE_NUMBER_EDITOR,
182                 G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET
183                 (ModestNumberEditorClass, range_error),
184                 g_signal_accumulator_true_handled, NULL,
185                 modest_marshal_BOOLEAN__ENUM,
186                 G_TYPE_BOOLEAN, 1, MODEST_TYPE_NUMBER_EDITOR_ERROR_TYPE);
187 }
188
189 static void
190 modest_number_editor_finalize                   (GObject *self)
191 {
192     ModestNumberEditorPrivate *priv;
193
194     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (self);
195     g_assert (priv);
196
197     if (priv->select_all_idle_id)
198         g_source_remove (priv->select_all_idle_id);
199
200     /* Call parent class finalize, if have one */
201     if (G_OBJECT_CLASS (parent_class)->finalize)
202         G_OBJECT_CLASS (parent_class)->finalize(self);
203 }
204
205 static void
206 modest_number_editor_init                       (ModestNumberEditor *editor)
207 {
208     ModestNumberEditorPrivate *priv;
209
210     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
211     g_assert (priv);
212
213     priv->select_all_idle_id = 0;
214
215     /* Connect child widget signals */
216     g_signal_connect (GTK_OBJECT (editor), "changed",
217             G_CALLBACK (modest_number_editor_entry_changed),
218             editor);
219
220     g_signal_connect (GTK_OBJECT (editor), "focus-out-event",
221             G_CALLBACK (modest_number_editor_entry_focusout),
222             editor);
223
224     hildon_gtk_entry_set_input_mode (GTK_ENTRY (editor), 
225                                      HILDON_GTK_INPUT_MODE_NUMERIC);
226
227     modest_number_editor_set_range (editor, G_MININT, G_MAXINT);
228 }
229
230 /* Format given number to editor field, no checks performed, all signals
231    are sent normally. */
232 static void
233 modest_number_editor_real_set_value             (ModestNumberEditor *editor, 
234                                                  gint value)
235 {
236     /* FIXME: That looks REALLY bad */
237     gchar buffer[32];
238
239     /* Update text in entry to new value */
240     g_snprintf (buffer, sizeof (buffer), "%d", value);
241     gtk_entry_set_text (GTK_ENTRY (editor), buffer);
242 }
243
244 static void
245 add_select_all_idle                             (ModestNumberEditor *editor)
246 {
247     ModestNumberEditorPrivate *priv;
248
249     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
250
251     if (! priv->select_all_idle_id)
252     {
253         priv->select_all_idle_id =
254             g_idle_add((GSourceFunc) modest_number_editor_select_all, editor);
255     }    
256 }
257
258 static void
259 modest_number_editor_validate_value             (ModestNumberEditor *editor, 
260                                                  gboolean allow_intermediate)
261 {
262     ModestNumberEditorPrivate *priv;
263     gint error_code, fixup_value;
264     const gchar *text;
265     long value;
266     gchar *tail;
267     gboolean r;
268
269     g_assert (MODEST_IS_NUMBER_EDITOR(editor));
270
271     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
272     g_assert (priv);
273
274     text = gtk_entry_get_text (GTK_ENTRY (editor));
275     error_code = -1;
276     fixup_value = priv->default_val;
277
278     if (text && text[0])
279     { 
280         /* Try to convert entry text to number */
281         value = strtol (text, &tail, 10);
282
283         /* Check if conversion succeeded */
284         if (tail[0] == 0)
285         {    
286             /* Check if value is in allowed range. This is tricky in those
287                cases when user is editing a value. 
288                For example: Range = [100, 500] and user have just inputted "4".
289                This should not lead into error message. Otherwise value is
290                resetted back to "100" and next "4" press will reset it back
291                and so on. */
292             if (allow_intermediate)
293             {
294                 /* We now have the following error cases:
295                  * If inputted value as above maximum and
296                  maximum is either positive or then maximum
297                  negative and value is positive.
298                  * If inputted value is below minimum and minimum
299                  is negative or minumum positive and value
300                  negative or zero.
301                  In all other cases situation can be fixed just by
302                  adding new numbers to the string.
303                  */
304                 if (value > priv->end && (priv->end >= 0 || (priv->end < 0 && value >= 0)))
305                 {
306                     error_code = MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED;
307                     fixup_value = priv->end;
308                 }
309                 else if (value < priv->start && (priv->start < 0 || (priv->start >= 0 && value <= 0)))
310                 {
311                     error_code = MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED;
312                     fixup_value = priv->start;
313                 }
314             }
315             else
316             {
317                 if (value > priv->end) {
318                     error_code = MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED;
319                     fixup_value = priv->end;
320                 }
321                 else if (value < priv->start) {
322                     error_code = MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED;
323                     fixup_value = priv->start;
324                 }
325             }
326         }
327         /* The only valid case when conversion can fail is when we
328            have plain '-', intermediate forms are allowed AND
329            minimum bound is negative */
330         else if (! allow_intermediate || strcmp (text, "-") != 0 || priv->start >= 0)
331             error_code = MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE;
332     }
333     else if (! allow_intermediate)
334         error_code = MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE;
335
336     if (error_code != -1)
337     {
338         /* If entry is empty and intermediate forms are nor allowed, 
339            emit error signal */
340         /* Change to default value */
341         modest_number_editor_set_value (editor, fixup_value);
342         g_signal_emit (editor, ModestNumberEditor_signal[RANGE_ERROR], 0, error_code, &r);
343         add_select_all_idle (editor);
344     }
345 }
346
347 static void 
348 modest_number_editor_entry_changed              (GtkWidget *widget, 
349                                                  gpointer data)
350 {
351     g_assert (MODEST_IS_NUMBER_EDITOR (data));
352     modest_number_editor_validate_value (MODEST_NUMBER_EDITOR (data), TRUE);
353     g_object_notify (G_OBJECT (data), "value");
354 }
355
356 static gboolean
357 modest_number_editor_entry_focusout             (GtkWidget *widget, 
358                                                  GdkEventFocus *event,
359                                                  gpointer data)
360 {
361     g_assert (MODEST_IS_NUMBER_EDITOR(data));
362
363     modest_number_editor_validate_value (MODEST_NUMBER_EDITOR(data), FALSE);
364     return FALSE;
365 }
366
367 static gboolean
368 modest_number_editor_range_error                (ModestNumberEditor *editor,
369                                                  ModestNumberEditorErrorType type)
370 {
371
372     gint min, max;
373     gchar *err_msg = NULL;
374     ModestNumberEditorPrivate *priv;
375
376     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
377     g_assert (priv);
378
379     min = priv->start;
380     max = priv->end;
381
382     /* Construct error message */
383     switch (type)
384     {
385         case MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED:
386             err_msg = g_strdup_printf (_HL("ckct_ib_maximum_value"), max, max);
387             break;
388
389         case MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED:
390             err_msg = g_strdup_printf (_HL("ckct_ib_minimum_value"), min, min);
391             break;
392
393         case MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE:
394             err_msg =
395                 g_strdup_printf (_HL("ckct_ib_set_a_value_within_range"), min, max);
396             break;
397     }
398
399     /* Infoprint error */
400     if (err_msg)
401     {
402         hildon_banner_show_information (GTK_WIDGET (GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET(editor),
403                                         GTK_TYPE_WINDOW))), NULL, err_msg);
404         g_free(err_msg);
405     }
406
407     return TRUE;
408 }
409
410 /**
411  * modest_number_editor_new:
412  * @min: minimum accepted value
413  * @max: maximum accepted value
414  * 
415  * Creates new number editor
416  *
417  * Returns: a new #ModestNumberEditor widget
418  */
419 GtkWidget*
420 modest_number_editor_new                        (gint min, 
421                                                  gint max)
422 {
423     ModestNumberEditor *editor = g_object_new (MODEST_TYPE_NUMBER_EDITOR, NULL);
424
425     /* Set user inputted range to editor */
426     modest_number_editor_set_range (editor, min, max);
427
428     return GTK_WIDGET (editor);
429 }
430
431 /**
432  * modest_number_editor_set_range:
433  * @editor: a #ModestNumberEditor widget
434  * @min: minimum accepted value
435  * @max: maximum accepted value
436  *
437  * Sets accepted number range for editor
438  */
439 void
440 modest_number_editor_set_range                  (ModestNumberEditor *editor, 
441                                                  gint min, 
442                                                  gint max)
443 {
444     ModestNumberEditorPrivate *priv;
445     gchar buffer_min[32], buffer_max[32];
446     gint a, b;
447
448     g_return_if_fail (MODEST_IS_NUMBER_EDITOR (editor));
449
450     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
451     g_assert (priv);
452
453     /* Set preferences */
454     priv->start = MIN (min, max);
455     priv->end = MAX (min, max);
456
457     /* Find maximum allowed length of value */
458     g_snprintf (buffer_min, sizeof (buffer_min), "%d", min);
459     g_snprintf (buffer_max, sizeof (buffer_max), "%d", max);
460     a = strlen (buffer_min);
461     b = strlen (buffer_max);
462
463     /* Set maximum size of entry */
464     gtk_entry_set_width_chars (GTK_ENTRY (editor), MAX (a, b));
465     modest_number_editor_set_value (editor, priv->start);
466 }
467
468 /**
469  * modest_number_editor_get_value:
470  * @editor: pointer to #ModestNumberEditor
471  *
472  * Returns: current NumberEditor value
473  */
474 gint
475 modest_number_editor_get_value                  (ModestNumberEditor *editor)
476 {
477     ModestNumberEditorPrivate *priv;
478
479     g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), 0);
480
481     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
482     g_assert (priv);
483
484     return atoi (gtk_entry_get_text (GTK_ENTRY (editor)));
485 }
486
487 /**
488  * modest_number_editor_set_value:
489  * @editor: pointer to #ModestNumberEditor
490  * @value: numeric value for number editor
491  *
492  * Sets numeric value for number editor
493  */
494 void
495 modest_number_editor_set_value                  (ModestNumberEditor *editor, 
496                                                  gint value)
497 {
498     ModestNumberEditorPrivate *priv;
499
500     g_return_if_fail (MODEST_IS_NUMBER_EDITOR (editor));
501
502     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
503     g_assert (priv);
504
505     g_return_if_fail (value <= priv->end);
506     g_return_if_fail (value >= priv->start);
507
508     priv->default_val = value;
509     modest_number_editor_real_set_value (editor, value);
510     g_object_notify (G_OBJECT(editor), "value");
511 }
512
513 /* When calling gtk_entry_set_text, the entry widget does things that can
514  * cause the whole widget to redraw. This redrawing is delayed and if any
515  * selections are made right after calling the gtk_entry_set_text the
516  * setting of the selection might seem to have no effect.
517  *
518  * If the selection is delayed with a lower priority than the redrawing,
519  * the selection should stick. Calling this function with g_idle_add should
520  * do it.
521  */
522 static gboolean
523 modest_number_editor_select_all                 (ModestNumberEditor *editor)
524 {   
525     ModestNumberEditorPrivate *priv;
526
527     g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), FALSE);
528
529     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
530     g_assert (priv);
531
532     GDK_THREADS_ENTER ();
533     gtk_editable_select_region (GTK_EDITABLE (editor), 0, -1);
534     priv->select_all_idle_id = 0;
535     GDK_THREADS_LEAVE ();
536     return FALSE;
537
538
539 static void
540 modest_number_editor_set_property               (GObject *object,
541                                                  guint prop_id,
542                                                  const GValue *value, 
543                                                  GParamSpec *pspec)
544 {
545     ModestNumberEditor *editor;
546
547     editor = MODEST_NUMBER_EDITOR (object);
548
549     switch (prop_id) {
550
551         case PROP_VALUE:
552             modest_number_editor_set_value (editor, g_value_get_int (value));
553             break;
554
555         default:
556             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
557             break;
558     }
559 }
560
561 static void
562 modest_number_editor_get_property               (GObject *object,
563                                                  guint prop_id, 
564                                                  GValue *value, 
565                                                  GParamSpec *pspec)
566 {
567     ModestNumberEditor *editor;
568
569     editor = MODEST_NUMBER_EDITOR (object);
570
571     switch (prop_id) {
572
573         case PROP_VALUE:
574             g_value_set_int(value, modest_number_editor_get_value (editor));
575             break;
576
577         default:
578             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
579             break;
580     }
581 }
582
583
584 /* enumerations from "modest-number-editor.h" */
585 GType
586 modest_number_editor_error_type_get_type (void)
587 {
588   static GType etype = 0;
589   if (etype == 0) {
590     static const GEnumValue values[] = {
591       { MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED, "MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED", "maximum-value-exceed" },
592       { MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED, "MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED", "minimum-value-exceed" },
593       { MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE, "MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE", "erroneous-value" },
594       { 0, NULL, NULL }
595     };
596     etype = g_enum_register_static ("ModestNumberEditorErrorType", values);
597   }
598   return etype;
599 }
600