* cosmetics
[modest] / src / modest-tny-header-tree-view.c
1 /* Copyright (c) 2006, Nokia Corporation
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  *   notice, this list of conditions and the following disclaimer in the
12  *   documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Nokia Corporation nor the names of its
14  *   contributors may be used to endorse or promote products derived from
15  *   this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30
31 /* modest-tny-header-tree-view.c */
32
33 #include <glib/gi18n.h>
34 #include "modest-tny-header-tree-view.h"
35 #include <tny-list-iface.h>
36 #include <string.h>
37 #include "modest-marshal.h"
38
39 #include <modest-icon-names.h>
40 #include "modest-icon-factory.h"
41
42 /* 'private'/'protected' functions */
43 static void modest_tny_header_tree_view_class_init  (ModestTnyHeaderTreeViewClass *klass);
44 static void modest_tny_header_tree_view_init        (ModestTnyHeaderTreeView *obj);
45 static void modest_tny_header_tree_view_finalize    (GObject *obj);
46
47 static void selection_changed (GtkTreeSelection *sel, gpointer user_data);
48 static void column_clicked (GtkTreeViewColumn *treeviewcolumn, gpointer user_data);
49 static gboolean refresh_folder_finish_status_update (gpointer user_data);
50
51 enum {
52         MESSAGE_SELECTED_SIGNAL,
53         STATUS_UPDATE_SIGNAL,
54         LAST_SIGNAL
55 };
56
57 typedef struct _ModestTnyHeaderTreeViewPrivate ModestTnyHeaderTreeViewPrivate;
58 struct _ModestTnyHeaderTreeViewPrivate {
59
60         TnyMsgFolderIface *tny_msg_folder;
61         TnyListIface      *headers;
62
63         gint              status_id;
64         GSList            *columns;
65         
66         ModestTnyHeaderTreeViewStyle style;
67 };
68 #define MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
69                                                          MODEST_TYPE_TNY_HEADER_TREE_VIEW, \
70                                                          ModestTnyHeaderTreeViewPrivate))
71 /* globals */
72 static GObjectClass *parent_class = NULL;
73
74 /* uncomment the following if you have defined any signals */
75 static guint signals[LAST_SIGNAL] = {0};
76
77 GType
78 modest_tny_header_tree_view_get_type (void)
79 {
80         static GType my_type = 0;
81         if (!my_type) {
82                 static const GTypeInfo my_info = {
83                         sizeof(ModestTnyHeaderTreeViewClass),
84                         NULL,           /* base init */
85                         NULL,           /* base finalize */
86                         (GClassInitFunc) modest_tny_header_tree_view_class_init,
87                         NULL,           /* class finalize */
88                         NULL,           /* class data */
89                         sizeof(ModestTnyHeaderTreeView),
90                         1,              /* n_preallocs */
91                         (GInstanceInitFunc) modest_tny_header_tree_view_init,
92                 };
93                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
94                                                   "ModestTnyHeaderTreeView",
95                                                   &my_info, 0);
96         }
97         return my_type;
98 }
99
100 static void
101 modest_tny_header_tree_view_class_init (ModestTnyHeaderTreeViewClass *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_tny_header_tree_view_finalize;
108         
109         g_type_class_add_private (gobject_class, sizeof(ModestTnyHeaderTreeViewPrivate));
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 (ModestTnyHeaderTreeViewClass,message_selected),
116                               NULL, NULL,
117                               g_cclosure_marshal_VOID__POINTER,
118                               G_TYPE_NONE, 1, G_TYPE_POINTER);
119         
120         signals[STATUS_UPDATE_SIGNAL] = 
121                 g_signal_new ("status_update",
122                               G_TYPE_FROM_CLASS (gobject_class),
123                               G_SIGNAL_RUN_FIRST,
124                               G_STRUCT_OFFSET (ModestTnyHeaderTreeViewClass,message_selected),
125                               NULL, NULL,
126                               modest_marshal_VOID__STRING_INT,
127                               G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT);       
128 }
129
130
131
132
133
134 static void
135 msgtype_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
136                    GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data)
137 {
138         TnyMsgHeaderFlags flags;
139         GdkPixbuf *pixbuf = NULL;
140
141         gtk_tree_model_get (tree_model, iter, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
142                             &flags, -1);
143
144         if (flags & TNY_MSG_HEADER_FLAG_DELETED)
145                 pixbuf = modest_icon_factory_get_icon (MODEST_HEADER_ICON_DELETED);
146         else if (flags & TNY_MSG_HEADER_FLAG_SEEN)
147                 pixbuf = modest_icon_factory_get_icon (MODEST_HEADER_ICON_READ);
148         else
149                 pixbuf = modest_icon_factory_get_icon (MODEST_HEADER_ICON_UNREAD);
150                 
151         g_object_set (G_OBJECT (renderer), "pixbuf", pixbuf, NULL);
152 }
153
154 static void
155 attach_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
156                   GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data)
157 {
158         TnyMsgHeaderFlags flags;
159         GdkPixbuf *pixbuf = NULL;
160
161         gtk_tree_model_get (tree_model, iter, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
162                             &flags, -1);
163
164         if (flags & TNY_MSG_HEADER_FLAG_ATTACHMENTS)
165                 pixbuf = modest_icon_factory_get_icon (MODEST_HEADER_ICON_ATTACH);
166
167         g_object_set (G_OBJECT (renderer), "pixbuf", pixbuf, NULL);
168 }
169
170
171 static void
172 header_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
173                   GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer user_data)
174 {
175         TnyMsgHeaderFlags flags;
176         
177         gtk_tree_model_get (tree_model, iter, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
178                             &flags, -1);
179
180         g_object_set (G_OBJECT(renderer),
181                       "weight", (flags & TNY_MSG_HEADER_FLAG_SEEN) ? 400: 800,
182                       "style",  (flags & TNY_MSG_HEADER_FLAG_DELETED) ?
183                                  PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL,
184                       NULL);    
185 }
186
187
188
189 static void
190 sender_receiver_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
191                             GtkTreeModel *tree_model,  GtkTreeIter *iter,  gboolean is_sender)
192 {
193         TnyMsgHeaderFlags flags;
194         gchar *addr1, *addr2;
195         gint sender_receiver_col;
196
197         if (is_sender)
198                 sender_receiver_col = TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN;
199         else
200                 sender_receiver_col = TNY_MSG_HEADER_LIST_MODEL_TO_COLUMN;
201                 
202         gtk_tree_model_get (tree_model, iter,
203                             sender_receiver_col,  &addr1,
204                             TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
205                             -1);
206         
207         /* simplistic --> remove <email@address> from display */
208         addr2 = g_strstr_len (addr1, strlen(addr1), "<");
209         if (addr2) 
210                 addr2[0]='\0';
211
212         g_object_set (G_OBJECT(renderer),
213                       "text", addr1,
214                       "weight", (flags & TNY_MSG_HEADER_FLAG_SEEN) ? 400 : 800,
215                       "style",  (flags & TNY_MSG_HEADER_FLAG_DELETED) ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL,
216                       NULL);
217
218         g_free (addr1); 
219 }
220
221
222 /* not reentrant/thread-safe */
223 const gchar*
224 display_date (time_t date)
225 {
226         struct tm date_tm, now_tm; 
227         time_t now;
228
229         const gint buf_size = 64; 
230         static gchar date_buf[64]; /* buf_size is not ... */
231         static gchar now_buf[64];  /* ...const enough... */
232         
233         now = time (NULL);
234         
235         localtime_r(&now, &now_tm);
236         localtime_r(&date, &date_tm);
237
238         /* get today's date */
239         strftime (date_buf, buf_size, "%x", &date_tm);
240         strftime (now_buf,  buf_size, "%x",  &now_tm);  /* today */
241
242         /* if this is today, get the time instead of the date */
243         if (strcmp (date_buf, now_buf) == 0)
244                 strftime (date_buf, buf_size, _("%X"), &date_tm); 
245                 
246         return date_buf;
247 }
248
249
250 static void
251 compact_header_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
252                            GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer user_data)
253 {
254         GObject *rendobj;
255         TnyMsgHeaderFlags flags;
256         gchar *from, *subject;
257         gchar *address;
258         gchar *header;
259         time_t date;
260                 
261         gtk_tree_model_get (tree_model, iter,
262                             TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
263                             TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN,  &from,
264                             TNY_MSG_HEADER_LIST_MODEL_SUBJECT_COLUMN, &subject,
265                             TNY_MSG_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN, &date,   
266                             -1);
267         rendobj = G_OBJECT(renderer);           
268         
269         /* simplistic --> remove <email@address> from display */
270         address = g_strstr_len (from, strlen(from), "<");
271         if (address)
272                 address[0]='\0'; /* set a new endpoint */
273         
274         header = g_strdup_printf ("%s %s\n%s", from,
275                                   display_date(date), subject);
276         g_object_set (G_OBJECT(renderer),
277                       "text",  header,
278                       "weight", (flags & TNY_MSG_HEADER_FLAG_SEEN) ? 400: 800,
279                       "style",  (flags & TNY_MSG_HEADER_FLAG_DELETED) ?
280                                  PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL,
281                       NULL);    
282         g_free (header);
283         g_free (from);
284         g_free (subject);
285 }
286
287
288 static GtkTreeViewColumn*
289 get_new_column (const gchar *name, GtkCellRenderer *renderer,
290                 gboolean resizable, gint sort_col_id, gboolean show_as_text,
291                 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
292 {
293         GtkTreeViewColumn *column;
294
295         column =  gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
296         gtk_tree_view_column_set_resizable (column, resizable);
297
298         if (show_as_text) 
299                 gtk_tree_view_column_add_attribute (column, renderer, "text",
300                                                     sort_col_id);
301         if (sort_col_id >= 0)
302                 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
303
304         gtk_tree_view_column_set_sort_indicator (column, FALSE);
305         gtk_tree_view_column_set_reorderable (column, TRUE);
306
307         if (cell_data_func)
308                 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
309                                                         user_data, NULL);
310
311 /*      g_signal_connect (G_OBJECT (column), "clicked", */
312 /*                        G_CALLBACK (column_clicked), obj);  */
313
314         return column;
315 }
316
317
318
319
320 static void
321 remove_all_columns (ModestTnyHeaderTreeView *obj)
322 {
323         GList *columns, *cursor;
324
325         columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
326
327         for (cursor = columns; cursor; cursor = cursor->next)
328                 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
329                                              GTK_TREE_VIEW_COLUMN(cursor->data));
330         g_list_free (columns);  
331 }
332
333
334
335
336 static void
337 init_columns (ModestTnyHeaderTreeView *obj)
338 {
339         GtkTreeViewColumn *column=NULL;
340         GtkCellRenderer *renderer_msgtype,
341                 *renderer_header,
342                 *renderer_attach;
343
344         ModestTnyHeaderTreeViewPrivate *priv;
345         GSList *cursor;
346         
347         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(obj); 
348                         
349         renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
350         renderer_attach  = gtk_cell_renderer_pixbuf_new ();
351         renderer_header = gtk_cell_renderer_text_new (); 
352         
353         remove_all_columns (obj);
354         
355         for (cursor = priv->columns; cursor; cursor = cursor->next) {
356                 ModestTnyHeaderTreeViewColumn col =
357                         (ModestTnyHeaderTreeViewColumn) GPOINTER_TO_INT(cursor->data);
358                 
359                 switch (col) {
360                         
361                 case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_MSGTYPE:
362
363                         column = get_new_column (_("M"), renderer_msgtype, FALSE,
364                                                  TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
365                                                  FALSE, (GtkTreeCellDataFunc)msgtype_cell_data,
366                                                  NULL);
367                         break;
368
369                 case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_ATTACH:
370
371                         column = get_new_column (_("A"), renderer_attach, FALSE,
372                                                  TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
373                                                  FALSE, (GtkTreeCellDataFunc)attach_cell_data,
374                                                  NULL);
375                         break;
376                         
377                 case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_RECEIVED_DATE:
378                         column = get_new_column (_("Received"), renderer_header, TRUE,
379                                                  TNY_MSG_HEADER_LIST_MODEL_DATE_RECEIVED_COLUMN,
380                                                  TRUE, (GtkTreeCellDataFunc)header_cell_data,
381                                                  NULL);
382                         break;
383                         
384                 case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_FROM:
385                         column = get_new_column (_("From"), renderer_header, TRUE,
386                                                  TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN,
387                                                  TRUE, (GtkTreeCellDataFunc)sender_receiver_cell_data,
388                                                  GINT_TO_POINTER(TRUE));
389                         break;
390
391                 case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_TO:
392                         column = get_new_column (_("To"), renderer_header, TRUE,
393                                                  TNY_MSG_HEADER_LIST_MODEL_TO_COLUMN,
394                                                  TRUE, (GtkTreeCellDataFunc)sender_receiver_cell_data,
395                                                  GINT_TO_POINTER(FALSE));
396                         break;
397                         
398                 case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_COMPACT_HEADER:
399                         column = get_new_column (_("Header"), renderer_header, TRUE,
400                                                  TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN,
401                                                  TRUE, (GtkTreeCellDataFunc)compact_header_cell_data,
402                                                  NULL);
403                         break;
404                         
405                 case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_SUBJECT:
406                         column = get_new_column (_("Subject"), renderer_header, TRUE,
407                                                  TNY_MSG_HEADER_LIST_MODEL_SUBJECT_COLUMN,
408                                                  TRUE, (GtkTreeCellDataFunc)header_cell_data,
409                                                  NULL);
410                         break;
411                         
412                         
413                 case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_SENT_DATE:
414                         column = get_new_column (_("Sent"), renderer_header, TRUE,
415                                                  TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_COLUMN,
416                                                  TRUE, (GtkTreeCellDataFunc)header_cell_data,
417                                                  NULL);
418                         break;
419                 }
420                 gtk_tree_view_append_column (GTK_TREE_VIEW(obj), column);               
421         }       
422 }
423
424
425
426
427
428 static void
429 modest_tny_header_tree_view_init (ModestTnyHeaderTreeView *obj)
430 {
431         ModestTnyHeaderTreeViewPrivate *priv;
432         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(obj); 
433
434         priv->status_id = 0;
435 }
436
437 static void
438 modest_tny_header_tree_view_finalize (GObject *obj)
439 {
440         ModestTnyHeaderTreeView        *self;
441         ModestTnyHeaderTreeViewPrivate *priv;
442         
443         self = MODEST_TNY_HEADER_TREE_VIEW(obj);
444         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self);
445
446         if (priv->headers)      
447                 g_object_unref (G_OBJECT(priv->headers));
448         
449         priv->headers = NULL;
450         priv->tny_msg_folder    = NULL;
451 }
452
453 GtkWidget*
454 modest_tny_header_tree_view_new (TnyMsgFolderIface *folder,
455                                  GSList *columns,
456                                  ModestTnyHeaderTreeViewStyle style)
457 {
458         GObject *obj;
459         GtkTreeSelection *sel;
460         ModestTnyHeaderTreeView *self;
461         
462         obj  = G_OBJECT(g_object_new(MODEST_TYPE_TNY_HEADER_TREE_VIEW, NULL));
463         self = MODEST_TNY_HEADER_TREE_VIEW(obj);
464         
465         if (!modest_tny_header_tree_view_set_folder (self, NULL)) {
466                 g_warning ("could not set the folder");
467                 g_object_unref (obj);
468                 return NULL;
469         }
470         
471         modest_tny_header_tree_view_set_style   (self, style);
472         modest_tny_header_tree_view_set_columns (self, columns);
473         
474         /* all cols */
475         gtk_tree_view_set_headers_visible   (GTK_TREE_VIEW(obj), TRUE);
476         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(obj), TRUE);
477         
478         gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
479                                       TRUE); /* alternating row colors */
480
481         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
482         g_signal_connect (sel, "changed",
483                           G_CALLBACK(selection_changed), self);
484         
485         return GTK_WIDGET(self);
486 }
487
488 gboolean
489 modest_tny_header_tree_view_set_columns (ModestTnyHeaderTreeView *self, GSList *columns)
490 {
491         ModestTnyHeaderTreeViewPrivate *priv;
492         GSList *cursor;
493
494         g_return_val_if_fail (self, FALSE);
495         
496         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self);
497
498         g_slist_free (priv->columns);
499         
500         for (cursor = columns; cursor; cursor = cursor->next) {
501                 ModestTnyHeaderTreeViewColumn col = 
502                         (ModestTnyHeaderTreeViewColumn) GPOINTER_TO_INT(cursor->data);
503                 if (0 > col || col >= MODEST_TNY_HEADER_TREE_VIEW_COLUMN_NUM)
504                         g_warning ("invalid column in column list");
505                 else
506                         priv->columns = g_slist_append (priv->columns, cursor->data);
507         }
508
509         init_columns (self); /* redraw them */
510         return TRUE;
511 }
512
513
514
515 const GSList*
516 modest_tny_header_tree_view_get_columns (ModestTnyHeaderTreeView *self)
517 {
518         ModestTnyHeaderTreeViewPrivate *priv;
519
520         g_return_val_if_fail (self, FALSE);
521         
522         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self);
523         return priv->columns;
524 }
525
526
527
528
529 gboolean
530 modest_tny_header_tree_view_set_style (ModestTnyHeaderTreeView *self,
531                                        ModestTnyHeaderTreeViewStyle style)
532 {
533         g_return_val_if_fail (self, FALSE);
534         g_return_val_if_fail (style >= 0 && style < MODEST_TNY_HEADER_TREE_VIEW_STYLE_NUM,
535                               FALSE);
536         
537         MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self)->style = style;
538         
539         return TRUE;
540 }
541
542 ModestTnyHeaderTreeViewStyle
543 modest_tny_header_tree_view_get_style (ModestTnyHeaderTreeView *self)
544 {
545         g_return_val_if_fail (self, FALSE);
546
547         return MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self)->style;
548 }
549
550
551
552 /* get the length of any prefix that should be ignored for sorting */
553 static inline int 
554 get_prefix_len (const gchar *sub)
555 {
556         gint i = 0;
557         const static gchar* prefix[] = {"Re:", "RE:", "Fwd:", "FWD:", "FW:", "AW:", NULL};
558
559         if (sub[0] != 'R' && sub[0] != 'F') /* optimization */
560                 return 0;
561         
562         while (prefix[i]) {
563                 if (g_str_has_prefix(sub, prefix[i])) {
564                         int prefix_len = strlen(prefix[i]); 
565                         if (sub[prefix_len] == ' ')
566                                 ++prefix_len; /* ignore space after prefix as well */
567                         return prefix_len; 
568                 }
569                 ++i;
570         }
571         return 0;
572 }
573
574
575 static inline gint
576 cmp_normalized_subject (const gchar* s1, const gchar *s2)
577 {
578         gint result = 0;
579         register gchar *n1, *n2;
580         
581         n1 = g_utf8_collate_key (s1 + get_prefix_len(s1), -1);
582         n2 = g_utf8_collate_key (s2 + get_prefix_len(s2), -1);
583         
584         result = strcmp (n1, n2);
585         g_free (n1);
586         g_free (n2);
587         
588         return result;
589 }
590
591
592 static gint
593 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
594           gpointer user_data)
595 {
596         gint col_id = GPOINTER_TO_INT (user_data);
597         gint t1, t2;
598         gint val1, val2;
599         gchar *s1, *s2;
600         gint cmp;
601         
602         g_return_val_if_fail (GTK_IS_TREE_MODEL(tree_model), -1);
603         
604         switch (col_id) {
605
606                 /* first one, we decide based on the time */
607         case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_COMPACT_HEADER:
608         case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_RECEIVED_DATE:
609                 gtk_tree_model_get (tree_model, iter1, TNY_MSG_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
610                                     &t1,-1);
611                 gtk_tree_model_get (tree_model, iter2, TNY_MSG_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
612                                     &t2,-1);
613                 return t1 - t2;
614                 
615         case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_SENT_DATE:
616                 gtk_tree_model_get (tree_model, iter1, TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
617                                     &t1,-1);
618                 gtk_tree_model_get (tree_model, iter2, TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
619                                     &t2,-1);
620                 return t1 - t2;
621
622                 
623                 /* next ones, we try the search criteria first, if they're the same, then we use 'sent date' */
624         case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_SUBJECT: {
625
626                 gtk_tree_model_get (tree_model, iter1,
627                                     TNY_MSG_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s1,
628                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
629                                     -1);
630                 gtk_tree_model_get (tree_model, iter2,
631                                     TNY_MSG_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s2,
632                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
633                                     -1);
634                 
635                 cmp = cmp_normalized_subject(s1, s2);
636
637                 g_free (s1);
638                 g_free (s2);
639                 
640                 return cmp ? cmp : t1 - t2;
641         }
642                 
643         case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_FROM:
644                 
645                 gtk_tree_model_get (tree_model, iter1,
646                                     TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN, &s1,
647                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
648                                     -1);
649                 gtk_tree_model_get (tree_model, iter2,
650                                     TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN, &s2,
651                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
652                                     -1);
653                 cmp = strcmp (s1, s2);
654                 g_free (s1);
655                 g_free (s2);
656                 
657                 return cmp ? cmp : t1 - t2;
658                 
659         case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_TO: 
660                 
661                 gtk_tree_model_get (tree_model, iter1,
662                                     TNY_MSG_HEADER_LIST_MODEL_TO_COLUMN, &s1,
663                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
664                                     -1);
665                 gtk_tree_model_get (tree_model, iter2,
666                                     TNY_MSG_HEADER_LIST_MODEL_TO_COLUMN, &s2,
667                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
668                                     -1);
669                 cmp = strcmp (s1, s2);
670                 g_free (s1);
671                 g_free (s2);
672                 
673                 return cmp ? cmp : t1 - t2;
674
675         case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_ATTACH:
676
677                 gtk_tree_model_get (tree_model, iter1, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
678                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
679                 gtk_tree_model_get (tree_model, iter2, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
680                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
681                 
682                 cmp = (val1 & TNY_MSG_HEADER_FLAG_ATTACHMENTS) - (val2 & TNY_MSG_HEADER_FLAG_ATTACHMENTS);
683
684                 return cmp ? cmp : t1 - t2;
685                 
686         case MODEST_TNY_HEADER_TREE_VIEW_COLUMN_MSGTYPE:
687                 gtk_tree_model_get (tree_model, iter1, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
688                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
689                 gtk_tree_model_get (tree_model, iter2, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
690                                     TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
691                 cmp =  (val1 & TNY_MSG_HEADER_FLAG_SEEN) - (val2 & TNY_MSG_HEADER_FLAG_SEEN);
692
693                 return cmp ? cmp : t1 - t2;
694
695         default:
696                 return &iter1 - &iter2; /* oughhhh  */
697         }
698 }
699
700
701 static void
702 refresh_folder (TnyMsgFolderIface *folder, gboolean cancelled,
703                                        gpointer user_data)
704 {
705         GtkTreeModel *oldsortable, *sortable;
706         ModestTnyHeaderTreeView *self =
707                 MODEST_TNY_HEADER_TREE_VIEW(user_data);
708         ModestTnyHeaderTreeViewPrivate *priv;
709
710         g_return_if_fail (self);
711         
712         if (cancelled)
713                 return;
714         
715         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self);
716
717         if (!folder)  /* when there is no folder */
718                 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), FALSE);
719         
720         else { /* it's a new one or a refresh */
721                 GSList *col;
722
723                 priv->headers = TNY_LIST_IFACE(tny_msg_header_list_model_new ());
724                         
725                 tny_msg_folder_iface_get_headers (folder, priv->headers, FALSE);
726                 tny_msg_header_list_model_set_folder (TNY_MSG_HEADER_LIST_MODEL(priv->headers),
727                                                       folder, TRUE); /* async */
728                 
729                 oldsortable = gtk_tree_view_get_model(GTK_TREE_VIEW (self));
730                 if (oldsortable && GTK_IS_TREE_MODEL_SORT(oldsortable)) {
731                         GtkTreeModel *oldmodel = gtk_tree_model_sort_get_model
732                                 (GTK_TREE_MODEL_SORT(oldsortable));
733                         if (oldmodel)
734                                 g_object_unref (G_OBJECT(oldmodel));
735                         g_object_unref (oldsortable);
736                 }
737         
738                 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(priv->headers));
739
740                 /* install our special sorting functions */
741                 col = priv->columns;
742                 while (col) {
743                         gint col_id = GPOINTER_TO_INT (col->data);
744                         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable), col_id,
745                                                          (GtkTreeIterCompareFunc)cmp_rows,
746                                                          GINT_TO_POINTER(col_id), NULL);
747                         col = col->next;
748                 }
749                 
750                 gtk_tree_view_set_model (GTK_TREE_VIEW (self), sortable);
751                 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), TRUE);
752                 /* no need to unref sortable */
753         } 
754 }
755
756
757 static void
758 refresh_folder_status_update (TnyMsgFolderIface *folder, const gchar *msg,
759                               gint status_id, gpointer user_data)
760 {
761         ModestTnyHeaderTreeView *self;
762         ModestTnyHeaderTreeViewPrivate *priv;
763         
764         self = MODEST_TNY_HEADER_TREE_VIEW (user_data);
765         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self);
766
767         g_signal_emit (G_OBJECT(self),
768                                signals[STATUS_UPDATE_SIGNAL], 0,
769                                msg, status_id);
770         if (msg) 
771                 g_timeout_add  (750,
772                                 (GSourceFunc)refresh_folder_finish_status_update,
773                                 self);
774         
775         priv->status_id = status_id;
776 }
777
778
779 static gboolean
780 refresh_folder_finish_status_update (gpointer user_data)
781 {
782         ModestTnyHeaderTreeView *self;
783         ModestTnyHeaderTreeViewPrivate *priv;
784         
785         self = MODEST_TNY_HEADER_TREE_VIEW (user_data);
786         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self);
787
788         if (priv->status_id == 0)
789                 return FALSE;
790         
791         refresh_folder_status_update (NULL, NULL, priv->status_id,
792                                       user_data);
793         priv->status_id = 0;
794
795         return FALSE;   
796 }
797
798
799 gboolean
800 modest_tny_header_tree_view_set_folder (ModestTnyHeaderTreeView *self,
801                                         TnyMsgFolderIface *folder)
802 {
803         ModestTnyHeaderTreeViewPrivate *priv;
804         
805         
806         g_return_val_if_fail (MODEST_IS_TNY_HEADER_TREE_VIEW (self), FALSE);
807
808         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self);
809         
810         if (!folder)  {/* when there is no folder */
811                 GtkTreeModel *model;
812                 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
813                 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), FALSE);
814                 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
815                 if (model)
816                         g_object_unref (model);
817         }
818         else { /* it's a new one or a refresh */
819                 tny_msg_folder_iface_refresh_async (folder,
820                                             refresh_folder,
821                                                     refresh_folder_status_update,
822                                                     self);
823         }
824         return TRUE;
825 }
826
827
828
829 static void
830 selection_changed (GtkTreeSelection *sel, gpointer user_data)
831 {
832         GtkTreeModel            *model;
833         TnyMsgHeaderIface       *header;
834         GtkTreeIter             iter;
835         ModestTnyHeaderTreeView *tree_view;
836
837         g_return_if_fail (sel);
838         g_return_if_fail (user_data);
839         
840         if (!gtk_tree_selection_get_selected (sel, &model, &iter))
841                 return; /* msg was _un_selected */
842         
843         tree_view = MODEST_TNY_HEADER_TREE_VIEW (user_data);
844         
845         gtk_tree_model_get (model, &iter,
846                             TNY_MSG_HEADER_LIST_MODEL_INSTANCE_COLUMN,
847                             &header, -1);
848         
849         if (header) {
850                 const TnyMsgIface *msg = NULL;
851                 const TnyMsgFolderIface *folder;
852                 
853                 folder = tny_msg_header_iface_get_folder (TNY_MSG_HEADER_IFACE(header));
854                 if (!folder)
855                         g_message ("cannot find folder");
856                 else {
857                         msg = tny_msg_folder_iface_get_message (TNY_MSG_FOLDER_IFACE(folder),
858                                                                 header);
859                         if (!msg) {
860                                 g_message ("cannot find msg");
861                                 gtk_tree_store_remove (GTK_TREE_STORE(model), 
862                                                        &iter); 
863                         }
864                 }
865                                         
866                 g_signal_emit (G_OBJECT(tree_view), signals[MESSAGE_SELECTED_SIGNAL], 0,
867                                msg);
868
869                 /* mark message as seen; _set_flags crashes, bug in tinymail? */
870                 //flags = tny_msg_header_iface_get_flags (TNY_MSG_HEADER_IFACE(header));
871                 //tny_msg_header_iface_set_flags (header, TNY_MSG_HEADER_FLAG_SEEN);
872         }
873 }
874
875 static void
876 column_clicked (GtkTreeViewColumn *col, gpointer user_data)
877 {
878         GtkTreeView *treeview;
879         gint id;
880
881         treeview = GTK_TREE_VIEW (user_data);
882         id = gtk_tree_view_column_get_sort_column_id (col);
883         
884         gtk_tree_view_set_search_column (treeview, id);
885 }