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