ce0fd9b212c27a24bec08ba381b12c120564b85d
[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_or_status;
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_or_status  = 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_or_status, FALSE);
322         g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
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_or_status),
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(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
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((type == TNY_FOLDER_TYPE_OUTBOX)?
410                                                                  MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
411                                                                  MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
412                         compact_column = column;
413                         break;
414
415                         
416                 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
417                         column = get_new_column (_("Subject"), renderer_header, TRUE,
418                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
419                                                  TRUE,
420                                                  (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
421                                                  NULL);
422                         break;
423                         
424                 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
425                         column = get_new_column (_("Received"), renderer_header, TRUE,
426                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
427                                                  TRUE,
428                                                  (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
429                                                  GINT_TO_POINTER(TRUE));
430                         break;
431                         
432                 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:  
433                         column = get_new_column (_("Sent"), renderer_header, TRUE,
434                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
435                                                  TRUE,
436                                                  (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
437                                                  GINT_TO_POINTER(FALSE));
438                         break;
439                         
440                 case MODEST_HEADER_VIEW_COLUMN_SIZE:
441                         column = get_new_column (_("Size"), renderer_header, TRUE,
442                                                  TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
443                                                  FALSE,
444                                                  (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
445                                                  NULL); 
446                         break;
447                 case MODEST_HEADER_VIEW_COLUMN_STATUS:
448                         column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
449                                                  TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
450                                                  FALSE,
451                                                  (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
452                                                  NULL); 
453                         break;
454
455                 default:
456                         g_return_val_if_reached(FALSE);
457                 }
458
459                 /* we keep the column id around */
460                 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
461                                    GINT_TO_POINTER(col));
462                 
463                 /* we need this ptr when sorting the rows */
464                 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
465                                    self);
466                 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);              
467         }               
468
469         if (sortable) {
470                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
471                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
472                                                  (GtkTreeIterCompareFunc) cmp_rows,
473                                                  compact_column, NULL);
474                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
475                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
476                                                  (GtkTreeIterCompareFunc) cmp_subject_rows,
477                                                  compact_column, NULL);
478         }
479
480
481         return TRUE;
482 }
483
484 static void
485 modest_header_view_init (ModestHeaderView *obj)
486 {
487         ModestHeaderViewPrivate *priv;
488         guint i, j;
489
490         priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj); 
491
492         priv->folder  = NULL;
493
494         priv->monitor        = NULL;
495         priv->observers_lock = g_mutex_new ();
496
497         priv->clipboard = modest_runtime_get_email_clipboard ();
498         priv->hidding_ids = NULL;
499         priv->n_selected = 0;
500
501         /* Sort parameters */
502         for (j=0; j < 2; j++) {
503                 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
504                         priv->sort_colid[j][i] = -1;
505                         priv->sort_type[j][i] = GTK_SORT_DESCENDING;
506                 }                       
507         }
508
509         setup_drag_and_drop (GTK_TREE_VIEW (obj));
510 }
511
512 static void
513 modest_header_view_dispose (GObject *obj)
514 {
515         ModestHeaderView        *self;
516         ModestHeaderViewPrivate *priv;
517         
518         self = MODEST_HEADER_VIEW(obj);
519         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
520
521         if (priv->folder) {
522                 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
523                 g_object_unref (G_OBJECT (priv->folder));
524                 priv->folder = NULL;
525         }
526
527         G_OBJECT_CLASS(parent_class)->dispose (obj);
528 }
529
530 static void
531 modest_header_view_finalize (GObject *obj)
532 {
533         ModestHeaderView        *self;
534         ModestHeaderViewPrivate *priv;
535         
536         self = MODEST_HEADER_VIEW(obj);
537         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
538
539         g_mutex_lock (priv->observers_lock);
540         if (priv->monitor) {
541                 tny_folder_monitor_stop (priv->monitor);
542                 g_object_unref (G_OBJECT (priv->monitor));
543         }
544         g_mutex_unlock (priv->observers_lock);
545         g_mutex_free (priv->observers_lock);
546
547         /* Clear hidding array created by cut operation */
548         _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
549
550         G_OBJECT_CLASS(parent_class)->finalize (obj);
551 }
552
553
554 GtkWidget*
555 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
556 {
557         GObject *obj;
558         GtkTreeSelection *sel;
559         ModestHeaderView *self;
560         ModestHeaderViewPrivate *priv;
561         
562         g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
563                               NULL);
564         
565         obj  = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
566         self = MODEST_HEADER_VIEW(obj);
567         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
568         
569         modest_header_view_set_style   (self, style);
570 /*      modest_header_view_set_folder (self, NULL, NULL, NULL); */
571
572         gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
573         gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
574         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
575         
576         gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
577                                       TRUE); /* alternating row colors */
578         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
579         
580         g_signal_connect (sel, "changed",
581                           G_CALLBACK(on_selection_changed), self);
582         
583         g_signal_connect (self, "row-activated",
584                           G_CALLBACK (on_header_row_activated), NULL);
585
586         g_signal_connect (self, "focus-in-event",
587                           G_CALLBACK(on_focus_in), NULL);
588         
589         return GTK_WIDGET(self);
590 }
591
592
593 guint
594 modest_header_view_count_selected_headers (ModestHeaderView *self)
595 {
596         GtkTreeSelection *sel;
597         guint selected_rows;
598
599         g_return_val_if_fail (self, 0);
600         
601         /* Get selection object and check selected rows count */
602         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
603         selected_rows = gtk_tree_selection_count_selected_rows (sel);
604         
605         return selected_rows;
606 }
607
608 gboolean
609 modest_header_view_has_selected_headers (ModestHeaderView *self)
610 {
611         GtkTreeSelection *sel;
612         gboolean empty;
613
614         g_return_val_if_fail (self, FALSE);
615         
616         /* Get selection object and check selected rows count */
617         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
618         empty = gtk_tree_selection_count_selected_rows (sel) == 0;
619         
620         return !empty;
621 }
622
623
624 TnyList * 
625 modest_header_view_get_selected_headers (ModestHeaderView *self)
626 {
627         GtkTreeSelection *sel;
628         ModestHeaderViewPrivate *priv;
629         TnyList *header_list = NULL;
630         TnyHeader *header;
631         GList *list, *tmp = NULL;
632         GtkTreeModel *tree_model = NULL;
633         GtkTreeIter iter;
634
635         g_return_val_if_fail (self, NULL);
636         
637         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
638
639         /* Get selected rows */
640         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
641         list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
642
643         if (list) {
644                 header_list = tny_simple_list_new();
645
646                 list = g_list_reverse (list);
647                 tmp = list;
648                 while (tmp) {                   
649                         /* get header from selection */
650                         gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
651                         gtk_tree_model_get (tree_model, &iter,
652                                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
653                                             &header, -1);
654                         /* Prepend to list */
655                         tny_list_prepend (header_list, G_OBJECT (header));
656                         g_object_unref (G_OBJECT (header));
657
658                         tmp = g_list_next (tmp);
659                 }
660                 /* Clean up*/
661                 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
662                 g_list_free (list);
663         }
664         return header_list;
665 }
666
667
668 /* scroll our list view so the selected item is visible */
669 static void
670 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
671 {
672 #ifdef MODEST_PLATFORM_GNOME 
673
674         GtkTreePath *selected_path;
675         GtkTreePath *start, *end;
676         
677         GtkTreeModel *model;
678         
679         model         = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
680         selected_path = gtk_tree_model_get_path (model, iter);
681
682         start = gtk_tree_path_new ();
683         end   = gtk_tree_path_new ();
684
685         gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
686
687         if (gtk_tree_path_compare (selected_path, start) < 0 ||
688             gtk_tree_path_compare (end, selected_path) < 0)
689                 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
690                                               selected_path, NULL, TRUE,
691                                               up ? 0.0 : 1.0,
692                                               up ? 0.0 : 1.0);
693         gtk_tree_path_free (selected_path);
694         gtk_tree_path_free (start);
695         gtk_tree_path_free (end);
696
697 #endif /* MODEST_PLATFORM_GNOME */
698 }
699
700
701 void 
702 modest_header_view_select_next (ModestHeaderView *self)
703 {
704         GtkTreeSelection *sel;
705         GtkTreeIter iter;
706         GtkTreeModel *model;
707         GtkTreePath *path;
708
709         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
710         path = get_selected_row (GTK_TREE_VIEW(self), &model);
711         if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
712                 /* Unselect previous path */
713                 gtk_tree_selection_unselect_path (sel, path);
714                 
715                 /* Move path down and selects new one  */
716                 if (gtk_tree_model_iter_next (model, &iter)) {
717                         gtk_tree_selection_select_iter (sel, &iter);
718                         scroll_to_selected (self, &iter, FALSE);        
719                 }
720                 gtk_tree_path_free(path);
721         }
722         
723 }
724
725 void 
726 modest_header_view_select_prev (ModestHeaderView *self)
727 {
728         GtkTreeSelection *sel;
729         GtkTreeIter iter;
730         GtkTreeModel *model;
731         GtkTreePath *path;
732
733         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
734         path = get_selected_row (GTK_TREE_VIEW(self), &model);
735         if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
736                 /* Unselect previous path */
737                 gtk_tree_selection_unselect_path (sel, path);
738
739                 /* Move path up */
740                 if (gtk_tree_path_prev (path)) {
741                         gtk_tree_model_get_iter (model, &iter, path);
742                         
743                         /* Select the new one */
744                         gtk_tree_selection_select_iter (sel, &iter);
745                         scroll_to_selected (self, &iter, TRUE); 
746
747                 }
748                 gtk_tree_path_free (path);
749         }
750 }
751
752 GList*
753 modest_header_view_get_columns (ModestHeaderView *self)
754 {
755         g_return_val_if_fail (self, FALSE);
756         return gtk_tree_view_get_columns (GTK_TREE_VIEW(self)); 
757 }
758
759
760 gboolean
761 modest_header_view_is_empty (ModestHeaderView *self)
762 {
763         g_return_val_if_fail (self, FALSE);
764         return FALSE; /* FIXME */
765 }
766
767
768 gboolean
769 modest_header_view_set_style (ModestHeaderView *self,
770                               ModestHeaderViewStyle style)
771 {
772         ModestHeaderViewPrivate *priv;
773         gboolean show_col_headers = FALSE;
774         ModestHeaderViewStyle old_style;
775         
776         g_return_val_if_fail (self, FALSE);
777         g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
778                               FALSE);
779
780         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
781         if (priv->style == style)
782                 return TRUE; /* nothing to do */
783         
784         switch (style) {
785         case MODEST_HEADER_VIEW_STYLE_DETAILS:
786                 show_col_headers = TRUE;
787                 break;
788         case MODEST_HEADER_VIEW_STYLE_TWOLINES:
789                 break;
790         default:
791                 g_return_val_if_reached (FALSE);
792         }
793         gtk_tree_view_set_headers_visible   (GTK_TREE_VIEW(self), show_col_headers);
794         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);    
795
796         old_style   = priv->style;
797         priv->style = style;
798
799         return TRUE;
800 }
801
802
803 ModestHeaderViewStyle
804 modest_header_view_get_style (ModestHeaderView *self)
805 {
806         g_return_val_if_fail (self, FALSE);
807         return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
808 }
809
810 /* 
811  * This function sets a sortable model in the header view. It's just
812  * used for developing purposes, because it only does a
813  * gtk_tree_view_set_model
814  */
815 static void
816 modest_header_view_set_model (GtkTreeView *header_view, GtkTreeModel *model)
817 {
818 /*      GtkTreeModel *old_model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view)); */
819 /*      if (old_model_sort && GTK_IS_TREE_MODEL_SORT (old_model_sort)) { */
820 /*              GtkTreeModel *old_model; */
821 /*              ModestHeaderViewPrivate *priv; */
822 /*              priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view); */
823 /*              old_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (old_model_sort)); */
824
825 /*              /\* Set new model *\/ */
826 /*              gtk_tree_view_set_model (header_view, model); */
827 /*      } else */
828         gtk_tree_view_set_model (header_view, model);
829 }
830
831 TnyFolder*
832 modest_header_view_get_folder (ModestHeaderView *self)
833 {
834         ModestHeaderViewPrivate *priv;
835         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
836
837         if (priv->folder)
838                 g_object_ref (priv->folder);
839
840         return priv->folder;
841 }
842
843 static void
844 modest_header_view_set_folder_intern (ModestHeaderView *self, TnyFolder *folder)
845 {
846         TnyFolderType type;
847         TnyList *headers;
848         ModestHeaderViewPrivate *priv;
849         GList *cols, *cursor;
850         GtkTreeModel *filter_model, *sortable; 
851         guint sort_colid;
852         GtkSortType sort_type;
853
854         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
855
856         headers = TNY_LIST (tny_gtk_header_list_model_new ());
857
858         tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
859                                               folder, FALSE);
860
861         /* Add IDLE observer (monitor) and another folder observer for
862            new messages (self) */
863         g_mutex_lock (priv->observers_lock);
864         if (priv->monitor) {
865                 tny_folder_monitor_stop (priv->monitor);
866                 g_object_unref (G_OBJECT (priv->monitor));
867         }
868         priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
869         tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
870         tny_folder_monitor_start (priv->monitor);
871         g_mutex_unlock (priv->observers_lock);
872
873         sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(headers));
874         g_object_unref (G_OBJECT (headers));
875
876         /* Create a tree model filter to hide and show rows for cut operations  */
877         filter_model = gtk_tree_model_filter_new (sortable, NULL);
878         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
879                                                 filter_row,
880                                                 self,
881                                                 NULL);
882         g_object_unref (G_OBJECT (sortable));
883
884         /* install our special sorting functions */
885         cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
886
887         /* Restore sort column id */
888         if (cols) {
889                 type  = modest_tny_folder_guess_folder_type (folder);
890                 sort_colid = modest_header_view_get_sort_column_id (self, type); 
891                 sort_type = modest_header_view_get_sort_type (self, type); 
892                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
893                                                       sort_colid,
894                                                       sort_type);
895                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
896                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
897                                                  (GtkTreeIterCompareFunc) cmp_rows,
898                                                  cols->data, NULL);
899                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
900                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
901                                                  (GtkTreeIterCompareFunc) cmp_subject_rows,
902                                                  cols->data, NULL);
903         }
904
905         /* Set new model */
906         modest_header_view_set_model (GTK_TREE_VIEW (self), filter_model);
907         g_object_unref (G_OBJECT (filter_model));
908 /*      modest_header_view_set_model (GTK_TREE_VIEW (self), sortable); */
909 /*      g_object_unref (G_OBJECT (sortable)); */
910
911         /* Free */
912         g_list_free (cols);
913 }
914
915 void
916 modest_header_view_sort_by_column_id (ModestHeaderView *self, 
917                                       guint sort_colid,
918                                       GtkSortType sort_type)
919 {
920         ModestHeaderViewPrivate *priv = NULL;
921         GtkTreeModel *tree_filter, *sortable = NULL; 
922         TnyFolderType type;
923
924         /* Get model and private data */
925         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);            
926         tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
927         sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
928 /*      sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
929         
930         /* Sort tree model */
931         type  = modest_tny_folder_guess_folder_type (priv->folder);
932         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
933                                               sort_colid,
934                                               sort_type);
935         /* Store new sort parameters */
936         modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
937
938         /* Save GConf parameters */
939 /*      modest_widget_memory_save (modest_runtime_get_conf(), */
940 /*                                 G_OBJECT(self), "header-view"); */
941         
942 }
943
944 void
945 modest_header_view_set_sort_params (ModestHeaderView *self, 
946                                     guint sort_colid, 
947                                     GtkSortType sort_type,
948                                     TnyFolderType type)
949 {
950         ModestHeaderViewPrivate *priv;
951         ModestHeaderViewStyle style;
952
953         style = modest_header_view_get_style   (self);
954         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
955
956         priv->sort_colid[style][type] = sort_colid;
957         priv->sort_type[style][type] = sort_type;
958 }
959
960 gint
961 modest_header_view_get_sort_column_id (ModestHeaderView *self, 
962                                        TnyFolderType type)
963 {
964         ModestHeaderViewPrivate *priv;
965         ModestHeaderViewStyle style;
966
967         style = modest_header_view_get_style   (self);
968         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
969
970         return priv->sort_colid[style][type];
971 }
972
973 GtkSortType
974 modest_header_view_get_sort_type (ModestHeaderView *self, 
975                                   TnyFolderType type)
976 {
977         ModestHeaderViewPrivate *priv;
978         ModestHeaderViewStyle style;
979
980         style = modest_header_view_get_style   (self);
981         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
982
983         return priv->sort_type[style][type];
984 }
985
986 typedef struct {
987         ModestHeaderView *header_view;
988         RefreshAsyncUserCallback cb;
989         gpointer user_data;
990 } SetFolderHelper;
991
992 static void
993 folder_refreshed_cb (const GObject *obj, 
994                      TnyFolder *folder, 
995                      gpointer user_data)
996 {
997         ModestHeaderViewPrivate *priv;
998         SetFolderHelper *info;
999  
1000         info = (SetFolderHelper*) user_data;
1001
1002         priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1003
1004         /* User callback */
1005         if (info->cb)
1006                 info->cb (obj, folder, info->user_data);
1007
1008         /* Start the folder count changes observer. We do not need it
1009            before the refresh. Note that the monitor could still be
1010            called for this refresh but now we know that the callback
1011            was previously called */
1012         g_mutex_lock (priv->observers_lock);
1013         tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1014         g_mutex_unlock (priv->observers_lock);
1015
1016         /* Frees */
1017         g_free (info);
1018 }
1019
1020 void
1021 modest_header_view_set_folder (ModestHeaderView *self, 
1022                                TnyFolder *folder,
1023                                RefreshAsyncUserCallback callback,
1024                                gpointer user_data)
1025 {
1026         ModestHeaderViewPrivate *priv;
1027         ModestWindowMgr *mgr = NULL;
1028         GObject *source = NULL;
1029         SetFolderHelper *info;
1030  
1031         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1032
1033         if (priv->folder) {
1034                 g_mutex_lock (priv->observers_lock);
1035                 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1036                 g_object_unref (priv->folder);
1037                 priv->folder = NULL;
1038                 g_mutex_unlock (priv->observers_lock);
1039         }
1040
1041         if (folder) {
1042                 ModestMailOperation *mail_op = NULL;
1043
1044                 /* Get main window to use it as source of mail operation */
1045                 mgr = modest_runtime_get_window_mgr ();
1046                 source = G_OBJECT (modest_window_mgr_get_main_window (modest_runtime_get_window_mgr ()));
1047
1048                 /* Set folder in the model */
1049                 modest_header_view_set_folder_intern (self, folder);
1050
1051                 /* Pick my reference. Nothing to do with the mail operation */
1052                 priv->folder = g_object_ref (folder);
1053
1054                 /* no message selected */
1055                 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1056
1057                 info = g_malloc0 (sizeof(SetFolderHelper));
1058                 info->header_view = self;
1059                 info->cb = callback;
1060                 info->user_data = user_data;
1061
1062                 /* Create the mail operation (source will be the parent widget) */
1063                 mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_TYPE_RECEIVE, source);
1064                 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1065                                                  mail_op);
1066
1067                 /* Refresh the folder asynchronously */
1068                 modest_mail_operation_refresh_folder (mail_op, 
1069                                                       folder,
1070                                                       folder_refreshed_cb,
1071                                                       info);
1072
1073                 /* Free */
1074                 g_object_unref (mail_op);
1075         } else {
1076                 g_mutex_lock (priv->observers_lock);
1077
1078                 if (priv->monitor) {
1079                         tny_folder_monitor_stop (priv->monitor);
1080                         g_object_unref (G_OBJECT (priv->monitor));
1081                         priv->monitor = NULL;
1082                 }
1083                 modest_header_view_set_model (GTK_TREE_VIEW (self), NULL); 
1084
1085                 g_mutex_unlock (priv->observers_lock);
1086         }
1087 }
1088
1089 static void
1090 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1091                          GtkTreeViewColumn *column, gpointer userdata)
1092 {
1093         ModestHeaderView *self = NULL;
1094         ModestHeaderViewPrivate *priv = NULL;
1095         GtkTreeIter iter;
1096         GtkTreeModel *model = NULL;
1097         TnyHeader *header = NULL;
1098         TnyHeaderFlags flags;
1099
1100         self = MODEST_HEADER_VIEW (treeview);
1101         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1102
1103         model = gtk_tree_view_get_model (treeview);     
1104         if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path))) 
1105                 goto frees;
1106
1107         /* get the first selected item */
1108         gtk_tree_model_get (model, &iter,
1109                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1110                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header, 
1111                             -1);
1112
1113         /* Dont open DELETED messages */
1114         if (flags & TNY_HEADER_FLAG_DELETED) {
1115                 modest_platform_information_banner (NULL, NULL, _("mcen_ib_message_already_deleted"));
1116                 goto frees;
1117         }
1118
1119         /* Emit signal */
1120         g_signal_emit (G_OBJECT(self), 
1121                        signals[HEADER_ACTIVATED_SIGNAL], 
1122                        0, header);
1123
1124         /* Free */
1125  frees:
1126         if (header != NULL) 
1127                 g_object_unref (G_OBJECT (header));     
1128
1129 }
1130
1131 static void
1132 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1133 {
1134         GtkTreeModel *model;
1135         TnyHeader *header;
1136         GtkTreePath *path = NULL;       
1137         GtkTreeIter iter;
1138         ModestHeaderView *self;
1139         ModestHeaderViewPrivate *priv;
1140         GList *selected = NULL;
1141         
1142         g_return_if_fail (sel);
1143         g_return_if_fail (user_data);
1144         
1145         self = MODEST_HEADER_VIEW (user_data);
1146         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);    
1147
1148         selected = gtk_tree_selection_get_selected_rows (sel, &model);
1149         if (selected != NULL) 
1150                 path = (GtkTreePath *) selected->data;
1151         if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1152                 return; /* msg was _un_selected */
1153
1154         gtk_tree_model_get (model, &iter,
1155                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1156                             &header, -1);
1157
1158         /* Emit signal */
1159         g_signal_emit (G_OBJECT(self), 
1160                        signals[HEADER_SELECTED_SIGNAL], 
1161                        0, header);
1162
1163         g_object_unref (G_OBJECT (header));
1164
1165         /* free all items in 'selected' */
1166         g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1167         g_list_free (selected);
1168 }
1169
1170
1171 /* PROTECTED method. It's useful when we want to force a given
1172    selection to reload a msg. For example if we have selected a header
1173    in offline mode, when Modest become online, we want to reload the
1174    message automatically without an user click over the header */
1175 void 
1176 _modest_header_view_change_selection (GtkTreeSelection *selection,
1177                                       gpointer user_data)
1178 {
1179         g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1180         g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1181
1182         on_selection_changed (selection, user_data);
1183 }
1184
1185 static gint compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1186 {
1187         p1 = p1 & TNY_HEADER_FLAG_PRIORITY;
1188         p2 = p2 & TNY_HEADER_FLAG_PRIORITY;
1189         if (p1 == 0) 
1190                 p1 = TNY_HEADER_FLAG_LOW_PRIORITY + 1;
1191         if (p2 == 0) 
1192                 p2 = TNY_HEADER_FLAG_LOW_PRIORITY + 1;
1193         return p1 - p2;
1194 }
1195
1196 static gint
1197 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1198           gpointer user_data)
1199 {
1200         gint col_id;
1201         gint t1, t2;
1202         gint val1, val2;
1203         gint cmp;
1204 /*      static int counter = 0; */
1205
1206         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1207 /*      col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1208         col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1209
1210         
1211         switch (col_id) {
1212         case TNY_HEADER_FLAG_ATTACHMENTS:
1213
1214                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1215                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1216                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1217                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1218
1219                 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1220                         (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1221
1222                 return cmp ? cmp : t1 - t2;
1223                 
1224         case TNY_HEADER_FLAG_PRIORITY:
1225                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1226                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1227                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1228                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1229
1230                 /* This is for making priority values respect the intuitive sort relationship 
1231                  * as HIGH is 11, LOW is 01, and we put NORMAL AS 10 (2) */
1232                 cmp =  compare_priorities (val1, val2);
1233
1234                 return cmp ? cmp : t1 - t2;
1235
1236         default:
1237                 return &iter1 - &iter2; /* oughhhh  */
1238         }
1239 }
1240
1241 static gint
1242 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1243                   gpointer user_data)
1244 {
1245         gint t1, t2;
1246         gchar *val1, *val2;
1247         gint cmp;
1248 /*      static int counter = 0; */
1249
1250         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1251
1252         gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1253                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1254         gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1255                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1256
1257         cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1258                                              val2 + modest_text_utils_get_subject_prefix_len(val2),
1259                                              TRUE);
1260         g_free (val1);
1261         g_free (val2);
1262         return cmp;
1263 }
1264
1265 /* Drag and drop stuff */
1266 static void
1267 drag_data_get_cb (GtkWidget *widget, GdkDragContext *context, 
1268                   GtkSelectionData *selection_data, 
1269                   guint info,  guint time, gpointer data)
1270 {
1271         GtkTreeModel *model = NULL;
1272         GtkTreeIter iter;
1273         GtkTreePath *source_row = NULL;
1274         
1275         source_row = get_selected_row (GTK_TREE_VIEW (widget), &model);
1276         if ((source_row == NULL) || (!gtk_tree_model_get_iter(model, &iter, source_row))) return;
1277
1278         switch (info) {
1279         case MODEST_HEADER_ROW:
1280                 gtk_tree_set_row_drag_data (selection_data, model, source_row);
1281                 break;
1282         case MODEST_MSG: {
1283                 TnyHeader *hdr;
1284                 gtk_tree_model_get (model, &iter,
1285                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &hdr,
1286                                     -1);
1287                 if (hdr) {
1288                         g_object_unref (G_OBJECT(hdr));
1289                 }
1290                 break;
1291         }
1292         default:
1293                 g_message ("%s: default switch case.", __FUNCTION__);
1294         }
1295
1296         gtk_tree_path_free (source_row);
1297 }
1298
1299 /* Header view drag types */
1300 const GtkTargetEntry header_view_drag_types[] = {
1301         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, MODEST_HEADER_ROW },
1302         { "text/uri-list",      0,                   MODEST_MSG }, 
1303 };
1304
1305 static void
1306 setup_drag_and_drop (GtkTreeView *self)
1307 {
1308         gtk_drag_source_set (GTK_WIDGET (self),
1309                              GDK_BUTTON1_MASK,
1310                              header_view_drag_types,
1311                              G_N_ELEMENTS (header_view_drag_types),
1312                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1313
1314         g_signal_connect(G_OBJECT (self), "drag_data_get",
1315                          G_CALLBACK(drag_data_get_cb), NULL);
1316 }
1317
1318 static GtkTreePath *
1319 get_selected_row (GtkTreeView *self, GtkTreeModel **model) 
1320 {
1321         GtkTreePath *path = NULL;
1322         GtkTreeSelection *sel = NULL;   
1323         GList *rows = NULL;
1324
1325         sel   = gtk_tree_view_get_selection(self);
1326         rows = gtk_tree_selection_get_selected_rows (sel, model);
1327         
1328         if ((rows == NULL) || (g_list_length(rows) != 1))
1329                 goto frees;
1330
1331         path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1332         
1333
1334         /* Free */
1335  frees:
1336         g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1337         g_list_free(rows);
1338
1339         return path;
1340 }
1341
1342 /*
1343  * This function moves the tree view scroll to the current selected
1344  * row when the widget grabs the focus 
1345  */
1346 static gboolean 
1347 on_focus_in (GtkWidget     *self,
1348              GdkEventFocus *event,
1349              gpointer       user_data)
1350 {
1351         GtkTreeSelection *selection;
1352         GtkTreeModel *model;
1353         GList *selected = NULL;
1354         GtkTreePath *selected_path = NULL;
1355
1356         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1357         if (!model)
1358                 return FALSE;
1359
1360         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1361         /* If none selected yet, pick the first one */
1362         if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1363                 GtkTreeIter iter;
1364                 GtkTreePath *path;
1365
1366                 /* Return if the model is empty */
1367                 if (!gtk_tree_model_get_iter_first (model, &iter))
1368                         return FALSE;
1369
1370                 path = gtk_tree_model_get_path (model, &iter);
1371                 gtk_tree_selection_select_path (selection, path);
1372                 gtk_tree_path_free (path);
1373         }
1374
1375         /* Need to get the all the rows because is selection multiple */
1376         selected = gtk_tree_selection_get_selected_rows (selection, &model);
1377         if (selected == NULL) return FALSE;
1378         selected_path = (GtkTreePath *) selected->data;
1379
1380         /* Check if we need to scroll */
1381         #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1382         GtkTreePath *start_path = NULL;
1383         GtkTreePath *end_path = NULL;
1384         if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1385                                              &start_path,
1386                                              &end_path)) {
1387
1388                 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1389                     (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1390
1391                         /* Scroll to first path */
1392                         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1393                                                       selected_path,
1394                                                       NULL,
1395                                                       TRUE,
1396                                                       0.5,
1397                                                       0.0);
1398                 }
1399         }
1400         if (start_path)
1401                 gtk_tree_path_free (start_path);
1402         if (end_path)
1403                 gtk_tree_path_free (end_path);
1404
1405         #endif /* GTK_CHECK_VERSION */
1406
1407         /* Frees */     
1408         g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1409         g_list_free (selected);
1410
1411         return FALSE;
1412 }
1413
1414 static void
1415 idle_notify_headers_count_changed_destroy (gpointer data)
1416 {
1417         HeadersCountChangedHelper *helper = NULL;
1418
1419         g_return_if_fail (data != NULL);
1420         helper = (HeadersCountChangedHelper *) data; 
1421
1422         g_object_unref (helper->change);
1423         g_slice_free (HeadersCountChangedHelper, helper);
1424 }
1425
1426 static gboolean
1427 idle_notify_headers_count_changed (gpointer data)
1428 {
1429         TnyFolder *folder = NULL;
1430         ModestHeaderViewPrivate *priv = NULL;
1431         HeadersCountChangedHelper *helper = NULL;
1432
1433         g_return_val_if_fail (data != NULL, FALSE);
1434         helper = (HeadersCountChangedHelper *) data; 
1435         g_return_val_if_fail (MODEST_IS_HEADER_VIEW(helper->self), FALSE);
1436         g_return_val_if_fail (TNY_FOLDER_CHANGE(helper->change), FALSE);
1437
1438         folder = tny_folder_change_get_folder (helper->change);
1439
1440         priv = MODEST_HEADER_VIEW_GET_PRIVATE (helper->self);
1441
1442         g_mutex_lock (priv->observers_lock);
1443
1444         /* Emit signal to evaluate how headers changes affects to the window view  */
1445         g_signal_emit (G_OBJECT(helper->self), 
1446                        signals[MSG_COUNT_CHANGED_SIGNAL], 
1447                        0, folder, helper->change);
1448                 
1449         /* Added or removed headers, so data stored on cliboard are invalid  */
1450         if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1451             modest_email_clipboard_clear (priv->clipboard);
1452             
1453         g_mutex_unlock (priv->observers_lock);
1454
1455         return FALSE;
1456 }
1457
1458 static void
1459 folder_monitor_update (TnyFolderObserver *self, 
1460                        TnyFolderChange *change)
1461 {
1462         ModestHeaderViewPrivate *priv;
1463         TnyFolderChangeChanged changed;
1464         HeadersCountChangedHelper *helper = NULL;
1465
1466         changed = tny_folder_change_get_changed (change);
1467         
1468         /* Do not notify the observers if the folder of the header
1469            view has changed before this call to the observer
1470            happens */
1471         priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1472         if (tny_folder_change_get_folder (change) != priv->folder)
1473                 return;
1474
1475         /* Check folder count */
1476         if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1477             (changed & TNY_FOLDER_CHANGE_CHANGED_REMOVED_HEADERS)) {
1478                 helper = g_slice_new0 (HeadersCountChangedHelper);
1479                 helper->self = MODEST_HEADER_VIEW(self);
1480                 helper->change = g_object_ref(change);
1481                 
1482                 g_idle_add_full (G_PRIORITY_DEFAULT, 
1483                                  idle_notify_headers_count_changed, 
1484                                  helper,
1485                                  idle_notify_headers_count_changed_destroy);
1486         }       
1487 }
1488
1489 void
1490 modest_header_view_clear (ModestHeaderView *self)
1491 {
1492         modest_header_view_set_folder (self, NULL, NULL, NULL);
1493 }
1494
1495 void 
1496 modest_header_view_copy_selection (ModestHeaderView *header_view)
1497 {
1498         /* Copy selection */
1499         _clipboard_set_selected_data (header_view, FALSE);
1500 }
1501
1502 void 
1503 modest_header_view_cut_selection (ModestHeaderView *header_view)
1504 {
1505         ModestHeaderViewPrivate *priv = NULL;
1506         const gchar **hidding = NULL;
1507         guint i, n_selected;
1508
1509         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1510         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1511
1512         /* Copy selection */
1513         _clipboard_set_selected_data (header_view, TRUE);
1514
1515         /* Get hidding ids */
1516         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected); 
1517         
1518         /* Clear hidding array created by previous cut operation */
1519         _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1520
1521         /* Copy hidding array */
1522         priv->n_selected = n_selected;
1523         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1524         for (i=0; i < n_selected; i++) 
1525                 priv->hidding_ids[i] = g_strdup(hidding[i]);            
1526
1527         /* Hide cut headers */
1528         modest_header_view_refilter (header_view);
1529 }
1530
1531
1532  
1533
1534 static void
1535 _clipboard_set_selected_data (ModestHeaderView *header_view,
1536                               gboolean delete)
1537 {
1538         ModestHeaderViewPrivate *priv = NULL;
1539         TnyList *headers = NULL;
1540
1541         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1542         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1543                 
1544         /* Set selected data on clipboard   */
1545         g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1546         headers = modest_header_view_get_selected_headers (header_view);
1547         modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1548
1549         /* Free */
1550         g_object_unref (headers);
1551 }
1552
1553
1554
1555 static gboolean
1556 filter_row (GtkTreeModel *model,
1557             GtkTreeIter *iter,
1558             gpointer user_data)
1559 {
1560         ModestHeaderViewPrivate *priv = NULL;
1561         TnyHeaderFlags flags;
1562         TnyHeader *header = NULL;
1563         guint i;
1564         gchar *id = NULL;
1565         gboolean visible = TRUE;
1566         gboolean found = FALSE;
1567         
1568         g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1569         priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1570
1571         /* Get header from model */
1572         gtk_tree_model_get (model, iter,
1573                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1574                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1575                             -1);
1576         
1577         /* Hide mark as deleted heders */
1578         if (flags & TNY_HEADER_FLAG_DELETED) {
1579                 visible = FALSE;
1580                 goto frees;
1581         }
1582
1583         /* If no data on clipboard, return always TRUE */
1584         if (modest_email_clipboard_cleared(priv->clipboard)) {
1585                 visible = TRUE;
1586                 goto frees;
1587         }               
1588
1589         /* Get message id from header (ensure is a valid id) */
1590         if (!header) return FALSE;
1591         id = g_strdup(tny_header_get_message_id (header));
1592         
1593         /* Check hiding */
1594         if (priv->hidding_ids != NULL) {
1595                 for (i=0; i < priv->n_selected && !found; i++)
1596                         if (priv->hidding_ids[i] != NULL && id != NULL)
1597                                 found = (!strcmp (priv->hidding_ids[i], id));
1598         
1599                 visible = !found;
1600         }
1601
1602         /* Free */
1603  frees:
1604         if (header)
1605                 g_object_unref (header);
1606         g_free(id);
1607
1608         return visible;
1609 }
1610
1611 static void
1612 _clear_hidding_filter (ModestHeaderView *header_view) 
1613 {
1614         ModestHeaderViewPrivate *priv;
1615         guint i;
1616         
1617         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view)); 
1618         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1619
1620         if (priv->hidding_ids != NULL) {
1621                 for (i=0; i < priv->n_selected; i++) 
1622                         g_free (priv->hidding_ids[i]);
1623                 g_free(priv->hidding_ids);
1624         }       
1625 }
1626
1627 void 
1628 modest_header_view_refilter (ModestHeaderView *header_view)
1629 {
1630         GtkTreeModel *model;
1631
1632         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1633
1634         /* Hide cut headers */
1635         model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
1636         if (GTK_IS_TREE_MODEL_FILTER (model))
1637                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1638 }