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