b1d1191716b01570998dc241b2b0925f7a8ab8c2
[modest] / src / widgets / modest-validating-entry.c
1 /* Copyright (c) 2007, Nokia Corporation
2  * All rights reserved.
3  *
4  */
5
6 #include "modest-validating-entry.h"
7 #include <gtk/gtksignal.h> /* For the gtk_signal_stop_emit_by_name() convenience function. */
8 #include <string.h> /* For strlen(). */
9
10 /* Include config.h so that _() works: */
11 #ifdef HAVE_CONFIG_H
12 #include <config.h>
13 #endif
14
15 G_DEFINE_TYPE (EasysetupValidatingEntry, easysetup_validating_entry, GTK_TYPE_ENTRY);
16
17 #define VALIDATING_ENTRY_GET_PRIVATE(o) \
18         (G_TYPE_INSTANCE_GET_PRIVATE ((o), EASYSETUP_TYPE_VALIDATING_ENTRY, EasysetupValidatingEntryPrivate))
19
20 typedef struct _EasysetupValidatingEntryPrivate EasysetupValidatingEntryPrivate;
21
22 struct _EasysetupValidatingEntryPrivate
23 {
24         /* A list of gunichar, rather than char*,
25          * because gunichar is easier to deal with internally,
26          * but gchar* is easier to supply from the external interface.
27          */
28         GList *list_prevent;
29         
30         gboolean prevent_whitespace;
31 };
32
33 static void
34 easysetup_validating_entry_get_property (GObject *object, guint property_id,
35                                                                                                                         GValue *value, GParamSpec *pspec)
36 {
37         switch (property_id) {
38         default:
39                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
40         }
41 }
42
43 static void
44 easysetup_validating_entry_set_property (GObject *object, guint property_id,
45                                                                                                                         const GValue *value, GParamSpec *pspec)
46 {
47         switch (property_id) {
48         default:
49                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
50         }
51 }
52
53 static void
54 easysetup_validating_entry_dispose (GObject *object)
55 {
56         if (G_OBJECT_CLASS (easysetup_validating_entry_parent_class)->dispose)
57                 G_OBJECT_CLASS (easysetup_validating_entry_parent_class)->dispose (object);
58 }
59
60 static void
61 easysetup_validating_entry_finalize (GObject *object)
62 {
63         EasysetupValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (object);
64         
65         /* Free the list and its items: */
66         if (priv->list_prevent) {
67                 g_list_foreach (priv->list_prevent, (GFunc)&g_free, NULL);
68                 g_list_free (priv->list_prevent);
69         }
70         
71         G_OBJECT_CLASS (easysetup_validating_entry_parent_class)->finalize (object);
72 }
73
74 static void
75 easysetup_validating_entry_class_init (EasysetupValidatingEntryClass *klass)
76 {
77         GObjectClass *object_class = G_OBJECT_CLASS (klass);
78
79         g_type_class_add_private (klass, sizeof (EasysetupValidatingEntryPrivate));
80
81         object_class->get_property = easysetup_validating_entry_get_property;
82         object_class->set_property = easysetup_validating_entry_set_property;
83         object_class->dispose = easysetup_validating_entry_dispose;
84         object_class->finalize = easysetup_validating_entry_finalize;
85 }
86
87 static gint
88 on_list_compare(gconstpointer a, gconstpointer b)
89 {
90         gunichar* unichar_a = (gunichar*)(a);
91         gunichar* unichar_b = (gunichar*)(b);
92         if(*unichar_a == *unichar_b)
93                 return 0;
94         else
95                 return -1; /* Really, we should return > and <, but we don't use this for sorting. */
96 }
97                                              
98 static void 
99 on_insert_text(GtkEditable *editable,
100         gchar *new_text, gint new_text_length, 
101         gint *position,
102     gpointer user_data)
103 {
104         EasysetupValidatingEntry *self = EASYSETUP_VALIDATING_ENTRY (user_data);
105         EasysetupValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
106         
107         if(!new_text_length)
108                 return;
109                 
110         /* Note: new_text_length is documented as the number of bytes, not characters. */
111         if(!g_utf8_validate (new_text, new_text_length, NULL))
112                 return;
113         
114         /* Look at each UTF-8 character in the text (it could be several via a drop or a paste),
115          * and check them */
116         gboolean allow = TRUE;
117         gchar *iter = new_text; /* new_text seems to be NULL-terminated, though that is not documented. */
118         while (iter)
119         {
120                 if(priv->list_prevent) {
121                         /* If the character is in our prevent list, 
122                          * then do not allow this text to be entered.
123                          * 
124                          * This prevents entry of all text, without removing the unwanted characters.
125                          * It is debatable whether that is the best thing to do.
126                          */
127                         gunichar one_char = g_utf8_get_char (iter);
128                         GList *found = g_list_find_custom(priv->list_prevent, &one_char, &on_list_compare);
129                         if(found) {
130                                 allow = FALSE;
131                                 break;
132                         }       
133                 }
134                 
135                 if(priv->prevent_whitespace) {
136                         /* Check for whitespace characters: */
137                         gunichar one_char = g_utf8_get_char (iter);
138                         if (g_unichar_isspace (one_char)) {
139                                 allow = FALSE;
140                                 break;
141                         }
142                 }
143
144                 /* Crashes. Don't know why: iter = g_utf8_next_char (iter); 
145                  * Maybe it doesn't check for null-termination. */      
146                 iter = g_utf8_find_next_char (iter, new_text + new_text_length);
147         }
148         
149         if(!allow) {
150                 /* The signal documentation says 
151                  * "by connecting to this signal and then stopping the signal with 
152                  * gtk_signal_emit_stop(), it is possible to modify the inserted text, 
153                  * or prevent it from being inserted entirely."
154                  */
155                  gtk_signal_emit_stop_by_name (GTK_OBJECT (self), "insert-text");
156         }
157
158                                             
159 static void
160 easysetup_validating_entry_init (EasysetupValidatingEntry *self)
161 {
162         /* Connect to the GtkEditable::insert-text signal 
163          * so we can filter out some characters:
164          * We connect _before_ so we can stop the default signal handler from running.
165          */
166         g_signal_connect (G_OBJECT (self), "insert-text", (GCallback)&on_insert_text, self);
167 }
168
169 EasysetupValidatingEntry*
170 easysetup_validating_entry_new (void)
171 {
172         return g_object_new (EASYSETUP_TYPE_VALIDATING_ENTRY, NULL);
173 }
174
175 /** Specify characters that may not be entered into this GtkEntry.
176  *  
177  * list: A list of gchar* strings. Each one identifies a UTF-8 character.
178  */
179 void easysetup_validating_entry_set_unallowed_characters (EasysetupValidatingEntry *self, GList *list)
180 {
181         EasysetupValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
182             
183         /* Free the list and its items: */      
184         if (priv->list_prevent) {
185                 g_list_foreach (priv->list_prevent, (GFunc)&g_free, NULL);
186                 g_list_free (priv->list_prevent);
187         }
188      
189     /* Do a deep copy of the list, converting gchar* to gunichar: */
190     priv->list_prevent = NULL;
191     GList *iter = NULL;               
192     for (iter = list; iter != NULL; iter = iter->next) {
193         gunichar *one_char = g_new0 (gunichar, 1);
194         if(iter->data)
195                 *one_char = g_utf8_get_char ((gchar*)iter->data);
196         else
197                 *one_char = 0;
198                 
199         priv->list_prevent = g_list_append (priv->list_prevent, one_char);      
200     }
201 }
202
203 /** Specify that no whitespace characters may be entered into this GtkEntry.
204  *  
205  */
206 void easysetup_validating_entry_set_unallowed_characters_whitespace (EasysetupValidatingEntry *self)
207 {
208         EasysetupValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
209         priv->prevent_whitespace = TRUE;
210 }