* update for new progress update in tinymail
[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         MESSAGE_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            *tny_folder;
60         TnyList              *headers;
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_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_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_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
328
329         gtk_tree_view_column_set_resizable (column, resizable);
330         if (resizable)
331                 gtk_tree_view_column_set_min_width (column, 100);
332         
333         if (show_as_text) 
334                 gtk_tree_view_column_add_attribute (column, renderer, "text",
335                                                     sort_col_id);
336         if (sort_col_id >= 0)
337                 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
338
339         gtk_tree_view_column_set_sort_indicator (column, FALSE);
340         gtk_tree_view_column_set_reorderable (column, TRUE);
341
342         if (cell_data_func)
343                 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
344                                                         user_data, NULL);
345         return column;
346 }
347
348
349
350
351 static void
352 remove_all_columns (ModestHeaderView *obj)
353 {
354         GList *columns, *cursor;
355
356         columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
357
358         for (cursor = columns; cursor; cursor = cursor->next)
359                 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
360                                              GTK_TREE_VIEW_COLUMN(cursor->data));
361         g_list_free (columns);  
362 }
363
364
365
366 gboolean
367 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns)
368 {
369         GtkTreeViewColumn *column=NULL;
370         GtkCellRenderer *renderer_msgtype,
371                 *renderer_header,
372                 *renderer_attach;
373
374         ModestHeaderViewPrivate *priv;
375         const GList *cursor;
376         
377         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); 
378
379         /* FIXME: check whether these renderers need to be freed */
380         renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
381         renderer_attach  = gtk_cell_renderer_pixbuf_new ();
382         renderer_header  = gtk_cell_renderer_text_new (); 
383         
384         remove_all_columns (self);
385         
386         for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
387                 ModestHeaderViewColumn col =
388                         (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
389                 
390                 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
391                         g_printerr ("modest: invalid column %d in column list\n", col);
392                         continue;
393                 }
394                 
395                 switch (col) {
396                         
397                 case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
398                         column = get_new_column (_("M"), renderer_msgtype, FALSE,
399                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
400                                                  FALSE, (GtkTreeCellDataFunc)msgtype_cell_data,
401                                                  NULL);
402                         gtk_tree_view_column_set_fixed_width (column, 32);
403                         break;
404
405                 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
406                         column = get_new_column (_("A"), renderer_attach, FALSE,
407                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
408                                                  FALSE, (GtkTreeCellDataFunc)attach_cell_data,
409                                                  NULL);
410                         gtk_tree_view_column_set_fixed_width (column, 32);
411                         break;
412                 
413                 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
414                         column = get_new_column (_("Received"), renderer_header, TRUE,
415                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_COLUMN,
416                                                  TRUE, (GtkTreeCellDataFunc)header_cell_data,
417                                                  NULL);
418                         break;
419                         
420                 case MODEST_HEADER_VIEW_COLUMN_FROM:
421                         column = get_new_column (_("From"), renderer_header, TRUE,
422                                                  TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
423                                                  TRUE, (GtkTreeCellDataFunc)sender_receiver_cell_data,
424                                                  GINT_TO_POINTER(TRUE));
425                         break;
426
427                 case MODEST_HEADER_VIEW_COLUMN_TO:
428                         column = get_new_column (_("To"), renderer_header, TRUE,
429                                                  TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
430                                                  TRUE, (GtkTreeCellDataFunc)sender_receiver_cell_data,
431                                                  GINT_TO_POINTER(FALSE));
432                         break;
433                         
434                 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER:
435                         column = get_new_column (_("Header"), renderer_header, TRUE,
436                                                  TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
437                                                  TRUE, (GtkTreeCellDataFunc)compact_header_cell_data,
438                                                  NULL);
439                         break;
440                         
441                 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
442                         column = get_new_column (_("Subject"), renderer_header, TRUE,
443                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
444                                                  TRUE, (GtkTreeCellDataFunc)header_cell_data,
445                                                  NULL);
446                         break;
447                         
448                         
449                 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
450                         column = get_new_column (_("Sent"), renderer_header, TRUE,
451                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_COLUMN,
452                                                  TRUE, (GtkTreeCellDataFunc)header_cell_data,
453                                                  NULL);
454                         break;
455
456                 default:
457                         g_assert_not_reached ();
458                 }
459
460                 /* we keep the column id around */
461                 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
462                                    GINT_TO_POINTER(col));
463                 
464                 /* we need this ptr when sorting the rows */
465                 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
466                                    self);
467                 
468                 gtk_tree_view_column_set_visible   (column, TRUE);
469                 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);              
470         }       
471         return TRUE;
472 }
473
474
475
476 static void
477 modest_header_view_init (ModestHeaderView *obj)
478 {
479         ModestHeaderViewPrivate *priv;
480         priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj); 
481
482         priv->lock = g_mutex_new ();
483         priv->sig1 = 0;
484 }
485
486 static void
487 modest_header_view_finalize (GObject *obj)
488 {
489         ModestHeaderView        *self;
490         ModestHeaderViewPrivate *priv;
491         GtkTreeSelection        *sel;
492         
493         self = MODEST_HEADER_VIEW(obj);
494         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
495
496         if (priv->headers)      
497                 g_object_unref (G_OBJECT(priv->headers));
498
499         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
500
501         if (sel && priv->sig1 != 0) {
502                 g_signal_handler_disconnect (G_OBJECT(sel), priv->sig1);
503                 priv->sig1 = 0;
504         }
505                 
506         if (priv->lock) {
507                 g_mutex_free (priv->lock);
508                 priv->lock = NULL;
509         }
510
511         priv->headers       = NULL;
512         priv->tny_folder    = NULL;
513         
514         //G_OBJECT_CLASS(parent_class)->finalize (obj);
515 }
516         
517 GtkWidget*
518 modest_header_view_new (TnyFolder *folder, const GList *columns,
519                         ModestHeaderViewStyle style)
520 {
521         GObject *obj;
522         GtkTreeSelection *sel;
523         ModestHeaderView *self;
524         ModestHeaderViewPrivate *priv;
525         
526         obj  = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
527         self = MODEST_HEADER_VIEW(obj);
528         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
529         
530         if (!modest_header_view_set_folder (self, NULL)) {
531                 g_warning ("could not set the folder");
532                 g_object_unref (obj);
533                 return NULL;
534         }
535         
536         modest_header_view_set_style   (self, style);
537         modest_header_view_set_columns (self, columns);
538         
539         /* all cols */
540         gtk_tree_view_set_headers_visible   (GTK_TREE_VIEW(obj), TRUE);
541         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(obj), TRUE);
542         gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
543         
544         gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
545                                       TRUE); /* alternating row colors */
546
547         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
548         
549         priv->sig1 = g_signal_connect (sel, "changed",
550                                        G_CALLBACK(on_selection_changed), self);
551         return GTK_WIDGET(self);
552 }
553
554
555 TnyList * 
556 modest_header_view_get_selected_headers (ModestHeaderView *self)
557 {
558         GtkTreeSelection *sel;
559         ModestHeaderViewPrivate *priv;
560         TnyList *header_list = NULL;
561         TnyHeader *header;
562         GList *list, *tmp = NULL;
563         GtkTreeModel *tree_model = NULL;
564         GtkTreeIter iter;
565         
566         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
567
568         /* Get selected rows */
569         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
570         list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
571
572         if (list) {
573                 header_list = tny_simple_list_new();
574
575                 list = g_list_reverse (list);
576                 tmp = list;
577                 while (tmp) {                   
578                         /* get header from selection */
579                         gtk_tree_model_get_iter (tree_model,
580                                                  &iter,
581                                                  (GtkTreePath *) (tmp->data));
582                                                                           
583                         gtk_tree_model_get (tree_model, &iter,
584                                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
585                                             &header, -1);
586
587                         /* Prepend to list */
588                         tny_list_prepend (header_list, G_OBJECT (header));
589                         tmp = g_list_next (tmp);
590                 }
591                 /* Clean up*/
592                 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
593                 g_list_free (list);
594         }
595         return header_list;
596 }
597
598
599 GList*
600 modest_header_view_get_columns (ModestHeaderView *self)
601 {
602         g_return_val_if_fail (self, FALSE);
603         return gtk_tree_view_get_columns (GTK_TREE_VIEW(self)); 
604 }
605
606
607
608
609 gboolean
610 modest_header_view_set_style (ModestHeaderView *self,
611                               ModestHeaderViewStyle style)
612 {
613         g_return_val_if_fail (self, FALSE);
614         g_return_val_if_fail (style < MODEST_HEADER_VIEW_STYLE_NUM, FALSE);
615         
616         MODEST_HEADER_VIEW_GET_PRIVATE(self)->style = style;
617         
618         return TRUE;
619 }
620
621 ModestHeaderViewStyle
622 modest_header_view_get_style (ModestHeaderView *self)
623 {
624         g_return_val_if_fail (self, FALSE);
625
626         return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
627 }
628
629
630
631 /* get the length of any prefix that should be ignored for sorting */
632 static inline int 
633 get_prefix_len (const gchar *sub)
634 {
635         gint i;
636         static const gchar* prefix[] = {
637                 "Re:", "RE:", "Fwd:", "FWD:", "FW:", NULL
638         };
639                 
640         if (!sub || (sub[0] != 'R' && sub[0] != 'F')) /* optimization */
641                 return 0;
642
643         i = 0;
644         
645         while (prefix[i]) {
646                 if (g_str_has_prefix(sub, prefix[i])) {
647                         int prefix_len = strlen(prefix[i]); 
648                         if (sub[prefix_len] == ' ')
649                                 ++prefix_len; /* ignore space after prefix as well */
650                         return prefix_len; 
651                 }
652                 ++i;
653         }
654         return 0;
655 }
656
657
658
659 /* a strcmp that is case insensitive and NULL-safe */
660 static gint
661 cmp_insensitive_str (const gchar* s1, const gchar *s2)
662 {
663         gint result = 0;
664         gchar *n1, *n2;
665
666         /* work even when s1 and/or s2 == NULL */
667         if (s1 == s2)
668                 return 0;
669         if (!s1)
670                 return -1;
671         if (!s2)
672                 return 1;
673
674         n1 = g_utf8_collate_key (s1, -1);
675         n2 = g_utf8_collate_key (s2, -1);
676         
677         result = strcmp (n1, n2);
678
679         g_free (n1);
680         g_free (n2);
681         
682         return result;
683 }
684
685
686 static gint
687 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
688           gpointer user_data)
689 {
690         gint col_id;
691         gint t1, t2;
692         gint val1, val2;
693         gchar *s1, *s2;
694         gint cmp;
695         
696         static int counter = 0;
697         col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_COLUMN));
698         
699         if (!(++counter % 100)) {
700                 GObject *header_view = g_object_get_data(G_OBJECT(user_data),
701                                                          MODEST_HEADER_VIEW_PTR);
702                 g_signal_emit (header_view,
703                                signals[STATUS_UPDATE_SIGNAL],
704                                0, _("Sorting..."), 0, 0);
705         }
706         
707         switch (col_id) {
708
709                 /* first one, we decide based on the time */
710         case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER:
711         case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
712                 gtk_tree_model_get (tree_model, iter1,
713                                     TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
714                                     &t1,-1);
715                 gtk_tree_model_get (tree_model, iter2,
716                                     TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
717                                     &t2,-1);
718                 return t1 - t2;
719                 
720         case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
721                 gtk_tree_model_get (tree_model, iter1,
722                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
723                                     &t1,-1);
724                 gtk_tree_model_get (tree_model, iter2,
725                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
726                                     &t2,-1);
727                 return t1 - t2;
728
729                 
730                 /* next ones, we try the search criteria first, if they're the same, then we use 'sent date' */
731         case MODEST_HEADER_VIEW_COLUMN_SUBJECT: {
732
733                 gtk_tree_model_get (tree_model, iter1,
734                                     TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s1,
735                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
736                                     -1);
737                 gtk_tree_model_get (tree_model, iter2,
738                                     TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s2,
739                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
740                                     -1);
741
742                 /* the prefix ('Re:', 'Fwd:' etc.) we ignore */ 
743                 cmp = cmp_insensitive_str (s1 + get_prefix_len(s1), s2 + get_prefix_len(s2));
744
745                 g_free (s1);
746                 g_free (s2);
747                 
748                 return cmp ? cmp : t1 - t2;
749         }
750                 
751         case MODEST_HEADER_VIEW_COLUMN_FROM:
752                 
753                 gtk_tree_model_get (tree_model, iter1,
754                                     TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN, &s1,
755                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
756                                     -1);
757                 gtk_tree_model_get (tree_model, iter2,
758                                     TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN, &s2,
759                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
760                                     -1);
761                 cmp = cmp_insensitive_str (s1, s2);
762                 g_free (s1);
763                 g_free (s2);
764                 
765                 return cmp ? cmp : t1 - t2;
766                 
767         case MODEST_HEADER_VIEW_COLUMN_TO: 
768                 
769                 gtk_tree_model_get (tree_model, iter1,
770                                     TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN, &s1,
771                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
772                                     -1);
773                 gtk_tree_model_get (tree_model, iter2,
774                                     TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN, &s2,
775                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
776                                     -1);
777                 cmp = cmp_insensitive_str (s1, s2);
778                 g_free (s1);
779                 g_free (s2);
780                 
781                 return cmp ? cmp : t1 - t2;
782
783         case MODEST_HEADER_VIEW_COLUMN_ATTACH:
784
785                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
786                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
787                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
788                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
789                 
790                 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
791                         (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
792
793                 return cmp ? cmp : t1 - t2;
794                 
795         case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
796                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
797                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
798                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
799                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
800                 cmp =  (val1 & TNY_HEADER_FLAG_SEEN) - (val2 & TNY_HEADER_FLAG_SEEN);
801
802                 return cmp ? cmp : t1 - t2;
803
804         default:
805                 return &iter1 - &iter2; /* oughhhh  */
806         }
807 }
808
809
810 static void
811 on_refresh_folder (TnyFolder *folder, gboolean cancelled, GError **err,
812                    gpointer user_data)
813 {
814         GtkTreeModel *sortable; 
815         ModestHeaderView *self;
816         ModestHeaderViewPrivate *priv;
817
818         if (cancelled)
819                 return;
820         
821         self = MODEST_HEADER_VIEW(user_data);
822         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
823
824         if (!folder)  /* when there is no folder */
825                 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), FALSE);
826         else { /* it's a new one or a refresh */
827                 GList *cols, *cursor;
828
829                 if (priv->headers)
830                         g_object_unref (priv->headers);
831
832                 priv->headers = TNY_LIST(tny_gtk_header_list_model_new ());
833                 tny_folder_get_headers (folder, priv->headers, FALSE, NULL); /* FIXME */
834                 
835                 tny_gtk_header_list_model_set_folder
836                         (TNY_GTK_HEADER_LIST_MODEL(priv->headers),folder, TRUE); /*async*/
837                         
838                 sortable = gtk_tree_model_sort_new_with_model
839                         (GTK_TREE_MODEL(priv->headers));
840
841                 /* install our special sorting functions */
842                 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
843                 while (cursor) {
844                         gint col_id = GPOINTER_TO_INT (g_object_get_data(G_OBJECT(cursor->data),
845                                                                          MODEST_HEADER_VIEW_COLUMN));
846                         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
847                                                          col_id,
848                                                          (GtkTreeIterCompareFunc)cmp_rows,
849                                                          cursor->data, NULL);
850                         cursor = g_list_next(cursor);
851                 }
852                 g_list_free (cols);
853                 
854                 gtk_tree_view_set_model (GTK_TREE_VIEW (self), sortable);
855                 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self),TRUE);
856                 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), TRUE);
857                 /* no need to unref sortable */ 
858         }
859 }
860
861
862 static void
863 on_refresh_folder_status_update (TnyFolder *folder, const gchar *msg,
864                                  gint num, gint total,  gpointer user_data)
865 {
866         ModestHeaderView *self;
867         ModestHeaderViewPrivate *priv;
868
869         self = MODEST_HEADER_VIEW(user_data);
870         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
871
872         g_signal_emit (G_OBJECT(self), signals[STATUS_UPDATE_SIGNAL],
873                        0, msg, num, total);
874 }
875
876
877
878 gboolean
879 modest_header_view_set_folder (ModestHeaderView *self, TnyFolder *folder)
880 {
881         ModestHeaderViewPrivate *priv;
882         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
883
884         if (!folder)  {/* when there is no folder */
885                 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), FALSE);
886                 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
887         } else { /* it's a new one or a refresh */
888                 tny_folder_refresh_async (folder,
889                                           on_refresh_folder,
890                                           on_refresh_folder_status_update,
891                                           self);
892         }
893
894         /* no message selected */
895         g_signal_emit (G_OBJECT(self), signals[MESSAGE_SELECTED_SIGNAL], 0,
896                        NULL);
897
898         //g_mutex_unlock (priv->lock);
899
900         return TRUE;
901 }
902
903
904
905 static void
906 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
907 {
908         GtkTreeModel            *model;
909         TnyHeader       *header;
910         TnyHeaderFlags header_flags;
911         GtkTreeIter             iter;
912         ModestHeaderView        *self;
913         ModestHeaderViewPrivate *priv;
914         const TnyMsg *msg = NULL;
915         const TnyFolder *folder;
916         
917         g_return_if_fail (sel);
918         g_return_if_fail (user_data);
919         
920         self = MODEST_HEADER_VIEW (user_data);
921         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);    
922         
923         if (!gtk_tree_selection_get_selected (sel, &model, &iter))
924                 return; /* msg was _un_selected */
925         
926         gtk_tree_model_get (model, &iter,
927                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
928                             &header, -1);
929         
930         if (!header) {
931                 g_printerr ("modest: cannot find header\n");
932                 return;
933         }
934
935         folder = tny_header_get_folder (TNY_HEADER(header));
936         if (!folder) {
937                 g_signal_emit (G_OBJECT(self), signals[ITEM_NOT_FOUND_SIGNAL], 0,
938                                MODEST_ITEM_TYPE_FOLDER);
939                 return;
940         }
941         
942         msg = tny_folder_get_msg (TNY_FOLDER(folder),
943                                   header, NULL); /* FIXME */
944         if (!msg) {
945                 g_signal_emit (G_OBJECT(self), signals[ITEM_NOT_FOUND_SIGNAL], 0,
946                                MODEST_ITEM_TYPE_MESSAGE);
947                 return;
948         }
949                                         
950         g_signal_emit (G_OBJECT(self), signals[MESSAGE_SELECTED_SIGNAL], 0,
951                        msg);
952         
953         /* mark message as seen; _set_flags crashes, bug in tinymail? */
954         header_flags = tny_header_get_flags (TNY_HEADER(header));
955         tny_header_set_flags (header, header_flags | TNY_HEADER_FLAG_SEEN);
956
957         /* Free */
958 /*      g_free (folder); */
959 }       
960