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