Lots of fixes on selection behavior in recipient editor (fixes NB#133776).
[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 #include <modest-ui-constants.h>
47
48 /* FIXNE: we should have no maemo-deps in widgets/ */
49 #ifndef MODEST_TOOLKIT_GTK
50 #include "modest-hildon-includes.h"
51 #endif /*!MODEST_TOOLKIT_GTK*/
52
53 #define RECPT_BUTTON_WIDTH_HILDON2 118
54
55
56 static GObjectClass *parent_class = NULL;
57
58 #define RECIPIENT_TAG_ID "recpt-id"
59
60 /* signals */
61 enum {
62         OPEN_ADDRESSBOOK_SIGNAL,
63         LAST_SIGNAL
64 };
65
66 typedef struct _ModestRecptEditorPrivate ModestRecptEditorPrivate;
67
68 struct _ModestRecptEditorPrivate
69 {
70         GtkWidget *text_view;
71         GtkWidget *abook_button;
72         GtkWidget *scrolled_window;
73         gchar *recipients;
74         gulong on_mark_set_handler;
75         gboolean show_abook;
76 };
77
78 #define MODEST_RECPT_EDITOR_GET_PRIVATE(o)      \
79         (G_TYPE_INSTANCE_GET_PRIVATE ((o), MODEST_TYPE_RECPT_EDITOR, ModestRecptEditorPrivate))
80
81 static guint signals[LAST_SIGNAL] = {0};
82
83 /* static functions: GObject */
84 static void modest_recpt_editor_instance_init (GTypeInstance *instance, gpointer g_class);
85 static void modest_recpt_editor_finalize (GObject *object);
86 static void modest_recpt_editor_class_init (ModestRecptEditorClass *klass);
87
88 /* widget events */
89 static void modest_recpt_editor_on_abook_clicked (GtkButton *button,
90                                                   ModestRecptEditor *editor);
91 static void modest_recpt_editor_add_tags (ModestRecptEditor *editor,
92                                           const gchar * recipient_id);
93 static gboolean modest_recpt_editor_on_focus_in (GtkTextView *text_view,
94                                              GdkEventFocus *event,
95                                              ModestRecptEditor *editor);
96 static void modest_recpt_editor_on_mark_set (GtkTextBuffer *buffer,
97                                              GtkTextIter *iter,
98                                              GtkTextMark *mark,
99                                              ModestRecptEditor *editor);
100 static void modest_recpt_editor_on_insert_text (GtkTextBuffer *buffer,
101                                                 GtkTextIter *location,
102                                                 gchar *text,
103                                                 gint len,
104                                                 ModestRecptEditor *editor);
105 static void modest_recpt_editor_on_insert_text_after (GtkTextBuffer *buffer,
106                                                       GtkTextIter *location,
107                                                       gchar *text,
108                                                       gint len,
109                                                       ModestRecptEditor *editor);
110 static gboolean modest_recpt_editor_on_key_press_event (GtkTextView *text_view,
111                                                           GdkEventKey *key,
112                                                           ModestRecptEditor *editor);
113 static GtkTextTag *iter_has_recipient (GtkTextIter *iter);
114 static gunichar iter_previous_char (GtkTextIter *iter);
115 /* static gunichar iter_next_char (GtkTextIter *iter); */
116 static GtkTextTag *prev_iter_has_recipient (GtkTextIter *iter);
117 /* static GtkTextTag *next_iter_has_recipient (GtkTextIter *iter); */
118 static void select_tag_of_iter (GtkTextIter *iter, GtkTextTag *tag, gboolean grow, gboolean left_not_right);
119 static gboolean quote_opened (GtkTextIter *iter);
120 static gboolean is_valid_insert (const gchar *text, gint len);
121 static gchar *create_valid_text (const gchar *text, gint len);
122
123 /**
124  * modest_recpt_editor_new:
125  *
126  * Return value: a new #ModestRecptEditor instance implemented for Gtk+
127  **/
128 GtkWidget*
129 modest_recpt_editor_new (void)
130 {
131         ModestRecptEditor *self = g_object_new (MODEST_TYPE_RECPT_EDITOR, 
132                                                 "homogeneous", FALSE,
133                                                 "spacing", MODEST_MARGIN_NONE,
134                                                 NULL);
135
136         return GTK_WIDGET (self);
137 }
138
139 void
140 modest_recpt_editor_set_recipients (ModestRecptEditor *recpt_editor, const gchar *recipients)
141 {
142         ModestRecptEditorPrivate *priv;
143         GtkTextBuffer *buffer = NULL;
144         gchar *valid_recipients = NULL;
145
146         g_return_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor));
147         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
148
149 #ifdef MODEST_TOOLKIT_HILDON2
150         buffer = hildon_text_view_get_buffer (HILDON_TEXT_VIEW (priv->text_view));
151 #else
152         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
153 #endif
154
155         valid_recipients = create_valid_text (recipients, -1);
156         g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text, recpt_editor);
157         g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text_after, recpt_editor);
158         gtk_text_buffer_set_text (buffer, valid_recipients, -1);
159         g_free (valid_recipients);
160         if (GTK_WIDGET_REALIZED (recpt_editor))
161                 gtk_widget_queue_resize (GTK_WIDGET (recpt_editor));
162         g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text, recpt_editor);
163         g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text_after, recpt_editor);
164
165 }
166
167 void
168 modest_recpt_editor_add_recipients (ModestRecptEditor *recpt_editor, const gchar *recipients)
169 {
170         ModestRecptEditorPrivate *priv;
171         GtkTextBuffer *buffer = NULL;
172         GtkTextIter iter;
173         gchar *string_to_add;
174
175         g_return_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor));
176         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
177
178         if (recipients == NULL)
179                 return;
180
181 #ifdef MODEST_TOOLKIT_HILDON2
182         buffer = hildon_text_view_get_buffer (HILDON_TEXT_VIEW (priv->text_view));
183 #else
184         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
185 #endif
186
187         if (gtk_text_buffer_get_char_count (buffer) > 0) {
188                 string_to_add = g_strconcat (";\n", recipients, NULL);
189         } else {
190                 string_to_add = g_strdup (recipients);
191         }
192
193         gtk_text_buffer_get_end_iter (buffer, &iter);
194
195         g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text, recpt_editor);
196         g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text_after, recpt_editor);
197
198         gtk_text_buffer_insert (buffer, &iter, string_to_add, -1);
199
200         g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text, recpt_editor);
201         g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text_after, recpt_editor);
202
203         if (GTK_WIDGET_REALIZED (recpt_editor))
204                 gtk_widget_queue_resize (GTK_WIDGET (recpt_editor));
205
206         g_free (string_to_add);
207 }
208
209 void 
210 modest_recpt_editor_add_resolved_recipient (ModestRecptEditor *recpt_editor, GSList *email_list, const gchar * recipient_id)
211 {
212         ModestRecptEditorPrivate *priv;
213         GtkTextBuffer *buffer = NULL;
214         GtkTextIter start, end, iter;
215         GSList *node;
216         gboolean is_first_recipient = TRUE;
217       
218         g_return_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor));
219         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
220
221 #ifdef MODEST_TOOLKIT_HILDON2
222         buffer = hildon_text_view_get_buffer (HILDON_TEXT_VIEW (priv->text_view));
223 #else
224         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
225 #endif
226
227         g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text, recpt_editor);
228         g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text_after, recpt_editor);
229         gtk_text_buffer_get_bounds (buffer, &start, &end);
230         if (gtk_text_buffer_get_char_count (buffer) > 0) {
231                 gchar * buffer_contents;
232
233                 gtk_text_buffer_get_bounds (buffer, &start, &end);
234                 buffer_contents = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
235                 g_strstrip (buffer_contents);
236                 if (!g_str_has_suffix (buffer_contents, "\n")) {
237                         if (g_str_has_suffix (buffer_contents, ";")||(g_str_has_suffix (buffer_contents, ",")))
238                                 gtk_text_buffer_insert (buffer, &end, "\n", -1);
239                         else
240                                 gtk_text_buffer_insert (buffer, &end, ";\n", -1);
241                 }
242                 g_free (buffer_contents);
243         }
244
245         gtk_text_buffer_get_end_iter (buffer, &iter);
246
247         for (node = email_list; node != NULL; node = g_slist_next (node)) {
248                 gchar *recipient = (gchar *) node->data;
249
250                 if ((recipient) && (strlen (recipient) != 0)) {
251
252                         if (!is_first_recipient)
253                                 gtk_text_buffer_insert (buffer, &iter, "\n", -1);
254
255                         gtk_text_buffer_insert (buffer, &iter, recipient, -1);
256                         gtk_text_buffer_insert (buffer, &iter, ";", -1);
257                         is_first_recipient = FALSE;
258                 }
259         }
260         modest_recpt_editor_add_tags (recpt_editor, recipient_id);
261
262         g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text, recpt_editor);
263         g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text_after, recpt_editor);
264 }
265
266 void 
267 modest_recpt_editor_replace_with_resolved_recipient (ModestRecptEditor *recpt_editor, 
268                                                      GtkTextIter *start, GtkTextIter *end,
269                                                      GSList *email_list, const gchar * recipient_id)
270 {
271         GSList *email_lists_list;
272         GSList *recipient_ids_list;
273
274         email_lists_list = g_slist_append (NULL, email_list);
275         recipient_ids_list = g_slist_append (NULL, (gpointer) recipient_id);
276
277         modest_recpt_editor_replace_with_resolved_recipients (recpt_editor, start, end,
278                                                               email_lists_list, recipient_ids_list);
279
280         g_slist_free (email_lists_list);
281         g_slist_free (recipient_ids_list);
282
283 }
284
285 void 
286 modest_recpt_editor_replace_with_resolved_recipients (ModestRecptEditor *recpt_editor, 
287                                                      GtkTextIter *start, GtkTextIter *end,
288                                                      GSList *email_lists_list, GSList * recipient_ids_list)
289 {
290         ModestRecptEditorPrivate *priv;
291         GtkTextBuffer *buffer;
292         GtkTextTag *tag;
293         GSList *node;
294         gboolean is_first_recipient = TRUE;
295
296         g_return_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor));
297         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
298
299 #ifdef MODEST_TOOLKIT_HILDON2
300         buffer = hildon_text_view_get_buffer (HILDON_TEXT_VIEW (priv->text_view));
301 #else
302         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
303 #endif
304         g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text, recpt_editor);
305         g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text_after, recpt_editor);
306
307         gtk_text_buffer_delete (buffer, start, end);
308
309         while (email_lists_list && recipient_ids_list) {
310                 gchar *recipient_id = (gchar *) recipient_ids_list->data;
311                 GSList *email_list = (GSList *) email_lists_list->data;
312
313                 tag = gtk_text_buffer_create_tag (buffer, NULL, 
314                                                   "underline", PANGO_UNDERLINE_SINGLE,
315                                                   "wrap-mode", GTK_WRAP_NONE,
316                                                   "editable", TRUE, NULL);
317
318                 g_object_set_data (G_OBJECT (tag), "recipient-tag-id", GINT_TO_POINTER (RECIPIENT_TAG_ID));
319                 g_object_set_data_full (G_OBJECT (tag), "recipient-id", g_strdup (recipient_id), (GDestroyNotify) g_free);
320
321                 for (node = email_list; node != NULL; node = g_slist_next (node)) {
322                         gchar *recipient = (gchar *) node->data;
323
324                         if ((recipient) && (strlen (recipient) != 0)) {
325
326                                 if (!is_first_recipient || !gtk_text_iter_is_start(start))
327                                         gtk_text_buffer_insert (buffer, start, "\n", -1);
328
329                                 gtk_text_buffer_insert_with_tags (buffer, start, recipient, -1, tag, NULL);
330
331                                 if (node->next != NULL)
332                                         gtk_text_buffer_insert (buffer, start, ";", -1);
333                                 is_first_recipient = FALSE;
334                         }
335                 }
336
337                 email_lists_list = g_slist_next (email_lists_list);
338                 recipient_ids_list = g_slist_next (recipient_ids_list);
339
340                 /* Add a separator between lists of emails*/
341                 if (recipient_ids_list)
342                         gtk_text_buffer_insert (buffer, start, ";", -1);
343         }
344         g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text, recpt_editor);
345         g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text_after, recpt_editor);
346
347 }
348
349
350 const gchar *
351 modest_recpt_editor_get_recipients (ModestRecptEditor *recpt_editor)
352 {
353         ModestRecptEditorPrivate *priv;
354         GtkTextBuffer *buffer = NULL;
355         GtkTextIter start, end;
356
357         g_return_val_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor), NULL);
358         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
359
360         if (priv->recipients != NULL) {
361                 g_free (priv->recipients);
362                 priv->recipients = NULL;
363         }
364
365 #ifdef MODEST_TOOKIT_HILDON2
366         buffer = hildon_text_view_get_buffer (HILDON_TEXT_VIEW (priv->text_view));
367 #else
368         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
369 #endif
370
371         gtk_text_buffer_get_start_iter (buffer, &start);
372         gtk_text_buffer_get_end_iter (buffer, &end);
373
374         priv->recipients = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
375
376         return priv->recipients;
377
378 }
379
380 static void
381 modest_recpt_editor_instance_init (GTypeInstance *instance, gpointer g_class)
382 {
383         ModestRecptEditorPrivate *priv;
384         GtkWidget *abook_icon;
385         GtkWidget *abook_align;
386         GtkTextBuffer *buffer;
387
388         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (instance);
389
390         priv->show_abook = TRUE;
391         priv->abook_button = gtk_button_new ();
392         gtk_widget_set_no_show_all (GTK_WIDGET (priv->abook_button), TRUE);
393         gtk_widget_show (priv->abook_button);
394 #ifdef MODEST_TOOLKIT_HILDON2
395         gtk_widget_set_size_request (priv->abook_button, RECPT_BUTTON_WIDTH_HILDON2, -1);
396 #else
397         gtk_button_set_relief (GTK_BUTTON (priv->abook_button), GTK_RELIEF_NONE);
398 #endif
399         gtk_button_set_focus_on_click (GTK_BUTTON (priv->abook_button), FALSE);
400         GTK_WIDGET_UNSET_FLAGS (priv->abook_button, GTK_CAN_FOCUS);
401         gtk_button_set_alignment (GTK_BUTTON (priv->abook_button), 0.5, 0.5);
402 #ifdef MODEST_TOOLKIT_HILDON2
403         abook_icon = gtk_image_new_from_icon_name ("general_contacts", HILDON_ICON_SIZE_FINGER);
404 #else
405         abook_icon = gtk_image_new_from_icon_name ("qgn_list_addressbook", GTK_ICON_SIZE_BUTTON);
406 #endif
407         gtk_container_add (GTK_CONTAINER (priv->abook_button), abook_icon);
408
409 #ifdef MODEST_TOOLKIT_HILDON2
410         priv->text_view = hildon_text_view_new ();
411 #else
412         priv->text_view = gtk_text_view_new ();
413 #endif
414         /* Auto-capitalization is the default, so let's turn it off: */
415 #ifdef MAEMO_CHANGES
416         hildon_gtk_text_view_set_input_mode (GTK_TEXT_VIEW (priv->text_view), 
417                 HILDON_GTK_INPUT_MODE_FULL);
418 #endif
419         
420         priv->recipients = NULL;
421
422 #ifdef MODEST_TOOLKIT_HILDON2
423         priv->scrolled_window = NULL;
424         gtk_box_pack_start (GTK_BOX (instance), priv->text_view, TRUE, TRUE, 0);
425 #else
426         priv->scrolled_window = modest_scroll_text_new (GTK_TEXT_VIEW (priv->text_view), 1024);
427         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), GTK_POLICY_NEVER,
428                                         GTK_POLICY_AUTOMATIC);
429         gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (priv->scrolled_window), GTK_SHADOW_IN);
430 /*      gtk_container_add (GTK_CONTAINER (priv->scrolled_window), priv->text_view); */
431
432         gtk_box_pack_start (GTK_BOX (instance), priv->scrolled_window, TRUE, TRUE, 0);
433 /*      gtk_box_pack_start (GTK_BOX (instance), priv->text_view, TRUE, TRUE, 0); */
434 #endif
435         abook_align = gtk_alignment_new (0.0, 0.0, 1.0, 1.0);
436         gtk_alignment_set_padding (GTK_ALIGNMENT (abook_align), 0, 0, MODEST_MARGIN_DEFAULT, 0);
437         gtk_container_add (GTK_CONTAINER (abook_align), priv->abook_button);
438         gtk_box_pack_end (GTK_BOX (instance), abook_align, FALSE, FALSE, 0);
439
440         gtk_text_view_set_accepts_tab (GTK_TEXT_VIEW (priv->text_view), FALSE);
441         gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (priv->text_view), TRUE);
442         gtk_text_view_set_editable (GTK_TEXT_VIEW (priv->text_view), TRUE);
443
444         gtk_text_view_set_justification (GTK_TEXT_VIEW (priv->text_view), GTK_JUSTIFY_LEFT);
445         gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (priv->text_view), GTK_WRAP_CHAR);
446
447         gtk_widget_set_size_request (priv->text_view, 75, -1);
448
449 #ifdef MODEST_TOOLKIT_HILDON2
450         buffer = hildon_text_view_get_buffer (HILDON_TEXT_VIEW (priv->text_view));
451 #else
452         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
453 #endif
454         g_signal_connect (G_OBJECT (priv->abook_button), "clicked", G_CALLBACK (modest_recpt_editor_on_abook_clicked), instance);
455         g_signal_connect (G_OBJECT (priv->text_view), "key-press-event", G_CALLBACK (modest_recpt_editor_on_key_press_event), instance);
456         g_signal_connect (G_OBJECT (priv->text_view), "focus-in-event", G_CALLBACK (modest_recpt_editor_on_focus_in), instance);
457         g_signal_connect (G_OBJECT (buffer), "insert-text",
458                           G_CALLBACK (modest_recpt_editor_on_insert_text),
459                           instance);
460         g_signal_connect_after (G_OBJECT (buffer), "insert-text",
461                                 G_CALLBACK (modest_recpt_editor_on_insert_text_after),
462                                 instance);
463
464         priv->on_mark_set_handler = g_signal_connect (G_OBJECT (buffer), "mark-set", 
465                                                       G_CALLBACK (modest_recpt_editor_on_mark_set), 
466                                                       instance);
467
468         return;
469 }
470
471 void
472 modest_recpt_editor_set_field_size_group (ModestRecptEditor *recpt_editor, GtkSizeGroup *size_group)
473 {
474         ModestRecptEditorPrivate *priv;
475
476         g_return_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor));
477         g_return_if_fail (GTK_IS_SIZE_GROUP (size_group));
478         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
479
480 #ifdef MODEST_TOOLKIT_HILDON2
481         gtk_size_group_add_widget (size_group, priv->text_view);
482 #else
483         gtk_size_group_add_widget (size_group, priv->scrolled_window);
484 #endif
485 }
486
487 GtkTextBuffer *
488 modest_recpt_editor_get_buffer (ModestRecptEditor *recpt_editor)
489 {
490         ModestRecptEditorPrivate *priv;
491
492         g_return_val_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor), NULL);
493         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
494
495 #ifdef MODEST_TOOLKIT_HILDON2
496         return hildon_text_view_get_buffer (HILDON_TEXT_VIEW (priv->text_view));
497 #else
498         return gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
499 #endif
500 }
501
502 static void
503 modest_recpt_editor_on_abook_clicked (GtkButton *button, ModestRecptEditor *editor)
504 {
505         g_return_if_fail (MODEST_IS_RECPT_EDITOR (editor));
506
507         g_signal_emit_by_name (G_OBJECT (editor), "open-addressbook");
508 }
509
510 static void
511 modest_recpt_editor_on_mark_set (GtkTextBuffer *buffer,
512                                  GtkTextIter *iter,
513                                  GtkTextMark *mark,
514                                  ModestRecptEditor *recpt_editor)
515 {
516         ModestRecptEditorPrivate *priv;
517         GtkTextIter insert_iter, selection_iter;
518         GtkTextMark *selection_bound;
519         GtkTextMark *insert;
520         GtkTextTag *tag;
521         gboolean selection_changed = FALSE;
522         gboolean select_to_left;
523
524         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
525
526         buffer = modest_recpt_editor_get_buffer (recpt_editor);
527         selection_bound = gtk_text_buffer_get_selection_bound (buffer);
528         insert = gtk_text_buffer_get_insert (buffer);
529
530         if (mark != selection_bound && mark != insert)
531                 return;
532
533         gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter, insert);
534         gtk_text_buffer_get_iter_at_mark (buffer, &selection_iter, selection_bound);
535
536         select_to_left = gtk_text_iter_get_offset (&selection_iter) > gtk_text_iter_get_offset (&insert_iter);
537
538         tag = iter_has_recipient (&insert_iter);
539         if (tag) {
540                 if (select_to_left) {
541                         if (!gtk_text_iter_begins_tag (&insert_iter, tag)) {
542                                 gtk_text_iter_backward_to_tag_toggle (&insert_iter, tag);
543                                 selection_changed = TRUE;
544                         }
545                 } else {
546                         gtk_text_iter_forward_to_tag_toggle (&insert_iter, tag);
547                         selection_changed = TRUE;
548                 }
549         }
550                 
551         tag = iter_has_recipient (&selection_iter);
552         if (tag != NULL) {
553                 if (select_to_left) {
554                         gtk_text_iter_forward_to_tag_toggle (&selection_iter, tag);
555                         selection_changed = TRUE;
556                 } else {
557                         if (!gtk_text_iter_begins_tag (&selection_iter, tag)) {
558                                 gtk_text_iter_backward_to_tag_toggle (&selection_iter, tag);
559                                 selection_changed = TRUE;
560                         }
561                 }
562         }
563         
564         if (selection_changed) {
565                 /* We block this signal handler in order to prevent a
566                    stack overflow caused by recursive calls to this
567                    handler as the select_range call could issue a
568                    "mark-set" signal */
569                 g_signal_handler_block (buffer, priv->on_mark_set_handler);
570                 gtk_text_buffer_select_range (buffer, &insert_iter, &selection_iter);
571                 g_signal_handler_unblock (buffer, priv->on_mark_set_handler);
572         }
573 }
574
575 static gboolean 
576 modest_recpt_editor_on_focus_in (GtkTextView *text_view,
577                                  GdkEventFocus *event,
578                                  ModestRecptEditor *editor)
579 {
580         ModestRecptEditorPrivate *priv = MODEST_RECPT_EDITOR_GET_PRIVATE (editor);
581         gtk_text_view_place_cursor_onscreen (GTK_TEXT_VIEW (priv->text_view));
582
583         return FALSE;
584 }
585
586 static gboolean
587 is_valid_insert (const gchar *text, gint len)
588 {
589         gunichar c;
590         gunichar next_c;
591         gint i= 0;
592         gboolean quoted = FALSE;
593         const gchar *current, *next_current;
594         if (text == NULL)
595                 return TRUE;
596         current = text;
597
598         while (((len == -1)||(i < len)) && (*current != '\0')) {
599                 c = g_utf8_get_char (current);
600                 next_current = g_utf8_next_char (current);
601                 if (next_current && *next_current != '\0')
602                         next_c = g_utf8_get_char (g_utf8_next_char (current));
603                 else
604                         next_c = 0;
605                 if (!quoted && ((c == g_utf8_get_char(",") || c == g_utf8_get_char (";")))) {
606                         if ((next_c != 0) && (next_c != g_utf8_get_char ("\n")))
607                                 return FALSE;
608                         else {
609                           current = g_utf8_next_char (next_current);
610                           continue;
611                         }
612                 }
613                 if (c == 0x2022 || c == 0xfffc ||
614                     c == g_utf8_get_char ("\n") ||
615                     c == g_utf8_get_char ("\t"))
616                         return FALSE;
617                 if (c == g_utf8_get_char ("\""))
618                         quoted = !quoted;
619                 current = g_utf8_next_char (current);
620                 i = current - text;
621         }
622         return TRUE;
623 }
624
625 static gchar *
626 create_valid_text (const gchar *text, gint len)
627 {
628         gunichar c;
629         gunichar next_c;
630         gint i= 0;
631         GString *str;
632         gboolean quoted = FALSE;
633         const gchar *current, *next_current;
634
635         if (text == NULL)
636                 return NULL;
637
638         str = g_string_new ("");
639         current = text;
640
641         while (((len == -1)||(i < len)) && (*current != '\0')) {
642                 c = g_utf8_get_char (current);
643                 next_current = g_utf8_next_char (current);
644                 if (next_current && *next_current != '\0')
645                         next_c = g_utf8_get_char (g_utf8_next_char (current));
646                 else
647                         next_c = 0;
648                 if (c != 0x2022 && c != 0xfffc &&
649                     c != g_utf8_get_char ("\n") &&
650                     c != g_utf8_get_char ("\t"))
651                         str = g_string_append_unichar (str, c);
652                 if (!quoted && ((c == g_utf8_get_char(",") || c == g_utf8_get_char (";")))) {
653                         if ((next_c != 0) && (next_c != g_utf8_get_char ("\n"))) {
654                                 gchar *last_separator = MAX (g_utf8_strrchr(str->str, -1, g_utf8_get_char (",")),
655                                                              g_utf8_strrchr(str->str, -1, g_utf8_get_char (";")));
656                                 if (last_separator) {
657                                         gchar *last_at = g_utf8_strrchr (str->str, -1, g_utf8_get_char ("@"));
658                                         if (last_at) {
659                                                 if (last_at < last_separator)
660                                                         str = g_string_append_c (str, '\n');
661                                         }
662                                 } else {
663                                         if (g_utf8_strrchr (str->str, -1, g_utf8_get_char ("@")))
664                                                 str = g_string_append_c (str, '\n');
665                                 }
666                         }
667                 }
668                 if (c == g_utf8_get_char ("\""))
669                         quoted = !quoted;
670                 current = g_utf8_next_char (current);
671                 i = current - text;
672         }
673
674         return g_string_free (str, FALSE);
675 }
676
677 /* Called after the default handler, and thus after the text was
678    inserted. We use this to insert a break after a ',' or a ';'*/
679 static void
680 modest_recpt_editor_on_insert_text_after (GtkTextBuffer *buffer,
681                                           GtkTextIter *location,
682                                           gchar *text,
683                                           gint len,
684                                           ModestRecptEditor *editor)
685 {
686         GtkTextIter prev;
687         gunichar prev_char;
688         ModestRecptEditorPrivate *priv = MODEST_RECPT_EDITOR_GET_PRIVATE (editor);
689
690         prev = *location;
691         /* We must go backwards twice as location points to the next
692            valid position to insert text */
693         if (!gtk_text_iter_backward_chars (&prev, 2))
694                 return;
695
696         prev_char = gtk_text_iter_get_char (&prev);
697         g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text, editor);
698         g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text_after, editor);
699         if ((prev_char == ';'||prev_char == ',')&&(!quote_opened(&prev))) {
700                 GtkTextMark *insert;
701                 gtk_text_iter_forward_char (&prev);
702                 gtk_text_buffer_insert (buffer, &prev, "\n",-1);
703                 insert = gtk_text_buffer_get_insert (buffer);
704                 gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (priv->text_view), &prev, 0.0,TRUE, 0.0, 1.0);
705         }
706         g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text, editor);
707         g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text_after, editor);
708 }
709
710 /* Called before the default handler, we use it to validate the inputs */
711 static void
712 modest_recpt_editor_on_insert_text (GtkTextBuffer *buffer,
713                                     GtkTextIter *location,
714                                     gchar *text,
715                                     gint len,
716                                     ModestRecptEditor *editor)
717 {
718         if (len > 1024)
719                 len = 1024;
720
721         if (!is_valid_insert (text, len)) {
722                 gchar *new_text = create_valid_text (text, len);
723                 g_signal_stop_emission_by_name (G_OBJECT (buffer), "insert-text");
724                 g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text, editor);
725                 g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text_after, editor);
726                 gtk_text_buffer_insert (buffer, location, new_text, -1);
727                 g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text, editor);
728                 g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text_after, editor);
729                 g_free (new_text);
730                 return;
731         }
732
733         if (iter_has_recipient (location)) {
734                 gtk_text_buffer_get_end_iter (buffer, location);
735                 gtk_text_buffer_place_cursor (buffer, location);
736         }
737 }
738
739 static GtkTextTag *
740 iter_has_recipient (GtkTextIter *iter)
741 {
742         GSList *tags, *node;
743         GtkTextTag *result = NULL;
744
745         tags = gtk_text_iter_get_tags (iter);
746
747         for (node = tags; node != NULL; node = g_slist_next (node)) {
748                 GtkTextTag *tag = GTK_TEXT_TAG (node->data);
749
750                 if (g_object_get_data (G_OBJECT (tag), "recipient-tag-id")) {
751                         result = tag;
752                         break;
753                 }
754         }
755         g_slist_free (tags);
756         return result;
757 }
758
759 static GtkTextTag *
760 prev_iter_has_recipient (GtkTextIter *iter)
761 {
762         GtkTextIter prev;
763
764         prev = *iter;
765         gtk_text_iter_backward_char (&prev);
766         return iter_has_recipient (&prev);
767 }
768
769 /* static GtkTextTag * */
770 /* next_iter_has_recipient (GtkTextIter *iter) */
771 /* { */
772 /*      GtkTextIter next; */
773
774 /*      next = *iter; */
775 /*      return iter_has_recipient (&next); */
776 /* } */
777
778 static gunichar 
779 iter_previous_char (GtkTextIter *iter)
780 {
781         GtkTextIter prev;
782
783         prev = *iter;
784         gtk_text_iter_backward_char (&prev);
785         return gtk_text_iter_get_char (&prev);
786 }
787
788 /* static gunichar  */
789 /* iter_next_char (GtkTextIter *iter) */
790 /* { */
791 /*      GtkTextIter next; */
792
793 /*      next = *iter; */
794 /*      gtk_text_iter_forward_char (&next); */
795 /*      return gtk_text_iter_get_char (&next); */
796 /* } */
797
798 static void
799 select_tag_of_iter (GtkTextIter *iter, GtkTextTag *tag, gboolean grow, gboolean left_not_right)
800 {
801         GtkTextIter start, end;
802
803         start = *iter;
804         if (!gtk_text_iter_begins_tag (&start, tag)) {
805                 gtk_text_iter_backward_to_tag_toggle (&start, tag);
806         } else {
807                 if (!left_not_right) {
808                         gtk_text_buffer_select_range (gtk_text_iter_get_buffer (iter), &start, &start);
809                         return;
810                 }
811         }
812         end = *iter;
813         if (!gtk_text_iter_ends_tag (&end, tag)) {
814                 gtk_text_iter_forward_to_tag_toggle (&end, tag);
815         } else {
816                 if (left_not_right) {
817                         gtk_text_buffer_select_range (gtk_text_iter_get_buffer (iter), &end, &end);
818                         return;
819                 }
820         }
821         if (grow) {
822                 if (left_not_right)
823                         gtk_text_buffer_select_range (gtk_text_iter_get_buffer (iter), &start, &end);
824                 else
825                         gtk_text_buffer_select_range (gtk_text_iter_get_buffer (iter), &end, &start);
826         } else {
827                 if (left_not_right)
828                         gtk_text_buffer_select_range (gtk_text_iter_get_buffer (iter), &start, &start);
829                 else
830                         gtk_text_buffer_select_range (gtk_text_iter_get_buffer (iter), &end, &end);
831         }
832         *iter = left_not_right?start:end;
833 }
834
835 static gboolean 
836 quote_opened (GtkTextIter *iter)
837 {
838         GtkTextIter start;
839         GtkTextBuffer *buffer;
840         gboolean opened = FALSE;
841
842         buffer = gtk_text_iter_get_buffer (iter);
843         gtk_text_buffer_get_start_iter (buffer, &start);
844
845         while (!gtk_text_iter_equal (&start, iter)) {
846                 gunichar current_char = gtk_text_iter_get_char (&start);
847                 if (current_char == '"')
848                         opened = !opened;
849                 else if (current_char == '\\')
850                         gtk_text_iter_forward_char (&start);
851                 if (!gtk_text_iter_equal (&start, iter))
852                         gtk_text_iter_forward_char (&start);
853                         
854         }
855         return opened;
856
857 }
858
859
860 static gboolean
861 modest_recpt_editor_on_key_press_event (GtkTextView *text_view,
862                                           GdkEventKey *key,
863                                           ModestRecptEditor *editor)
864 {
865         GtkTextMark *insert;
866         GtkTextMark *selection;
867         GtkTextBuffer * buffer;
868         GtkTextIter location, selection_loc;
869         GtkTextTag *tag;
870         gboolean shift_pressed;
871         gboolean select_to_left;
872         gboolean has_selection;
873      
874 #ifdef MODEST_TOOLKIT_HILDON2
875         buffer = hildon_text_view_get_buffer (HILDON_TEXT_VIEW (text_view));
876 #else
877         buffer = gtk_text_view_get_buffer (text_view);
878 #endif
879         insert = gtk_text_buffer_get_insert (buffer);
880         selection = gtk_text_buffer_get_selection_bound (buffer);
881
882         /* cases to cover:
883          *    * cursor is on resolved recipient:
884          *        - right should go to first character after the recipient (usually ; or ,)
885          *        - left should fo to the first character before the recipient
886          *        - return should run check names on the recipient.
887          *    * cursor is just after a recipient:
888          *        - right should go to the next character. If it's a recipient, should select
889          *          it
890          *        - left should go to the previous character. If it's a recipient, should go
891          *          to the first character of the recipient, and select it.
892          *    * cursor is on arbitrary text:
893          *        - return should add a ; and go to the next line
894          *        - left or right standard ones.
895          *    * cursor is after a \n:
896          *        - left should go to the character before the \n (as if \n was not a character)
897          *    * cursor is before a \n:
898          *        - right should go to the character after the \n
899          */
900
901         gtk_text_buffer_get_iter_at_mark (buffer, &location, insert);
902         gtk_text_buffer_get_iter_at_mark (buffer, &selection_loc, selection);
903
904         select_to_left = gtk_text_iter_get_offset (&selection_loc) > gtk_text_iter_get_offset (&location);
905         has_selection = gtk_text_iter_get_offset (&selection_loc) != gtk_text_iter_get_offset (&location);
906         shift_pressed = key->state & GDK_SHIFT_MASK;
907
908         switch (key->keyval) {
909         case GDK_Left:
910         case GDK_KP_Left: 
911         {
912                 gboolean cursor_ready = FALSE;
913                 GtkTextIter prev_location;
914
915                 prev_location = location;
916                 while (!cursor_ready) {
917                         if (iter_previous_char (&location) == '\n') {
918                                 gtk_text_iter_backward_char (&location);
919                         } else {
920                                 cursor_ready = TRUE;
921                         }
922                 }
923                 tag = iter_has_recipient (&location);
924                 if (has_selection && gtk_text_iter_ends_tag (&prev_location, tag)) {
925                         gtk_text_iter_backward_to_tag_toggle (&prev_location, tag);
926                         location = prev_location;
927                         cursor_ready = FALSE;
928                         while (!cursor_ready) {
929                                 if (iter_previous_char (&location) == '\n') {
930                                         gtk_text_iter_backward_char (&location);
931                                 } else {
932                                         cursor_ready = TRUE;
933                                 }
934                         }
935                 }
936                 
937                 if ((tag != NULL)&& (gtk_text_iter_is_start (&location) || !(gtk_text_iter_begins_tag (&location, tag)))) {
938                         if (has_selection) {
939                                 gtk_text_buffer_select_range (buffer, &location, &location);
940                         } else {
941                                 select_tag_of_iter (&location, tag, select_to_left, TRUE);
942                         }
943                         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (text_view), insert, 0.0, FALSE, 0.0, 1.0);
944
945                         if (shift_pressed) {
946                                 gtk_text_buffer_select_range (buffer, &location, &selection_loc);
947                         }
948                         return TRUE;
949                 }
950                 gtk_text_iter_backward_char (&location);
951                 tag = iter_has_recipient (&location);
952                 if (tag != NULL)
953                         select_tag_of_iter (&location, tag, select_to_left, TRUE);
954                 else {
955                         gtk_text_buffer_place_cursor (buffer, &location);
956                 }
957                 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (text_view), insert, 0.0, FALSE, 0.0, 1.0);
958
959                 if (shift_pressed) {
960                         gtk_text_buffer_select_range (buffer, &location, &selection_loc);
961                 }
962
963                 return TRUE;
964         }
965         break;
966         case GDK_Right:
967         case GDK_KP_Right:
968         {
969                 gboolean cursor_moved = FALSE;
970
971                 tag = iter_has_recipient (&location);
972                 if ((tag != NULL)&&(!gtk_text_iter_ends_tag (&location, tag))) {
973                         gtk_text_iter_forward_to_tag_toggle (&location, tag);
974                         while (gtk_text_iter_get_char (&location) == '\n')
975                                 gtk_text_iter_forward_char (&location);
976                         gtk_text_buffer_place_cursor (buffer, &location);
977                         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (text_view), insert, 0.0, FALSE, 0.0, 1.0);
978
979                         if (shift_pressed) {
980                                 gtk_text_buffer_select_range (buffer, &location, &selection_loc);
981                         }
982                         return TRUE;
983                 }
984
985                 while (gtk_text_iter_get_char (&location) == '\n') {
986                         gtk_text_iter_forward_char (&location);
987                         cursor_moved = TRUE;
988                 }
989                 if (!cursor_moved)
990                         gtk_text_iter_forward_char (&location);
991                 while (gtk_text_iter_get_char (&location) == '\n') {
992                         gtk_text_iter_forward_char (&location);
993                         cursor_moved = TRUE;
994                 }
995
996                 tag = iter_has_recipient (&location);
997                 if (tag != NULL)
998                         select_tag_of_iter (&location, tag, !select_to_left, FALSE);
999                 else
1000                         gtk_text_buffer_place_cursor (buffer, &location);
1001                 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (text_view), insert, 0.0, FALSE, 0.0, 1.0);
1002
1003                 if (shift_pressed) {
1004                         gtk_text_buffer_select_range (buffer, &location, &selection_loc);
1005                 }
1006                 return TRUE;
1007         }
1008         break;
1009         case GDK_Return:
1010         case GDK_KP_Enter:
1011         {
1012                 gint insert_offset, selection_offset;
1013                 insert_offset = gtk_text_iter_get_offset (&location);
1014                 selection_offset = gtk_text_iter_get_offset (&selection_loc);
1015                 g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text, editor);
1016                 g_signal_handlers_block_by_func (buffer, modest_recpt_editor_on_insert_text_after, editor);
1017                 if (selection_offset > insert_offset)
1018                         location = selection_loc;
1019                 tag = iter_has_recipient (&location);
1020                 if (tag != NULL) {
1021                         gtk_text_buffer_get_end_iter (buffer, &location);
1022                         gtk_text_buffer_place_cursor (buffer, &location);
1023                         if ((iter_previous_char (&location) != ';')&&(iter_previous_char (&location) != ','))
1024                                 gtk_text_buffer_insert_at_cursor (buffer, ";", -1);
1025                         gtk_text_buffer_insert_at_cursor (buffer, "\n", -1);
1026                 } else {
1027                         gunichar prev_char = iter_previous_char (&location);
1028                         if ((gtk_text_iter_is_start (&location))||(prev_char == '\n')
1029                             ||(prev_char == ';')||(prev_char == ',')) 
1030                                 g_signal_emit_by_name (G_OBJECT (editor), "open-addressbook");
1031                         else {
1032                                 if ((prev_char != ';') && (prev_char != ','))
1033                                         gtk_text_buffer_insert_at_cursor (buffer, ";", -1);
1034                                 gtk_text_buffer_insert_at_cursor (buffer, "\n", -1);
1035                         }
1036                 }
1037                 g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text, editor);
1038                 g_signal_handlers_unblock_by_func (buffer, modest_recpt_editor_on_insert_text_after, editor);
1039                 return TRUE;
1040         }
1041         break;
1042         case GDK_BackSpace:
1043         {
1044                 #if GTK_CHECK_VERSION(2, 10, 0) /* gtk_text_buffer_get_has_selection is only available in GTK+ 2.10 */
1045                 if (gtk_text_buffer_get_has_selection (buffer)) {
1046                         gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
1047                         return TRUE;
1048                 }
1049                 #else
1050                 if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
1051                         gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
1052                         return TRUE;
1053                 }
1054                 #endif
1055
1056                 tag = prev_iter_has_recipient (&location);
1057                 if (tag != NULL) {
1058                         GtkTextIter iter_in_tag;
1059                         iter_in_tag = location;
1060                         gtk_text_iter_backward_char (&iter_in_tag);
1061                         select_tag_of_iter (&iter_in_tag, tag, FALSE, TRUE);
1062                         gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
1063                         return TRUE;
1064                 }
1065                 return FALSE;
1066         }
1067         break;
1068         default:
1069                 return FALSE;
1070         }
1071 }
1072
1073 static void _discard_chars (GtkTextIter *start, GtkTextIter *end)
1074 {
1075         while (!gtk_text_iter_equal (start, end)) {
1076                 gunichar c = gtk_text_iter_get_char (start);
1077
1078                 if (c == '\n' || c == ';' || c == ',' || c == ' ') {
1079                         if (!gtk_text_iter_forward_char (start))
1080                                 break;
1081                 } else {
1082                         break;
1083                 }
1084
1085         }
1086 }
1087
1088 /* NOTE: before calling this function be sure that both
1089    modest_recpt_editor_on_insert_text and
1090    modest_recpt_editor_on_insert_text_after won't be triggered during
1091    the execution of the procedure. You'll have to block both signal
1092    handlers otherwise you'll get an infinite loop and most likely a
1093    SIGSEV caused by a stack overflow */
1094 static void
1095 modest_recpt_editor_add_tags (ModestRecptEditor *editor,
1096                               const gchar * recipient_id)
1097 {
1098
1099         ModestRecptEditorPrivate *priv = MODEST_RECPT_EDITOR_GET_PRIVATE (editor);
1100 #ifdef MODEST_TOOLKIT_HILDON2
1101         GtkTextBuffer *buffer = hildon_text_view_get_buffer (HILDON_TEXT_VIEW (priv->text_view));
1102 #else
1103         GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->text_view));
1104 #endif
1105         GtkTextTag *tag;
1106         GtkTextIter start, end;
1107         gchar * buffer_contents;
1108         GtkTextIter start_match, end_match;
1109
1110         /* This would move the cursor to the end of the buffer
1111            containing new line character. */
1112         gtk_text_buffer_get_bounds (buffer, &start, &end);
1113         buffer_contents = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
1114         gtk_text_buffer_set_text(buffer,buffer_contents,strlen(buffer_contents));
1115
1116         tag = gtk_text_buffer_create_tag (buffer, NULL,
1117                                           "underline", PANGO_UNDERLINE_SINGLE,
1118                                           "wrap-mode", GTK_WRAP_NONE,
1119                                           "editable", TRUE, NULL);
1120
1121         g_object_set_data (G_OBJECT (tag), "recipient-tag-id", GINT_TO_POINTER (RECIPIENT_TAG_ID));
1122         g_object_set_data_full (G_OBJECT (tag), "recipient-id", g_strdup (recipient_id), (GDestroyNotify) g_free);
1123
1124         /* Formatting the buffer content by applying tag */
1125         gtk_text_buffer_get_bounds (buffer, &start, &end);
1126         _discard_chars (&start, &end);
1127         while (gtk_text_iter_forward_search(&start, ";",
1128                                             GTK_TEXT_SEARCH_TEXT_ONLY |
1129                                             GTK_TEXT_SEARCH_VISIBLE_ONLY,
1130                                             &start_match, &end_match, &end )) {
1131                 int offset;
1132
1133                 gtk_text_buffer_apply_tag(buffer, tag, &start, &start_match);
1134                 offset = gtk_text_iter_get_offset (&end_match);
1135                 gtk_text_buffer_get_iter_at_offset(buffer, &start, offset);
1136                 _discard_chars (&start, &end);
1137         }
1138         g_free (buffer_contents);
1139 }
1140
1141 void
1142 modest_recpt_editor_grab_focus (ModestRecptEditor *recpt_editor)
1143 {
1144         ModestRecptEditorPrivate *priv;
1145         
1146         g_return_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor));
1147         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
1148
1149         if (GTK_WIDGET_VISIBLE (recpt_editor)) {
1150                 gtk_widget_grab_focus (priv->text_view);
1151         }
1152 }
1153
1154 gboolean
1155 modest_recpt_editor_has_focus (ModestRecptEditor *recpt_editor)
1156 {
1157         ModestRecptEditorPrivate *priv;
1158         
1159         g_return_val_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor), FALSE);
1160         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
1161
1162         return GTK_WIDGET_VISIBLE (priv->text_view) && 
1163                 gtk_widget_is_focus (priv->text_view);
1164 }
1165
1166 void 
1167 modest_recpt_editor_set_show_abook_button (ModestRecptEditor *recpt_editor, gboolean show)
1168 {
1169         ModestRecptEditorPrivate *priv;
1170         
1171         g_return_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor));
1172         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
1173
1174         priv->show_abook = show;
1175
1176         if (show)
1177                 gtk_widget_show (priv->abook_button);
1178         else
1179                 gtk_widget_hide (priv->abook_button);
1180 }
1181
1182 gboolean
1183 modest_recpt_editor_get_show_abook_button (ModestRecptEditor *recpt_editor, gboolean show)
1184 {
1185         ModestRecptEditorPrivate *priv;
1186         
1187         g_return_val_if_fail (MODEST_IS_RECPT_EDITOR (recpt_editor), FALSE);
1188         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (recpt_editor);
1189
1190         return priv->show_abook;
1191         
1192 }
1193
1194 static void
1195 modest_recpt_editor_finalize (GObject *object)
1196 {
1197         ModestRecptEditorPrivate *priv;
1198         priv = MODEST_RECPT_EDITOR_GET_PRIVATE (object);
1199
1200         if (g_signal_handler_is_connected (object, priv->on_mark_set_handler))
1201                 g_signal_handler_disconnect (object, priv->on_mark_set_handler);
1202         priv->on_mark_set_handler = 0;
1203
1204         if (priv->recipients) {
1205                 g_free (priv->recipients);
1206                 priv->recipients = NULL;
1207         }
1208
1209         (*parent_class->finalize) (object);
1210
1211         return;
1212 }
1213
1214 static void 
1215 modest_recpt_editor_class_init (ModestRecptEditorClass *klass)
1216 {
1217         GObjectClass *object_class;
1218
1219         parent_class = g_type_class_peek_parent (klass);
1220         object_class = (GObjectClass*) klass;
1221
1222         object_class->finalize = modest_recpt_editor_finalize;
1223
1224         g_type_class_add_private (object_class, sizeof (ModestRecptEditorPrivate));
1225
1226         signals[OPEN_ADDRESSBOOK_SIGNAL] = 
1227                 g_signal_new ("open-addressbook",
1228                               G_TYPE_FROM_CLASS (object_class),
1229                               G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
1230                               G_STRUCT_OFFSET (ModestRecptEditorClass, open_addressbook),
1231                               NULL, NULL,
1232                               g_cclosure_marshal_VOID__VOID,
1233                               G_TYPE_NONE, 0);
1234
1235         return;
1236 }
1237
1238 GType 
1239 modest_recpt_editor_get_type (void)
1240 {
1241         static GType type = 0;
1242
1243         if (G_UNLIKELY(type == 0))
1244         {
1245                 static const GTypeInfo info = 
1246                 {
1247                   sizeof (ModestRecptEditorClass),
1248                   NULL,   /* base_init */
1249                   NULL,   /* base_finalize */
1250                   (GClassInitFunc) modest_recpt_editor_class_init,   /* class_init */
1251                   NULL,   /* class_finalize */
1252                   NULL,   /* class_data */
1253                   sizeof (ModestRecptEditor),
1254                   0,      /* n_preallocs */
1255                   modest_recpt_editor_instance_init    /* instance_init */
1256                 };
1257
1258                 type = g_type_register_static (GTK_TYPE_HBOX,
1259                         "ModestRecptEditor",
1260                         &info, 0);
1261
1262         }
1263
1264         return type;
1265 }