Use server security selector in connection specific smtp dialog.
[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 #ifndef MODEST_TOOLKIT_HILDON2
41 #define USE_GTK_ENTRY
42 #endif
43
44 #ifdef USE_GTK_ENTRY
45 G_DEFINE_TYPE (ModestValidatingEntry, modest_validating_entry, GTK_TYPE_ENTRY);
46 #else
47 G_DEFINE_TYPE (ModestValidatingEntry, modest_validating_entry, HILDON_TYPE_ENTRY);
48 #endif
49
50 #define VALIDATING_ENTRY_GET_PRIVATE(o) \
51         (G_TYPE_INSTANCE_GET_PRIVATE ((o), MODEST_TYPE_VALIDATING_ENTRY, ModestValidatingEntryPrivate))
52
53 typedef struct _ModestValidatingEntryPrivate ModestValidatingEntryPrivate;
54
55 struct _ModestValidatingEntryPrivate
56 {
57         /* A list of gunichar, rather than char*,
58          * because gunichar is easier to deal with internally,
59          * but gchar* is easier to supply from the external interface.
60          */
61         GList *list_prevent;
62         
63         gboolean prevent_whitespace;
64         
65         EasySetupValidatingEntryFunc func;
66         gpointer func_user_data;
67
68         EasySetupValidatingEntryMaxFunc max_func;
69         gpointer max_func_user_data;
70 };
71
72 static void
73 modest_validating_entry_get_property (GObject *object, guint property_id,
74                                                                                                                         GValue *value, GParamSpec *pspec)
75 {
76         switch (property_id) {
77         default:
78                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
79         }
80 }
81
82 static void
83 modest_validating_entry_set_property (GObject *object, guint property_id,
84                                                                                                                         const GValue *value, GParamSpec *pspec)
85 {
86         switch (property_id) {
87         default:
88                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
89         }
90 }
91
92 static void
93 modest_validating_entry_dispose (GObject *object)
94 {
95         if (G_OBJECT_CLASS (modest_validating_entry_parent_class)->dispose)
96                 G_OBJECT_CLASS (modest_validating_entry_parent_class)->dispose (object);
97 }
98
99 static void
100 modest_validating_entry_finalize (GObject *object)
101 {
102         ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (object);
103         
104         /* Free the list and its items: */
105         if (priv->list_prevent) {
106                 g_list_foreach (priv->list_prevent, (GFunc)&g_free, NULL);
107                 g_list_free (priv->list_prevent);
108         }
109         
110         G_OBJECT_CLASS (modest_validating_entry_parent_class)->finalize (object);
111 }
112
113 static void
114 modest_validating_entry_class_init (ModestValidatingEntryClass *klass)
115 {
116         GObjectClass *object_class = G_OBJECT_CLASS (klass);
117
118         g_type_class_add_private (klass, sizeof (ModestValidatingEntryPrivate));
119
120         object_class->get_property = modest_validating_entry_get_property;
121         object_class->set_property = modest_validating_entry_set_property;
122         object_class->dispose = modest_validating_entry_dispose;
123         object_class->finalize = modest_validating_entry_finalize;
124 }
125
126 static gint
127 on_list_compare(gconstpointer a, gconstpointer b)
128 {
129         gunichar* unichar_a = (gunichar*)(a);
130         gunichar* unichar_b = (gunichar*)(b);
131         if(*unichar_a == *unichar_b)
132                 return 0;
133         else
134                 return -1; /* Really, we should return > and <, but we don't use this for sorting. */
135 }
136                                              
137 static void 
138 on_insert_text(GtkEditable *editable,
139         gchar *new_text, gint new_text_length, 
140         gint *position,
141     gpointer user_data)
142 {
143         ModestValidatingEntry *self = MODEST_VALIDATING_ENTRY (user_data);
144         ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
145         
146         if(!new_text_length)
147                 return;
148                 
149         /* Note: new_text_length is documented as the number of bytes, not characters. */
150         if(!g_utf8_validate (new_text, new_text_length, NULL))
151                 return;
152         
153         /* Look at each UTF-8 character in the text (it could be several via a drop or a paste),
154          * and check them */
155         gboolean allow = TRUE;
156         gchar *iter = new_text; /* new_text seems to be NULL-terminated, though that is not documented. */
157         while (iter)
158         {
159                 if(priv->list_prevent) {
160                         /* If the character is in our prevent list, 
161                          * then do not allow this text to be entered.
162                          * 
163                          * This prevents entry of all text, without removing the unwanted characters.
164                          * It is debatable whether that is the best thing to do.
165                          */
166                         gunichar one_char = g_utf8_get_char (iter);
167                         GList *found = g_list_find_custom(priv->list_prevent, &one_char, &on_list_compare);
168                         if(found) {
169                                 allow = FALSE;
170                                 if (priv->func)
171                                 {
172                                         priv->func(self, iter, priv->func_user_data);
173                                 }
174                                 break;
175                         }       
176                 }
177                 
178                 if(priv->prevent_whitespace) {
179                         /* Check for whitespace characters: */
180                         gunichar one_char = g_utf8_get_char (iter);
181                         if (g_unichar_isspace (one_char)) {
182                                 allow = FALSE;
183                                 if (priv->func)
184                                 {
185                                         priv->func(self, NULL, priv->func_user_data);
186                                 }
187                                 break;
188                         }
189                 }
190
191                 /* Crashes. Don't know why: iter = g_utf8_next_char (iter); 
192                  * Maybe it doesn't check for null-termination. */      
193                 iter = g_utf8_find_next_char (iter, new_text + new_text_length);
194         }
195         
196         /* Prevent more than the max characters.
197          * The regular GtkEntry does this already, but we also want to call a specified callback,
198          * so that the application can show a warning dialog. */
199         if(priv->max_func) {
200                 const gint max_num = gtk_entry_get_max_length (GTK_ENTRY (self));
201                 if (max_num > 0) {
202                         const gchar *existing_text = gtk_entry_get_text (GTK_ENTRY(self));
203                         const gint existing_length = existing_text ? g_utf8_strlen (existing_text, -1) : 0;
204                         const gint new_length_chars = g_utf8_strlen (new_text, new_text_length);
205                         
206                         if ((existing_length + new_length_chars) > max_num) {
207                                 priv->max_func (self, priv->max_func_user_data);
208                                 /* We shouldn't need to stop the signal because the underlying code will check too.
209                                 * Well, that would maybe be a performance optimization, 
210                                  * but it's generally safer not to interfere too much. */       
211                         }
212                 }
213         }
214         
215         if(!allow) {
216                 /* The signal documentation says 
217                  * "by connecting to this signal and then stopping the signal with 
218                  * gtk_signal_emit_stop(), it is possible to modify the inserted text, 
219                  * or prevent it from being inserted entirely."
220                  */
221                  gtk_signal_emit_stop_by_name (GTK_OBJECT (self), "insert-text");
222         }
223
224
225                                             
226 static void
227 modest_validating_entry_init (ModestValidatingEntry *self)
228 {
229         /* Connect to the GtkEditable::insert-text signal 
230          * so we can filter out some characters:
231          * We connect _before_ so we can stop the default signal handler from running.
232          */
233         g_signal_connect (G_OBJECT (self), "insert-text", (GCallback)&on_insert_text, self);
234 }
235
236 ModestValidatingEntry*
237 modest_validating_entry_new (void)
238 {
239         ModestValidatingEntry *entry;
240         
241         entry = g_object_new (MODEST_TYPE_VALIDATING_ENTRY, NULL);
242
243 #ifndef MODEST_TOOLKIT_HILDON2
244         hildon_gtk_widget_set_theme_size (GTK_WIDGET (entry), MODEST_EDITABLE_SIZE);
245 #endif
246
247         return entry;
248 }
249
250 /** Specify characters that may not be entered into this GtkEntry.
251  *  
252  * list: A list of gchar* strings. Each one identifies a UTF-8 character.
253  */
254 void modest_validating_entry_set_unallowed_characters (ModestValidatingEntry *self, GList *list)
255 {
256         ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
257             
258         /* Free the list and its items: */      
259         if (priv->list_prevent) {
260                 g_list_foreach (priv->list_prevent, (GFunc)&g_free, NULL);
261                 g_list_free (priv->list_prevent);
262         }
263      
264     /* Do a deep copy of the list, converting gchar* to gunichar: */
265     priv->list_prevent = NULL;
266     GList *iter = NULL;               
267     for (iter = list; iter != NULL; iter = iter->next) {
268         gunichar *one_char = g_new0 (gunichar, 1);
269         if(iter->data)
270                 *one_char = g_utf8_get_char ((gchar*)iter->data);
271         else
272                 *one_char = 0;
273                 
274         priv->list_prevent = g_list_append (priv->list_prevent, one_char);      
275     }
276 }
277
278 /** Specify that no whitespace characters may be entered into this GtkEntry.
279  *  
280  */
281 void modest_validating_entry_set_unallowed_characters_whitespace (ModestValidatingEntry *self)
282 {
283         ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
284         priv->prevent_whitespace = TRUE;
285 }
286
287 /** Set a callback to be called when the maximum number of characters have been entered.
288  * This may be used to show an informative dialog.
289  */
290 void modest_validating_entry_set_max_func (ModestValidatingEntry *self, EasySetupValidatingEntryMaxFunc func, gpointer user_data)
291 {
292         ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
293         priv->max_func = func;
294         priv->max_func_user_data = user_data;
295 }
296
297 /** Set a callback to be called when a character was prevented so that a
298  * note can be shown by the application to inform the user. For whitespaces,
299  * character will be NULL
300  */
301 void modest_validating_entry_set_func (ModestValidatingEntry *self, EasySetupValidatingEntryFunc func, gpointer user_data)
302 {
303         ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
304         priv->func = func;
305         priv->func_user_data = user_data;
306 }
307