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