These changes are for reenabling build of gnome port.
[modest] / src / widgets / modest-recpt-editor.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 <config.h>
31
32 #include <glib/gi18n-lib.h>
33
34 #include <gtk/gtkscrolledwindow.h>
35 #include <gtk/gtktextview.h>
36 #include <gtk/gtkimage.h>
37 #include <gtk/gtkbutton.h>
38
39 #include <modest-text-utils.h>
40 #include <modest-recpt-editor.h>
41 #include <modest-scroll-text.h>
42 #include <pango/pango-attributes.h>
43 #include <string.h>
44 #include <gdk/gdkkeysyms.h>
45 #include <gtk/gtk.h>
46
47 static GObjectClass *parent_class = NULL;
48
49 #define RECIPIENT_TAG_ID "recpt-id"
50
51 /* signals */
52 enum {
53         OPEN_ADDRESSBOOK_SIGNAL,
54         LAST_SIGNAL
55 };
56
57 typedef struct _ModestRecptEditorPrivate ModestRecptEditorPrivate;
58
59 struct _ModestRecptEditorPrivate
60 {
61         GtkWidget *text_view;
62         GtkWidget *abook_button;
63         GtkWidget *scrolled_window;
64         gchar *recipients;
65
66 };
67
68 #define MODEST_RECPT_EDITOR_GET_PRIVATE(o)      \
69         (G_TYPE_INSTANCE_GET_PRIVATE ((o), MODEST_TYPE_RECPT_EDITOR, ModestRecptEditorPrivate))
70
71 static guint signals[LAST_SIGNAL] = {0};
72
73 /* static functions: GObject */
74 static void modest_recpt_editor_instance_init (GTypeInstance *instance, gpointer g_class);
75 static void modest_recpt_editor_finalize (GObject *object);
76 static void modest_recpt_editor_class_init (ModestRecptEditorClass *klass);
77
78 /* widget events */
79 static void modest_recpt_editor_on_abook_clicked (GtkButton *button,
80                                                   ModestRecptEditor *editor);
81 static gboolean modest_recpt_editor_on_button_release_event (GtkWidget *widget,
82                                                              GdkEventButton *event,
83                                                              ModestRecptEditor *editor);
84 static void modest_recpt_editor_move_cursor_to_end (ModestRecptEditor *editor);
85 static void modest_recpt_editor_on_focus_in (GtkTextView *text_view,
86                                              GdkEventFocus *event,
87                                              ModestRecptEditor *editor);
88 static void modest_recpt_editor_on_insert_text (GtkTextBuffer *buffer,
89                                                 GtkTextIter *location,
90                                                 gchar *text,
91                                                 gint len,
92                                                 ModestRecptEditor *editor);
93 static gboolean modest_recpt_editor_on_key_press_event (GtkTextView *text_view,
94                                                           GdkEventKey *key,
95                                                           ModestRecptEditor *editor);
96 static GtkTextTag *iter_has_recipient (GtkTextIter *iter);
97 static gunichar iter_previous_char (GtkTextIter *iter);
98 /* static gunichar iter_next_char (GtkTextIter *iter); */
99 static GtkTextTag *prev_iter_has_recipient (GtkTextIter *iter);
100 /* static GtkTextTag *next_iter_has_recipient (GtkTextIter *iter); */
101 static void select_tag_of_iter (GtkTextIter *iter, GtkTextTag *tag);
102 static gboolean quote_opened (GtkTextIter *iter);
103 static gboolean is_valid_insert (const gchar *text, gint len);
104 static gchar *create_valid_text (const gchar *text, gint len);
105
106 /**
107  * modest_recpt_editor_new:
108  *
109  * Return value: a new #ModestRecptEditor instance implemented for Gtk+
110  **/
111 GtkWidget*
112 modest_recpt_editor_new (void)
113 {
114         ModestRecptEditor *self = g_object_new (MODEST_TYPE_RECPT_EDITOR, 
115                                                 "homogeneous", FALSE,
116                                                 "spacing", 1,
117                                                 NULL);
118
119         return GTK_WIDGET (self);
120 }
121
122 void
123 modest_recpt_editor_set_recipients (ModestRecptEditor *recpt_editor, const gchar *recipients)
124 {
125         ModestRecptEditorPrivate *priv;
126         GtkTextBuffer *buffer = NULL;
127         gchar *valid_recipients = NULL;
128
129         g_return_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor));
130         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
131
132         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
133
134         valid_recipients = create_valid_text (recipients, -1);
135         g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text, recpt_editor);
136         gtk_text_buffer_set_text (buffer, valid_recipients, -1);
137         g_free (valid_recipients);
138         if (GTK_WIDGET_REALIZED (recpt_editor))
139                 gtk_widget_queue_resize (GTK_WIDGET (recpt_editor));
140         g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text, recpt_editor);
141
142 }
143
144 void
145 modest_recpt_editor_add_recipients (ModestRecptEditor *recpt_editor, const gchar *recipients)
146 {
147         ModestRecptEditorPrivate *priv;
148         GtkTextBuffer *buffer = NULL;
149         GtkTextIter iter;
150         gchar * string_to_add;
151
152         g_return_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor));
153         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
154
155         if (recipients == NULL)
156                 return;
157
158         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
159
160         if (gtk_text_buffer_get_char_count (buffer) > 0) {
161                 string_to_add = g_strconcat (";\n", recipients, NULL);
162         } else {
163                 string_to_add = g_strdup (recipients);
164         }
165
166         gtk_text_buffer_get_end_iter (buffer, &iter);
167
168         g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text, recpt_editor);
169
170         gtk_text_buffer_insert (buffer, &iter, recipients, -1);
171         
172         g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text, recpt_editor);
173
174         if (GTK_WIDGET_REALIZED (recpt_editor))
175                 gtk_widget_queue_resize (GTK_WIDGET (recpt_editor));
176
177 }
178
179 void 
180 modest_recpt_editor_add_resolved_recipient (ModestRecptEditor *recpt_editor, GSList *email_list, const gchar * recipient_id)
181 {
182         ModestRecptEditorPrivate *priv;
183         GtkTextBuffer *buffer = NULL;
184         GtkTextIter start, end, iter;
185         GtkTextTag *tag = NULL;
186         GSList *node;
187         gboolean is_first_recipient = TRUE;
188       
189         g_return_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor));
190         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
191
192         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
193
194         g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text, recpt_editor);
195         gtk_text_buffer_get_bounds (buffer, &start, &end);
196         if (gtk_text_buffer_get_char_count (buffer) > 0) {
197                 gchar * buffer_contents;
198
199                 gtk_text_buffer_get_bounds (buffer, &start, &end);
200                 buffer_contents = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
201                 if (!g_str_has_suffix (buffer_contents, "\n")) {
202                         if (g_str_has_suffix (buffer_contents, ";")||(g_str_has_suffix (buffer_contents, ",")))
203                                 gtk_text_buffer_insert (buffer, &end, "\n", -1);
204                         else 
205                                 gtk_text_buffer_insert (buffer, &end, ";\n", -1);
206                 }
207                 g_free (buffer_contents);
208         }
209
210         gtk_text_buffer_get_end_iter (buffer, &iter);
211
212         tag = gtk_text_buffer_create_tag (buffer, NULL, 
213                                           "underline", PANGO_UNDERLINE_SINGLE,
214                                           "wrap-mode", GTK_WRAP_NONE,
215                                           "editable", TRUE, NULL);
216
217         g_object_set_data (G_OBJECT (tag), "recipient-tag-id", GINT_TO_POINTER (RECIPIENT_TAG_ID));
218         g_object_set_data_full (G_OBJECT (tag), "recipient-id", g_strdup (recipient_id), (GDestroyNotify) g_free);
219
220         for (node = email_list; node != NULL; node = g_slist_next (node)) {
221                 gchar *recipient = (gchar *) node->data;
222
223                 if ((recipient) && (strlen (recipient) != 0)) {
224
225                         if (!is_first_recipient)
226                         gtk_text_buffer_insert (buffer, &iter, "\n", -1);
227
228                         gtk_text_buffer_insert_with_tags (buffer, &iter, recipient, -1, tag, NULL);
229                         gtk_text_buffer_insert (buffer, &iter, ";", -1);
230                         is_first_recipient = FALSE;
231                 }
232         }
233         g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text, recpt_editor);
234
235         modest_recpt_editor_move_cursor_to_end (recpt_editor);
236
237 }
238
239 void 
240 modest_recpt_editor_replace_with_resolved_recipient (ModestRecptEditor *recpt_editor, 
241                                                      GtkTextIter *start, GtkTextIter *end,
242                                                      GSList *email_list, const gchar * recipient_id)
243 {
244         ModestRecptEditorPrivate *priv;
245         GtkTextBuffer *buffer;
246         GtkTextTag *tag;
247         GSList *node;
248         gboolean is_first_recipient = TRUE;
249
250         g_return_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor));
251         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
252
253         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
254         g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text, recpt_editor);
255
256         gtk_text_buffer_delete (buffer, start, end);
257
258         tag = gtk_text_buffer_create_tag (buffer, NULL, 
259                                           "underline", PANGO_UNDERLINE_SINGLE,
260                                           "wrap-mode", GTK_WRAP_NONE,
261                                           "editable", TRUE, NULL);
262
263         g_object_set_data (G_OBJECT (tag), "recipient-tag-id", GINT_TO_POINTER (RECIPIENT_TAG_ID));
264         g_object_set_data_full (G_OBJECT (tag), "recipient-id", g_strdup (recipient_id), (GDestroyNotify) g_free);
265
266         for (node = email_list; node != NULL; node = g_slist_next (node)) {
267                 gchar *recipient = (gchar *) node->data;
268
269                 if ((recipient) && (strlen (recipient) != 0)) {
270
271                         if (!is_first_recipient)
272                         gtk_text_buffer_insert (buffer, start, "\n", -1);
273
274                         gtk_text_buffer_insert_with_tags (buffer, start, recipient, -1, tag, NULL);
275
276                         if (node->next != NULL)
277                                 gtk_text_buffer_insert (buffer, start, ";", -1);
278                         is_first_recipient = FALSE;
279                 }
280         }
281         g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text, recpt_editor);
282
283 }
284
285
286 const gchar *
287 modest_recpt_editor_get_recipients (ModestRecptEditor *recpt_editor)
288 {
289         ModestRecptEditorPrivate *priv;
290         GtkTextBuffer *buffer = NULL;
291         GtkTextIter start, end;
292         gchar *c;
293
294         g_return_val_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor), NULL);
295         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
296
297         if (priv->recipients != NULL) {
298                 g_free (priv->recipients);
299                 priv->recipients = NULL;
300         }
301
302         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
303
304         gtk_text_buffer_get_start_iter (buffer, &start);
305         gtk_text_buffer_get_end_iter (buffer, &end);
306
307         priv->recipients = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
308         for (c = priv->recipients; *c != '\0'; c = g_utf8_next_char (c)) {
309                 if (*c == '\n') {
310                         *c = ' ';
311                 }
312         }
313
314         return priv->recipients;
315
316 }
317
318 static void
319 modest_recpt_editor_instance_init (GTypeInstance *instance, gpointer g_class)
320 {
321         ModestRecptEditorPrivate *priv;
322         GtkWidget *abook_icon;
323         GtkTextBuffer *buffer;
324
325         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (instance);
326
327         priv->abook_button = gtk_button_new ();
328         gtk_button_set_relief (GTK_BUTTON (priv->abook_button), GTK_RELIEF_NONE);
329         gtk_button_set_focus_on_click (GTK_BUTTON (priv->abook_button), FALSE);
330         GTK_WIDGET_UNSET_FLAGS (priv->abook_button, GTK_CAN_FOCUS);
331         gtk_button_set_alignment (GTK_BUTTON (priv->abook_button), 1.0, 1.0);
332         abook_icon = gtk_image_new_from_icon_name ("qgn_list_addressbook", GTK_ICON_SIZE_BUTTON);
333         gtk_container_add (GTK_CONTAINER (priv->abook_button), abook_icon);
334
335         priv->text_view = gtk_text_view_new ();
336         /* Auto-capitalization is the default, so let's turn it off: */
337 #ifdef MAEMO_CHANGES
338         hildon_gtk_text_view_set_input_mode (GTK_TEXT_VIEW (priv->text_view), 
339                 HILDON_GTK_INPUT_MODE_FULL);
340 #endif
341         
342         priv->recipients = NULL;
343
344         priv->scrolled_window = modest_scroll_text_new (GTK_TEXT_VIEW (priv->text_view), 1024);
345         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), GTK_POLICY_NEVER,
346                                         GTK_POLICY_AUTOMATIC);
347         gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (priv->scrolled_window), GTK_SHADOW_IN);
348 /*      gtk_container_add (GTK_CONTAINER (priv->scrolled_window), priv->text_view); */
349
350         gtk_box_pack_start (GTK_BOX (instance), priv->scrolled_window, TRUE, TRUE, 0);
351 /*      gtk_box_pack_start (GTK_BOX (instance), priv->text_view, TRUE, TRUE, 0); */
352         gtk_box_pack_end (GTK_BOX (instance), priv->abook_button, FALSE, FALSE, 0);
353
354         gtk_text_view_set_accepts_tab (GTK_TEXT_VIEW (priv->text_view), FALSE);
355         gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (priv->text_view), TRUE);
356         gtk_text_view_set_editable (GTK_TEXT_VIEW (priv->text_view), TRUE);
357
358         gtk_text_view_set_justification (GTK_TEXT_VIEW (priv->text_view), GTK_JUSTIFY_LEFT);
359         gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (priv->text_view), GTK_WRAP_CHAR);
360
361         gtk_widget_set_size_request (priv->text_view, 75, -1);
362
363         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
364         g_signal_connect (G_OBJECT (priv->abook_button), "clicked", G_CALLBACK (modest_recpt_editor_on_abook_clicked), instance);
365         g_signal_connect (G_OBJECT (priv->text_view), "button-release-event", G_CALLBACK (modest_recpt_editor_on_button_release_event), instance);
366         g_signal_connect (G_OBJECT (priv->text_view), "key-press-event", G_CALLBACK (modest_recpt_editor_on_key_press_event), instance);
367         g_signal_connect (G_OBJECT (priv->text_view), "focus-in-event", G_CALLBACK (modest_recpt_editor_on_focus_in), instance);
368         g_signal_connect (G_OBJECT (buffer), "insert-text", G_CALLBACK (modest_recpt_editor_on_insert_text), instance);
369
370 /*      gtk_container_set_focus_child (GTK_CONTAINER (instance), priv->text_view); */
371
372         return;
373 }
374
375 void
376 modest_recpt_editor_set_field_size_group (ModestRecptEditor *recpt_editor, GtkSizeGroup *size_group)
377 {
378         ModestRecptEditorPrivate *priv;
379
380         g_return_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor));
381         g_return_if_fail (GTK_IS_SIZE_GROUP (size_group));
382         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
383
384         gtk_size_group_add_widget (size_group, priv->scrolled_window);
385 }
386
387 GtkTextBuffer *
388 modest_recpt_editor_get_buffer (ModestRecptEditor *recpt_editor)
389 {
390         ModestRecptEditorPrivate *priv;
391
392         g_return_val_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor), NULL);
393         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
394
395         return gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
396 }
397
398 static void
399 modest_recpt_editor_on_abook_clicked (GtkButton *button, ModestRecptEditor *editor)
400 {
401         g_return_if_fail (MODEST_IS_RECPT_EDITOR (editor));
402
403         g_signal_emit_by_name (G_OBJECT (editor), "open-addressbook");
404 }
405
406 static gboolean
407 modest_recpt_editor_on_button_release_event (GtkWidget *widget,
408                                              GdkEventButton *event,
409                                              ModestRecptEditor *recpt_editor)
410 {
411         ModestRecptEditorPrivate *priv;
412         GtkTextIter location, start, end;
413         GtkTextMark *mark;
414         GtkTextBuffer *buffer;
415         GtkTextTag *tag;
416         gboolean selection_changed = FALSE;
417         
418         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
419
420         buffer = modest_recpt_editor_get_buffer (recpt_editor);
421         mark = gtk_text_buffer_get_insert (buffer);
422         gtk_text_buffer_get_iter_at_mark (buffer, &location, mark);
423
424         gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
425
426         tag = iter_has_recipient (&start);
427         if (tag != NULL)
428                 if (!gtk_text_iter_begins_tag (&start, tag)) {
429                         gtk_text_iter_backward_to_tag_toggle (&start, tag);
430                         selection_changed = TRUE;
431                 } 
432
433         tag = iter_has_recipient (&end);
434         if (tag != NULL) 
435                 if (!gtk_text_iter_ends_tag (&end, tag)) {
436                         gtk_text_iter_forward_to_tag_toggle (&end, tag);
437                         selection_changed = TRUE;
438                 }
439
440         if (selection_changed) {
441                 gtk_text_buffer_select_range (buffer, &start, &end);
442         } else {
443                 GTK_TEXT_VIEW (priv->text_view)->pending_place_cursor_button = 0;
444         }
445
446         return FALSE;
447 }
448
449 static void 
450 modest_recpt_editor_on_focus_in (GtkTextView *text_view,
451                                  GdkEventFocus *event,
452                                  ModestRecptEditor *editor)
453 {
454         ModestRecptEditorPrivate *priv = MODEST_RECPT_EDITOR_GET_PRIVATE (editor);
455         gtk_text_view_place_cursor_onscreen (GTK_TEXT_VIEW (priv->text_view));
456 }
457
458 static gboolean
459 is_valid_insert (const gchar *text, gint len)
460 {
461         gunichar c;
462         gunichar next_c;
463         gint i= 0;
464         gboolean quoted = FALSE;
465         const gchar *current, *next_current;
466         if (text == NULL)
467                 return TRUE;
468         current = text;
469
470         while (((len == -1)||(i < len)) && (*current != '\0')) {
471                 c = g_utf8_get_char (current);
472                 next_current = g_utf8_next_char (current);
473                 if (next_current && *next_current != '\0')
474                         next_c = g_utf8_get_char (g_utf8_next_char (current));
475                 else
476                         next_c = 0;
477                 if (!quoted && ((c == g_utf8_get_char(",") || c == g_utf8_get_char (";")))) {
478                         if ((next_c != 0) && (next_c != g_utf8_get_char ("\n")))
479                                 return FALSE;
480                         else {
481                           current = g_utf8_next_char (next_current);
482                           continue;
483                         }
484                 }
485                 if (c == 0x2022 || c == 0xfffc ||
486                     c == g_utf8_get_char ("\n") ||
487                     c == g_utf8_get_char ("\t"))
488                         return FALSE;
489                 if (c == g_utf8_get_char ("\""))
490                         quoted = !quoted;
491                 current = g_utf8_next_char (current);
492                 i = current - text;
493         }
494         return TRUE;
495 }
496
497 static gchar *
498 create_valid_text (const gchar *text, gint len)
499 {
500         gunichar c;
501         gunichar next_c;
502         gint i= 0;
503         GString *str;
504         gboolean quoted = FALSE;
505         const gchar *current, *next_current;
506
507         if (text == NULL)
508                 return NULL;
509
510         str = g_string_new ("");
511         current = text;
512
513         while (((len == -1)||(i < len)) && (*current != '\0')) {
514                 c = g_utf8_get_char (current);
515                 next_current = g_utf8_next_char (current);
516                 if (next_current && *next_current != '\0')
517                         next_c = g_utf8_get_char (g_utf8_next_char (current));
518                 else
519                         next_c = 0;
520                 if (c != 0x2022 && c != 0xfffc && 
521                     c != g_utf8_get_char ("\n") &&
522                     c != g_utf8_get_char ("\t"))
523                         str = g_string_append_unichar (str, c);
524                 if (!quoted && ((c == g_utf8_get_char(",") || c == g_utf8_get_char (";")))) {
525                         if ((next_c != 0) && (next_c != g_utf8_get_char ("\n")))
526                                 str = g_string_append_c (str, '\n');
527                 }
528                 if (c == g_utf8_get_char ("\""))
529                         quoted = !quoted;
530                 current = g_utf8_next_char (current);
531                 i = current - text;
532         }
533         return g_string_free (str, FALSE);
534 }
535
536 static void 
537 modest_recpt_editor_on_insert_text (GtkTextBuffer *buffer,
538                                     GtkTextIter *location,
539                                     gchar *text,
540                                     gint len,
541                                     ModestRecptEditor *editor)
542 {
543         GtkTextIter prev;
544         gunichar prev_char;
545         ModestRecptEditorPrivate *priv = MODEST_RECPT_EDITOR_GET_PRIVATE (editor);
546         
547         if (len > 1024)
548                 len = 1024;
549
550         if (!is_valid_insert (text, len)) {
551                 gchar *new_text = create_valid_text (text, len);
552                 g_signal_stop_emission_by_name (G_OBJECT (buffer), "insert-text");
553                 gtk_text_buffer_insert (buffer, location, new_text, -1);
554                 g_free (new_text);
555                 return;
556         }
557
558         if (iter_has_recipient (location)) {
559                 gtk_text_buffer_get_end_iter (buffer, location);
560                 gtk_text_buffer_place_cursor (buffer, location);
561         }
562
563         if (gtk_text_iter_is_start (location))
564                 return;
565
566         if (gtk_text_iter_is_end (location)) {
567                 prev = *location;
568                 if (!gtk_text_iter_backward_char (&prev))
569                         return;
570                 prev_char = gtk_text_iter_get_char (&prev);
571                 g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text, editor);
572                 if ((prev_char == ';'||prev_char == ',')&&(!quote_opened(location))) {
573                         GtkTextMark *insert;
574                         gtk_text_buffer_insert (buffer, location, "\n",-1);
575                         insert = gtk_text_buffer_get_insert (buffer);
576                         gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (priv->text_view), location, 0.0,TRUE, 0.0, 1.0);
577                 }
578                 g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text, editor);
579                 
580         }
581
582 }
583
584 static GtkTextTag *
585 iter_has_recipient (GtkTextIter *iter)
586 {
587         GSList *tags, *node;
588         GtkTextTag *result = NULL;
589
590         tags = gtk_text_iter_get_tags (iter);
591
592         for (node = tags; node != NULL; node = g_slist_next (node)) {
593                 GtkTextTag *tag = GTK_TEXT_TAG (node->data);
594
595                 if (g_object_get_data (G_OBJECT (tag), "recipient-tag-id")) {
596                         result = tag;
597                         break;
598                 }
599         }
600         g_slist_free (tags);
601         return result;
602 }
603
604 static GtkTextTag *
605 prev_iter_has_recipient (GtkTextIter *iter)
606 {
607         GtkTextIter prev;
608
609         prev = *iter;
610         gtk_text_iter_backward_char (&prev);
611         return iter_has_recipient (&prev);
612 }
613
614 /* static GtkTextTag * */
615 /* next_iter_has_recipient (GtkTextIter *iter) */
616 /* { */
617 /*      GtkTextIter next; */
618
619 /*      next = *iter; */
620 /*      return iter_has_recipient (&next); */
621 /* } */
622
623 static gunichar 
624 iter_previous_char (GtkTextIter *iter)
625 {
626         GtkTextIter prev;
627
628         prev = *iter;
629         gtk_text_iter_backward_char (&prev);
630         return gtk_text_iter_get_char (&prev);
631 }
632
633 /* static gunichar  */
634 /* iter_next_char (GtkTextIter *iter) */
635 /* { */
636 /*      GtkTextIter next; */
637
638 /*      next = *iter; */
639 /*      gtk_text_iter_forward_char (&next); */
640 /*      return gtk_text_iter_get_char (&next); */
641 /* } */
642
643 static void
644 select_tag_of_iter (GtkTextIter *iter, GtkTextTag *tag)
645 {
646         GtkTextIter start, end;
647
648         start = *iter;
649         if (!gtk_text_iter_begins_tag (&start, tag))
650                 gtk_text_iter_backward_to_tag_toggle (&start, tag);
651         end = *iter;
652         if (!gtk_text_iter_ends_tag (&end, tag))
653                 gtk_text_iter_forward_to_tag_toggle (&end, tag);
654         gtk_text_buffer_select_range (gtk_text_iter_get_buffer (iter), &start, &end);
655 }
656
657 static gboolean 
658 quote_opened (GtkTextIter *iter)
659 {
660         GtkTextIter start;
661         GtkTextBuffer *buffer;
662         gboolean opened = FALSE;
663
664         buffer = gtk_text_iter_get_buffer (iter);
665         gtk_text_buffer_get_start_iter (buffer, &start);
666
667         while (!gtk_text_iter_equal (&start, iter)) {
668                 gunichar current_char = gtk_text_iter_get_char (&start);
669                 if (current_char == '"')
670                         opened = !opened;
671                 else if (current_char == '\\')
672                         gtk_text_iter_forward_char (&start);
673                 if (!gtk_text_iter_equal (&start, iter))
674                         gtk_text_iter_forward_char (&start);
675                         
676         }
677         return opened;
678
679 }
680
681
682 static gboolean
683 modest_recpt_editor_on_key_press_event (GtkTextView *text_view,
684                                           GdkEventKey *key,
685                                           ModestRecptEditor *editor)
686 {
687         GtkTextMark *insert;
688         GtkTextMark *selection;
689         GtkTextBuffer * buffer;
690         GtkTextIter location, selection_loc;
691         GtkTextTag *tag;
692      
693         buffer = gtk_text_view_get_buffer (text_view);
694         insert = gtk_text_buffer_get_insert (buffer);
695         selection = gtk_text_buffer_get_selection_bound (buffer);
696
697         /* cases to cover:
698          *    * cursor is on resolved recipient:
699          *        - right should go to first character after the recipient (usually ; or ,)
700          *        - left should fo to the first character before the recipient
701          *        - return should run check names on the recipient.
702          *    * cursor is just after a recipient:
703          *        - right should go to the next character. If it's a recipient, should select
704          *          it
705          *        - left should go to the previous character. If it's a recipient, should go
706          *          to the first character of the recipient, and select it.
707          *    * cursor is on arbitrary text:
708          *        - return should add a ; and go to the next line
709          *        - left or right standard ones.
710          *    * cursor is after a \n:
711          *        - left should go to the character before the \n (as if \n was not a character)
712          *    * cursor is before a \n:
713          *        - right should go to the character after the \n
714          */
715
716         gtk_text_buffer_get_iter_at_mark (buffer, &location, insert);
717         gtk_text_buffer_get_iter_at_mark (buffer, &selection_loc, selection);
718
719         switch (key->keyval) {
720         case GDK_Left:
721         case GDK_KP_Left: 
722         {
723                 gboolean cursor_ready = FALSE;
724                 while (!cursor_ready) {
725                         if (iter_previous_char (&location) == '\n') {
726                                 gtk_text_iter_backward_char (&location);
727                         } else {
728                                 cursor_ready = TRUE;
729                         }
730                 }
731                 tag = iter_has_recipient (&location);
732                 if ((tag != NULL)&& (gtk_text_iter_is_start (&location) || !(gtk_text_iter_begins_tag (&location, tag)))) {
733                         select_tag_of_iter (&location, tag);
734                         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (text_view), insert, 0.0, FALSE, 0.0, 1.0);
735                         return TRUE;
736                 }
737                 gtk_text_iter_backward_char (&location);
738                 tag = iter_has_recipient (&location);
739                 if (tag != NULL)
740                         select_tag_of_iter (&location, tag);
741                 else {
742                         gtk_text_buffer_place_cursor (buffer, &location);
743                 }
744                 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (text_view), insert, 0.0, FALSE, 0.0, 1.0);
745
746                 return TRUE;
747         }
748         break;
749         case GDK_Right:
750         case GDK_KP_Right:
751         {
752                 gboolean cursor_moved = FALSE;
753
754                 tag = iter_has_recipient (&location);
755                 if ((tag != NULL)&&(!gtk_text_iter_ends_tag (&location, tag))) {
756                         gtk_text_iter_forward_to_tag_toggle (&location, tag);
757                         while (gtk_text_iter_get_char (&location) == '\n')
758                                 gtk_text_iter_forward_char (&location);
759                         gtk_text_buffer_place_cursor (buffer, &location);
760                         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (text_view), insert, 0.0, FALSE, 0.0, 1.0);
761                         return TRUE;
762                 }
763
764                 while (gtk_text_iter_get_char (&location) == '\n') {
765                         gtk_text_iter_forward_char (&location);
766                         cursor_moved = TRUE;
767                 }
768                 if (!cursor_moved)
769                         gtk_text_iter_forward_char (&location);
770                 while (gtk_text_iter_get_char (&location) == '\n') {
771                         gtk_text_iter_forward_char (&location);
772                         cursor_moved = TRUE;
773                 }
774
775                 tag = iter_has_recipient (&location);
776                 if (tag != NULL)
777                         select_tag_of_iter (&location, tag);
778                 else
779                         gtk_text_buffer_place_cursor (buffer, &location);
780                 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (text_view), insert, 0.0, FALSE, 0.0, 1.0);
781                 return TRUE;
782         }
783         break;
784         case GDK_Return:
785         case GDK_KP_Enter:
786         {
787                 gint insert_offset, selection_offset;
788                 insert_offset = gtk_text_iter_get_offset (&location);
789                 selection_offset = gtk_text_iter_get_offset (&selection_loc);
790                 g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text, editor);
791                 if (selection_offset > insert_offset)
792                         location = selection_loc;
793                 tag = iter_has_recipient (&location);
794                 if (tag != NULL) {
795                         gtk_text_buffer_get_end_iter (buffer, &location);
796                         gtk_text_buffer_place_cursor (buffer, &location);
797                         if ((iter_previous_char (&location) != ';')&&(iter_previous_char (&location) != ','))
798                                 gtk_text_buffer_insert_at_cursor (buffer, ";", -1);
799                         gtk_text_buffer_insert_at_cursor (buffer, "\n", -1);
800                 } else {
801                         gunichar prev_char = iter_previous_char (&location);
802                         if ((gtk_text_iter_is_start (&location))||(prev_char == '\n')
803                             ||(prev_char == ';')||(prev_char == ',')) 
804                                 g_signal_emit_by_name (G_OBJECT (editor), "open-addressbook");
805                         else {
806                                 if ((prev_char != ';') && (prev_char != ','))
807                                         gtk_text_buffer_insert_at_cursor (buffer, ";", -1);
808                                 gtk_text_buffer_insert_at_cursor (buffer, "\n", -1);
809                         }
810                 }
811                 g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text, editor);
812                 return TRUE;
813         }
814         break;
815         case GDK_BackSpace:
816         {
817                 #if GTK_CHECK_VERSION(2, 10, 0) /* gtk_text_buffer_get_has_selection is only available in GTK+ 2.10 */
818                 if (gtk_text_buffer_get_has_selection (buffer)) {
819                         gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
820                         return TRUE;
821                 }
822                 #else
823                 if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
824                         gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
825                         return TRUE;
826                 }
827                 #endif
828
829                 tag = prev_iter_has_recipient (&location);
830                 if (tag != NULL) {
831                         GtkTextIter iter_in_tag;
832                         iter_in_tag = location;
833                         gtk_text_iter_backward_char (&iter_in_tag);
834                         select_tag_of_iter (&iter_in_tag, tag);
835                         gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
836                         return TRUE;
837                 }
838                 return FALSE;
839         }
840         break;
841         default:
842                 return FALSE;
843         }
844 }
845
846 static void 
847 modest_recpt_editor_move_cursor_to_end (ModestRecptEditor *editor)
848 {
849         ModestRecptEditorPrivate *priv = MODEST_RECPT_EDITOR_GET_PRIVATE (editor);
850         GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
851         GtkTextIter start, end;
852
853         gtk_text_buffer_get_end_iter (buffer, &start);
854         end = start;
855         gtk_text_buffer_select_range (buffer, &start, &end);
856
857
858 }
859
860 void
861 modest_recpt_editor_grab_focus (ModestRecptEditor *recpt_editor)
862 {
863         ModestRecptEditorPrivate *priv;
864         
865         g_return_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor));
866         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
867
868         gtk_widget_grab_focus (priv->text_view);
869 }
870
871 gboolean
872 modest_recpt_editor_has_focus (ModestRecptEditor *recpt_editor)
873 {
874         ModestRecptEditorPrivate *priv;
875         
876         g_return_val_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor), FALSE);
877         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
878
879         return gtk_widget_is_focus (priv->text_view);
880 }
881
882 static void
883 modest_recpt_editor_finalize (GObject *object)
884 {
885         ModestRecptEditorPrivate *priv;
886         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (object);
887
888         if (priv->recipients) {
889                 g_free (priv->recipients);
890                 priv->recipients = NULL;
891         }
892
893         (*parent_class->finalize) (object);
894
895         return;
896 }
897
898 static void 
899 modest_recpt_editor_class_init (ModestRecptEditorClass *klass)
900 {
901         GObjectClass *object_class;
902         GtkWidgetClass *widget_class;
903
904         parent_class = g_type_class_peek_parent (klass);
905         object_class = (GObjectClass*) klass;
906         widget_class = GTK_WIDGET_CLASS (klass);
907
908         object_class->finalize = modest_recpt_editor_finalize;
909
910         g_type_class_add_private (object_class, sizeof (ModestRecptEditorPrivate));
911
912         signals[OPEN_ADDRESSBOOK_SIGNAL] = 
913                 g_signal_new ("open-addressbook",
914                               G_TYPE_FROM_CLASS (object_class),
915                               G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
916                               G_STRUCT_OFFSET (ModestRecptEditorClass, open_addressbook),
917                               NULL, NULL,
918                               g_cclosure_marshal_VOID__VOID,
919                               G_TYPE_NONE, 0);
920
921         return;
922 }
923
924 GType 
925 modest_recpt_editor_get_type (void)
926 {
927         static GType type = 0;
928
929         if (G_UNLIKELY(type == 0))
930         {
931                 static const GTypeInfo info = 
932                 {
933                   sizeof (ModestRecptEditorClass),
934                   NULL,   /* base_init */
935                   NULL,   /* base_finalize */
936                   (GClassInitFunc) modest_recpt_editor_class_init,   /* class_init */
937                   NULL,   /* class_finalize */
938                   NULL,   /* class_data */
939                   sizeof (ModestRecptEditor),
940                   0,      /* n_preallocs */
941                   modest_recpt_editor_instance_init    /* instance_init */
942                 };
943
944                 type = g_type_register_static (GTK_TYPE_HBOX,
945                         "ModestRecptEditor",
946                         &info, 0);
947
948         }
949
950         return type;
951 }