d08cdd7c3d4ff43a736e90d67442d22d45396268
[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 #include <glib/gi18n.h>
31 #include <tny-list.h>
32 #include <tny-simple-list.h>
33 #include <string.h>
34
35 #include <modest-header-view.h>
36 #include <modest-marshal.h>
37 #include <modest-text-utils.h>
38 #include <modest-icon-names.h>
39 #include <modest-icon-factory.h>
40
41 static void modest_header_view_class_init  (ModestHeaderViewClass *klass);
42 static void modest_header_view_init        (ModestHeaderView *obj);
43 static void modest_header_view_finalize    (GObject *obj);
44
45 static void on_selection_changed (GtkTreeSelection *sel, gpointer user_data);
46
47 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
48
49 enum {
50         HEADER_SELECTED_SIGNAL,
51         ITEM_NOT_FOUND_SIGNAL,
52         STATUS_UPDATE_SIGNAL,
53         LAST_SIGNAL
54 };
55
56
57 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
58 struct _ModestHeaderViewPrivate {
59         TnyFolder            *folder;
60         TnyList              *headers;
61         GMutex               *lock;
62         ModestHeaderViewStyle style;
63         gulong               sig1;
64
65 };
66 #define MODEST_HEADER_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
67                                                 MODEST_TYPE_HEADER_VIEW, \
68                                                 ModestHeaderViewPrivate))
69
70 /* globals */
71 static GObjectClass *parent_class = NULL;
72
73 /* uncomment the following if you have defined any signals */
74 static guint signals[LAST_SIGNAL] = {0};
75
76 GType
77 modest_header_view_get_type (void)
78 {
79         static GType my_type = 0;
80         if (!my_type) {
81                 static const GTypeInfo my_info = {
82                         sizeof(ModestHeaderViewClass),
83                         NULL,           /* base init */
84                         NULL,           /* base finalize */
85                         (GClassInitFunc) modest_header_view_class_init,
86                         NULL,           /* class finalize */
87                         NULL,           /* class data */
88                         sizeof(ModestHeaderView),
89                         1,              /* n_preallocs */
90                         (GInstanceInitFunc) modest_header_view_init,
91                         NULL
92                 };
93                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
94                                                   "ModestHeaderView",
95                                                   &my_info, 0);
96         }
97         return my_type;
98 }
99
100 static void
101 modest_header_view_class_init (ModestHeaderViewClass *klass)
102 {
103         GObjectClass *gobject_class;
104         gobject_class = (GObjectClass*) klass;
105
106         parent_class            = g_type_class_peek_parent (klass);
107         gobject_class->finalize = modest_header_view_finalize;
108         
109         g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
110         
111         signals[HEADER_SELECTED_SIGNAL] = 
112                 g_signal_new ("header_selected",
113                               G_TYPE_FROM_CLASS (gobject_class),
114                               G_SIGNAL_RUN_FIRST,
115                               G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
116                               NULL, NULL,
117                               g_cclosure_marshal_VOID__POINTER,
118                               G_TYPE_NONE, 1, G_TYPE_POINTER);
119
120         signals[ITEM_NOT_FOUND_SIGNAL] = 
121                 g_signal_new ("item_not_found",
122                               G_TYPE_FROM_CLASS (gobject_class),
123                               G_SIGNAL_RUN_FIRST,
124                               G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
125                               NULL, NULL,
126                               g_cclosure_marshal_VOID__INT,
127                               G_TYPE_NONE, 1, G_TYPE_INT);
128
129         signals[STATUS_UPDATE_SIGNAL] =
130                 g_signal_new ("status_update",
131                               G_TYPE_FROM_CLASS (gobject_class),
132                               G_SIGNAL_RUN_FIRST,
133                               G_STRUCT_OFFSET (ModestHeaderViewClass,status_update),
134                               NULL, NULL,
135                               modest_marshal_VOID__STRING_INT_INT,
136                               G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
137 }
138
139 static void
140 msgtype_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
141                    GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data)
142 {
143         TnyHeaderFlags flags;
144         GdkPixbuf *pixbuf = NULL;
145         
146         gtk_tree_model_get (tree_model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
147                             &flags, -1);
148
149         if (flags & TNY_HEADER_FLAG_DELETED)
150                 pixbuf = modest_icon_factory_get_icon (MODEST_HEADER_ICON_DELETED);
151         else if (flags & TNY_HEADER_FLAG_SEEN)
152                 pixbuf = modest_icon_factory_get_icon (MODEST_HEADER_ICON_READ);
153         else
154                 pixbuf = modest_icon_factory_get_icon (MODEST_HEADER_ICON_UNREAD);
155                 
156         g_object_set (G_OBJECT (renderer), "pixbuf", pixbuf, NULL);
157 }
158
159 static void
160 attach_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
161                   GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data)
162 {
163         TnyHeaderFlags flags;
164         GdkPixbuf *pixbuf = NULL;
165
166         gtk_tree_model_get (tree_model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
167                             &flags, -1);
168
169         if (flags & TNY_HEADER_FLAG_ATTACHMENTS)
170                 pixbuf = modest_icon_factory_get_small_icon (MODEST_HEADER_ICON_ATTACH);
171
172         g_object_set (G_OBJECT (renderer), "pixbuf", pixbuf, NULL);
173 }
174
175
176 static void
177 header_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
178                   GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer user_data)
179 {
180         TnyHeaderFlags flags;
181         
182         gtk_tree_model_get (tree_model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
183                             &flags, -1);
184
185         g_object_set (G_OBJECT(renderer),
186                       "weight", (flags & TNY_HEADER_FLAG_SEEN) ? 400: 800,
187                       "style",  (flags & TNY_HEADER_FLAG_DELETED) ?
188                                  PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL,
189                       NULL);    
190 }
191
192
193 static void
194 sender_receiver_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
195                             GtkTreeModel *tree_model,  GtkTreeIter *iter,  gboolean is_sender)
196 {
197         TnyHeaderFlags flags;
198         gchar *address;
199         gint sender_receiver_col;
200
201         if (is_sender)
202                 sender_receiver_col = TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN;
203         else
204                 sender_receiver_col = TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN;
205                 
206         gtk_tree_model_get (tree_model, iter,
207                             sender_receiver_col,  &address,
208                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
209                             -1);
210         
211         g_object_set (G_OBJECT(renderer),
212                       "text",
213                       modest_text_utils_get_display_address (address),
214                       "weight",
215                       (flags & TNY_HEADER_FLAG_SEEN) ? 400 : 800,
216                       "style",
217                       (flags & TNY_HEADER_FLAG_DELETED)?PANGO_STYLE_ITALIC:PANGO_STYLE_NORMAL,
218                       NULL);
219
220         g_free (address);       
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         TnyHeaderFlags flags;
229         gchar *from, *subject;
230         gchar *header;
231         time_t date;
232                 
233         gtk_tree_model_get (tree_model, iter,
234                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
235                             TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,  &from,
236                             TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &subject,
237                             TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN, &date,   
238                             -1);
239         rendobj = G_OBJECT(renderer);           
240
241         header = g_strdup_printf ("%s %s\n%s",
242                                   modest_text_utils_get_display_address (from),
243                                   modest_text_utils_get_display_date (date),
244                                   subject);
245         
246         g_object_set (G_OBJECT(renderer),
247                       "text",  header,
248                       "weight", (flags & TNY_HEADER_FLAG_SEEN) ? 400: 800,
249                       "style",  (flags & TNY_HEADER_FLAG_DELETED) ?
250                                  PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL,
251                       NULL);    
252         g_free (header);
253         g_free (from);
254         g_free (subject);
255 }
256
257
258 static GtkTreeViewColumn*
259 get_new_column (const gchar *name, GtkCellRenderer *renderer,
260                 gboolean resizable, gint sort_col_id, gboolean show_as_text,
261                 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
262 {
263         GtkTreeViewColumn *column;
264
265         column =  gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
266         gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
267
268         gtk_tree_view_column_set_resizable (column, resizable);
269         if (resizable)
270                 gtk_tree_view_column_set_min_width (column, 100);
271         
272         if (show_as_text) 
273                 gtk_tree_view_column_add_attribute (column, renderer, "text",
274                                                     sort_col_id);
275         if (sort_col_id >= 0)
276                 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
277
278         gtk_tree_view_column_set_sort_indicator (column, FALSE);
279         gtk_tree_view_column_set_reorderable (column, TRUE);
280
281         if (cell_data_func)
282                 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
283                                                         user_data, NULL);
284         return column;
285 }
286
287
288
289
290 static void
291 remove_all_columns (ModestHeaderView *obj)
292 {
293         GList *columns, *cursor;
294
295         columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
296
297         for (cursor = columns; cursor; cursor = cursor->next)
298                 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
299                                              GTK_TREE_VIEW_COLUMN(cursor->data));
300         g_list_free (columns);  
301 }
302
303
304
305 gboolean
306 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns)
307 {
308         GtkTreeViewColumn *column=NULL;
309         GtkCellRenderer *renderer_msgtype,
310                 *renderer_header,
311                 *renderer_attach;
312
313         ModestHeaderViewPrivate *priv;
314         const GList *cursor;
315         
316         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); 
317
318         /* FIXME: check whether these renderers need to be freed */
319         renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
320         renderer_attach  = gtk_cell_renderer_pixbuf_new ();
321         renderer_header  = gtk_cell_renderer_text_new (); 
322         
323         remove_all_columns (self);
324         
325         for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
326                 ModestHeaderViewColumn col =
327                         (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
328                 
329                 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
330                         g_printerr ("modest: invalid column %d in column list\n", col);
331                         continue;
332                 }
333                 
334                 switch (col) {
335                         
336                 case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
337                         column = get_new_column (_("M"), renderer_msgtype, FALSE,
338                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
339                                                  FALSE, (GtkTreeCellDataFunc)msgtype_cell_data,
340                                                  NULL);
341                         gtk_tree_view_column_set_fixed_width (column, 32);
342                         break;
343
344                 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
345                         column = get_new_column (_("A"), renderer_attach, FALSE,
346                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
347                                                  FALSE, (GtkTreeCellDataFunc)attach_cell_data,
348                                                  NULL);
349                         gtk_tree_view_column_set_fixed_width (column, 32);
350                         break;
351                 
352                 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
353                         column = get_new_column (_("Received"), renderer_header, TRUE,
354                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_COLUMN,
355                                                  TRUE, (GtkTreeCellDataFunc)header_cell_data,
356                                                  NULL);
357                         break;
358                         
359                 case MODEST_HEADER_VIEW_COLUMN_FROM:
360                         column = get_new_column (_("From"), renderer_header, TRUE,
361                                                  TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
362                                                  TRUE, (GtkTreeCellDataFunc)sender_receiver_cell_data,
363                                                  GINT_TO_POINTER(TRUE));
364                         break;
365
366                 case MODEST_HEADER_VIEW_COLUMN_TO:
367                         column = get_new_column (_("To"), renderer_header, TRUE,
368                                                  TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
369                                                  TRUE, (GtkTreeCellDataFunc)sender_receiver_cell_data,
370                                                  GINT_TO_POINTER(FALSE));
371                         break;
372                         
373                 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER:
374                         column = get_new_column (_("Header"), renderer_header, TRUE,
375                                                  TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
376                                                  TRUE, (GtkTreeCellDataFunc)compact_header_cell_data,
377                                                  NULL);
378                         break;
379                         
380                 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
381                         column = get_new_column (_("Subject"), renderer_header, TRUE,
382                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
383                                                  TRUE, (GtkTreeCellDataFunc)header_cell_data,
384                                                  NULL);
385                         break;
386                         
387                         
388                 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
389                         column = get_new_column (_("Sent"), renderer_header, TRUE,
390                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_COLUMN,
391                                                  TRUE, (GtkTreeCellDataFunc)header_cell_data,
392                                                  NULL);
393                         break;
394
395                 default:
396                         g_assert_not_reached ();
397                 }
398
399                 /* we keep the column id around */
400                 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
401                                    GINT_TO_POINTER(col));
402                 
403                 /* we need this ptr when sorting the rows */
404                 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
405                                    self);
406                 
407                 gtk_tree_view_column_set_visible   (column, TRUE);
408                 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);              
409         }       
410         return TRUE;
411 }
412
413
414
415 static void
416 modest_header_view_init (ModestHeaderView *obj)
417 {
418         ModestHeaderViewPrivate *priv;
419         priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj); 
420
421         priv->lock = g_mutex_new ();
422         priv->sig1 = 0;
423 }
424
425 static void
426 modest_header_view_finalize (GObject *obj)
427 {
428         ModestHeaderView        *self;
429         ModestHeaderViewPrivate *priv;
430         GtkTreeSelection        *sel;
431         
432         self = MODEST_HEADER_VIEW(obj);
433         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
434
435         if (priv->headers)      
436                 g_object_unref (G_OBJECT(priv->headers));
437
438         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
439
440         if (sel && priv->sig1 != 0) {
441                 g_signal_handler_disconnect (G_OBJECT(sel), priv->sig1);
442                 priv->sig1 = 0;
443         }
444                 
445         if (priv->lock) {
446                 g_mutex_free (priv->lock);
447                 priv->lock = NULL;
448         }
449
450         priv->headers  = NULL;
451         priv->folder   = NULL;
452         
453         G_OBJECT_CLASS(parent_class)->finalize (obj);
454 }
455         
456 GtkWidget*
457 modest_header_view_new (TnyFolder *folder, const GList *columns,
458                         ModestHeaderViewStyle style)
459 {
460         GObject *obj;
461         GtkTreeSelection *sel;
462         ModestHeaderView *self;
463         ModestHeaderViewPrivate *priv;
464         
465         obj  = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
466         self = MODEST_HEADER_VIEW(obj);
467         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
468         
469         if (!modest_header_view_set_folder (self, NULL)) {
470                 g_warning ("could not set the folder");
471                 g_object_unref (obj);
472                 return NULL;
473         }
474         
475         modest_header_view_set_style   (self, style);
476         modest_header_view_set_columns (self, columns);
477         
478         /* all cols */
479         gtk_tree_view_set_headers_visible   (GTK_TREE_VIEW(obj), TRUE);
480         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(obj), TRUE);
481         gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
482         
483         gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
484                                       TRUE); /* alternating row colors */
485
486         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
487         
488         priv->sig1 = g_signal_connect (sel, "changed",
489                                        G_CALLBACK(on_selection_changed), self);
490         return GTK_WIDGET(self);
491 }
492
493
494 TnyList * 
495 modest_header_view_get_selected_headers (ModestHeaderView *self)
496 {
497         GtkTreeSelection *sel;
498         ModestHeaderViewPrivate *priv;
499         TnyList *header_list = NULL;
500         TnyHeader *header;
501         GList *list, *tmp = NULL;
502         GtkTreeModel *tree_model = NULL;
503         GtkTreeIter iter;
504         
505         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
506
507         /* Get selected rows */
508         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
509         list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
510
511         if (list) {
512                 header_list = tny_simple_list_new();
513
514                 list = g_list_reverse (list);
515                 tmp = list;
516                 while (tmp) {                   
517                         /* get header from selection */
518                         gtk_tree_model_get_iter (tree_model,
519                                                  &iter,
520                                                  (GtkTreePath *) (tmp->data));
521                                                                           
522                         gtk_tree_model_get (tree_model, &iter,
523                                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
524                                             &header, -1);
525
526                         /* Prepend to list */
527                         tny_list_prepend (header_list, G_OBJECT (header));
528                         tmp = g_list_next (tmp);
529                 }
530                 /* Clean up*/
531                 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
532                 g_list_free (list);
533         }
534         return header_list;
535 }
536
537 void 
538 modest_header_view_select_next (ModestHeaderView *self)
539 {
540         GtkTreeSelection *sel;
541         GtkTreeIter iter;
542         GtkTreeModel *model;
543
544         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
545         if (sel) {
546                 gtk_tree_selection_get_selected (sel, &model, &iter);
547                 gtk_tree_model_iter_next (model, &iter);
548                 gtk_tree_selection_select_iter (sel, &iter);
549         }
550 }
551
552 GList*
553 modest_header_view_get_columns (ModestHeaderView *self)
554 {
555         g_return_val_if_fail (self, FALSE);
556         return gtk_tree_view_get_columns (GTK_TREE_VIEW(self)); 
557 }
558
559 gboolean
560 modest_header_view_set_style (ModestHeaderView *self,
561                               ModestHeaderViewStyle style)
562 {
563         g_return_val_if_fail (self, FALSE);
564         g_return_val_if_fail (style < MODEST_HEADER_VIEW_STYLE_NUM, FALSE);
565         
566         MODEST_HEADER_VIEW_GET_PRIVATE(self)->style = style;
567         
568         return TRUE;
569 }
570
571 ModestHeaderViewStyle
572 modest_header_view_get_style (ModestHeaderView *self)
573 {
574         g_return_val_if_fail (self, FALSE);
575
576         return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
577 }
578
579
580 static gint
581 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
582           gpointer user_data)
583 {
584         gint col_id;
585         gint t1, t2;
586         gint val1, val2;
587         gchar *s1, *s2;
588         gint cmp;
589         
590         static int counter = 0;
591         col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_COLUMN));
592         
593         if (!(++counter % 100)) {
594                 GObject *header_view = g_object_get_data(G_OBJECT(user_data),
595                                                          MODEST_HEADER_VIEW_PTR);
596                 g_signal_emit (header_view,
597                                signals[STATUS_UPDATE_SIGNAL],
598                                0, _("Sorting..."), 0, 0);
599         }
600         
601         switch (col_id) {
602
603                 /* first one, we decide based on the time */
604         case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER:
605         case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
606                 gtk_tree_model_get (tree_model, iter1,
607                                     TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
608                                     &t1,-1);
609                 gtk_tree_model_get (tree_model, iter2,
610                                     TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
611                                     &t2,-1);
612                 return t1 - t2;
613                 
614         case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
615                 gtk_tree_model_get (tree_model, iter1,
616                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
617                                     &t1,-1);
618                 gtk_tree_model_get (tree_model, iter2,
619                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
620                                     &t2,-1);
621                 return t1 - t2;
622
623                 
624                 /* next ones, we try the search criteria first, if they're the same, then we use 'sent date' */
625         case MODEST_HEADER_VIEW_COLUMN_SUBJECT: {
626
627                 gtk_tree_model_get (tree_model, iter1,
628                                     TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s1,
629                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
630                                     -1);
631                 gtk_tree_model_get (tree_model, iter2,
632                                     TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s2,
633                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
634                                     -1);
635
636                 /* the prefix ('Re:', 'Fwd:' etc.) we ignore */ 
637                 cmp = modest_text_utils_utf8_strcmp (s1 + modest_text_utils_get_subject_prefix_len(s1),
638                                                      s2 + modest_text_utils_get_subject_prefix_len(s2),
639                                                      TRUE);
640
641                 g_free (s1);
642                 g_free (s2);
643                 
644                 return cmp ? cmp : t1 - t2;
645         }
646                 
647         case MODEST_HEADER_VIEW_COLUMN_FROM:
648                 
649                 gtk_tree_model_get (tree_model, iter1,
650                                     TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN, &s1,
651                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
652                                     -1);
653                 gtk_tree_model_get (tree_model, iter2,
654                                     TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN, &s2,
655                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
656                                     -1);
657                 cmp = modest_text_utils_utf8_strcmp (s1, s2, TRUE);
658                 g_free (s1);
659                 g_free (s2);
660                 
661                 return cmp ? cmp : t1 - t2;
662                 
663         case MODEST_HEADER_VIEW_COLUMN_TO: 
664                 
665                 gtk_tree_model_get (tree_model, iter1,
666                                     TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN, &s1,
667                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
668                                     -1);
669                 gtk_tree_model_get (tree_model, iter2,
670                                     TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN, &s2,
671                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
672                                     -1);
673                 cmp = modest_text_utils_utf8_strcmp (s1, s2, TRUE);
674                 g_free (s1);
675                 g_free (s2);
676                 
677                 return cmp ? cmp : t1 - t2;
678
679         case MODEST_HEADER_VIEW_COLUMN_ATTACH:
680
681                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
682                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
683                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
684                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
685                 
686                 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
687                         (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
688
689                 return cmp ? cmp : t1 - t2;
690                 
691         case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
692                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
693                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
694                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
695                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
696                 cmp =  (val1 & TNY_HEADER_FLAG_SEEN) - (val2 & TNY_HEADER_FLAG_SEEN);
697
698                 return cmp ? cmp : t1 - t2;
699
700         default:
701                 return &iter1 - &iter2; /* oughhhh  */
702         }
703 }
704
705
706 static void
707 on_refresh_folder (TnyFolder *folder, gboolean cancelled, GError **err,
708                    gpointer user_data)
709 {
710         GtkTreeModel *sortable; 
711         ModestHeaderView *self;
712         ModestHeaderViewPrivate *priv;
713         GError *error = NULL;
714
715         if (cancelled)
716                 return;
717         
718         self = MODEST_HEADER_VIEW(user_data);
719         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
720
721         priv->folder = folder;
722         
723         if (!folder)  /* when there is no folder */
724                 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), FALSE);
725         else { /* it's a new one or a refresh */
726                 GList *cols, *cursor;
727
728                 if (priv->headers)
729                         g_object_unref (priv->headers);
730
731                 priv->headers = TNY_LIST(tny_gtk_header_list_model_new ());
732                 tny_folder_get_headers (folder, priv->headers, FALSE, &error); /* FIXME */
733                 if (error) {
734                         g_signal_emit (G_OBJECT(self), signals[ITEM_NOT_FOUND_SIGNAL],
735                                        0, MODEST_ITEM_TYPE_MESSAGE);
736                         g_print (error->message);
737                         g_error_free (error);
738                         return;
739                 }
740
741                 tny_gtk_header_list_model_set_folder
742                         (TNY_GTK_HEADER_LIST_MODEL(priv->headers),folder, TRUE); /*async*/
743
744                 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(priv->headers));
745
746                 /* install our special sorting functions */
747                 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
748                 while (cursor) {
749                         gint col_id = GPOINTER_TO_INT (g_object_get_data(G_OBJECT(cursor->data),
750                                                                          MODEST_HEADER_VIEW_COLUMN));
751                         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
752                                                          col_id,
753                                                          (GtkTreeIterCompareFunc)cmp_rows,
754                                                          cursor->data, NULL);
755                         cursor = g_list_next(cursor);
756                 }
757                 g_list_free (cols);
758                 
759                 gtk_tree_view_set_model (GTK_TREE_VIEW (self), sortable);
760                 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self),TRUE);
761                 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), TRUE);
762         }
763 }
764
765
766 static void
767 on_refresh_folder_status_update (TnyFolder *folder, const gchar *msg,
768                                  gint num, gint total,  gpointer user_data)
769 {
770         ModestHeaderView *self;
771         ModestHeaderViewPrivate *priv;
772
773         self = MODEST_HEADER_VIEW(user_data);
774         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
775
776         g_signal_emit (G_OBJECT(self), signals[STATUS_UPDATE_SIGNAL],
777                        0, msg, num, total);
778 }
779
780
781 TnyFolder*
782 modest_header_view_get_folder (ModestHeaderView *self)
783 {
784         ModestHeaderViewPrivate *priv;
785         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
786
787         return priv->folder;
788 }
789
790
791 gboolean
792 modest_header_view_set_folder (ModestHeaderView *self, TnyFolder *folder)
793 {
794         ModestHeaderViewPrivate *priv;
795         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
796
797         priv->folder = folder;
798         
799         if (!folder)  {/* when there is no folder */
800                 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), FALSE);
801                 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
802         } else { /* it's a new one or a refresh */
803                 tny_folder_refresh_async (folder,
804                                           on_refresh_folder,
805                                           on_refresh_folder_status_update,
806                                           self);
807         }
808         
809         /* no message selected */
810         g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0,
811                        NULL);
812         return TRUE;
813 }
814
815 static void
816 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
817 {
818         GtkTreeModel *model;
819         TnyHeader *header;
820         GtkTreeIter iter;
821         ModestHeaderView *self;
822         ModestHeaderViewPrivate *priv;
823         
824         g_return_if_fail (sel);
825         g_return_if_fail (user_data);
826         
827         self = MODEST_HEADER_VIEW (user_data);
828         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);    
829         
830         if (!gtk_tree_selection_get_selected (sel, &model, &iter))
831                 return; /* msg was _un_selected */
832         
833         gtk_tree_model_get (model, &iter,
834                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
835                             &header, -1);
836
837         /* Emit signal */
838         g_signal_emit (G_OBJECT(self), 
839                        signals[HEADER_SELECTED_SIGNAL], 
840                        0, header);
841 }
842
843
844 /* PROTECTED method. It's useful when we want to force a given
845    selection to reload a msg. For example if we have selected a header
846    in offline mode, when Modest become online, we want to reload the
847    message automatically without an user click over the header */
848 void 
849 _modest_header_view_change_selection (GtkTreeSelection *selection,
850                                       gpointer user_data)
851 {
852         g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
853         g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
854
855         on_selection_changed (selection, user_data);
856 }