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