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