Modified webpage: now tinymail repository is in gitorious.
[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 <tny-error.h>
36 #include <tny-merge-folder.h>
37 #include <string.h>
38
39 #include <modest-header-view.h>
40 #include <modest-header-view-priv.h>
41 #include <modest-dnd.h>
42 #include <modest-tny-folder.h>
43 #include <modest-debug.h>
44 #include <modest-ui-actions.h>
45 #include <modest-marshal.h>
46 #include <modest-text-utils.h>
47 #include <modest-icon-names.h>
48 #include <modest-runtime.h>
49 #include "modest-platform.h"
50 #include <modest-hbox-cell-renderer.h>
51 #include <modest-vbox-cell-renderer.h>
52 #include <modest-datetime-formatter.h>
53 #include <modest-ui-constants.h>
54 #ifdef MODEST_TOOLKIT_HILDON2
55 #include <hildon/hildon.h>
56 #endif
57
58 static void modest_header_view_class_init  (ModestHeaderViewClass *klass);
59 static void modest_header_view_init        (ModestHeaderView *obj);
60 static void modest_header_view_finalize    (GObject *obj);
61 static void modest_header_view_dispose     (GObject *obj);
62
63 static void          on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
64                                               GtkTreeViewColumn *column, gpointer userdata);
65
66 static gint          cmp_rows               (GtkTreeModel *tree_model,
67                                              GtkTreeIter *iter1,
68                                              GtkTreeIter *iter2,
69                                              gpointer user_data);
70
71 static gint          cmp_subject_rows       (GtkTreeModel *tree_model,
72                                              GtkTreeIter *iter1,
73                                              GtkTreeIter *iter2,
74                                              gpointer user_data);
75
76 static gboolean     filter_row             (GtkTreeModel *model,
77                                             GtkTreeIter *iter,
78                                             gpointer data);
79
80 static void         on_account_removed     (TnyAccountStore *self,
81                                             TnyAccount *account,
82                                             gpointer user_data);
83
84 static void          on_selection_changed   (GtkTreeSelection *sel,
85                                              gpointer user_data);
86
87 static gboolean      on_button_press_event  (GtkWidget * self, GdkEventButton * event,
88                                              gpointer userdata);
89
90 static gboolean      on_button_release_event(GtkWidget * self, GdkEventButton * event,
91                                              gpointer userdata);
92
93 static void          setup_drag_and_drop    (GtkWidget *self);
94
95 static void          enable_drag_and_drop   (GtkWidget *self);
96
97 static void          disable_drag_and_drop  (GtkWidget *self);
98
99 static GtkTreePath * get_selected_row       (GtkTreeView *self, GtkTreeModel **model);
100
101 #ifndef MODEST_TOOLKIT_HILDON2
102 static gboolean      on_focus_in            (GtkWidget     *sef,
103                                              GdkEventFocus *event,
104                                              gpointer       user_data);
105
106 static gboolean      on_focus_out            (GtkWidget     *self,
107                                               GdkEventFocus *event,
108                                               gpointer       user_data);
109 #endif
110
111 static void          folder_monitor_update  (TnyFolderObserver *self,
112                                              TnyFolderChange *change);
113
114 static void          tny_folder_observer_init (TnyFolderObserverIface *klass);
115
116 static void          _clipboard_set_selected_data (ModestHeaderView *header_view, gboolean delete);
117
118 static void          _clear_hidding_filter (ModestHeaderView *header_view);
119
120 static void          modest_header_view_notify_observers(ModestHeaderView *header_view,
121                                                          GtkTreeModel *model,
122                                                          const gchar *tny_folder_id);
123
124 static gboolean      modest_header_view_on_expose_event (GtkTreeView *header_view,
125                                                          GdkEventExpose *event,
126                                                          gpointer user_data);
127
128 static void         on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata);
129 static void         update_style (ModestHeaderView *self);
130 static void         modest_header_view_refilter_by_chunks (ModestHeaderView *self);
131
132 typedef enum {
133         HEADER_VIEW_NON_EMPTY,
134         HEADER_VIEW_EMPTY,
135         HEADER_VIEW_INIT
136 } HeaderViewStatus;
137
138 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
139 struct _ModestHeaderViewPrivate {
140         TnyFolder            *folder;
141         ModestHeaderViewStyle style;
142         gboolean is_outbox;
143
144         TnyFolderMonitor     *monitor;
145         GMutex               *observers_lock;
146
147         /*header-view-observer observer*/
148         GMutex *observer_list_lock;
149         GSList *observer_list;
150
151         /* not unref this object, its a singlenton */
152         ModestEmailClipboard *clipboard;
153
154         /* Filter tree model */
155         gchar **hidding_ids;
156         guint   n_selected;
157         GtkTreeRowReference *autoselect_reference;
158         ModestHeaderViewFilter filter;
159 #ifdef MODEST_TOOLKIT_HILDON2
160         GtkWidget *live_search;
161         guint live_search_timeout;
162 #endif
163
164         gint    sort_colid[2][TNY_FOLDER_TYPE_NUM];
165         gint    sort_type[2][TNY_FOLDER_TYPE_NUM];
166
167         gulong  selection_changed_handler;
168         gulong  acc_removed_handler;
169
170         GList *drag_begin_cached_selected_rows;
171
172         HeaderViewStatus status;
173         guint status_timeout;
174         gboolean notify_status; /* whether or not the filter_row should notify about changes in the filtering */
175
176         ModestDatetimeFormatter *datetime_formatter;
177
178         GtkCellRenderer *renderer_subject;
179         GtkCellRenderer *renderer_address;
180         GtkCellRenderer *renderer_date_status;
181
182         GdkColor active_color;
183         GdkColor secondary_color;
184
185         gchar *filter_string;
186         gchar **filter_string_splitted;
187         gboolean filter_date_range;
188         time_t date_range_start;
189         time_t date_range_end;
190
191         guint refilter_handler_id;
192         GtkTreeModel *filtered_model;
193         GtkTreeIter refilter_iter;
194 };
195
196 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
197 struct _HeadersCountChangedHelper {
198         ModestHeaderView *self;
199         TnyFolderChange  *change;
200 };
201
202
203 #define MODEST_HEADER_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
204                                                 MODEST_TYPE_HEADER_VIEW, \
205                                                 ModestHeaderViewPrivate))
206
207
208
209 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
210
211 #define _HEADER_VIEW_SUBJECT_FOLD "_subject_modest_header_view"
212 #define _HEADER_VIEW_FROM_FOLD "_from_modest_header_view"
213 #define _HEADER_VIEW_TO_FOLD "_to_modest_header_view"
214 #define _HEADER_VIEW_CC_FOLD "_cc_modest_header_view"
215 #define _HEADER_VIEW_BCC_FOLD "_bcc_modest_header_view"
216
217 enum {
218         HEADER_SELECTED_SIGNAL,
219         HEADER_ACTIVATED_SIGNAL,
220         ITEM_NOT_FOUND_SIGNAL,
221         MSG_COUNT_CHANGED_SIGNAL,
222         UPDATING_MSG_LIST_SIGNAL,
223         LAST_SIGNAL
224 };
225
226 /* globals */
227 static GObjectClass *parent_class = NULL;
228
229 /* uncomment the following if you have defined any signals */
230 static guint signals[LAST_SIGNAL] = {0};
231
232 GType
233 modest_header_view_get_type (void)
234 {
235         static GType my_type = 0;
236         if (!my_type) {
237                 static const GTypeInfo my_info = {
238                         sizeof(ModestHeaderViewClass),
239                         NULL,           /* base init */
240                         NULL,           /* base finalize */
241                         (GClassInitFunc) modest_header_view_class_init,
242                         NULL,           /* class finalize */
243                         NULL,           /* class data */
244                         sizeof(ModestHeaderView),
245                         1,              /* n_preallocs */
246                         (GInstanceInitFunc) modest_header_view_init,
247                         NULL
248                 };
249
250                 static const GInterfaceInfo tny_folder_observer_info =
251                 {
252                         (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
253                         NULL,         /* interface_finalize */
254                         NULL          /* interface_data */
255                 };
256                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
257                                                   "ModestHeaderView",
258                                                   &my_info, 0);
259
260                 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
261                                              &tny_folder_observer_info);
262
263
264         }
265         return my_type;
266 }
267
268 static void
269 modest_header_view_class_init (ModestHeaderViewClass *klass)
270 {
271         GObjectClass *gobject_class;
272         gobject_class = (GObjectClass*) klass;
273
274         parent_class            = g_type_class_peek_parent (klass);
275         gobject_class->finalize = modest_header_view_finalize;
276         gobject_class->dispose = modest_header_view_dispose;
277
278         g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
279
280         signals[HEADER_SELECTED_SIGNAL] =
281                 g_signal_new ("header_selected",
282                               G_TYPE_FROM_CLASS (gobject_class),
283                               G_SIGNAL_RUN_FIRST,
284                               G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
285                               NULL, NULL,
286                               g_cclosure_marshal_VOID__POINTER,
287                               G_TYPE_NONE, 1, G_TYPE_POINTER);
288
289         signals[HEADER_ACTIVATED_SIGNAL] =
290                 g_signal_new ("header_activated",
291                               G_TYPE_FROM_CLASS (gobject_class),
292                               G_SIGNAL_RUN_FIRST,
293                               G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
294                               NULL, NULL,
295                               gtk_marshal_VOID__POINTER_POINTER,
296                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
297
298
299         signals[ITEM_NOT_FOUND_SIGNAL] =
300                 g_signal_new ("item_not_found",
301                               G_TYPE_FROM_CLASS (gobject_class),
302                               G_SIGNAL_RUN_FIRST,
303                               G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
304                               NULL, NULL,
305                               g_cclosure_marshal_VOID__INT,
306                               G_TYPE_NONE, 1, G_TYPE_INT);
307
308         signals[MSG_COUNT_CHANGED_SIGNAL] =
309                 g_signal_new ("msg_count_changed",
310                               G_TYPE_FROM_CLASS (gobject_class),
311                               G_SIGNAL_RUN_FIRST,
312                               G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
313                               NULL, NULL,
314                               modest_marshal_VOID__POINTER_POINTER,
315                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
316
317         signals[UPDATING_MSG_LIST_SIGNAL] =
318                 g_signal_new ("updating-msg-list",
319                               G_TYPE_FROM_CLASS (gobject_class),
320                               G_SIGNAL_RUN_FIRST,
321                               G_STRUCT_OFFSET (ModestHeaderViewClass, updating_msg_list),
322                               NULL, NULL,
323                               g_cclosure_marshal_VOID__BOOLEAN,
324                               G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
325
326 #ifdef MODEST_TOOLKIT_HILDON2
327         gtk_rc_parse_string ("class \"ModestHeaderView\" style \"fremantle-touchlist\"");
328
329 #endif
330 }
331
332 static void
333 tny_folder_observer_init (TnyFolderObserverIface *klass)
334 {
335         klass->update = folder_monitor_update;
336 }
337
338 static GtkTreeViewColumn*
339 get_new_column (const gchar *name, GtkCellRenderer *renderer,
340                 gboolean resizable, gint sort_col_id, gboolean show_as_text,
341                 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
342 {
343         GtkTreeViewColumn *column;
344
345         column =  gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
346         gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
347
348         gtk_tree_view_column_set_resizable (column, resizable);
349         if (resizable)
350                 gtk_tree_view_column_set_expand (column, TRUE);
351
352         if (show_as_text)
353                 gtk_tree_view_column_add_attribute (column, renderer, "text",
354                                                     sort_col_id);
355         if (sort_col_id >= 0)
356                 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
357
358         gtk_tree_view_column_set_sort_indicator (column, FALSE);
359         gtk_tree_view_column_set_reorderable (column, TRUE);
360
361         if (cell_data_func)
362                 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
363                                                         user_data, NULL);
364         return column;
365 }
366
367
368 static void
369 remove_all_columns (ModestHeaderView *obj)
370 {
371         GList *columns, *cursor;
372
373         columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
374
375         for (cursor = columns; cursor; cursor = cursor->next)
376                 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
377                                              GTK_TREE_VIEW_COLUMN(cursor->data));
378         g_list_free (columns);
379 }
380
381 gboolean
382 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
383 {
384         GtkTreeModel *sortable, *filter_model;
385         GtkTreeViewColumn *column=NULL;
386         GtkTreeSelection *selection = NULL;
387         GtkCellRenderer *renderer_header,
388                 *renderer_attach, *renderer_compact_date_or_status;
389         GtkCellRenderer *renderer_compact_header, *renderer_recpt_box,
390                 *renderer_subject_box, *renderer_recpt,
391                 *renderer_priority;
392         ModestHeaderViewPrivate *priv;
393         GtkTreeViewColumn *compact_column = NULL;
394         const GList *cursor;
395
396         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
397         g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, FALSE);
398
399         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
400
401         priv->is_outbox = (type == TNY_FOLDER_TYPE_OUTBOX);
402
403         /* TODO: check whether these renderers need to be freed */
404         renderer_attach  = gtk_cell_renderer_pixbuf_new ();
405         renderer_priority  = gtk_cell_renderer_pixbuf_new ();
406         renderer_header  = gtk_cell_renderer_text_new ();
407
408         renderer_compact_header = modest_vbox_cell_renderer_new ();
409         renderer_recpt_box = modest_hbox_cell_renderer_new ();
410         renderer_subject_box = modest_hbox_cell_renderer_new ();
411         renderer_recpt = gtk_cell_renderer_text_new ();
412         priv->renderer_address = renderer_recpt;
413         priv->renderer_subject = gtk_cell_renderer_text_new ();
414         renderer_compact_date_or_status  = gtk_cell_renderer_text_new ();
415         priv->renderer_date_status = renderer_compact_date_or_status;
416
417         modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
418         g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
419         modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
420         g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
421         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
422         g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
423         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), priv->renderer_subject, TRUE);
424         g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", priv->renderer_subject);
425         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
426         g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
427         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
428         g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
429         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
430         g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
431
432 #ifdef MODEST_TOOLKIT_HILDON2
433         g_object_set (G_OBJECT (renderer_compact_header), "xpad", 0, NULL);
434 #endif
435         g_object_set (G_OBJECT (renderer_subject_box), "yalign", 1.0, NULL);
436 #ifndef MODEST_TOOLKIT_GTK
437         gtk_cell_renderer_set_fixed_size (renderer_subject_box, -1, 32);
438         gtk_cell_renderer_set_fixed_size (renderer_recpt_box, -1, 32);
439 #endif
440         g_object_set (G_OBJECT (renderer_recpt_box), "yalign", 0.0, NULL);
441         g_object_set(G_OBJECT(renderer_header),
442                      "ellipsize", PANGO_ELLIPSIZE_END,
443                      NULL);
444         g_object_set (G_OBJECT (priv->renderer_subject),
445                       "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 1.0,
446                       NULL);
447         gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (priv->renderer_subject), 1);
448         g_object_set (G_OBJECT (renderer_recpt),
449                       "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.1,
450                       NULL);
451         gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_recpt), 1);
452         g_object_set(G_OBJECT(renderer_compact_date_or_status),
453                      "xalign", 1.0, "yalign", 0.1,
454                      NULL);
455         gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_compact_date_or_status), 1);
456 #ifdef MODEST_TOOLKIT_HILDON2
457         g_object_set (G_OBJECT (renderer_priority),
458                       "yalign", 0.5,
459                       "xalign", 0.0, NULL);
460         g_object_set (G_OBJECT (renderer_attach),
461                       "yalign", 0.5,
462                       "xalign", 0.0, NULL);
463 #else
464         g_object_set (G_OBJECT (renderer_priority),
465                       "yalign", 0.5, NULL);
466         g_object_set (G_OBJECT (renderer_attach),
467                       "yalign", 0.0, NULL);
468 #endif
469
470 #ifdef MODEST_TOOLKIT_HILDON1
471         gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 26);
472         gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 26);
473         gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
474 #elif MODEST_TOOLKIT_HILDON2
475         gtk_cell_renderer_set_fixed_size (renderer_attach, 24 + MODEST_MARGIN_DEFAULT, 26);
476         gtk_cell_renderer_set_fixed_size (renderer_priority, 24 + MODEST_MARGIN_DEFAULT, 26);
477         gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
478 #else
479         gtk_cell_renderer_set_fixed_size (renderer_attach, 16, 16);
480         gtk_cell_renderer_set_fixed_size (renderer_priority, 16, 16);
481         /* gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64); */
482 #endif
483
484         remove_all_columns (self);
485
486         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
487         gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
488         sortable = NULL;
489         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
490         if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
491                 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
492         }
493
494         /* Add new columns */
495         for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
496                 ModestHeaderViewColumn col =
497                         (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
498
499                 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
500                         g_printerr ("modest: invalid column %d in column list\n", col);
501                         continue;
502                 }
503
504                 switch (col) {
505
506                 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
507                         column = get_new_column (_("A"), renderer_attach, FALSE,
508                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
509                                                  FALSE,
510                                                  (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
511                                                  NULL);
512                         gtk_tree_view_column_set_fixed_width (column, 45);
513                         break;
514
515
516                 case MODEST_HEADER_VIEW_COLUMN_FROM:
517                         column = get_new_column (_("From"), renderer_header, TRUE,
518                                                  TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
519                                                  TRUE,
520                                                  (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
521                                                  GINT_TO_POINTER(TRUE));
522                         break;
523
524                 case MODEST_HEADER_VIEW_COLUMN_TO:
525                         column = get_new_column (_("To"), renderer_header, TRUE,
526                                                  TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
527                                                  TRUE,
528                                                  (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
529                                                  GINT_TO_POINTER(FALSE));
530                         break;
531
532                 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
533                         column = get_new_column (_("Header"), renderer_compact_header, TRUE,
534                                                      TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
535                                                      FALSE,
536                                                      (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
537                                                      GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
538                         compact_column = column;
539                         break;
540
541                 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
542                         column = get_new_column (_("Header"), renderer_compact_header, TRUE,
543                                                  TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
544                                                  FALSE,
545                                                  (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
546                                                  GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
547                                                                  MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
548                                                                  MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
549                         compact_column = column;
550                         break;
551
552
553                 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
554                         column = get_new_column (_("Subject"), renderer_header, TRUE,
555                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
556                                                  TRUE,
557                                                  (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
558                                                  NULL);
559                         break;
560
561                 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
562                         column = get_new_column (_("Received"), renderer_header, TRUE,
563                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
564                                                  TRUE,
565                                                  (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
566                                                  GINT_TO_POINTER(TRUE));
567                         break;
568
569                 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
570                         column = get_new_column (_("Sent"), renderer_header, TRUE,
571                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
572                                                  TRUE,
573                                                  (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
574                                                  GINT_TO_POINTER(FALSE));
575                         break;
576
577                 case MODEST_HEADER_VIEW_COLUMN_SIZE:
578                         column = get_new_column (_("Size"), renderer_header, TRUE,
579                                                  TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
580                                                  FALSE,
581                                                  (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
582                                                  NULL);
583                         break;
584                 case MODEST_HEADER_VIEW_COLUMN_STATUS:
585                         column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
586                                                  TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
587                                                  FALSE,
588                                                  (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
589                                                  NULL);
590                         break;
591
592                 default:
593                         g_return_val_if_reached(FALSE);
594                 }
595
596                 /* we keep the column id around */
597                 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
598                                    GINT_TO_POINTER(col));
599
600                 /* we need this ptr when sorting the rows */
601                 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
602                                    self);
603                 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
604         }
605
606         if (sortable) {
607                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
608                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
609                                                  (GtkTreeIterCompareFunc) cmp_rows,
610                                                  compact_column, NULL);
611                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
612                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
613                                                  (GtkTreeIterCompareFunc) cmp_subject_rows,
614                                                  compact_column, NULL);
615         }
616
617         update_style (self);
618         g_signal_connect (G_OBJECT (self), "notify::style", G_CALLBACK (on_notify_style), (gpointer) self);
619
620         return TRUE;
621 }
622
623 static void
624 datetime_format_changed (ModestDatetimeFormatter *formatter,
625                          ModestHeaderView *self)
626 {
627         gtk_widget_queue_draw (GTK_WIDGET (self));
628 }
629
630 static void
631 modest_header_view_init (ModestHeaderView *obj)
632 {
633         ModestHeaderViewPrivate *priv;
634         guint i, j;
635
636         priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
637
638         priv->folder  = NULL;
639         priv->is_outbox = FALSE;
640
641         priv->monitor        = NULL;
642         priv->observers_lock = g_mutex_new ();
643         priv->autoselect_reference = NULL;
644
645         priv->status  = HEADER_VIEW_INIT;
646         priv->status_timeout = 0;
647         priv->notify_status = TRUE;
648
649         priv->observer_list_lock = g_mutex_new();
650         priv->observer_list = NULL;
651
652         priv->clipboard = modest_runtime_get_email_clipboard ();
653         priv->hidding_ids = NULL;
654         priv->n_selected = 0;
655         priv->filter = MODEST_HEADER_VIEW_FILTER_NONE;
656 #ifdef MODEST_TOOLKIT_HILDON2
657         priv->live_search = NULL;
658 #endif
659         priv->filter_string = NULL;
660         priv->filter_string_splitted = NULL;
661         priv->filter_date_range = FALSE;
662         priv->selection_changed_handler = 0;
663         priv->acc_removed_handler = 0;
664
665         priv->filtered_model = NULL;
666         priv->refilter_handler_id = 0;
667
668         /* Sort parameters */
669         for (j=0; j < 2; j++) {
670                 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
671                         priv->sort_colid[j][i] = -1;
672                         priv->sort_type[j][i] = GTK_SORT_DESCENDING;
673                 }
674         }
675
676         priv->datetime_formatter = modest_datetime_formatter_new ();
677         g_signal_connect (G_OBJECT (priv->datetime_formatter), "format-changed",
678                           G_CALLBACK (datetime_format_changed), (gpointer) obj);
679
680         setup_drag_and_drop (GTK_WIDGET(obj));
681 }
682
683 static void
684 modest_header_view_dispose (GObject *obj)
685 {
686         ModestHeaderView        *self;
687         ModestHeaderViewPrivate *priv;
688         GtkTreeSelection *sel;
689
690         self = MODEST_HEADER_VIEW(obj);
691         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
692
693         if (priv->refilter_handler_id > 0) {
694                 g_source_remove (priv->refilter_handler_id);
695                 priv->refilter_handler_id = 0;
696                 priv->filtered_model = NULL;
697         }
698
699 #ifdef MODEST_TOOLKIT_HILDON2
700         if (priv->live_search_timeout > 0) {
701                 g_source_remove (priv->live_search_timeout);
702                 priv->live_search_timeout = 0;
703         }
704 #endif
705
706         if (priv->datetime_formatter) {
707                 g_object_unref (priv->datetime_formatter);
708                 priv->datetime_formatter = NULL;
709         }
710
711         /* Free in the dispose to avoid unref cycles */
712         if (priv->folder) {
713                 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
714                 g_object_unref (G_OBJECT (priv->folder));
715                 priv->folder = NULL;
716         }
717
718         /* We need to do this here in the dispose because the
719            selection won't exist when finalizing */
720         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
721         if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
722                 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
723                 priv->selection_changed_handler = 0;
724         }
725
726         G_OBJECT_CLASS(parent_class)->dispose (obj);
727 }
728
729 static void
730 modest_header_view_finalize (GObject *obj)
731 {
732         ModestHeaderView        *self;
733         ModestHeaderViewPrivate *priv;
734
735         self = MODEST_HEADER_VIEW(obj);
736         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
737
738         if (g_signal_handler_is_connected (modest_runtime_get_account_store (),
739                                            priv->acc_removed_handler)) {
740                 g_signal_handler_disconnect (modest_runtime_get_account_store (),
741                                              priv->acc_removed_handler);
742         }
743
744         /* There is no need to lock because there should not be any
745          * reference to self now. */
746         g_mutex_free(priv->observer_list_lock);
747         g_slist_free(priv->observer_list);
748
749         g_mutex_lock (priv->observers_lock);
750         if (priv->monitor) {
751                 tny_folder_monitor_stop (priv->monitor);
752                 g_object_unref (G_OBJECT (priv->monitor));
753         }
754         g_mutex_unlock (priv->observers_lock);
755         g_mutex_free (priv->observers_lock);
756
757         /* Clear hidding array created by cut operation */
758         _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
759
760         if (priv->autoselect_reference != NULL) {
761                 gtk_tree_row_reference_free (priv->autoselect_reference);
762                 priv->autoselect_reference = NULL;
763         }
764
765         if (priv->filter_string) {
766                 g_free (priv->filter_string);
767         }
768
769         if (priv->filter_string_splitted) {
770                 g_strfreev (priv->filter_string_splitted);
771         }
772
773         G_OBJECT_CLASS(parent_class)->finalize (obj);
774 }
775
776
777 GtkWidget*
778 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
779 {
780         GObject *obj;
781         GtkTreeSelection *sel;
782         ModestHeaderView *self;
783         ModestHeaderViewPrivate *priv;
784
785         g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
786                               NULL);
787
788         obj  = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
789         self = MODEST_HEADER_VIEW(obj);
790         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
791
792         modest_header_view_set_style   (self, style);
793
794         gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
795         gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
796         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
797
798         gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
799                                       TRUE); /* alternating row colors */
800
801         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
802         priv->selection_changed_handler =
803                 g_signal_connect_after (sel, "changed",
804                                         G_CALLBACK(on_selection_changed), self);
805
806         g_signal_connect (self, "row-activated",
807                           G_CALLBACK (on_header_row_activated), NULL);
808
809 #ifndef MODEST_TOOLKIT_HILDON2
810         g_signal_connect (self, "focus-in-event",
811                           G_CALLBACK(on_focus_in), NULL);
812         g_signal_connect (self, "focus-out-event",
813                           G_CALLBACK(on_focus_out), NULL);
814 #endif
815
816         g_signal_connect (self, "button-press-event",
817                           G_CALLBACK(on_button_press_event), NULL);
818         g_signal_connect (self, "button-release-event",
819                           G_CALLBACK(on_button_release_event), NULL);
820
821         priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
822                                                       "account_removed",
823                                                       G_CALLBACK (on_account_removed),
824                                                       self);
825
826         g_signal_connect (self, "expose-event",
827                         G_CALLBACK(modest_header_view_on_expose_event),
828                         NULL);
829
830         return GTK_WIDGET(self);
831 }
832
833
834 guint
835 modest_header_view_count_selected_headers (ModestHeaderView *self)
836 {
837         GtkTreeSelection *sel;
838         guint selected_rows;
839
840         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
841
842         /* Get selection object and check selected rows count */
843         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
844         selected_rows = gtk_tree_selection_count_selected_rows (sel);
845
846         return selected_rows;
847 }
848
849 gboolean
850 modest_header_view_has_selected_headers (ModestHeaderView *self)
851 {
852         GtkTreeSelection *sel;
853         gboolean empty;
854
855         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
856
857         /* Get selection object and check selected rows count */
858         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
859         empty = gtk_tree_selection_count_selected_rows (sel) == 0;
860
861         return !empty;
862 }
863
864
865 TnyList *
866 modest_header_view_get_selected_headers (ModestHeaderView *self)
867 {
868         GtkTreeSelection *sel;
869         TnyList *header_list = NULL;
870         TnyHeader *header;
871         GList *list, *tmp = NULL;
872         GtkTreeModel *tree_model = NULL;
873         GtkTreeIter iter;
874
875         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
876
877
878         /* Get selected rows */
879         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
880         list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
881
882         if (list) {
883                 header_list = tny_simple_list_new();
884
885                 list = g_list_reverse (list);
886                 tmp = list;
887                 while (tmp) {
888                         /* get header from selection */
889                         gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
890                         gtk_tree_model_get (tree_model, &iter,
891                                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
892                                             &header, -1);
893                         /* Prepend to list */
894                         tny_list_prepend (header_list, G_OBJECT (header));
895                         g_object_unref (G_OBJECT (header));
896
897                         tmp = g_list_next (tmp);
898                 }
899                 /* Clean up*/
900                 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
901                 g_list_free (list);
902         }
903         return header_list;
904 }
905
906
907 /* scroll our list view so the selected item is visible */
908 static void
909 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
910 {
911 #ifdef MODEST_TOOLKIT_GTK
912
913         GtkTreePath *selected_path;
914         GtkTreePath *start, *end;
915
916         GtkTreeModel *model;
917
918         model         = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
919         selected_path = gtk_tree_model_get_path (model, iter);
920
921         start = gtk_tree_path_new ();
922         end   = gtk_tree_path_new ();
923
924         gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
925
926         if (gtk_tree_path_compare (selected_path, start) < 0 ||
927             gtk_tree_path_compare (end, selected_path) < 0)
928                 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
929                                               selected_path, NULL, TRUE,
930                                               up ? 0.0 : 1.0,
931                                               up ? 0.0 : 1.0);
932         gtk_tree_path_free (selected_path);
933         gtk_tree_path_free (start);
934         gtk_tree_path_free (end);
935
936 #endif /* MODEST_TOOLKIT_GTK */
937 }
938
939
940 void
941 modest_header_view_select_next (ModestHeaderView *self)
942 {
943         GtkTreeSelection *sel;
944         GtkTreeIter iter;
945         GtkTreeModel *model;
946         GtkTreePath *path;
947
948         g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
949
950         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
951         path = get_selected_row (GTK_TREE_VIEW(self), &model);
952         if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
953                 /* Unselect previous path */
954                 gtk_tree_selection_unselect_path (sel, path);
955
956                 /* Move path down and selects new one  */
957                 if (gtk_tree_model_iter_next (model, &iter)) {
958                         gtk_tree_selection_select_iter (sel, &iter);
959                         scroll_to_selected (self, &iter, FALSE);
960                 }
961                 gtk_tree_path_free(path);
962         }
963
964 }
965
966 void
967 modest_header_view_select_prev (ModestHeaderView *self)
968 {
969         GtkTreeSelection *sel;
970         GtkTreeIter iter;
971         GtkTreeModel *model;
972         GtkTreePath *path;
973
974         g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
975
976         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
977         path = get_selected_row (GTK_TREE_VIEW(self), &model);
978         if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
979                 /* Unselect previous path */
980                 gtk_tree_selection_unselect_path (sel, path);
981
982                 /* Move path up */
983                 if (gtk_tree_path_prev (path)) {
984                         gtk_tree_model_get_iter (model, &iter, path);
985
986                         /* Select the new one */
987                         gtk_tree_selection_select_iter (sel, &iter);
988                         scroll_to_selected (self, &iter, TRUE);
989
990                 }
991                 gtk_tree_path_free (path);
992         }
993 }
994
995 GList*
996 modest_header_view_get_columns (ModestHeaderView *self)
997 {
998         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
999
1000         return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1001 }
1002
1003
1004
1005 gboolean
1006 modest_header_view_set_style (ModestHeaderView *self,
1007                               ModestHeaderViewStyle style)
1008 {
1009         ModestHeaderViewPrivate *priv;
1010         gboolean show_col_headers = FALSE;
1011         ModestHeaderViewStyle old_style;
1012
1013         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
1014         g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
1015                               FALSE);
1016
1017         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1018         if (priv->style == style)
1019                 return TRUE; /* nothing to do */
1020
1021         switch (style) {
1022         case MODEST_HEADER_VIEW_STYLE_DETAILS:
1023                 show_col_headers = TRUE;
1024                 break;
1025         case MODEST_HEADER_VIEW_STYLE_TWOLINES:
1026                 break;
1027         default:
1028                 g_return_val_if_reached (FALSE);
1029         }
1030         gtk_tree_view_set_headers_visible   (GTK_TREE_VIEW(self), show_col_headers);
1031         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
1032
1033         old_style   = priv->style;
1034         priv->style = style;
1035
1036         return TRUE;
1037 }
1038
1039
1040 ModestHeaderViewStyle
1041 modest_header_view_get_style (ModestHeaderView *self)
1042 {
1043         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
1044
1045         return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
1046 }
1047
1048 /* This is used to automatically select the first header if the user
1049  * has not selected any header yet.
1050  */
1051 static gboolean
1052 modest_header_view_on_expose_event(GtkTreeView *header_view,
1053                                    GdkEventExpose *event,
1054                                    gpointer user_data)
1055 {
1056         GtkTreeSelection *sel;
1057         GtkTreeModel *model;
1058         GtkTreeIter tree_iter;
1059         ModestHeaderViewPrivate *priv;
1060
1061         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1062         model = gtk_tree_view_get_model(header_view);
1063
1064         if (!model)
1065                 return FALSE;
1066
1067 #ifdef MODEST_TOOLKIT_HILDON2
1068         return FALSE;
1069 #endif
1070         sel = gtk_tree_view_get_selection(header_view);
1071         if(!gtk_tree_selection_count_selected_rows(sel)) {
1072                 if (gtk_tree_model_get_iter_first(model, &tree_iter)) {
1073                         GtkTreePath *tree_iter_path;
1074                         /* Prevent the widget from getting the focus
1075                            when selecting the first item */
1076                         tree_iter_path = gtk_tree_model_get_path (model, &tree_iter);
1077                         g_object_set(header_view, "can-focus", FALSE, NULL);
1078                         gtk_tree_selection_select_iter(sel, &tree_iter);
1079                         gtk_tree_view_set_cursor (header_view, tree_iter_path, NULL, FALSE);
1080                         g_object_set(header_view, "can-focus", TRUE, NULL);
1081                         if (priv->autoselect_reference) {
1082                                 gtk_tree_row_reference_free (priv->autoselect_reference);
1083                         }
1084                         priv->autoselect_reference = gtk_tree_row_reference_new (model, tree_iter_path);
1085                         gtk_tree_path_free (tree_iter_path);
1086                 }
1087         } else {
1088                 if (priv->autoselect_reference != NULL && gtk_tree_row_reference_valid (priv->autoselect_reference)) {
1089                         gboolean moved_selection = FALSE;
1090                         GtkTreePath * last_path;
1091                         if (gtk_tree_selection_count_selected_rows (sel) != 1) {
1092                                 moved_selection = TRUE;
1093                         } else {
1094                                 GList *rows;
1095
1096                                 rows = gtk_tree_selection_get_selected_rows (sel, NULL);
1097                                 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1098                                 if (gtk_tree_path_compare (last_path, (GtkTreePath *) rows->data) != 0)
1099                                         moved_selection = TRUE;
1100                                 g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
1101                                 g_list_free (rows);
1102                                 gtk_tree_path_free (last_path);
1103                         }
1104                         if (moved_selection) {
1105                                 gtk_tree_row_reference_free (priv->autoselect_reference);
1106                                 priv->autoselect_reference = NULL;
1107                         } else {
1108
1109                                 if (gtk_tree_model_get_iter_first (model, &tree_iter)) {
1110                                         GtkTreePath *current_path;
1111                                         current_path = gtk_tree_model_get_path (model, &tree_iter);
1112                                         last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1113                                         if (gtk_tree_path_compare (current_path, last_path) != 0) {
1114                                                 g_object_set(header_view, "can-focus", FALSE, NULL);
1115                                                 gtk_tree_selection_unselect_all (sel);
1116                                                 gtk_tree_selection_select_iter(sel, &tree_iter);
1117                                                 gtk_tree_view_set_cursor (header_view, current_path, NULL, FALSE);
1118                                                 g_object_set(header_view, "can-focus", TRUE, NULL);
1119                                                 gtk_tree_row_reference_free (priv->autoselect_reference);
1120                                                 priv->autoselect_reference = gtk_tree_row_reference_new (model, current_path);
1121                                         }
1122                                         gtk_tree_path_free (current_path);
1123                                         gtk_tree_path_free (last_path);
1124                                 }
1125                         }
1126                 }
1127         }
1128
1129         return FALSE;
1130 }
1131
1132 TnyFolder*
1133 modest_header_view_get_folder (ModestHeaderView *self)
1134 {
1135         ModestHeaderViewPrivate *priv;
1136
1137         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
1138
1139         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1140
1141         if (priv->folder)
1142                 g_object_ref (priv->folder);
1143
1144         return priv->folder;
1145 }
1146
1147 static void
1148 set_folder_intern_get_headers_async_cb (TnyFolder *folder,
1149                                         gboolean cancelled,
1150                                         TnyList *headers,
1151                                         GError *err,
1152                                         gpointer user_data)
1153 {
1154         ModestHeaderView *self;
1155         ModestHeaderViewPrivate *priv;
1156
1157         g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1158
1159         self = MODEST_HEADER_VIEW (user_data);
1160         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1161
1162         if (cancelled || err)
1163                 return;
1164
1165         /* Add IDLE observer (monitor) and another folder observer for
1166            new messages (self) */
1167         g_mutex_lock (priv->observers_lock);
1168         if (priv->monitor) {
1169                 tny_folder_monitor_stop (priv->monitor);
1170                 g_object_unref (G_OBJECT (priv->monitor));
1171         }
1172         priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
1173         tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
1174         tny_folder_monitor_start (priv->monitor);
1175         g_mutex_unlock (priv->observers_lock);
1176 }
1177
1178 static void
1179 modest_header_view_set_folder_intern (ModestHeaderView *self,
1180                                       TnyFolder *folder,
1181                                       gboolean refresh)
1182 {
1183         TnyFolderType type;
1184         TnyList *headers;
1185         ModestHeaderViewPrivate *priv;
1186         GList *cols, *cursor;
1187         GtkTreeModel *filter_model, *sortable;
1188         guint sort_colid;
1189         GtkSortType sort_type;
1190
1191         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1192
1193         headers = TNY_LIST (tny_gtk_header_list_model_new ());
1194         tny_gtk_header_list_model_set_update_in_batches (TNY_GTK_HEADER_LIST_MODEL (headers), 300);
1195
1196         /* Start the monitor in the callback of the
1197            tny_gtk_header_list_model_set_folder call. It's crucial to
1198            do it there and not just after the call because we want the
1199            monitor to observe only the headers returned by the
1200            tny_folder_get_headers_async call that it's inside the
1201            tny_gtk_header_list_model_set_folder call. This way the
1202            monitor infrastructure could successfully cope with
1203            duplicates. For example if a tny_folder_add_msg_async is
1204            happening while tny_gtk_header_list_model_set_folder is
1205            invoked, then the first call could add a header that will
1206            be added again by tny_gtk_header_list_model_set_folder, so
1207            we'd end up with duplicate headers. sergio */
1208         tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
1209                                               folder, refresh,
1210                                               set_folder_intern_get_headers_async_cb,
1211                                               NULL, self);
1212
1213         /* Init filter_row function to examine empty status */
1214         priv->status  = HEADER_VIEW_INIT;
1215
1216         /* Create sortable model */
1217         sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (headers));
1218         g_object_unref (headers);
1219
1220         /* Create a tree model filter to hide and show rows for cut operations  */
1221         filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (sortable), NULL);
1222         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1223                                                 filter_row, self, NULL);
1224         g_object_unref (sortable);
1225
1226         /* install our special sorting functions */
1227         cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1228
1229         /* Restore sort column id */
1230         if (cols) {
1231                 type  = modest_tny_folder_guess_folder_type (folder);
1232                 if (type == TNY_FOLDER_TYPE_INVALID)
1233                         g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1234
1235                 sort_colid = modest_header_view_get_sort_column_id (self, type);
1236                 sort_type = modest_header_view_get_sort_type (self, type);
1237                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1238                                                       sort_colid,
1239                                                       sort_type);
1240                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1241                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1242                                                  (GtkTreeIterCompareFunc) cmp_rows,
1243                                                  cols->data, NULL);
1244                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1245                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1246                                                  (GtkTreeIterCompareFunc) cmp_subject_rows,
1247                                                  cols->data, NULL);
1248         }
1249
1250         /* Set new model */
1251         gtk_tree_view_set_model (GTK_TREE_VIEW (self), filter_model);
1252         modest_header_view_notify_observers (self, sortable, tny_folder_get_id (folder));
1253         g_object_unref (filter_model);
1254
1255         /* Free */
1256         g_list_free (cols);
1257 }
1258
1259 void
1260 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1261                                       guint sort_colid,
1262                                       GtkSortType sort_type)
1263 {
1264         ModestHeaderViewPrivate *priv = NULL;
1265         GtkTreeModel *sortable = NULL, *filter_model = NULL;
1266         TnyFolderType type;
1267
1268         g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1269         g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1270
1271         /* Get model and private data */
1272         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
1273         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1274         if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
1275                 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1276         }
1277
1278         /* Sort tree model */
1279         type  = modest_tny_folder_guess_folder_type (priv->folder);
1280         if (type == TNY_FOLDER_TYPE_INVALID)
1281                 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1282         else {
1283                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1284                                                       sort_colid,
1285                                                       sort_type);
1286                 /* Store new sort parameters */
1287                 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1288         }
1289 }
1290
1291 void
1292 modest_header_view_set_sort_params (ModestHeaderView *self,
1293                                     guint sort_colid,
1294                                     GtkSortType sort_type,
1295                                     TnyFolderType type)
1296 {
1297         ModestHeaderViewPrivate *priv;
1298         ModestHeaderViewStyle style;
1299
1300         g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1301         g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1302         g_return_if_fail (type != TNY_FOLDER_TYPE_INVALID);
1303
1304         style = modest_header_view_get_style   (self);
1305         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1306
1307         priv->sort_colid[style][type] = sort_colid;
1308         priv->sort_type[style][type] = sort_type;
1309 }
1310
1311 gint
1312 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1313                                        TnyFolderType type)
1314 {
1315         ModestHeaderViewPrivate *priv;
1316         ModestHeaderViewStyle style;
1317
1318         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
1319         g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, 0);
1320
1321         style = modest_header_view_get_style   (self);
1322         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1323
1324         return priv->sort_colid[style][type];
1325 }
1326
1327 GtkSortType
1328 modest_header_view_get_sort_type (ModestHeaderView *self,
1329                                   TnyFolderType type)
1330 {
1331         ModestHeaderViewPrivate *priv;
1332         ModestHeaderViewStyle style;
1333
1334         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), GTK_SORT_DESCENDING);
1335         g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, GTK_SORT_DESCENDING);
1336
1337         style = modest_header_view_get_style   (self);
1338         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1339
1340         return priv->sort_type[style][type];
1341 }
1342
1343 typedef struct {
1344         ModestHeaderView *header_view;
1345         RefreshAsyncUserCallback cb;
1346         gpointer user_data;
1347 } SetFolderHelper;
1348
1349 static void
1350 folder_refreshed_cb (ModestMailOperation *mail_op,
1351                      TnyFolder *folder,
1352                      gpointer user_data)
1353 {
1354         ModestHeaderViewPrivate *priv;
1355         SetFolderHelper *info;
1356
1357         info = (SetFolderHelper*) user_data;
1358
1359         priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1360
1361         /* User callback */
1362         if (info->cb)
1363                 info->cb (mail_op, folder, info->user_data);
1364
1365         /* Start the folder count changes observer. We do not need it
1366            before the refresh. Note that the monitor could still be
1367            called for this refresh but now we know that the callback
1368            was previously called */
1369         g_mutex_lock (priv->observers_lock);
1370         tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1371         g_mutex_unlock (priv->observers_lock);
1372
1373         /* Notify the observers that the update is over */
1374         g_signal_emit (G_OBJECT (info->header_view),
1375                        signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1376
1377         /* Allow filtering notifications from now on if the current
1378            folder is still the same (if not then the user has selected
1379            another one to refresh, we should wait until that refresh
1380            finishes) */
1381         if (priv->folder == folder)
1382                 priv->notify_status = TRUE;
1383
1384         /* Frees */
1385         g_object_unref (info->header_view);
1386         g_free (info);
1387 }
1388
1389 static void
1390 refresh_folder_error_handler (ModestMailOperation *mail_op,
1391                               gpointer user_data)
1392 {
1393         const GError *error = modest_mail_operation_get_error (mail_op);
1394
1395         if (error->code == TNY_SYSTEM_ERROR_MEMORY ||
1396             error->code == TNY_IO_ERROR_WRITE ||
1397             error->code == TNY_IO_ERROR_READ) {
1398                 ModestMailOperationStatus st = modest_mail_operation_get_status (mail_op);
1399                 /* If the mail op has been cancelled then it's not an error: don't show any message */
1400                 if (st != MODEST_MAIL_OPERATION_STATUS_CANCELED) {
1401                         gchar *msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
1402                         modest_platform_information_banner (NULL, NULL, msg);
1403                         g_free (msg);
1404                 }
1405         }
1406 }
1407
1408 void
1409 modest_header_view_set_folder (ModestHeaderView *self,
1410                                TnyFolder *folder,
1411                                gboolean refresh,
1412                                ModestWindow *progress_window,
1413                                RefreshAsyncUserCallback callback,
1414                                gpointer user_data)
1415 {
1416         ModestHeaderViewPrivate *priv;
1417
1418         g_return_if_fail (self);
1419
1420         priv =     MODEST_HEADER_VIEW_GET_PRIVATE(self);
1421
1422         if (priv->folder) {
1423                 if (priv->status_timeout) {
1424                         g_source_remove (priv->status_timeout);
1425                         priv->status_timeout = 0;
1426                 }
1427
1428                 g_mutex_lock (priv->observers_lock);
1429                 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1430                 g_object_unref (priv->folder);
1431                 priv->folder = NULL;
1432                 g_mutex_unlock (priv->observers_lock);
1433         }
1434
1435         if (folder) {
1436                 GtkTreeSelection *selection;
1437                 SetFolderHelper *info;
1438                 ModestMailOperation *mail_op = NULL;
1439
1440                 /* Set folder in the model */
1441                 modest_header_view_set_folder_intern (self, folder, refresh);
1442
1443                 /* Pick my reference. Nothing to do with the mail operation */
1444                 priv->folder = g_object_ref (folder);
1445
1446                 /* Do not notify about filterings until the refresh finishes */
1447                 priv->notify_status = FALSE;
1448
1449                 /* Clear the selection if exists */
1450                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1451                 gtk_tree_selection_unselect_all(selection);
1452                 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1453
1454                 /* Notify the observers that the update begins */
1455                 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1456                                0, TRUE, NULL);
1457
1458                 /* create the helper */
1459                 info = g_malloc0 (sizeof (SetFolderHelper));
1460                 info->header_view = g_object_ref (self);
1461                 info->cb = callback;
1462                 info->user_data = user_data;
1463
1464                 /* Create the mail operation (source will be the parent widget) */
1465                 if (progress_window)
1466                         mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(progress_window),
1467                                                                                  refresh_folder_error_handler,
1468                                                                                  NULL, NULL);
1469                 if (refresh) {
1470                         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1471                                                          mail_op);
1472
1473                         /* Refresh the folder asynchronously */
1474                         modest_mail_operation_refresh_folder (mail_op,
1475                                                               folder,
1476                                                               folder_refreshed_cb,
1477                                                               info);
1478                 } else {
1479                         folder_refreshed_cb (mail_op, folder, info);
1480                 }
1481                 /* Free */
1482                 if (mail_op)
1483                         g_object_unref (mail_op);
1484         } else {
1485                 g_mutex_lock (priv->observers_lock);
1486
1487                 if (priv->monitor) {
1488                         tny_folder_monitor_stop (priv->monitor);
1489                         g_object_unref (G_OBJECT (priv->monitor));
1490                         priv->monitor = NULL;
1491                 }
1492
1493                 if (priv->autoselect_reference) {
1494                         gtk_tree_row_reference_free (priv->autoselect_reference);
1495                         priv->autoselect_reference = NULL;
1496                 }
1497
1498                 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
1499
1500                 modest_header_view_notify_observers(self, NULL, NULL);
1501
1502                 g_mutex_unlock (priv->observers_lock);
1503
1504                 /* Notify the observers that the update is over */
1505                 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1506                                0, FALSE, NULL);
1507         }
1508 }
1509
1510 static void
1511 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1512                          GtkTreeViewColumn *column, gpointer userdata)
1513 {
1514         ModestHeaderView *self = NULL;
1515         GtkTreeIter iter;
1516         GtkTreeModel *model = NULL;
1517         TnyHeader *header = NULL;
1518         TnyHeaderFlags flags;
1519
1520         self = MODEST_HEADER_VIEW (treeview);
1521
1522         model = gtk_tree_view_get_model (treeview);
1523         if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1524                 goto frees;
1525
1526         /* get the first selected item */
1527         gtk_tree_model_get (model, &iter,
1528                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1529                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1530                             -1);
1531
1532         /* Dont open DELETED messages */
1533         if (flags & TNY_HEADER_FLAG_DELETED) {
1534                 GtkWidget *win;
1535                 gchar *msg;
1536                 win = gtk_widget_get_ancestor (GTK_WIDGET (treeview), GTK_TYPE_WINDOW);
1537                 msg = modest_ui_actions_get_msg_already_deleted_error_msg (MODEST_WINDOW (win));
1538                 modest_platform_information_banner (NULL, NULL, msg);
1539                 g_free (msg);
1540                 goto frees;
1541         }
1542
1543         /* Emit signal */
1544         g_signal_emit (G_OBJECT(self),
1545                        signals[HEADER_ACTIVATED_SIGNAL],
1546                        0, header, path);
1547
1548         /* Free */
1549  frees:
1550         if (header != NULL)
1551                 g_object_unref (G_OBJECT (header));
1552
1553 }
1554
1555 static void
1556 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1557 {
1558         GtkTreeModel *model;
1559         TnyHeader *header = NULL;
1560         GtkTreePath *path = NULL;
1561         GtkTreeIter iter;
1562         ModestHeaderView *self;
1563         GList *selected = NULL;
1564
1565         g_return_if_fail (sel);
1566         g_return_if_fail (user_data);
1567
1568         self = MODEST_HEADER_VIEW (user_data);
1569
1570         selected = gtk_tree_selection_get_selected_rows (sel, &model);
1571         if (selected != NULL)
1572                 path = (GtkTreePath *) selected->data;
1573         if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1574                 return; /* msg was _un_selected */
1575
1576         gtk_tree_model_get (model, &iter,
1577                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1578                             &header, -1);
1579
1580         /* Emit signal */
1581         g_signal_emit (G_OBJECT(self),
1582                        signals[HEADER_SELECTED_SIGNAL],
1583                        0, header);
1584
1585         g_object_unref (G_OBJECT (header));
1586
1587         /* free all items in 'selected' */
1588         g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1589         g_list_free (selected);
1590 }
1591
1592
1593 /* PROTECTED method. It's useful when we want to force a given
1594    selection to reload a msg. For example if we have selected a header
1595    in offline mode, when Modest become online, we want to reload the
1596    message automatically without an user click over the header */
1597 void
1598 _modest_header_view_change_selection (GtkTreeSelection *selection,
1599                                       gpointer user_data)
1600 {
1601         g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1602         g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1603
1604         on_selection_changed (selection, user_data);
1605 }
1606
1607 static gint
1608 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1609 {
1610         /* HH, LL, NN */
1611         if (p1 == p2)
1612                 return 0;
1613
1614         /* HL HN */
1615         if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1616                 return 1;
1617
1618         /* LH LN */
1619         if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1620                 return -1;
1621
1622         /* NH */
1623         if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1624                 return -1;
1625
1626         /* NL */
1627         return 1;
1628 }
1629
1630 static gint
1631 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1632           gpointer user_data)
1633 {
1634         gint col_id;
1635         gint t1, t2;
1636         gint val1, val2;
1637         gint cmp;
1638
1639         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1640         col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1641
1642
1643         switch (col_id) {
1644         case TNY_HEADER_FLAG_ATTACHMENTS:
1645
1646                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1647                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1648                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1649                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1650
1651                 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1652                         (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1653
1654                 return cmp ? cmp : t1 - t2;
1655
1656         case TNY_HEADER_FLAG_PRIORITY_MASK: {
1657                 TnyHeader *header1 = NULL, *header2 = NULL;
1658
1659                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1660                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1661                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1662                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1663
1664                 /* This is for making priority values respect the intuitive sort relationship
1665                  * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1666
1667                 if (header1 && header2) {
1668                         cmp =  compare_priorities (tny_header_get_priority (header1),
1669                                 tny_header_get_priority (header2));
1670                         g_object_unref (header1);
1671                         g_object_unref (header2);
1672
1673                         return cmp ? cmp : t1 - t2;
1674                 }
1675
1676                 return t1 - t2;
1677         }
1678         default:
1679                 return &iter1 - &iter2; /* oughhhh  */
1680         }
1681 }
1682
1683 static gint
1684 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1685                   gpointer user_data)
1686 {
1687         gint t1, t2;
1688         gchar *val1, *val2;
1689         gint cmp;
1690
1691         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1692
1693         gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1694                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1695         gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1696                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1697
1698         /* Do not use the prefixes for sorting. Consume all the blank
1699            spaces for sorting */
1700         cmp = modest_text_utils_utf8_strcmp (g_strchug (val1 + modest_text_utils_get_subject_prefix_len(val1)),
1701                                              g_strchug (val2 + modest_text_utils_get_subject_prefix_len(val2)),
1702                                              TRUE);
1703
1704         /* If they're equal based on subject without prefix then just
1705            sort them by length. This will show messages like this.
1706            * Fw:
1707            * Fw:Fw:
1708            * Fw:Fw:
1709            * Fw:Fw:Fw:
1710            * */
1711         if (cmp == 0)
1712                 cmp = (g_utf8_strlen (val1, -1) >= g_utf8_strlen (val2, -1)) ? 1 : -1;
1713
1714         g_free (val1);
1715         g_free (val2);
1716         return cmp;
1717 }
1718
1719 /* Drag and drop stuff */
1720 static void
1721 drag_data_get_cb (GtkWidget *widget,
1722                   GdkDragContext *context,
1723                   GtkSelectionData *selection_data,
1724                   guint info,
1725                   guint time,
1726                   gpointer data)
1727 {
1728         ModestHeaderView *self = NULL;
1729         ModestHeaderViewPrivate *priv = NULL;
1730
1731         self = MODEST_HEADER_VIEW (widget);
1732         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1733
1734         /* Set the data. Do not use the current selection because it
1735            could be different than the selection at the beginning of
1736            the d&d */
1737         modest_dnd_selection_data_set_paths (selection_data,
1738                                              priv->drag_begin_cached_selected_rows);
1739 }
1740
1741 /**
1742  * We're caching the selected rows at the beginning because the
1743  * selection could change between drag-begin and drag-data-get, for
1744  * example if we have a set of rows already selected, and then we
1745  * click in one of them (without SHIFT key pressed) and begin a drag,
1746  * the selection at that moment contains all the selected lines, but
1747  * after dropping the selection, the release event provokes that only
1748  * the row used to begin the drag is selected, so at the end the
1749  * drag&drop affects only one rows instead of all the selected ones.
1750  *
1751  */
1752 static void
1753 drag_begin_cb (GtkWidget *widget,
1754                GdkDragContext *context,
1755                gpointer data)
1756 {
1757         ModestHeaderView *self = NULL;
1758         ModestHeaderViewPrivate *priv = NULL;
1759         GtkTreeSelection *selection;
1760
1761         self = MODEST_HEADER_VIEW (widget);
1762         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1763
1764         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1765         priv->drag_begin_cached_selected_rows =
1766                 gtk_tree_selection_get_selected_rows (selection, NULL);
1767 }
1768
1769 /**
1770  * We use the drag-end signal to clear the cached selection, we use
1771  * this because this allways happens, whether or not the d&d was a
1772  * success
1773  */
1774 static void
1775 drag_end_cb (GtkWidget *widget,
1776              GdkDragContext *dc,
1777              gpointer data)
1778 {
1779         ModestHeaderView *self = NULL;
1780         ModestHeaderViewPrivate *priv = NULL;
1781
1782         self = MODEST_HEADER_VIEW (widget);
1783         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1784
1785         /* Free cached data */
1786         g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1787         g_list_free (priv->drag_begin_cached_selected_rows);
1788         priv->drag_begin_cached_selected_rows = NULL;
1789 }
1790
1791 /* Header view drag types */
1792 const GtkTargetEntry header_view_drag_types[] = {
1793         { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1794 };
1795
1796 static void
1797 enable_drag_and_drop (GtkWidget *self)
1798 {
1799 #ifdef MODEST_TOOLKIT_HILDON2
1800         return;
1801 #endif
1802         gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1803                              header_view_drag_types,
1804                              G_N_ELEMENTS (header_view_drag_types),
1805                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1806 }
1807
1808 static void
1809 disable_drag_and_drop (GtkWidget *self)
1810 {
1811 #ifdef MODEST_TOOLKIT_HILDON2
1812         return;
1813 #endif
1814         gtk_drag_source_unset (self);
1815 }
1816
1817 static void
1818 setup_drag_and_drop (GtkWidget *self)
1819 {
1820 #ifdef MODEST_TOOLKIT_HILDON2
1821         return;
1822 #endif
1823         enable_drag_and_drop(self);
1824         g_signal_connect(G_OBJECT (self), "drag_data_get",
1825                          G_CALLBACK(drag_data_get_cb), NULL);
1826
1827         g_signal_connect(G_OBJECT (self), "drag_begin",
1828                          G_CALLBACK(drag_begin_cb), NULL);
1829
1830         g_signal_connect(G_OBJECT (self), "drag_end",
1831                          G_CALLBACK(drag_end_cb), NULL);
1832 }
1833
1834 static GtkTreePath *
1835 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1836 {
1837         GtkTreePath *path = NULL;
1838         GtkTreeSelection *sel = NULL;
1839         GList *rows = NULL;
1840
1841         sel   = gtk_tree_view_get_selection(self);
1842         rows = gtk_tree_selection_get_selected_rows (sel, model);
1843
1844         if ((rows == NULL) || (g_list_length(rows) != 1))
1845                 goto frees;
1846
1847         path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1848
1849
1850         /* Free */
1851  frees:
1852         g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1853         g_list_free(rows);
1854
1855         return path;
1856 }
1857
1858 #ifndef MODEST_TOOLKIT_HILDON2
1859 /*
1860  * This function moves the tree view scroll to the current selected
1861  * row when the widget grabs the focus
1862  */
1863 static gboolean
1864 on_focus_in (GtkWidget     *self,
1865              GdkEventFocus *event,
1866              gpointer       user_data)
1867 {
1868         GtkTreeSelection *selection;
1869         GtkTreeModel *model;
1870         GList *selected = NULL;
1871         GtkTreePath *selected_path = NULL;
1872
1873         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1874         if (!model)
1875                 return FALSE;
1876
1877         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1878         /* If none selected yet, pick the first one */
1879         if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1880                 GtkTreeIter iter;
1881                 GtkTreePath *path;
1882
1883                 /* Return if the model is empty */
1884                 if (!gtk_tree_model_get_iter_first (model, &iter))
1885                         return FALSE;
1886
1887                 path = gtk_tree_model_get_path (model, &iter);
1888                 gtk_tree_selection_select_path (selection, path);
1889                 gtk_tree_path_free (path);
1890         }
1891
1892         /* Need to get the all the rows because is selection multiple */
1893         selected = gtk_tree_selection_get_selected_rows (selection, &model);
1894         if (selected == NULL) return FALSE;
1895         selected_path = (GtkTreePath *) selected->data;
1896
1897         /* Frees */
1898         g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1899         g_list_free (selected);
1900
1901         return FALSE;
1902 }
1903
1904 static gboolean
1905 on_focus_out (GtkWidget     *self,
1906              GdkEventFocus *event,
1907              gpointer       user_data)
1908 {
1909
1910         if (!gtk_widget_is_focus (self)) {
1911                 GtkTreeSelection *selection = NULL;
1912                 GList *selected_rows = NULL;
1913                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1914                 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1915                         selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1916                         g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1917                         gtk_tree_selection_unselect_all (selection);
1918                         gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1919                         g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1920                         g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1921                         g_list_free (selected_rows);
1922                 }
1923         }
1924         return FALSE;
1925 }
1926 #endif
1927
1928 static gboolean
1929 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1930 {
1931         enable_drag_and_drop(self);
1932         return FALSE;
1933 }
1934
1935 static gboolean
1936 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1937 {
1938         GtkTreeSelection *selection = NULL;
1939         GtkTreePath *path = NULL;
1940         gboolean already_selected = FALSE, already_opened = FALSE;
1941         ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
1942
1943         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1944                 GtkTreeIter iter;
1945                 GtkTreeModel *model;
1946
1947                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1948                 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1949
1950                 /* Get header from model */
1951                 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1952                 if (gtk_tree_model_get_iter (model, &iter, path)) {
1953                         GValue value = {0,};
1954                         TnyHeader *header;
1955
1956                         gtk_tree_model_get_value (model, &iter,
1957                                                   TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1958                                                   &value);
1959                         header = (TnyHeader *) g_value_get_object (&value);
1960                         if (TNY_IS_HEADER (header)) {
1961                                 status = modest_tny_all_send_queues_get_msg_status (header);
1962                                 already_opened = modest_window_mgr_find_registered_header (modest_runtime_get_window_mgr (),
1963                                                                                            header, NULL);
1964                         }
1965                         g_value_unset (&value);
1966                 }
1967         }
1968
1969         /* Enable drag and drop only if the user clicks on a row that
1970            it's already selected. If not, let him select items using
1971            the pointer. If the message is in an OUTBOX and in sending
1972            status disable drag and drop as well */
1973         if (!already_selected ||
1974             status == MODEST_TNY_SEND_QUEUE_SENDING ||
1975             already_opened)
1976                 disable_drag_and_drop(self);
1977
1978         if (path != NULL)
1979                 gtk_tree_path_free(path);
1980
1981         /* If it's already opened then do not let the button-press
1982            event go on because it'll perform a message open because
1983            we're clicking on to an already selected header */
1984         return FALSE;
1985 }
1986
1987 static void
1988 folder_monitor_update (TnyFolderObserver *self,
1989                        TnyFolderChange *change)
1990 {
1991         ModestHeaderViewPrivate *priv = NULL;
1992         TnyFolderChangeChanged changed;
1993         TnyFolder *folder = NULL;
1994
1995         changed = tny_folder_change_get_changed (change);
1996
1997         /* Do not notify the observers if the folder of the header
1998            view has changed before this call to the observer
1999            happens */
2000         priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
2001         folder = tny_folder_change_get_folder (change);
2002         if (folder != priv->folder)
2003                 goto frees;
2004
2005         MODEST_DEBUG_BLOCK (
2006                             if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
2007                                     g_print ("ADDED %d/%d (r/t) \n",
2008                                              tny_folder_change_get_new_unread_count (change),
2009                                              tny_folder_change_get_new_all_count (change));
2010                             if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
2011                                     g_print ("ALL COUNT %d\n",
2012                                              tny_folder_change_get_new_all_count (change));
2013                             if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
2014                                     g_print ("UNREAD COUNT %d\n",
2015                                              tny_folder_change_get_new_unread_count (change));
2016                             if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
2017                                     g_print ("EXPUNGED %d/%d (r/t) \n",
2018                                              tny_folder_change_get_new_unread_count (change),
2019                                              tny_folder_change_get_new_all_count (change));
2020                             if (changed & TNY_FOLDER_CHANGE_CHANGED_FOLDER_RENAME)
2021                                     g_print ("FOLDER RENAME\n");
2022                             if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
2023                                     g_print ("MSG RECEIVED %d/%d (r/t) \n",
2024                                              tny_folder_change_get_new_unread_count (change),
2025                                              tny_folder_change_get_new_all_count (change));
2026                             g_print ("---------------------------------------------------\n");
2027                             );
2028
2029         /* Check folder count */
2030         if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
2031             (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
2032
2033                 g_mutex_lock (priv->observers_lock);
2034
2035                 /* Emit signal to evaluate how headers changes affects
2036                    to the window view  */
2037                 g_signal_emit (G_OBJECT(self),
2038                                signals[MSG_COUNT_CHANGED_SIGNAL],
2039                                0, folder, change);
2040
2041                 /* Added or removed headers, so data stored on cliboard are invalid  */
2042                 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
2043                         modest_email_clipboard_clear (priv->clipboard);
2044
2045                 g_mutex_unlock (priv->observers_lock);
2046         }
2047
2048         /* Free */
2049  frees:
2050         if (folder != NULL)
2051                 g_object_unref (folder);
2052 }
2053
2054 gboolean
2055 modest_header_view_is_empty (ModestHeaderView *self)
2056 {
2057         ModestHeaderViewPrivate *priv;
2058
2059         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
2060
2061         priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
2062
2063         return priv->status == HEADER_VIEW_EMPTY;
2064 }
2065
2066 void
2067 modest_header_view_clear (ModestHeaderView *self)
2068 {
2069         g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
2070
2071         modest_header_view_set_folder (self, NULL, FALSE, NULL, NULL, NULL);
2072 }
2073
2074 void
2075 modest_header_view_copy_selection (ModestHeaderView *header_view)
2076 {
2077         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2078
2079         /* Copy selection */
2080         _clipboard_set_selected_data (header_view, FALSE);
2081 }
2082
2083 void
2084 modest_header_view_cut_selection (ModestHeaderView *header_view)
2085 {
2086         ModestHeaderViewPrivate *priv = NULL;
2087         const gchar **hidding = NULL;
2088         guint i, n_selected;
2089
2090         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2091
2092         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2093
2094         /* Copy selection */
2095         _clipboard_set_selected_data (header_view, TRUE);
2096
2097         /* Get hidding ids */
2098         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2099
2100         /* Clear hidding array created by previous cut operation */
2101         _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
2102
2103         /* Copy hidding array */
2104         priv->n_selected = n_selected;
2105         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2106         for (i=0; i < n_selected; i++)
2107                 priv->hidding_ids[i] = g_strdup(hidding[i]);
2108
2109         /* Hide cut headers */
2110         modest_header_view_refilter (header_view);
2111 }
2112
2113
2114
2115
2116 static void
2117 _clipboard_set_selected_data (ModestHeaderView *header_view,
2118                               gboolean delete)
2119 {
2120         ModestHeaderViewPrivate *priv = NULL;
2121         TnyList *headers = NULL;
2122
2123         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2124         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2125
2126         /* Set selected data on clipboard   */
2127         g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
2128         headers = modest_header_view_get_selected_headers (header_view);
2129         modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
2130
2131         /* Free */
2132         g_object_unref (headers);
2133 }
2134
2135 typedef struct {
2136         ModestHeaderView *self;
2137         TnyFolder *folder;
2138 } NotifyFilterInfo;
2139
2140 static gboolean
2141 notify_filter_change (gpointer data)
2142 {
2143         NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2144
2145         g_signal_emit (info->self,
2146                        signals[MSG_COUNT_CHANGED_SIGNAL],
2147                        0, info->folder, NULL);
2148
2149         return FALSE;
2150 }
2151
2152 static void
2153 notify_filter_change_destroy (gpointer data)
2154 {
2155         NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2156         ModestHeaderViewPrivate *priv;
2157
2158         priv = MODEST_HEADER_VIEW_GET_PRIVATE (info->self);
2159         priv->status_timeout = 0;
2160
2161         g_object_unref (info->self);
2162         g_object_unref (info->folder);
2163         g_slice_free (NotifyFilterInfo, info);
2164 }
2165
2166 static gboolean
2167 current_folder_needs_filtering (ModestHeaderViewPrivate *priv)
2168 {
2169         /* For the moment we only need to filter outbox */
2170         return priv->is_outbox;
2171 }
2172
2173 static gboolean
2174 header_match_string (TnyHeader *header, gchar **words)
2175 {
2176         gchar *subject_fold;
2177         gchar *cc_fold;
2178         gchar *bcc_fold;
2179         gchar *to_fold;
2180         gchar *from_fold;
2181
2182         gchar **current_word;
2183         gboolean found;
2184
2185         subject_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_SUBJECT_FOLD);
2186         if (subject_fold == NULL) {
2187                 gchar *subject;
2188                 subject = tny_header_dup_subject (header);
2189                 if (subject != NULL) {
2190                         subject_fold = subject?g_utf8_casefold (subject, -1):NULL;
2191                         g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_SUBJECT_FOLD,
2192                                                 subject_fold, (GDestroyNotify) g_free);
2193                 }
2194                 g_free (subject);
2195         }
2196
2197         from_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_FROM_FOLD);
2198         if (from_fold == NULL) {
2199                 gchar *from;
2200                 from = tny_header_dup_from (header);
2201                 if (from != NULL) {
2202                         from_fold = from?g_utf8_casefold (from, -1):NULL;
2203                         g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_FROM_FOLD,
2204                                                 from_fold, (GDestroyNotify) g_free);
2205                 }
2206                 g_free (from);
2207         }
2208
2209         to_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_TO_FOLD);
2210         if (to_fold == NULL) {
2211                 gchar *to;
2212                 to = tny_header_dup_to (header);
2213                 if (to != NULL) {
2214                         to_fold = to?g_utf8_casefold (to, -1):NULL;
2215                         g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_TO_FOLD,
2216                                                 to_fold, (GDestroyNotify) g_free);
2217                 }
2218                 g_free (to);
2219         }
2220
2221         cc_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_CC_FOLD);
2222         if (cc_fold == NULL) {
2223                 gchar *cc;
2224                 cc = tny_header_dup_cc (header);
2225                 if (cc != NULL) {
2226                         cc_fold = cc?g_utf8_casefold (cc, -1):NULL;
2227                         g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_CC_FOLD,
2228                                                 cc_fold, (GDestroyNotify) g_free);
2229                 }
2230                 g_free (cc);
2231         }
2232
2233         bcc_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_BCC_FOLD);
2234         if (bcc_fold == NULL) {
2235                 gchar *bcc;
2236                 bcc = tny_header_dup_bcc (header);
2237                 if (bcc != NULL) {
2238                         bcc_fold = bcc?g_utf8_casefold (bcc, -1):NULL;
2239                         g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_BCC_FOLD,
2240                                                 bcc_fold, (GDestroyNotify) g_free);
2241                 }
2242                 g_free (bcc);
2243         }
2244
2245         found = TRUE;
2246
2247         for (current_word = words; *current_word != NULL; current_word++) {
2248
2249                 if ((subject_fold && g_strstr_len (subject_fold, -1, *current_word))
2250                     || (cc_fold && g_strstr_len (cc_fold, -1, *current_word))
2251                     || (bcc_fold && g_strstr_len (bcc_fold, -1, *current_word))
2252                     || (to_fold && g_strstr_len (to_fold, -1, *current_word))
2253                     || (from_fold && g_strstr_len (from_fold, -1, *current_word))) {
2254                         found = TRUE;
2255                 } else {
2256                         found = FALSE;
2257                         break;
2258                 }
2259         }
2260
2261         return found;
2262 }
2263
2264 static gboolean
2265 filter_row (GtkTreeModel *model,
2266             GtkTreeIter *iter,
2267             gpointer user_data)
2268 {
2269         ModestHeaderViewPrivate *priv = NULL;
2270         TnyHeaderFlags flags;
2271         TnyHeader *header = NULL;
2272         guint i;
2273         gchar *id = NULL;
2274         gboolean visible = TRUE;
2275         gboolean found = FALSE;
2276         GValue value = {0,};
2277         HeaderViewStatus old_status;
2278
2279         g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
2280         priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2281
2282         /* Get header from model */
2283         gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
2284         header = (TnyHeader *) g_value_get_object (&value);
2285         g_value_unset (&value);
2286         flags = tny_header_get_flags (header);
2287
2288         /* Get message id from header (ensure is a valid id) */
2289         if (!header) {
2290                 visible = FALSE;
2291                 goto frees;
2292         }
2293
2294         /* Hide deleted and mark as deleted heders */
2295         if (flags & TNY_HEADER_FLAG_DELETED ||
2296             flags & TNY_HEADER_FLAG_EXPUNGED) {
2297                 visible = FALSE;
2298                 goto frees;
2299         }
2300
2301         if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_DELETABLE)) {
2302                 if (current_folder_needs_filtering (priv) &&
2303                     modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2304                         visible = FALSE;
2305                         goto frees;
2306                 }
2307         }
2308
2309         if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_MOVEABLE)) {
2310                 if (current_folder_needs_filtering (priv) &&
2311                     modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2312                         visible = FALSE;
2313                         goto frees;
2314                 }
2315         }
2316
2317         if (visible && priv->filter_string) {
2318                 if (!header_match_string (header, priv->filter_string_splitted)) {
2319                         visible = FALSE;
2320                         goto frees;
2321                 }
2322                 if (priv->filter_date_range) {
2323                         if ((tny_header_get_date_sent (TNY_HEADER (header)) < priv->date_range_start) ||
2324                             ((priv->date_range_end != -1) && (tny_header_get_date_sent (TNY_HEADER (header)) > priv->date_range_end))) {
2325                                 visible = FALSE;
2326                                 goto frees;
2327                         }
2328                 }
2329         }
2330
2331         /* If no data on clipboard, return always TRUE */
2332         if (modest_email_clipboard_cleared(priv->clipboard)) {
2333                 visible = TRUE;
2334                 goto frees;
2335         }
2336
2337         /* Check hiding */
2338         if (priv->hidding_ids != NULL) {
2339                 id = tny_header_dup_message_id (header);
2340                 for (i=0; i < priv->n_selected && !found; i++)
2341                         if (priv->hidding_ids[i] != NULL && id != NULL)
2342                                 found = (!strcmp (priv->hidding_ids[i], id));
2343
2344                 visible = !found;
2345                 g_free(id);
2346         }
2347
2348  frees:
2349         old_status = priv->status;
2350         priv->status = ((gboolean) priv->status) && !visible;
2351         if ((priv->notify_status) && (priv->status != old_status)) {
2352                 if (priv->status_timeout)
2353                         g_source_remove (priv->status_timeout);
2354
2355                 if (header) {
2356                         NotifyFilterInfo *info;
2357
2358                         info = g_slice_new0 (NotifyFilterInfo);
2359                         info->self = g_object_ref (G_OBJECT (user_data));
2360                         if (header)
2361                                 info->folder = tny_header_get_folder (header);
2362                         priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
2363                                                                    notify_filter_change,
2364                                                                    info,
2365                                                                    notify_filter_change_destroy);
2366                 }
2367         }
2368         
2369         return visible;
2370 }
2371
2372 static void
2373 _clear_hidding_filter (ModestHeaderView *header_view)
2374 {
2375         ModestHeaderViewPrivate *priv = NULL;
2376         guint i;
2377
2378         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2379         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2380
2381         if (priv->hidding_ids != NULL) {
2382                 for (i=0; i < priv->n_selected; i++)
2383                         g_free (priv->hidding_ids[i]);
2384                 g_free(priv->hidding_ids);
2385         }
2386 }
2387
2388 void
2389 modest_header_view_refilter (ModestHeaderView *header_view)
2390 {
2391         GtkTreeModel *filter_model = NULL;
2392         ModestHeaderViewPrivate *priv = NULL;
2393
2394         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2395         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2396
2397         /* Hide cut headers */
2398         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2399         if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
2400                 priv->status = HEADER_VIEW_INIT;
2401                 modest_header_view_refilter_by_chunks (header_view);
2402         }
2403 }
2404
2405 /*
2406  * Called when an account is removed. If I'm showing a folder of the
2407  * account that has been removed then clear the view
2408  */
2409 static void
2410 on_account_removed (TnyAccountStore *self,
2411                     TnyAccount *account,
2412                     gpointer user_data)
2413 {
2414         ModestHeaderViewPrivate *priv = NULL;
2415
2416         /* Ignore changes in transport accounts */
2417         if (TNY_IS_TRANSPORT_ACCOUNT (account))
2418                 return;
2419
2420         priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2421
2422         if (priv->folder) {
2423                 TnyAccount *my_account;
2424
2425                 if (TNY_IS_MERGE_FOLDER (priv->folder) &&
2426                     tny_folder_get_folder_type (priv->folder) == TNY_FOLDER_TYPE_OUTBOX) {
2427                         ModestTnyAccountStore *acc_store = modest_runtime_get_account_store ();
2428                         my_account = modest_tny_account_store_get_local_folders_account (acc_store);
2429                 } else {
2430                         my_account = tny_folder_get_account (priv->folder);
2431                 }
2432
2433                 if (my_account) {
2434                         if (my_account == account)
2435                                 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
2436                         g_object_unref (my_account);
2437                 }
2438         }
2439 }
2440
2441 void
2442 modest_header_view_add_observer(ModestHeaderView *header_view,
2443                                      ModestHeaderViewObserver *observer)
2444 {
2445         ModestHeaderViewPrivate *priv;
2446
2447         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2448         g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2449
2450         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2451
2452         g_mutex_lock(priv->observer_list_lock);
2453         priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2454         g_mutex_unlock(priv->observer_list_lock);
2455 }
2456
2457 void
2458 modest_header_view_remove_observer(ModestHeaderView *header_view,
2459                                    ModestHeaderViewObserver *observer)
2460 {
2461         ModestHeaderViewPrivate *priv;
2462
2463         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2464         g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2465
2466         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2467
2468         g_mutex_lock(priv->observer_list_lock);
2469         priv->observer_list = g_slist_remove(priv->observer_list, observer);
2470         g_mutex_unlock(priv->observer_list_lock);
2471 }
2472
2473 static void
2474 modest_header_view_notify_observers(ModestHeaderView *header_view,
2475                                     GtkTreeModel *model,
2476                                     const gchar *tny_folder_id)
2477 {
2478         ModestHeaderViewPrivate *priv = NULL;
2479         GSList *iter;
2480         ModestHeaderViewObserver *observer;
2481
2482
2483         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2484
2485         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2486
2487         g_mutex_lock(priv->observer_list_lock);
2488         iter = priv->observer_list;
2489         while(iter != NULL){
2490                 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2491                 modest_header_view_observer_update(observer, model,
2492                                 tny_folder_id);
2493                 iter = g_slist_next(iter);
2494         }
2495         g_mutex_unlock(priv->observer_list_lock);
2496 }
2497
2498 const gchar *
2499 _modest_header_view_get_display_date (ModestHeaderView *self, time_t date)
2500 {
2501         ModestHeaderViewPrivate *priv = NULL;
2502
2503         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
2504         return modest_datetime_formatter_display_datetime (priv->datetime_formatter, date);
2505 }
2506
2507 void
2508 modest_header_view_set_filter (ModestHeaderView *self,
2509                                ModestHeaderViewFilter filter)
2510 {
2511         ModestHeaderViewPrivate *priv;
2512
2513         g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2514         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2515
2516         priv->filter |= filter;
2517
2518         if (current_folder_needs_filtering (priv))
2519                 modest_header_view_refilter (self);
2520 }
2521
2522 void
2523 modest_header_view_unset_filter (ModestHeaderView *self,
2524                                  ModestHeaderViewFilter filter)
2525 {
2526         ModestHeaderViewPrivate *priv;
2527
2528         g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2529         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2530
2531         priv->filter &= ~filter;
2532
2533         if (current_folder_needs_filtering (priv))
2534                 modest_header_view_refilter (self);
2535 }
2536
2537 static void
2538 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
2539 {
2540         if (strcmp ("style", spec->name) == 0) {
2541                 update_style (MODEST_HEADER_VIEW (obj));
2542                 gtk_widget_queue_draw (GTK_WIDGET (obj));
2543         }
2544 }
2545
2546 static void
2547 update_style (ModestHeaderView *self)
2548 {
2549         ModestHeaderViewPrivate *priv;
2550         GdkColor style_color;
2551         GdkColor style_active_color;
2552         PangoAttrList *attr_list;
2553         GtkStyle *style;
2554         PangoAttribute *attr;
2555
2556         g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2557         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2558
2559         /* Set color */
2560
2561         attr_list = pango_attr_list_new ();
2562         if (!gtk_style_lookup_color (gtk_widget_get_style (GTK_WIDGET (self)), "SecondaryTextColor", &style_color)) {
2563                 gdk_color_parse (MODEST_SECONDARY_COLOR, &style_color);
2564         }
2565         priv->secondary_color = style_color;
2566         attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
2567         pango_attr_list_insert (attr_list, attr);
2568
2569         /* set font */
2570         style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
2571                                            (GTK_WIDGET(self)),
2572                                            "SmallSystemFont", NULL,
2573                                            G_TYPE_NONE);
2574         if (style) {
2575                 attr = pango_attr_font_desc_new (pango_font_description_copy
2576                                                  (style->font_desc));
2577                 pango_attr_list_insert (attr_list, attr);
2578
2579                 g_object_set (G_OBJECT (priv->renderer_address),
2580                               "foreground-gdk", &(priv->secondary_color),
2581                               "foreground-set", TRUE,
2582                               "attributes", attr_list,
2583                               NULL);
2584                 g_object_set (G_OBJECT (priv->renderer_date_status),
2585                               "foreground-gdk", &(priv->secondary_color),
2586                               "foreground-set", TRUE,
2587                               "attributes", attr_list,
2588                               NULL);
2589                 pango_attr_list_unref (attr_list);
2590         } else {
2591                 g_object_set (G_OBJECT (priv->renderer_address),
2592                               "foreground-gdk", &(priv->secondary_color),
2593                               "foreground-set", TRUE,
2594                               "scale", PANGO_SCALE_SMALL,
2595                               "scale-set", TRUE,
2596                               NULL);
2597                 g_object_set (G_OBJECT (priv->renderer_date_status),
2598                               "foreground-gdk", &(priv->secondary_color),
2599                               "foreground-set", TRUE,
2600                               "scale", PANGO_SCALE_SMALL,
2601                               "scale-set", TRUE,
2602                               NULL);
2603         }
2604
2605         if (gtk_style_lookup_color (gtk_widget_get_style (GTK_WIDGET (self)), "ActiveTextColor", &style_active_color)) {
2606                 priv->active_color = style_active_color;
2607 #ifdef MODEST_TOOLKIT_HILDON2
2608                 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (TRUE));
2609                 g_object_set_data (G_OBJECT (priv->renderer_subject), ACTIVE_COLOR, &(priv->active_color));
2610 #endif
2611         } else {
2612 #ifdef MODEST_TOOLKIT_HILDON2
2613                 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (FALSE));
2614 #endif
2615         }
2616 }
2617
2618 TnyHeader *
2619 modest_header_view_get_header_at_pos (ModestHeaderView *header_view,
2620                                       gint initial_x,
2621                                       gint initial_y)
2622 {
2623         GtkTreePath *path;
2624         GtkTreeModel *tree_model;
2625         GtkTreeIter iter;
2626         TnyHeader *header;
2627
2628         /* Get tree path */
2629         if (!gtk_tree_view_get_dest_row_at_pos ((GtkTreeView *) header_view,
2630                                                 initial_x,
2631                                                 initial_y,
2632                                                 &path,
2633                                                 NULL))
2634                 return NULL;
2635
2636         /* Get model */
2637         tree_model = gtk_tree_view_get_model ((GtkTreeView *) header_view);
2638         if (!gtk_tree_model_get_iter (tree_model, &iter, path))
2639                 return NULL;
2640
2641         /* Get header */
2642         gtk_tree_model_get (tree_model, &iter,
2643                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2644                             &header, -1);
2645
2646         return header;
2647 }
2648
2649 static gboolean
2650 parse_date_side (const gchar *string, time_t *date_side)
2651 {
2652         gchar *today;
2653         gchar *yesterday;
2654         gchar *casefold;
2655         GDate *date;
2656         gboolean result = FALSE;
2657
2658         if (string && string[0] == '\0') {
2659                 *date_side = 0;
2660                 return TRUE;
2661         }
2662
2663         casefold = g_utf8_casefold (string, -1);
2664         today = g_utf8_casefold (dgettext ("gtk20", "Today"), -1);
2665         yesterday = g_utf8_casefold (dgettext ("gtk20", "Yesterday"), -1);
2666         date = g_date_new ();
2667
2668         if (g_utf8_collate (casefold, today) == 0) {
2669                 *date_side = time (NULL);
2670                 result = TRUE;
2671                 goto frees;
2672         }
2673
2674         if (g_utf8_collate (casefold, yesterday) == 0) {
2675                 *date_side = time (NULL) - 24*60*60;
2676                 result = TRUE;
2677                 goto frees;
2678         }
2679
2680         g_date_set_parse (date, string);
2681         if (g_date_valid (date)) {
2682                 struct tm tm = {0};
2683                 g_date_to_struct_tm (date, &tm);
2684                 *date_side = mktime (&tm);
2685                 
2686                 result = TRUE;
2687                 goto frees;
2688         }
2689 frees:
2690         g_free (today);
2691         g_free (yesterday);
2692         g_free (casefold);
2693         g_date_free (date);
2694
2695         return result;
2696 }
2697
2698 static gboolean
2699 parse_date_range (const gchar *string, time_t *date_range_start, time_t *date_range_end)
2700 {
2701         gchar ** parts;
2702         gboolean valid;
2703
2704         parts = g_strsplit (string, "..", 2);
2705         valid = TRUE;
2706
2707         if (g_strv_length (parts) != 2) {
2708                 valid = FALSE;
2709                 goto frees;
2710                 g_strfreev (parts);
2711                 return FALSE;
2712         }
2713
2714         if (!parse_date_side (parts[0], date_range_start)) {
2715                 valid = FALSE;
2716                 goto frees;
2717         }
2718
2719         if (parse_date_side (parts[1], date_range_end)) {
2720                 if (*date_range_end == 0) {
2721                         *date_range_end = (time_t) -1;
2722                 } else {
2723                         *date_range_end += (24*60*60 - 1);
2724                 }
2725         } else {
2726                 valid = FALSE;
2727                 goto frees;
2728         }
2729                 
2730 frees:
2731         g_strfreev (parts);
2732         return valid;
2733 }
2734
2735 void
2736 modest_header_view_set_filter_string (ModestHeaderView *self,
2737                                       const gchar *filter_string)
2738 {
2739         ModestHeaderViewPrivate *priv;
2740
2741         g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2742         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2743
2744         if (priv->filter_string)
2745                 g_free (priv->filter_string);
2746
2747         priv->filter_string = g_strdup (filter_string);
2748         priv->filter_date_range = FALSE;
2749
2750         if (priv->filter_string_splitted) {
2751                 g_strfreev (priv->filter_string_splitted);
2752                 priv->filter_string_splitted = NULL;
2753         }
2754
2755         if (priv->filter_string) {
2756                 gchar **split, **current, **current_target;
2757
2758                 split = g_strsplit (priv->filter_string, " ", 0);
2759
2760                 priv->filter_string_splitted = g_malloc0 (sizeof (gchar *)*(g_strv_length (split) + 1));
2761                 current_target = priv->filter_string_splitted;
2762                 for (current = split; *current != 0; current ++) {
2763                         gboolean has_date_range = FALSE;;
2764                         if (g_strstr_len (*current, -1, "..") && strcmp(*current, "..")) {
2765                                 time_t range_start, range_end;
2766                                 /* It contains .. but it's not ".." so it may be a date range */
2767                                 if (parse_date_range (*current, &range_start, &range_end)) {
2768                                         priv->filter_date_range = TRUE;
2769                                         has_date_range = TRUE;
2770                                         priv->date_range_start = range_start;
2771                                         priv->date_range_end = range_end;
2772                                 }
2773                         }
2774                         if (!has_date_range) {
2775                                 *current_target = g_utf8_casefold (*current, -1);
2776                                 current_target++;
2777                         }
2778                 }
2779                 *current_target = '\0';
2780                 g_strfreev (split);
2781         }
2782         modest_header_view_refilter (MODEST_HEADER_VIEW (self));
2783 }
2784
2785 #ifdef MODEST_TOOLKIT_HILDON2
2786 static gboolean
2787 on_live_search_timeout (ModestHeaderView *self)
2788 {
2789         const gchar *needle;
2790         ModestHeaderViewPrivate *priv;
2791
2792         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2793
2794         needle = hildon_live_search_get_text ((HildonLiveSearch *) priv->live_search);
2795         if (needle && needle[0] != '\0') {
2796                 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), needle);
2797         } else {
2798                 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), NULL);
2799         }
2800
2801         priv->live_search_timeout = 0;
2802
2803         return FALSE;
2804 }
2805
2806 static gboolean
2807 on_live_search_refilter (HildonLiveSearch *livesearch,
2808                          ModestHeaderView *self)
2809 {
2810         ModestHeaderViewPrivate *priv;
2811         GtkTreeModel *model, *sortable, *filter;
2812
2813         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2814
2815         if (priv->live_search_timeout > 0) {
2816                 g_source_remove (priv->live_search_timeout);
2817                 priv->live_search_timeout = 0;
2818         }
2819
2820         model = NULL;
2821         filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2822         if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2823                 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2824                 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2825                         model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2826                 }
2827         }
2828
2829         if (model && tny_list_get_length (TNY_LIST (model)) > 250) {
2830                 priv->live_search_timeout = g_timeout_add (1000, (GSourceFunc) on_live_search_timeout, self);
2831         } else {
2832                 on_live_search_timeout (self);
2833         }
2834
2835         return TRUE;
2836 }
2837
2838 GtkWidget *
2839 modest_header_view_setup_live_search (ModestHeaderView *self)
2840 {
2841         ModestHeaderViewPrivate *priv;
2842
2843         g_return_val_if_fail (MODEST_IS_HEADER_VIEW (self), NULL);
2844         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2845         priv->live_search = hildon_live_search_new ();
2846
2847         g_signal_connect (G_OBJECT (priv->live_search), "refilter", G_CALLBACK (on_live_search_refilter), self);
2848
2849         return priv->live_search;
2850 }
2851 #endif
2852
2853 static gboolean
2854 refilter_idle_handler (gpointer userdata)
2855 {
2856         ModestHeaderView *self = MODEST_HEADER_VIEW (userdata);
2857         ModestHeaderViewPrivate *priv;
2858         GtkTreeModel *filter_model;
2859         GtkTreeModel *filtered_model;
2860         gint i;
2861         gboolean has_more;
2862
2863         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2864         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2865         filtered_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
2866
2867         if (filtered_model != priv->filtered_model) {
2868                 priv->refilter_handler_id = 0;
2869                 priv->filtered_model = NULL;
2870                 return FALSE;
2871         }
2872
2873         if (!gtk_tree_model_sort_iter_is_valid (GTK_TREE_MODEL_SORT (filtered_model), &(priv->refilter_iter))) {
2874                 priv->refilter_handler_id = 0;
2875                 priv->filtered_model = NULL;
2876                 modest_header_view_refilter_by_chunks (self);
2877                 return FALSE;
2878         }
2879
2880         i = 0;
2881         do {
2882                 GtkTreePath *path;
2883                 path = gtk_tree_model_get_path (priv->filtered_model, &(priv->refilter_iter));
2884                 gtk_tree_model_row_changed (priv->filtered_model, path, &(priv->refilter_iter));
2885                 gtk_tree_path_free (path);
2886                 i++;
2887
2888                 has_more = gtk_tree_model_iter_next (priv->filtered_model, &(priv->refilter_iter));
2889         } while (i < 100 && has_more);
2890
2891         if (has_more) {
2892                 return TRUE;
2893         } else {
2894                 priv->filtered_model = NULL;
2895                 priv->refilter_handler_id = 0;
2896                 return FALSE;
2897         }
2898 }
2899
2900 static void
2901 modest_header_view_refilter_by_chunks (ModestHeaderView *self)
2902 {
2903         ModestHeaderViewPrivate *priv;
2904         GtkTreeModel *filter_model;
2905
2906         g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2907         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2908
2909         if (priv->refilter_handler_id > 0) {
2910                 g_source_remove (priv->refilter_handler_id);
2911                 priv->refilter_handler_id = 0;
2912         }
2913
2914         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2915         priv->filtered_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
2916
2917         if (gtk_tree_model_get_iter_first (priv->filtered_model, &(priv->refilter_iter))) {
2918                 priv->refilter_handler_id = g_idle_add (refilter_idle_handler, self);
2919         }
2920 }