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