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