Security options view based in gtk
[modest] / src / widgets / modest-validating-entry.c
1 /* Copyright (c) 2007, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include "modest-validating-entry.h"
31 #include <modest-ui-constants.h>
32 #include <gtk/gtksignal.h> /* For the gtk_signal_stop_emit_by_name() convenience function. */
33 #include <string.h> /* For strlen(). */
34
35 /* Include config.h so that _() works: */
36 #ifdef HAVE_CONFIG_H
37 #include <config.h>
38 #endif
39
40 #ifdef USE_GTK_ENTRY
41 G_DEFINE_TYPE (ModestValidatingEntry, modest_validating_entry, GTK_TYPE_ENTRY);
42 #else
43 G_DEFINE_TYPE (ModestValidatingEntry, modest_validating_entry, HILDON_TYPE_ENTRY);
44 #endif
45
46 #define VALIDATING_ENTRY_GET_PRIVATE(o) \
47         (G_TYPE_INSTANCE_GET_PRIVATE ((o), MODEST_TYPE_VALIDATING_ENTRY, ModestValidatingEntryPrivate))
48
49 typedef struct _ModestValidatingEntryPrivate ModestValidatingEntryPrivate;
50
51 struct _ModestValidatingEntryPrivate
52 {
53         /* A list of gunichar, rather than char*,
54          * because gunichar is easier to deal with internally,
55          * but gchar* is easier to supply from the external interface.
56          */
57         GList *list_prevent;
58         
59         gboolean prevent_whitespace;
60         
61         EasySetupValidatingEntryFunc func;
62         gpointer func_user_data;
63
64         EasySetupValidatingEntryMaxFunc max_func;
65         gpointer max_func_user_data;
66 };
67
68 static void
69 modest_validating_entry_get_property (GObject *object, guint property_id,
70                                                                                                                         GValue *value, GParamSpec *pspec)
71 {
72         switch (property_id) {
73         default:
74                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
75         }
76 }
77
78 static void
79 modest_validating_entry_set_property (GObject *object, guint property_id,
80                                                                                                                         const GValue *value, GParamSpec *pspec)
81 {
82         switch (property_id) {
83         default:
84                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
85         }
86 }
87
88 static void
89 modest_validating_entry_dispose (GObject *object)
90 {
91         if (G_OBJECT_CLASS (modest_validating_entry_parent_class)->dispose)
92                 G_OBJECT_CLASS (modest_validating_entry_parent_class)->dispose (object);
93 }
94
95 static void
96 modest_validating_entry_finalize (GObject *object)
97 {
98         ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (object);
99         
100         /* Free the list and its items: */
101         if (priv->list_prevent) {
102                 g_list_foreach (priv->list_prevent, (GFunc)&g_free, NULL);
103                 g_list_free (priv->list_prevent);
104         }
105         
106         G_OBJECT_CLASS (modest_validating_entry_parent_class)->finalize (object);
107 }
108
109 static void
110 modest_validating_entry_class_init (ModestValidatingEntryClass *klass)
111 {
112         GObjectClass *object_class = G_OBJECT_CLASS (klass);
113
114         g_type_class_add_private (klass, sizeof (ModestValidatingEntryPrivate));
115
116         object_class->get_property = modest_validating_entry_get_property;
117         object_class->set_property = modest_validating_entry_set_property;
118         object_class->dispose = modest_validating_entry_dispose;
119         object_class->finalize = modest_validating_entry_finalize;
120 }
121
122 static gint
123 on_list_compare(gconstpointer a, gconstpointer b)
124 {
125         gunichar* unichar_a = (gunichar*)(a);
126         gunichar* unichar_b = (gunichar*)(b);
127         if(*unichar_a == *unichar_b)
128                 return 0;
129         else
130                 return -1; /* Really, we should return > and <, but we don't use this for sorting. */
131 }
132                                              
133 static void 
134 on_insert_text(GtkEditable *editable,
135         gchar *new_text, gint new_text_length, 
136         gint *position,
137     gpointer user_data)
138 {
139         ModestValidatingEntry *self = MODEST_VALIDATING_ENTRY (user_data);
140         ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
141         
142         if(!new_text_length)
143                 return;
144                 
145         /* Note: new_text_length is documented as the number of bytes, not characters. */
146         if(!g_utf8_validate (new_text, new_text_length, NULL))
147                 return;
148         
149         /* Look at each UTF-8 character in the text (it could be several via a drop or a paste),
150          * and check them */
151         gboolean allow = TRUE;
152         gchar *iter = new_text; /* new_text seems to be NULL-terminated, though that is not documented. */
153         while (iter)
154         {
155                 if(priv->list_prevent) {
156                         /* If the character is in our prevent list, 
157                          * then do not allow this text to be entered.
158                          * 
159                          * This prevents entry of all text, without removing the unwanted characters.
160                          * It is debatable whether that is the best thing to do.
161                          */
162                         gunichar one_char = g_utf8_get_char (iter);
163                         GList *found = g_list_find_custom(priv->list_prevent, &one_char, &on_list_compare);
164                         if(found) {
165                                 allow = FALSE;
166                                 if (priv->func)
167                                 {
168                                         priv->func(self, iter, priv->func_user_data);
169                                 }
170                                 break;
171                         }       
172                 }
173                 
174                 if(priv->prevent_whitespace) {
175                         /* Check for whitespace characters: */
176                         gunichar one_char = g_utf8_get_char (iter);
177                         if (g_unichar_isspace (one_char)) {
178                                 allow = FALSE;
179                                 if (priv->func)
180                                 {
181                                         priv->func(self, NULL, priv->func_user_data);
182                                 }
183                                 break;
184                         }
185                 }
186
187                 /* Crashes. Don't know why: iter = g_utf8_next_char (iter); 
188                  * Maybe it doesn't check for null-termination. */      
189                 iter = g_utf8_find_next_char (iter, new_text + new_text_length);
190         }
191         
192         /* Prevent more than the max characters.
193          * The regular GtkEntry does this already, but we also want to call a specified callback,
194          * so that the application can show a warning dialog. */
195         if(priv->max_func) {
196                 const gint max_num = gtk_entry_get_max_length (GTK_ENTRY (self));
197                 if (max_num > 0) {
198                         const gchar *existing_text = gtk_entry_get_text (GTK_ENTRY(self));
199                         const gint existing_length = existing_text ? g_utf8_strlen (existing_text, -1) : 0;
200                         const gint new_length_chars = g_utf8_strlen (new_text, new_text_length);
201                         
202                         if ((existing_length + new_length_chars) > max_num) {
203                                 priv->max_func (self, priv->max_func_user_data);
204                                 /* We shouldn't need to stop the signal because the underlying code will check too.
205                                 * Well, that would maybe be a performance optimization, 
206                                  * but it's generally safer not to interfere too much. */       
207                         }
208                 }
209         }
210         
211         if(!allow) {
212                 /* The signal documentation says 
213                  * "by connecting to this signal and then stopping the signal with 
214                  * gtk_signal_emit_stop(), it is possible to modify the inserted text, 
215                  * or prevent it from being inserted entirely."
216                  */
217                  gtk_signal_emit_stop_by_name (GTK_OBJECT (self), "insert-text");
218         }
219
220
221                                             
222 static void
223 modest_validating_entry_init (ModestValidatingEntry *self)
224 {
225         /* Connect to the GtkEditable::insert-text signal 
226          * so we can filter out some characters:
227          * We connect _before_ so we can stop the default signal handler from running.
228          */
229         g_signal_connect (G_OBJECT (self), "insert-text", (GCallback)&on_insert_text, self);
230 }
231
232 ModestValidatingEntry*
233 modest_validating_entry_new (void)
234 {
235         ModestValidatingEntry *entry;
236         
237         entry = g_object_new (MODEST_TYPE_VALIDATING_ENTRY, NULL);
238
239 #ifdef MODEST_TOOLKIT_HILDON2
240         hildon_gtk_widget_set_theme_size (GTK_WIDGET (entry), MODEST_EDITABLE_SIZE);
241 #endif
242
243         return entry;
244 }
245
246 /** Specify characters that may not be entered into this GtkEntry.
247  *  
248  * list: A list of gchar* strings. Each one identifies a UTF-8 character.
249  */
250 void modest_validating_entry_set_unallowed_characters (ModestValidatingEntry *self, GList *list)
251 {
252         ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
253             
254         /* Free the list and its items: */      
255         if (priv->list_prevent) {
256                 g_list_foreach (priv->list_prevent, (GFunc)&g_free, NULL);
257                 g_list_free (priv->list_prevent);
258         }
259      
260     /* Do a deep copy of the list, converting gchar* to gunichar: */
261     priv->list_prevent = NULL;
262     GList *iter = NULL;               
263     for (iter = list; iter != NULL; iter = iter->next) {
264         gunichar *one_char = g_new0 (gunichar, 1);
265         if(iter->data)
266                 *one_char = g_utf8_get_char ((gchar*)iter->data);
267         else
268                 *one_char = 0;
269                 
270         priv->list_prevent = g_list_append (priv->list_prevent, one_char);      
271     }
272 }
273
274 /** Specify that no whitespace characters may be entered into this GtkEntry.
275  *  
276  */
277 void modest_validating_entry_set_unallowed_characters_whitespace (ModestValidatingEntry *self)
278 {
279         ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
280         priv->prevent_whitespace = TRUE;
281 }
282
283 /** Set a callback to be called when the maximum number of characters have been entered.
284  * This may be used to show an informative dialog.
285  */
286 void modest_validating_entry_set_max_func (ModestValidatingEntry *self, EasySetupValidatingEntryMaxFunc func, gpointer user_data)
287 {
288         ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
289         priv->max_func = func;
290         priv->max_func_user_data = user_data;
291 }
292
293 /** Set a callback to be called when a character was prevented so that a
294  * note can be shown by the application to inform the user. For whitespaces,
295  * character will be NULL
296  */
297 void modest_validating_entry_set_func (ModestValidatingEntry *self, EasySetupValidatingEntryFunc func, gpointer user_data)
298 {
299         ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
300         priv->func = func;
301         priv->func_user_data = user_data;
302 }
303