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