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