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