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