086b636886b50f1798596b58b639a755b2536adc
[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
225     g_object_set (G_OBJECT (editor),
226             "hildon-input-mode", HILDON_GTK_INPUT_MODE_NUMERIC, NULL);
227
228     modest_number_editor_set_range (editor, G_MININT, G_MAXINT);
229 }
230
231 /* Format given number to editor field, no checks performed, all signals
232    are sent normally. */
233 static void
234 modest_number_editor_real_set_value             (ModestNumberEditor *editor, 
235                                                  gint value)
236 {
237     /* FIXME: That looks REALLY bad */
238     gchar buffer[32];
239
240     /* Update text in entry to new value */
241     g_snprintf (buffer, sizeof (buffer), "%d", value);
242     gtk_entry_set_text (GTK_ENTRY (editor), buffer);
243 }
244
245 static void
246 add_select_all_idle                             (ModestNumberEditor *editor)
247 {
248     ModestNumberEditorPrivate *priv;
249
250     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
251
252     if (! priv->select_all_idle_id)
253     {
254         priv->select_all_idle_id =
255             g_idle_add((GSourceFunc) modest_number_editor_select_all, editor);
256     }    
257 }
258
259 static void
260 modest_number_editor_validate_value             (ModestNumberEditor *editor, 
261                                                  gboolean allow_intermediate)
262 {
263     ModestNumberEditorPrivate *priv;
264     gint error_code, fixup_value;
265     const gchar *text;
266     long value;
267     gchar *tail;
268     gboolean r;
269
270     g_assert (MODEST_IS_NUMBER_EDITOR(editor));
271
272     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
273     g_assert (priv);
274
275     text = gtk_entry_get_text (GTK_ENTRY (editor));
276     error_code = -1;
277     fixup_value = priv->default_val;
278
279     if (text && text[0])
280     { 
281         /* Try to convert entry text to number */
282         value = strtol (text, &tail, 10);
283
284         /* Check if conversion succeeded */
285         if (tail[0] == 0)
286         {    
287             /* Check if value is in allowed range. This is tricky in those
288                cases when user is editing a value. 
289                For example: Range = [100, 500] and user have just inputted "4".
290                This should not lead into error message. Otherwise value is
291                resetted back to "100" and next "4" press will reset it back
292                and so on. */
293             if (allow_intermediate)
294             {
295                 /* We now have the following error cases:
296                  * If inputted value as above maximum and
297                  maximum is either positive or then maximum
298                  negative and value is positive.
299                  * If inputted value is below minimum and minimum
300                  is negative or minumum positive and value
301                  negative or zero.
302                  In all other cases situation can be fixed just by
303                  adding new numbers to the string.
304                  */
305                 if (value > priv->end && (priv->end >= 0 || (priv->end < 0 && value >= 0)))
306                 {
307                     error_code = MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED;
308                     fixup_value = priv->end;
309                 }
310                 else if (value < priv->start && (priv->start < 0 || (priv->start >= 0 && value <= 0)))
311                 {
312                     error_code = MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED;
313                     fixup_value = priv->start;
314                 }
315             }
316             else
317             {
318                 if (value > priv->end) {
319                     error_code = MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED;
320                     fixup_value = priv->end;
321                 }
322                 else if (value < priv->start) {
323                     error_code = MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED;
324                     fixup_value = priv->start;
325                 }
326             }
327         }
328         /* The only valid case when conversion can fail is when we
329            have plain '-', intermediate forms are allowed AND
330            minimum bound is negative */
331         else if (! allow_intermediate || strcmp (text, "-") != 0 || priv->start >= 0)
332             error_code = MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE;
333     }
334     else if (! allow_intermediate)
335         error_code = MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE;
336
337     if (error_code != -1)
338     {
339         /* If entry is empty and intermediate forms are nor allowed, 
340            emit error signal */
341         /* Change to default value */
342         modest_number_editor_set_value (editor, fixup_value);
343         g_signal_emit (editor, ModestNumberEditor_signal[RANGE_ERROR], 0, error_code, &r);
344         add_select_all_idle (editor);
345     }
346 }
347
348 static void 
349 modest_number_editor_entry_changed              (GtkWidget *widget, 
350                                                  gpointer data)
351 {
352     g_assert (MODEST_IS_NUMBER_EDITOR (data));
353     modest_number_editor_validate_value (MODEST_NUMBER_EDITOR (data), TRUE);
354     g_object_notify (G_OBJECT (data), "value");
355 }
356
357 static gboolean
358 modest_number_editor_entry_focusout             (GtkWidget *widget, 
359                                                  GdkEventFocus *event,
360                                                  gpointer data)
361 {
362     g_assert (MODEST_IS_NUMBER_EDITOR(data));
363
364     modest_number_editor_validate_value (MODEST_NUMBER_EDITOR(data), FALSE);
365     return FALSE;
366 }
367
368 static gboolean
369 modest_number_editor_range_error                (ModestNumberEditor *editor,
370                                                  ModestNumberEditorErrorType type)
371 {
372
373     gint min, max;
374     gchar *err_msg = NULL;
375     ModestNumberEditorPrivate *priv;
376
377     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
378     g_assert (priv);
379
380     min = priv->start;
381     max = priv->end;
382
383     /* Construct error message */
384     switch (type)
385     {
386         case MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED:
387             err_msg = g_strdup_printf (_HL("ckct_ib_maximum_value"), max, max);
388             break;
389
390         case MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED:
391             err_msg = g_strdup_printf (_HL("ckct_ib_minimum_value"), min, min);
392             break;
393
394         case MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE:
395             err_msg =
396                 g_strdup_printf (_HL("ckct_ib_set_a_value_within_range"), min, max);
397             break;
398     }
399
400     /* Infoprint error */
401     if (err_msg)
402     {
403         hildon_banner_show_information (GTK_WIDGET (GTK_WINDOW (gtk_widget_get_ancestor (GTK_WIDGET(editor),
404                                         GTK_TYPE_WINDOW))), NULL, err_msg);
405         g_free(err_msg);
406     }
407
408     return TRUE;
409 }
410
411 /**
412  * modest_number_editor_new:
413  * @min: minimum accepted value
414  * @max: maximum accepted value
415  * 
416  * Creates new number editor
417  *
418  * Returns: a new #ModestNumberEditor widget
419  */
420 GtkWidget*
421 modest_number_editor_new                        (gint min, 
422                                                  gint max)
423 {
424     ModestNumberEditor *editor = g_object_new (MODEST_TYPE_NUMBER_EDITOR, NULL);
425
426     /* Set user inputted range to editor */
427     modest_number_editor_set_range (editor, min, max);
428
429     return GTK_WIDGET (editor);
430 }
431
432 /**
433  * modest_number_editor_set_range:
434  * @editor: a #ModestNumberEditor widget
435  * @min: minimum accepted value
436  * @max: maximum accepted value
437  *
438  * Sets accepted number range for editor
439  */
440 void
441 modest_number_editor_set_range                  (ModestNumberEditor *editor, 
442                                                  gint min, 
443                                                  gint max)
444 {
445     ModestNumberEditorPrivate *priv;
446     gchar buffer_min[32], buffer_max[32];
447     gint a, b;
448
449     g_return_if_fail (MODEST_IS_NUMBER_EDITOR (editor));
450
451     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
452     g_assert (priv);
453
454     /* Set preferences */
455     priv->start = MIN (min, max);
456     priv->end = MAX (min, max);
457
458     /* Find maximum allowed length of value */
459     g_snprintf (buffer_min, sizeof (buffer_min), "%d", min);
460     g_snprintf (buffer_max, sizeof (buffer_max), "%d", max);
461     a = strlen (buffer_min);
462     b = strlen (buffer_max);
463
464     /* Set maximum size of entry */
465     gtk_entry_set_width_chars (GTK_ENTRY (editor), MAX (a, b));
466     modest_number_editor_set_value (editor, priv->start);
467 }
468
469 /**
470  * modest_number_editor_get_value:
471  * @editor: pointer to #ModestNumberEditor
472  *
473  * Returns: current NumberEditor value
474  */
475 gint
476 modest_number_editor_get_value                  (ModestNumberEditor *editor)
477 {
478     ModestNumberEditorPrivate *priv;
479
480     g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), 0);
481
482     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
483     g_assert (priv);
484
485     return atoi (gtk_entry_get_text (GTK_ENTRY (editor)));
486 }
487
488 /**
489  * modest_number_editor_set_value:
490  * @editor: pointer to #ModestNumberEditor
491  * @value: numeric value for number editor
492  *
493  * Sets numeric value for number editor
494  */
495 void
496 modest_number_editor_set_value                  (ModestNumberEditor *editor, 
497                                                  gint value)
498 {
499     ModestNumberEditorPrivate *priv;
500
501     g_return_if_fail (MODEST_IS_NUMBER_EDITOR (editor));
502
503     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
504     g_assert (priv);
505
506     g_return_if_fail (value <= priv->end);
507     g_return_if_fail (value >= priv->start);
508
509     priv->default_val = value;
510     modest_number_editor_real_set_value (editor, value);
511     g_object_notify (G_OBJECT(editor), "value");
512 }
513
514 /* When calling gtk_entry_set_text, the entry widget does things that can
515  * cause the whole widget to redraw. This redrawing is delayed and if any
516  * selections are made right after calling the gtk_entry_set_text the
517  * setting of the selection might seem to have no effect.
518  *
519  * If the selection is delayed with a lower priority than the redrawing,
520  * the selection should stick. Calling this function with g_idle_add should
521  * do it.
522  */
523 static gboolean
524 modest_number_editor_select_all                 (ModestNumberEditor *editor)
525 {   
526     ModestNumberEditorPrivate *priv;
527
528     g_return_val_if_fail (MODEST_IS_NUMBER_EDITOR (editor), FALSE);
529
530     priv = MODEST_NUMBER_EDITOR_GET_PRIVATE (editor);
531     g_assert (priv);
532
533     GDK_THREADS_ENTER ();
534     gtk_editable_select_region (GTK_EDITABLE (editor), 0, -1);
535     priv->select_all_idle_id = 0;
536     GDK_THREADS_LEAVE ();
537     return FALSE;
538
539
540 static void
541 modest_number_editor_set_property               (GObject *object,
542                                                  guint prop_id,
543                                                  const GValue *value, 
544                                                  GParamSpec *pspec)
545 {
546     ModestNumberEditor *editor;
547
548     editor = MODEST_NUMBER_EDITOR (object);
549
550     switch (prop_id) {
551
552         case PROP_VALUE:
553             modest_number_editor_set_value (editor, g_value_get_int (value));
554             break;
555
556         default:
557             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
558             break;
559     }
560 }
561
562 static void
563 modest_number_editor_get_property               (GObject *object,
564                                                  guint prop_id, 
565                                                  GValue *value, 
566                                                  GParamSpec *pspec)
567 {
568     ModestNumberEditor *editor;
569
570     editor = MODEST_NUMBER_EDITOR (object);
571
572     switch (prop_id) {
573
574         case PROP_VALUE:
575             g_value_set_int(value, modest_number_editor_get_value (editor));
576             break;
577
578         default:
579             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
580             break;
581     }
582 }
583
584
585 /* enumerations from "modest-number-editor.h" */
586 GType
587 modest_number_editor_error_type_get_type (void)
588 {
589   static GType etype = 0;
590   if (etype == 0) {
591     static const GEnumValue values[] = {
592       { MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED, "MODEST_NUMBER_EDITOR_ERROR_MAXIMUM_VALUE_EXCEED", "maximum-value-exceed" },
593       { MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED, "MODEST_NUMBER_EDITOR_ERROR_MINIMUM_VALUE_EXCEED", "minimum-value-exceed" },
594       { MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE, "MODEST_NUMBER_EDITOR_ERROR_ERRONEOUS_VALUE", "erroneous-value" },
595       { 0, NULL, NULL }
596     };
597     etype = g_enum_register_static ("ModestNumberEditorErrorType", values);
598   }
599   return etype;
600 }
601