* Set focus on next header after drag&drop operaiton.
[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 <tny-folder-change.h>
35 #include <string.h>
36
37 #include <modest-header-view.h>
38 #include <modest-header-view-priv.h>
39 #include <modest-dnd.h>
40 #include <modest-tny-folder.h>
41
42 #include <modest-main-window.h>
43
44 #include <modest-marshal.h>
45 #include <modest-text-utils.h>
46 #include <modest-icon-names.h>
47 #include <modest-runtime.h>
48 #include "modest-platform.h"
49 #include <modest-hbox-cell-renderer.h>
50 #include <modest-vbox-cell-renderer.h>
51
52 static void modest_header_view_class_init  (ModestHeaderViewClass *klass);
53 static void modest_header_view_init        (ModestHeaderView *obj);
54 static void modest_header_view_finalize    (GObject *obj);
55 static void modest_header_view_dispose     (GObject *obj);
56
57 static void          on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
58                                               GtkTreeViewColumn *column, gpointer userdata);
59
60 static gint          cmp_rows               (GtkTreeModel *tree_model,
61                                              GtkTreeIter *iter1,
62                                              GtkTreeIter *iter2,
63                                              gpointer user_data);
64
65 static gint          cmp_subject_rows       (GtkTreeModel *tree_model,
66                                              GtkTreeIter *iter1,
67                                              GtkTreeIter *iter2,
68                                              gpointer user_data);
69
70 static gboolean     filter_row             (GtkTreeModel *model,
71                                             GtkTreeIter *iter,
72                                             gpointer data);
73
74 static void          on_selection_changed   (GtkTreeSelection *sel, 
75                                              gpointer user_data);
76
77 static void          setup_drag_and_drop    (GtkTreeView *self);
78
79 static GtkTreePath * get_selected_row       (GtkTreeView *self, GtkTreeModel **model);
80
81 static gboolean      on_focus_in            (GtkWidget     *sef,
82                                              GdkEventFocus *event,
83                                              gpointer       user_data);
84
85 static void          folder_monitor_update  (TnyFolderObserver *self, 
86                                              TnyFolderChange *change);
87
88 static void          tny_folder_observer_init (TnyFolderObserverIface *klass);
89
90 static void          _clipboard_set_selected_data (ModestHeaderView *header_view, gboolean delete);
91
92 static void          _clear_hidding_filter (ModestHeaderView *header_view);
93
94
95 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
96 struct _ModestHeaderViewPrivate {
97         TnyFolder            *folder;
98         ModestHeaderViewStyle style;
99
100         TnyFolderMonitor     *monitor;
101         GMutex               *observers_lock;
102
103         /* not unref this object, its a singlenton */
104         ModestEmailClipboard *clipboard;
105
106         /* Filter tree model */
107         gchar **hidding_ids;
108         guint n_selected;
109
110         gint                  sort_colid[2][TNY_FOLDER_TYPE_NUM];
111         gint                  sort_type[2][TNY_FOLDER_TYPE_NUM];
112
113
114 };
115
116 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
117 struct _HeadersCountChangedHelper {
118         ModestHeaderView *self;
119         TnyFolderChange  *change;       
120 };
121
122
123 #define MODEST_HEADER_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
124                                                 MODEST_TYPE_HEADER_VIEW, \
125                                                 ModestHeaderViewPrivate))
126
127
128
129 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
130
131 enum {
132         HEADER_SELECTED_SIGNAL,
133         HEADER_ACTIVATED_SIGNAL,
134         ITEM_NOT_FOUND_SIGNAL,
135         MSG_COUNT_CHANGED_SIGNAL,
136         LAST_SIGNAL
137 };
138
139 /* globals */
140 static GObjectClass *parent_class = NULL;
141
142 /* uncomment the following if you have defined any signals */
143 static guint signals[LAST_SIGNAL] = {0};
144
145 GType
146 modest_header_view_get_type (void)
147 {
148         static GType my_type = 0;
149         if (!my_type) {
150                 static const GTypeInfo my_info = {
151                         sizeof(ModestHeaderViewClass),
152                         NULL,           /* base init */
153                         NULL,           /* base finalize */
154                         (GClassInitFunc) modest_header_view_class_init,
155                         NULL,           /* class finalize */
156                         NULL,           /* class data */
157                         sizeof(ModestHeaderView),
158                         1,              /* n_preallocs */
159                         (GInstanceInitFunc) modest_header_view_init,
160                         NULL
161                 };
162
163                 static const GInterfaceInfo tny_folder_observer_info = 
164                 {
165                         (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
166                         NULL,         /* interface_finalize */
167                         NULL          /* interface_data */
168                 };
169                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
170                                                   "ModestHeaderView",
171                                                   &my_info, 0);
172
173                 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
174                                              &tny_folder_observer_info);
175
176
177         }
178         return my_type;
179 }
180
181 static void
182 modest_header_view_class_init (ModestHeaderViewClass *klass)
183 {
184         GObjectClass *gobject_class;
185         gobject_class = (GObjectClass*) klass;
186
187         parent_class            = g_type_class_peek_parent (klass);
188         gobject_class->finalize = modest_header_view_finalize;
189         gobject_class->dispose = modest_header_view_dispose;
190         
191         g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
192         
193         signals[HEADER_SELECTED_SIGNAL] = 
194                 g_signal_new ("header_selected",
195                               G_TYPE_FROM_CLASS (gobject_class),
196                               G_SIGNAL_RUN_FIRST,
197                               G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
198                               NULL, NULL,
199                               g_cclosure_marshal_VOID__POINTER,
200                               G_TYPE_NONE, 1, G_TYPE_POINTER);
201
202         signals[HEADER_ACTIVATED_SIGNAL] = 
203                 g_signal_new ("header_activated",
204                               G_TYPE_FROM_CLASS (gobject_class),
205                               G_SIGNAL_RUN_FIRST,
206                               G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
207                               NULL, NULL,
208                               g_cclosure_marshal_VOID__POINTER,
209                               G_TYPE_NONE, 1, G_TYPE_POINTER);
210         
211         
212         signals[ITEM_NOT_FOUND_SIGNAL] = 
213                 g_signal_new ("item_not_found",
214                               G_TYPE_FROM_CLASS (gobject_class),
215                               G_SIGNAL_RUN_FIRST,
216                               G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
217                               NULL, NULL,
218                               g_cclosure_marshal_VOID__INT,
219                               G_TYPE_NONE, 1, G_TYPE_INT);
220
221         signals[MSG_COUNT_CHANGED_SIGNAL] =
222                 g_signal_new ("msg_count_changed",
223                               G_TYPE_FROM_CLASS (gobject_class),
224                               G_SIGNAL_RUN_FIRST,
225                               G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
226                               NULL, NULL,
227                               modest_marshal_VOID__POINTER_POINTER,
228                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
229 }
230
231 static void
232 tny_folder_observer_init (TnyFolderObserverIface *klass)
233 {
234         klass->update_func = folder_monitor_update;
235 }
236
237 static GtkTreeViewColumn*
238 get_new_column (const gchar *name, GtkCellRenderer *renderer,
239                 gboolean resizable, gint sort_col_id, gboolean show_as_text,
240                 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
241 {
242         GtkTreeViewColumn *column;
243
244         column =  gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
245         gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
246
247         gtk_tree_view_column_set_resizable (column, resizable);
248         if (resizable) 
249                 gtk_tree_view_column_set_expand (column, TRUE);
250         
251         if (show_as_text)
252                 gtk_tree_view_column_add_attribute (column, renderer, "text",
253                                                     sort_col_id);
254         if (sort_col_id >= 0)
255                 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
256
257         gtk_tree_view_column_set_sort_indicator (column, FALSE);
258         gtk_tree_view_column_set_reorderable (column, TRUE);
259         
260         if (cell_data_func)
261                 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
262                                                         user_data, NULL);
263         return column;
264 }
265
266
267 static void
268 remove_all_columns (ModestHeaderView *obj)
269 {
270         GList *columns, *cursor;
271         
272         columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
273
274         for (cursor = columns; cursor; cursor = cursor->next)
275                 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
276                                              GTK_TREE_VIEW_COLUMN(cursor->data));
277         g_list_free (columns);  
278 }
279
280 gboolean
281 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
282 {
283         GtkTreeModel *tree_filter, *sortable;
284         GtkTreeViewColumn *column=NULL;
285         GtkTreeSelection *selection = NULL;
286         GtkCellRenderer *renderer_msgtype,*renderer_header,
287                 *renderer_attach, *renderer_compact_date_or_status;
288         GtkCellRenderer *renderer_compact_header, *renderer_recpt_box, 
289                 *renderer_subject, *renderer_subject_box, *renderer_recpt,
290                 *renderer_priority;
291         ModestHeaderViewPrivate *priv;
292         GtkTreeViewColumn *compact_column = NULL;
293         const GList *cursor;
294         
295         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); 
296
297         /* FIXME: check whether these renderers need to be freed */
298         renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
299         renderer_attach  = gtk_cell_renderer_pixbuf_new ();
300         renderer_priority  = gtk_cell_renderer_pixbuf_new ();
301         renderer_header  = gtk_cell_renderer_text_new ();
302
303         renderer_compact_header = modest_vbox_cell_renderer_new ();
304         renderer_recpt_box = modest_hbox_cell_renderer_new ();
305         renderer_subject_box = modest_hbox_cell_renderer_new ();
306         renderer_recpt = gtk_cell_renderer_text_new ();
307         renderer_subject = gtk_cell_renderer_text_new ();
308         renderer_compact_date_or_status  = gtk_cell_renderer_text_new ();
309
310         modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
311         g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
312         modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
313         g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
314         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
315         g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
316         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_subject, TRUE);
317         g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", renderer_subject);
318         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
319         g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
320         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
321         g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
322         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
323         g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
324
325         g_object_set(G_OBJECT(renderer_header),
326                      "ellipsize", PANGO_ELLIPSIZE_END,
327                      NULL);
328         g_object_set (G_OBJECT (renderer_subject),
329                       "ellipsize", PANGO_ELLIPSIZE_END,
330                       NULL);
331         g_object_set (G_OBJECT (renderer_recpt),
332                       "ellipsize", PANGO_ELLIPSIZE_END,
333                       NULL);
334         g_object_set(G_OBJECT(renderer_compact_date_or_status),
335                      "xalign", 1.0,
336                      NULL);
337
338         gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 32);
339         gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 32);
340         
341         remove_all_columns (self);
342
343         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
344         gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
345 /*      sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
346         tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
347         sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
348
349         /* Add new columns */
350         for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
351                 ModestHeaderViewColumn col =
352                         (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
353                 
354                 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
355                         g_printerr ("modest: invalid column %d in column list\n", col);
356                         continue;
357                 }
358                 
359                 switch (col) {
360                         
361                 case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
362                         column = get_new_column (_("M"), renderer_msgtype, FALSE,
363                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
364                                                  FALSE,
365                                                  (GtkTreeCellDataFunc)_modest_header_view_msgtype_cell_data,
366                                                  NULL);
367                         gtk_tree_view_column_set_fixed_width (column, 45);
368                         break;
369
370                 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
371                         column = get_new_column (_("A"), renderer_attach, FALSE,
372                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
373                                                  FALSE,
374                                                  (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
375                                                  NULL);
376                         gtk_tree_view_column_set_fixed_width (column, 45);
377                         break;
378
379                         
380                 case MODEST_HEADER_VIEW_COLUMN_FROM:
381                         column = get_new_column (_("From"), renderer_header, TRUE,
382                                                  TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
383                                                  TRUE,
384                                                  (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
385                                                  GINT_TO_POINTER(TRUE));
386                         break;
387
388                 case MODEST_HEADER_VIEW_COLUMN_TO:
389                         column = get_new_column (_("To"), renderer_header, TRUE,
390                                                  TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
391                                                  TRUE,
392                                                  (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
393                                                  GINT_TO_POINTER(FALSE));
394                         break;
395                         
396                 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
397                         column = get_new_column (_("Header"), renderer_compact_header, TRUE,
398                                                      TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
399                                                      FALSE,
400                                                      (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
401                                                      GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
402                         compact_column = column;
403                         break;
404
405                 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
406                         column = get_new_column (_("Header"), renderer_compact_header, TRUE,
407                                                  TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
408                                                  FALSE,
409                                                  (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
410                                                  GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
411                                                                  MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
412                                                                  MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
413                         compact_column = column;
414                         break;
415
416                         
417                 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
418                         column = get_new_column (_("Subject"), renderer_header, TRUE,
419                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
420                                                  TRUE,
421                                                  (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
422                                                  NULL);
423                         break;
424                         
425                 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
426                         column = get_new_column (_("Received"), renderer_header, TRUE,
427                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
428                                                  TRUE,
429                                                  (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
430                                                  GINT_TO_POINTER(TRUE));
431                         break;
432                         
433                 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:  
434                         column = get_new_column (_("Sent"), renderer_header, TRUE,
435                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
436                                                  TRUE,
437                                                  (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
438                                                  GINT_TO_POINTER(FALSE));
439                         break;
440                         
441                 case MODEST_HEADER_VIEW_COLUMN_SIZE:
442                         column = get_new_column (_("Size"), renderer_header, TRUE,
443                                                  TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
444                                                  FALSE,
445                                                  (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
446                                                  NULL); 
447                         break;
448                 case MODEST_HEADER_VIEW_COLUMN_STATUS:
449                         column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
450                                                  TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
451                                                  FALSE,
452                                                  (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
453                                                  NULL); 
454                         break;
455
456                 default:
457                         g_return_val_if_reached(FALSE);
458                 }
459
460                 /* we keep the column id around */
461                 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
462                                    GINT_TO_POINTER(col));
463                 
464                 /* we need this ptr when sorting the rows */
465                 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
466                                    self);
467                 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);              
468         }               
469
470         if (sortable) {
471                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
472                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
473                                                  (GtkTreeIterCompareFunc) cmp_rows,
474                                                  compact_column, NULL);
475                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
476                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
477                                                  (GtkTreeIterCompareFunc) cmp_subject_rows,
478                                                  compact_column, NULL);
479         }
480
481
482         return TRUE;
483 }
484
485 static void
486 modest_header_view_init (ModestHeaderView *obj)
487 {
488         ModestHeaderViewPrivate *priv;
489         guint i, j;
490
491         priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj); 
492
493         priv->folder  = NULL;
494
495         priv->monitor        = NULL;
496         priv->observers_lock = g_mutex_new ();
497
498         priv->clipboard = modest_runtime_get_email_clipboard ();
499         priv->hidding_ids = NULL;
500         priv->n_selected = 0;
501
502         /* Sort parameters */
503         for (j=0; j < 2; j++) {
504                 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
505                         priv->sort_colid[j][i] = -1;
506                         priv->sort_type[j][i] = GTK_SORT_DESCENDING;
507                 }                       
508         }
509
510         setup_drag_and_drop (GTK_TREE_VIEW (obj));
511 }
512
513 static void
514 modest_header_view_dispose (GObject *obj)
515 {
516         ModestHeaderView        *self;
517         ModestHeaderViewPrivate *priv;
518         
519         self = MODEST_HEADER_VIEW(obj);
520         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
521
522         if (priv->folder) {
523                 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
524                 g_object_unref (G_OBJECT (priv->folder));
525                 priv->folder = NULL;
526         }
527
528         G_OBJECT_CLASS(parent_class)->dispose (obj);
529 }
530
531 static void
532 modest_header_view_finalize (GObject *obj)
533 {
534         ModestHeaderView        *self;
535         ModestHeaderViewPrivate *priv;
536         
537         self = MODEST_HEADER_VIEW(obj);
538         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
539
540         g_mutex_lock (priv->observers_lock);
541         if (priv->monitor) {
542                 tny_folder_monitor_stop (priv->monitor);
543                 g_object_unref (G_OBJECT (priv->monitor));
544         }
545         g_mutex_unlock (priv->observers_lock);
546         g_mutex_free (priv->observers_lock);
547
548         /* Clear hidding array created by cut operation */
549         _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
550
551         G_OBJECT_CLASS(parent_class)->finalize (obj);
552 }
553
554
555 GtkWidget*
556 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
557 {
558         GObject *obj;
559         GtkTreeSelection *sel;
560         ModestHeaderView *self;
561         ModestHeaderViewPrivate *priv;
562         
563         g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
564                               NULL);
565         
566         obj  = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
567         self = MODEST_HEADER_VIEW(obj);
568         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
569         
570         modest_header_view_set_style   (self, style);
571 /*      modest_header_view_set_folder (self, NULL, NULL, NULL); */
572
573         gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
574         gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
575         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
576         
577         gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
578                                       TRUE); /* alternating row colors */
579         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
580         
581         g_signal_connect (sel, "changed",
582                           G_CALLBACK(on_selection_changed), self);
583         
584         g_signal_connect (self, "row-activated",
585                           G_CALLBACK (on_header_row_activated), NULL);
586
587         g_signal_connect (self, "focus-in-event",
588                           G_CALLBACK(on_focus_in), NULL);
589         
590         return GTK_WIDGET(self);
591 }
592
593
594 guint
595 modest_header_view_count_selected_headers (ModestHeaderView *self)
596 {
597         GtkTreeSelection *sel;
598         guint selected_rows;
599
600         g_return_val_if_fail (self, 0);
601         
602         /* Get selection object and check selected rows count */
603         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
604         selected_rows = gtk_tree_selection_count_selected_rows (sel);
605         
606         return selected_rows;
607 }
608
609 gboolean
610 modest_header_view_has_selected_headers (ModestHeaderView *self)
611 {
612         GtkTreeSelection *sel;
613         gboolean empty;
614
615         g_return_val_if_fail (self, FALSE);
616         
617         /* Get selection object and check selected rows count */
618         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
619         empty = gtk_tree_selection_count_selected_rows (sel) == 0;
620         
621         return !empty;
622 }
623
624
625 TnyList * 
626 modest_header_view_get_selected_headers (ModestHeaderView *self)
627 {
628         GtkTreeSelection *sel;
629         ModestHeaderViewPrivate *priv;
630         TnyList *header_list = NULL;
631         TnyHeader *header;
632         GList *list, *tmp = NULL;
633         GtkTreeModel *tree_model = NULL;
634         GtkTreeIter iter;
635
636         g_return_val_if_fail (self, NULL);
637         
638         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
639
640         /* Get selected rows */
641         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
642         list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
643
644         if (list) {
645                 header_list = tny_simple_list_new();
646
647                 list = g_list_reverse (list);
648                 tmp = list;
649                 while (tmp) {                   
650                         /* get header from selection */
651                         gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
652                         gtk_tree_model_get (tree_model, &iter,
653                                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
654                                             &header, -1);
655                         /* Prepend to list */
656                         tny_list_prepend (header_list, G_OBJECT (header));
657                         g_object_unref (G_OBJECT (header));
658
659                         tmp = g_list_next (tmp);
660                 }
661                 /* Clean up*/
662                 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
663                 g_list_free (list);
664         }
665         return header_list;
666 }
667
668
669 /* scroll our list view so the selected item is visible */
670 static void
671 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
672 {
673 #ifdef MODEST_PLATFORM_GNOME 
674
675         GtkTreePath *selected_path;
676         GtkTreePath *start, *end;
677         
678         GtkTreeModel *model;
679         
680         model         = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
681         selected_path = gtk_tree_model_get_path (model, iter);
682
683         start = gtk_tree_path_new ();
684         end   = gtk_tree_path_new ();
685
686         gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
687
688         if (gtk_tree_path_compare (selected_path, start) < 0 ||
689             gtk_tree_path_compare (end, selected_path) < 0)
690                 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
691                                               selected_path, NULL, TRUE,
692                                               up ? 0.0 : 1.0,
693                                               up ? 0.0 : 1.0);
694         gtk_tree_path_free (selected_path);
695         gtk_tree_path_free (start);
696         gtk_tree_path_free (end);
697
698 #endif /* MODEST_PLATFORM_GNOME */
699 }
700
701
702 void 
703 modest_header_view_select_next (ModestHeaderView *self)
704 {
705         GtkTreeSelection *sel;
706         GtkTreeIter iter;
707         GtkTreeModel *model;
708         GtkTreePath *path;
709
710         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
711         path = get_selected_row (GTK_TREE_VIEW(self), &model);
712         if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
713                 /* Unselect previous path */
714                 gtk_tree_selection_unselect_path (sel, path);
715                 
716                 /* Move path down and selects new one  */
717                 if (gtk_tree_model_iter_next (model, &iter)) {
718                         gtk_tree_selection_select_iter (sel, &iter);
719                         scroll_to_selected (self, &iter, FALSE);        
720                 }
721                 gtk_tree_path_free(path);
722         }
723         
724 }
725
726 void 
727 modest_header_view_select_prev (ModestHeaderView *self)
728 {
729         GtkTreeSelection *sel;
730         GtkTreeIter iter;
731         GtkTreeModel *model;
732         GtkTreePath *path;
733
734         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
735         path = get_selected_row (GTK_TREE_VIEW(self), &model);
736         if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
737                 /* Unselect previous path */
738                 gtk_tree_selection_unselect_path (sel, path);
739
740                 /* Move path up */
741                 if (gtk_tree_path_prev (path)) {
742                         gtk_tree_model_get_iter (model, &iter, path);
743                         
744                         /* Select the new one */
745                         gtk_tree_selection_select_iter (sel, &iter);
746                         scroll_to_selected (self, &iter, TRUE); 
747
748                 }
749                 gtk_tree_path_free (path);
750         }
751 }
752
753 GList*
754 modest_header_view_get_columns (ModestHeaderView *self)
755 {
756         g_return_val_if_fail (self, FALSE);
757         return gtk_tree_view_get_columns (GTK_TREE_VIEW(self)); 
758 }
759
760
761 gboolean
762 modest_header_view_is_empty (ModestHeaderView *self)
763 {
764         g_return_val_if_fail (self, FALSE);
765         return FALSE; /* FIXME */
766 }
767
768
769 gboolean
770 modest_header_view_set_style (ModestHeaderView *self,
771                               ModestHeaderViewStyle style)
772 {
773         ModestHeaderViewPrivate *priv;
774         gboolean show_col_headers = FALSE;
775         ModestHeaderViewStyle old_style;
776         
777         g_return_val_if_fail (self, FALSE);
778         g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
779                               FALSE);
780
781         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
782         if (priv->style == style)
783                 return TRUE; /* nothing to do */
784         
785         switch (style) {
786         case MODEST_HEADER_VIEW_STYLE_DETAILS:
787                 show_col_headers = TRUE;
788                 break;
789         case MODEST_HEADER_VIEW_STYLE_TWOLINES:
790                 break;
791         default:
792                 g_return_val_if_reached (FALSE);
793         }
794         gtk_tree_view_set_headers_visible   (GTK_TREE_VIEW(self), show_col_headers);
795         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);    
796
797         old_style   = priv->style;
798         priv->style = style;
799
800         return TRUE;
801 }
802
803
804 ModestHeaderViewStyle
805 modest_header_view_get_style (ModestHeaderView *self)
806 {
807         g_return_val_if_fail (self, FALSE);
808         return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
809 }
810
811 /* 
812  * This function sets a sortable model in the header view. It's just
813  * used for developing purposes, because it only does a
814  * gtk_tree_view_set_model
815  */
816 static void
817 modest_header_view_set_model (GtkTreeView *header_view, GtkTreeModel *model)
818 {
819 /*      GtkTreeModel *old_model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view)); */
820 /*      if (old_model_sort && GTK_IS_TREE_MODEL_SORT (old_model_sort)) { */
821 /*              GtkTreeModel *old_model; */
822 /*              ModestHeaderViewPrivate *priv; */
823 /*              priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view); */
824 /*              old_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (old_model_sort)); */
825
826 /*              /\* Set new model *\/ */
827 /*              gtk_tree_view_set_model (header_view, model); */
828 /*      } else */
829         gtk_tree_view_set_model (header_view, model);
830 }
831
832 TnyFolder*
833 modest_header_view_get_folder (ModestHeaderView *self)
834 {
835         ModestHeaderViewPrivate *priv;
836         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
837
838         if (priv->folder)
839                 g_object_ref (priv->folder);
840
841         return priv->folder;
842 }
843
844 static void
845 modest_header_view_set_folder_intern (ModestHeaderView *self, TnyFolder *folder)
846 {
847         TnyFolderType type;
848         TnyList *headers;
849         ModestHeaderViewPrivate *priv;
850         GList *cols, *cursor;
851         GtkTreeModel *filter_model, *sortable; 
852         guint sort_colid;
853         GtkSortType sort_type;
854
855         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
856
857         headers = TNY_LIST (tny_gtk_header_list_model_new ());
858
859         tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
860                                               folder, FALSE);
861
862         /* Add IDLE observer (monitor) and another folder observer for
863            new messages (self) */
864         g_mutex_lock (priv->observers_lock);
865         if (priv->monitor) {
866                 tny_folder_monitor_stop (priv->monitor);
867                 g_object_unref (G_OBJECT (priv->monitor));
868         }
869         priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
870         tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
871         tny_folder_monitor_start (priv->monitor);
872         g_mutex_unlock (priv->observers_lock);
873
874         sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(headers));
875         g_object_unref (G_OBJECT (headers));
876
877         /* Create a tree model filter to hide and show rows for cut operations  */
878         filter_model = gtk_tree_model_filter_new (sortable, NULL);
879         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
880                                                 filter_row,
881                                                 self,
882                                                 NULL);
883         g_object_unref (G_OBJECT (sortable));
884
885         /* install our special sorting functions */
886         cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
887
888         /* Restore sort column id */
889         if (cols) {
890                 type  = modest_tny_folder_guess_folder_type (folder);
891                 sort_colid = modest_header_view_get_sort_column_id (self, type); 
892                 sort_type = modest_header_view_get_sort_type (self, type); 
893                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
894                                                       sort_colid,
895                                                       sort_type);
896                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
897                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
898                                                  (GtkTreeIterCompareFunc) cmp_rows,
899                                                  cols->data, NULL);
900                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
901                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
902                                                  (GtkTreeIterCompareFunc) cmp_subject_rows,
903                                                  cols->data, NULL);
904         }
905
906         /* Set new model */
907         modest_header_view_set_model (GTK_TREE_VIEW (self), filter_model);
908         g_object_unref (G_OBJECT (filter_model));
909 /*      modest_header_view_set_model (GTK_TREE_VIEW (self), sortable); */
910 /*      g_object_unref (G_OBJECT (sortable)); */
911
912         /* Free */
913         g_list_free (cols);
914 }
915
916 void
917 modest_header_view_sort_by_column_id (ModestHeaderView *self, 
918                                       guint sort_colid,
919                                       GtkSortType sort_type)
920 {
921         ModestHeaderViewPrivate *priv = NULL;
922         GtkTreeModel *tree_filter, *sortable = NULL; 
923         TnyFolderType type;
924
925         /* Get model and private data */
926         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);            
927         tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
928         sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
929 /*      sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
930         
931         /* Sort tree model */
932         type  = modest_tny_folder_guess_folder_type (priv->folder);
933         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
934                                               sort_colid,
935                                               sort_type);
936         /* Store new sort parameters */
937         modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
938
939         /* Save GConf parameters */
940 /*      modest_widget_memory_save (modest_runtime_get_conf(), */
941 /*                                 G_OBJECT(self), "header-view"); */
942         
943 }
944
945 void
946 modest_header_view_set_sort_params (ModestHeaderView *self, 
947                                     guint sort_colid, 
948                                     GtkSortType sort_type,
949                                     TnyFolderType type)
950 {
951         ModestHeaderViewPrivate *priv;
952         ModestHeaderViewStyle style;
953
954         style = modest_header_view_get_style   (self);
955         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
956
957         priv->sort_colid[style][type] = sort_colid;
958         priv->sort_type[style][type] = sort_type;
959 }
960
961 gint
962 modest_header_view_get_sort_column_id (ModestHeaderView *self, 
963                                        TnyFolderType type)
964 {
965         ModestHeaderViewPrivate *priv;
966         ModestHeaderViewStyle style;
967
968         style = modest_header_view_get_style   (self);
969         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
970
971         return priv->sort_colid[style][type];
972 }
973
974 GtkSortType
975 modest_header_view_get_sort_type (ModestHeaderView *self, 
976                                   TnyFolderType type)
977 {
978         ModestHeaderViewPrivate *priv;
979         ModestHeaderViewStyle style;
980
981         style = modest_header_view_get_style   (self);
982         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
983
984         return priv->sort_type[style][type];
985 }
986
987 typedef struct {
988         ModestHeaderView *header_view;
989         RefreshAsyncUserCallback cb;
990         gpointer user_data;
991 } SetFolderHelper;
992
993 static void
994 folder_refreshed_cb (ModestMailOperation *mail_op,
995                      TnyFolder *folder, 
996                      gpointer user_data)
997 {
998         ModestHeaderViewPrivate *priv;
999         SetFolderHelper *info;
1000  
1001         info = (SetFolderHelper*) user_data;
1002
1003         priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1004
1005         /* User callback */
1006         if (info->cb)
1007                 info->cb (mail_op, folder, info->user_data);
1008
1009         /* Start the folder count changes observer. We do not need it
1010            before the refresh. Note that the monitor could still be
1011            called for this refresh but now we know that the callback
1012            was previously called */
1013         g_mutex_lock (priv->observers_lock);
1014         tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1015         g_mutex_unlock (priv->observers_lock);
1016
1017         /* Frees */
1018         g_free (info);
1019 }
1020
1021 void
1022 modest_header_view_set_folder (ModestHeaderView *self, 
1023                                TnyFolder *folder,
1024                                RefreshAsyncUserCallback callback,
1025                                gpointer user_data)
1026 {
1027         ModestHeaderViewPrivate *priv;
1028         ModestWindowMgr *mgr = NULL;
1029         GObject *source = NULL;
1030         SetFolderHelper *info;
1031  
1032         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1033
1034         if (priv->folder) {
1035                 g_mutex_lock (priv->observers_lock);
1036                 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1037                 g_object_unref (priv->folder);
1038                 priv->folder = NULL;
1039                 g_mutex_unlock (priv->observers_lock);
1040         }
1041
1042         if (folder) {
1043                 ModestMailOperation *mail_op = NULL;
1044
1045                 /* Get main window to use it as source of mail operation */
1046                 mgr = modest_runtime_get_window_mgr ();
1047                 source = G_OBJECT (modest_window_mgr_get_main_window (modest_runtime_get_window_mgr ()));
1048
1049                 /* Set folder in the model */
1050                 modest_header_view_set_folder_intern (self, folder);
1051
1052                 /* Pick my reference. Nothing to do with the mail operation */
1053                 priv->folder = g_object_ref (folder);
1054
1055                 /* no message selected */
1056                 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1057
1058                 info = g_malloc0 (sizeof(SetFolderHelper));
1059                 info->header_view = self;
1060                 info->cb = callback;
1061                 info->user_data = user_data;
1062
1063                 /* Create the mail operation (source will be the parent widget) */
1064                 mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_TYPE_RECEIVE, source);
1065                 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1066                                                  mail_op);
1067
1068                 /* Refresh the folder asynchronously */
1069                 modest_mail_operation_refresh_folder (mail_op, 
1070                                                       folder,
1071                                                       folder_refreshed_cb,
1072                                                       info);
1073
1074                 /* Free */
1075                 g_object_unref (mail_op);
1076         } else {
1077                 g_mutex_lock (priv->observers_lock);
1078
1079                 if (priv->monitor) {
1080                         tny_folder_monitor_stop (priv->monitor);
1081                         g_object_unref (G_OBJECT (priv->monitor));
1082                         priv->monitor = NULL;
1083                 }
1084                 modest_header_view_set_model (GTK_TREE_VIEW (self), NULL); 
1085
1086                 g_mutex_unlock (priv->observers_lock);
1087         }
1088 }
1089
1090 static void
1091 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1092                          GtkTreeViewColumn *column, gpointer userdata)
1093 {
1094         ModestHeaderView *self = NULL;
1095         ModestHeaderViewPrivate *priv = NULL;
1096         GtkTreeIter iter;
1097         GtkTreeModel *model = NULL;
1098         TnyHeader *header = NULL;
1099         TnyHeaderFlags flags;
1100
1101         self = MODEST_HEADER_VIEW (treeview);
1102         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1103
1104         model = gtk_tree_view_get_model (treeview);     
1105         if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path))) 
1106                 goto frees;
1107
1108         /* get the first selected item */
1109         gtk_tree_model_get (model, &iter,
1110                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1111                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header, 
1112                             -1);
1113
1114         /* Dont open DELETED messages */
1115         if (flags & TNY_HEADER_FLAG_DELETED) {
1116                 modest_platform_information_banner (NULL, NULL, _("mcen_ib_message_already_deleted"));
1117                 goto frees;
1118         }
1119
1120         /* Emit signal */
1121         g_signal_emit (G_OBJECT(self), 
1122                        signals[HEADER_ACTIVATED_SIGNAL], 
1123                        0, header);
1124
1125         /* Free */
1126  frees:
1127         if (header != NULL) 
1128                 g_object_unref (G_OBJECT (header));     
1129
1130 }
1131
1132 static void
1133 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1134 {
1135         GtkTreeModel *model;
1136         TnyHeader *header;
1137         GtkTreePath *path = NULL;       
1138         GtkTreeIter iter;
1139         ModestHeaderView *self;
1140         ModestHeaderViewPrivate *priv;
1141         GList *selected = NULL;
1142         
1143         g_return_if_fail (sel);
1144         g_return_if_fail (user_data);
1145         
1146         self = MODEST_HEADER_VIEW (user_data);
1147         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);    
1148
1149         selected = gtk_tree_selection_get_selected_rows (sel, &model);
1150         if (selected != NULL) 
1151                 path = (GtkTreePath *) selected->data;
1152         if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1153                 return; /* msg was _un_selected */
1154
1155         gtk_tree_model_get (model, &iter,
1156                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1157                             &header, -1);
1158
1159         /* Emit signal */
1160         g_signal_emit (G_OBJECT(self), 
1161                        signals[HEADER_SELECTED_SIGNAL], 
1162                        0, header);
1163
1164         g_object_unref (G_OBJECT (header));
1165
1166         /* free all items in 'selected' */
1167         g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1168         g_list_free (selected);
1169 }
1170
1171
1172 /* PROTECTED method. It's useful when we want to force a given
1173    selection to reload a msg. For example if we have selected a header
1174    in offline mode, when Modest become online, we want to reload the
1175    message automatically without an user click over the header */
1176 void 
1177 _modest_header_view_change_selection (GtkTreeSelection *selection,
1178                                       gpointer user_data)
1179 {
1180         g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1181         g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1182
1183         on_selection_changed (selection, user_data);
1184 }
1185
1186 static gint compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1187 {
1188         p1 = p1 & TNY_HEADER_FLAG_PRIORITY;
1189         p2 = p2 & TNY_HEADER_FLAG_PRIORITY;
1190         if (p1 == 0) 
1191                 p1 = TNY_HEADER_FLAG_LOW_PRIORITY + 1;
1192         if (p2 == 0) 
1193                 p2 = TNY_HEADER_FLAG_LOW_PRIORITY + 1;
1194         return p1 - p2;
1195 }
1196
1197 static gint
1198 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1199           gpointer user_data)
1200 {
1201         gint col_id;
1202         gint t1, t2;
1203         gint val1, val2;
1204         gint cmp;
1205 /*      static int counter = 0; */
1206
1207         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1208 /*      col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1209         col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1210
1211         
1212         switch (col_id) {
1213         case TNY_HEADER_FLAG_ATTACHMENTS:
1214
1215                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1216                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1217                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1218                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1219
1220                 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1221                         (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1222
1223                 return cmp ? cmp : t1 - t2;
1224                 
1225         case TNY_HEADER_FLAG_PRIORITY:
1226                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1227                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1228                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1229                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1230
1231                 /* This is for making priority values respect the intuitive sort relationship 
1232                  * as HIGH is 11, LOW is 01, and we put NORMAL AS 10 (2) */
1233                 cmp =  compare_priorities (val1, val2);
1234
1235                 return cmp ? cmp : t1 - t2;
1236
1237         default:
1238                 return &iter1 - &iter2; /* oughhhh  */
1239         }
1240 }
1241
1242 static gint
1243 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1244                   gpointer user_data)
1245 {
1246         gint t1, t2;
1247         gchar *val1, *val2;
1248         gint cmp;
1249 /*      static int counter = 0; */
1250
1251         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1252
1253         gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1254                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1255         gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1256                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1257
1258         cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1259                                              val2 + modest_text_utils_get_subject_prefix_len(val2),
1260                                              TRUE);
1261         g_free (val1);
1262         g_free (val2);
1263         return cmp;
1264 }
1265
1266 /* Drag and drop stuff */
1267 static void
1268 drag_data_get_cb (GtkWidget *widget, GdkDragContext *context, 
1269                   GtkSelectionData *selection_data, 
1270                   guint info,  guint time, gpointer data)
1271 {
1272         GtkTreeModel *model = NULL;
1273         GtkTreeIter iter;
1274         GtkTreePath *source_row = NULL;
1275         GtkTreeSelection *sel = NULL;   
1276         
1277         source_row = get_selected_row (GTK_TREE_VIEW (widget), &model);
1278         if ((source_row == NULL) || (!gtk_tree_model_get_iter(model, &iter, source_row))) return;
1279
1280         switch (info) {
1281         case MODEST_HEADER_ROW:
1282                 gtk_tree_set_row_drag_data (selection_data, model, source_row);
1283                 break;
1284         case MODEST_MSG: {
1285                 TnyHeader *hdr;
1286                 gtk_tree_model_get (model, &iter,
1287                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &hdr,
1288                                     -1);
1289                 if (hdr) {
1290                         g_object_unref (G_OBJECT(hdr));
1291                 }
1292                 break;
1293         }
1294         default:
1295                 g_message ("%s: default switch case.", __FUNCTION__);
1296         }
1297
1298         /* Set focus on next header */
1299         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW (widget));
1300         gtk_tree_path_next (source_row);
1301         gtk_tree_selection_select_path (sel, source_row);
1302
1303         gtk_tree_path_free (source_row);
1304 }
1305
1306 /* Header view drag types */
1307 const GtkTargetEntry header_view_drag_types[] = {
1308         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, MODEST_HEADER_ROW },
1309         { "text/uri-list",      0,                   MODEST_MSG }, 
1310 };
1311
1312 static void
1313 setup_drag_and_drop (GtkTreeView *self)
1314 {
1315         gtk_drag_source_set (GTK_WIDGET (self),
1316                              GDK_BUTTON1_MASK,
1317                              header_view_drag_types,
1318                              G_N_ELEMENTS (header_view_drag_types),
1319                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1320
1321         g_signal_connect(G_OBJECT (self), "drag_data_get",
1322                          G_CALLBACK(drag_data_get_cb), NULL);
1323 }
1324
1325 static GtkTreePath *
1326 get_selected_row (GtkTreeView *self, GtkTreeModel **model) 
1327 {
1328         GtkTreePath *path = NULL;
1329         GtkTreeSelection *sel = NULL;   
1330         GList *rows = NULL;
1331
1332         sel   = gtk_tree_view_get_selection(self);
1333         rows = gtk_tree_selection_get_selected_rows (sel, model);
1334         
1335         if ((rows == NULL) || (g_list_length(rows) != 1))
1336                 goto frees;
1337
1338         path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1339         
1340
1341         /* Free */
1342  frees:
1343         g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1344         g_list_free(rows);
1345
1346         return path;
1347 }
1348
1349 /*
1350  * This function moves the tree view scroll to the current selected
1351  * row when the widget grabs the focus 
1352  */
1353 static gboolean 
1354 on_focus_in (GtkWidget     *self,
1355              GdkEventFocus *event,
1356              gpointer       user_data)
1357 {
1358         GtkTreeSelection *selection;
1359         GtkTreeModel *model;
1360         GList *selected = NULL;
1361         GtkTreePath *selected_path = NULL;
1362
1363         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1364         if (!model)
1365                 return FALSE;
1366
1367         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1368         /* If none selected yet, pick the first one */
1369         if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1370                 GtkTreeIter iter;
1371                 GtkTreePath *path;
1372
1373                 /* Return if the model is empty */
1374                 if (!gtk_tree_model_get_iter_first (model, &iter))
1375                         return FALSE;
1376
1377                 path = gtk_tree_model_get_path (model, &iter);
1378                 gtk_tree_selection_select_path (selection, path);
1379                 gtk_tree_path_free (path);
1380         }
1381
1382         /* Need to get the all the rows because is selection multiple */
1383         selected = gtk_tree_selection_get_selected_rows (selection, &model);
1384         if (selected == NULL) return FALSE;
1385         selected_path = (GtkTreePath *) selected->data;
1386
1387         /* Check if we need to scroll */
1388         #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1389         GtkTreePath *start_path = NULL;
1390         GtkTreePath *end_path = NULL;
1391         if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1392                                              &start_path,
1393                                              &end_path)) {
1394
1395                 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1396                     (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1397
1398                         /* Scroll to first path */
1399                         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1400                                                       selected_path,
1401                                                       NULL,
1402                                                       TRUE,
1403                                                       0.5,
1404                                                       0.0);
1405                 }
1406         }
1407         if (start_path)
1408                 gtk_tree_path_free (start_path);
1409         if (end_path)
1410                 gtk_tree_path_free (end_path);
1411
1412         #endif /* GTK_CHECK_VERSION */
1413
1414         /* Frees */     
1415         g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1416         g_list_free (selected);
1417
1418         return FALSE;
1419 }
1420
1421 static void
1422 idle_notify_headers_count_changed_destroy (gpointer data)
1423 {
1424         HeadersCountChangedHelper *helper = NULL;
1425
1426         g_return_if_fail (data != NULL);
1427         helper = (HeadersCountChangedHelper *) data; 
1428
1429         g_object_unref (helper->change);
1430         g_slice_free (HeadersCountChangedHelper, helper);
1431 }
1432
1433 static gboolean
1434 idle_notify_headers_count_changed (gpointer data)
1435 {
1436         TnyFolder *folder = NULL;
1437         ModestHeaderViewPrivate *priv = NULL;
1438         HeadersCountChangedHelper *helper = NULL;
1439
1440         g_return_val_if_fail (data != NULL, FALSE);
1441         helper = (HeadersCountChangedHelper *) data; 
1442         g_return_val_if_fail (MODEST_IS_HEADER_VIEW(helper->self), FALSE);
1443         g_return_val_if_fail (TNY_FOLDER_CHANGE(helper->change), FALSE);
1444
1445         folder = tny_folder_change_get_folder (helper->change);
1446
1447         priv = MODEST_HEADER_VIEW_GET_PRIVATE (helper->self);
1448
1449         g_mutex_lock (priv->observers_lock);
1450
1451         /* Emit signal to evaluate how headers changes affects to the window view  */
1452         g_signal_emit (G_OBJECT(helper->self), 
1453                        signals[MSG_COUNT_CHANGED_SIGNAL], 
1454                        0, folder, helper->change);
1455                 
1456         /* Added or removed headers, so data stored on cliboard are invalid  */
1457         if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1458             modest_email_clipboard_clear (priv->clipboard);
1459             
1460         g_mutex_unlock (priv->observers_lock);
1461
1462         return FALSE;
1463 }
1464
1465 static void
1466 folder_monitor_update (TnyFolderObserver *self, 
1467                        TnyFolderChange *change)
1468 {
1469         ModestHeaderViewPrivate *priv;
1470         TnyFolderChangeChanged changed;
1471         HeadersCountChangedHelper *helper = NULL;
1472
1473         changed = tny_folder_change_get_changed (change);
1474         
1475         /* Do not notify the observers if the folder of the header
1476            view has changed before this call to the observer
1477            happens */
1478         priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1479         if (tny_folder_change_get_folder (change) != priv->folder)
1480                 return;
1481
1482         /* Check folder count */
1483         if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1484             (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1485                 helper = g_slice_new0 (HeadersCountChangedHelper);
1486                 helper->self = MODEST_HEADER_VIEW(self);
1487                 helper->change = g_object_ref(change);
1488                 
1489                 g_idle_add_full (G_PRIORITY_DEFAULT, 
1490                                  idle_notify_headers_count_changed, 
1491                                  helper,
1492                                  idle_notify_headers_count_changed_destroy);
1493         }       
1494 }
1495
1496 void
1497 modest_header_view_clear (ModestHeaderView *self)
1498 {
1499         modest_header_view_set_folder (self, NULL, NULL, NULL);
1500 }
1501
1502 void 
1503 modest_header_view_copy_selection (ModestHeaderView *header_view)
1504 {
1505         /* Copy selection */
1506         _clipboard_set_selected_data (header_view, FALSE);
1507 }
1508
1509 void 
1510 modest_header_view_cut_selection (ModestHeaderView *header_view)
1511 {
1512         ModestHeaderViewPrivate *priv = NULL;
1513         const gchar **hidding = NULL;
1514         guint i, n_selected;
1515
1516         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1517         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1518
1519         /* Copy selection */
1520         _clipboard_set_selected_data (header_view, TRUE);
1521
1522         /* Get hidding ids */
1523         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected); 
1524         
1525         /* Clear hidding array created by previous cut operation */
1526         _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1527
1528         /* Copy hidding array */
1529         priv->n_selected = n_selected;
1530         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1531         for (i=0; i < n_selected; i++) 
1532                 priv->hidding_ids[i] = g_strdup(hidding[i]);            
1533
1534         /* Hide cut headers */
1535         modest_header_view_refilter (header_view);
1536 }
1537
1538
1539  
1540
1541 static void
1542 _clipboard_set_selected_data (ModestHeaderView *header_view,
1543                               gboolean delete)
1544 {
1545         ModestHeaderViewPrivate *priv = NULL;
1546         TnyList *headers = NULL;
1547
1548         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1549         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1550                 
1551         /* Set selected data on clipboard   */
1552         g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1553         headers = modest_header_view_get_selected_headers (header_view);
1554         modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1555
1556         /* Free */
1557         g_object_unref (headers);
1558 }
1559
1560
1561
1562 static gboolean
1563 filter_row (GtkTreeModel *model,
1564             GtkTreeIter *iter,
1565             gpointer user_data)
1566 {
1567         ModestHeaderViewPrivate *priv = NULL;
1568         TnyHeaderFlags flags;
1569         TnyHeader *header = NULL;
1570         guint i;
1571         gchar *id = NULL;
1572         gboolean visible = TRUE;
1573         gboolean found = FALSE;
1574         
1575         g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1576         priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1577
1578         /* Get header from model */
1579         gtk_tree_model_get (model, iter,
1580                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1581                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1582                             -1);
1583         
1584         /* Hide mark as deleted heders */
1585         if (flags & TNY_HEADER_FLAG_DELETED) {
1586                 visible = FALSE;
1587                 goto frees;
1588         }
1589
1590         /* If no data on clipboard, return always TRUE */
1591         if (modest_email_clipboard_cleared(priv->clipboard)) {
1592                 visible = TRUE;
1593                 goto frees;
1594         }               
1595
1596         /* Get message id from header (ensure is a valid id) */
1597         if (!header) return FALSE;
1598         id = g_strdup(tny_header_get_message_id (header));
1599         
1600         /* Check hiding */
1601         if (priv->hidding_ids != NULL) {
1602                 for (i=0; i < priv->n_selected && !found; i++)
1603                         if (priv->hidding_ids[i] != NULL && id != NULL)
1604                                 found = (!strcmp (priv->hidding_ids[i], id));
1605         
1606                 visible = !found;
1607         }
1608
1609         /* Free */
1610  frees:
1611         if (header)
1612                 g_object_unref (header);
1613         g_free(id);
1614
1615         return visible;
1616 }
1617
1618 static void
1619 _clear_hidding_filter (ModestHeaderView *header_view) 
1620 {
1621         ModestHeaderViewPrivate *priv;
1622         guint i;
1623         
1624         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view)); 
1625         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1626
1627         if (priv->hidding_ids != NULL) {
1628                 for (i=0; i < priv->n_selected; i++) 
1629                         g_free (priv->hidding_ids[i]);
1630                 g_free(priv->hidding_ids);
1631         }       
1632 }
1633
1634 void 
1635 modest_header_view_refilter (ModestHeaderView *header_view)
1636 {
1637         GtkTreeModel *model;
1638
1639         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1640
1641         /* Hide cut headers */
1642         model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
1643         if (GTK_IS_TREE_MODEL_FILTER (model))
1644                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1645 }