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