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