Better attachments detection for text/calendar
[modest] / src / widgets / modest-attachments-view.c
1 /* Copyright (c) 2007, 2009, 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 <string.h>
33 #include <gtk/gtk.h>
34 #include <gdk/gdkkeysyms.h>
35 #include <tny-list.h>
36 #include <tny-simple-list.h>
37
38 #include <modest-platform.h>
39 #include <modest-runtime.h>
40 #include <modest-attachment-view.h>
41 #include <modest-attachments-view.h>
42 #include <modest-tny-mime-part.h>
43 #include <modest-tny-msg.h>
44 #include <modest-ui-constants.h>
45
46 static GObjectClass *parent_class = NULL;
47
48 /* signals */
49 enum {
50         ACTIVATE_SIGNAL,
51         DELETE_SIGNAL,
52         LAST_SIGNAL
53 };
54
55 typedef struct _ModestAttachmentsViewPrivate ModestAttachmentsViewPrivate;
56
57 struct _ModestAttachmentsViewPrivate
58 {
59         TnyMsg *msg;
60         GtkWidget *box;
61         GList *selected;
62         GtkWidget *rubber_start;
63         GtkWidget *press_att_view;
64         GtkWidget *previous_selection;
65         ModestAttachmentsViewStyle style;
66 };
67
68 #define MODEST_ATTACHMENTS_VIEW_GET_PRIVATE(o)  \
69         (G_TYPE_INSTANCE_GET_PRIVATE ((o), MODEST_TYPE_ATTACHMENTS_VIEW, ModestAttachmentsViewPrivate))
70
71 static gboolean button_press_event (GtkWidget *widget, GdkEventButton *event, ModestAttachmentsView *atts_view);
72 static gboolean motion_notify_event (GtkWidget *widget, GdkEventMotion *event, ModestAttachmentsView *atts_view);
73 static gboolean button_release_event (GtkWidget *widget, GdkEventButton *event, ModestAttachmentsView *atts_view);
74 static gboolean key_press_event (GtkWidget *widget, GdkEventKey *event, ModestAttachmentsView *atts_view);
75 static gboolean focus_out_event (GtkWidget *widget, GdkEventFocus *event, ModestAttachmentsView *atts_view);
76 static gboolean focus (GtkWidget *widget, GtkDirectionType direction, ModestAttachmentsView *atts_view);
77 static GtkWidget *get_att_view_at_coords (ModestAttachmentsView *atts_view,
78                                           gdouble x, gdouble y);
79 static void unselect_all (ModestAttachmentsView *atts_view);
80 static void set_selected (ModestAttachmentsView *atts_view, ModestAttachmentView *att_view);
81 static void select_range (ModestAttachmentsView *atts_view, ModestAttachmentView *att1, ModestAttachmentView *att2);
82 static void clipboard_get (GtkClipboard *clipboard, GtkSelectionData *selection_data,
83                            guint info, gpointer userdata);
84 static void own_clipboard (ModestAttachmentsView *atts_view);
85 static void on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata);
86 static void update_style (ModestAttachmentsView *self);
87
88 static guint signals[LAST_SIGNAL] = {0};
89
90 /**
91  * modest_attachments_view_new:
92  * @msg: a #TnyMsg
93  *
94  * Constructor for attachments view widget.
95  *
96  * Return value: a new #ModestAttachmentsView instance implemented for Gtk+
97  **/
98 GtkWidget*
99 modest_attachments_view_new (TnyMsg *msg)
100 {
101         ModestAttachmentsView *self = g_object_new (MODEST_TYPE_ATTACHMENTS_VIEW, 
102                                                     "resize-mode", GTK_RESIZE_PARENT,
103                                                     NULL);
104
105         modest_attachments_view_set_message (self, msg);
106
107         return GTK_WIDGET (self);
108 }
109
110 static void
111 add_digest_attachments (ModestAttachmentsView *attachments_view, TnyMimePart *part)
112 {
113         TnyList *parts;
114         TnyIterator *iter;
115
116         parts = TNY_LIST (tny_simple_list_new());
117         tny_mime_part_get_parts (TNY_MIME_PART (part), parts);
118
119         for (iter  = tny_list_create_iterator(parts); 
120              !tny_iterator_is_done (iter);
121              tny_iterator_next (iter)) {
122                 TnyMimePart *cur_part = TNY_MIME_PART (tny_iterator_get_current (iter));
123                 modest_attachments_view_add_attachment (attachments_view, cur_part, TRUE, 0);
124                 g_object_unref (cur_part);
125         }
126         g_object_unref (iter);
127         g_object_unref (parts);
128
129 }
130
131
132 void
133 modest_attachments_view_set_message (ModestAttachmentsView *attachments_view, TnyMsg *msg)
134 {
135         ModestAttachmentsViewPrivate *priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (attachments_view);
136         TnyList *parts;
137         TnyIterator *iter;
138         gchar *msg_content_type = NULL;
139         TnyMimePart *part_to_check;
140         gboolean body_found;
141         gboolean is_alternate;
142         
143         if (msg == priv->msg) return;
144
145         if (priv->msg)
146                 g_object_unref (priv->msg);
147         if (msg)
148                 g_object_ref (G_OBJECT(msg));
149         
150         priv->msg = msg;
151
152         g_list_free (priv->selected);
153         priv->selected = NULL;
154
155         gtk_container_foreach (GTK_CONTAINER (priv->box), (GtkCallback) gtk_widget_destroy, NULL);
156
157         if (priv->msg == NULL)
158                 return;
159
160         part_to_check = modest_tny_msg_get_attachments_parent (TNY_MSG (msg));
161         msg_content_type = modest_tny_mime_part_get_content_type (TNY_MIME_PART (part_to_check));
162         is_alternate = (msg_content_type != NULL) && !strcasecmp (msg_content_type, "multipart/alternative");
163
164         /* If the top mime part is a multipart/related, we don't show the attachments, as they're
165          * embedded images in body */
166         if ((msg_content_type != NULL) && !strcasecmp (msg_content_type, "multipart/related")) {
167                 gchar *header_content_type;
168                 gboolean application_multipart = FALSE;
169
170                 g_free (msg_content_type);
171
172                 header_content_type = modest_tny_mime_part_get_headers_content_type (TNY_MIME_PART (part_to_check));
173
174                 if ((header_content_type != NULL) && 
175                     !strstr (header_content_type, "application/")) {
176                         application_multipart = TRUE;
177                 }
178                 g_free (header_content_type);
179
180                 if (application_multipart) {
181                         gtk_widget_queue_draw (GTK_WIDGET (attachments_view));
182                         g_object_unref (part_to_check);
183                         return;
184                 }
185         } else {
186                 gboolean direct_attach;
187
188                 direct_attach = (!g_str_has_prefix (msg_content_type, "message/rfc822") && 
189                                  !g_str_has_prefix (msg_content_type, "multipart") && 
190                                  !g_str_has_prefix (msg_content_type, "text/plain") &&
191                                  !g_str_has_prefix (msg_content_type, "text/html"));
192
193                 g_free (msg_content_type);
194
195                 if (direct_attach) {
196                         modest_attachments_view_add_attachment (attachments_view, TNY_MIME_PART (part_to_check), TRUE, 0);
197                         gtk_widget_queue_draw (GTK_WIDGET (attachments_view));
198                         g_object_unref (part_to_check);
199                         return;
200                 }
201         }
202
203         parts = TNY_LIST (tny_simple_list_new ());
204         tny_mime_part_get_parts (TNY_MIME_PART (part_to_check), parts);
205         iter = tny_list_create_iterator (parts);
206
207         body_found = FALSE;
208         while (!tny_iterator_is_done (iter)) {
209                 TnyMimePart *part;
210                 gchar *content_type;
211
212                 part = TNY_MIME_PART (tny_iterator_get_current (iter));
213
214                 if (part && (modest_tny_mime_part_is_attachment_for_modest (part))) {
215                         modest_attachments_view_add_attachment (attachments_view, part, TRUE, 0);
216
217                 } else if (part && !is_alternate) {
218                         content_type = g_ascii_strdown (tny_mime_part_get_content_type (part), -1);
219                         g_strstrip (content_type);
220
221                         if (g_str_has_prefix (content_type, "multipart/digest")) {
222                                 add_digest_attachments (attachments_view, part);
223                         } else if (body_found && 
224                                    (g_str_has_prefix (content_type, "text/plain") ||
225                                     g_str_has_prefix (content_type, "text/html"))) {
226                                    modest_attachments_view_add_attachment (attachments_view, part, TRUE, 0);
227                         } else if (g_str_has_prefix (content_type, "multipart/") || 
228                                    g_str_has_prefix (content_type, "text/plain") ||
229                                    g_str_has_prefix (content_type, "text/html")) {
230                                 body_found = TRUE;
231                         }
232                 }
233
234
235                 if (part)
236                         g_object_unref (part);
237
238                 tny_iterator_next (iter);
239         }
240         g_object_unref (iter);
241         g_object_unref (parts);
242         g_object_unref (part_to_check);
243
244         gtk_widget_queue_draw (GTK_WIDGET (attachments_view));
245
246 }
247
248 void
249 modest_attachments_view_add_attachment (ModestAttachmentsView *attachments_view, TnyMimePart *part,
250                                         gboolean detect_size, guint64 size)
251 {
252         GtkWidget *att_view = NULL;
253         ModestAttachmentsViewPrivate *priv = NULL;
254
255         g_return_if_fail (MODEST_IS_ATTACHMENTS_VIEW (attachments_view));
256         g_return_if_fail (TNY_IS_MIME_PART (part));
257
258         priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (attachments_view);
259
260         att_view = modest_attachment_view_new (part, detect_size);
261         if (!detect_size)
262                 modest_attachment_view_set_size (MODEST_ATTACHMENT_VIEW (att_view), size);
263         gtk_box_pack_start (GTK_BOX (priv->box), att_view, FALSE, FALSE, 0);
264         gtk_widget_show_all (att_view);
265         gtk_widget_queue_resize (GTK_WIDGET (attachments_view));
266 }
267
268 void
269 modest_attachments_view_remove_attachment (ModestAttachmentsView *atts_view, TnyMimePart *mime_part)
270 {
271         ModestAttachmentsViewPrivate *priv = NULL;
272         GList *box_children = NULL, *node = NULL;
273         ModestAttachmentView *found_att_view = NULL;
274
275         g_return_if_fail (MODEST_IS_ATTACHMENTS_VIEW (atts_view));
276         g_return_if_fail (TNY_IS_MIME_PART (mime_part));
277
278         priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
279         box_children = gtk_container_get_children (GTK_CONTAINER (priv->box));
280
281         for (node = box_children; node != NULL; node = g_list_next (node)) {
282                 ModestAttachmentView *att_view = (ModestAttachmentView *) node->data;
283                 TnyMimePart *cur_mime_part = tny_mime_part_view_get_part (TNY_MIME_PART_VIEW (att_view));
284
285                 if (mime_part == cur_mime_part)
286                         found_att_view = att_view;
287
288                 g_object_unref (cur_mime_part);
289
290                 if (found_att_view != NULL)
291                         break;
292         }
293
294         if (found_att_view) {
295                 GList *node = NULL;
296                 GtkWidget *next_widget = NULL;
297                 GList *box_children = NULL;
298
299                 box_children = gtk_container_get_children (GTK_CONTAINER (priv->box));
300                 node = g_list_find (box_children, found_att_view);
301                 if (node && node->next)
302                         next_widget = node->next->data;
303
304                 g_list_free (box_children);
305                 gtk_widget_destroy (GTK_WIDGET (found_att_view));
306
307                 node = g_list_find (priv->selected, found_att_view);
308                 if (node) {
309                         priv->selected = g_list_delete_link (priv->selected, node);
310                         if ((priv->selected == NULL) && (next_widget != NULL))
311                                 set_selected (MODEST_ATTACHMENTS_VIEW (atts_view), 
312                                               MODEST_ATTACHMENT_VIEW (next_widget));
313                 }
314                 own_clipboard (atts_view);
315
316         }
317
318         gtk_widget_queue_resize (GTK_WIDGET (atts_view));
319 }
320
321 void
322 modest_attachments_view_remove_attachment_by_id (ModestAttachmentsView *atts_view, const gchar *att_id)
323 {
324         ModestAttachmentsViewPrivate *priv = NULL;
325         GList *box_children = NULL, *node = NULL;
326
327         g_return_if_fail (MODEST_IS_ATTACHMENTS_VIEW (atts_view));
328         g_return_if_fail (att_id != NULL);
329
330         priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
331         box_children = gtk_container_get_children (GTK_CONTAINER (priv->box));
332
333         for (node = box_children; node != NULL; node = g_list_next (node)) {
334                 ModestAttachmentView *att_view = (ModestAttachmentView *) node->data;
335                 TnyMimePart *cur_mime_part = tny_mime_part_view_get_part (TNY_MIME_PART_VIEW (att_view));
336                 const gchar *mime_part_id = NULL;
337
338                 mime_part_id = tny_mime_part_get_content_id (cur_mime_part);
339                 if ((mime_part_id != NULL) && (strcmp (mime_part_id, att_id) == 0)) {
340                         gtk_widget_destroy (GTK_WIDGET (att_view));
341                         priv->selected = g_list_remove (priv->selected, att_view);
342                 }
343
344                 g_object_unref (cur_mime_part);
345         }
346
347         own_clipboard (atts_view);
348
349         gtk_widget_queue_resize (GTK_WIDGET (atts_view));
350 }
351
352 static void
353 modest_attachments_view_instance_init (GTypeInstance *instance, gpointer g_class)
354 {
355         ModestAttachmentsViewPrivate *priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (instance);
356
357         priv->msg = NULL;
358         priv->box = gtk_vbox_new (FALSE, 0);
359         priv->rubber_start = NULL;
360         priv->press_att_view = NULL;
361         priv->selected = NULL;
362         priv->style = MODEST_ATTACHMENTS_VIEW_STYLE_SELECTABLE;
363
364         gtk_container_add (GTK_CONTAINER (instance), priv->box);
365         gtk_event_box_set_above_child (GTK_EVENT_BOX (instance), TRUE);
366
367         g_signal_connect (G_OBJECT (instance), "button-press-event", G_CALLBACK (button_press_event), instance);
368         g_signal_connect (G_OBJECT (instance), "button-release-event", G_CALLBACK (button_release_event), instance);
369         g_signal_connect (G_OBJECT (instance), "motion-notify-event", G_CALLBACK (motion_notify_event), instance);
370         g_signal_connect (G_OBJECT (instance), "key-press-event", G_CALLBACK (key_press_event), instance);
371         g_signal_connect (G_OBJECT (instance), "focus-out-event", G_CALLBACK (focus_out_event), instance);
372         g_signal_connect (G_OBJECT (instance), "focus", G_CALLBACK (focus), instance);
373
374         GTK_WIDGET_SET_FLAGS (instance, GTK_CAN_FOCUS);
375
376         g_signal_connect (G_OBJECT (instance), "notify::style", G_CALLBACK (on_notify_style), (gpointer) instance);
377
378         update_style (MODEST_ATTACHMENTS_VIEW (instance));
379
380         return;
381 }
382
383 static void
384 modest_attachments_view_finalize (GObject *object)
385 {
386         ModestAttachmentsViewPrivate *priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (object);
387
388         if (priv->msg) {
389                 g_object_unref (priv->msg);
390                 priv->msg = NULL;
391         }
392
393         (*parent_class->finalize) (object);
394
395         return;
396 }
397
398 static void 
399 modest_attachments_view_class_init (ModestAttachmentsViewClass *klass)
400 {
401         GObjectClass *object_class;
402         GtkWidgetClass *widget_class;
403
404         parent_class = g_type_class_peek_parent (klass);
405         object_class = (GObjectClass*) klass;
406         widget_class = GTK_WIDGET_CLASS (klass);
407
408         object_class->finalize = modest_attachments_view_finalize;
409
410         klass->activate = NULL;
411
412         g_type_class_add_private (object_class, sizeof (ModestAttachmentsViewPrivate));
413
414         signals[ACTIVATE_SIGNAL] =
415                 g_signal_new ("activate",
416                               G_TYPE_FROM_CLASS (object_class),
417                               G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
418                               G_STRUCT_OFFSET(ModestAttachmentsViewClass, activate),
419                               NULL, NULL,
420                               g_cclosure_marshal_VOID__OBJECT,
421                               G_TYPE_NONE, 1, G_TYPE_OBJECT);
422         
423         signals[DELETE_SIGNAL] =
424                 g_signal_new ("delete",
425                               G_TYPE_FROM_CLASS (object_class),
426                               G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
427                               G_STRUCT_OFFSET(ModestAttachmentsViewClass, delete),
428                               NULL, NULL,
429                               g_cclosure_marshal_VOID__VOID,
430                               G_TYPE_NONE, 0);
431
432         return;
433 }
434
435 GType 
436 modest_attachments_view_get_type (void)
437 {
438         static GType type = 0;
439
440         if (G_UNLIKELY(type == 0))
441         {
442                 static const GTypeInfo info = 
443                 {
444                   sizeof (ModestAttachmentsViewClass),
445                   NULL,   /* base_init */
446                   NULL,   /* base_finalize */
447                   (GClassInitFunc) modest_attachments_view_class_init,   /* class_init */
448                   NULL,   /* class_finalize */
449                   NULL,   /* class_data */
450                   sizeof (ModestAttachmentsView),
451                   0,      /* n_preallocs */
452                   modest_attachments_view_instance_init    /* instance_init */
453                 };
454
455                 type = g_type_register_static (GTK_TYPE_EVENT_BOX,
456                         "ModestAttachmentsView",
457                         &info, 0);
458
459         }
460
461         return type;
462 }
463
464 /* buttons signal events */
465 static gboolean
466 button_press_event (GtkWidget *widget, 
467                     GdkEventButton *event,
468                     ModestAttachmentsView *atts_view)
469 {
470         ModestAttachmentsViewPrivate *priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
471         if (priv->style == MODEST_ATTACHMENTS_VIEW_STYLE_SELECTABLE && 
472             !GTK_WIDGET_HAS_FOCUS (widget))
473                 gtk_widget_grab_focus (widget);
474
475         if (event->button == 1 && event->type == GDK_BUTTON_PRESS) {
476                 GtkWidget *att_view = NULL;
477
478                 att_view = get_att_view_at_coords (MODEST_ATTACHMENTS_VIEW (widget), 
479                                                    (gint) event->x_root, (gint) event->y_root);
480
481                 if (att_view != NULL) {
482                         if (priv->style == MODEST_ATTACHMENTS_VIEW_STYLE_NO_FOCUS) {
483                                 unselect_all (MODEST_ATTACHMENTS_VIEW (widget));
484                         } else if (priv->style == MODEST_ATTACHMENTS_VIEW_STYLE_LINKS) {
485                                 priv->press_att_view = att_view;
486                                 set_selected (MODEST_ATTACHMENTS_VIEW (widget), MODEST_ATTACHMENT_VIEW (att_view));
487                                 gtk_grab_add (widget);
488                         } else {
489                                 if (g_list_length (priv->selected) == 1) {
490                                         priv->previous_selection = GTK_WIDGET (priv->selected->data);
491                                 } else {
492                                         priv->previous_selection = NULL;
493                                 }
494                                 TnyMimePart *mime_part = tny_mime_part_view_get_part (TNY_MIME_PART_VIEW (att_view));
495
496                                 /* Do not select purged attachments */
497                                 if (TNY_IS_MIME_PART (mime_part) && !tny_mime_part_is_purged (mime_part)) {
498                                         set_selected (MODEST_ATTACHMENTS_VIEW (widget), MODEST_ATTACHMENT_VIEW (att_view));
499                                         priv->rubber_start = att_view;
500                                         gtk_grab_add (widget);
501                                 }
502                                 g_object_unref (mime_part);
503                         }
504                 }
505         }
506         return TRUE;
507
508 }
509
510 static gboolean
511 button_release_event (GtkWidget *widget,
512                       GdkEventButton *event,
513                       ModestAttachmentsView *atts_view)
514 {
515         ModestAttachmentsViewPrivate *priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
516         if (widget == gtk_grab_get_current ()) {
517                 GtkWidget *att_view = NULL;
518
519                 att_view = get_att_view_at_coords (MODEST_ATTACHMENTS_VIEW (widget), 
520                                                    (gint) event->x_root, (gint) event->y_root);
521
522                 if (priv->style == MODEST_ATTACHMENTS_VIEW_STYLE_LINKS) {
523                         unselect_all (MODEST_ATTACHMENTS_VIEW (widget));
524                         if (att_view == priv->press_att_view) {
525                                 TnyMimePart *mime_part;
526                                 mime_part = tny_mime_part_view_get_part (TNY_MIME_PART_VIEW (att_view));
527                                 g_signal_emit (G_OBJECT (widget), signals[ACTIVATE_SIGNAL], 0, mime_part);
528                                 g_object_unref (mime_part);
529                         }
530                         priv->press_att_view = NULL;
531                 } else {
532
533                         if (priv->style != MODEST_ATTACHMENTS_VIEW_STYLE_NO_FOCUS &&
534                             priv->rubber_start == att_view  && 
535                             priv->previous_selection == att_view) {
536                                 TnyMimePart *mime_part;
537                                 mime_part = tny_mime_part_view_get_part (TNY_MIME_PART_VIEW (att_view));
538                                 g_signal_emit (G_OBJECT (widget), signals[ACTIVATE_SIGNAL], 0, mime_part);
539                                 g_object_unref (mime_part);
540                         } else if (att_view != NULL) {
541                                 unselect_all (MODEST_ATTACHMENTS_VIEW (widget));
542                                 select_range (MODEST_ATTACHMENTS_VIEW (widget), 
543                                               MODEST_ATTACHMENT_VIEW (priv->rubber_start), 
544                                               MODEST_ATTACHMENT_VIEW (att_view));
545                         }
546                         priv->rubber_start = NULL;
547                 }
548                 gtk_grab_remove (widget);
549         }
550         return TRUE;
551 }
552
553 static gboolean
554 motion_notify_event (GtkWidget *widget,
555                      GdkEventMotion *event,
556                      ModestAttachmentsView *atts_view)
557 {
558         ModestAttachmentsViewPrivate *priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
559         if (gtk_grab_get_current () == widget) {
560                 GtkWidget *att_view = NULL;
561
562                 att_view = get_att_view_at_coords (MODEST_ATTACHMENTS_VIEW (widget), 
563                                                    (gint) event->x_root, (gint) event->y_root);
564                 if (priv->style == MODEST_ATTACHMENTS_VIEW_STYLE_LINKS) {
565                         if (att_view == priv->press_att_view) {
566                                 if (priv->selected == NULL)
567                                 set_selected (MODEST_ATTACHMENTS_VIEW (widget), MODEST_ATTACHMENT_VIEW (att_view));
568                         } else {
569                                 if (priv->selected) {
570                                         unselect_all (MODEST_ATTACHMENTS_VIEW (widget));
571                                 }
572                         }
573                 } else {
574
575                         if (att_view != NULL) {
576                                 unselect_all (MODEST_ATTACHMENTS_VIEW (widget));
577                                 select_range (MODEST_ATTACHMENTS_VIEW (widget), 
578                                               MODEST_ATTACHMENT_VIEW (priv->rubber_start), 
579                                               MODEST_ATTACHMENT_VIEW (att_view));
580                         }
581                 }
582                 gdk_event_request_motions (event);
583         }
584         return TRUE;
585 }
586
587 static GList*
588 find_prev_or_next_not_purged (GList *list, gboolean prev, gboolean include_this)
589 {
590         GList *tmp = NULL;
591         gboolean is_valid;
592
593         if (!include_this) {
594                 if (prev) {
595                         tmp = g_list_previous (list);
596                 } else {
597                         tmp = g_list_next (list);
598                 }
599         } else {
600                 tmp = list;
601         }
602
603         if (!tmp)
604                 return NULL;
605
606         do {
607                 ModestAttachmentView *att_view = (ModestAttachmentView *) tmp->data;
608                 TnyMimePart *mime_part = tny_mime_part_view_get_part (TNY_MIME_PART_VIEW (att_view));
609                 
610                 /* Do not select purged attachments */
611                 if (TNY_IS_MIME_PART (mime_part) && !tny_mime_part_is_purged (mime_part)) {
612                         is_valid = TRUE;
613                 } else {
614                         if (prev)
615                                 tmp = g_list_previous (tmp);
616                         else
617                                 tmp = g_list_next (tmp);
618                         is_valid = FALSE;
619                 }
620                 g_object_unref (mime_part);
621         } while (!is_valid && tmp);
622
623         return tmp;
624 }
625
626
627 static gboolean
628 key_press_event (GtkWidget *widget,
629                  GdkEventKey *event,
630                  ModestAttachmentsView *atts_view)
631 {
632         ModestAttachmentsViewPrivate *priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
633
634         if (priv->style == MODEST_ATTACHMENTS_VIEW_STYLE_NO_FOCUS) {
635                 unselect_all (atts_view);
636                 return FALSE;
637         }
638
639         /* If grabbed (for example rubber banding), escape leaves the rubberbanding mode */
640         if (gtk_grab_get_current () == widget) {
641                 if (event->keyval == GDK_Escape) {
642                         set_selected (MODEST_ATTACHMENTS_VIEW (widget),
643                                       MODEST_ATTACHMENT_VIEW (priv->rubber_start));
644                         priv->rubber_start = NULL;
645                         gtk_grab_remove (widget);
646                         return TRUE;
647                 } 
648                 return FALSE;
649         }
650
651         if (event->keyval == GDK_Up) {
652                 ModestAttachmentView *current_sel = NULL;
653                 gboolean move_out = FALSE;
654                 GList * box_children, *new_sel, *first_child;
655
656                 box_children = gtk_container_get_children (GTK_CONTAINER (priv->box));
657                 if (box_children == NULL) {
658                         move_out = TRUE;
659                 } else { 
660                         first_child = box_children;
661                         first_child = find_prev_or_next_not_purged (box_children, FALSE, TRUE);
662                         if (priv->selected != NULL && first_child != NULL) {
663                                 if (priv->selected->data != first_child->data)
664                                         current_sel = (ModestAttachmentView *) priv->selected->data;
665                                 else
666                                         move_out = TRUE;
667                         } else {
668                                 move_out = TRUE;
669                         }
670                 }
671
672                 if (move_out) {
673                         GtkWidget *toplevel = NULL;
674                         /* move cursor outside */
675                         toplevel = gtk_widget_get_toplevel (widget);
676                         if (GTK_WIDGET_TOPLEVEL (toplevel) && GTK_IS_WINDOW (toplevel))
677                                 g_signal_emit_by_name (toplevel, "move-focus", GTK_DIR_UP);
678                         unselect_all (atts_view);
679                 } else {
680                         new_sel = g_list_find (box_children, (gpointer) current_sel);
681                         new_sel = find_prev_or_next_not_purged (new_sel, TRUE, FALSE);
682                         /* We assume that we detected properly that
683                            there is a not purge attachment so we don't
684                            need to check NULL */
685                         set_selected (MODEST_ATTACHMENTS_VIEW (atts_view), MODEST_ATTACHMENT_VIEW (new_sel->data));
686                 }
687                 g_list_free (box_children);
688                 return TRUE;
689         }
690
691         if (event->keyval == GDK_Down) {
692                 ModestAttachmentView *current_sel = NULL;
693                 gboolean move_out = FALSE;
694                 GList * box_children, *new_sel, *last_child = NULL;
695
696                 box_children = gtk_container_get_children (GTK_CONTAINER (priv->box));
697
698                 if (box_children == NULL) {
699                         move_out = TRUE;
700                 } else {
701                         last_child = g_list_last (box_children);
702                         last_child = find_prev_or_next_not_purged (last_child, TRUE, TRUE);
703                         if (priv->selected != NULL && last_child != NULL) {
704                                 GList *last_selected = g_list_last (priv->selected);
705                                 if (last_selected->data != last_child->data)
706                                         current_sel = (ModestAttachmentView *) last_selected->data;
707                                 else
708                                         move_out = TRUE;
709                         } else {
710                                 move_out = TRUE;
711                         }
712                 }
713
714                 if (move_out) {
715                         GtkWidget *toplevel = NULL;
716                         /* move cursor outside */
717                         toplevel = gtk_widget_get_toplevel (widget);
718                         if (GTK_WIDGET_TOPLEVEL (toplevel) && GTK_IS_WINDOW (toplevel))
719                                 g_signal_emit_by_name (toplevel, "move-focus", GTK_DIR_DOWN);
720                         unselect_all (atts_view);
721                 } else {
722                         new_sel = g_list_find (box_children, (gpointer) current_sel);
723                         new_sel = find_prev_or_next_not_purged (new_sel, FALSE, FALSE);
724                         set_selected (MODEST_ATTACHMENTS_VIEW (atts_view), MODEST_ATTACHMENT_VIEW (new_sel->data));
725                 }
726                 g_list_free (box_children);
727                 return TRUE;
728         }
729
730         if (event->keyval == GDK_BackSpace) {
731                 g_signal_emit (G_OBJECT (widget), signals[DELETE_SIGNAL], 0);
732                 return TRUE;
733         }
734
735         /* Activates selected item */
736         if (g_list_length (priv->selected) == 1) {
737                 ModestAttachmentView *att_view = (ModestAttachmentView *) priv->selected->data;
738                 if ((event->keyval == GDK_Return)) {
739                         TnyMimePart *mime_part = tny_mime_part_view_get_part (TNY_MIME_PART_VIEW (att_view));
740                         if (TNY_IS_MIME_PART (mime_part)) {
741                                 g_signal_emit (G_OBJECT (widget), signals[ACTIVATE_SIGNAL], 0, mime_part);
742                                 g_object_unref (mime_part);
743                         }
744                         return TRUE;
745                 }
746         }
747
748         return FALSE;
749 }
750
751
752 static GtkWidget *
753 get_att_view_at_coords (ModestAttachmentsView *atts_view,
754                         gdouble x, gdouble y)
755 {
756         ModestAttachmentsViewPrivate *priv = NULL;
757         GList *att_view_list, *node;
758         GtkWidget *result = NULL;
759
760         priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
761         att_view_list = gtk_container_get_children (GTK_CONTAINER (priv->box));
762         
763         for (node = att_view_list; node != NULL; node = g_list_next (node)) {
764                 GtkWidget *att_view = (GtkWidget *) node->data;
765                 gint pos_x, pos_y, w, h, int_x, int_y;
766                 gint widget_x, widget_y;
767
768                 gdk_window_get_origin (att_view->window, &widget_x, &widget_y);
769
770                 pos_x = widget_x;
771                 pos_y = widget_y;
772                 w = att_view->allocation.width;
773                 h = att_view->allocation.height;
774
775                 int_x = (gint) x - GTK_WIDGET (atts_view)->allocation.x;
776                 int_y = (gint) y - GTK_WIDGET (atts_view)->allocation.y;
777
778                 if ((x >= pos_x) && (x <= (pos_x + w)) && (y >= pos_y) && (y <= (pos_y + h))) {
779                         result = att_view;
780                         break;
781                 }
782         }
783
784         g_list_free (att_view_list);
785         return result;
786 }
787
788 static void
789 unselect_all (ModestAttachmentsView *atts_view)
790 {
791         ModestAttachmentsViewPrivate *priv = NULL;
792         GList *att_view_list, *node;
793
794         priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
795         att_view_list = gtk_container_get_children (GTK_CONTAINER (priv->box));
796         
797         for (node = att_view_list; node != NULL; node = g_list_next (node)) {
798                 GtkWidget *att_view = (GtkWidget *) node->data;
799
800                 if (GTK_WIDGET_STATE (att_view) == GTK_STATE_SELECTED)
801                         gtk_widget_set_state (att_view, GTK_STATE_NORMAL);
802         }
803
804         g_list_free (priv->selected);
805         priv->selected = NULL;
806
807         g_list_free (att_view_list);
808 }
809
810 static void 
811 set_selected (ModestAttachmentsView *atts_view, ModestAttachmentView *att_view)
812 {
813         ModestAttachmentsViewPrivate *priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
814         TnyMimePart *part;
815
816         unselect_all (atts_view);
817         part = tny_mime_part_view_get_part (TNY_MIME_PART_VIEW (att_view));
818         
819         g_list_free (priv->selected);
820         priv->selected = NULL;
821         if (TNY_IS_MIME_PART (part) && !tny_mime_part_is_purged (part)) {
822                 gtk_widget_set_state (GTK_WIDGET (att_view), GTK_STATE_SELECTED);
823                 priv->selected = g_list_append (priv->selected, att_view);
824         }
825         if (part)
826                 g_object_unref (part);
827         
828         own_clipboard (atts_view);
829 }
830
831 static void 
832 select_range (ModestAttachmentsView *atts_view, ModestAttachmentView *att1, ModestAttachmentView *att2)
833 {
834         ModestAttachmentsViewPrivate *priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
835         GList *children = NULL;
836         GList *node = NULL;
837         gboolean selecting = FALSE;
838         TnyMimePart *part;
839
840         unselect_all (atts_view);
841
842         if (att1 == att2) {
843                 set_selected (atts_view, att1);
844                 return;
845         }
846
847         children = gtk_container_get_children (GTK_CONTAINER (priv->box));
848         g_list_free (priv->selected);
849         priv->selected = NULL;
850
851
852         for (node = children; node != NULL; node = g_list_next (node)) {
853                 if ((node->data == att1) || (node->data == att2)) {
854                         part = tny_mime_part_view_get_part (TNY_MIME_PART_VIEW (node->data));
855                         if (!tny_mime_part_is_purged (part)) {
856                                 gtk_widget_set_state (GTK_WIDGET (node->data), GTK_STATE_SELECTED);
857                                 priv->selected = g_list_append (priv->selected, node->data);
858                         }
859                         g_object_unref (part);
860                         selecting = !selecting;
861                 } else if (selecting) {
862                         part = tny_mime_part_view_get_part (TNY_MIME_PART_VIEW (node->data));
863                         if (!tny_mime_part_is_purged (part)) {
864                                 gtk_widget_set_state (GTK_WIDGET (node->data), GTK_STATE_SELECTED);
865                                 priv->selected = g_list_append (priv->selected, node->data);
866                         }
867                         g_object_unref (part);
868                 }
869                         
870         }
871         g_list_free (children);
872         
873         own_clipboard (atts_view);
874 }
875
876 static void clipboard_get (GtkClipboard *clipboard, GtkSelectionData *selection_data,
877                            guint info, gpointer userdata)
878 {
879         ModestAttachmentsView *atts_view = (ModestAttachmentsView *) userdata;
880         ModestAttachmentsViewPrivate *priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
881
882         if ((priv->selected != NULL)&&(priv->selected->next == NULL)) {
883                 if (info == MODEST_ATTACHMENTS_VIEW_CLIPBOARD_TYPE_INDEX) {
884                         /* MODEST_ATTACHMENT requested. As the content id is not filled in all the case, we'll
885                          * use an internal index. This index is simply the index of the attachment in the vbox */
886                         GList *box_children = NULL;
887                         gint index;
888                         box_children = gtk_container_get_children (GTK_CONTAINER (priv->box));
889                         index = g_list_index (box_children, priv->selected);
890                         if (index >= 0) {
891                                 gchar *index_str = g_strdup_printf("%d", index);
892                                 gtk_selection_data_set_text (selection_data, index_str, -1);
893                                 g_free (index_str);
894                         }
895                 }
896         }
897 }
898
899 TnyList *
900 modest_attachments_view_get_selection (ModestAttachmentsView *atts_view)
901 {
902         ModestAttachmentsViewPrivate *priv;
903         TnyList *selection;
904         GList *node;
905
906         g_return_val_if_fail (MODEST_IS_ATTACHMENTS_VIEW (atts_view), NULL);
907         priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
908
909         selection = tny_simple_list_new ();
910         for (node = priv->selected; node != NULL; node = g_list_next (node)) {
911                 ModestAttachmentView *att_view = (ModestAttachmentView *) node->data;
912                 TnyMimePart *part = tny_mime_part_view_get_part (TNY_MIME_PART_VIEW (att_view));
913                 tny_list_append (selection, (GObject *) part);
914                 g_object_unref (part);
915         }
916         
917         return selection;
918 }
919
920 TnyList *
921 modest_attachments_view_get_attachments (ModestAttachmentsView *atts_view)
922 {
923         ModestAttachmentsViewPrivate *priv;
924         TnyList *att_list;
925         GList *children, *node= NULL;
926
927         g_return_val_if_fail (MODEST_IS_ATTACHMENTS_VIEW (atts_view), NULL);
928         priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
929
930         att_list = TNY_LIST (tny_simple_list_new ());
931
932         children = gtk_container_get_children (GTK_CONTAINER (priv->box));
933         for (node = children; node != NULL; node = g_list_next (node)) {
934                 GtkWidget *att_view = GTK_WIDGET (node->data);
935                 TnyMimePart *mime_part = tny_mime_part_view_get_part (TNY_MIME_PART_VIEW (att_view));
936                 tny_list_append (att_list, (GObject *) mime_part);
937                 g_object_unref (mime_part);
938         }
939         g_list_free (children);
940         return att_list;
941
942 }
943
944 void
945 modest_attachments_view_select_all (ModestAttachmentsView *atts_view)
946 {
947         ModestAttachmentsViewPrivate *priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
948         GList *children = NULL;
949         GList *node = NULL;
950
951         unselect_all (atts_view);
952
953         if (priv->style == MODEST_ATTACHMENTS_VIEW_STYLE_LINKS)
954                 return;
955
956         children = gtk_container_get_children (GTK_CONTAINER (priv->box));
957         g_list_free (priv->selected);
958         priv->selected = NULL;
959
960         for (node = children; node != NULL; node = g_list_next (node)) {
961                 ModestAttachmentView *att_view = (ModestAttachmentView *) node->data;
962                 TnyMimePart *mime_part = tny_mime_part_view_get_part (TNY_MIME_PART_VIEW (att_view));
963
964                 /* Do not select purged attachments */
965                 if (TNY_IS_MIME_PART (mime_part) && !tny_mime_part_is_purged (mime_part)) {
966                         gtk_widget_set_state (GTK_WIDGET (node->data), GTK_STATE_SELECTED);
967                         priv->selected = g_list_append (priv->selected, node->data);
968                 }
969                 g_object_unref (mime_part);
970         }
971         g_list_free (children);
972
973         own_clipboard (atts_view);
974 }
975
976 gboolean
977 modest_attachments_view_has_attachments (ModestAttachmentsView *atts_view)
978 {
979         ModestAttachmentsViewPrivate *priv;
980         GList *children;
981         gboolean result;
982
983         g_return_val_if_fail (MODEST_IS_ATTACHMENTS_VIEW (atts_view), FALSE);
984         priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
985
986         children = gtk_container_get_children (GTK_CONTAINER (priv->box));
987         result = (children != NULL);
988         g_list_free (children);
989
990         return result;
991 }
992
993 void
994 modest_attachments_view_get_sizes (ModestAttachmentsView *attachments_view,
995                                    gint *attachments_count,
996                                    guint64 *attachments_size)
997 {
998         ModestAttachmentsViewPrivate *priv;
999         GList *children, *node;
1000
1001         g_return_if_fail (MODEST_IS_ATTACHMENTS_VIEW (attachments_view));
1002         g_return_if_fail (attachments_count != NULL && attachments_size != NULL);
1003
1004         *attachments_count = 0;
1005         *attachments_size = 0;
1006
1007         priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (attachments_view);
1008
1009         children = gtk_container_get_children (GTK_CONTAINER (priv->box));
1010         for (node = children; node != NULL; node = g_list_next (node)) {
1011                 GtkWidget *att_view = (GtkWidget *) node->data;
1012                 TnyMimePart *part = tny_mime_part_view_get_part (TNY_MIME_PART_VIEW (att_view));
1013
1014                 if (!tny_mime_part_is_purged (part)) {
1015                         guint64 size;
1016                         (*attachments_count) ++;
1017                         size = modest_attachment_view_get_size (MODEST_ATTACHMENT_VIEW (att_view));
1018                         if (size == 0) {
1019                                 /* we do a random estimation of the size of an attachment */
1020                                 size = 32768;
1021                         }
1022                         *attachments_size += size;
1023                 }
1024                 g_object_unref (part);
1025         }
1026         g_list_free (children);
1027 }
1028
1029 static void
1030 dummy_clear_func (GtkClipboard *clipboard,
1031                   gpointer user_data_or_owner)
1032 {
1033         /* Do nothing */
1034 }
1035
1036 static void
1037 own_clipboard (ModestAttachmentsView *atts_view)
1038 {
1039         GtkTargetEntry targets[] = {
1040                 {MODEST_ATTACHMENTS_VIEW_CLIPBOARD_TYPE, 0, MODEST_ATTACHMENTS_VIEW_CLIPBOARD_TYPE_INDEX},
1041         };
1042
1043         gtk_clipboard_set_with_owner (gtk_widget_get_clipboard (GTK_WIDGET (atts_view), GDK_SELECTION_PRIMARY),
1044                                       targets, G_N_ELEMENTS (targets),
1045                                       clipboard_get, dummy_clear_func, G_OBJECT(atts_view));
1046 }
1047
1048 static gboolean 
1049 focus_out_event (GtkWidget *widget, GdkEventFocus *event, ModestAttachmentsView *atts_view)
1050 {
1051         if (!gtk_widget_is_focus (widget))
1052                 unselect_all (atts_view);
1053
1054         return FALSE;
1055 }
1056
1057 static gboolean 
1058 focus (GtkWidget *widget, GtkDirectionType direction, ModestAttachmentsView *atts_view)
1059 {
1060         ModestAttachmentsViewPrivate *priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
1061         GList *children = NULL;
1062         GtkWidget *toplevel = NULL;
1063
1064         toplevel = gtk_widget_get_toplevel (widget);
1065         if (!gtk_window_has_toplevel_focus (GTK_WINDOW (toplevel)))
1066                 return FALSE;
1067
1068         if (priv->style != MODEST_ATTACHMENTS_VIEW_STYLE_NO_FOCUS) {
1069                 children = gtk_container_get_children (GTK_CONTAINER (priv->box));
1070                 if (children != NULL) {
1071                         set_selected (atts_view, MODEST_ATTACHMENT_VIEW (children->data));
1072                 }
1073                 g_list_free (children);
1074         }
1075
1076         return FALSE;
1077 }
1078
1079 void 
1080 modest_attachments_view_set_style (ModestAttachmentsView *self,
1081                                    ModestAttachmentsViewStyle style)
1082 {
1083         ModestAttachmentsViewPrivate *priv;
1084
1085         g_return_if_fail (MODEST_IS_ATTACHMENTS_VIEW (self));
1086         priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (self);
1087
1088         if (priv->style != style) {
1089                 priv->style = style;
1090                 gtk_widget_queue_draw (GTK_WIDGET (self));
1091                 if (priv->style == MODEST_ATTACHMENTS_VIEW_STYLE_SELECTABLE) {
1092                         GTK_WIDGET_SET_FLAGS (self, GTK_CAN_FOCUS);
1093                 } else {
1094                         GTK_WIDGET_UNSET_FLAGS (self, GTK_CAN_FOCUS);
1095                 }
1096
1097         }
1098 }
1099
1100 guint
1101 modest_attachments_view_get_num_attachments (ModestAttachmentsView *atts_view)
1102 {
1103         ModestAttachmentsViewPrivate *priv;
1104         GList *children;
1105         gint result;
1106
1107         g_return_val_if_fail (MODEST_IS_ATTACHMENTS_VIEW (atts_view), 0);
1108         priv = MODEST_ATTACHMENTS_VIEW_GET_PRIVATE (atts_view);
1109
1110         children = gtk_container_get_children (GTK_CONTAINER (priv->box));
1111         result = g_list_length (children);
1112         g_list_free (children);
1113
1114         return result;
1115 }
1116
1117 static void 
1118 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
1119 {
1120         if (strcmp ("style", spec->name) == 0) {
1121                 update_style (MODEST_ATTACHMENTS_VIEW (obj));
1122                 gtk_widget_queue_draw (GTK_WIDGET (obj));
1123         } 
1124 }
1125
1126 /* This method updates the color (and other style settings) of widgets using secondary text color,
1127  * tracking the gtk style */
1128 static void
1129 update_style (ModestAttachmentsView *self)
1130 {
1131 #ifdef MODEST_COMPACT_HEADER_BG
1132         GdkColor bg_color;
1133         GtkStyle *style;
1134         GdkColor *current_bg;
1135
1136         g_return_if_fail (MODEST_IS_ATTACHMENTS_VIEW (self));
1137
1138         gdk_color_parse (MODEST_COMPACT_HEADER_BG, &bg_color);
1139         style = gtk_widget_get_style (GTK_WIDGET (self));
1140         current_bg = &(style->bg[GTK_STATE_NORMAL]);
1141         if (current_bg->red != bg_color.red || current_bg->blue != bg_color.blue || current_bg->green != bg_color.green)
1142                 gtk_widget_modify_bg (GTK_WIDGET (self), GTK_STATE_NORMAL, &bg_color);
1143 #endif
1144 }
1145