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