4a5509bfef38e15c9e63bba57bfd1c7b8e512edc
[modest] / src / modest-tny-header-tree-view.c
1 /* modest-tny-header-tree-view.c 
2  */
3 #include <glib/gi18n.h>
4 #include "modest-tny-header-tree-view.h"
5 #include <tny-list-iface.h>
6 #include <string.h>
7
8 /* 'private'/'protected' functions */
9 static void modest_tny_header_tree_view_class_init  (ModestTnyHeaderTreeViewClass *klass);
10 static void modest_tny_header_tree_view_init        (ModestTnyHeaderTreeView *obj);
11 static void modest_tny_header_tree_view_finalize    (GObject *obj);
12
13 static void selection_changed (GtkTreeSelection *sel, gpointer user_data);
14 static void column_clicked (GtkTreeViewColumn *treeviewcolumn, gpointer user_data);
15
16 #define PIXMAP_PREFIX PREFIX "/share/modest/pixmaps/"
17
18 enum {
19         MESSAGE_SELECTED_SIGNAL,
20         LAST_SIGNAL
21 };
22
23
24 enum {
25         HEADER_ICON_READ  = 1,
26         HEADER_ICON_UNREAD,
27         HEADER_ICON_ATTACH,
28         HEADER_ICON_NUM
29 };
30
31
32 enum {
33         SORT_COLUMN_FROM  = 1,
34         SORT_COLUMN_TO,
35         SORT_COLUMN_SUBJECT,
36         SORT_COLUMN_ATTACH,
37         SORT_COLUMN_RECEIVED,
38         SORT_COLUMN_SENT,
39         SORT_COLUMN_MSGTYPE,
40         SORT_COLUMN_NUM
41 };
42
43
44 typedef struct _ModestTnyHeaderTreeViewPrivate ModestTnyHeaderTreeViewPrivate;
45 struct _ModestTnyHeaderTreeViewPrivate {
46         TnyMsgFolderIface *tny_msg_folder;
47         TnyListIface      *headers;
48
49         GdkPixbuf *icons[HEADER_ICON_NUM];
50         guint sort_columns[SORT_COLUMN_NUM];
51 };
52 #define MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
53                                                          MODEST_TYPE_TNY_HEADER_TREE_VIEW, \
54                                                          ModestTnyHeaderTreeViewPrivate))
55 /* globals */
56 static GObjectClass *parent_class = NULL;
57
58 /* uncomment the following if you have defined any signals */
59 static guint signals[LAST_SIGNAL] = {0};
60
61 GType
62 modest_tny_header_tree_view_get_type (void)
63 {
64         static GType my_type = 0;
65         if (!my_type) {
66                 static const GTypeInfo my_info = {
67                         sizeof(ModestTnyHeaderTreeViewClass),
68                         NULL,           /* base init */
69                         NULL,           /* base finalize */
70                         (GClassInitFunc) modest_tny_header_tree_view_class_init,
71                         NULL,           /* class finalize */
72                         NULL,           /* class data */
73                         sizeof(ModestTnyHeaderTreeView),
74                         1,              /* n_preallocs */
75                         (GInstanceInitFunc) modest_tny_header_tree_view_init,
76                 };
77                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
78                                                   "ModestTnyHeaderTreeView",
79                                                   &my_info, 0);
80         }
81         return my_type;
82 }
83
84 static void
85 modest_tny_header_tree_view_class_init (ModestTnyHeaderTreeViewClass *klass)
86 {
87         GObjectClass *gobject_class;
88         gobject_class = (GObjectClass*) klass;
89
90         parent_class            = g_type_class_peek_parent (klass);
91         gobject_class->finalize = modest_tny_header_tree_view_finalize;
92
93         g_type_class_add_private (gobject_class, sizeof(ModestTnyHeaderTreeViewPrivate));
94
95         signals[MESSAGE_SELECTED_SIGNAL] = 
96                 g_signal_new ("message_selected",
97                               G_TYPE_FROM_CLASS (gobject_class),
98                               G_SIGNAL_RUN_FIRST,
99                               G_STRUCT_OFFSET (ModestTnyHeaderTreeViewClass,message_selected),
100                               NULL, NULL,
101                               g_cclosure_marshal_VOID__POINTER,
102                               G_TYPE_NONE, 1, G_TYPE_POINTER);  
103 }
104
105
106
107
108
109 static void
110 msgtype_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
111                    GtkTreeModel *tree_model, GtkTreeIter *iter,
112                    GdkPixbuf *icons[HEADER_ICON_NUM])
113 {
114         TnyMsgHeaderFlags flags;
115         GdkPixbuf *pixbuf;
116
117         gtk_tree_model_get (tree_model, iter, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
118                             &flags, -1);
119
120         if (flags & TNY_MSG_HEADER_FLAG_SEEN)
121                 pixbuf = icons[HEADER_ICON_READ];
122         else
123                 pixbuf = icons[HEADER_ICON_UNREAD];
124         
125         g_object_set (G_OBJECT (renderer), "pixbuf", pixbuf,
126                       NULL);
127 }
128
129 static void
130 attach_cell_data (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
131                   GtkTreeModel *tree_model,  GtkTreeIter *iter, GdkPixbuf *icons[HEADER_ICON_NUM])
132 {
133         TnyMsgHeaderFlags flags;
134         GdkPixbuf *pixbuf;
135
136         gtk_tree_model_get (tree_model, iter, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
137                             &flags, -1);
138         if (flags & TNY_MSG_HEADER_FLAG_ATTACHMENTS)
139                 pixbuf = icons[HEADER_ICON_ATTACH];
140         else
141                 pixbuf = NULL;
142         
143         g_object_set (G_OBJECT (renderer), "pixbuf", pixbuf, NULL);
144 }
145
146
147
148 static void
149 header_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
150                   GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer user_data)
151 {
152         GObject *rendobj;
153         TnyMsgHeaderFlags flags;
154         
155         gtk_tree_model_get (tree_model, iter, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
156                             &flags, -1);
157         rendobj = G_OBJECT(renderer);           
158         
159         if (!(flags & TNY_MSG_HEADER_FLAG_SEEN))
160                 g_object_set (rendobj, "weight", 800, NULL);
161         else
162                 g_object_set (rendobj, "weight", 400, NULL); /* default, non-bold */
163 }
164
165
166
167 static void
168 sender_cell_data  (GtkTreeViewColumn *column,  GtkCellRenderer *renderer,
169                    GtkTreeModel *tree_model,  GtkTreeIter *iter,  gpointer user_data)
170 {
171         GObject *rendobj;
172         TnyMsgHeaderFlags flags;
173         gchar *from;
174         gchar *address;
175         
176         gtk_tree_model_get (tree_model, iter,
177                             TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN,  &from,
178                             TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
179                             -1);
180         rendobj = G_OBJECT(renderer);           
181
182         /* simplistic --> remove <email@address> from display */
183         address = g_strstr_len (from, strlen(from), "<");
184         if (address) {
185                 address[0]='\0';
186                 g_object_set (rendobj, "text", from, NULL);
187                 g_free (from);
188         }
189                              
190         if (!(flags & TNY_MSG_HEADER_FLAG_SEEN))
191                 g_object_set (rendobj, "weight", 800, NULL);
192         else
193                 g_object_set (rendobj, "weight", 400, NULL); /* default, non-bold */
194 }
195
196
197 static void
198 init_icons (GdkPixbuf *icons[HEADER_ICON_NUM])
199 {
200         icons[HEADER_ICON_READ] =
201                 gdk_pixbuf_new_from_file (PIXMAP_PREFIX "read.xpm",NULL);
202         icons[HEADER_ICON_UNREAD] =
203                 gdk_pixbuf_new_from_file (PIXMAP_PREFIX "unread.xpm",NULL);
204         icons[HEADER_ICON_ATTACH] =
205                 gdk_pixbuf_new_from_file (PIXMAP_PREFIX "clip.xpm",NULL);
206 }
207
208
209
210 static GtkTreeViewColumn*
211 get_new_column (const gchar *name, GtkCellRenderer *renderer,
212                 gboolean resizable, gint sort_col_id, gboolean show_as_text,
213                 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
214 {
215         GtkTreeViewColumn *column;
216
217         column =  gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
218         gtk_tree_view_column_set_resizable (column, resizable);
219
220         if (show_as_text) 
221                 gtk_tree_view_column_add_attribute (column, renderer, "text",
222                                                     sort_col_id);
223         if (sort_col_id >= 0)
224                 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
225
226         gtk_tree_view_column_set_sort_indicator (column, FALSE);
227         gtk_tree_view_column_set_reorderable (column, TRUE);
228
229         if (cell_data_func)
230                 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
231                                                         user_data, NULL);
232
233 /*      g_signal_connect (G_OBJECT (column), "clicked", */
234 /*                        G_CALLBACK (column_clicked), obj);  */
235
236         return column;
237 }
238
239
240
241
242
243 static void
244 modest_tny_header_tree_view_init (ModestTnyHeaderTreeView *obj)
245 {
246         GtkTreeViewColumn *column;
247         GtkCellRenderer *renderer_msgtype,
248                 *renderer_header,
249                 *renderer_attach;
250         int i;
251         ModestTnyHeaderTreeViewPrivate *priv;
252         
253         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(obj); 
254
255         init_icons (priv->icons);
256         
257         renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
258         renderer_attach  = gtk_cell_renderer_pixbuf_new ();
259         renderer_header = gtk_cell_renderer_text_new (); 
260
261         priv->tny_msg_folder = NULL;
262         priv->headers        = NULL;
263
264         for (i = 0; i != SORT_COLUMN_NUM; ++i)
265                 priv->sort_columns[i] = -1;
266
267         /* msgtype */
268         column = get_new_column (_("M"), renderer_msgtype, FALSE, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
269                                  FALSE, (GtkTreeCellDataFunc)msgtype_cell_data, priv->icons);
270         gtk_tree_view_append_column (GTK_TREE_VIEW(obj), column);
271         priv->sort_columns[SORT_COLUMN_MSGTYPE] =
272                 gtk_tree_view_column_get_sort_column_id (column);
273         g_signal_connect (G_OBJECT (column), "clicked",G_CALLBACK (column_clicked), obj);  
274
275         
276         /* attachment */
277         column = get_new_column (_("A"), renderer_attach, FALSE, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
278                                  FALSE, (GtkTreeCellDataFunc)attach_cell_data, priv->icons);
279         gtk_tree_view_append_column (GTK_TREE_VIEW(obj), column);
280         priv->sort_columns[SORT_COLUMN_ATTACH] =
281                 gtk_tree_view_column_get_sort_column_id (column);
282         g_signal_connect (G_OBJECT (column), "clicked",G_CALLBACK (column_clicked), obj);  
283         
284         /* received */
285         column = get_new_column (_("Received"), renderer_header, TRUE, TNY_MSG_HEADER_LIST_MODEL_DATE_RECEIVED_COLUMN,
286                                  TRUE, (GtkTreeCellDataFunc)header_cell_data, NULL);
287         gtk_tree_view_append_column (GTK_TREE_VIEW(obj), column);
288         priv->sort_columns[SORT_COLUMN_RECEIVED] =
289                 gtk_tree_view_column_get_sort_column_id (column);
290         g_signal_connect (G_OBJECT (column), "clicked",G_CALLBACK (column_clicked), obj);  
291
292         
293         /* from */
294         column = get_new_column (_("From"), renderer_header, TRUE, TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN,
295                                  TRUE, (GtkTreeCellDataFunc)sender_cell_data, NULL);
296         gtk_tree_view_append_column (GTK_TREE_VIEW(obj), column);
297         priv->sort_columns[SORT_COLUMN_FROM] =
298                 gtk_tree_view_column_get_sort_column_id (column);
299         g_signal_connect (G_OBJECT (column), "clicked",G_CALLBACK (column_clicked), obj);  
300
301
302         /* subject */
303         column = get_new_column (_("Subject"), renderer_header, TRUE, TNY_MSG_HEADER_LIST_MODEL_SUBJECT_COLUMN,
304                                  TRUE, (GtkTreeCellDataFunc)header_cell_data, NULL);
305         gtk_tree_view_append_column (GTK_TREE_VIEW(obj), column);
306         priv->sort_columns[SORT_COLUMN_SUBJECT] =
307                 gtk_tree_view_column_get_sort_column_id (column);
308         g_signal_connect (G_OBJECT (column), "clicked",G_CALLBACK (column_clicked), obj);  
309
310         
311         /* all cols */
312         gtk_tree_view_set_headers_visible   (GTK_TREE_VIEW(obj), TRUE);
313         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(obj), TRUE);
314         
315         gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj), TRUE); /* alternating row colors */
316         
317 }
318
319 static void
320 modest_tny_header_tree_view_finalize (GObject *obj)
321 {
322         ModestTnyHeaderTreeView        *self;
323         ModestTnyHeaderTreeViewPrivate *priv;
324         int i;
325         
326         self = MODEST_TNY_HEADER_TREE_VIEW(obj);
327         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self);
328
329         if (priv->headers)      
330                 g_object_unref (G_OBJECT(priv->headers));
331         
332         priv->headers = NULL;
333         priv->tny_msg_folder    = NULL;
334
335         /* cleanup our icons */
336         for (i = 0; i != HEADER_ICON_NUM; ++i) {
337                 if (priv->icons[i]) {
338                         g_object_unref (G_OBJECT(priv->icons[i]));
339                         priv->icons[i] = NULL;
340                 }
341         }
342 }
343
344 GtkWidget*
345 modest_tny_header_tree_view_new (TnyMsgFolderIface *folder)
346 {
347         GObject *obj;
348         GtkTreeSelection *sel;
349         ModestTnyHeaderTreeView *self;
350                 
351         obj  = G_OBJECT(g_object_new(MODEST_TYPE_TNY_HEADER_TREE_VIEW, NULL));
352         self = MODEST_TNY_HEADER_TREE_VIEW(obj);
353
354         if (!modest_tny_header_tree_view_set_folder (self, NULL)) {
355                 g_warning ("could not set the folder");
356                 g_object_unref (obj);
357                 return NULL;
358         }
359         
360         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
361         g_signal_connect (sel, "changed",
362                           G_CALLBACK(selection_changed), self);
363
364         return GTK_WIDGET(self);
365 }
366
367
368 static gint
369 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
370           gpointer user_data)
371 {
372         gint col_id = GPOINTER_TO_INT (user_data);
373         gint val1, val2;
374
375         g_return_val_if_fail (GTK_IS_TREE_MODEL(tree_model), -1);
376         
377         switch (col_id) {
378
379         case SORT_COLUMN_RECEIVED:
380                 gtk_tree_model_get (tree_model, iter1, TNY_MSG_HEADER_LIST_MODEL_DATE_RECEIVED_COLUMN,
381                                     &val1,-1);
382                 gtk_tree_model_get (tree_model, iter2, TNY_MSG_HEADER_LIST_MODEL_DATE_RECEIVED_COLUMN,
383                                     &val2,-1);
384
385                 g_message ("%d %d %d %d", col_id, val1, val2, val1 - val2);
386                 
387                 return val1 - val2;
388                 
389         case SORT_COLUMN_SENT:
390                 gtk_tree_model_get (tree_model, iter1, TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_COLUMN,
391                                     &val1,-1);
392                 gtk_tree_model_get (tree_model, iter2, TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_COLUMN,
393                                     &val2,-1);
394                 return val1 - val2;
395         
396         case SORT_COLUMN_ATTACH:
397                 gtk_tree_model_get (tree_model, iter1, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
398                                     &val1,-1);
399                 gtk_tree_model_get (tree_model, iter2, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
400                                     &val2,-1);
401                 
402                 return (val1 & TNY_MSG_HEADER_FLAG_ATTACHMENTS) - (val2 & TNY_MSG_HEADER_FLAG_ATTACHMENTS);
403         
404
405         case SORT_COLUMN_MSGTYPE:
406                 gtk_tree_model_get (tree_model, iter1, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
407                                     &val1,-1);
408                 gtk_tree_model_get (tree_model, iter2, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN,
409                                     &val2,-1);
410                 
411                 return (val1 & TNY_MSG_HEADER_FLAG_SEEN) - (val2 & TNY_MSG_HEADER_FLAG_SEEN);
412
413         default:
414                 g_message ("%p %p", iter1, iter2);
415                 return &iter1 - &iter2;
416         }
417 }
418
419
420
421 gboolean
422 modest_tny_header_tree_view_set_folder (ModestTnyHeaderTreeView *self,
423                                         TnyMsgFolderIface *folder)
424 {
425         int i;
426         GtkTreeModel *oldsortable, *sortable, *oldmodel;
427         ModestTnyHeaderTreeViewPrivate *priv;
428         
429         g_return_val_if_fail (self, FALSE);
430
431         priv = MODEST_TNY_HEADER_TREE_VIEW_GET_PRIVATE(self);
432         
433         if (folder) {
434                 priv->headers = TNY_LIST_IFACE(tny_msg_header_list_model_new ());
435                 tny_msg_folder_iface_get_headers (folder, priv->headers,
436                                                   FALSE);
437                 tny_msg_header_list_model_set_folder (TNY_MSG_HEADER_LIST_MODEL(priv->headers),
438                                                       folder);
439                 
440                 oldsortable = gtk_tree_view_get_model(GTK_TREE_VIEW (self));
441                 if (oldsortable && GTK_IS_TREE_MODEL_SORT(oldsortable)) {
442                         GtkTreeModel *oldmodel = gtk_tree_model_sort_get_model
443                                 (GTK_TREE_MODEL_SORT(oldsortable));
444                         if (oldmodel)
445                                 g_object_unref (G_OBJECT(oldmodel));
446                         g_object_unref (oldsortable);
447                 }
448         
449                 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(priv->headers));
450                 
451                 /* set special sorting functions */
452 #if 0 /* FIXME */
453                 gtk_tree_model_sort_reset_default_sort_func (sortable);
454
455                 for (i = 0; i != SORT_COLUMN_NUM; ++i) {
456                         int col_id = priv->sort_columns[i];
457                         if (col_id >= 0) {
458                                 g_message ("%d: %p: %p: %d", i, GTK_TREE_SORTABLE(sortable), sortable, col_id);
459                                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable), col_id,
460                                                                  (GtkTreeIterCompareFunc)cmp_rows,
461                                                                  GINT_TO_POINTER(col_id), NULL);
462                         }
463                 }
464 #endif
465                 gtk_tree_view_set_model (GTK_TREE_VIEW (self), sortable);
466                 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), TRUE);
467
468                 /* no need to unref sortable */
469                 
470         } else /* when there is no folder */
471                 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), FALSE);
472                                          
473         return TRUE;
474 }
475
476
477 static void
478 selection_changed (GtkTreeSelection *sel, gpointer user_data)
479 {
480         GtkTreeModel            *model;
481         TnyMsgHeaderIface       *header;
482         GtkTreeIter             iter;
483         ModestTnyHeaderTreeView *tree_view;
484
485         g_return_if_fail (sel);
486         g_return_if_fail (user_data);
487         
488         if (!gtk_tree_selection_get_selected (sel, &model, &iter))
489                 return; /* msg was _un_selected */
490         
491         tree_view = MODEST_TNY_HEADER_TREE_VIEW (user_data);
492         
493         gtk_tree_model_get (model, &iter,
494                             TNY_MSG_HEADER_LIST_MODEL_INSTANCE_COLUMN,
495                             &header, -1);
496         
497         if (header) {
498                 TnyMsgHeaderFlags flags;
499                 const TnyMsgIface *msg;
500                 const TnyMsgFolderIface *folder;
501                 
502                 folder = tny_msg_header_iface_get_folder (TNY_MSG_HEADER_IFACE(header));
503                 if (!folder)
504                         g_message ("cannot find folder");
505                 else {
506                         msg = tny_msg_folder_iface_get_message (TNY_MSG_FOLDER_IFACE(folder),
507                                                                 header);
508                         if (!msg) {
509                                 g_message ("cannot find msg");
510                                 /* FIXME: update display */
511                         }
512                 }
513                                         
514                 g_signal_emit (G_OBJECT(tree_view), signals[MESSAGE_SELECTED_SIGNAL], 0,
515                                msg);
516
517                 /* mark message as seen; _set_flags crashes, bug in tinymail? */
518                 flags = tny_msg_header_iface_get_flags (TNY_MSG_HEADER_IFACE(header));
519                 //tny_msg_header_iface_set_flags (header, flags | TNY_MSG_HEADER_FLAG_SEEN);
520         }
521 }
522
523 static void
524 column_clicked (GtkTreeViewColumn *col, gpointer user_data)
525 {
526         GtkTreeView *treeview;
527         gint id;
528
529         treeview = GTK_TREE_VIEW (user_data);
530         id = gtk_tree_view_column_get_sort_column_id (col);
531         
532         gtk_tree_view_set_search_column (treeview, id);
533 }