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