* modest-header-view.c:
[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 enum {
48         MESSAGE_SELECTED_SIGNAL,
49         ITEM_NOT_FOUND_SIGNAL,
50         STATUS_UPDATE_SIGNAL,
51         LAST_SIGNAL
52 };
53
54
55 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
56 struct _ModestHeaderViewPrivate {
57
58         TnyFolder *tny_folder;
59         TnyList      *headers;
60         GSList            *columns;
61         GMutex            *lock;
62         ModestHeaderViewStyle style;
63
64         gulong            sig1;
65
66 };
67 #define MODEST_HEADER_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
68                                                 MODEST_TYPE_HEADER_VIEW, \
69                                                 ModestHeaderViewPrivate))
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[MESSAGE_SELECTED_SIGNAL] = 
112                 g_signal_new ("message_selected",
113                               G_TYPE_FROM_CLASS (gobject_class),
114                               G_SIGNAL_RUN_FIRST,
115                               G_STRUCT_OFFSET (ModestHeaderViewClass,message_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,message_selected),
134                               NULL, NULL,
135                               modest_marshal_VOID__STRING_INT,
136                               G_TYPE_NONE, 2, G_TYPE_STRING, 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_small_icon (MODEST_HEADER_ICON_DELETED);
151         else if (flags & TNY_HEADER_FLAG_SEEN)
152                 pixbuf = modest_icon_factory_get_small_icon (MODEST_HEADER_ICON_READ);
153         else
154                 pixbuf = modest_icon_factory_get_small_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
194 /* try to make a shorter display address; changes its argument in-place */
195 static gchar*
196 display_address (gchar *address)
197 {
198         gchar *cursor;
199
200         if (!address)
201                 return NULL;
202         
203         /* simplistic --> remove <email@address> from display name */
204         cursor = g_strstr_len (address, strlen(address), "<");
205         if (cursor) 
206                 cursor[0]='\0';
207
208         /* simplistic --> remove (bla bla) from display name */
209         cursor = g_strstr_len (address, strlen(address), "(");
210         if (cursor) 
211                 cursor[0]='\0';
212         
213         /* FIXME */
214         if (!g_utf8_validate (address, -1, NULL)) 
215                 g_printerr ("modest: invalid: '%s'", address);
216
217         return address;
218 }
219
220
221
222 static void
223 sender_receiver_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
224                             GtkTreeModel *tree_model,  GtkTreeIter *iter,  gboolean is_sender)
225 {
226         TnyHeaderFlags flags;
227         gchar *address;
228         gint sender_receiver_col;
229
230         if (is_sender)
231                 sender_receiver_col = TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN;
232         else
233                 sender_receiver_col = TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN;
234                 
235         gtk_tree_model_get (tree_model, iter,
236                             sender_receiver_col,  &address,
237                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
238                             -1);
239         
240         g_object_set (G_OBJECT(renderer),
241                       "text",
242                       display_address (address),
243                       "weight",
244                       (flags & TNY_HEADER_FLAG_SEEN) ? 400 : 800,
245                       "style",
246                       (flags & TNY_HEADER_FLAG_DELETED)?PANGO_STYLE_ITALIC:PANGO_STYLE_NORMAL,
247                       NULL);
248
249         g_free (address);       
250 }
251
252
253
254
255 /* not reentrant/thread-safe */
256 const gchar*
257 display_date (time_t date)
258 {
259         struct tm date_tm, now_tm; 
260         time_t now;
261
262         const gint buf_size = 64; 
263         static gchar date_buf[64]; /* buf_size is not ... */
264         static gchar now_buf[64];  /* ...const enough... */
265         
266         now = time (NULL);
267         
268         localtime_r(&now, &now_tm);
269         localtime_r(&date, &date_tm);
270
271         /* get today's date */
272         modest_text_utils_strftime (date_buf, buf_size, "%x", &date_tm);
273         modest_text_utils_strftime (now_buf,  buf_size, "%x",  &now_tm);
274         /* today */
275
276         /* if this is today, get the time instead of the date */
277         if (strcmp (date_buf, now_buf) == 0)
278                 strftime (date_buf, buf_size, _("%X"), &date_tm); 
279                 
280         return date_buf;
281 }
282
283
284 static void
285 compact_header_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
286                            GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer user_data)
287 {
288         GObject *rendobj;
289         TnyHeaderFlags flags;
290         gchar *from, *subject;
291         gchar *header;
292         time_t date;
293                 
294         gtk_tree_model_get (tree_model, iter,
295                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
296                             TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,  &from,
297                             TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &subject,
298                             TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN, &date,   
299                             -1);
300         rendobj = G_OBJECT(renderer);           
301
302         header = g_strdup_printf ("%s %s\n%s",
303                                   display_address (from),
304                                   display_date(date),
305                                   subject);
306         
307         g_object_set (G_OBJECT(renderer),
308                       "text",  header,
309                       "weight", (flags & TNY_HEADER_FLAG_SEEN) ? 400: 800,
310                       "style",  (flags & TNY_HEADER_FLAG_DELETED) ?
311                                  PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL,
312                       NULL);    
313         g_free (header);
314         g_free (from);
315         g_free (subject);
316 }
317
318
319 static GtkTreeViewColumn*
320 get_new_column (const gchar *name, GtkCellRenderer *renderer,
321                 gboolean resizable, gint sort_col_id, gboolean show_as_text,
322                 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
323 {
324         GtkTreeViewColumn *column;
325
326         column =  gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
327         gtk_tree_view_column_set_resizable (column, resizable);
328         gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
329         
330         if (show_as_text) 
331                 gtk_tree_view_column_add_attribute (column, renderer, "text",
332                                                     sort_col_id);
333         if (sort_col_id >= 0)
334                 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
335
336         gtk_tree_view_column_set_sort_indicator (column, FALSE);
337         gtk_tree_view_column_set_reorderable (column, TRUE);
338
339         if (cell_data_func)
340                 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
341                                                         user_data, NULL);
342
343         return column;
344 }
345
346
347
348
349 static void
350 remove_all_columns (ModestHeaderView *obj)
351 {
352         GList *columns, *cursor;
353
354         columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
355
356         for (cursor = columns; cursor; cursor = cursor->next)
357                 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
358                                              GTK_TREE_VIEW_COLUMN(cursor->data));
359         g_list_free (columns);  
360 }
361
362
363
364
365 static void
366 init_columns (ModestHeaderView *obj)
367 {
368         GtkTreeViewColumn *column=NULL;
369         GtkCellRenderer *renderer_msgtype,
370                 *renderer_header,
371                 *renderer_attach;
372
373         ModestHeaderViewPrivate *priv;
374         GSList *cursor;
375         
376         priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj); 
377                         
378         renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
379         renderer_attach  = gtk_cell_renderer_pixbuf_new ();
380         renderer_header = gtk_cell_renderer_text_new (); 
381         
382         remove_all_columns (obj);
383         
384         for (cursor = priv->columns; cursor; cursor = cursor->next) {
385                 ModestHeaderViewColumn col =
386                         (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
387                 
388                 switch (col) {
389                         
390                 case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
391
392                         column = get_new_column (_("M"), renderer_msgtype, FALSE,
393                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
394                                                  FALSE, (GtkTreeCellDataFunc)msgtype_cell_data,
395                                                  NULL);
396                         break;
397
398                 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
399
400                         column = get_new_column (_("A"), renderer_attach, FALSE,
401                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
402                                                  FALSE, (GtkTreeCellDataFunc)attach_cell_data,
403                                                  NULL);
404                         break;
405                         
406                 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
407                         column = get_new_column (_("Received"), renderer_header, TRUE,
408                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_COLUMN,
409                                                  TRUE, (GtkTreeCellDataFunc)header_cell_data,
410                                                  NULL);
411                         break;
412                         
413                 case MODEST_HEADER_VIEW_COLUMN_FROM:
414                         column = get_new_column (_("From"), renderer_header, TRUE,
415                                                  TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
416                                                  TRUE, (GtkTreeCellDataFunc)sender_receiver_cell_data,
417                                                  GINT_TO_POINTER(TRUE));
418                         break;
419
420                 case MODEST_HEADER_VIEW_COLUMN_TO:
421                         column = get_new_column (_("To"), renderer_header, TRUE,
422                                                  TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
423                                                  TRUE, (GtkTreeCellDataFunc)sender_receiver_cell_data,
424                                                  GINT_TO_POINTER(FALSE));
425                         break;
426                         
427                 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER:
428                         column = get_new_column (_("Header"), renderer_header, TRUE,
429                                                  TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
430                                                  TRUE, (GtkTreeCellDataFunc)compact_header_cell_data,
431                                                  NULL);
432                         break;
433                         
434                 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
435                         column = get_new_column (_("Subject"), renderer_header, TRUE,
436                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
437                                                  TRUE, (GtkTreeCellDataFunc)header_cell_data,
438                                                  NULL);
439                         break;
440                         
441                         
442                 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
443                         column = get_new_column (_("Sent"), renderer_header, TRUE,
444                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_COLUMN,
445                                                  TRUE, (GtkTreeCellDataFunc)header_cell_data,
446                                                  NULL);
447                         break;
448
449                 default:
450                         g_assert_not_reached ();
451                 }
452
453                 gtk_tree_view_column_set_visible (column, TRUE);
454                 gtk_tree_view_column_set_min_width (column, 36);
455                 gtk_tree_view_column_set_resizable (column, TRUE);
456                 
457                 gtk_tree_view_append_column (GTK_TREE_VIEW(obj), column);               
458         }       
459 }
460
461
462
463 static void
464 modest_header_view_init (ModestHeaderView *obj)
465 {
466         ModestHeaderViewPrivate *priv;
467         priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj); 
468
469         priv->lock = g_mutex_new ();
470         priv->sig1 = 0;
471 }
472
473 static void
474 modest_header_view_finalize (GObject *obj)
475 {
476         ModestHeaderView        *self;
477         ModestHeaderViewPrivate *priv;
478         GtkTreeSelection        *sel;
479         
480         self = MODEST_HEADER_VIEW(obj);
481         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
482
483         if (priv->headers)      
484                 g_object_unref (G_OBJECT(priv->headers));
485
486         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
487
488         if (sel && priv->sig1 != 0) {
489                 g_signal_handler_disconnect (G_OBJECT(sel), priv->sig1);
490                 priv->sig1 = 0;
491         }
492                 
493         if (priv->lock) {
494                 g_mutex_free (priv->lock);
495                 priv->lock = NULL;
496         }
497
498         priv->headers       = NULL;
499         priv->tny_folder    = NULL;
500         
501         G_OBJECT_CLASS(parent_class)->finalize (obj);
502 }
503
504 GtkWidget*
505 modest_header_view_new (TnyFolder *folder,
506                         GSList *columns,
507                         ModestHeaderViewStyle style)
508 {
509         GObject *obj;
510         GtkTreeSelection *sel;
511         ModestHeaderView *self;
512         ModestHeaderViewPrivate *priv;
513         
514         obj  = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
515         self = MODEST_HEADER_VIEW(obj);
516         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
517         
518         if (!modest_header_view_set_folder (self, NULL)) {
519                 g_warning ("could not set the folder");
520                 g_object_unref (obj);
521                 return NULL;
522         }
523         
524         modest_header_view_set_style   (self, style);
525         modest_header_view_set_columns (self, columns);
526         
527         /* all cols */
528         gtk_tree_view_set_headers_visible   (GTK_TREE_VIEW(obj), TRUE);
529         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(obj), TRUE);
530         gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
531         
532         gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
533                                       TRUE); /* alternating row colors */
534
535         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
536         
537         priv->sig1 = g_signal_connect (sel, "changed",
538                                        G_CALLBACK(on_selection_changed), self);
539         
540
541         return GTK_WIDGET(self);
542 }
543
544 gboolean
545 modest_header_view_set_columns (ModestHeaderView *self, GSList *columns)
546 {
547         ModestHeaderViewPrivate *priv;
548         GSList *cursor;
549
550         g_return_val_if_fail (self, FALSE);
551         
552         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
553
554         g_slist_free (priv->columns);
555         
556         for (cursor = columns; cursor; cursor = cursor->next) {
557                 ModestHeaderViewColumn col = 
558                         (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
559                 if (col >= MODEST_HEADER_VIEW_COLUMN_NUM)
560                         g_printerr ("modest: invalid column %d in column list\n", col);
561                 else
562                         priv->columns = g_slist_append (priv->columns, cursor->data);
563         }
564
565         init_columns (self); /* redraw them */
566         return TRUE;
567 }
568
569
570 TnyList * 
571 modest_header_view_get_selected_headers (ModestHeaderView *self)
572 {
573         GtkTreeSelection *sel;
574         ModestHeaderViewPrivate *priv;
575         TnyList *header_list = NULL;
576         TnyHeader *header;
577         GList *list, *tmp = NULL;
578         GtkTreeModel *tree_model = NULL;
579         GtkTreeIter iter;
580         
581         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
582
583         /* Get selected rows */
584         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
585         list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
586
587         if (list) {
588                 header_list = tny_simple_list_new();
589
590                 list = g_list_reverse (list);
591                 tmp = list;
592                 while (tmp) {                   
593                         /* Get Header from selection */
594                         gtk_tree_model_get_iter (tree_model,
595                                                  &iter,
596                                                  (GtkTreePath *) (tmp->data));
597                                                                           
598                         gtk_tree_model_get (tree_model, &iter,
599                                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
600                                             &header, -1);
601
602                         /* Prepend to list */
603                         tny_list_prepend (header_list, G_OBJECT (header));
604                         tmp = g_list_next (tmp);
605                 }
606                 /* Clean up*/
607                 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
608                 g_list_free (list);
609         }
610         return header_list;
611 }
612
613 const GSList*
614 modest_header_view_get_columns (ModestHeaderView *self)
615 {
616         ModestHeaderViewPrivate *priv;
617
618         g_return_val_if_fail (self, FALSE);
619         
620         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
621         return priv->columns;
622 }
623
624
625
626
627 gboolean
628 modest_header_view_set_style (ModestHeaderView *self,
629                                        ModestHeaderViewStyle style)
630 {
631         g_return_val_if_fail (self, FALSE);
632         g_return_val_if_fail (style < MODEST_HEADER_VIEW_STYLE_NUM, FALSE);
633         
634         MODEST_HEADER_VIEW_GET_PRIVATE(self)->style = style;
635         
636         return TRUE;
637 }
638
639 ModestHeaderViewStyle
640 modest_header_view_get_style (ModestHeaderView *self)
641 {
642         g_return_val_if_fail (self, FALSE);
643
644         return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
645 }
646
647
648
649 /* get the length of any prefix that should be ignored for sorting */
650 static inline int 
651 get_prefix_len (const gchar *sub)
652 {
653         gint i;
654         static const gchar* prefix[] = {
655                 "Re:", "RE:", "Fwd:", "FWD:", "FW:", NULL
656         };
657                 
658         if (!sub || (sub[0] != 'R' && sub[0] != 'F')) /* optimization */
659                 return 0;
660
661         i = 0;
662         
663         while (prefix[i]) {
664                 if (g_str_has_prefix(sub, prefix[i])) {
665                         int prefix_len = strlen(prefix[i]); 
666                         if (sub[prefix_len] == ' ')
667                                 ++prefix_len; /* ignore space after prefix as well */
668                         return prefix_len; 
669                 }
670                 ++i;
671         }
672         return 0;
673 }
674
675
676
677 /* a strcmp that is case insensitive and NULL-safe */
678 static gint
679 cmp_insensitive_str (const gchar* s1, const gchar *s2)
680 {
681         gint result = 0;
682         gchar *n1, *n2;
683
684         /* work even when s1 and/or s2 == NULL */
685         if (s1 == s2)
686                 return 0;
687         if (!s1)
688                 return -1;
689         if (!s2)
690                 return 1;
691
692         n1 = g_utf8_collate_key (s1, -1);
693         n2 = g_utf8_collate_key (s2, -1);
694         
695         result = strcmp (n1, n2);
696
697         g_free (n1);
698         g_free (n2);
699         
700         return result;
701 }
702
703
704 static gint
705 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
706           gpointer user_data)
707 {
708         gint col_id = GPOINTER_TO_INT (user_data);
709         gint t1, t2;
710         gint val1, val2;
711         gchar *s1, *s2;
712         gint cmp;
713         
714         g_return_val_if_fail (GTK_IS_TREE_MODEL(tree_model), -1);
715         
716         switch (col_id) {
717
718                 /* first one, we decide based on the time */
719         case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER:
720         case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
721                 gtk_tree_model_get (tree_model, iter1,
722                                     TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
723                                     &t1,-1);
724                 gtk_tree_model_get (tree_model, iter2,
725                                     TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
726                                     &t2,-1);
727                 return t1 - t2;
728                 
729         case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
730                 gtk_tree_model_get (tree_model, iter1,
731                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
732                                     &t1,-1);
733                 gtk_tree_model_get (tree_model, iter2,
734                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
735                                     &t2,-1);
736                 return t1 - t2;
737
738                 
739                 /* next ones, we try the search criteria first, if they're the same, then we use 'sent date' */
740         case MODEST_HEADER_VIEW_COLUMN_SUBJECT: {
741
742                 gtk_tree_model_get (tree_model, iter1,
743                                     TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s1,
744                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
745                                     -1);
746                 gtk_tree_model_get (tree_model, iter2,
747                                     TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s2,
748                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
749                                     -1);
750
751                 /* the prefix ('Re:', 'Fwd:' etc.) we ignore */ 
752                 cmp = cmp_insensitive_str (s1 + get_prefix_len(s1), s2 + get_prefix_len(s2));
753
754                 g_free (s1);
755                 g_free (s2);
756                 
757                 return cmp ? cmp : t1 - t2;
758         }
759                 
760         case MODEST_HEADER_VIEW_COLUMN_FROM:
761                 
762                 gtk_tree_model_get (tree_model, iter1,
763                                     TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN, &s1,
764                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
765                                     -1);
766                 gtk_tree_model_get (tree_model, iter2,
767                                     TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN, &s2,
768                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
769                                     -1);
770                 cmp = cmp_insensitive_str (s1, s2);
771                 g_free (s1);
772                 g_free (s2);
773                 
774                 return cmp ? cmp : t1 - t2;
775                 
776         case MODEST_HEADER_VIEW_COLUMN_TO: 
777                 
778                 gtk_tree_model_get (tree_model, iter1,
779                                     TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN, &s1,
780                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
781                                     -1);
782                 gtk_tree_model_get (tree_model, iter2,
783                                     TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN, &s2,
784                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
785                                     -1);
786                 cmp = cmp_insensitive_str (s1, s2);
787                 g_free (s1);
788                 g_free (s2);
789                 
790                 return cmp ? cmp : t1 - t2;
791
792         case MODEST_HEADER_VIEW_COLUMN_ATTACH:
793
794                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
795                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
796                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
797                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
798                 
799                 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
800                         (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
801
802                 return cmp ? cmp : t1 - t2;
803                 
804         case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
805                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
806                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
807                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
808                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
809                 cmp =  (val1 & TNY_HEADER_FLAG_SEEN) - (val2 & TNY_HEADER_FLAG_SEEN);
810
811                 return cmp ? cmp : t1 - t2;
812
813         default:
814                 return &iter1 - &iter2; /* oughhhh  */
815         }
816 }
817
818
819 static void
820 on_refresh_folder (TnyFolder *folder, gboolean cancelled, GError **err,
821                    gpointer user_data)
822 {
823         GtkTreeModel *sortable; 
824         ModestHeaderView *self;
825         ModestHeaderViewPrivate *priv;
826
827         if (cancelled)
828                 return;
829
830         self = MODEST_HEADER_VIEW(user_data);
831         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
832
833         if (!folder)  /* when there is no folder */
834                 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), FALSE);
835         else { /* it's a new one or a refresh */
836                 GSList *col;
837
838                 if (priv->headers)
839                         g_object_unref (priv->headers);
840
841                 priv->headers = TNY_LIST(tny_gtk_header_list_model_new ());
842                 tny_folder_get_headers (folder, priv->headers, FALSE, NULL); /* FIXME */
843                 
844                 tny_gtk_header_list_model_set_folder
845                         (TNY_GTK_HEADER_LIST_MODEL(priv->headers),folder, TRUE); /*async*/
846                         
847                 sortable = gtk_tree_model_sort_new_with_model
848                         (GTK_TREE_MODEL(priv->headers));
849
850                 /* install our special sorting functions */
851                 col = priv->columns;
852                 while (col) {
853                         gint col_id = GPOINTER_TO_INT (col->data);
854                         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
855                                                          col_id,
856                                                          (GtkTreeIterCompareFunc)cmp_rows,
857                                                          GINT_TO_POINTER(col_id), NULL);
858                         col = col->next;
859                 }
860
861                 gtk_tree_view_set_model (GTK_TREE_VIEW (self), sortable);
862                 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self),TRUE);
863                 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), TRUE);
864                 /* no need to unref sortable */
865         }
866 }
867
868
869 static void
870 on_refresh_folder_status_update (TnyFolder *folder, const gchar *msg,
871                                  gint status_id,  gpointer user_data)
872 {
873         ModestHeaderView *self;
874         ModestHeaderViewPrivate *priv;
875
876         self = MODEST_HEADER_VIEW(user_data);
877         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
878
879         g_signal_emit (G_OBJECT(self), signals[STATUS_UPDATE_SIGNAL],
880                        0, msg, status_id);
881 }
882
883
884
885 gboolean
886 modest_header_view_set_folder (ModestHeaderView *self,
887                                TnyFolder *folder)
888 {
889         ModestHeaderViewPrivate *priv;
890         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
891
892         if (!folder)  {/* when there is no folder */
893                 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), FALSE);
894                 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
895         } else { /* it's a new one or a refresh */
896                 tny_folder_refresh_async (folder,
897                                           on_refresh_folder,
898                                           on_refresh_folder_status_update,
899                                           self);
900         }
901
902         /* no message selected */
903         g_signal_emit (G_OBJECT(self), signals[MESSAGE_SELECTED_SIGNAL], 0,
904                        NULL);
905
906         //g_mutex_unlock (priv->lock);
907
908         return TRUE;
909 }
910
911
912
913 static void
914 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
915 {
916         GtkTreeModel            *model;
917         TnyHeader       *header;
918         TnyHeaderFlags header_flags;
919         GtkTreeIter             iter;
920         ModestHeaderView        *self;
921         ModestHeaderViewPrivate *priv;
922         const TnyMsg *msg = NULL;
923         const TnyFolder *folder;
924         
925         g_return_if_fail (sel);
926         g_return_if_fail (user_data);
927         
928         self = MODEST_HEADER_VIEW (user_data);
929         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);    
930         
931         if (!gtk_tree_selection_get_selected (sel, &model, &iter))
932                 return; /* msg was _un_selected */
933         
934         gtk_tree_model_get (model, &iter,
935                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
936                             &header, -1);
937         
938         if (!header) {
939                 g_printerr ("modest: cannot find header\n");
940                 return;
941         }
942
943         folder = tny_header_get_folder (TNY_HEADER(header));
944         if (!folder) {
945                 g_signal_emit (G_OBJECT(self), signals[ITEM_NOT_FOUND_SIGNAL], 0,
946                                MODEST_ITEM_TYPE_FOLDER);
947                 return;
948         }
949         
950         msg = tny_folder_get_msg (TNY_FOLDER(folder),
951                                   header, NULL); /* FIXME */
952         if (!msg) {
953                 g_signal_emit (G_OBJECT(self), signals[ITEM_NOT_FOUND_SIGNAL], 0,
954                                MODEST_ITEM_TYPE_MESSAGE);
955                 return;
956         }
957                                         
958         g_signal_emit (G_OBJECT(self), signals[MESSAGE_SELECTED_SIGNAL], 0,
959                        msg);
960         
961         /* mark message as seen; _set_flags crashes, bug in tinymail? */
962         header_flags = tny_header_get_flags (TNY_HEADER(header));
963         tny_header_set_flags (header, header_flags | TNY_HEADER_FLAG_SEEN);
964
965         /* Free */
966 /*      g_free (folder); */
967 }       
968