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