* fixed bug which crashed modest after sending an email with attachment(s). The attac...
[modest] / src / modest-tny-header-tree-view.c
1
2 /* modest-tny-header-tree-view.c 
3  */
4 #include <glib/gi18n.h>
5 #include "modest-tny-header-tree-view.h"
6 #include <tny-list-iface.h>
7 #include <string.h>
8 #include "modest-marshal.h"
9
10 #include <modest-icon-names.h>
11 #include "modest-icon-factory.h"
12
13 /* 'private'/'protected' functions */
14 static void modest_tny_header_tree_view_class_init  (ModestTnyHeaderTreeViewClass *klass);
15 static void modest_tny_header_tree_view_init        (ModestTnyHeaderTreeView *obj);
16 static void modest_tny_header_tree_view_finalize    (GObject *obj);
17
18 static void selection_changed (GtkTreeSelection *sel, gpointer user_data);
19 static void column_clicked (GtkTreeViewColumn *treeviewcolumn, gpointer user_data);
20 static gboolean refresh_folder_finish_status_update (gpointer user_data);
21
22 enum {
23         MESSAGE_SELECTED_SIGNAL,
24         STATUS_UPDATE_SIGNAL,
25         LAST_SIGNAL
26 };
27
28 typedef struct _ModestTnyHeaderTreeViewPrivate ModestTnyHeaderTreeViewPrivate;
29 struct _ModestTnyHeaderTreeViewPrivate {
30
31         TnyMsgFolderIface *tny_msg_folder;
32         TnyListIface      *headers;
33
34         gint              status_id;
35         GSList            *columns;
36         
37         ModestTnyHeaderTreeViewStyle style;
38 };
39 #define MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
40                                                          MODEST_TYPE_TNY_HEADER_TREE_VIEW, \
41                                                          ModestTnyHeaderTreeViewPrivate))
42 /* globals */
43 static GObjectClass *parent_class = NULL;
44
45 /* uncomment the following if you have defined any signals */
46 static guint signals[LAST_SIGNAL] = {0};
47
48 GType
49 modest_tny_header_tree_view_get_type (void)
50 {
51         static GType my_type = 0;
52         if (!my_type) {
53                 static const GTypeInfo my_info = {
54                         sizeof(ModestTnyHeaderTreeViewClass),
55                         NULL,           /* base init */
56                         NULL,           /* base finalize */
57                         (GClassInitFunc) modest_tny_header_tree_view_class_init,
58                         NULL,           /* class finalize */
59                         NULL,           /* class data */
60                         sizeof(ModestTnyHeaderTreeView),
61                         1,              /* n_preallocs */
62                         (GInstanceInitFunc) modest_tny_header_tree_view_init,
63                 };
64                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
65                                                   "ModestTnyHeaderTreeView",
66                                                   &my_info, 0);
67         }
68         return my_type;
69 }
70
71 static void
72 modest_tny_header_tree_view_class_init (ModestTnyHeaderTreeViewClass *klass)
73 {
74         GObjectClass *gobject_class;
75         gobject_class = (GObjectClass*) klass;
76
77         parent_class            = g_type_class_peek_parent (klass);
78         gobject_class->finalize = modest_tny_header_tree_view_finalize;
79         
80         g_type_class_add_private (gobject_class, sizeof(ModestTnyHeaderTreeViewPrivate));
81         
82         signals[MESSAGE_SELECTED_SIGNAL] = 
83                 g_signal_new ("message_selected",
84                               G_TYPE_FROM_CLASS (gobject_class),
85                               G_SIGNAL_RUN_FIRST,
86                               G_STRUCT_OFFSET (ModestTnyHeaderTreeViewClass,message_selected),
87                               NULL, NULL,
88                               g_cclosure_marshal_VOID__POINTER,
89                               G_TYPE_NONE, 1, G_TYPE_POINTER);
90         
91         signals[STATUS_UPDATE_SIGNAL] = 
92                 g_signal_new ("status_update",
93                               G_TYPE_FROM_CLASS (gobject_class),
94                               G_SIGNAL_RUN_FIRST,
95                               G_STRUCT_OFFSET (ModestTnyHeaderTreeViewClass,message_selected),
96                               NULL, NULL,
97                               modest_marshal_VOID__STRING_INT,
98                               G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT);       
99 }
100
101
102
103
104
105 static void
106 msgtype_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
107                    GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data)
108 {
109         TnyMsgHeaderFlags flags;
110         GdkPixbuf *pixbuf;
111
112         gtk_tree_model_get (tree_model, iter, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
113                             &flags, -1);
114
115         if (flags & TNY_MSG_HEADER_FLAG_DELETED)
116                 pixbuf = modest_icon_factory_get_icon (MODEST_HEADER_ICON_DELETED);
117         else if (flags & TNY_MSG_HEADER_FLAG_SEEN)
118                 pixbuf = modest_icon_factory_get_icon (MODEST_HEADER_ICON_READ);
119         else
120                 pixbuf = modest_icon_factory_get_icon (MODEST_HEADER_ICON_UNREAD);
121         if (pixbuf)
122                 g_object_set (G_OBJECT (renderer), "pixbuf", pixbuf, NULL);
123 }
124
125 static void
126 attach_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
127                   GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data)
128 {
129         TnyMsgHeaderFlags flags;
130         GdkPixbuf *pixbuf = NULL;
131
132         gtk_tree_model_get (tree_model, iter, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
133                             &flags, -1);
134
135         if (flags & TNY_MSG_HEADER_FLAG_ATTACHMENTS) {
136                 pixbuf = modest_icon_factory_get_icon (MODEST_HEADER_ICON_ATTACH);
137                 if (pixbuf)
138                         g_object_set (G_OBJECT (renderer), "pixbuf", pixbuf, NULL);
139         }
140 }
141
142
143
144 static void
145 header_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
146                   GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer user_data)
147 {
148         TnyMsgHeaderFlags flags;
149         
150         gtk_tree_model_get (tree_model, iter, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
151                             &flags, -1);
152
153         g_object_set (G_OBJECT(renderer),
154                       "weight", (flags & TNY_MSG_HEADER_FLAG_SEEN) ? 400: 800,
155                       "style",  (flags & TNY_MSG_HEADER_FLAG_DELETED) ?
156                                  PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL,
157                       NULL);    
158 }
159
160
161
162 static void
163 sender_receiver_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
164                             GtkTreeModel *tree_model,  GtkTreeIter *iter,  gboolean is_sender)
165 {
166         TnyMsgHeaderFlags flags;
167         gchar *addr1, *addr2;
168         gint sender_receiver_col;
169
170         if (is_sender)
171                 sender_receiver_col = TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN;
172         else
173                 sender_receiver_col = TNY_MSG_HEADER_LIST_MODEL_TO_COLUMN;
174                 
175         gtk_tree_model_get (tree_model, iter,
176                             sender_receiver_col,  &addr1,
177                             TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
178                             -1);
179         
180         /* simplistic --> remove <email@address> from display */
181         addr2 = g_strstr_len (addr1, strlen(addr1), "<");
182         if (addr2) 
183                 addr2[0]='\0';
184
185         g_object_set (G_OBJECT(renderer),
186                       "text", addr1,
187                       "weight", (flags & TNY_MSG_HEADER_FLAG_SEEN) ? 400 : 800,
188                       "style",  (flags & TNY_MSG_HEADER_FLAG_DELETED) ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL,
189                       NULL);
190
191         g_free (addr1); 
192 }
193
194
195 /* not reentrant/thread-safe */
196 const gchar*
197 display_date (time_t date)
198 {
199         struct tm date_tm, now_tm; 
200         time_t now;
201
202         const gint buf_size = 64; 
203         static gchar date_buf[64]; /* buf_size is not ... */
204         static gchar now_buf[64];  /* ...const enough... */
205         
206         now = time (NULL);
207         
208         localtime_r(&now, &now_tm);
209         localtime_r(&date, &date_tm);
210
211         /* get today's date */
212         strftime (date_buf, buf_size, "%x", &date_tm);
213         strftime (now_buf,  buf_size, "%x",  &now_tm);  /* today */
214
215         /* if this is today, get the time instead of the date */
216         if (strcmp (date_buf, now_buf) == 0)
217                 strftime (date_buf, buf_size, _("%X"), &date_tm); 
218                 
219         return date_buf;
220 }
221
222
223 static void
224 compact_header_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
225                            GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer user_data)
226 {
227         GObject *rendobj;
228         TnyMsgHeaderFlags flags;
229         gchar *from, *subject;
230         gchar *address;
231         gchar *header;
232         time_t date;
233                 
234         gtk_tree_model_get (tree_model, iter,
235                             TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
236                             TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN,  &from,
237                             TNY_MSG_HEADER_LIST_MODEL_SUBJECT_COLUMN, &subject,
238                             TNY_MSG_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN, &date,   
239                             -1);
240         rendobj = G_OBJECT(renderer);           
241         
242         /* simplistic --> remove <email@address> from display */
243         address = g_strstr_len (from, strlen(from), "<");
244         if (address)
245                 address[0]='\0'; /* set a new endpoint */
246         
247         header = g_strdup_printf ("%s %s\n%s", from,
248                                   display_date(date), subject);
249         g_object_set (G_OBJECT(renderer),
250                       "text",  header,
251                       "weight", (flags & TNY_MSG_HEADER_FLAG_SEEN) ? 400: 800,
252                       "style",  (flags & TNY_MSG_HEADER_FLAG_DELETED) ?
253                                  PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL,
254                       NULL);    
255         g_free (header);
256         g_free (from);
257         g_free (subject);
258 }
259
260
261 static GtkTreeViewColumn*
262 get_new_column (const gchar *name, GtkCellRenderer *renderer,
263                 gboolean resizable, gint sort_col_id, gboolean show_as_text,
264                 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
265 {
266         GtkTreeViewColumn *column;
267
268         column =  gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
269         gtk_tree_view_column_set_resizable (column, resizable);
270
271         if (show_as_text) 
272                 gtk_tree_view_column_add_attribute (column, renderer, "text",
273                                                     sort_col_id);
274         if (sort_col_id >= 0)
275                 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
276
277         gtk_tree_view_column_set_sort_indicator (column, FALSE);
278         gtk_tree_view_column_set_reorderable (column, TRUE);
279
280         if (cell_data_func)
281                 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
282                                                         user_data, NULL);
283
284 /*      g_signal_connect (G_OBJECT (column), "clicked", */
285 /*                        G_CALLBACK (column_clicked), obj);  */
286
287         return column;
288 }
289
290
291
292
293 static void
294 remove_all_columns (ModestTnyHeaderTreeView *obj)
295 {
296         GList *columns, *cursor;
297
298         columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
299
300         for (cursor = columns; cursor; cursor = cursor->next)
301                 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
302                                              GTK_TREE_VIEW_COLUMN(cursor->data));
303         g_list_free (columns);  
304 }
305
306
307
308
309 static void
310 init_columns (ModestTnyHeaderTreeView *obj)
311 {
312         GtkTreeViewColumn *column=NULL;
313         GtkCellRenderer *renderer_msgtype,
314                 *renderer_header,
315                 *renderer_attach;
316
317         ModestTnyHeaderTreeViewPrivate *priv;
318         GSList *cursor;
319         
320         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(obj); 
321                         
322         renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
323         renderer_attach  = gtk_cell_renderer_pixbuf_new ();
324         renderer_header = gtk_cell_renderer_text_new (); 
325         
326         remove_all_columns (obj);
327         
328         for (cursor = priv->columns; cursor; cursor = cursor->next) {
329                 ModestTnyHeaderTreeViewColumn col =
330                         (ModestTnyHeaderTreeViewColumn) GPOINTER_TO_INT(cursor->data);
331                 
332                 switch (col) {
333                         
334                 case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_MSGTYPE:
335
336                         column = get_new_column (_("M"), renderer_msgtype, FALSE,
337                                                  TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
338                                                  FALSE, (GtkTreeCellDataFunc)msgtype_cell_data,
339                                                  NULL);
340                         break;
341
342                 case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_ATTACH:
343
344                         column = get_new_column (_("A"), renderer_attach, FALSE,
345                                                  TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
346                                                  FALSE, (GtkTreeCellDataFunc)attach_cell_data,
347                                                  NULL);
348                         break;
349                         
350                 case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_RECEIVED_DATE:
351                         column = get_new_column (_("Received"), renderer_header, TRUE,
352                                                  TNY_MSG_HEADER_LIST_MODEL_DATE_RECEIVED_COLUMN,
353                                                  TRUE, (GtkTreeCellDataFunc)header_cell_data,
354                                                  NULL);
355                         break;
356                         
357                 case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_FROM:
358                         column = get_new_column (_("From"), renderer_header, TRUE,
359                                                  TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN,
360                                                  TRUE, (GtkTreeCellDataFunc)sender_receiver_cell_data,
361                                                  GINT_TO_POINTER(TRUE));
362                         break;
363
364                 case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_TO:
365                         column = get_new_column (_("To"), renderer_header, TRUE,
366                                                  TNY_MSG_HEADER_LIST_MODEL_TO_COLUMN,
367                                                  TRUE, (GtkTreeCellDataFunc)sender_receiver_cell_data,
368                                                  GINT_TO_POINTER(FALSE));
369                         break;
370                         
371                 case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_COMPACT_HEADER:
372                         column = get_new_column (_("Header"), renderer_header, TRUE,
373                                                  TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN,
374                                                  TRUE, (GtkTreeCellDataFunc)compact_header_cell_data,
375                                                  NULL);
376                         break;
377                         
378                 case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_SUBJECT:
379                         column = get_new_column (_("Subject"), renderer_header, TRUE,
380                                                  TNY_MSG_HEADER_LIST_MODEL_SUBJECT_COLUMN,
381                                                  TRUE, (GtkTreeCellDataFunc)header_cell_data,
382                                                  NULL);
383                         break;
384                         
385                         
386                 case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_SENT_DATE:
387                         column = get_new_column (_("Sent"), renderer_header, TRUE,
388                                                  TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_COLUMN,
389                                                  TRUE, (GtkTreeCellDataFunc)header_cell_data,
390                                                  NULL);
391                         break;
392                 }
393                 gtk_tree_view_append_column (GTK_TREE_VIEW(obj), column);               
394         }       
395 }
396
397
398
399
400
401 static void
402 modest_tny_header_tree_view_init (ModestTnyHeaderTreeView *obj)
403 {
404         ModestTnyHeaderTreeViewPrivate *priv;
405         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(obj); 
406
407         priv->status_id = 0;
408 }
409
410 static void
411 modest_tny_header_tree_view_finalize (GObject *obj)
412 {
413         ModestTnyHeaderTreeView        *self;
414         ModestTnyHeaderTreeViewPrivate *priv;
415         
416         self = MODEST_TNY_HEADER_TREE_VIEW(obj);
417         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self);
418
419         if (priv->headers)      
420                 g_object_unref (G_OBJECT(priv->headers));
421         
422         priv->headers = NULL;
423         priv->tny_msg_folder    = NULL;
424 }
425
426 GtkWidget*
427 modest_tny_header_tree_view_new (TnyMsgFolderIface *folder,
428                                  GSList *columns,
429                                  ModestTnyHeaderTreeViewStyle style)
430 {
431         GObject *obj;
432         GtkTreeSelection *sel;
433         ModestTnyHeaderTreeView *self;
434         
435         obj  = G_OBJECT(g_object_new(MODEST_TYPE_TNY_HEADER_TREE_VIEW, NULL));
436         self = MODEST_TNY_HEADER_TREE_VIEW(obj);
437         
438         if (!modest_tny_header_tree_view_set_folder (self, NULL)) {
439                 g_warning ("could not set the folder");
440                 g_object_unref (obj);
441                 return NULL;
442         }
443         
444         modest_tny_header_tree_view_set_style   (self, style);
445         modest_tny_header_tree_view_set_columns (self, columns);
446         
447         /* all cols */
448         gtk_tree_view_set_headers_visible   (GTK_TREE_VIEW(obj), TRUE);
449         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(obj), TRUE);
450         
451         gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
452                                       TRUE); /* alternating row colors */
453
454         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
455         g_signal_connect (sel, "changed",
456                           G_CALLBACK(selection_changed), self);
457         
458         return GTK_WIDGET(self);
459 }
460
461 gboolean
462 modest_tny_header_tree_view_set_columns (ModestTnyHeaderTreeView *self, GSList *columns)
463 {
464         ModestTnyHeaderTreeViewPrivate *priv;
465         GSList *cursor;
466
467         g_return_val_if_fail (self, FALSE);
468         
469         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self);
470
471         g_slist_free (priv->columns);
472         
473         for (cursor = columns; cursor; cursor = cursor->next) {
474                 ModestTnyHeaderTreeViewColumn col = 
475                         (ModestTnyHeaderTreeViewColumn) GPOINTER_TO_INT(cursor->data);
476                 if (0 > col || col >= MODEST_TNY_HEADER_TREE_VIEW_COLUMN_NUM)
477                         g_warning ("invalid column in column list");
478                 else
479                         priv->columns = g_slist_append (priv->columns, cursor->data);
480         }
481
482         init_columns (self); /* redraw them */
483         return TRUE;
484 }
485
486
487
488 const GSList*
489 modest_tny_header_tree_view_get_columns (ModestTnyHeaderTreeView *self)
490 {
491         ModestTnyHeaderTreeViewPrivate *priv;
492
493         g_return_val_if_fail (self, FALSE);
494         
495         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self);
496         return priv->columns;
497 }
498
499
500
501
502 gboolean
503 modest_tny_header_tree_view_set_style (ModestTnyHeaderTreeView *self,
504                                        ModestTnyHeaderTreeViewStyle style)
505 {
506         g_return_val_if_fail (self, FALSE);
507         g_return_val_if_fail (style >= 0 && style < MODEST_TNY_HEADER_TREE_VIEW_STYLE_NUM,
508                               FALSE);
509         
510         MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self)->style = style;
511         
512         return TRUE;
513 }
514
515 ModestTnyHeaderTreeViewStyle
516 modest_tny_header_tree_view_get_style (ModestTnyHeaderTreeView *self)
517 {
518         g_return_val_if_fail (self, FALSE);
519
520         return MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self)->style;
521 }
522
523
524
525 /* get the length of any prefix that should be ignored for sorting */
526 static inline int 
527 get_prefix_len (const gchar *sub)
528 {
529         gint i = 0;
530         const static gchar* prefix[] = {"Re:", "RE:", "Fwd:", "FWD:", "FW:", "AW:", NULL};
531
532         if (sub[0] != 'R' && sub[0] != 'F') /* optimization */
533                 return 0;
534         
535         while (prefix[i]) {
536                 if (g_str_has_prefix(sub, prefix[i])) {
537                         int prefix_len = strlen(prefix[i]); 
538                         if (sub[prefix_len] == ' ')
539                                 ++prefix_len; /* ignore space after prefix as well */
540                         return prefix_len; 
541                 }
542                 ++i;
543         }
544         return 0;
545 }
546
547
548 static inline gint
549 cmp_normalized_subject (const gchar* s1, const gchar *s2)
550 {
551         gint result = 0;
552         register gchar *n1, *n2;
553         
554         n1 = g_utf8_collate_key (s1 + get_prefix_len(s1), -1);
555         n2 = g_utf8_collate_key (s2 + get_prefix_len(s2), -1);
556         
557         result = strcmp (n1, n2);
558         g_free (n1);
559         g_free (n2);
560         
561         return result;
562 }
563
564
565 static gint
566 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
567           gpointer user_data)
568 {
569         gint col_id = GPOINTER_TO_INT (user_data);
570         gint t1, t2;
571         gint val1, val2;
572         gchar *s1, *s2;
573         gint cmp;
574         
575         g_return_val_if_fail (GTK_IS_TREE_MODEL(tree_model), -1);
576         
577         switch (col_id) {
578
579                 /* first one, we decide based on the time */
580         case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_COMPACT_HEADER:
581         case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_RECEIVED_DATE:
582                 gtk_tree_model_get (tree_model, iter1, TNY_MSG_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
583                                     &t1,-1);
584                 gtk_tree_model_get (tree_model, iter2, TNY_MSG_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
585                                     &t2,-1);
586                 return t1 - t2;
587                 
588         case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_SENT_DATE:
589                 gtk_tree_model_get (tree_model, iter1, TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
590                                     &t1,-1);
591                 gtk_tree_model_get (tree_model, iter2, TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
592                                     &t2,-1);
593                 return t1 - t2;
594
595                 
596                 /* next ones, we try the search criteria first, if they're the same, then we use 'sent date' */
597         case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_SUBJECT: {
598
599                 gtk_tree_model_get (tree_model, iter1,
600                                     TNY_MSG_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s1,
601                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
602                                     -1);
603                 gtk_tree_model_get (tree_model, iter2,
604                                     TNY_MSG_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s2,
605                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
606                                     -1);
607                 
608                 cmp = cmp_normalized_subject(s1, s2);
609
610                 g_free (s1);
611                 g_free (s2);
612                 
613                 return cmp ? cmp : t1 - t2;
614         }
615                 
616         case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_FROM:
617                 
618                 gtk_tree_model_get (tree_model, iter1,
619                                     TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN, &s1,
620                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
621                                     -1);
622                 gtk_tree_model_get (tree_model, iter2,
623                                     TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN, &s2,
624                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
625                                     -1);
626                 cmp = strcmp (s1, s2);
627                 g_free (s1);
628                 g_free (s2);
629                 
630                 return cmp ? cmp : t1 - t2;
631                 
632         case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_TO: 
633                 
634                 gtk_tree_model_get (tree_model, iter1,
635                                     TNY_MSG_HEADER_LIST_MODEL_TO_COLUMN, &s1,
636                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
637                                     -1);
638                 gtk_tree_model_get (tree_model, iter2,
639                                     TNY_MSG_HEADER_LIST_MODEL_TO_COLUMN, &s2,
640                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
641                                     -1);
642                 cmp = strcmp (s1, s2);
643                 g_free (s1);
644                 g_free (s2);
645                 
646                 return cmp ? cmp : t1 - t2;
647
648         case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_ATTACH:
649
650                 gtk_tree_model_get (tree_model, iter1, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
651                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
652                 gtk_tree_model_get (tree_model, iter2, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
653                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
654                 
655                 cmp = (val1 & TNY_MSG_HEADER_FLAG_ATTACHMENTS) - (val2 & TNY_MSG_HEADER_FLAG_ATTACHMENTS);
656
657                 return cmp ? cmp : t1 - t2;
658                 
659         case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_MSGTYPE:
660                 gtk_tree_model_get (tree_model, iter1, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
661                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
662                 gtk_tree_model_get (tree_model, iter2, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
663                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
664                 cmp =  (val1 & TNY_MSG_HEADER_FLAG_SEEN) - (val2 & TNY_MSG_HEADER_FLAG_SEEN);
665
666                 return cmp ? cmp : t1 - t2;
667
668         default:
669                 return &iter1 - &iter2; /* oughhhh  */
670         }
671 }
672
673
674 static void
675 refresh_folder (TnyMsgFolderIface *folder, gboolean cancelled,
676                                        gpointer user_data)
677 {
678         GtkTreeModel *oldsortable, *sortable;
679         ModestTnyHeaderTreeView *self =
680                 MODEST_TNY_HEADER_TREE_VIEW(user_data);
681         ModestTnyHeaderTreeViewPrivate *priv;
682
683         g_return_if_fail (self);
684         
685         if (cancelled)
686                 return;
687         
688         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self);
689
690         if (!folder)  /* when there is no folder */
691                 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), FALSE);
692         
693         else { /* it's a new one or a refresh */
694                 GSList *col;
695
696                 priv->headers = TNY_LIST_IFACE(tny_msg_header_list_model_new ());
697                         
698                 tny_msg_folder_iface_get_headers (folder, priv->headers, FALSE);
699                 tny_msg_header_list_model_set_folder (TNY_MSG_HEADER_LIST_MODEL(priv->headers),
700                                                       folder, TRUE); /* async */
701                 
702                 oldsortable = gtk_tree_view_get_model(GTK_TREE_VIEW (self));
703                 if (oldsortable && GTK_IS_TREE_MODEL_SORT(oldsortable)) {
704                         GtkTreeModel *oldmodel = gtk_tree_model_sort_get_model
705                                 (GTK_TREE_MODEL_SORT(oldsortable));
706                         if (oldmodel)
707                                 g_object_unref (G_OBJECT(oldmodel));
708                         g_object_unref (oldsortable);
709                 }
710         
711                 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(priv->headers));
712
713                 /* install our special sorting functions */
714                 col = priv->columns;
715                 while (col) {
716                         gint col_id = GPOINTER_TO_INT (col->data);
717                         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable), col_id,
718                                                          (GtkTreeIterCompareFunc)cmp_rows,
719                                                          GINT_TO_POINTER(col_id), NULL);
720                         col = col->next;
721                 }
722                 
723                 gtk_tree_view_set_model (GTK_TREE_VIEW (self), sortable);
724                 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), TRUE);
725                 /* no need to unref sortable */
726         } 
727 }
728
729
730 static void
731 refresh_folder_status_update (TnyMsgFolderIface *folder, const gchar *msg,
732                               gint status_id, gpointer user_data)
733 {
734         ModestTnyHeaderTreeView *self;
735         ModestTnyHeaderTreeViewPrivate *priv;
736         
737         self = MODEST_TNY_HEADER_TREE_VIEW (user_data);
738         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self);
739
740         g_signal_emit (G_OBJECT(self),
741                                signals[STATUS_UPDATE_SIGNAL], 0,
742                                msg, status_id);
743         if (msg) 
744                 g_timeout_add  (750,
745                                 (GSourceFunc)refresh_folder_finish_status_update,
746                                 self);
747         
748         priv->status_id = status_id;
749 }
750
751
752 static gboolean
753 refresh_folder_finish_status_update (gpointer user_data)
754 {
755         ModestTnyHeaderTreeView *self;
756         ModestTnyHeaderTreeViewPrivate *priv;
757         
758         self = MODEST_TNY_HEADER_TREE_VIEW (user_data);
759         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self);
760
761         if (priv->status_id == 0)
762                 return FALSE;
763         
764         refresh_folder_status_update (NULL, NULL, priv->status_id,
765                                       user_data);
766         priv->status_id = 0;
767
768         return FALSE;   
769 }
770
771
772 gboolean
773 modest_tny_header_tree_view_set_folder (ModestTnyHeaderTreeView *self,
774                                         TnyMsgFolderIface *folder)
775 {
776         ModestTnyHeaderTreeViewPrivate *priv;
777         
778         
779         g_return_val_if_fail (MODEST_IS_TNY_HEADER_TREE_VIEW (self), FALSE);
780
781         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self);
782         
783         if (!folder)  {/* when there is no folder */
784                 GtkTreeModel *model;
785                 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
786                 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), FALSE);
787                 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
788                 if (model)
789                         g_object_unref (model);
790         }
791         else { /* it's a new one or a refresh */
792                 tny_msg_folder_iface_refresh_async (folder,
793                                             refresh_folder,
794                                                     refresh_folder_status_update,
795                                                     self);
796         }
797         return TRUE;
798 }
799
800
801
802 static void
803 selection_changed (GtkTreeSelection *sel, gpointer user_data)
804 {
805         GtkTreeModel            *model;
806         TnyMsgHeaderIface       *header;
807         GtkTreeIter             iter;
808         ModestTnyHeaderTreeView *tree_view;
809
810         g_return_if_fail (sel);
811         g_return_if_fail (user_data);
812         
813         if (!gtk_tree_selection_get_selected (sel, &model, &iter))
814                 return; /* msg was _un_selected */
815         
816         tree_view = MODEST_TNY_HEADER_TREE_VIEW (user_data);
817         
818         gtk_tree_model_get (model, &iter,
819                             TNY_MSG_HEADER_LIST_MODEL_INSTANCE_COLUMN,
820                             &header, -1);
821         
822         if (header) {
823                 const TnyMsgIface *msg = NULL;
824                 const TnyMsgFolderIface *folder;
825                 
826                 folder = tny_msg_header_iface_get_folder (TNY_MSG_HEADER_IFACE(header));
827                 if (!folder)
828                         g_message ("cannot find folder");
829                 else {
830                         msg = tny_msg_folder_iface_get_message (TNY_MSG_FOLDER_IFACE(folder),
831                                                                 header);
832                         if (!msg) {
833                                 g_message ("cannot find msg");
834                                 gtk_tree_store_remove (GTK_TREE_STORE(model), 
835                                                        &iter); 
836                         }
837                 }
838                                         
839                 g_signal_emit (G_OBJECT(tree_view), signals[MESSAGE_SELECTED_SIGNAL], 0,
840                                msg);
841
842                 /* mark message as seen; _set_flags crashes, bug in tinymail? */
843                 //flags = tny_msg_header_iface_get_flags (TNY_MSG_HEADER_IFACE(header));
844                 //tny_msg_header_iface_set_flags (header, TNY_MSG_HEADER_FLAG_SEEN);
845         }
846 }
847
848 static void
849 column_clicked (GtkTreeViewColumn *col, gpointer user_data)
850 {
851         GtkTreeView *treeview;
852         gint id;
853
854         treeview = GTK_TREE_VIEW (user_data);
855         id = gtk_tree_view_column_get_sort_column_id (col);
856         
857         gtk_tree_view_set_search_column (treeview, id);
858 }