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