* src/widgets/modest-header-view.c:
[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 <tny-error.h>
36 #include <string.h>
37
38 #include <modest-header-view.h>
39 #include <modest-header-view-priv.h>
40 #include <modest-dnd.h>
41 #include <modest-tny-folder.h>
42 #include <modest-debug.h>
43 #include <modest-main-window.h>
44 #include <modest-ui-actions.h>
45 #include <modest-marshal.h>
46 #include <modest-text-utils.h>
47 #include <modest-icon-names.h>
48 #include <modest-runtime.h>
49 #include "modest-platform.h"
50 #include <modest-hbox-cell-renderer.h>
51 #include <modest-vbox-cell-renderer.h>
52
53 static void modest_header_view_class_init  (ModestHeaderViewClass *klass);
54 static void modest_header_view_init        (ModestHeaderView *obj);
55 static void modest_header_view_finalize    (GObject *obj);
56 static void modest_header_view_dispose     (GObject *obj);
57
58 static void          on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
59                                               GtkTreeViewColumn *column, gpointer userdata);
60
61 static gint          cmp_rows               (GtkTreeModel *tree_model,
62                                              GtkTreeIter *iter1,
63                                              GtkTreeIter *iter2,
64                                              gpointer user_data);
65
66 static gint          cmp_subject_rows       (GtkTreeModel *tree_model,
67                                              GtkTreeIter *iter1,
68                                              GtkTreeIter *iter2,
69                                              gpointer user_data);
70
71 static gboolean     filter_row             (GtkTreeModel *model,
72                                             GtkTreeIter *iter,
73                                             gpointer data);
74
75 static void         on_account_removed     (TnyAccountStore *self, 
76                                             TnyAccount *account,
77                                             gpointer user_data);
78
79 static void          on_selection_changed   (GtkTreeSelection *sel,
80                                              gpointer user_data);
81
82 static gboolean      on_button_press_event  (GtkWidget * self, GdkEventButton * event,
83                                              gpointer userdata);
84
85 static gboolean      on_button_release_event(GtkWidget * self, GdkEventButton * event,
86                                              gpointer userdata);
87
88 static void          setup_drag_and_drop    (GtkWidget *self);
89
90 static void          enable_drag_and_drop   (GtkWidget *self);
91
92 static void          disable_drag_and_drop  (GtkWidget *self);
93
94 static GtkTreePath * get_selected_row       (GtkTreeView *self, GtkTreeModel **model);
95
96 static gboolean      on_focus_in            (GtkWidget     *sef,
97                                              GdkEventFocus *event,
98                                              gpointer       user_data);
99
100 static gboolean      on_focus_out            (GtkWidget     *self,
101                                               GdkEventFocus *event,
102                                               gpointer       user_data);
103
104 static void          folder_monitor_update  (TnyFolderObserver *self, 
105                                              TnyFolderChange *change);
106
107 static void          tny_folder_observer_init (TnyFolderObserverIface *klass);
108
109 static void          _clipboard_set_selected_data (ModestHeaderView *header_view, gboolean delete);
110
111 static void          _clear_hidding_filter (ModestHeaderView *header_view);
112
113 static void modest_header_view_notify_observers(
114                 ModestHeaderView *header_view,
115                 GtkTreeModel *model,
116                 const gchar *tny_folder_id);
117
118 static gboolean modest_header_view_on_expose_event (GtkTreeView *header_view,
119                                                     GdkEventExpose *event,
120                                                     gpointer user_data);
121
122 typedef enum {
123         HEADER_VIEW_NON_EMPTY,
124         HEADER_VIEW_EMPTY,
125         HEADER_VIEW_INIT
126 } HeaderViewStatus;
127
128 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
129 struct _ModestHeaderViewPrivate {
130         TnyFolder            *folder;
131         ModestHeaderViewStyle style;
132
133         TnyFolderMonitor     *monitor;
134         GMutex               *observers_lock;
135
136         /*header-view-observer observer*/
137         GMutex *observer_list_lock;
138         GSList *observer_list;
139
140         /* not unref this object, its a singlenton */
141         ModestEmailClipboard *clipboard;
142
143         /* Filter tree model */
144         gchar **hidding_ids;
145         guint   n_selected;
146         GtkTreeRowReference *autoselect_reference;
147
148         gint    sort_colid[2][TNY_FOLDER_TYPE_NUM];
149         gint    sort_type[2][TNY_FOLDER_TYPE_NUM];
150
151         gulong  selection_changed_handler;
152         gulong  acc_removed_handler;
153
154         GList *drag_begin_cached_selected_rows;
155
156         HeaderViewStatus status;
157         guint status_timeout;
158         gboolean notify_status; /* whether or not the filter_row should notify about changes in the filtering */
159 };
160
161 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
162 struct _HeadersCountChangedHelper {
163         ModestHeaderView *self;
164         TnyFolderChange  *change;       
165 };
166
167
168 #define MODEST_HEADER_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
169                                                 MODEST_TYPE_HEADER_VIEW, \
170                                                 ModestHeaderViewPrivate))
171
172
173
174 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
175
176 enum {
177         HEADER_SELECTED_SIGNAL,
178         HEADER_ACTIVATED_SIGNAL,
179         ITEM_NOT_FOUND_SIGNAL,
180         MSG_COUNT_CHANGED_SIGNAL,
181         UPDATING_MSG_LIST_SIGNAL,
182         LAST_SIGNAL
183 };
184
185 /* globals */
186 static GObjectClass *parent_class = NULL;
187
188 /* uncomment the following if you have defined any signals */
189 static guint signals[LAST_SIGNAL] = {0};
190
191 GType
192 modest_header_view_get_type (void)
193 {
194         static GType my_type = 0;
195         if (!my_type) {
196                 static const GTypeInfo my_info = {
197                         sizeof(ModestHeaderViewClass),
198                         NULL,           /* base init */
199                         NULL,           /* base finalize */
200                         (GClassInitFunc) modest_header_view_class_init,
201                         NULL,           /* class finalize */
202                         NULL,           /* class data */
203                         sizeof(ModestHeaderView),
204                         1,              /* n_preallocs */
205                         (GInstanceInitFunc) modest_header_view_init,
206                         NULL
207                 };
208
209                 static const GInterfaceInfo tny_folder_observer_info = 
210                 {
211                         (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
212                         NULL,         /* interface_finalize */
213                         NULL          /* interface_data */
214                 };
215                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
216                                                   "ModestHeaderView",
217                                                   &my_info, 0);
218
219                 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
220                                              &tny_folder_observer_info);
221
222
223         }
224         return my_type;
225 }
226
227 static void
228 modest_header_view_class_init (ModestHeaderViewClass *klass)
229 {
230         GObjectClass *gobject_class;
231         gobject_class = (GObjectClass*) klass;
232
233         parent_class            = g_type_class_peek_parent (klass);
234         gobject_class->finalize = modest_header_view_finalize;
235         gobject_class->dispose = modest_header_view_dispose;
236         
237         g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
238         
239         signals[HEADER_SELECTED_SIGNAL] = 
240                 g_signal_new ("header_selected",
241                               G_TYPE_FROM_CLASS (gobject_class),
242                               G_SIGNAL_RUN_FIRST,
243                               G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
244                               NULL, NULL,
245                               g_cclosure_marshal_VOID__POINTER,
246                               G_TYPE_NONE, 1, G_TYPE_POINTER);
247
248         signals[HEADER_ACTIVATED_SIGNAL] = 
249                 g_signal_new ("header_activated",
250                               G_TYPE_FROM_CLASS (gobject_class),
251                               G_SIGNAL_RUN_FIRST,
252                               G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
253                               NULL, NULL,
254                               g_cclosure_marshal_VOID__POINTER,
255                               G_TYPE_NONE, 1, G_TYPE_POINTER);
256         
257         
258         signals[ITEM_NOT_FOUND_SIGNAL] = 
259                 g_signal_new ("item_not_found",
260                               G_TYPE_FROM_CLASS (gobject_class),
261                               G_SIGNAL_RUN_FIRST,
262                               G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
263                               NULL, NULL,
264                               g_cclosure_marshal_VOID__INT,
265                               G_TYPE_NONE, 1, G_TYPE_INT);
266
267         signals[MSG_COUNT_CHANGED_SIGNAL] =
268                 g_signal_new ("msg_count_changed",
269                               G_TYPE_FROM_CLASS (gobject_class),
270                               G_SIGNAL_RUN_FIRST,
271                               G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
272                               NULL, NULL,
273                               modest_marshal_VOID__POINTER_POINTER,
274                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
275
276         signals[UPDATING_MSG_LIST_SIGNAL] =
277                 g_signal_new ("updating-msg-list",
278                               G_TYPE_FROM_CLASS (gobject_class),
279                               G_SIGNAL_RUN_FIRST,
280                               G_STRUCT_OFFSET (ModestHeaderViewClass, updating_msg_list),
281                               NULL, NULL,
282                               g_cclosure_marshal_VOID__BOOLEAN,
283                               G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
284 }
285
286 static void
287 tny_folder_observer_init (TnyFolderObserverIface *klass)
288 {
289         klass->update = folder_monitor_update;
290 }
291
292 static GtkTreeViewColumn*
293 get_new_column (const gchar *name, GtkCellRenderer *renderer,
294                 gboolean resizable, gint sort_col_id, gboolean show_as_text,
295                 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
296 {
297         GtkTreeViewColumn *column;
298
299         column =  gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
300         gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
301
302         gtk_tree_view_column_set_resizable (column, resizable);
303         if (resizable) 
304                 gtk_tree_view_column_set_expand (column, TRUE);
305         
306         if (show_as_text)
307                 gtk_tree_view_column_add_attribute (column, renderer, "text",
308                                                     sort_col_id);
309         if (sort_col_id >= 0)
310                 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
311
312         gtk_tree_view_column_set_sort_indicator (column, FALSE);
313         gtk_tree_view_column_set_reorderable (column, TRUE);
314         
315         if (cell_data_func)
316                 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
317                                                         user_data, NULL);
318         return column;
319 }
320
321
322 static void
323 remove_all_columns (ModestHeaderView *obj)
324 {
325         GList *columns, *cursor;
326         
327         columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
328
329         for (cursor = columns; cursor; cursor = cursor->next)
330                 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
331                                              GTK_TREE_VIEW_COLUMN(cursor->data));
332         g_list_free (columns);  
333 }
334
335 gboolean
336 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
337 {
338         GtkTreeModel *tree_filter, *sortable;
339         GtkTreeViewColumn *column=NULL;
340         GtkTreeSelection *selection = NULL;
341         GtkCellRenderer *renderer_msgtype,*renderer_header,
342                 *renderer_attach, *renderer_compact_date_or_status;
343         GtkCellRenderer *renderer_compact_header, *renderer_recpt_box, 
344                 *renderer_subject, *renderer_subject_box, *renderer_recpt,
345                 *renderer_priority;
346         ModestHeaderViewPrivate *priv;
347         GtkTreeViewColumn *compact_column = NULL;
348         const GList *cursor;
349
350         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
351         g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, FALSE);
352         
353         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); 
354
355         /* FIXME: check whether these renderers need to be freed */
356         renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
357         renderer_attach  = gtk_cell_renderer_pixbuf_new ();
358         renderer_priority  = gtk_cell_renderer_pixbuf_new ();
359         renderer_header  = gtk_cell_renderer_text_new ();
360
361         renderer_compact_header = modest_vbox_cell_renderer_new ();
362         renderer_recpt_box = modest_hbox_cell_renderer_new ();
363         renderer_subject_box = modest_hbox_cell_renderer_new ();
364         renderer_recpt = gtk_cell_renderer_text_new ();
365         renderer_subject = gtk_cell_renderer_text_new ();
366         renderer_compact_date_or_status  = gtk_cell_renderer_text_new ();
367
368         modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
369         g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
370         modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
371         g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
372         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
373         g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
374         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_subject, TRUE);
375         g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", renderer_subject);
376         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
377         g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
378         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
379         g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
380         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
381         g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
382
383         g_object_set (G_OBJECT (renderer_subject_box), "yalign", 1.0, NULL);
384 #ifdef MODEST_PLATFORM_MAEMO
385         gtk_cell_renderer_set_fixed_size (renderer_subject_box, -1, 32);
386         gtk_cell_renderer_set_fixed_size (renderer_recpt_box, -1, 32);
387 #endif
388         g_object_set (G_OBJECT (renderer_recpt_box), "yalign", 0.0, NULL);
389         g_object_set(G_OBJECT(renderer_header),
390                      "ellipsize", PANGO_ELLIPSIZE_END,
391                      NULL);
392         g_object_set (G_OBJECT (renderer_subject),
393                       "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 1.0,
394                       NULL);
395         gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_subject), 1);
396         g_object_set (G_OBJECT (renderer_recpt),
397                       "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.0,
398                       NULL);
399         gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_recpt), 1);
400         g_object_set(G_OBJECT(renderer_compact_date_or_status),
401                      "xalign", 1.0, "yalign", 0.0,
402                      NULL);
403         gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_compact_date_or_status), 1);
404         g_object_set (G_OBJECT (renderer_priority),
405                       "yalign", 1.0, NULL);
406         g_object_set (G_OBJECT (renderer_attach),
407                       "yalign", 0.0, NULL);
408
409 #ifdef MODEST_PLATFORM_MAEMO
410         gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 26);
411         gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 26);
412         gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
413 #else
414         gtk_cell_renderer_set_fixed_size (renderer_attach, 16, 16);
415         gtk_cell_renderer_set_fixed_size (renderer_priority, 16, 16);
416         /* gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64); */
417 #endif
418         
419         remove_all_columns (self);
420
421         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
422         gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
423         tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
424         sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
425
426         /* Add new columns */
427         for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
428                 ModestHeaderViewColumn col =
429                         (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
430                 
431                 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
432                         g_printerr ("modest: invalid column %d in column list\n", col);
433                         continue;
434                 }
435                 
436                 switch (col) {
437                         
438                 case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
439                         column = get_new_column (_("M"), renderer_msgtype, FALSE,
440                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
441                                                  FALSE,
442                                                  (GtkTreeCellDataFunc)_modest_header_view_msgtype_cell_data,
443                                                  NULL);
444                         gtk_tree_view_column_set_fixed_width (column, 45);
445                         break;
446
447                 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
448                         column = get_new_column (_("A"), renderer_attach, FALSE,
449                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
450                                                  FALSE,
451                                                  (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
452                                                  NULL);
453                         gtk_tree_view_column_set_fixed_width (column, 45);
454                         break;
455
456                         
457                 case MODEST_HEADER_VIEW_COLUMN_FROM:
458                         column = get_new_column (_("From"), renderer_header, TRUE,
459                                                  TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
460                                                  TRUE,
461                                                  (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
462                                                  GINT_TO_POINTER(TRUE));
463                         break;
464
465                 case MODEST_HEADER_VIEW_COLUMN_TO:
466                         column = get_new_column (_("To"), renderer_header, TRUE,
467                                                  TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
468                                                  TRUE,
469                                                  (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
470                                                  GINT_TO_POINTER(FALSE));
471                         break;
472                         
473                 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
474                         column = get_new_column (_("Header"), renderer_compact_header, TRUE,
475                                                      TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
476                                                      FALSE,
477                                                      (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
478                                                      GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
479                         compact_column = column;
480                         break;
481
482                 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
483                         column = get_new_column (_("Header"), renderer_compact_header, TRUE,
484                                                  TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
485                                                  FALSE,
486                                                  (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
487                                                  GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
488                                                                  MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
489                                                                  MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
490                         compact_column = column;
491                         break;
492
493                         
494                 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
495                         column = get_new_column (_("Subject"), renderer_header, TRUE,
496                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
497                                                  TRUE,
498                                                  (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
499                                                  NULL);
500                         break;
501                         
502                 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
503                         column = get_new_column (_("Received"), renderer_header, TRUE,
504                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
505                                                  TRUE,
506                                                  (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
507                                                  GINT_TO_POINTER(TRUE));
508                         break;
509                         
510                 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:  
511                         column = get_new_column (_("Sent"), renderer_header, TRUE,
512                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
513                                                  TRUE,
514                                                  (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
515                                                  GINT_TO_POINTER(FALSE));
516                         break;
517                         
518                 case MODEST_HEADER_VIEW_COLUMN_SIZE:
519                         column = get_new_column (_("Size"), renderer_header, TRUE,
520                                                  TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
521                                                  FALSE,
522                                                  (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
523                                                  NULL); 
524                         break;
525                 case MODEST_HEADER_VIEW_COLUMN_STATUS:
526                         column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
527                                                  TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
528                                                  FALSE,
529                                                  (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
530                                                  NULL); 
531                         break;
532
533                 default:
534                         g_return_val_if_reached(FALSE);
535                 }
536
537                 /* we keep the column id around */
538                 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
539                                    GINT_TO_POINTER(col));
540                 
541                 /* we need this ptr when sorting the rows */
542                 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
543                                    self);
544                 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);              
545         }               
546
547         if (sortable) {
548                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
549                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
550                                                  (GtkTreeIterCompareFunc) cmp_rows,
551                                                  compact_column, NULL);
552                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
553                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
554                                                  (GtkTreeIterCompareFunc) cmp_subject_rows,
555                                                  compact_column, NULL);
556         }
557
558
559         return TRUE;
560 }
561
562 static void
563 modest_header_view_init (ModestHeaderView *obj)
564 {
565         ModestHeaderViewPrivate *priv;
566         guint i, j;
567
568         priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj); 
569
570         priv->folder  = NULL;
571
572         priv->monitor        = NULL;
573         priv->observers_lock = g_mutex_new ();
574         priv->autoselect_reference = NULL;
575
576         priv->status  = HEADER_VIEW_INIT;
577         priv->status_timeout = 0;
578         priv->notify_status = TRUE;
579
580         priv->observer_list_lock = g_mutex_new();
581         priv->observer_list = NULL;
582
583         priv->clipboard = modest_runtime_get_email_clipboard ();
584         priv->hidding_ids = NULL;
585         priv->n_selected = 0;
586         priv->selection_changed_handler = 0;
587         priv->acc_removed_handler = 0;
588
589         /* Sort parameters */
590         for (j=0; j < 2; j++) {
591                 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
592                         priv->sort_colid[j][i] = -1;
593                         priv->sort_type[j][i] = GTK_SORT_DESCENDING;
594                 }                       
595         }
596
597         setup_drag_and_drop (GTK_WIDGET(obj));
598 }
599
600 static void
601 modest_header_view_dispose (GObject *obj)
602 {
603         ModestHeaderView        *self;
604         ModestHeaderViewPrivate *priv;
605         GtkTreeSelection *sel;
606         
607         self = MODEST_HEADER_VIEW(obj);
608         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
609
610         /* Free in the dispose to avoid unref cycles */
611         if (priv->folder) {
612                 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
613                 g_object_unref (G_OBJECT (priv->folder));
614                 priv->folder = NULL;
615         }
616
617         /* We need to do this here in the dispose because the
618            selection won't exist when finalizing */
619         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
620         if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
621                 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
622                 priv->selection_changed_handler = 0;
623         }
624
625         G_OBJECT_CLASS(parent_class)->dispose (obj);
626 }
627
628 static void
629 modest_header_view_finalize (GObject *obj)
630 {
631         ModestHeaderView        *self;
632         ModestHeaderViewPrivate *priv;
633         
634         self = MODEST_HEADER_VIEW(obj);
635         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
636
637         if (g_signal_handler_is_connected (modest_runtime_get_account_store (), 
638                                            priv->acc_removed_handler)) {
639                 g_signal_handler_disconnect (modest_runtime_get_account_store (), 
640                                              priv->acc_removed_handler);
641         }
642
643         /* There is no need to lock because there should not be any
644          * reference to self now. */
645         g_mutex_free(priv->observer_list_lock);
646         g_slist_free(priv->observer_list);
647
648         g_mutex_lock (priv->observers_lock);
649         if (priv->monitor) {
650                 tny_folder_monitor_stop (priv->monitor);
651                 g_object_unref (G_OBJECT (priv->monitor));
652         }
653         g_mutex_unlock (priv->observers_lock);
654         g_mutex_free (priv->observers_lock);
655
656         /* Clear hidding array created by cut operation */
657         _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
658
659         if (priv->autoselect_reference != NULL) {
660                 gtk_tree_row_reference_free (priv->autoselect_reference);
661                 priv->autoselect_reference = NULL;
662         }
663
664         G_OBJECT_CLASS(parent_class)->finalize (obj);
665 }
666
667
668 GtkWidget*
669 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
670 {
671         GObject *obj;
672         GtkTreeSelection *sel;
673         ModestHeaderView *self;
674         ModestHeaderViewPrivate *priv;
675         
676         g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
677                               NULL);
678         
679         obj  = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
680         self = MODEST_HEADER_VIEW(obj);
681         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
682         
683         modest_header_view_set_style   (self, style);
684
685         gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
686         gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
687         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
688         
689         gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
690                                       TRUE); /* alternating row colors */
691
692         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self)); 
693         priv->selection_changed_handler =
694                 g_signal_connect_after (sel, "changed",
695                                         G_CALLBACK(on_selection_changed), self);
696         
697         g_signal_connect (self, "row-activated",
698                           G_CALLBACK (on_header_row_activated), NULL);
699
700         g_signal_connect (self, "focus-in-event",
701                           G_CALLBACK(on_focus_in), NULL);
702         g_signal_connect (self, "focus-out-event",
703                           G_CALLBACK(on_focus_out), NULL);
704
705         g_signal_connect (self, "button-press-event",
706                           G_CALLBACK(on_button_press_event), NULL);
707         g_signal_connect (self, "button-release-event",
708                           G_CALLBACK(on_button_release_event), NULL);
709         
710         priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
711                                                       "account_removed",
712                                                       G_CALLBACK (on_account_removed),
713                                                       self);
714
715         g_signal_connect (self, "expose-event",
716                         G_CALLBACK(modest_header_view_on_expose_event),
717                         NULL);
718
719         return GTK_WIDGET(self);
720 }
721
722
723 guint
724 modest_header_view_count_selected_headers (ModestHeaderView *self)
725 {
726         GtkTreeSelection *sel;
727         guint selected_rows;
728
729         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
730         
731         /* Get selection object and check selected rows count */
732         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
733         selected_rows = gtk_tree_selection_count_selected_rows (sel);
734         
735         return selected_rows;
736 }
737
738 gboolean
739 modest_header_view_has_selected_headers (ModestHeaderView *self)
740 {
741         GtkTreeSelection *sel;
742         gboolean empty;
743         
744         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
745         
746         /* Get selection object and check selected rows count */
747         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
748         empty = gtk_tree_selection_count_selected_rows (sel) == 0;
749         
750         return !empty;
751 }
752
753
754 TnyList * 
755 modest_header_view_get_selected_headers (ModestHeaderView *self)
756 {
757         GtkTreeSelection *sel;
758         ModestHeaderViewPrivate *priv;
759         TnyList *header_list = NULL;
760         TnyHeader *header;
761         GList *list, *tmp = NULL;
762         GtkTreeModel *tree_model = NULL;
763         GtkTreeIter iter;
764
765         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
766         
767         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
768
769         /* Get selected rows */
770         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
771         list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
772
773         if (list) {
774                 header_list = tny_simple_list_new();
775
776                 list = g_list_reverse (list);
777                 tmp = list;
778                 while (tmp) {                   
779                         /* get header from selection */
780                         gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
781                         gtk_tree_model_get (tree_model, &iter,
782                                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
783                                             &header, -1);
784                         /* Prepend to list */
785                         tny_list_prepend (header_list, G_OBJECT (header));
786                         g_object_unref (G_OBJECT (header));
787
788                         tmp = g_list_next (tmp);
789                 }
790                 /* Clean up*/
791                 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
792                 g_list_free (list);
793         }
794         return header_list;
795 }
796
797
798 /* scroll our list view so the selected item is visible */
799 static void
800 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
801 {
802 #ifdef MODEST_PLATFORM_GNOME 
803
804         GtkTreePath *selected_path;
805         GtkTreePath *start, *end;
806         
807         GtkTreeModel *model;
808         
809         model         = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
810         selected_path = gtk_tree_model_get_path (model, iter);
811
812         start = gtk_tree_path_new ();
813         end   = gtk_tree_path_new ();
814
815         gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
816
817         if (gtk_tree_path_compare (selected_path, start) < 0 ||
818             gtk_tree_path_compare (end, selected_path) < 0)
819                 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
820                                               selected_path, NULL, TRUE,
821                                               up ? 0.0 : 1.0,
822                                               up ? 0.0 : 1.0);
823         gtk_tree_path_free (selected_path);
824         gtk_tree_path_free (start);
825         gtk_tree_path_free (end);
826
827 #endif /* MODEST_PLATFORM_GNOME */
828 }
829
830
831 void 
832 modest_header_view_select_next (ModestHeaderView *self)
833 {
834         GtkTreeSelection *sel;
835         GtkTreeIter iter;
836         GtkTreeModel *model;
837         GtkTreePath *path;
838
839         g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
840
841         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
842         path = get_selected_row (GTK_TREE_VIEW(self), &model);
843         if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
844                 /* Unselect previous path */
845                 gtk_tree_selection_unselect_path (sel, path);
846                 
847                 /* Move path down and selects new one  */
848                 if (gtk_tree_model_iter_next (model, &iter)) {
849                         gtk_tree_selection_select_iter (sel, &iter);
850                         scroll_to_selected (self, &iter, FALSE);        
851                 }
852                 gtk_tree_path_free(path);
853         }
854         
855 }
856
857 void 
858 modest_header_view_select_prev (ModestHeaderView *self)
859 {
860         GtkTreeSelection *sel;
861         GtkTreeIter iter;
862         GtkTreeModel *model;
863         GtkTreePath *path;
864
865         g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
866
867         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
868         path = get_selected_row (GTK_TREE_VIEW(self), &model);
869         if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
870                 /* Unselect previous path */
871                 gtk_tree_selection_unselect_path (sel, path);
872
873                 /* Move path up */
874                 if (gtk_tree_path_prev (path)) {
875                         gtk_tree_model_get_iter (model, &iter, path);
876                         
877                         /* Select the new one */
878                         gtk_tree_selection_select_iter (sel, &iter);
879                         scroll_to_selected (self, &iter, TRUE); 
880
881                 }
882                 gtk_tree_path_free (path);
883         }
884 }
885
886 GList*
887 modest_header_view_get_columns (ModestHeaderView *self)
888 {       
889         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
890         
891         return gtk_tree_view_get_columns (GTK_TREE_VIEW(self)); 
892 }
893
894
895
896 gboolean
897 modest_header_view_set_style (ModestHeaderView *self,
898                               ModestHeaderViewStyle style)
899 {
900         ModestHeaderViewPrivate *priv;
901         gboolean show_col_headers = FALSE;
902         ModestHeaderViewStyle old_style;
903         
904         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
905         g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
906                               FALSE);
907         
908         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
909         if (priv->style == style)
910                 return TRUE; /* nothing to do */
911         
912         switch (style) {
913         case MODEST_HEADER_VIEW_STYLE_DETAILS:
914                 show_col_headers = TRUE;
915                 break;
916         case MODEST_HEADER_VIEW_STYLE_TWOLINES:
917                 break;
918         default:
919                 g_return_val_if_reached (FALSE);
920         }
921         gtk_tree_view_set_headers_visible   (GTK_TREE_VIEW(self), show_col_headers);
922         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);    
923
924         old_style   = priv->style;
925         priv->style = style;
926
927         return TRUE;
928 }
929
930
931 ModestHeaderViewStyle
932 modest_header_view_get_style (ModestHeaderView *self)
933 {
934         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
935
936         return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
937 }
938
939 /* This is used to automatically select the first header if the user
940  * has not selected any header yet.
941  */
942 static gboolean 
943 modest_header_view_on_expose_event(GtkTreeView *header_view,
944                                    GdkEventExpose *event,
945                                    gpointer user_data)
946 {
947         GtkTreeSelection *sel;
948         GtkTreeModel *model;
949         GtkTreeIter tree_iter;
950         ModestHeaderViewPrivate *priv;
951
952         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
953         model = gtk_tree_view_get_model(header_view);
954
955         if (!model)
956                 return FALSE;
957
958         sel = gtk_tree_view_get_selection(header_view);
959         if(!gtk_tree_selection_count_selected_rows(sel)) {
960                 if (gtk_tree_model_get_iter_first(model, &tree_iter)) {
961                         GtkTreePath *tree_iter_path;
962                         /* Prevent the widget from getting the focus
963                            when selecting the first item */
964                         tree_iter_path = gtk_tree_model_get_path (model, &tree_iter);
965                         g_object_set(header_view, "can-focus", FALSE, NULL);
966                         gtk_tree_selection_select_iter(sel, &tree_iter);
967                         gtk_tree_view_set_cursor (header_view, tree_iter_path, NULL, FALSE);
968                         g_object_set(header_view, "can-focus", TRUE, NULL);
969                         if (priv->autoselect_reference) {
970                                 gtk_tree_row_reference_free (priv->autoselect_reference);
971                         }
972                         priv->autoselect_reference = gtk_tree_row_reference_new (model, tree_iter_path);
973                         gtk_tree_path_free (tree_iter_path);
974                 }
975         } else {
976                 if (priv->autoselect_reference != NULL) {
977                         gboolean moved_selection = FALSE;
978                         GtkTreePath * last_path;
979                         if (gtk_tree_selection_count_selected_rows (sel) != 1) {
980                                 moved_selection = TRUE;
981                                 g_message ("MULTISELECTION %d -> MOVED", gtk_tree_selection_count_selected_rows (sel));
982                         } else {
983                                 GList *rows;
984
985                                 rows = gtk_tree_selection_get_selected_rows (sel, NULL);
986                                 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
987                                 g_message ("SELECTION PATH %s LAST PATH %s", gtk_tree_path_to_string (rows->data), gtk_tree_path_to_string (last_path));
988                                 if (gtk_tree_path_compare (last_path, (GtkTreePath *) rows->data) != 0)
989                                         moved_selection = TRUE;
990                                 g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
991                                 g_list_free (rows);
992                         }
993                         if (moved_selection) {
994                                 gtk_tree_row_reference_free (priv->autoselect_reference);
995                                 priv->autoselect_reference = NULL;
996                         } else {
997
998                                 if (gtk_tree_model_get_iter_first (model, &tree_iter)) {
999                                         GtkTreePath *current_path;
1000                                         current_path = gtk_tree_model_get_path (model, &tree_iter);
1001                                         last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1002                                         g_message ("CURRENT PATH %s LAST PATH %s", gtk_tree_path_to_string (current_path), gtk_tree_path_to_string (last_path));
1003                                         if (gtk_tree_path_compare (current_path, last_path) != 0) {
1004                                                 g_object_set(header_view, "can-focus", FALSE, NULL);
1005                                                 gtk_tree_selection_unselect_all (sel);
1006                                                 gtk_tree_selection_select_iter(sel, &tree_iter);
1007                                                 gtk_tree_view_set_cursor (header_view, current_path, NULL, FALSE);
1008                                                 g_object_set(header_view, "can-focus", TRUE, NULL);
1009                                                 gtk_tree_row_reference_free (priv->autoselect_reference);
1010                                                 priv->autoselect_reference = gtk_tree_row_reference_new (model, current_path);
1011                                         }
1012                                         gtk_tree_path_free (current_path);
1013                                         gtk_tree_path_free (last_path);
1014                                 }
1015                         }
1016                 }
1017         }
1018
1019         return FALSE;
1020 }
1021
1022 TnyFolder*
1023 modest_header_view_get_folder (ModestHeaderView *self)
1024 {
1025         ModestHeaderViewPrivate *priv;
1026
1027         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
1028
1029         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1030
1031         if (priv->folder)
1032                 g_object_ref (priv->folder);
1033
1034         return priv->folder;
1035 }
1036
1037 static void
1038 set_folder_intern_get_headers_async_cb (TnyFolder *folder, 
1039                                         gboolean cancelled, 
1040                                         TnyList *headers, 
1041                                         GError *err, 
1042                                         gpointer user_data)
1043 {
1044         ModestHeaderView *self;
1045         ModestHeaderViewPrivate *priv;
1046
1047         g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1048
1049         self = MODEST_HEADER_VIEW (user_data);
1050         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1051
1052         if (cancelled || err)
1053                 return;
1054
1055         /* Add IDLE observer (monitor) and another folder observer for
1056            new messages (self) */
1057         g_mutex_lock (priv->observers_lock);
1058         if (priv->monitor) {
1059                 tny_folder_monitor_stop (priv->monitor);
1060                 g_object_unref (G_OBJECT (priv->monitor));
1061         }
1062         priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
1063         tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
1064         tny_folder_monitor_start (priv->monitor);
1065         g_mutex_unlock (priv->observers_lock);
1066 }
1067
1068 static void
1069 modest_header_view_set_folder_intern (ModestHeaderView *self, TnyFolder *folder)
1070 {
1071         TnyFolderType type;
1072         TnyList *headers;
1073         ModestHeaderViewPrivate *priv;
1074         GList *cols, *cursor;
1075         GtkTreeModel *filter_model, *sortable; 
1076         guint sort_colid;
1077         GtkSortType sort_type;
1078
1079         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1080
1081         headers = TNY_LIST (tny_gtk_header_list_model_new ());
1082
1083         /* Start the monitor in the callback of the
1084            tny_gtk_header_list_model_set_folder call. It's crucial to
1085            do it there and not just after the call because we want the
1086            monitor to observe only the headers returned by the
1087            tny_folder_get_headers_async call that it's inside the
1088            tny_gtk_header_list_model_set_folder call. This way the
1089            monitor infrastructure could successfully cope with
1090            duplicates. For example if a tny_folder_add_msg_async is
1091            happening while tny_gtk_header_list_model_set_folder is
1092            invoked, then the first call could add a header that will
1093            be added again by tny_gtk_header_list_model_set_folder, so
1094            we'd end up with duplicate headers. sergio */
1095         tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
1096                                               folder, FALSE, 
1097                                               set_folder_intern_get_headers_async_cb, 
1098                                               NULL, self);
1099
1100         sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(headers));
1101         g_object_unref (G_OBJECT (headers));
1102
1103         /* Init filter_row function to examine empty status */
1104         priv->status  = HEADER_VIEW_INIT;
1105
1106         /* Create a tree model filter to hide and show rows for cut operations  */
1107         filter_model = gtk_tree_model_filter_new (sortable, NULL);
1108         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1109                                                 filter_row,
1110                                                 self,
1111                                                 NULL);
1112         g_object_unref (G_OBJECT (sortable));
1113
1114         /* install our special sorting functions */
1115         cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1116
1117         /* Restore sort column id */
1118         if (cols) {
1119                 type  = modest_tny_folder_guess_folder_type (folder);
1120                 if (type == TNY_FOLDER_TYPE_INVALID)
1121                         g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1122                 
1123                 sort_colid = modest_header_view_get_sort_column_id (self, type); 
1124                 sort_type = modest_header_view_get_sort_type (self, type); 
1125                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1126                                                       sort_colid,
1127                                                       sort_type);
1128                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1129                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1130                                                  (GtkTreeIterCompareFunc) cmp_rows,
1131                                                  cols->data, NULL);
1132                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1133                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1134                                                  (GtkTreeIterCompareFunc) cmp_subject_rows,
1135                                                  cols->data, NULL);
1136         }
1137
1138         /* Set new model */
1139         gtk_tree_view_set_model (GTK_TREE_VIEW (self), filter_model);
1140         modest_header_view_notify_observers(self, GTK_TREE_MODEL(filter_model),
1141                         tny_folder_get_id(folder));
1142         g_object_unref (G_OBJECT (filter_model));
1143
1144         /* Free */
1145         g_list_free (cols);
1146 }
1147
1148 void
1149 modest_header_view_sort_by_column_id (ModestHeaderView *self, 
1150                                       guint sort_colid,
1151                                       GtkSortType sort_type)
1152 {
1153         ModestHeaderViewPrivate *priv = NULL;
1154         GtkTreeModel *tree_filter, *sortable = NULL; 
1155         TnyFolderType type;
1156
1157         g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1158         g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1159         
1160         /* Get model and private data */
1161         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);            
1162         tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1163         sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
1164 /*      sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
1165         
1166         /* Sort tree model */
1167         type  = modest_tny_folder_guess_folder_type (priv->folder);
1168         if (type == TNY_FOLDER_TYPE_INVALID)
1169                 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1170         else {
1171                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1172                                               sort_colid,
1173                                               sort_type);
1174                 /* Store new sort parameters */
1175                 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1176         }       
1177 }
1178
1179 void
1180 modest_header_view_set_sort_params (ModestHeaderView *self, 
1181                                     guint sort_colid, 
1182                                     GtkSortType sort_type,
1183                                     TnyFolderType type)
1184 {
1185         ModestHeaderViewPrivate *priv;
1186         ModestHeaderViewStyle style;
1187         
1188         g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1189         g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1190         g_return_if_fail (type != TNY_FOLDER_TYPE_INVALID);
1191         
1192         style = modest_header_view_get_style   (self);
1193         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1194
1195         priv->sort_colid[style][type] = sort_colid;
1196         priv->sort_type[style][type] = sort_type;
1197 }
1198
1199 gint
1200 modest_header_view_get_sort_column_id (ModestHeaderView *self, 
1201                                        TnyFolderType type)
1202 {
1203         ModestHeaderViewPrivate *priv;
1204         ModestHeaderViewStyle style;
1205
1206         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
1207         g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, 0);
1208         
1209         style = modest_header_view_get_style   (self);
1210         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1211
1212         return priv->sort_colid[style][type];
1213 }
1214
1215 GtkSortType
1216 modest_header_view_get_sort_type (ModestHeaderView *self, 
1217                                   TnyFolderType type)
1218 {
1219         ModestHeaderViewPrivate *priv;
1220         ModestHeaderViewStyle style;
1221         
1222         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), GTK_SORT_DESCENDING);
1223         g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, GTK_SORT_DESCENDING);
1224         
1225         style = modest_header_view_get_style   (self);
1226         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1227
1228         return priv->sort_type[style][type];
1229 }
1230
1231 typedef struct {
1232         ModestHeaderView *header_view;
1233         RefreshAsyncUserCallback cb;
1234         gpointer user_data;
1235 } SetFolderHelper;
1236
1237 static void
1238 folder_refreshed_cb (ModestMailOperation *mail_op,
1239                      TnyFolder *folder,
1240                      gpointer user_data)
1241 {
1242         ModestHeaderViewPrivate *priv;
1243         SetFolderHelper *info;
1244  
1245         info = (SetFolderHelper*) user_data;
1246
1247         priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1248
1249         /* User callback */
1250         if (info->cb)
1251                 info->cb (mail_op, folder, info->user_data);
1252
1253         /* Start the folder count changes observer. We do not need it
1254            before the refresh. Note that the monitor could still be
1255            called for this refresh but now we know that the callback
1256            was previously called */
1257         g_mutex_lock (priv->observers_lock);
1258         tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1259         g_mutex_unlock (priv->observers_lock);
1260
1261         /* Notify the observers that the update is over */
1262         g_signal_emit (G_OBJECT (info->header_view), 
1263                        signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1264
1265         /* Allow filtering notifications from now on if the current
1266            folder is still the same (if not then the user has selected
1267            another one to refresh, we should wait until that refresh
1268            finishes) */
1269         if (priv->folder == folder)
1270                 priv->notify_status = TRUE;
1271
1272         /* Frees */
1273         g_object_unref (info->header_view);
1274         g_free (info);
1275 }
1276
1277 static void
1278 refresh_folder_error_handler (ModestMailOperation *mail_op, 
1279                               gpointer user_data)
1280 {
1281         const GError *error = modest_mail_operation_get_error (mail_op);
1282
1283         if (error->code == TNY_SYSTEM_ERROR_MEMORY ||
1284             error->code == TNY_IO_ERROR_WRITE ||
1285             error->code == TNY_IO_ERROR_READ) {
1286                 ModestMailOperationStatus st = modest_mail_operation_get_status (mail_op);
1287                 /* If the mail op has been cancelled then it's not an error: don't show any message */
1288                 if (st != MODEST_MAIL_OPERATION_STATUS_CANCELED) {
1289                         modest_platform_information_banner (NULL, NULL,
1290                                                             dgettext("ke-recv",
1291                                                                      "cerm_device_memory_full"));
1292                 }
1293         }
1294 }
1295
1296 void
1297 modest_header_view_set_folder (ModestHeaderView *self, 
1298                                TnyFolder *folder,
1299                                gboolean refresh,
1300                                RefreshAsyncUserCallback callback,
1301                                gpointer user_data)
1302 {
1303         ModestHeaderViewPrivate *priv;
1304         ModestWindow *main_win;
1305         
1306         g_return_if_fail (self);
1307
1308         priv =     MODEST_HEADER_VIEW_GET_PRIVATE(self);
1309
1310         main_win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr (),
1311                                                       FALSE); /* don't create */
1312         if (!main_win) {
1313                 g_warning ("%s: BUG: no main window", __FUNCTION__);
1314                 return;
1315         }
1316                                                       
1317         if (priv->folder) {
1318                 if (priv->status_timeout) {
1319                         g_source_remove (priv->status_timeout);
1320                         priv->status_timeout = 0;
1321                 }
1322
1323                 g_mutex_lock (priv->observers_lock);
1324                 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1325                 g_object_unref (priv->folder);
1326                 priv->folder = NULL;
1327                 g_mutex_unlock (priv->observers_lock);
1328         }
1329
1330         if (folder) {
1331                 GtkTreeSelection *selection;
1332                 SetFolderHelper *info;
1333                 ModestMailOperation *mail_op = NULL;
1334
1335                 /* Set folder in the model */
1336                 modest_header_view_set_folder_intern (self, folder);
1337                 
1338                 /* Pick my reference. Nothing to do with the mail operation */
1339                 priv->folder = g_object_ref (folder);
1340
1341                 /* Do not notify about filterings until the refresh finishes */
1342                 priv->notify_status = FALSE;
1343
1344                 /* Clear the selection if exists */
1345                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1346                 gtk_tree_selection_unselect_all(selection);
1347                 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1348
1349                 /* Notify the observers that the update begins */
1350                 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL], 
1351                                0, TRUE, NULL);
1352
1353                 /* create the helper */
1354                 info = g_malloc0 (sizeof (SetFolderHelper));
1355                 info->header_view = g_object_ref (self);
1356                 info->cb = callback;
1357                 info->user_data = user_data;
1358
1359                 /* Create the mail operation (source will be the parent widget) */
1360                 mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(main_win),
1361                                                                          refresh_folder_error_handler,
1362                                                                          NULL, NULL);
1363                 if (refresh) {                  
1364                         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1365                                                          mail_op);
1366                         
1367                         /* Refresh the folder asynchronously */
1368                         modest_mail_operation_refresh_folder (mail_op,
1369                                                               folder,
1370                                                               folder_refreshed_cb,
1371                                                               info);
1372                 } else {
1373                         folder_refreshed_cb (mail_op, folder, info);
1374                 }
1375                 /* Free */
1376                 g_object_unref (mail_op);
1377         } else {
1378                 g_mutex_lock (priv->observers_lock);
1379
1380                 if (priv->monitor) {
1381                         tny_folder_monitor_stop (priv->monitor);
1382                         g_object_unref (G_OBJECT (priv->monitor));
1383                         priv->monitor = NULL;
1384                 }
1385                 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL); 
1386
1387                 modest_header_view_notify_observers(self, NULL, NULL);
1388
1389                 g_mutex_unlock (priv->observers_lock);
1390
1391                 /* Notify the observers that the update is over */
1392                 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL], 
1393                                0, FALSE, NULL);
1394         }
1395 }
1396
1397 static void
1398 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1399                          GtkTreeViewColumn *column, gpointer userdata)
1400 {
1401         ModestHeaderView *self = NULL;
1402         ModestHeaderViewPrivate *priv = NULL;
1403         GtkTreeIter iter;
1404         GtkTreeModel *model = NULL;
1405         TnyHeader *header = NULL;
1406         TnyHeaderFlags flags;
1407
1408         self = MODEST_HEADER_VIEW (treeview);
1409         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1410
1411         model = gtk_tree_view_get_model (treeview);     
1412         if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path))) 
1413                 goto frees;
1414
1415         /* get the first selected item */
1416         gtk_tree_model_get (model, &iter,
1417                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1418                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header, 
1419                             -1);
1420
1421         /* Dont open DELETED messages */
1422         if (flags & TNY_HEADER_FLAG_DELETED) {
1423                 GtkWidget *win;
1424                 gchar *msg;
1425                 win = gtk_widget_get_ancestor (GTK_WIDGET (treeview), GTK_TYPE_WINDOW);
1426                 msg = modest_ui_actions_get_msg_already_deleted_error_msg (MODEST_WINDOW (win));
1427                 modest_platform_information_banner (NULL, NULL, msg);
1428                 g_free (msg);
1429                 goto frees;
1430         }
1431
1432         /* Emit signal */
1433         g_signal_emit (G_OBJECT(self), 
1434                        signals[HEADER_ACTIVATED_SIGNAL], 
1435                        0, header);
1436
1437         /* Free */
1438  frees:
1439         if (header != NULL) 
1440                 g_object_unref (G_OBJECT (header));     
1441
1442 }
1443
1444 static void
1445 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1446 {
1447         GtkTreeModel *model;
1448         TnyHeader *header = NULL;
1449         GtkTreePath *path = NULL;       
1450         GtkTreeIter iter;
1451         ModestHeaderView *self;
1452         ModestHeaderViewPrivate *priv;
1453         GList *selected = NULL;
1454         
1455         g_return_if_fail (sel);
1456         g_return_if_fail (user_data);
1457         
1458         self = MODEST_HEADER_VIEW (user_data);
1459         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);    
1460
1461         selected = gtk_tree_selection_get_selected_rows (sel, &model);
1462         if (selected != NULL) 
1463                 path = (GtkTreePath *) selected->data;
1464         if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1465                 return; /* msg was _un_selected */
1466
1467         gtk_tree_model_get (model, &iter,
1468                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1469                             &header, -1);
1470
1471         /* Emit signal */
1472         g_signal_emit (G_OBJECT(self), 
1473                        signals[HEADER_SELECTED_SIGNAL], 
1474                        0, header);
1475
1476         g_object_unref (G_OBJECT (header));
1477
1478         /* free all items in 'selected' */
1479         g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1480         g_list_free (selected);
1481 }
1482
1483
1484 /* PROTECTED method. It's useful when we want to force a given
1485    selection to reload a msg. For example if we have selected a header
1486    in offline mode, when Modest become online, we want to reload the
1487    message automatically without an user click over the header */
1488 void 
1489 _modest_header_view_change_selection (GtkTreeSelection *selection,
1490                                       gpointer user_data)
1491 {
1492         g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1493         g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1494         
1495         on_selection_changed (selection, user_data);
1496 }
1497
1498 static gint
1499 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1500 {
1501         /* HH, LL, NN */
1502         if (p1 == p2)
1503                 return 0;
1504
1505         /* HL HN */
1506         if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1507                 return 1;
1508
1509         /* LH LN */
1510         if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1511                 return -1;
1512
1513         /* NH */
1514         if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1515                 return -1;
1516
1517         /* NL */
1518         return 1;
1519 }
1520
1521 static gint
1522 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1523           gpointer user_data)
1524 {
1525         gint col_id;
1526         gint t1, t2;
1527         gint val1, val2;
1528         gint cmp;
1529 /*      static int counter = 0; */
1530
1531         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1532 /*      col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1533         col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1534
1535         
1536         switch (col_id) {
1537         case TNY_HEADER_FLAG_ATTACHMENTS:
1538
1539                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1540                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1541                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1542                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1543
1544                 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1545                         (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1546
1547                 return cmp ? cmp : t1 - t2;
1548                 
1549         case TNY_HEADER_FLAG_PRIORITY_MASK: {
1550                 TnyHeader *header1 = NULL, *header2 = NULL;
1551
1552                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1553                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1554                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1555                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1556
1557                 /* This is for making priority values respect the intuitive sort relationship 
1558                  * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1559
1560                 if (header1 && header2) {
1561                         cmp =  compare_priorities (tny_header_get_priority (header1), 
1562                                 tny_header_get_priority (header2));
1563                         g_object_unref (header1);
1564                         g_object_unref (header2);
1565
1566                         return cmp ? cmp : t1 - t2;
1567                 }
1568
1569                 return t1 - t2;
1570         }
1571         default:
1572                 return &iter1 - &iter2; /* oughhhh  */
1573         }
1574 }
1575
1576 static gint
1577 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1578                   gpointer user_data)
1579 {
1580         gint t1, t2;
1581         gchar *val1, *val2;
1582         gint cmp;
1583 /*      static int counter = 0; */
1584
1585         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1586
1587         gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1588                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1589         gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1590                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1591
1592         cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1593                                              val2 + modest_text_utils_get_subject_prefix_len(val2),
1594                                              TRUE);
1595         g_free (val1);
1596         g_free (val2);
1597         return cmp;
1598 }
1599
1600 /* Drag and drop stuff */
1601 static void
1602 drag_data_get_cb (GtkWidget *widget, 
1603                   GdkDragContext *context, 
1604                   GtkSelectionData *selection_data, 
1605                   guint info,  
1606                   guint time, 
1607                   gpointer data)
1608 {
1609         ModestHeaderView *self = NULL;
1610         ModestHeaderViewPrivate *priv = NULL;
1611
1612         self = MODEST_HEADER_VIEW (widget);
1613         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1614
1615         /* Set the data. Do not use the current selection because it
1616            could be different than the selection at the beginning of
1617            the d&d */
1618         modest_dnd_selection_data_set_paths (selection_data, 
1619                                              priv->drag_begin_cached_selected_rows);
1620 }
1621
1622 /**
1623  * We're caching the selected rows at the beginning because the
1624  * selection could change between drag-begin and drag-data-get, for
1625  * example if we have a set of rows already selected, and then we
1626  * click in one of them (without SHIFT key pressed) and begin a drag,
1627  * the selection at that moment contains all the selected lines, but
1628  * after dropping the selection, the release event provokes that only
1629  * the row used to begin the drag is selected, so at the end the
1630  * drag&drop affects only one rows instead of all the selected ones.
1631  *
1632  */
1633 static void
1634 drag_begin_cb (GtkWidget *widget, 
1635                GdkDragContext *context, 
1636                gpointer data)
1637 {
1638         ModestHeaderView *self = NULL;
1639         ModestHeaderViewPrivate *priv = NULL;
1640         GtkTreeSelection *selection;
1641
1642         self = MODEST_HEADER_VIEW (widget);
1643         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1644
1645         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1646         priv->drag_begin_cached_selected_rows = 
1647                 gtk_tree_selection_get_selected_rows (selection, NULL);
1648 }
1649
1650 /**
1651  * We use the drag-end signal to clear the cached selection, we use
1652  * this because this allways happens, whether or not the d&d was a
1653  * success
1654  */
1655 static void
1656 drag_end_cb (GtkWidget *widget, 
1657              GdkDragContext *dc, 
1658              gpointer data)
1659 {
1660         ModestHeaderView *self = NULL;
1661         ModestHeaderViewPrivate *priv = NULL;
1662
1663         self = MODEST_HEADER_VIEW (widget);
1664         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1665
1666         /* Free cached data */
1667         g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1668         g_list_free (priv->drag_begin_cached_selected_rows);
1669         priv->drag_begin_cached_selected_rows = NULL;
1670 }
1671
1672 /* Header view drag types */
1673 const GtkTargetEntry header_view_drag_types[] = {
1674         { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1675 };
1676
1677 static void
1678 enable_drag_and_drop (GtkWidget *self)
1679 {
1680         gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1681                              header_view_drag_types,
1682                              G_N_ELEMENTS (header_view_drag_types),
1683                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1684 }
1685
1686 static void
1687 disable_drag_and_drop (GtkWidget *self)
1688 {
1689         gtk_drag_source_unset (self);
1690 }
1691
1692 static void
1693 setup_drag_and_drop (GtkWidget *self)
1694 {
1695         enable_drag_and_drop(self);
1696         g_signal_connect(G_OBJECT (self), "drag_data_get",
1697                          G_CALLBACK(drag_data_get_cb), NULL);
1698
1699         g_signal_connect(G_OBJECT (self), "drag_begin",
1700                          G_CALLBACK(drag_begin_cb), NULL);
1701
1702         g_signal_connect(G_OBJECT (self), "drag_end",
1703                          G_CALLBACK(drag_end_cb), NULL);
1704 }
1705
1706 static GtkTreePath *
1707 get_selected_row (GtkTreeView *self, GtkTreeModel **model) 
1708 {
1709         GtkTreePath *path = NULL;
1710         GtkTreeSelection *sel = NULL;   
1711         GList *rows = NULL;
1712
1713         sel   = gtk_tree_view_get_selection(self);
1714         rows = gtk_tree_selection_get_selected_rows (sel, model);
1715         
1716         if ((rows == NULL) || (g_list_length(rows) != 1))
1717                 goto frees;
1718
1719         path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1720         
1721
1722         /* Free */
1723  frees:
1724         g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1725         g_list_free(rows);
1726
1727         return path;
1728 }
1729
1730 /*
1731  * This function moves the tree view scroll to the current selected
1732  * row when the widget grabs the focus 
1733  */
1734 static gboolean 
1735 on_focus_in (GtkWidget     *self,
1736              GdkEventFocus *event,
1737              gpointer       user_data)
1738 {
1739         GtkTreeSelection *selection;
1740         GtkTreeModel *model;
1741         GList *selected = NULL;
1742         GtkTreePath *selected_path = NULL;
1743
1744         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1745         if (!model)
1746                 return FALSE;
1747
1748         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1749         /* If none selected yet, pick the first one */
1750         if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1751                 GtkTreeIter iter;
1752                 GtkTreePath *path;
1753
1754                 /* Return if the model is empty */
1755                 if (!gtk_tree_model_get_iter_first (model, &iter))
1756                         return FALSE;
1757
1758                 path = gtk_tree_model_get_path (model, &iter);
1759                 gtk_tree_selection_select_path (selection, path);
1760                 gtk_tree_path_free (path);
1761         }
1762
1763         /* Need to get the all the rows because is selection multiple */
1764         selected = gtk_tree_selection_get_selected_rows (selection, &model);
1765         if (selected == NULL) return FALSE;
1766         selected_path = (GtkTreePath *) selected->data;
1767
1768         /* Check if we need to scroll */
1769         #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1770         GtkTreePath *start_path = NULL;
1771         GtkTreePath *end_path = NULL;
1772         if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1773                                              &start_path,
1774                                              &end_path)) {
1775
1776                 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1777                     (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1778
1779                         /* Scroll to first path */
1780                         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1781                                                       selected_path,
1782                                                       NULL,
1783                                                       TRUE,
1784                                                       0.5,
1785                                                       0.0);
1786                 }
1787         }
1788         if (start_path)
1789                 gtk_tree_path_free (start_path);
1790         if (end_path)
1791                 gtk_tree_path_free (end_path);
1792
1793         #endif /* GTK_CHECK_VERSION */
1794
1795         /* Frees */     
1796         g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1797         g_list_free (selected);
1798
1799         return FALSE;
1800 }
1801
1802 static gboolean 
1803 on_focus_out (GtkWidget     *self,
1804              GdkEventFocus *event,
1805              gpointer       user_data)
1806 {
1807
1808         if (!gtk_widget_is_focus (self)) {
1809                 GtkTreeSelection *selection = NULL;
1810                 GList *selected_rows = NULL;
1811                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1812                 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1813                         selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1814                         g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1815                         gtk_tree_selection_unselect_all (selection);
1816                         gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1817                         g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1818                         g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1819                         g_list_free (selected_rows);
1820                 }
1821         }
1822         return FALSE;
1823 }
1824
1825 static gboolean
1826 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1827 {
1828         enable_drag_and_drop(self);
1829         return FALSE;
1830 }
1831
1832 static gboolean
1833 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1834 {
1835         GtkTreeSelection *selection = NULL;
1836         GtkTreePath *path = NULL;
1837         gboolean already_selected = FALSE, already_opened = FALSE;
1838         ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
1839
1840         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1841                 GtkTreeIter iter;
1842                 GtkTreeModel *model;
1843
1844                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1845                 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1846
1847                 /* Get header from model */
1848                 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1849                 if (gtk_tree_model_get_iter (model, &iter, path)) {
1850                         GValue value = {0,};
1851                         TnyHeader *header;
1852
1853                         gtk_tree_model_get_value (model, &iter, 
1854                                                   TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, 
1855                                                   &value);
1856                         header = (TnyHeader *) g_value_get_object (&value);
1857                         if (TNY_IS_HEADER (header)) {
1858                                 status = modest_tny_all_send_queues_get_msg_status (header);
1859                                 already_opened = modest_window_mgr_find_registered_header (modest_runtime_get_window_mgr (), 
1860                                                                                            header, NULL);
1861                         }
1862                         g_value_unset (&value);
1863                 }
1864         }
1865
1866         /* Enable drag and drop only if the user clicks on a row that
1867            it's already selected. If not, let him select items using
1868            the pointer. If the message is in an OUTBOX and in sending
1869            status disable drag and drop as well */
1870         if (!already_selected ||
1871             status == MODEST_TNY_SEND_QUEUE_SENDING ||
1872             already_opened)
1873                 disable_drag_and_drop(self);
1874
1875         if (path != NULL)
1876                 gtk_tree_path_free(path);
1877
1878         /* If it's already opened then do not let the button-press
1879            event go on because it'll perform a message open because
1880            we're clicking on to an already selected header */
1881         return FALSE;
1882 }
1883
1884 static void
1885 folder_monitor_update (TnyFolderObserver *self, 
1886                        TnyFolderChange *change)
1887 {
1888         ModestHeaderViewPrivate *priv = NULL;
1889         TnyFolderChangeChanged changed;
1890         TnyFolder *folder = NULL;
1891
1892         changed = tny_folder_change_get_changed (change);
1893         
1894         /* Do not notify the observers if the folder of the header
1895            view has changed before this call to the observer
1896            happens */
1897         priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1898         folder = tny_folder_change_get_folder (change);
1899         if (folder != priv->folder)
1900                 goto frees;
1901
1902         MODEST_DEBUG_BLOCK (
1903                             if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1904                                     g_print ("ADDED %d/%d (r/t) \n", 
1905                                              tny_folder_change_get_new_unread_count (change),
1906                                              tny_folder_change_get_new_all_count (change));
1907                             if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
1908                                     g_print ("ALL COUNT %d\n", 
1909                                              tny_folder_change_get_new_all_count (change));
1910                             if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
1911                                     g_print ("UNREAD COUNT %d\n", 
1912                                              tny_folder_change_get_new_unread_count (change));
1913                             if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
1914                                     g_print ("EXPUNGED %d/%d (r/t) \n", 
1915                                              tny_folder_change_get_new_unread_count (change),
1916                                              tny_folder_change_get_new_all_count (change));
1917                             if (changed & TNY_FOLDER_CHANGE_CHANGED_FOLDER_RENAME)
1918                                     g_print ("FOLDER RENAME\n");
1919                             if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
1920                                     g_print ("MSG RECEIVED %d/%d (r/t) \n", 
1921                                              tny_folder_change_get_new_unread_count (change),
1922                                              tny_folder_change_get_new_all_count (change));
1923                             g_print ("---------------------------------------------------\n");
1924                             );
1925
1926         /* Check folder count */
1927         if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1928             (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1929
1930                 g_mutex_lock (priv->observers_lock);
1931
1932                 /* Emit signal to evaluate how headers changes affects
1933                    to the window view  */
1934                 g_signal_emit (G_OBJECT(self), 
1935                                signals[MSG_COUNT_CHANGED_SIGNAL], 
1936                                0, folder, change);
1937                 
1938                 /* Added or removed headers, so data stored on cliboard are invalid  */
1939                 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1940                         modest_email_clipboard_clear (priv->clipboard);
1941             
1942                 g_mutex_unlock (priv->observers_lock);
1943         }       
1944
1945         /* Free */
1946  frees:
1947         if (folder != NULL)
1948                 g_object_unref (folder);
1949 }
1950
1951 gboolean
1952 modest_header_view_is_empty (ModestHeaderView *self)
1953 {
1954         ModestHeaderViewPrivate *priv;
1955         
1956         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
1957         
1958         priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1959
1960         return priv->status == HEADER_VIEW_EMPTY;
1961 }
1962
1963 void
1964 modest_header_view_clear (ModestHeaderView *self)
1965 {
1966         g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1967         
1968         modest_header_view_set_folder (self, NULL, FALSE, NULL, NULL);
1969 }
1970
1971 void 
1972 modest_header_view_copy_selection (ModestHeaderView *header_view)
1973 {
1974         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
1975         
1976         /* Copy selection */
1977         _clipboard_set_selected_data (header_view, FALSE);
1978 }
1979
1980 void 
1981 modest_header_view_cut_selection (ModestHeaderView *header_view)
1982 {
1983         ModestHeaderViewPrivate *priv = NULL;
1984         const gchar **hidding = NULL;
1985         guint i, n_selected;
1986
1987         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
1988         
1989         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1990
1991         /* Copy selection */
1992         _clipboard_set_selected_data (header_view, TRUE);
1993
1994         /* Get hidding ids */
1995         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected); 
1996         
1997         /* Clear hidding array created by previous cut operation */
1998         _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1999
2000         /* Copy hidding array */
2001         priv->n_selected = n_selected;
2002         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2003         for (i=0; i < n_selected; i++) 
2004                 priv->hidding_ids[i] = g_strdup(hidding[i]);            
2005
2006         /* Hide cut headers */
2007         modest_header_view_refilter (header_view);
2008 }
2009
2010
2011  
2012
2013 static void
2014 _clipboard_set_selected_data (ModestHeaderView *header_view,
2015                               gboolean delete)
2016 {
2017         ModestHeaderViewPrivate *priv = NULL;
2018         TnyList *headers = NULL;
2019
2020         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2021         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2022                 
2023         /* Set selected data on clipboard   */
2024         g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
2025         headers = modest_header_view_get_selected_headers (header_view);
2026         modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
2027
2028         /* Free */
2029         g_object_unref (headers);
2030 }
2031
2032 typedef struct {
2033         ModestHeaderView *self;
2034         TnyFolder *folder;
2035 } NotifyFilterInfo;
2036
2037 static gboolean
2038 notify_filter_change (gpointer data)
2039 {
2040         NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2041
2042         g_signal_emit (info->self, 
2043                        signals[MSG_COUNT_CHANGED_SIGNAL], 
2044                        0, info->folder, NULL);
2045
2046         return FALSE;
2047 }
2048
2049 static void
2050 notify_filter_change_destroy (gpointer data)
2051 {
2052         NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2053         ModestHeaderViewPrivate *priv;
2054
2055         priv = MODEST_HEADER_VIEW_GET_PRIVATE (info->self);
2056         priv->status_timeout = 0;
2057
2058         g_object_unref (info->self);
2059         g_object_unref (info->folder);
2060         g_slice_free (NotifyFilterInfo, info);
2061 }
2062
2063 static gboolean
2064 filter_row (GtkTreeModel *model,
2065             GtkTreeIter *iter,
2066             gpointer user_data)
2067 {
2068         ModestHeaderViewPrivate *priv = NULL;
2069         TnyHeaderFlags flags;
2070         TnyHeader *header = NULL;
2071         guint i;
2072         gchar *id = NULL;
2073         gboolean visible = TRUE;
2074         gboolean found = FALSE;
2075         GValue value = {0,};
2076         HeaderViewStatus old_status;
2077
2078         g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
2079         priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2080
2081         /* Get header from model */
2082         gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
2083         flags = (TnyHeaderFlags) g_value_get_int (&value);
2084         g_value_unset (&value);
2085         gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
2086         header = (TnyHeader *) g_value_get_object (&value);
2087         g_value_unset (&value);
2088         
2089         /* Hide deleted and mark as deleted heders */
2090         if (flags & TNY_HEADER_FLAG_DELETED ||
2091             flags & TNY_HEADER_FLAG_EXPUNGED) {
2092                 visible = FALSE;
2093                 goto frees;
2094         }
2095
2096         /* If no data on clipboard, return always TRUE */
2097         if (modest_email_clipboard_cleared(priv->clipboard)) {
2098                 visible = TRUE;
2099                 goto frees;
2100         }               
2101
2102         /* Get message id from header (ensure is a valid id) */
2103         if (!header) {
2104                 visible = FALSE;
2105                 goto frees;
2106         }
2107         
2108         /* Check hiding */
2109         if (priv->hidding_ids != NULL) {
2110                 id = tny_header_dup_message_id (header);
2111                 for (i=0; i < priv->n_selected && !found; i++)
2112                         if (priv->hidding_ids[i] != NULL && id != NULL)
2113                                 found = (!strcmp (priv->hidding_ids[i], id));
2114         
2115                 visible = !found;
2116                 g_free(id);
2117         }
2118
2119  frees:
2120         old_status = priv->status;
2121         priv->status = ((gboolean) priv->status) && !visible;
2122         if ((priv->notify_status) && (priv->status != old_status)) {
2123                 NotifyFilterInfo *info;
2124
2125                 if (priv->status_timeout)
2126                         g_source_remove (priv->status_timeout);
2127
2128                 info = g_slice_new0 (NotifyFilterInfo);
2129                 info->self = g_object_ref (G_OBJECT (user_data));
2130                 info->folder = tny_header_get_folder (header);
2131                 priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
2132                                                            notify_filter_change,
2133                                                            info,
2134                                                            notify_filter_change_destroy);
2135         }
2136
2137         return visible;
2138 }
2139
2140 static void
2141 _clear_hidding_filter (ModestHeaderView *header_view) 
2142 {
2143         ModestHeaderViewPrivate *priv = NULL;
2144         guint i;
2145         
2146         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view)); 
2147         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2148
2149         if (priv->hidding_ids != NULL) {
2150                 for (i=0; i < priv->n_selected; i++) 
2151                         g_free (priv->hidding_ids[i]);
2152                 g_free(priv->hidding_ids);
2153         }       
2154 }
2155
2156 void 
2157 modest_header_view_refilter (ModestHeaderView *header_view)
2158 {
2159         GtkTreeModel *model = NULL;
2160         ModestHeaderViewPrivate *priv = NULL;
2161
2162         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2163         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2164         
2165         /* Hide cut headers */
2166         model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2167         if (GTK_IS_TREE_MODEL_FILTER (model)) {
2168                 priv->status = HEADER_VIEW_INIT;
2169                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2170         }
2171 }
2172
2173 /* 
2174  * Called when an account is removed. If I'm showing a folder of the
2175  * account that has been removed then clear the view
2176  */
2177 static void
2178 on_account_removed (TnyAccountStore *self, 
2179                     TnyAccount *account,
2180                     gpointer user_data)
2181 {
2182         ModestHeaderViewPrivate *priv = NULL;
2183
2184         /* Ignore changes in transport accounts */
2185         if (TNY_IS_TRANSPORT_ACCOUNT (account))
2186                 return;
2187
2188         priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2189
2190         if (priv->folder) {
2191                 TnyAccount *my_account;
2192
2193                 my_account = tny_folder_get_account (priv->folder);
2194                 if (my_account == account)
2195                         modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
2196                 g_object_unref (my_account);
2197         }
2198 }
2199
2200 void
2201 modest_header_view_add_observer(ModestHeaderView *header_view,
2202                                      ModestHeaderViewObserver *observer)
2203 {
2204         ModestHeaderViewPrivate *priv;
2205         
2206         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2207         g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2208
2209         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2210
2211         g_mutex_lock(priv->observer_list_lock);
2212         priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2213         g_mutex_unlock(priv->observer_list_lock);
2214 }
2215
2216 void 
2217 modest_header_view_remove_observer(ModestHeaderView *header_view,
2218                                    ModestHeaderViewObserver *observer)
2219 {
2220         ModestHeaderViewPrivate *priv;
2221
2222         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2223         g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2224
2225         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2226
2227         g_mutex_lock(priv->observer_list_lock);
2228         priv->observer_list = g_slist_remove(priv->observer_list, observer);
2229         g_mutex_unlock(priv->observer_list_lock);
2230 }
2231
2232 static void 
2233 modest_header_view_notify_observers(ModestHeaderView *header_view,
2234                                     GtkTreeModel *model,
2235                                     const gchar *tny_folder_id)
2236 {
2237         ModestHeaderViewPrivate *priv = NULL;
2238         GSList *iter;
2239         ModestHeaderViewObserver *observer;
2240
2241
2242         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2243         
2244         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2245
2246         g_mutex_lock(priv->observer_list_lock);
2247         iter = priv->observer_list;
2248         while(iter != NULL){
2249                 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2250                 modest_header_view_observer_update(observer, model,
2251                                 tny_folder_id);
2252                 iter = g_slist_next(iter);
2253         }
2254         g_mutex_unlock(priv->observer_list_lock);
2255 }
2256