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