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