1 /* Copyright (c) 2007, Nokia Corporation
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(). */
10 /* Include config.h so that _() works: */
15 #ifdef MODEST_TOOLKIT_HILDON2
16 G_DEFINE_TYPE (ModestValidatingEntry, modest_validating_entry, HILDON_TYPE_ENTRY);
18 G_DEFINE_TYPE (ModestValidatingEntry, modest_validating_entry, GTK_TYPE_ENTRY);
21 #define VALIDATING_ENTRY_GET_PRIVATE(o) \
22 (G_TYPE_INSTANCE_GET_PRIVATE ((o), MODEST_TYPE_VALIDATING_ENTRY, ModestValidatingEntryPrivate))
24 typedef struct _ModestValidatingEntryPrivate ModestValidatingEntryPrivate;
26 struct _ModestValidatingEntryPrivate
28 /* A list of gunichar, rather than char*,
29 * because gunichar is easier to deal with internally,
30 * but gchar* is easier to supply from the external interface.
34 gboolean prevent_whitespace;
36 EasySetupValidatingEntryFunc func;
37 gpointer func_user_data;
39 EasySetupValidatingEntryMaxFunc max_func;
40 gpointer max_func_user_data;
44 modest_validating_entry_get_property (GObject *object, guint property_id,
45 GValue *value, GParamSpec *pspec)
47 switch (property_id) {
49 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
54 modest_validating_entry_set_property (GObject *object, guint property_id,
55 const GValue *value, GParamSpec *pspec)
57 switch (property_id) {
59 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
64 modest_validating_entry_dispose (GObject *object)
66 if (G_OBJECT_CLASS (modest_validating_entry_parent_class)->dispose)
67 G_OBJECT_CLASS (modest_validating_entry_parent_class)->dispose (object);
71 modest_validating_entry_finalize (GObject *object)
73 ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (object);
75 /* Free the list and its items: */
76 if (priv->list_prevent) {
77 g_list_foreach (priv->list_prevent, (GFunc)&g_free, NULL);
78 g_list_free (priv->list_prevent);
81 G_OBJECT_CLASS (modest_validating_entry_parent_class)->finalize (object);
85 modest_validating_entry_class_init (ModestValidatingEntryClass *klass)
87 GObjectClass *object_class = G_OBJECT_CLASS (klass);
89 g_type_class_add_private (klass, sizeof (ModestValidatingEntryPrivate));
91 object_class->get_property = modest_validating_entry_get_property;
92 object_class->set_property = modest_validating_entry_set_property;
93 object_class->dispose = modest_validating_entry_dispose;
94 object_class->finalize = modest_validating_entry_finalize;
98 on_list_compare(gconstpointer a, gconstpointer b)
100 gunichar* unichar_a = (gunichar*)(a);
101 gunichar* unichar_b = (gunichar*)(b);
102 if(*unichar_a == *unichar_b)
105 return -1; /* Really, we should return > and <, but we don't use this for sorting. */
109 on_insert_text(GtkEditable *editable,
110 gchar *new_text, gint new_text_length,
114 ModestValidatingEntry *self = MODEST_VALIDATING_ENTRY (user_data);
115 ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
120 /* Note: new_text_length is documented as the number of bytes, not characters. */
121 if(!g_utf8_validate (new_text, new_text_length, NULL))
124 /* Look at each UTF-8 character in the text (it could be several via a drop or a paste),
126 gboolean allow = TRUE;
127 gchar *iter = new_text; /* new_text seems to be NULL-terminated, though that is not documented. */
130 if(priv->list_prevent) {
131 /* If the character is in our prevent list,
132 * then do not allow this text to be entered.
134 * This prevents entry of all text, without removing the unwanted characters.
135 * It is debatable whether that is the best thing to do.
137 gunichar one_char = g_utf8_get_char (iter);
138 GList *found = g_list_find_custom(priv->list_prevent, &one_char, &on_list_compare);
143 priv->func(self, iter, priv->func_user_data);
149 if(priv->prevent_whitespace) {
150 /* Check for whitespace characters: */
151 gunichar one_char = g_utf8_get_char (iter);
152 if (g_unichar_isspace (one_char)) {
156 priv->func(self, NULL, priv->func_user_data);
162 /* Crashes. Don't know why: iter = g_utf8_next_char (iter);
163 * Maybe it doesn't check for null-termination. */
164 iter = g_utf8_find_next_char (iter, new_text + new_text_length);
167 /* Prevent more than the max characters.
168 * The regular GtkEntry does this already, but we also want to call a specified callback,
169 * so that the application can show a warning dialog. */
171 const gint max_num = gtk_entry_get_max_length (GTK_ENTRY (self));
173 const gchar *existing_text = gtk_entry_get_text (GTK_ENTRY(self));
174 const gint existing_length = existing_text ? g_utf8_strlen (existing_text, -1) : 0;
175 const gint new_length_chars = g_utf8_strlen (new_text, new_text_length);
177 if ((existing_length + new_length_chars) > max_num) {
178 priv->max_func (self, priv->max_func_user_data);
179 /* We shouldn't need to stop the signal because the underlying code will check too.
180 * Well, that would maybe be a performance optimization,
181 * but it's generally safer not to interfere too much. */
187 /* The signal documentation says
188 * "by connecting to this signal and then stopping the signal with
189 * gtk_signal_emit_stop(), it is possible to modify the inserted text,
190 * or prevent it from being inserted entirely."
192 gtk_signal_emit_stop_by_name (GTK_OBJECT (self), "insert-text");
198 modest_validating_entry_init (ModestValidatingEntry *self)
200 /* Connect to the GtkEditable::insert-text signal
201 * so we can filter out some characters:
202 * We connect _before_ so we can stop the default signal handler from running.
204 g_signal_connect (G_OBJECT (self), "insert-text", (GCallback)&on_insert_text, self);
207 ModestValidatingEntry*
208 modest_validating_entry_new (void)
210 return g_object_new (MODEST_TYPE_VALIDATING_ENTRY, NULL);
213 /** Specify characters that may not be entered into this GtkEntry.
215 * list: A list of gchar* strings. Each one identifies a UTF-8 character.
217 void modest_validating_entry_set_unallowed_characters (ModestValidatingEntry *self, GList *list)
219 ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
221 /* Free the list and its items: */
222 if (priv->list_prevent) {
223 g_list_foreach (priv->list_prevent, (GFunc)&g_free, NULL);
224 g_list_free (priv->list_prevent);
227 /* Do a deep copy of the list, converting gchar* to gunichar: */
228 priv->list_prevent = NULL;
230 for (iter = list; iter != NULL; iter = iter->next) {
231 gunichar *one_char = g_new0 (gunichar, 1);
233 *one_char = g_utf8_get_char ((gchar*)iter->data);
237 priv->list_prevent = g_list_append (priv->list_prevent, one_char);
241 /** Specify that no whitespace characters may be entered into this GtkEntry.
244 void modest_validating_entry_set_unallowed_characters_whitespace (ModestValidatingEntry *self)
246 ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
247 priv->prevent_whitespace = TRUE;
250 /** Set a callback to be called when the maximum number of characters have been entered.
251 * This may be used to show an informative dialog.
253 void modest_validating_entry_set_max_func (ModestValidatingEntry *self, EasySetupValidatingEntryMaxFunc func, gpointer user_data)
255 ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
256 priv->max_func = func;
257 priv->max_func_user_data = user_data;
260 /** Set a callback to be called when a character was prevented so that a
261 * note can be shown by the application to inform the user. For whitespaces,
262 * character will be NULL
264 void modest_validating_entry_set_func (ModestValidatingEntry *self, EasySetupValidatingEntryFunc func, gpointer user_data)
266 ModestValidatingEntryPrivate *priv = VALIDATING_ENTRY_GET_PRIVATE (self);
268 priv->func_user_data = user_data;